You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
Three skills, no code between them.
tickets — tickets, plans, claims.
agent-queue — TODO_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).
Names.@gemstack/skill-agent-queue, @gemstack/skill-agent-routines; skill = command = branch = agent-queue / agent-routines. Same rule as skill-branches → branches. You wrote skills-; that rename is one commit, say if you want it.
@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.)
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.
"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.
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).
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.
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.ts — ticketFromQueueEntry (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 (claude → ClaudeCodeDriver, codex → CodexDriver), target-driver.ts (actions → ActionsDriver, 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)
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 tickets → ai-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.
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).
Three skills, no code between them.
tickets— tickets, plans, claims.agent-queue—TODO_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-databranch, a path per skill —tickets/,TODO_AGENTS.md,routines/— with the framework's archives (agents/) beside them; today'sticketsandagents-logsbranches 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'supdate-ticketspreset — 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 ticketsSKILL.md).Names.
@gemstack/skill-agent-queue,@gemstack/skill-agent-routines; skill = command = branch =agent-queue/agent-routines. Same rule asskill-branches→branches. You wroteskills-; that rename is one commit, say if you want it.@gemstack/file-branch. The branch-as-file-store code leavesskill-branchesinto a small library (noSKILL.md). All four skills depend on it, none on each other.ticketsstops reading.branches/agent-<id>: the daemon passes the agent its id asAGENT_ID; without it, the holder is the branch name. (Or a per-checkoutagent.idgit config, if you'd rather have it in git.)The routines file (proposal).
routines/<name>.mdunderroutines/onai-data: an optionalEvery: 7dline, 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-machinemaintenance.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 bornPaused: yes— recurring spend gets the veto a queue entry has (a human unpauses); a human'sagent-routines addruns normally."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 & planagents concurrently #1327 survives (same concurrency); planning takes one extra sweep, and Run now shows entries first, agents on the next sweep.agent-driverper 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).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.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:
ai-data, a path per skill — over one branch per skill (decided on Discord).AGENT_IDenv 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.TODO_AGENTS.mdstays — over renaming while nothing is published; the name is in every preset and habit.agent-queue doneis the only verb, whatever the reason — over a secondremoveverb with identical behaviour.autoPmOptOut) — over a pause line in the file changing what the checkbox means; that line stays a natural follow-up.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).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.mdstamps 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.ai-datain code is exported once by@gemstack/file-branch(DATA_BRANCH) — over four hardcoded copies; every skill already depends on the library.ai-dataever touchedroutines/— 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.mdand 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 onfile-branch, never on another skill; what two skills have to agree on (a queue entry that links a ticket) is a convention in theirSKILL.mds, followed by the agent and by the caller, not an import.The code graph after the round:
What the code says today (the facts the plan rests on):
packages/skill-ticketsis tickets and queue:queue.ts(151 lines: parse, insert into a## Priority Nsection, remove,readQueue,queueAdd,queueDone) is its own file, and the seam to the tickets is two pure functions innames.ts—ticketFromQueueEntry(thetickets/<file>link an entry carries) andqueuePriorityForTicket(a ticket'sPriority:→ its queue section) — plus the CLI'squeue add --ticket <file>, which reads the ticket to build the link and pick the section. The queue file sits at the root of theticketsbranch besidetickets/;syncTicketsseeds it (store.ts).skill-ticketsdepends onskill-branchesfor: 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.AUTO_PM_JOBSinauto-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 asroutines/<name>.lock.mdonagents-logs(routine-locks.ts, 170 lines, with the boot-time release of a dead daemon's locks).AUTO_PM_ROUTINESis what the dashboard lists (checkbox per routine = theautoPmOptOutpreference, a list of routine names; Run now =wakeAutoPm({ only })whereonlyis'drain' | 'plan' | { lock }).Spike & planagents concurrently #1327): the sweep asks the tickets for candidates (planCandidates: unplanned, unclaimed, by priority), mints agent ids, claims them (lockPlans), and starts onepinnedPlanJobper ticket, whose appended text tells the agent to plan exactly that ticket. The stockplan_tickets.mdpreset, 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).daemon-runtime.ts:ticketson every agent's PATH,CHECKOUT_SKILLSlinks itsSKILL.md),system-prompt.ts(theTICKETS_YOURSELF+TICKETS_SKILLbridge for agents outside a daemon-made checkout),scripts/gen-prompts.mjs(compiles the skill'sSKILL.mdinto 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(theLAYOUTfile: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.tson those;codex.tson those;actions.ts+actions-zip.tsonclaude-code.ts'sStreamJsonParser(it replays a Claude Code run from a workflow artifact). The framework builds them indriver-cli.ts(claude→ClaudeCodeDriver,codex→CodexDriver),target-driver.ts(actions→ActionsDriver,web→ the framework's ownCloudDriver,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.0is a placeholder;@gemstack/skill-branchesand@gemstack/skill-ticketsare not published (404); the@agent-driverorg is ours.SkillLink/skillsoncreateCheckout,tickets_yourself.md) is already labelled for it.origin/ticketstoday:tickets/and aTODO_AGENTS.mdof two empty priority headings (31 bytes).origin/agents-logs:agents/only — noroutines/right now, since no routine is running. So the cutovers below move nothing but headings.1. Names
name:)ai-data@gemstack/skill-ticketspackages/skill-ticketsticketsticketstickets/@gemstack/skill-agent-queuepackages/skill-agent-queueagent-queueagent-queueTODO_AGENTS.md@gemstack/skill-agent-routinespackages/skill-agent-routinesagent-routinesagent-routinesroutines/@gemstack/file-branchpackages/file-branchDATA_BRANCHOne rule, already in force for
branchesandtickets: the skill is the package name withoutskill-, and the command is the skill's name. The files all live on the one sharedai-databranch (decided on Discord), each skill owning its path there; the framework's archives sit beside them atagents/, so theagents-logsbranch goes too.agent-queuerather thanqueuebecause the command is on every agent's PATH and the name is what the agent reads in a preset ("use theagent-queueskill"): a barequeuesays nothing about whose. The Discord spellingskills-agent-queuediffers only in the prefix; the prefix is the two existing packages' and a rename before publishing costs one commit.2.
@gemstack/file-branchWhat moves.
skill-branches/src/file-branch.ts(+ test, + SPEC) as it is;git.ts(the runner, the timeout,gitReason,checkoutRoot,isGitRepo,pushBranch) andgit-exclude.tswith 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-branchesthen imports the runner, the exclude and the constant fromfile-branch— the branches skill is a consumer of the library like the other three, and keeps its ownworktree.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 noSKILL.mdis not a capability, so depending on it says nothing false.Public surface = today's
file-branch.tsexports (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, andDATA_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-queueWhat it is. "Agents can add future agentic work."
TODO_AGENTS.mdat the root of the sharedai-databranch: markdown list items under## Priority Nheadings, 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 isfile-branch'sDATA_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'ssyncTickets; 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-branchonly.Command (the
queuesubcommands oftickets, one level up, same contract — JSON out, exit 0/1/2, detached write to origin's tip):--ticketgoes: 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 ticketsSKILL.md; the dashboard, through the tickets library) asagent-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.
ticketFromQueueEntryandqueuePriorityForTicketstay inskill-tickets/names.ts— they are ticket knowledge (what a link intotickets/is; what a ticket's priority means on a 0–10 queue). The ticketsSKILL.mdgains three lines under "Queue a ticket": the entry form above, the priority from the ticket'sPriority:line,agent-queue donewhen 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):
readQueueEntriesfor the sweep's decision (daemon-services.ts:217) and fornextQueuedTicket/the in-session queue loop (todo-loop.ts);queueDoneat settle (promote) and in the loop;queueAddfrom the dashboard (control.ts:361/380, which builds the ticket link withqueuePriorityForTicketfrom the tickets library);ticketFromQueueEntryto 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, theendedDryset: unchanged.syncProjectDataconverges the oneai-databranch 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 intoai-data(§9).4.
skill-ticketsafter the splitLoses
queue.ts, thequeuecommand,QUEUE_FILE, the queue seed insyncTickets, the--ticketflag. Keeps tickets, plans, claims,meta.json, the rootticketslink, and the two ticket-side queue functions above. Itsnames.tsdropsTICKETS_BRANCHforfile-branch'sDATA_BRANCH; the persistent checkout is.branches/ai-data, and the rootticketslink 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.tstoday: inside.branches/agent-<id>the holder is<id>(survivesbranches 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 isAGENT_IDfrom 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 setsAGENT_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), andskill-ticketsimports nothing fromskill-branches.SKILL.mdsays it in one line: "you claim asAGENT_IDif the process that started you set it, else as your branch". The git-shaped alternative, if the environment is not wanted as glue:skill-branchessetsagent.id=<id>in the checkout's worktree config atcreateCheckout(needsextensions.worktreeConfigon the repo, once), andholder.tsreadsgit config agent.idfirst — 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-routinesWhat 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 theai-databranch:routines/<name>.md: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 isPaused: yes— the routine is listed but never started. Anaddrun by an agent (the command seesAGENT_ID) writes it; a human'sadddoes 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 noEvery:or itsRAN:is older thanEvery:; among the due ones, the one with the oldestRAN:(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 aPaused: yesone. 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'ssweptAt,AUTO_PM_JOBS,AUTO_PM_MAINTENANCE_JOB,lock/fansOutonAutoPmJob, androutine-locks.tsgo from the framework; the lock code moves into the skill (lock,release,releaseDead(host, stillRunning)— the boot-time release keeps itsstillRunningseam, since which runs are alive is the caller's fact).Command:
No
run: starting an agent is the caller's. Detached writes to origin's tip like the other two commands.addunder anAGENT_IDwritesPaused: yesinto the file;resumeunder 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:
autoPmon, 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 becomesdueRoutines(project.path)[0]; its lock/stamp calls become the skill'slock+stampbefore the start andreleaseat 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 theautoPmOptOutpreference keyed by name.The seed. The daemon writes today's five routines from the presets —
update-tickets,triage-quick,triage-consensual,plan-ticketswith noEvery:,maintenancewithEvery: 7d— rendered to text, titled with the presets' labels. It seeds only when no commit onai-datahas ever touchedroutines/(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-ticketshistory and a decoy root file namedroutines.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 withagent-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
concurrencyat a time, claiming each entry's ticket first (lockDrains) exactly as it does for implementation entries, and the planning agent ends withtickets put …plan.md+tickets releaseas today's pinned plan agent does (the drain's pinned prompt says which). Goes:fansOut,pinnedPlanJob,planCandidates,lockPlans,only: 'plan', theplanAgentflag'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 thePlan: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 inauto-pm.tsand 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-dataexists 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.jsonlosessweptAt(the commit-delta half,reviewedSha, stays — it is themaintainCLI 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-queueinstead oftickets queue:system_prompt.md(TODO_FILEmacro),on_before_mergeable_prompt.md(TODO_FILE),triage_scope.md("throughagent-queue add"),maintenance.md,market_research.md:agent-queue add "<entry>" --priority <N>from theagent-queueskill.triage_quick.md,triage_consensual.md,suggest_tickets_to_work_on.md,plan_tickets.md: list withtickets list, queue withagent-queue add "[<title>](tickets/<file>)" --priority <N>(the link form the tickets skill documents).plan_tickets.mdkeeps writing its entries exactly asCreate tickets/<x>.plan.md— now a stated format rule, in the preset and the queueSKILL.mdboth, because the drain recognizes a plan entry by that text (§5).drain_queue.md:agent-queueto 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.mdis where the GitHub import lives and stays: a preset of the caller, overghand the ticket's optionalGitHub:line andmeta.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 compiledSKILL.md, TEMPORARY): extended to say the queue file lives onai-datatoo, and the queue skill'sSKILL.mdcompiled beside the tickets one. No bridge for routines: no preset asks an agent outside a checkout to add one. Every.SPEC.mdbeside a changed preset/prompt follows the text.7.
agent-driverper driveragent-driverpackages/agent-drivertypes.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 withDriverImplId(all five: the contract fixes the ids, as its SPEC says it does forclaude-web)@agent-driver/claudepackages/agent-driver-claudeclaude-code.ts,claude-code-quota.ts,actions.ts,actions-zip.tsclaude-code,github-actions@agent-driver/codexpackages/agent-driver-codexcodex.tscodexActions stays with Claude: it is Claude Code on a runner, parsed with
StreamJsonParser; a third package would need the parser exported fromclaudefor 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.tsandtarget-driver.tsimport from the two driver packages,dashboard/quota.tsfromclaude, the 27 type importers keep importingagent-driver. Each package:SPEC.md, its ownpackage.json(the two drivers depend onagent-driver), version 0.0.1 —agent-driver@0.0.0on 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 incheckout.ts/skill-links.ts);ticketsandbrancheson the PATH; the prompt bridge for the rest. This round:CHECKOUT_SKILLSlists 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, deletesskill-links.ts, theskillsoption,CHECKOUT_SKILLS,branch_yourself.md,tickets_yourself.md, thegen-prompts.mjscompile of theSKILL.mds, and theHARNESS_SKILL_DIRSlinks. 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-branchout + the queue split.packages/file-branch(moved files, SPECs),skill-branchesimporting from it,packages/skill-agent-queue(movedqueue.ts+ new store/cli/SKILL/SPECs),skill-ticketsminus the queue and minusskill-branches,AGENT_IDin the daemon's agent environment and inholder.ts, the framework's imports (LOGS_BRANCHgoes — the archives live atagents/onai-data), the presets of §6, the bridge,layout.ts(data-branch: ai-data), FEATURES-SPEC. Cutover on origin, once (gemstack; the dogfood repo): renametickets→ai-data(git push origin origin/tickets:refs/heads/ai-data, deletetickets), one commit bringingagents-logs'sagents/across, deleteagents-logs; per machine, remove.branches/ticketsand.branches/agents-logsand restart the daemon on the new build (it makes.branches/ai-dataitself). Dogfood on the rig from #1739/#1749: a triage agent queues a linked entry withagent-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 doneat settle.(b) Routines.
packages/skill-agent-routines,auto-pm.tsrewritten arounddueRoutines(and without the plan fan-out — §5 — if agreed),routine-locks.tsmoved,maintenance.tsminussweptAt, 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 itsRAN:holds it for a week;agent-routines addfrom an agent; Run now on a named routine.(c) Driver split. Three packages, framework imports, SPECs. No cutover. Dogfood: an agent on
claude, one oncodex, 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 — publishingskill-ticketswith 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-branchwithout a consumer change is a move with nothing to show for it.10. Risks
agent-queueon the PATH of a shell that already has aqueue/agent-queue: none known; the name is checked on npm (free) before (a).ticketsandagents-logson 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.AGENT_IDon 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.handoff: 'merge'? no — a plan entry's agent writes to the tickets branch and opens no PR; the drain'sautoMergeapplies to nothing, andpromoteseeshandoffSkip: '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 withagent-queue doneitself (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.