MCP Task Orchestrator · Field guidePlate 5 of 5
One Docker image, one SQLite file. It runs as a throwaway container per session or as a persistent local server shared by every project, and the same gate logic answers both the MCP tools and the REST API.
/mcp endpoint itself is never authenticated, so keep it on loopback or behind your own network controls.Dashboards, CI jobs and the plugin's own hooks use HTTP under /api/v1, switched on with API_ENABLED=true. An advance over REST runs the same gate logic as the MCP tool.
| Auth mode | How a caller proves itself |
|---|---|
bearer | Static tokens from a secrets file. Each has its own capabilities, scope and expiry. Loaded at startup. |
jwks | Signed JWTs checked against a key set, issuer and audience. Capabilities and scope travel as claims. |
none | An explicit double opt-in. Every request acts as admin. For a loopback port only. |
| Capability | Allows |
|---|---|
read | Every GET, and the event stream |
write-items, write-notes | Creating, editing and deleting items and notes |
advance | Role transitions |
manage-dependencies | Adding and removing edges |
write-config | Pushing per-project config, plans and rules |
admin | All of the above, plus attribution fields and lease overrides |
A token can also be limited to certain project roots and tags. A single item outside that scope returns 403, and lists are filtered.
| To do this | Route under /api/v1 |
|---|---|
| Read or change items | GET /items/{id} · POST /items · PATCH /items/{id} |
| Write a note | PUT /items/{id}/notes/{key} |
| Move an item, or ask what blocks it | POST /items/{id}/advance · GET /items/{id}/gate |
| Search | GET /search · GET /notes/search |
| Push or inspect project config | PUT /roots/{rootId}/config · GET /roots/{rootId}/config/effective |
| Audit who did what | GET /transitions?since=… |
| Inspect or release leases | GET /resources/leases · DELETE /resources/leases/{key} |
| Check liveness, with no auth | GET /health |
| Follow changes live | GET /events |
item.created, item.updated, item.deleted, item.advanced, note.upserted, note.deleted, dependency.added, dependency.removed, scope.entered, scope.left. Two control events always arrive: sync.lost and auth.expired.Editing an item requires If-Match with its current ETag, so a stale client gets a 412 and cannot overwrite. Edits are JSON merge-patch. An Idempotency-Key makes a retried write replay its first result.
Every error is { error, message, details }. A failed gate is 422 gate_blocked with the missing notes. A contended lease is 409 resource_unavailable with a Retry-After header.
The caller is always the credential's principal, on REST and on /mcp alike, and an actor in the body is refused as unknown_parameter. A REST advance is bound by claim ownership and by leases, as on MCP. Lease administration and the event stream exist only here.
The server works with any MCP client. The plugin wires it into a Claude Code session through hooks, in the order a feature normally unfolds.
| Three more hooks | What each does |
|---|---|
| Before a note is written | If the note's schema names a skill and the body looks thin, suggests running that skill first. Advisory only. |
When config.yaml changes | Re-syncs the project config to the server mid-session. |
| When a turn ends | Nudges a retrospective if an item finished on its own and nothing else prompted one. |
| Skills, by purpose | Slash commands under /task-orchestrator: |
|---|---|
| Get set up | quick-start (guided onboarding) · init (project or personal root) · configure-server (transport, REST, config-sync) · adopt-project-scope (migrate an existing database) |
| Shape the rules | manage-schemas (create, edit and validate schemas, traits and identity policy) |
| Day-to-day work | work-summary (dashboard) · create-item · status-progression (which trigger, what is missing) · dependency-manager · batch-complete |
| Run many items | run-wave (attended) · ralph (unattended queue drain) |
| Improve the process | session-retrospective · review-proposals |
Also shipped: agent definitions for implementer, reviewer, planner and test author, the orchestrate skill and four workflow scripts. Plate 8 covers how they run a feature.
When a schema declares seats (plate 4), run-wave reads them to build each item's stages and picks the agent and model for each seat. The stop guard is seat-aware too: it only sends a sub-agent back for notes its own seat owns.
| Topology | Claims | Fits |
|---|---|---|
| Pure orchestration | None | Single developer, sequential pipelines |
| Pure claim, flat items | Every item | Independent parallel work with no hierarchy |
| Pure claim, chains | Every item | Rarely. When a blocker completes, every agent races for the next link. |
| Hybrid: claim the parent, orchestrate the sub-tree | Feature level only | Feature-based development with many agents |
claimed: true; a claim_held error (pick another item); a select with no match (claimed: false, no reason: stop or wait); and a select with a reason (claim_held or dependency_unmet: matches exist but none can be claimed now, retry after retryAfterMs). An actor holds one claim at a time, and each element may carry an idempotencyKey.
Any transition into terminal clears the claim, and a reopened item starts unclaimed. A crashed agent's claim simply expires. Nothing has to clear it: a background sweep only records the expiry event.
Lists and searches show only whether an item is claimed. The holder's identity appears in one place: the claimDetail of items_read context for that item.
/mcp. The recorded actor is the principal's audited id (api:<id> or local) with kind external, and a verification of api-jwks, api-bearer or local.API_ENABLED=true API_AUTH_MODE=jwks API_JWKS_URL=https://idp.example/.well-known/jwks.json API_JWKS_ISSUER=https://idp.example API_JWKS_AUDIENCE=task-orchestrator API_JWKS_ALGORITHMS=EdDSA,RS256
| Setup | The caller of /mcp is |
|---|---|
| stdio | local |
HTTP, REST API off, MCP_ALLOW_UNAUTHENTICATED=true | local |
| HTTP, REST API off, no opt-in | The server refuses to start. |
API_AUTH_MODE=none | local |
API_AUTH_MODE=bearer | api: and the token's id |
API_AUTH_MODE=jwks | api: and the JWT's sub |
Every stdio or local-mode session is the same local caller, so they share claims and note ownership and never contend. Telling agents apart takes a credential or a run seat token for each.
An MCP session is bound to the principal that opened it, and another principal on that session gets a 401. A director credential reaches only the director endpoint. A wrong token is the JSON error envelope, not a JSON-RPC error.
No request carries an actor or a proof any more, so nothing authenticates or attributes a call by identity. The actor_authentication block is removed: a leftover block now stops the server from starting, and the info page (/api/v1/info) no longer reports it.
Always on. Transitions, gates, dependencies and cascades work with no further setup.
Opt-in. Agents take time-limited ownership of an item with the claim op of advance. The server then fences the item to its holder: a transition, note write, item update or dependency write by anyone else is not_claim_holder.
Opt-in and separate from claims. With a bearer or JWKS mode, REST and /mcp both require a credential, so attribution is proven and no longer shared.