Skip to content

Docket Crew — local multi-agent orchestration (MVP) - #2

Open
pasichDev wants to merge 1 commit into
mainfrom
feat/docket-crew-mvp
Open

Docket Crew — local multi-agent orchestration (MVP)#2
pasichDev wants to merge 1 commit into
mainfrom
feat/docket-crew-mvp

Conversation

@pasichDev

Copy link
Copy Markdown
Owner

What this is

@pasichdev/docket-crew — a separate package layered on Docket Core. A loopback daemon that launches real coding-agent CLIs, assigns them work, routes messages between them, isolates coding work in git worktrees, and serves a live Office UI.

Docket Core is untouched. Crew depends on it; nothing in src/ changed. Process supervision, model routing, CLI adapters and orchestration loops stay out of Core by design.

docket-crew start     # the one command; everything else happens in the Office

Real runtimes, not mocks

Mocks exist only inside tests. From the first vertical slice this drives the actual binaries installed on the machine:

Runtime Verified
claude 2.1.259 -p --output-format stream-json, resume by session_id
codex-cli 0.151.0 exec --json --sandbox workspace-write, resume by thread_id
opencode 1.18.26 run --format json -m openrouter/…, resume by sessionID

Every contract in crew/docs/RUNTIME-CONTRACTS.md was probed against the real binary, including three traps that break naive implementations:

  • codex hangs on Reading additional input from stdin... unless the prompt is on argv and stdin is closed.
  • opencode's stdout is not clean JSONL — a terminal plugin interleaves OSC escape payloads inline, sometimes glued onto the front of a real JSON line.
  • A prompt starting with - is parsed as a flag. SKILL.md frontmatter made every prompt begin ---, so every turn failed until frontmatter was stripped.

codex exec resume also accepts neither --sandbox nor -C and filters sessions by cwd — so a worker's worktree must outlive its turns. Sessions are keyed by the directory that created them.

Proven end to end

A real Claude manager delegates to a real Codex worker in its own worktree; the worker reports; the manager wakes automatically. No prompt copying.

manager → crew_spawn    → Codex #1
manager → crew_assign   → "Add CHANGELOG.md…"
agent.idle              manager finished its turn
assignment.completed    worker finished
manager.woken           woken by the worker            ← the feature

main untouched, work on crew/<id>-codex, nothing merged or pushed. The manager's session id is identical across both turns — resume works.

Architecture

adapters/ one AgentRuntimeAdapter per CLI, normalising to a single AgentEvent union
supervisor process lifecycle, cancellation, idle-timeout watchdog
orchestrator manager loop, automatic wake, assignments, autonomous-loop guard
mcp/ the crew_* tools real agents call back through, role-scoped
office/ pixel-art office + chat, live over SSE, loopback only
skills/ manager/worker/reviewer behaviour as SKILL.md, not hardcoded in TypeScript

Docket stays the canonical task store — Crew references todos, it does not copy them.

Safety properties (each with tests)

  • A worker only runs in your checkout when you authorised it. The decision lives where turns start, not in one call path, so a new caller cannot add another door.
  • Uncommitted changes refuse isolated work rather than silently handing a worker a different tree than you are looking at.
  • Observed sessions are look-don't-touch. Docket sessions Crew did not launch get no controls in the UI, 409 on every mutation, no RPC access — and their self-reported names are sanitised before they can reach a manager's context.
  • stop leaves zero Crew-owned processes. Interrupted runs are never marked successful.
  • A timeout is not a cancellation. Silence for 10 minutes kills a wedged turn and reports it as a failure with a cause; a deliberate stop is terminal and does not consume the retry budget. Neither invents a verdict about the work — the message points at the worktree diff.

Honest limits

Written down in crew/docs/OPERATIONS.md rather than papered over. The one worth reading before merging:

Agents run as the same uid as the user, with a shell. "Human-only" raises the bar — the capability is no longer handed out by an unauthenticated request — but it is not a boundary: a same-uid process can read the key exactly as the CLI does. Real isolation needs OS-level sandboxing.

Also not done: worktrees and crew/* branches accumulate (made visible, not auto-deleted — Crew cannot know which unmerged branch you still want); state.json's message ledger is unbounded; the reviewer path and opencode-as-worker are implemented and unit-tested but have never been watched end to end with a real model.

Review notes

Three adversarial review passes (concurrency, security, data-loss) ran against this before it was opened, and their findings were fixed in four waves — including several silent-data-loss bugs of the shape this codebase keeps producing: a finished worker's result dropped when the manager was failed, a message queued behind a busy worker that nothing would ever open, two queued assignments sharing one worktree so work landed on the wrong branch and was reported "done" with an empty diff, and agent replies truncated to 200 characters at emit time so the full text existed nowhere.

448 tests, 444 passing, 4 live-gated (they spend real model quota; run with DOCKET_CREW_LIVE=1).

Adds @pasichdev/docket-crew, a separate package layered on Docket Core: a
loopback daemon that launches real coding-agent CLIs, assigns them work,
routes messages between them, isolates coding work in git worktrees, and
serves a live Office UI. Docket Core is untouched — Crew depends on it, not
the other way round.

Real runtimes from the first slice, never mocked outside tests: claude
2.1.259, codex-cli 0.151.0, opencode 1.18.26 (incl. OpenRouter models).
Every CLI contract in docs/RUNTIME-CONTRACTS.md was probed against the real
binary, including the traps that break naive implementations: codex hangs
unless stdin is closed, opencode interleaves terminal OSC escapes into its
JSON stream, and a prompt starting with "-" is parsed as a flag.

Proven end to end: a real Claude manager delegates to a real Codex worker in
its own worktree, the worker reports, and the manager wakes automatically —
no prompt copying. Branch is the deliverable; nothing is merged or pushed.

Layers
- adapters/     one AgentRuntimeAdapter per CLI, normalising to AgentEvent
- supervisor    process lifecycle, cancellation, idle-timeout watchdog
- orchestrator  manager loop, auto-wake, assignments, autonomous-loop guard
- mcp/          the crew_* tool surface real agents call back through
- office/       pixel-art Office + chat, live over SSE
- skills/       manager/worker/reviewer behaviour as SKILL.md, not TypeScript

Safety properties, each with tests
- A worker only runs in the human's checkout when a human authorised it;
  the decision lives where turns start, so a new caller cannot add a door.
- Uncommitted changes refuse isolated work rather than handing a worker a
  different tree than the human is looking at.
- Observed Docket sessions are look-don't-touch: no controls, 409 on every
  mutation, no RPC access, and their self-reported names are sanitised
  before they reach a manager's context.
- `stop` leaves zero Crew-owned processes; interrupted runs are never
  marked successful.

Known limits are documented rather than papered over — see docs/OPERATIONS.md.
Agents run as the same uid as the user, so "human-only" raises the bar but is
not a boundary; that is stated plainly instead of claimed as solved.

448 tests, 444 passing, 4 live-gated (they spend real model quota).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant