Skip to content

Skills as capabilities: the queue and the routines as their own skills, the file-store code out of skill-branches, agent-driver per driver #1750

Description

@suleimansh

Next round after #1749, per Discord (08-30): a skill is a capability, and no skill depends on another skill's code. Points 1–3 and 6–8 I'm building unless you object. 4 and 5 are proposals — say yes or change them before PR (b).

  1. Three skills, no code between them.

    • tickets — tickets, plans, claims.
    • agent-queueTODO_AGENTS.md. An entry is a prompt for a future agent; a ticket link is one kind of entry. Nobody waits for a queued agent — your "no orchestration" case.
    • agent-routines — scheduled agentic work. New; today hard-coded in the daemon.

    Everything lives on one shared ai-data branch, a path per skill — tickets/, TODO_AGENTS.md, routines/ — with the framework's archives (agents/) beside them; today's tickets and agents-logs branches fold into it (decided on Discord). One bookkeeping branch per project, checked out once at .branches/ai-data. The daemon still starts everything, it just reads the files through the skills.

    Not in this round: a skill-github (the GitHub import stays the framework's update-tickets preset — an add-on on top of tickets, not something tickets needs), and dependency management between skills (the one dependency today, tickets → agent-queue, is a sentence in the tickets SKILL.md).

  2. Names. @gemstack/skill-agent-queue, @gemstack/skill-agent-routines; skill = command = branch = agent-queue / agent-routines. Same rule as skill-branchesbranches. You wrote skills-; that rename is one commit, say if you want it.

  3. @gemstack/file-branch. The branch-as-file-store code leaves skill-branches into a small library (no SKILL.md). All four skills depend on it, none on each other. tickets stops reading .branches/agent-<id>: the daemon passes the agent its id as AGENT_ID; without it, the holder is the branch name. (Or a per-checkout agent.id git config, if you'd rather have it in git.)

  4. The routines file (proposal). routines/<name>.md under routines/ on ai-data: an optional Every: 7d line, then the prompt. Beside it <name>.lock.md (running) and <name>.ran.md (last start). One rule: one run at a time across machines, and the least-recently-run routine that is due goes next. That replaces the rotation index, the triage-only lock and the per-machine maintenance.json. The daemon seeds today's five routines once per project; after that the file is the truth. Trade-off: the five prompts become per-project copies — a preset you improve later does not reach a project that already has its file. It's your prompt workflow, so say if that's not acceptable. And one guard from stress-testing the plan: a routine an agent adds is born Paused: yes — recurring spend gets the veto a queue entry has (a human unpauses); a human's agent-routines add runs normally.

  5. "Plan tickets" stops fanning out by itself (proposal). It queues one entry per ticket — your "Plan all unplanned tickets" example — and the drain works them N at a time under the claims it already takes. One mechanism; the fan-out code goes. Goal: fire 10 Spike & plan agents concurrently #1327 survives (same concurrency); planning takes one extra sweep, and Run now shows entries first, agents on the next sweep.

  6. agent-driver per driver. agent-driver = the contract (types, CLI runner, fake); @agent-driver/claude (this device and GitHub Actions — same CLI); @agent-driver/codex. The cloud driver stays in the framework (browser bridge).

  7. Three PRs, each merged and cut over alone: (a) file-branch + the queue split, (b) routines, (c) the driver split. Publish after each merge: (a) file-branch, skill-branches, skill-tickets, skill-agent-queue; (b) skill-agent-routines; (c) agent-driver, @agent-driver/claude, @agent-driver/codex.

  8. Loading stays temporary (symlinks + prompt bridge) until use-npm-skills commits skills into the repo (Commit skills into the repo; remove the symlink mechanism brillout/use-npm-skills#3 is open as I write). The switch is one small PR: a dependency line per skill, delete the link code and the bridges.

Design points stress-tested one by one — each with the option not taken, so the alternatives are on record:

  • Branch layout: one shared ai-data, a path per skill — over one branch per skill (decided on Discord).
  • Who a claim names: one generic AGENT_ID env var any skill can read — over a per-skill variable; same fact told once, and the lock content is identical either way, so switching later is one line.
  • Queue filename: TODO_AGENTS.md stays — over renaming while nothing is published; the name is in every preset and habit.
  • Removing a queue entry: agent-queue done is the only verb, whatever the reason — over a second remove verb with identical behaviour.
  • Routine on/off: stays the per-machine checkbox (autoPmOptOut) — over a pause line in the file changing what the checkbox means; that line stays a natural follow-up.
  • Agent-added routines are born Paused: yes, a human unpauses — over running immediately (recurring spend, no veto) and over refusing agents outright (which would also block an agent a human told to add one).
  • A plan entry on the queue is recognized by its text, Create tickets/<x>.plan.md, written down as a format rule in the queue skill and the preset — over a link-label marker.
  • <name>.ran.md stamps the start, not the success: pacing bounds spend, so a failing routine is throttled rather than retried every sweep (the drain learned this once, A drain that ends with no commits strands its ticket lock #1583); a failed start doesn't stamp.
  • The name ai-data in code is exported once by @gemstack/file-branch (DATA_BRANCH) — over four hardcoded copies; every skill already depends on the library.
  • The routines seed fires once: only when no commit on ai-data ever touched routines/ — git history as the marker, over a marker file, which dies in the very delete it must survive (both behaviours verified in a throwaway repo).

The 08-30 comments on #1725 / #1733 / #1748 still say "one package"; I'll update them once this is agreed.


🤖 curated · Fable 5, effort high — the plan, as the agent wrote it after reading the code on main (ca5aa4b); I read it and I stand behind the recommendations above.

The plan

What it is. The round after #1748. Three capabilities as three npm packages with a SKILL.md and a command each, one library package underneath them, and the daemon and dashboard as the caller that composes them. The rule that decides every split below: a skill's code depends on git and on file-branch, never on another skill; what two skills have to agree on (a queue entry that links a ticket) is a convention in their SKILL.mds, followed by the agent and by the caller, not an import.

The code graph after the round:

@gemstack/file-branch          a branch as a file store (library: no SKILL.md, no command)
  ▲          ▲            ▲             ▲
skill-branches  skill-tickets  skill-agent-queue  skill-agent-routines
  ▲          ▲            ▲             ▲
framework (the caller: daemon, dashboard, presets)

agent-driver (the contract)  ◄──  @agent-driver/claude, @agent-driver/codex  ◄──  framework

What the code says today (the facts the plan rests on):

  • packages/skill-tickets is tickets and queue: queue.ts (151 lines: parse, insert into a ## Priority N section, remove, readQueue, queueAdd, queueDone) is its own file, and the seam to the tickets is two pure functions in names.tsticketFromQueueEntry (the tickets/<file> link an entry carries) and queuePriorityForTicket (a ticket's Priority: → its queue section) — plus the CLI's queue add --ticket <file>, which reads the ticket to build the link and pick the section. The queue file sits at the root of the tickets branch beside tickets/; syncTickets seeds it (store.ts).
  • skill-tickets depends on skill-branches for: the file-store primitive (file-branch.ts, 461 lines: persistent checkout under .branches/<branch>, the serialized write cycle, the eager pull, reads off a ref, the detached one-shot write the command uses), the git runner (git.ts), excludeFromGit, BRANCHES_DIR, and — the one real capability leak — holder.ts, which knows that a checkout at .branches/agent-<id> means "the holder is agent <id>" (agentIdFromWorktreeDir, isAgentBranch). Everything but the last is the file store; the last is the branches skill's layout, read by the tickets skill.
  • The daemon's routines are AUTO_PM_JOBS in auto-pm.ts (1193 lines): four rotation jobs rendered from presets (update-tickets, triage-quick, triage-consensual, plan-tickets), the drain outside the rotation, the maintenance sweep outside it on a calendar. Per-job data: lock (only the two triage jobs), fansOut (only plan-tickets), drains + autoMerge (only the drain). The sweep keeps the rotation index in memory per project (nextJob), the maintenance calendar in .the-framework/maintenance.json (sweptAt, per machine — the code comments that a machine rebooted daily never reaches its interval), and the routine locks as routines/<name>.lock.md on agents-logs (routine-locks.ts, 170 lines, with the boot-time release of a dead daemon's locks). AUTO_PM_ROUTINES is what the dashboard lists (checkbox per routine = the autoPmOptOut preference, a list of routine names; Run now = wakeAutoPm({ only }) where only is 'drain' | 'plan' | { lock }).
  • The plan fan-out (Goal: fire 10 Spike & plan agents concurrently #1327): the sweep asks the tickets for candidates (planCandidates: unplanned, unclaimed, by priority), mints agent ids, claims them (lockPlans), and starts one pinnedPlanJob per ticket, whose appended text tells the agent to plan exactly that ticket. The stock plan_tickets.md preset, which the same routine runs when the fan-out is not wired, tells one agent to queue "Create tickets/.plan.md" entries. The drain already fans out per entry, with a ticket claim per linked entry (lockDrains).
  • Where the tickets skill is named: the daemon (daemon-runtime.ts: tickets on every agent's PATH, CHECKOUT_SKILLS links its SKILL.md), system-prompt.ts (the TICKETS_YOURSELF + TICKETS_SKILL bridge for agents outside a daemon-made checkout), scripts/gen-prompts.mjs (compiles the skill's SKILL.md into the bridge), 12 presets and 3 prompts (list in §6), dashboard-rpc/control.ts (queue a ticket, queue a plan, release a claim), dashboard/tickets.ts, todo-loop.ts (the in-session queue loop), layout.ts (the LAYOUT file: tickets-branch, queue-file).
  • agent-driver (3162 lines) is one package with four drivers behind one seam. Its internal graph: types.ts (the contract), session-support.ts (makeEmit, framing), cli-session.ts + child-registry.ts (spawn a CLI, parse its stream, reap the tree), fake.ts; claude-code.ts + claude-code-quota.ts on those; codex.ts on those; actions.ts + actions-zip.ts on claude-code.ts's StreamJsonParser (it replays a Claude Code run from a workflow artifact). The framework builds them in driver-cli.ts (claudeClaudeCodeDriver, codexCodexDriver), target-driver.ts (actionsActionsDriver, web → the framework's own CloudDriver, id: 'claude-web', which drives a claude.ai session through the Chrome extension), dashboard/quota.ts (new ClaudeCodeDriver().readQuota), and 27 files import its types. On npm: agent-driver@0.0.0 is a placeholder; @gemstack/skill-branches and @gemstack/skill-tickets are not published (404); the @agent-driver org is ours.
  • use-npm-skills: the repo has three commits (08-29), npm has 0.0.0, and Commit skills into the repo; remove the symlink mechanism brillout/use-npm-skills#3 ("Commit skills into the repo; remove the symlink mechanism", v0.2.0) is open with no comments. The temporary loading (SkillLink/skills on createCheckout, tickets_yourself.md) is already labelled for it.
  • origin/tickets today: tickets/ and a TODO_AGENTS.md of two empty priority headings (31 bytes). origin/agents-logs: agents/ only — no routines/ right now, since no routine is running. So the cutovers below move nothing but headings.

1. Names

package directory skill (name:) command on ai-data
tickets @gemstack/skill-tickets packages/skill-tickets tickets tickets tickets/
queue @gemstack/skill-agent-queue packages/skill-agent-queue agent-queue agent-queue TODO_AGENTS.md
routines @gemstack/skill-agent-routines packages/skill-agent-routines agent-routines agent-routines routines/
file store @gemstack/file-branch packages/file-branch the name itself: DATA_BRANCH

One rule, already in force for branches and tickets: the skill is the package name without skill-, and the command is the skill's name. The files all live on the one shared ai-data branch (decided on Discord), each skill owning its path there; the framework's archives sit beside them at agents/, so the agents-logs branch goes too. agent-queue rather than queue because the command is on every agent's PATH and the name is what the agent reads in a preset ("use the agent-queue skill"): a bare queue says nothing about whose. The Discord spelling skills-agent-queue differs only in the prefix; the prefix is the two existing packages' and a rename before publishing costs one commit.

2. @gemstack/file-branch

What moves. skill-branches/src/file-branch.ts (+ test, + SPEC) as it is; git.ts (the runner, the timeout, gitReason, checkoutRoot, isGitRepo, pushBranch) and git-exclude.ts with it, because the file store is built on them and a library must not reach up into a skill for its git runner; BRANCHES_DIR = '.branches' too — it is the file store's convention as much as the checkouts' (fileBranchPath(repo, branch) = .branches/<branch>). skill-branches then imports the runner, the exclude and the constant from file-branch — the branches skill is a consumer of the library like the other three, and keeps its own worktree.ts, reclaim.ts, branch-names.ts, checkout.ts, skill-links.ts, cli.ts.

Why a package and not "into the queue skill". Three skills and the framework write files to a branch (tickets, agent-queue, agent-routines, agents-logs). Inside any one skill, the other two would depend on that skill — the dependency the round exists to remove. A library with no SKILL.md is not a capability, so depending on it says nothing false.

Public surface = today's file-branch.ts exports (fileBranchPath, fileBranchRepo, ensureFileBranch, withFileBranch, pullFileBranch, readBranchFile, listBranchDir, openBranchReader, writeFileBranchDetached, nodeBranchFileFs, the types) plus the git runner (GitRunner, nodeGitRunner, GitTimeoutError, isGitTimeout, gitReason, checkoutRoot, isGitRepo, repoHasRemote), excludeFromGit, BRANCHES_DIR, and DATA_BRANCH = 'ai-data' — the shared data branch's name, defined once where every skill already looks. No CLI, no bin. Version 0.0.1.

3. @gemstack/skill-agent-queue

What it is. "Agents can add future agentic work." TODO_AGENTS.md at the root of the shared ai-data branch: markdown list items under ## Priority N headings, first within a band first to be taken; an entry is text — a prompt a future agent will be started with — and a link back to a ticket ([title](tickets/<file>)) is one kind of text the queue does not interpret. A project with no tickets can use it. What sets it apart from a sub-agent: nobody orchestrates a queued agent — the entry waits, and whoever runs the project's daemon starts it when there is a turn, with no parent holding the result. Done means deleted (#1748 point 3, unchanged).

Package (mirrors the two existing ones): SKILL.md, SPEC.md, bin/agent-queue, src/names.ts (QUEUE_FILE = 'TODO_AGENTS.md' at the branch root; the branch is file-branch's DATA_BRANCH), src/queue.ts (moved from skill-tickets verbatim: parseQueueEntries, appendQueueEntry, insertQueueEntry, removeQueueEntry, readQueue, readQueueEntries, queueAdd, queueDone, bound to the new branch), src/store.ts (syncQueue: ensure the branch and its checkout, seed an empty file, pull — the queue half of today's syncTickets; no repository-root link: the queue is one file, and the dashboard and the command show it), src/cli.ts, src/bin-dir.ts, src/index.ts. Depends on @gemstack/file-branch only.

Command (the queue subcommands of tickets, one level up, same contract — JSON out, exit 0/1/2, detached write to origin's tip):

agent-queue                          the open entries, in order of work
agent-queue add <text> [--priority N]   put an entry on the queue, in its priority section
agent-queue done <text>              take an entry off: done means deleted

--ticket goes: it read a ticket to build the link and pick the section, which is the tickets skill's knowledge. The entry for a ticket is written by whoever knows the ticket (the agent, told how by the tickets SKILL.md; the dashboard, through the tickets library) as agent-queue add "[<title>](tickets/<file>)" --priority <N> — the link form the format has always shown (- [Succinct description](/link-for-more-details)).

SKILL.md: the "Read"/"Change" sections and the queue format from today's tickets SKILL.md, rewritten around one file on one branch; product-free.

What the tickets skill keeps of the queue. ticketFromQueueEntry and queuePriorityForTicket stay in skill-tickets/names.ts — they are ticket knowledge (what a link into tickets/ is; what a ticket's priority means on a 0–10 queue). The tickets SKILL.md gains three lines under "Queue a ticket": the entry form above, the priority from the ticket's Priority: line, agent-queue done when the work is published — i.e. the tickets skill uses the queue capability by telling the agent to, which is the dependency the two skills have, and the one a later "requires: agent-queue" line will declare.

What the daemon keeps (all of it, composing the two libraries; nothing new): readQueueEntries for the sweep's decision (daemon-services.ts:217) and for nextQueuedTicket/the in-session queue loop (todo-loop.ts); queueDone at settle (promote) and in the loop; queueAdd from the dashboard (control.ts:361/380, which builds the ticket link with queuePriorityForTicket from the tickets library); ticketFromQueueEntry to know which ticket a drain implements and to claim it (lockDrains). The drain job, its pinned prompt (pinnedDrainJob, wording changed to name both skills), autoMerge, concurrency, cooldown, quota gate, the endedDry set: unchanged. syncProjectData converges the one ai-data branch instead of today's two, one project error.

Cutover: none of its own — the queue file already sits beside tickets/ on the branch the rename turns into ai-data (§9).

4. skill-tickets after the split

Loses queue.ts, the queue command, QUEUE_FILE, the queue seed in syncTickets, the --ticket flag. Keeps tickets, plans, claims, meta.json, the root tickets link, and the two ticket-side queue functions above. Its names.ts drops TICKETS_BRANCH for file-branch's DATA_BRANCH; the persistent checkout is .branches/ai-data, and the root tickets link points into it. SKILL.md: the queue section becomes the "Queue a ticket" note; the format block for the queue goes (it is the queue skill's).

Identity without the branches skill. holder.ts today: inside .branches/agent-<id> the holder is <id> (survives branches name, which renames the branch but not the directory); elsewhere the branch name. The first half is the branches skill's layout read by the tickets skill — the leak. Replacement: the holder is AGENT_ID from the environment when the process that started the agent set it, else the current branch name. The daemon already builds every agent's environment (daemon-runtime.ts:113, the PATH with the two commands) and mints the id before the claim (mintAgentIds, #1748), so it sets AGENT_ID=<id> there; a cloud session, an Actions runner, a person in a clone claim as their branch. Same lock content as today (CLAIMED: <agent id>), same dashboard resolution (lockedByAgent), and skill-tickets imports nothing from skill-branches. SKILL.md says it in one line: "you claim as AGENT_ID if the process that started you set it, else as your branch". The git-shaped alternative, if the environment is not wanted as glue: skill-branches sets agent.id=<id> in the checkout's worktree config at createCheckout (needs extensions.worktreeConfig on the repo, once), and holder.ts reads git config agent.id first — one more repo setting and two git calls where the environment costs one read; the same name agreed between two skills either way.

5. @gemstack/skill-agent-routines

What it is. "Agents can add scheduled agentic work." A routine is a prompt that is started again and again, on a project, by whoever runs the project's daemon; the skill is the file that lists them, the state of each, the command that adds and removes them, and the library a caller reads them with. The caller decides when a turn is available (idle, quota, queue empty — the framework's policy today); the skill decides which routine is due and takes the turn.

The file(s), under routines/ on the ai-data branch:

routines/<name>.md          the routine: an optional header, then the prompt
routines/<name>.lock.md     a run is going: `CLAIMED: <host>` / `SINCE: <iso>` (today's file, today's content)
routines/<name>.ran.md      the last start: `RAN: <iso>`

routines/<name>.md:

Every: 7d [optional: at most this often — 30m, 12h, 7d; absent: whenever the caller has an idle turn]

# Sweep the codebase for maintenance work

Analyze the entire codebase and look for opportunities to refactor code.
…

Header-then-# Title-then-body is the ticket format's shape, so an agent already knows how to read it. <name> is [a-z0-9-]+, the key the caller's checkbox and Run now use. The only other header line is Paused: yes — the routine is listed but never started. An add run by an agent (the command sees AGENT_ID) writes it; a human's add does not; a human lifts it. Recurring spend gets the veto a queue entry has. Beyond that, nothing: no lock flag (every routine runs one at a time — see the rule), no fan-out flag ("Plan tickets" below), no "when the queue is empty" (the caller's policy, the same for all).

The rule (the library's one function, dueRoutines(cwd)): a routine is due when it has no Every: or its RAN: is older than Every:; among the due ones, the one with the oldest RAN: (never ran first) goes next; a routine with a live .lock.md (younger than four hours, today's TTL) is not due anywhere, and neither is a Paused: yes one. That is today's rotation (round-robin emerges from "least recently run"), today's maintenance calendar ("7d"), and today's triage lock (extended to every routine, which also closes a real gap: two machines can run update-tickets at once today and import the same issues twice) — with the state on the branch, shared across machines, surviving a daemon restart. nextJob, maintenance.json's sweptAt, AUTO_PM_JOBS, AUTO_PM_MAINTENANCE_JOB, lock/fansOut on AutoPmJob, and routine-locks.ts go from the framework; the lock code moves into the skill (lock, release, releaseDead(host, stillRunning) — the boot-time release keeps its stillRunning seam, since which runs are alive is the caller's fact).

Command:

agent-routines                       every routine: name, Every, last run, running where
agent-routines add <name> [--every 7d]   write routines/<name>.md from stdin (the prompt)
agent-routines resume <name>         lift Paused: — refused for an agent: the unpause is the human veto
agent-routines remove <name>         delete the routine with its state

No run: starting an agent is the caller's. Detached writes to origin's tip like the other two commands. add under an AGENT_ID writes Paused: yes into the file; resume under one is refused — an agent adding and unpausing its own routine would make the veto decorative.

SKILL.md: what a routine is, the three commands, the file format, and the one rule ("the process that runs this project's routines starts the least-recently-run routine that is due, one at a time"). Product-free.

What the daemon keeps. The sweep's gates and start: autoPm on, quota headroom, cooldown, concurrency, "the queue is empty" before any routine (drain outranks routines, as today), startUnattended, the pending map and the settle loop. Its choice of routine becomes dueRoutines(project.path)[0]; its lock/stamp calls become the skill's lock + stamp before the start and release at settle. Run now = wakeAutoPm({ only: '<name>' | 'drain' }) — one shape instead of three. The routines list the dashboard shows = the drain row (the caller's own, first as now) + the file's routines, read through the library, labelled by their # Title; the checkbox stays the autoPmOptOut preference keyed by name.

The seed. The daemon writes today's five routines from the presets — update-tickets, triage-quick, triage-consensual, plan-tickets with no Every:, maintenance with Every: 7d — rendered to text, titled with the presets' labels. It seeds only when no commit on ai-data has ever touched routines/ (git log -1 -- routines/ empty): git history as the seeded-marker, so a user who deletes every routine — itself a commit touching the path — is never re-seeded, while a fresh project is seeded once. Verified in a throwaway repo, including the born-from-tickets history and a decoy root file named routines.md; a marker file instead would die in the very delete it must survive. After that the file is the truth: a preset edit in the framework does not reach a project whose file exists (the entry is a prompt, like a queue entry; the preset is the seed, not a reference — the presets are not materialized into checkouts, .the-framework/presets/ does not exist on this machine, so an entry could not point at one). Trade-off stated plainly: five prompts become per-project data a human or an agent edits with agent-routines add, which is what the capability is for.

"Plan tickets" without the fan-out. The routine's own preset queues one "Create tickets/.plan.md" entry per ticket (top 10, by importance); the drain then works the entries concurrency at a time, claiming each entry's ticket first (lockDrains) exactly as it does for implementation entries, and the planning agent ends with tickets put …plan.md + tickets release as today's pinned plan agent does (the drain's pinned prompt says which). Goes: fansOut, pinnedPlanJob, planCandidates, lockPlans, only: 'plan', the planAgent flag's special-casing in the start (a plan entry's agent still must not inherit (fix #42) — the drain reads it off the entry: an entry whose text starts with "Create tickets/…plan.md" is a plan; or the plan_tickets preset writes the entry as [Plan: <title>](tickets/<file>) and the drain keys on the Plan: prefix — decided in the build, said in the PR). Cost: a planning round is queue-then-drain, two sweeps instead of one. Gain: one fan-out mechanism, one claim path, ~150 lines less in auto-pm.ts and its tests, and "Plan tickets" is a routine like the other four instead of the one with a private start path.

Cutover: nothing of its own — ai-data exists since PR (a) and the seed lands at the first sweep; a live routine lock at cutover time is released by hand first; .the-framework/maintenance.json loses sweptAt (the commit-delta half, reviewedSha, stays — it is the maintain CLI command's, not the sweep's); per machine a daemon restart on the new build. The seed's first maintenance run: RAN: absent = due at once, which is today's "never swept = due" rule.

6. Presets and prompts that change text

Queue writers say agent-queue instead of tickets queue:

  • system_prompt.md (TODO_FILE macro), on_before_mergeable_prompt.md (TODO_FILE), triage_scope.md ("through agent-queue add"), maintenance.md, market_research.md: agent-queue add "<entry>" --priority <N> from the agent-queue skill.
  • triage_quick.md, triage_consensual.md, suggest_tickets_to_work_on.md, plan_tickets.md: list with tickets list, queue with agent-queue add "[<title>](tickets/<file>)" --priority <N> (the link form the tickets skill documents). plan_tickets.md keeps writing its entries exactly as Create tickets/<x>.plan.md — now a stated format rule, in the preset and the queue SKILL.md both, because the drain recognizes a plan entry by that text (§5).
  • drain_queue.md: agent-queue to read, agent-queue done "<entry>" when published; pinnedDrainJob's text names both skills.
  • todo-loop.ts's in-session prompt: unchanged wording (the framework removes the entry).

Tickets-only presets (update_tickets.md, suggest_new_tickets.md, suggest_new_features.md): unchanged. update_tickets.md is where the GitHub import lives and stays: a preset of the caller, over gh and the ticket's optional GitHub: line and meta.json; the skill itself needs no issue tracker — the markdown files are the ticketing system, and an import is something that fills them.

No preset tells an agent to add a routine yet. The skill is linked into every checkout, so an agent can; whether the system prompt should suggest it ("if this should recur, agent-routines add") is a product question for after the skill exists.

The prompt bridge for agents outside a daemon-made checkout (tickets_yourself.md + the compiled SKILL.md, TEMPORARY): extended to say the queue file lives on ai-data too, and the queue skill's SKILL.md compiled beside the tickets one. No bridge for routines: no preset asks an agent outside a checkout to add one. Every .SPEC.md beside a changed preset/prompt follows the text.

7. agent-driver per driver

package directory holds ids
agent-driver packages/agent-driver types.ts, session-support.ts, cli-session.ts, child-registry.ts, fake.ts — the contract and what every CLI driver is built from, plus the fake a caller tests with DriverImplId (all five: the contract fixes the ids, as its SPEC says it does for claude-web)
@agent-driver/claude packages/agent-driver-claude claude-code.ts, claude-code-quota.ts, actions.ts, actions-zip.ts claude-code, github-actions
@agent-driver/codex packages/agent-driver-codex codex.ts codex

Actions stays with Claude: it is Claude Code on a runner, parsed with StreamJsonParser; a third package would need the parser exported from claude for one consumer. claude-web (framework/src/driver/cloud.ts) stays in the framework: it drives claude.ai through the Chrome extension bridge, which is not extractable. Framework changes: driver-cli.ts and target-driver.ts import from the two driver packages, dashboard/quota.ts from claude, the 27 type importers keep importing agent-driver. Each package: SPEC.md, its own package.json (the two drivers depend on agent-driver), version 0.0.1 — agent-driver@0.0.0 on npm is a placeholder, so 0.0.1 is its first real release.

8. Loading the skills — unchanged, temporary

Today: createCheckout(skills: [...]) links the tickets skill beside the branches skill into .claude/skills/<name> and .agents/skills/<name> of every daemon-made checkout (labelled TEMPORARY in checkout.ts/skill-links.ts); tickets and branches on the PATH; the prompt bridge for the rest. This round: CHECKOUT_SKILLS lists three skills, the PATH carries four commands, the bridge covers tickets + queue. All still labelled temporary.

use-npm-skills: repo at three commits, npm 0.0.0, brillout/use-npm-skills#3 (committed copies, symlinks removed, v0.2.0) open and uncommented as of this plan. When it lands: one PR adds "use-npm-skills" to the four skills' dependencies (the marker), runs it once on this repo, deletes skill-links.ts, the skills option, CHECKOUT_SKILLS, branch_yourself.md, tickets_yourself.md, the gen-prompts.mjs compile of the SKILL.mds, and the HARNESS_SKILL_DIRS links. The commands stay on the PATH (a committed skill brings the text, not the binary). Not blocking any of the three PRs; ordered after (a) if it lands during (a), else after (b).

9. PRs, publish order, cutovers

(a) file-branch out + the queue split. packages/file-branch (moved files, SPECs), skill-branches importing from it, packages/skill-agent-queue (moved queue.ts + new store/cli/SKILL/SPECs), skill-tickets minus the queue and minus skill-branches, AGENT_ID in the daemon's agent environment and in holder.ts, the framework's imports (LOGS_BRANCH goes — the archives live at agents/ on ai-data), the presets of §6, the bridge, layout.ts (data-branch: ai-data), FEATURES-SPEC. Cutover on origin, once (gemstack; the dogfood repo): rename ticketsai-data (git push origin origin/tickets:refs/heads/ai-data, delete tickets), one commit bringing agents-logs's agents/ across, delete agents-logs; per machine, remove .branches/tickets and .branches/agents-logs and restart the daemon on the new build (it makes .branches/ai-data itself). Dogfood on the rig from #1739/#1749: a triage agent queues a linked entry with agent-queue add, the drain claims the ticket off the link, a plain-clone agent adds an entry through the bridge, the dashboard queues a ticket, queue done at settle.

(b) Routines. packages/skill-agent-routines, auto-pm.ts rewritten around dueRoutines (and without the plan fan-out — §5 — if agreed), routine-locks.ts moved, maintenance.ts minus sweptAt, the dashboard's routines list and Run now, layout.ts, FEATURES-SPEC (the routine rotation lines, "one triage at a time" → "one run of a routine at a time"). Cutover: none (see §5); restart daemons. Dogfood: the seed lands on the rig's branch; two daemons (two rig configs) — the second stands down on the first's lock; maintenance runs once and its RAN: holds it for a week; agent-routines add from an agent; Run now on a named routine.

(c) Driver split. Three packages, framework imports, SPECs. No cutover. Dogfood: an agent on claude, one on codex, the quota panel.

Publish (no longer waits for a read, per Discord 08-30): after (a) @gemstack/file-branch@0.0.1@gemstack/skill-branches@0.0.1@gemstack/skill-tickets@0.0.1 + @gemstack/skill-agent-queue@0.0.1; after (b) @gemstack/skill-agent-routines@0.0.1; after (c) agent-driver@0.0.1@agent-driver/claude@0.0.1 + @agent-driver/codex@0.0.1. Dependency order within each, workspace:* resolved at publish. Nothing is published before its split lands — publishing skill-tickets with the queue inside would be a release to unpublish a week later.

Why three PRs and not one. #1749 was one PR and already 18 SPEC files; (a), (b) and (c) touch three different things a reader holds in their head — the file layer, the daemon's policy, the driver seam — and (b) carries the only behaviour changes (the uniform lock, the calendar on the branch, born-paused for agent-added routines, the plan fan-out). Each is mergeable and cut over alone, and (c) is independent of the other two. Why not more: file-branch without a consumer change is a move with nothing to show for it.

10. Risks

  • agent-queue on the PATH of a shell that already has a queue/agent-queue: none known; the name is checked on npm (free) before (a).
  • A daemon on the old build after the rename re-creates tickets and agents-logs on origin (its sync pushes them). Delete them again once every machine runs the new build; say it in the ping, like Second standalone skill: tickets and the agent queue (#1748) #1749's per-machine steps.
  • The routines seed on a project that never had the framework's routines (a fresh repo): five routines appear on first sync, same as today's hard-coded five appear in the dashboard — no change in what runs, only where the list lives.
  • Preset text drift (§5, the seed): accepted, stated on the issue.
  • AGENT_ID on a resumed agent (attachCheckout): the daemon sets it from the run's id on every start, resume included — one line in the same place as the PATH.
  • The plan fan-out change doubles the latency of a planning round and makes a plan agent a drain agent (same handoff: 'merge'? no — a plan entry's agent writes to the tickets branch and opens no PR; the drain's autoMerge applies to nothing, and promote sees handoffSkip: 'no-commits' → the entry is not taken off). This is the one place the two mechanisms genuinely differ: a drained plan entry must count as done when the plan landed on the tickets branch, not when a PR merged. Handled in the build by the plan agent ending with agent-queue done itself (the pinned drain prompt tells a plan entry's agent to; an implementation entry's agent still does not) — said here so it is not a surprise in the PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions