Repository navigation
feat(agents): Crews, a roster the Captain proposes and the person approves - #148
Conversation
1d6b4da to
53d721f
Compare
340490a to
edaca70
Compare
Jacksondr5
left a comment
There was a problem hiding this comment.
Reviewed with the whole stack in view and re-checked after the /crew decoupling and concurrent-Crews rework. The rework resolves the Captain-as-role ruling cleanly: no persona, /crew in any thread, one Captain with several named Crews, and the server already keyed Captain-ness by participant and thread. The gate design (MCP proposes, the operate-scoped HTTP route approves, each seat runs its own saved agent's policy) is right.
Requesting changes on two groups.
Lifecycle recovery, three gaps that leave state nothing cleans up (inline on CrewProposalService, CrewLaunchService, AgentCrewInstanceService).
The notice format is injectable and the card parser trusts it (inline on tools.ts, crewNotices.logic.ts, ChatView.tsx). The three problems share one fix at the writer: single-line names, escape the opening seat tag as well as the closing body tag, and wrap after the effort prefix.
Minor, no inline:
- The body says id 13 is a deliberate gap; the migration list is contiguous and 13 is the machine-sender migration from #144.
- Human-edited seat names from the card bypass the length bounds, so an empty or over-long name throws while building the UPDATE and surfaces as a 500 rather than the mapped 409.
- The /crew send substitutes the wrapped brief for the full outgoing text, so appended terminal, element, and review contexts never reach the Captain, and the launch card renders no attachments.
- The proposals client adds a second 7.5s foreground poll beside the inbox count; not mentioned in the body. With 149's Fleet poll that is three, which is the case for a J5 change stream (separate issue, not this PR).
- FORK.md calls the ChatView edit one appended case; it is three hunks now. Accurate enough after the persona spread went away, but worth a recount.
Reviewed by Claude Fable 5.1 in Claude Code, with an independent pass by GPT-6-Astra in Codex.
|
All of it is in 800b48f8f. The Crew record is now written before any seat spawns, so a failed launch is handed back with my edits kept and a decline retires what exists; additions refuse a same-name seat under a different identity and any seat into a retired Crew; names are one line (seat names lowercase-hyphen) at both doors; the cap is reported as a cap; the /crew turn keeps its contexts and survives the effort prefix; and the body no longer calls id 13 a gap. FORK.md recounts the ChatView hunks. |
|
Proposal from Jackson for the orchestration instructions, to land with this PR since
Notes:
|
800b48f to
cac20d2
Compare
|
Round two is in cac20d224. Your orchestration-instructions proposal landed as written, with the mention bullet kept ahead of the three options and the test now pinning the Crew bullet; FORK.md case 8 records it and the /crew launch block shrank to the one sentence the standing text does not carry. The descriptions are shorter and say "user". Seat threads are titled by seat alone. The card's third field is the new seat's instructions on its own line, which is what that box was meant to be. Custom seats (no saved agent) are in #183 on top of the stack. |
cac20d2 to
cbf8113
Compare
Jacksondr5
left a comment
There was a problem hiding this comment.
Round three, re-checked against the fix commits. Fixed and verified: reservation identity, archived-target additions, single-line names at every door, the ultrathink prefix, contexts preserved on the /crew send with attachments listed on the card, the cap error, 409 for edited seats, the former-seat Captain chip, the id-13 sentence, the three-option orchestration instructions (test pins the Crew bullet, case 8 updated, launch block down to one sentence), the shorter descriptions, and seat threads titled by seat.
Requesting changes on the recovery path, four related items (inline on CrewProposalService.ts, CrewLaunchService.ts, CrewProposalCard.tsx). The record-before-spawn order and the roster retirement on decline are the right shape; these are the gaps left in it.
Comments:
- Steering text, two accuracy notes (inline on T3OrchestrationInstructions.ts).
- propose_crew description versus schema (inline on tools.ts).
- crews.md L42 still lists settle among the machinery the platform ships; #150 removed it.
- The proposals poll stays until #182, as the body says. Fine.
Reviewed by Claude Fable 5.1 in Claude Code, with an independent pass by GPT-6-Astra in Codex.
|
Round three is in d7ed4e8f1: cleanup before the decline is recorded, release by identity for every name a proposal reserved, renamed seats retired before the retry records its roster, and the card seeded from the approved seats. The two orchestration clauses and the seat-name hint are tracked in #187 with the rest of the non-blocking notes, so this could go back to you without them. |
Jacksondr5
left a comment
There was a problem hiding this comment.
Round four. Fixed and verified: release by identity for every row a proposal minted with its thread archived first, the retry retiring dropped or renamed seats before recording the roster through one unconditional removal, and the card seeding from approvedSeats. The two new tests cover the decline and the renamed-seat retry.
Requesting changes on one thing the reorder opened (inline on CrewProposalService.ts L548): resolving a reopened gate from two devices at once. AGENTS.md: "Multi-device and multi-environment cases are real." The not-found handling (inline on L468) folds into the same change.
For #187, not blocking:
- A seat dropped in one retry and reintroduced in the next reuses its deterministic spawn ids against a thread the earlier retry archived, and nothing unarchives it. Narrow; refuse or explicitly recover a reintroduced seat.
Reviewed by Claude Fable 5.1 in Claude Code, with an independent pass by GPT-6-Astra in Codex.
|
Not blocking, filed as #189: clicking anywhere inside the roster gate card flips the composer from its resting layout to its expanded one. Jackson's two screenshots of the same card, before and after one click: Cause: the gate is mounted inside the composer's root form, whose |
Jacksondr5
left a comment
There was a problem hiding this comment.
Round six. Fixed and verified by two independent passes: provider diagnostics encoded at the writer and decoded after field extraction with the named tests; the decline notice sent while the proposal is still claimed; FORK.md naming the reporter, gate-notice, and alert modules and migration 016; thread ids bounded to 78 characters, deterministic per request key, JSON-framed against delimiter collisions, with nothing anywhere parsing the old format; and the failure-injection fixture replaced with an explicit flag. CI green.
Requesting changes on three small things, inline: the escape set is short by two code points; the web badge still says "Failed to start"; and the decline reopen has one continuation that lets a declined proposal launch.
Comments on the id change, none blocking:
- Pre-upgrade spawn threads on a dev database no longer match by derivation, so a retried addition across the upgrade is refused and its reservations are not released. Acceptable under the pre-dogfood rule; the body's "replay stability unchanged" is true for post-upgrade requests only, and one sentence saying so avoids a surprise.
- The id itself is 78 bytes, but the terminal history name is id plus terminal id; with a 128-character terminal id, which the contract allows, the file name reaches 289 bytes. The UI generates uuids, so this is a boundary, not a path anyone hits today.
mcp/orchestratorVerbs.live.test.tsstill builds the old readable id and asserts equality against it; it fails when enabled.
Reviewed by Claude Fable 5.1 in Claude Code, with an independent pass by GPT-6-Astra in Codex.
776d8da to
2e2ba08
Compare
|
Round six fixes are in 2e2ba0894: the escape class covers U+2028/U+2029 with the decoder restoring them, the badge says "Failed", and a decline whose notice committed stays |
…roves /crew <brief> on a fresh web or desktop thread launches the bundled Crew Captain, which reads the saved-agent library with list_agents and files a roster with propose_crew. The person answers the roster inline above the Captain's composer, removing or adding seats, and approval spawns every seat as a saved-agent Peer Agent placed under the Captain with the brief and roster in its first turn. A Captain grows a Crew only through request_crew_member, which lands in the Inbox and counts on the bell; members are refused both verbs and spawn_agent with escalation to their Captain. Seats are validated where they are structured, a Crew holds at most twelve seats, and a failed spawn hands the gate back so the retry converges on its reserved seats. Crew members show a seat chip in the sidebar and the Captain a Captain chip. Migration 14 records instances, members, and proposals. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n persona /crew <brief> no longer turns a fresh draft into the crew-captain saved agent. It sends the brief, wrapped in the platform's crew-composition guidance, as the current thread's next turn, so the thread keeps whatever agent, model, and policy it already runs as and any thread, fresh or running, can become a Captain. The bundled crew-captain example, its built-in id, and the library check that refused the command without it are gone; the guidance the persona carried now rides with the command, and the tools describe the rest. A Captain may command several Crews at once for concurrent work, each proposed through its own gate and named for what it is for; the propose_crew description and the Crews definition say so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The person's /crew turn showed its guidance block in the bubble, and the platform's <j5_crew_gate> notice arrived in the Captain's thread as raw tagged text. Both now render as cards through the existing J5 user-row delivery seam: the brief as the brief, with the guidance one click away, and the decision as its title, the Crew's name, and the roster seat by seat, each seat opening its thread. The gate notice carries the crew name so the card can say which Crew was decided. Recognition is strict and anything unparsed stays raw. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two assertions still expected twelve bundled agents after the crew-captain example was removed; CI caught what the targeted local runs did not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ces cannot be forged
A roster launch now records its Crew before any seat thread exists, so a spawn that fails
partway leaves nothing a Crew record does not know: the gate is handed back with the human's
edited seats kept, a retry converges on the same seats (a renamed seat's stale reservation
is dropped), and a decline retires the record and archives whatever seat threads were
created. Additions release by the approved set, refuse a same-name seat under a different
identity inside the reservation transaction, and refuse to seat anyone into a retired Crew.
Crew and seat names are one line each (seat names lowercase-hyphen, as the card already
enforces), checked by the MCP schemas and again in the service for the human's edited
seats, so a name can no longer forge roster lines in the notices the Captain's card parses.
The seat cap is reported as a cap error with a matching next step, the request_crew_member
description no longer reads the literal `${CREW_SEAT_CAP}`, a retired seat's old
membership no longer hides the Crews that agent now commands, and the `/crew` turn keeps
its attached contexts, survives the Claude effort prefix, and lists its attachments on the
launch card.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…m takes instructions Jackson's review of the stack, round two. The standing orchestration instructions name three shapes of help (Subagent, Peer Agent, Crew) so an agent asked for a crew without the /crew prefix reaches propose_crew instead of making subagents; the /crew launch block shrinks to what those instructions do not say. The propose_crew and request_crew_member descriptions are shorter, say user rather than human, and ask for short seat names. Seat threads are titled by seat name alone, since the sidebar group and the Crew chip already name the Crew. The roster card's third field is the new seat's instructions on its own line; a user-added seat's reason is fixed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A decline recorded before its cleanup could not be retried once a seat archive failed, an addition decline released only the last approved names, a seat renamed between attempts kept its thread as a member with no brief, and the gate card reseeded from the requested seats after a failed launch. Cleanup now runs before the decline is recorded, every row a proposal minted is released by identity, the launcher retires a renamed seat's thread before recording the roster it launches, and the card seeds from the approved seats when the gate was handed back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… missing seat thread reads as absent Two devices resolving the same reopened gate at once could have a decline retire the Crew the other device's approval had just launched, because approve claimed first and launched while decline cleaned up first and claimed last. Both now claim the proposal first, compare-and-set from open into approving or declining, do their work, then write the final status; the second resolver finds the row claimed and is refused. A claim the server lost mid-way is settled by a boot sweep: a declining proposal has its cleanup finished and the decline recorded, an approving one is handed back to the gate, since its launch converges on a retry. The seat-thread reads in the decline cleanup and the launcher's retry path treated every failure as "no thread", so a storage error dropped the member row from under a live thread. Only a genuine not-found passes now; anything else fails the operation and the gate is handed back. The new seat's name field on the roster card now says "name" rather than "seat". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… started or failed
The approved gate notice was posted the moment the briefs were dispatched, which commits intent
only. Two Claude seats that died on a signed-out subscription reached the Captain later as
"finished" with no reason, after it had already been told the crew was running (Jackson's dogfood,
2026-09-17).
A launch reporter now watches each seat the approval launched: it takes a verdict from what the
seat thread already shows, then follows run updates on the stored-event stream until every first
turn is running or ended without running, or a 60 second window closes. One report then reaches
the Captain: what the person changed against the proposal (added, removed, renamed seats), and
per seat started, failed with the run's recorded error, or not started yet. Ids derive from the
proposal as before, so a replay posts nothing twice; the proposal records when it was reported and
a boot sweep reports any approval the server lost. The decline notice is unchanged.
A seat whose provider is signed out, disabled, or missing was already refused at spawn by the
persona route; the refusal now names the reason ("codex is signed out") instead of "unavailable".
The card shows the change line, a badge on seats that failed or had not started, and each failure.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gration, not by editing 014 Migration 14 had been edited in place to widen the status check and add reported_at, which a development database that already ran it would never pick up. Migration 16 rebuilds the proposal table instead, carrying every row and marking approvals from before the launch report as reported so the boot sweep does not re-announce them. It is 16 because 15 is the custom-seats rebuild above this change in the stack, and the migrator skips every id at or below the latest applied, so renumbering would strand any database that already ran 15. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2e2ba08 to
177a9ca
Compare


This is the heart of Crews. Nobody writes a Crew file in advance, and there is no dedicated Captain agent either: I send
/crew <brief>in any web or desktop thread, fresh or mid-conversation, and the brief goes to whatever agent that thread already runs as, wrapped in a short platform block that says to compose a Crew rather than do the work. That agent reads the saved-agent library withlist_agentsand files a roster withpropose_crew; the agent that proposes is the Captain. My call after the first cut of this branch was to drop the bundledcrew-captainpersona rather than keep a second copy of the same guidance that would drift.The roster is answered inline above the Captain's composer, like a planning question; I can drop seats or add agents from the library, then approve or decline. Approval spawns every seat as a saved-agent Peer Agent placed under the Captain, records the roster snapshot, and tells the Captain the decision in its thread. A Captain grows a Crew only through
request_crew_member, which lands in the Inbox and counts on the bell; members are refused both verbs andspawn_agentwith escalation to their Captain. One Captain may command several Crews at once when the work is concurrent, each proposed through its own gate and named for what it is for. Seats are checked where they are structured (missing or disabled agent, duplicate seat, twelve-seat cap), a failed spawn hands the gate back so the retry converges on its reserved seats, and declining releases seats a failed spawn reserved. Human approval is the authority: approved seats run with their own agent's permissions even when the Captain is read-only.Crew members show a seat chip in the sidebar and the Captain a Captain chip. In the thread, my
/crewturn renders as the brief I wrote with the guidance block one click away, and the gate's decision arrives as a card naming the Crew and listing the roster seat by seat, each seat opening its thread; anything the card parser does not recognize stays raw. Mobile has no delivery seam and shows these as text, as it does A2A deliveries. Open proposals reach the card through a 7.5 second foreground poll on the proposals client, a second poll beside the inbox count; that stays until the J5 change stream in #182 replaces the J5 read polls together. Migration 14 records instances, members, and proposals; id 13 is the machine-sender migration from #144, and this migration drops the earlier-shaped crews tables an early cut left under that id on my dev database. The Crews definition doc ships here, docs-first, with the acceptance criteria the later PRs in this stack fill in.Built with Claude Fable 5.1 in Claude Code.
Evidence
New UI, so after-only, from my dev server with the stack checked out. The gate card sits inline above the Captain's composer; the two seats here are a Critic seat and a custom seat (#183).
🤖 Generated with Claude Code
Review-fix browser evidence
Verified in an isolated local app copied from dev data. Focusing roster inputs leaves the composer collapsed; identical seat/persona labels appear once. Issues #189 and #190 remain open for maintainer review.
Review follow-ups implemented and verified with Codex in the Codex app.
Closes #204.