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, revised 08-31): a skill is a capability, and no skill depends on another skill's code. Points 1–3 and 6–8 are what I'm building; 4 and 5 are deferred (kept as the record).
The skills, no code between them (routines deferred 08-31 — see its bullet).
tickets — tickets, plans, claims.
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.
routines — scheduled agentic work — deferred (08-31): today's five routines stay hard-coded; nothing drives "agents add scheduled work" yet. Restart candidate: each routine as an npm skill.
Everything lives on one shared agent-data branch (name settled 08-31 — singular like the skills; not agents-data, the deleted branch's ghost old builds still push), a path per skill — tickets/, TODO_AGENTS.md — with the framework's archives (agents/) beside them; today's tickets and agents-logs branches fold into it. One bookkeeping branch per project, checked out once at .branches/agent-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 → queue, is a sentence in the tickets SKILL.md).
Names (settled 08-31). @gemstack/skill-queue; skill = command = queue. Same rule as skill-branches → branches: package skill-<x>, skill and command <x>. (skill-routines is the deferred one's name, when it comes.)
@gemstack/agent-data (renamed 08-31 — the package is named after the branch it owns). The branch-as-file-store code leaves skill-branches into a small library: the sync→commit→push cycle with race retries that every data-branch write already goes through today. A library, not a skill — a skill is read by the model, a library by code, and this package has nothing to tell an agent. Every skill depends 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.
The routines file — deferred (08-31), kept as the record for the restart. routines/<name>.md under routines/ on agent-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 routines add runs normally.
"Plan tickets" stops fanning out by itself — deferred (08-31) with the routines skill; the fan-out stays as it is. Kept as the record: 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 for now: it runs through the Chrome-extension bridge, and extracting it means a seam for the bridge first — its own round (@agent-driver/claude-web is reserved).
Two PRs (routines deferred), each merged and cut over alone: (a) agent-data + the queue split, (b) the driver split. Publish after each merge: (a) @gemstack/agent-data, skill-branches, skill-tickets, skill-queue; (b) agent-driver, @agent-driver/claude, @agent-driver/codex. Each skill ships a DECISIONS.md beside its SPECs — this issue's design points, moved to where a reviewer reads the package.
Loading stays temporary (symlinks + prompt bridge) until we adopt use-npm-skills — 0.1.0 is published (08-31); the marker is "use-npm-skills" in a package's keywords, materialization is an explicit npx use-npm-skills (no hooks), committed files named by the skill's frontmatter. The switch is one small PR: a keywords line per skill, the tool as a devDependency, delete the link code and the bridges. Open point raised upstream: a skill that ships a command commits its text but not its binary — a fresh clone reads the skill but needs an install to run it.
Design points stress-tested one by one — each with the option not taken, so the alternatives are on record:
Branch layout: one shared agent-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: 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 agent-data in code is exported once by @gemstack/agent-data (DATA_BRANCH) — over four hardcoded copies; every skill already depends on the library.
The routines seed fires once: only when no commit on agent-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).
agent-data, not agents-data/agent-data (08-31): singular matches the skill names; agents-data is the deleted branch's ghost old builds still push.
@gemstack/agent-data is a library, not a skill (08-31): a skill is read by the model, a library by code — a SKILL.md here would have nothing to tell an agent, and skill status would forbid the very import it exists for.
Short names (08-31): skill-queue, command queue — over the agent- prefix; nothing collides, presets read better.
Routines deferred (08-31): nothing drives "agents add scheduled work" yet; restart candidate: each routine as an npm skill (use-npm-skills).
claude-web stays in the framework (08-31): extracting it needs a bridge seam first — its own round; @agent-driver/claude-web reserved.
DECISIONS.md per skill (08-31): this section moves into each package as it ships, beside its SPECs.
The 08-30 comments on #1725 / #1733 still say "one package"; I'll update them once this is agreed (#1748 is closed, its leftovers forwarded here).
🤖 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, revised 08-31 (names settled, routines deferred). One new skill package beside the two existing ones, one library underneath them all, 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 agent-data, 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/agent-data a branch as a file store (library: no SKILL.md, no command)
▲ ▲ ▲
skill-branches skill-tickets skill-queue (skill-routines: deferred)
▲ ▲ ▲
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) was open with no comments; 08-31: closed for a reimplement, 0.1.0 published (keywords marker, no hooks — §8). 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 agent-data
tickets
@gemstack/skill-tickets
packages/skill-tickets
tickets
tickets
tickets/
queue
@gemstack/skill-queue
packages/skill-queue
queue
queue
TODO_AGENTS.md
routines (deferred)
@gemstack/skill-routines
—
routines
routines
routines/
file store
@gemstack/agent-data
packages/agent-data
—
—
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 agent-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. queue won over queue (08-31): shorter in presets and on the PATH, and it collides with nothing — the agent context lives in the branch name, agent-data.
2. @gemstack/agent-data
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 agent-data — 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". Every skill and the framework write files to the shared agent-data branch through the same machinery. Inside any one skill, the others 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 = 'agent-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-queue
What it is. "Agents can add future agentic work." TODO_AGENTS.md at the root of the shared agent-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, DECISIONS.md, bin/queue, src/names.ts (QUEUE_FILE = 'TODO_AGENTS.md' at the branch root; the branch is agent-data'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/agent-data only.
Command (the queue subcommands of tickets, one level up, same contract — JSON out, exit 0/1/2, detached write to origin's tip):
queue the open entries, in order of work
queue add <text> [--priority N] put an entry on the queue, in its priority section
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 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, 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: 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 agent-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 agent-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 agent-data's DATA_BRANCH; the persistent checkout is .branches/agent-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-routines — deferred (08-31); the record for the restart
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 agent-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:
routines every routine: name, Every, last run, running where
routines add <name> [--every 7d] write routines/<name>.md from stdin (the prompt)
routines resume <name> lift Paused: — refused for an agent: the unpause is the human veto
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 agent-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 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 — agent-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 queue instead of tickets queue:
system_prompt.md (TODO_FILE macro), on_before_mergeable_prompt.md (TODO_FILE), triage_scope.md ("through queue add"), maintenance.md, market_research.md: queue add "<entry>" --priority <N> from the queue skill.
triage_quick.md, triage_consensual.md, suggest_tickets_to_work_on.md, plan_tickets.md: list with tickets list, queue with 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: queue to read, 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, 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 agent-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: 0.1.0 published (08-31) — brillout/use-npm-skills#3 was closed for a reimplement; the marker is "use-npm-skills" in keywords, materialization is an explicit npx use-npm-skills (no lifecycle hooks), committed files named by frontmatter. The switch: one PR adds the keywords line to each skill, takes the tool as a devDependency, 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 either PR; its own small PR after (a).
9. PRs, publish order, cutovers
(a) agent-data out + the queue split.packages/agent-data (moved files, SPECs), skill-branches importing from it, packages/skill-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 agent-data), the presets of §6, the bridge, layout.ts (data-branch: agent-data), FEATURES-SPEC. Cutover on origin, once (gemstack; the dogfood repo): rename tickets → agent-data (git push origin origin/tickets:refs/heads/agent-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/agent-data itself). Dogfood on the rig from #1739/#1749: a triage agent queues a linked entry with 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. Two small nits from #1748's dogfood ride along: an emptied ## Priority N heading is removed when its last entry is (removeQueueEntry), and a drain started from the AI Queue play button gets its entry retired at settle like a sweep-started one.
(Deferred) Routines.packages/skill-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; routines add from an agent; Run now on a named routine.
(b) 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/agent-data@0.0.1 → @gemstack/skill-branches@0.0.1 → @gemstack/skill-tickets@0.0.1 + @gemstack/skill-queue@0.0.1; after (b) 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 two PRs and not one.#1749 was one PR and already 18 SPEC files; (a) and (b) touch different things a reader holds in their head — the file layer, the driver seam — and (b) is independent of (a). The deferred routines PR carried the only behaviour changes, one more reason it can wait. Why not more: agent-data without a consumer change is a move with nothing to show for it.
10. Risks
queue on the PATH of a shell that already has one: none found — no such binary on this machine, npm name free.
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.
(deferred)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.
(deferred)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.
(deferred)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 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, revised 08-31): a skill is a capability, and no skill depends on another skill's code. Points 1–3 and 6–8 are what I'm building; 4 and 5 are deferred (kept as the record).
The skills, no code between them (routines deferred 08-31 — see its bullet).
tickets— tickets, plans, claims.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.routines— scheduled agentic work — deferred (08-31): today's five routines stay hard-coded; nothing drives "agents add scheduled work" yet. Restart candidate: each routine as an npm skill.Everything lives on one shared
agent-databranch (name settled 08-31 — singular like the skills; notagents-data, the deleted branch's ghost old builds still push), a path per skill —tickets/,TODO_AGENTS.md— with the framework's archives (agents/) beside them; today'sticketsandagents-logsbranches fold into it. One bookkeeping branch per project, checked out once at.branches/agent-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 → queue, is a sentence in the ticketsSKILL.md).Names (settled 08-31).
@gemstack/skill-queue; skill = command =queue. Same rule asskill-branches→branches: packageskill-<x>, skill and command<x>. (skill-routinesis the deferred one's name, when it comes.)@gemstack/agent-data(renamed 08-31 — the package is named after the branch it owns). The branch-as-file-store code leavesskill-branchesinto a small library: the sync→commit→push cycle with race retries that every data-branch write already goes through today. A library, not a skill — a skill is read by the model, a library by code, and this package has nothing to tell an agent. Every skill depends 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.The routines file — deferred (08-31), kept as the record for the restart.
routines/<name>.mdunderroutines/onagent-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'sroutines addruns normally."Plan tickets" stops fanning out by itself — deferred (08-31) with the routines skill; the fan-out stays as it is. Kept as the record: 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 for now: it runs through the Chrome-extension bridge, and extracting it means a seam for the bridge first — its own round (@agent-driver/claude-webis reserved).Two PRs (routines deferred), each merged and cut over alone: (a)
agent-data+ the queue split, (b) the driver split. Publish after each merge: (a)@gemstack/agent-data,skill-branches,skill-tickets,skill-queue; (b)agent-driver,@agent-driver/claude,@agent-driver/codex. Each skill ships aDECISIONS.mdbeside its SPECs — this issue's design points, moved to where a reviewer reads the package.Loading stays temporary (symlinks + prompt bridge) until we adopt use-npm-skills — 0.1.0 is published (08-31); the marker is
"use-npm-skills"in a package'skeywords, materialization is an explicitnpx use-npm-skills(no hooks), committed files named by the skill's frontmatter. The switch is one small PR: a keywords line per skill, the tool as a devDependency, delete the link code and the bridges. Open point raised upstream: a skill that ships a command commits its text but not its binary — a fresh clone reads the skill but needs an install to run it.Design points stress-tested one by one — each with the option not taken, so the alternatives are on record:
agent-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.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.agent-datain code is exported once by@gemstack/agent-data(DATA_BRANCH) — over four hardcoded copies; every skill already depends on the library.agent-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).agent-data, notagents-data/agent-data(08-31): singular matches the skill names;agents-datais the deleted branch's ghost old builds still push.@gemstack/agent-datais a library, not a skill (08-31): a skill is read by the model, a library by code — aSKILL.mdhere would have nothing to tell an agent, and skill status would forbid the very import it exists for.skill-queue, commandqueue— over theagent-prefix; nothing collides, presets read better.@agent-driver/claude-webreserved.DECISIONS.mdper skill (08-31): this section moves into each package as it ships, beside its SPECs.The 08-30 comments on #1725 / #1733 still say "one package"; I'll update them once this is agreed (#1748 is closed, its leftovers forwarded here).
🤖 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, revised 08-31 (names settled, routines deferred). One new skill package beside the two existing ones, one library underneath them all, 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
agent-data, 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:)agent-data@gemstack/skill-ticketspackages/skill-ticketsticketsticketstickets/@gemstack/skill-queuepackages/skill-queuequeuequeueTODO_AGENTS.md@gemstack/skill-routinesroutinesroutinesroutines/@gemstack/agent-datapackages/agent-dataDATA_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 sharedagent-databranch (decided on Discord), each skill owning its path there; the framework's archives sit beside them atagents/, so theagents-logsbranch goes too.queuewon overqueue(08-31): shorter in presets and on the PATH, and it collides with nothing — the agent context lives in the branch name,agent-data.2.
@gemstack/agent-dataWhat 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 fromagent-data— 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". Every skill and the framework write files to the shared
agent-databranch through the same machinery. Inside any one skill, the others 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 = 'agent-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-queueWhat it is. "Agents can add future agentic work."
TODO_AGENTS.mdat the root of the sharedagent-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,DECISIONS.md,bin/queue,src/names.ts(QUEUE_FILE = 'TODO_AGENTS.md'at the branch root; the branch isagent-data'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/agent-dataonly.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) asqueue 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,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: 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 oneagent-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 intoagent-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_BRANCHforagent-data'sDATA_BRANCH; the persistent checkout is.branches/agent-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-routines— deferred (08-31); the record for the restartWhat 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 theagent-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 onagent-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 withroutines 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 —
agent-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
queueinstead oftickets queue:system_prompt.md(TODO_FILEmacro),on_before_mergeable_prompt.md(TODO_FILE),triage_scope.md("throughqueue add"),maintenance.md,market_research.md:queue add "<entry>" --priority <N>from thequeueskill.triage_quick.md,triage_consensual.md,suggest_tickets_to_work_on.md,plan_tickets.md: list withtickets list, queue withqueue 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:queueto read,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,
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 onagent-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: 0.1.0 published (08-31) — brillout/use-npm-skills#3 was closed for a reimplement; the marker is
"use-npm-skills"inkeywords, materialization is an explicitnpx use-npm-skills(no lifecycle hooks), committed files named by frontmatter. The switch: one PR adds the keywords line to each skill, takes the tool as a devDependency, 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 either PR; its own small PR after (a).9. PRs, publish order, cutovers
(a)
agent-dataout + the queue split.packages/agent-data(moved files, SPECs),skill-branchesimporting from it,packages/skill-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/onagent-data), the presets of §6, the bridge,layout.ts(data-branch: agent-data), FEATURES-SPEC. Cutover on origin, once (gemstack; the dogfood repo): renametickets→agent-data(git push origin origin/tickets:refs/heads/agent-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/agent-dataitself). Dogfood on the rig from #1739/#1749: a triage agent queues a linked entry withqueue 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. Two small nits from #1748's dogfood ride along: an emptied## Priority Nheading is removed when its last entry is (removeQueueEntry), and a drain started from the AI Queue play button gets its entry retired at settle like a sweep-started one.(Deferred) Routines.
packages/skill-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;routines addfrom an agent; Run now on a named routine.(b) 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/agent-data@0.0.1→@gemstack/skill-branches@0.0.1→@gemstack/skill-tickets@0.0.1+@gemstack/skill-queue@0.0.1; after (b)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 two PRs and not one. #1749 was one PR and already 18 SPEC files; (a) and (b) touch different things a reader holds in their head — the file layer, the driver seam — and (b) is independent of (a). The deferred routines PR carried the only behaviour changes, one more reason it can wait. Why not more:
agent-datawithout a consumer change is a move with nothing to show for it.10. Risks
queueon the PATH of a shell that already has one: none found — no such binary on this machine, npm name free.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 withqueue 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.