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
coder-lead supervises N coder-repo instances through agent_supervision (#183) — the Overseer moved up a level, which is the design. But the Lead is not yet as capable as the thing it replaces.
The hardcoded Overseer (routes/coding.ts ~1085) opens by building a global picture — for every repo: is a session live, what did it last print, what are its instructions — then answers from it in one model call, or delegates. That answering half is what makes it an overseer.
list_subordinates returns {instanceId, name, status}, and status is agent_instances.status — the subscription lifecycle (active|paused|canceled), permanently "active" whether a subordinate is idle, mid-run, or on fire. So the Lead must delegate a run (durable workflow + full LLM loop) to learn anything.
This is a wrong field on a read path, not a missing abstraction.
Decision: compose, do not unify
Rejected: widening agent_loop_runs with a kind discriminator so pipeline/apply/browse/coding runs share one table.
"Idle" and "stuck" are not rows. Idle is the absence of a row; stuck is a derivation over timestamps + runner liveness. A resolver is needed either way — the composition is irreducible, the table merge is not. That inverts the build order.
Four lifecycle records, not three. A human-driven coding session writes only coding_sessions — and that is the Lead's main case. A kind over the three named tables misses the row it most needs.
Measured cost. The one partial unification already done (delegateToPilot writing a loop row) shipped two stranded-row bugs, both memorialised in coding-session.ts:122 and :158. Unifying adds ~5 open and ~20 terminal sites, and a stranded row is permanent data — unlike a read bug, which a redeploy fixes.
It fails the docs' own test (coordination-primitives.md: "Build the abstraction when the fix you'd make anyway can be made through it"). The fix you'd make anyway is a status read, and it cannot be made through the unified table — it would be blocked behind five write-path conversions before producing one correct answer.
The staged gate for revisiting: a second consumer needs cross-instance run pagination that an in-memory merge cannot serve, and the reader is measured as the bottleneck. A read-only SQL VIEW union is the correct intermediate, and probably the terminal state.
The rule this must not break
Connectors read generic platform tables or a declared per-instance setting — never a domain store. Precedent: lib/connectors/repo-local.ts:35-51 stays generic by reading config.settings["repo_path"] rather than importing coding-store. lib/delegation.ts:8-27: "Generic so the board card doesn't have to be re-shaped per target kind."
The domain writes into a generic record; the supervisor reads platform records. A first attempt made lib/connectors/supervision.ts import coding-store/coding-timeline — reverted in 3f14bd3, because that is exactly the coupling migration 0063 says was removed.
The enabler
capabilities.boardColumns is already a declared, per-agent status vocabulary ({id,title,statuses[]}), resolved by boardConfigForInstance (lib/board.ts:33). A supervisor normalises any subordinate's free-text status through the subordinate's own declaration — no new declaration surface, and a third-party agent with columns "Triage / Cooking / Shipped" works on day one.
Two vocabularies, handled differently — this distinction is the design:
instance_runtime_tasks.status is free text → bucketed through the subordinate's declared columns; output carries both the raw status and that agent's own columnTitle.
agent_loop_runs.status is a closed platform enum → passed through verbatim, never bucketed.
Scope
columnForStatus in lib/agent-capabilities.ts. The rule exists twice already, both outside the API and both also matching column.id === status: store/console/src/tabs/BoardTab.tsx:636 and workers/mcp/src/instance-tools/shared.ts:87. Point the MCP copy at the new canonical one.
lib/instance-work.ts (new) — recentWorkForInstances / recentRunsForInstances. One D1 statement each, a UNION ALL of one indexed branch per subordinate (a flat IN (…) ORDER BY … LIMIT n lets one busy subordinate crowd out eleven). Must filter hidden = 0 (0019) or cleared cards resurface. Guard the empty-id case — an empty UNION ALL is a syntax error and a Lead with no links is reachable.
lib/subordinate-observation.ts (new) — pure, no D1. Bucketing, staleness, and the 16 000-char cap (the ceiling the Overseer itself uses).
lib/agent-loop-store.ts — last_progress_at, written by recordIteration via a defaulted arg so AgentLoopWorkflow starts reporting progress with no change. check_delegation improves for free.
lib/connectors/supervision.ts — add subordinate_status (read); rename list_subordinates' misleading status → subscription; intersect any instanceId argument with the graph.
agent-think.ts — add to READ_ONLY_TOOLS, or the cross-round dedup refuses the Lead's second observation in a turn.
Migration 0067 — the column, plus json_set on the coder-lead template's declared tools. No per-instance backfill: agentCapabilities resolves from agents.config at read time, so every subscribed Lead picks it up.
Read-only feature: it writes nothing, so no row can be stranded by it.
Acceptance
Ask the Lead "what is each of your agents doing right now?" — it answers from onesubordinate_status call with zerodelegate_goal calls.
Depth is deliberately outcomes + progress (objective, running/needs-you/done, step 4/10, quiet-for-N-min, outcome text), not raw terminal. The honest equivalent of the Overseer's terminal tail is deferred to #207.
Problem
coder-leadsupervises Ncoder-repoinstances throughagent_supervision(#183) — the Overseer moved up a level, which is the design. But the Lead is not yet as capable as the thing it replaces.The hardcoded Overseer (
routes/coding.ts~1085) opens by building a global picture — for every repo: is a session live, what did it last print, what are its instructions — then answers from it in one model call, or delegates. That answering half is what makes it an overseer.list_subordinatesreturns{instanceId, name, status}, andstatusisagent_instances.status— the subscription lifecycle (active|paused|canceled), permanently"active"whether a subordinate is idle, mid-run, or on fire. So the Lead must delegate a run (durable workflow + full LLM loop) to learn anything.This is a wrong field on a read path, not a missing abstraction.
Decision: compose, do not unify
Rejected: widening
agent_loop_runswith akinddiscriminator so pipeline/apply/browse/coding runs share one table.coding_sessions— and that is the Lead's main case. Akindover the three named tables misses the row it most needs.kindenum is a second closed enum, in D1 where it is harder to change, while [coordination][deferred] Retire the workflow closed enum → declarative behavior #160 is retiring the first.delegateToPilotwriting a loop row) shipped two stranded-row bugs, both memorialised incoding-session.ts:122and:158. Unifying adds ~5 open and ~20 terminal sites, and a stranded row is permanent data — unlike a read bug, which a redeploy fixes.coordination-primitives.md: "Build the abstraction when the fix you'd make anyway can be made through it"). The fix you'd make anyway is a status read, and it cannot be made through the unified table — it would be blocked behind five write-path conversions before producing one correct answer.The staged gate for revisiting: a second consumer needs cross-instance run pagination that an in-memory merge cannot serve, and the reader is measured as the bottleneck. A read-only SQL
VIEWunion is the correct intermediate, and probably the terminal state.The rule this must not break
Connectors read generic platform tables or a declared per-instance setting — never a domain store. Precedent:
lib/connectors/repo-local.ts:35-51stays generic by readingconfig.settings["repo_path"]rather than importingcoding-store.lib/delegation.ts:8-27: "Generic so the board card doesn't have to be re-shaped per target kind."The domain writes into a generic record; the supervisor reads platform records. A first attempt made
lib/connectors/supervision.tsimportcoding-store/coding-timeline— reverted in 3f14bd3, because that is exactly the coupling migration 0063 says was removed.The enabler
capabilities.boardColumnsis already a declared, per-agent status vocabulary ({id,title,statuses[]}), resolved byboardConfigForInstance(lib/board.ts:33). A supervisor normalises any subordinate's free-text status through the subordinate's own declaration — no new declaration surface, and a third-party agent with columns "Triage / Cooking / Shipped" works on day one.Two vocabularies, handled differently — this distinction is the design:
instance_runtime_tasks.statusis free text → bucketed through the subordinate's declared columns; output carries both the raw status and that agent's owncolumnTitle.agent_loop_runs.statusis a closed platform enum → passed through verbatim, never bucketed.Scope
columnForStatusinlib/agent-capabilities.ts. The rule exists twice already, both outside the API and both also matchingcolumn.id === status:store/console/src/tabs/BoardTab.tsx:636andworkers/mcp/src/instance-tools/shared.ts:87. Point the MCP copy at the new canonical one.lib/instance-work.ts(new) —recentWorkForInstances/recentRunsForInstances. One D1 statement each, aUNION ALLof one indexed branch per subordinate (a flatIN (…) ORDER BY … LIMIT nlets one busy subordinate crowd out eleven). Must filterhidden = 0(0019) or cleared cards resurface. Guard the empty-id case — an emptyUNION ALLis a syntax error and a Lead with no links is reachable.lib/subordinate-observation.ts(new) — pure, no D1. Bucketing, staleness, and the 16 000-char cap (the ceiling the Overseer itself uses).lib/agent-loop-store.ts—last_progress_at, written byrecordIterationvia a defaulted arg soAgentLoopWorkflowstarts reporting progress with no change.check_delegationimproves for free.lib/connectors/supervision.ts— addsubordinate_status(read); renamelist_subordinates' misleadingstatus→subscription; intersect anyinstanceIdargument with the graph.agent-think.ts— add toREAD_ONLY_TOOLS, or the cross-round dedup refuses the Lead's second observation in a turn.0067— the column, plusjson_seton thecoder-leadtemplate's declared tools. No per-instance backfill:agentCapabilitiesresolves fromagents.configat read time, so every subscribed Lead picks it up.Read-only feature: it writes nothing, so no row can be stranded by it.
Acceptance
Ask the Lead "what is each of your agents doing right now?" — it answers from one
subordinate_statuscall with zerodelegate_goalcalls.Depth is deliberately outcomes + progress (objective, running/needs-you/done, step 4/10, quiet-for-N-min, outcome text), not raw terminal. The honest equivalent of the Overseer's terminal tail is deferred to #207.