From 95b2f990398362b200d1a4c8322e19af654e89bb Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Sun, 30 Aug 2026 08:16:58 -0400 Subject: [PATCH] =?UTF-8?q?feat(skills):=20open-threads=20=E2=80=94=20swee?= =?UTF-8?q?p=20recent=20sessions,=20propose=20a=20consolidated=20spawn=20p?= =?UTF-8?q?lan=20(#646)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fleet's aggregated chat DB holds weeks of unclosed threads; nothing surfaces them. This skill codifies the sweep-and-resume pass: a read-only SQLite recipe (top-level sessions in a window, last assistant text per session, pending todos), a five-bucket classification (BLOCKED-ON-USER / INTERRUPTED / PARKED / AWAITING-REVIEW / STALE), consolidation of spawnable threads into at most 3-4 THEME-scoped sessions with self-contained seed prompts, and — only on explicit confirmation — one amicode_session call per theme (#641's spawn tool; graceful inline fallback when it isn't deployed). The DB recipe is verified verbatim against the live fleet server DB (97 sessions in a 14-day window → 60 after the noise filter; todo and last-text extraction working). Blocked-on-user and awaiting-review threads are deliberately never spawn candidates — a fresh session cannot press the button only the human can press. --- .../extension/skills/open-threads/SKILL.md | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 packages/extension/skills/open-threads/SKILL.md diff --git a/packages/extension/skills/open-threads/SKILL.md b/packages/extension/skills/open-threads/SKILL.md new file mode 100644 index 00000000..38605894 --- /dev/null +++ b/packages/extension/skills/open-threads/SKILL.md @@ -0,0 +1,144 @@ +--- +name: open-threads +description: Sweep recent sessions across the fleet's chat DB, classify their open threads (blocked-on-user / interrupted / parked / awaiting-review / stale), and propose a small consolidated set of fresh sessions to continue the work — spawned on confirmation via the amicode_session tool. Use at session start, on request ("rehash the last two weeks"), or when resuming after time away. +agents: [researcher, director] +surface: public +--- + +Sweep what the fleet actually did, surface what is still open, and turn the +spawnable parts into a small set of fresh sessions that continue the work. +The unit of resumption is a THEME, not a thread. + +## Usage + +`/open-threads` — sweep the last 14 days. +`/open-threads ` — custom window (e.g. `/open-threads 7`). +`/open-threads --report` — report only; skip the spawn proposal. + +The argument is: $ARGUMENTS + +## Instructions + +### Step 0: Find the chat DB (read-only, always) + +The canonical Amicode server aggregates every fleet device's sessions into one +SQLite DB. Resolve it in this order: the `OPENCODE_DB` env if set, else +`~/.local/share/opencode/opencode.db`. On a fleet client, run the sweep ON the +canonical server (see the `fleet` skill for which machine holds the role) — +never copy the DB around. Open it READ-ONLY (the server owns it): + +```python +con = sqlite3.connect("file:?mode=ro", uri=True) +``` + +If the DB is locked or missing, report that and stop — do not retry with a +writable connection. + +### Step 1: Pull top-level sessions in the window + +`window_days` = argument or 14. Run one query for the session list, one for +pending todos. Then, per session, pull the LAST assistant message that carries +text (join `message` → `part`, keep `type == "text"` parts; truncate to ~3 KB). + +```python +import sqlite3, datetime, json + +con = sqlite3.connect("file:?mode=ro", uri=True, timeout=5) +since = (datetime.datetime.now() - datetime.timedelta(days=window_days)).timestamp() * 1000 + +sessions = con.execute(""" + select id, title, datetime(time_created/1000,'unixepoch'), directory + from session + where parent_id is null and time_archived is null and time_created > ? + order by time_created +""", (since,)).fetchall() + +todos = con.execute(""" + select s.title, t.content from todo t join session s on s.id = t.session_id + where t.status != 'completed' order by t.time_created +""").fetchall() + +def last_text(session_id): + for (mid,) in con.execute(""" + select m.id from message m + where m.session_id = ? and json_extract(m.data,'$.role')='assistant' + order by m.time_created desc limit 8 + """, (session_id,)): + parts = con.execute("select data from part where message_id=? order by time_created", (mid,)).fetchall() + text = " ".join(json.loads(p[0]).get("text", "") + for p in parts if json.loads(p[0]).get("type") == "text") + if text.strip(): + return text[:3000] + return "" +``` + +Filter NOISE titles before classifying — sessions named `Greeting`, `New +session - `, `test`, and obvious scratch names carry no threads. Subagent +sessions are already excluded by `parent_id is null`. + +### Step 2: Classify every surviving session + +Read the last assistant text (the ending usually says it outright: "waiting +on", "next up", "say the word", a mid-action line with no wrap-up) and the +pending todos that name the session. Bucket each thread: + +- **BLOCKED-ON-USER** — a decision, key, sign-off, or merge the human owes. + These are REPORT items, never spawn candidates: a fresh session cannot + press the button that only the user can press. +- **INTERRUPTED** — the session died mid-action (last text is an unfinished + step, no verdict, no wrap-up). Prime spawn candidates. +- **PARKED** — ended at a documented next step (campaign ledger "next queue", + a pre-authorized experiment, a named follow-up). Spawn candidates; the + parked step is the seed prompt. +- **AWAITING-REVIEW** — green PRs / cards waiting on a human. Report items. + If a thread names a specific PR, verify its current state with one `gh pr + view` call before reporting — sessions age faster than GitHub. +- **STALE** — decisions that expired, deadlines long past, month-old + "awaiting decision" items. Report as "retire unless you say otherwise". + +### Step 3: Consolidate into a spawn plan + +The smart part is the MERGING. One session per theme, never one per thread: + +- Group related threads (same repo, same campaign, same incident) into ONE + session whose prompt lists every thread it owns. +- Cap the plan at 3-4 sessions total. If more themes survive the sweep, put + the overflow on the report as "not spooled — say the word". +- Each proposed session needs: a short title (it becomes a tab label), and a + first prompt that is SELF-CONTAINED — the child starts with zero memory of + this sweep, so the prompt must carry the pointers verbatim: file paths, + PR/issue numbers, run dirs, branch names, and the parked next step, in + plain text. A prompt that says "continue the warp work" is worthless; one + that says "PR #150 awaits merge review; then Piccolo#321 was interrupted + mid-debug on a stale precompile at " is a working session. +- `mode` is `fresh` for consolidation spawns. `fork` is the exception — only + when continuing a thread requires the parent's full transcript rather than + a state summary, and say so in the plan when you use it. + +### Step 4: Report, then confirm before spawning + +Deliver the report first: the classified table (thread, session date, what's +open, bucket), BLOCKED-ON-USER and AWAITING-REVIEW items called out as +explicitly yours-not-spawnable, and the STALE retire list. Then present the +spawn plan and ask via the `question` tool (`multiple: true`, one option per +proposed session, plus a "spawn nothing" option). NEVER spawn without an +explicit selection — every spawned session starts its first turn immediately +on the user's model budget, and the tool hard-caps at 4 per call anyway. + +### Step 5: Spawn + +For each confirmed theme, one `amicode_session` call: `prompt` = the +self-contained seed from Step 3, `title` = the tab label, `count: 1`. The +parent pane auto-opens the children as background tabs. + +**If the `amicode_session` tool is not available** (not yet deployed on this +install, or an older extension): say so plainly, deliver the report, and +offer to run the highest-priority theme inline in THIS session instead — do +not pretend the spawn happened. + +### Report format + +Skimmable, grouped by bucket, newest first. Thread lines carry: date, session +title, one line on what's open. The spawn plan is a numbered list of proposed +sessions with their seed-prompt summaries — the full prompts are what you +pass to the tool, not what you print in chat.