HTTP API · 6. Work and coordination

Sixty-five registrations, the largest group in this reference, describe how several agents share one repository without overwriting each other: a work board with approval gates, an advisory coordination plane, orchestrator groupings, path leases, and the project and plane hierarchy that scopes it all.

Planes covered: Work board, gates and status feed · Coordination plane · Orchestrators · Punchcards (leases) · Projects, layers, repos and context graph · Planes · Storybook · Dossiers.

Read chapter 0 first for the auth model, request-field notation and error shape.

Correcting one thing the source itself gets wrong. The route-auth table labels the orchestrator and punchcard feature gate CORECRUXD_AGENTGRAPH (route_auth.rs:528). That environment variable does not exist anywhere else in the codebase. The label is inert, the middleware never reads it, and setting it does nothing. The handlers read CORECRUXD_ORCHESTRATORS (agentgraph_kinds.rs:144) and CORECRUXD_PUNCHCARD (agentgraph_kinds.rs:161). The tables below name the real flags.

And one thing to know about the whole plane's posture. Coordination here is advisory by default. Announcements and overlap warnings never block a write. Punchcards block only in enforce mode, which is not the default. If you need a hard mutual exclusion, CORECRUXD_PUNCHCARD=enforce is the only setting that provides one.

6.1 Work board, gates and status feed

Eleven registrations.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/workwork.rs:154 get_workadmin:read · Readquery: project_id (String, opt), state (String, opt), tenant_id (String, opt), assignee_passport (String, opt), source (WorkSource, dflt), orchestrator (String, dflt)keys work, count, source, approvals400-R
POST/v1/workwork.rs:334 post_workfacts:write, tenant-bound · Writebody: project_id (String, req), title (String, req), created_by_passport (String, req), body (String, dflt), state (String, dflt), assignee_passport (String, dflt), tenant_id (String, dflt), linked_pr (String, dflt), linked_issue (String, dflt)Work item400, 404-W · facts
GET/v1/work/{id}work.rs:317 get_work_itemadmin:read · Readpath idWork item404-R
PATCH/v1/work/{id}work.rs:366 patch_workfacts:write, tenant-bound · Writepath id; body: by_passport (String, req), title (String, dflt), body (String, dflt), state (String, dflt), assignee_passport (nullable String, dflt), tenant_id (nullable String, dflt), linked_pr (nullable String, dflt), linked_issue (nullable String, dflt), blocker_reason (nullable String, dflt), blocker_kind (BlockerKind, dflt)keys work, applied, queued400, 404-W · facts
GET/v1/work/{id}/commentswork.rs:435 get_commentsadmin:read · Readpath idkeys comments, work_id-,R
POST/v1/work/{id}/commentswork.rs:413 post_commentfacts:write, tenant-bound · Writepath id; body: author_passport (String, req), body (String, req)Comment record400, 404-W · facts
GET/v1/work/{id}/transitionswork.rs:453 get_transitionsadmin:read · Readpath idkeys transitions, work_id-,R
GET/v1/work/gate/pendingwork.rs:471 get_pending_gatesadmin:read · Readquery: by_passport (String, opt)keys pending, count-,R
POST/v1/work/gate/{actionId}/approvework.rs:489 post_gate_approvefacts:write, tenant-bound · Writepath actionId; body: approver_passport (String, dflt)Gate resolution-,W
POST/v1/work/gate/{actionId}/rejectwork.rs:498 post_gate_rejectfacts:write, tenant-bound · Writepath actionId; body: approver_passport (String, dflt)Gate resolution-,W
GET/v1/status-feedwork.rs:688 get_status_feedadmin:read · Readquery: work_id (String, opt), limit (usize, opt)keys events, enabled, feature_flag, note-,R

The applied versus queued split on PATCH /v1/work/{id} is the gate mechanism surfacing: a transition that needs approval comes back in queued, not applied, and the call still returns 200. Check queued before assuming a state change took effect. The queued item then appears in GET /v1/work/gate/pending.

GET /v1/status-feed reports its own availability in the response body, enabled, feature_flag and note, rather than returning an error when the underlying feed is not configured. A 200 with enabled: false is the normal shape on a daemon that has not turned it on.

source on GET /v1/work selects between the kanban table and a read-time projection over plan files, or both.

6.2 Coordination plane

Two registrations. This is the live-session board: who is active, what they declared they are working on, and where two sessions overlap.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/coord/activecoord.rs:115 get_coord_activeadmin:read · FeatureGatedquery: project_id (String, opt)Live intents-CORECRUXD_COORD, default onR
POST/v1/coord/announcecoord.rs:165 post_coord_announceany-of facts:write, admin:write · FeatureGatedbody: session_id (String, req), project_id (String, req), by_passport (String, dflt), execplan_slug (String, dflt), milestone (String, dflt), deploy_target (String, dflt), paths (Vec String, dflt), note (String, dflt), ttl_seconds (u64, dflt)keys intent, overlaps, live_peer_intents, cleared400, 500CORECRUXD_COORD, default onW · facts

CORECRUXD_COORD is one of the few flags on this surface that defaults on (config.rs:1336). With it off, both routes return 404.

overlaps never blocks. A re-announce replaces the previous intent for that session. ttl_seconds: 0 clears it. The overlaps array in the response is information for the caller to act on, the daemon has already accepted the announcement by the time you read it. If you want an announcement to stop a write, that is what punchcards in enforce mode are for.

6.3 Orchestrators

Seven registrations, gated off by default behind CORECRUXD_ORCHESTRATORS, not CORECRUXD_AGENTGRAPH, which does not exist.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/orchestratorsorchestrators.rs:268 list_orchestratorsany-of facts:read, admin:read · FeatureGatedquery: assignee (String, opt), tenant_id (String, opt), state (String, opt), limit (usize, opt)keys orchestrators, count-CORECRUXD_ORCHESTRATORS, default offR
POST/v1/orchestratorsorchestrators.rs:211 create_orchestratorany-of facts:write, admin:write · FeatureGatedbody: name (String, req), created_by_passport (String, dflt), assignee_passport (String, dflt), tenant_id (String, dflt), state (String, dflt)keys orchestrator400CORECRUXD_ORCHESTRATORS, default offW
GET/v1/orchestrators/{id}orchestrators.rs:314 get_orchestratorany-of facts:read, admin:read · FeatureGatedpath idkeys orchestrator404CORECRUXD_ORCHESTRATORS, default offR
PATCH/v1/orchestrators/{id}orchestrators.rs:342 patch_orchestratorany-of facts:write, admin:write · FeatureGatedpath id; body: name (String, dflt), assignee_passport (String, dflt), state (String, dflt)keys orchestrator400, 404CORECRUXD_ORCHESTRATORS, default offW
POST/v1/orchestrators/{id}/membersorchestrators.rs:509 add_memberany-of facts:write, admin:write · FeatureGatedpath id; body: member_type (String, dflt)keys orchestrator400, 404CORECRUXD_ORCHESTRATORS, default offW
DELETE/v1/orchestrators/{id}/members/{ref}orchestrators.rs:618 remove_memberany-of facts:write, admin:write · FeatureGatedpath id, refkeys orchestrator400, 404CORECRUXD_ORCHESTRATORS, default offW
GET/v1/orchestrators/{id}/workorchestrators.rs:681 list_orchestrator_workany-of facts:read, admin:read · FeatureGatedpath idkeys members, count, orchestrator_id404CORECRUXD_ORCHESTRATORS, default offR

6.4 Punchcards (leases)

Five registrations, gated by CORECRUXD_PUNCHCARD, which is a three-valued flag, not a boolean.

ValueBehaviour
offThe default. All five routes return 501 (punchcards.rs:598).
advisoryLeases are tracked and reported. A conflicting writer is never denied.
enforceLeases are tracked and enforced. A conflicting writer is denied.
MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/punchcardspunchcards.rs:550 list_punchcardsany-of facts:read, admin:read · FeatureGatedquery: resource (String, opt), holder (String, opt), holder_passport (String, opt), status (String, opt), tenant_id (String, opt)keys punchcards, count501 when offCORECRUXD_PUNCHCARD, default offR
POST/v1/punchcards/acquirepunchcards.rs:322 acquireany-of facts:write, admin:write · FeatureGatedbody: resource (String, req), mode (String, dflt default_mode), reason (String, dflt), ttl_secs (u64, dflt), tenant_id (String, dflt), holder_passport (String, dflt)keys punchcard, punchcard_id, id, status, resource, mode, held_by, holder_passport, reason, tenant_id, acquired_at_unix_ms, expires_at_unix_ms, reentrant, receipt_acquire, error400, 409, 500, 501 when offCORECRUXD_PUNCHCARD, default offW · receipt
POST/v1/punchcards/checkpunchcards.rs:594 checkany-of facts:read, admin:read · FeatureGatedbody: resource (String, req), mode (String, dflt default_mode), passport (String, dflt)keys enforce, held_by_other, holder_passport, mode, resource, expires_at_unix_ms501 when offCORECRUXD_PUNCHCARD, default offR, mounted as a write-class POST
POST/v1/punchcards/releasepunchcards.rs:468 releaseany-of facts:write, admin:write · FeatureGatedbody: id (String, dflt), resource (String, dflt), release_commit_sha (String, dflt), holder_passport (String, dflt)keys punchcard400, 404, 501 when offCORECRUXD_PUNCHCARD, default offW · receipt
POST/v1/punchcards/{id}/force-releasepunchcards.rs:667 force_releaseany-of facts:write, admin:write · FeatureGatedpath id; body: confirm (bool, dflt), reason (String, dflt), by_passport (String, dflt)keys punchcard400, 404, 501 when offCORECRUXD_PUNCHCARD, default offW · receipt

POST /v1/punchcards/check returns enforce in its body so a caller can tell whether a conflict it just found is going to be blocked or merely noted. Read that field before deciding what a held_by_other: true means for you.

409 on acquire is a genuine conflict. reentrant: true means the same holder already had the lease and got it back rather than being refused.

Acquire, release and force-release all write receipts. Force-release requires confirm: true and records a reason, because taking someone else's lease is exactly the action a later reader will want to see justified.

6.5 Projects, layers, repos and the context graph

Sixteen registrations. A project is the top-level container: members, tenants, repository links, a layered instruction store and a derived context graph.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/projectsprojects.rs:50 get_projectsadmin:read · Read-keys projects-,R
POST/v1/projectsprojects.rs:77 post_projectadmin:read in the handler · Writebody: id (String, req), default_passport_id (String, req), name (String, dflt), planning_target (String, dflt), working_tenants (Vec String, dflt)Project record400, 404, 409-W · facts
GET/v1/projects/{id}projects.rs:60 get_projectadmin:read · Readpath idProject record404-R
PATCH/v1/projects/{id}projects.rs:134 patch_projectadmin:read in the handler · Writepath id; body: name (String, dflt), planning_target (nullable String, dflt), default_passport_id (String, dflt), archived (bool, dflt), is_default (bool, dflt)Project record400, 404-W · facts
DELETE/v1/projects/{id}projects.rs:169 delete_projectadmin:read in the handler · Writepath idDeletion result400, 404-W · facts
POST/v1/projects/{id}/passportsprojects.rs:189 post_project_memberadmin:read in the handler · Writepath id; body: passport_id (String, req), role (String, dflt default_role)Membership record400, 404-W · facts
DELETE/v1/projects/{id}/passports/{passportId}projects.rs:213 delete_project_memberadmin:read in the handler · Writepath id, passportIdDeletion result400-W · facts
POST/v1/projects/{id}/tenantsprojects.rs:230 post_project_tenantadmin:read in the handler · Writepath id; body: tenant_id (String, req), default_passport_id (String, dflt)Tenant link400, 404-W · facts
DELETE/v1/projects/{id}/tenants/{tenantId}projects.rs:257 delete_project_tenantadmin:read in the handler · Writepath id, tenantIdDeletion result400-W · facts
GET/v1/projects/{id}/reposprojects.rs:303 get_project_reposadmin:read · Readpath idkeys links, count, project_id-,R
POST/v1/projects/{id}/reposprojects.rs:325 post_project_repofacts:write · Writepath id; body: repo (String, req, owner/repo), plane_id (String, dflt), role (String, dflt default_link_role)Repo link400, 500-W · facts
DELETE/v1/projects/{id}/repos/{owner}/{repo}projects.rs:359 delete_project_repofacts:write · Writepath id, owner, repoDeletion result400-W · facts
GET/v1/projects/{id}/layersprojects.rs:421 get_project_layersadmin:read · Readpath idkeys layers, content, count, fact_id, project_id, stored_at, version-,R
PUT/v1/projects/{id}/layers/{layer}projects.rs:486 put_project_layerfacts:write · Writepath id, layer; body: content (String, req)keys bytes, fact_id, layer, private, project_id, version400-W · receipt, facts
DELETE/v1/projects/{id}/layers/{layer}projects.rs:568 delete_project_layerfacts:write · Writepath id, layerkeys cleared, layer, project_id, version-,W · receipt, facts
GET/v1/projects/{id}/context-graphprojects.rs:546 get_context_graphadmin:read · Readpath id; query: include_workspace (bool, dflt), include_symbols (bool, dflt)Context graph-,R

Nine of these sixteen routes are mutations whose handler check is admin:read. The route-auth contract classes them Write and accepts admin:write, facts:write or integrations:install, so under enforce a write-class scope is required to reach them. Under the default shadow mode the middleware does not block and the handler asks only for a read scope, so on a default install, admin:read can create and delete projects, members and tenant links. Set CORECRUXD_ROUTE_AUTH=enforce if that is not the posture you want.

Layer writes are versioned and produce a receipt. DELETE on a layer returns cleared and a new version rather than removing history.

6.6 Planes (sub-units of a project)

Thirteen registrations. Everything a project has, a plane has a narrower copy of.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/projects/{id}/planesplanes.rs:61 get_planesadmin:read · Readpath idkeys planes, count, project_id-,R
POST/v1/projects/{id}/planesplanes.rs:100 post_planeadmin:read in the handler · Writepath id; body: id (String, req), name (String, dflt), description (String, dflt), default_passport_id (String, dflt)Plane record400, 409-W · facts
GET/v1/projects/{id}/planes/{planeId}planes.rs:83 get_planeadmin:read · Readpath id, planeIdPlane record404-R
DELETE/v1/projects/{id}/planes/{planeId}planes.rs:132 delete_planeadmin:read in the handler · Writepath id, planeIdDeletion result400, 404-W · facts
POST/v1/projects/{id}/planes/{planeId}/passportsplanes.rs:150 post_plane_memberadmin:read in the handler · Writepath id, planeId; body: passport_id (String, req), role (String, dflt default_role)Membership record400, 404-W · facts
DELETE/v1/projects/{id}/planes/{planeId}/passports/{passportId}planes.rs:176 delete_plane_memberadmin:read in the handler · Writepath id, planeId, passportIdDeletion result400-W · facts
POST/v1/projects/{id}/planes/{planeId}/tenantsplanes.rs:193 post_plane_tenantadmin:read in the handler · Writepath id, planeId; body: tenant_id (String, req), default_passport_id (String, dflt)Tenant link400, 404-W · facts
DELETE/v1/projects/{id}/planes/{planeId}/tenants/{tenantId}planes.rs:219 delete_plane_tenantadmin:read in the handler · Writepath id, planeId, tenantIdDeletion result400-W · facts
GET/v1/projects/{id}/planes/{planeId}/layersplanes.rs:236 get_plane_layersadmin:read · Readpath id, planeIdkeys layers, content, count, fact_id, plane_id, project_id, stored_at, version-,R
PUT/v1/projects/{id}/planes/{planeId}/layers/{layer}planes.rs:297 put_plane_layerfacts:write · Writepath id, planeId, layer; body: content (String, req)keys bytes, fact_id, layer, plane_id, private, project_id, version400-W · receipt, facts
DELETE/v1/projects/{id}/planes/{planeId}/layers/{layer}planes.rs:400 delete_plane_layerfacts:write · Writepath id, planeId, layerkeys cleared, layer, plane_id, project_id, version-,W · receipt, facts
POST/v1/projects/{id}/planes/sync-layersplanes.rs:363 post_sync_layersfacts:write · Writepath id; body: source_path (String, req), layer (String, req), max_bytes (usize, dflt default_sync_max_bytes), confirm (bool, dflt)Sync result400, 500-W · facts
GET/v1/projects/{id}/planes/{planeId}/reposprojects.rs:377 get_plane_reposadmin:read · Readpath id, planeIdkeys links, count, plane_id, project_id-,R

POST /v1/projects/{id}/planes/sync-layers reads from a path on the daemon's filesystem (source_path) and writes it into a layer. It takes a confirm flag and a max_bytes cap. Treat it as a privileged local-filesystem read available to any caller who can reach the write class.

sync-layers is registered before {planeId}, so a plane whose id is literally sync-layers is unreachable.

6.7 Storybook

Five registrations. A storybook is a generated, versioned narrative snapshot of a project.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/projects/{id}/storybookstorybook.rs:149 get_latestadmin:read · Readpath idLatest storybook404, 500-R
POST/v1/projects/{id}/storybookstorybook.rs:44 post_generateall-of admin:read and facts:write · Writepath idkeys bytes, section_count, stats, generated_at_unix_ms, generated_by_passport, project_id404, 500-W · receipt, facts
GET/v1/projects/{id}/storybook/versionsstorybook.rs:173 list_versionsadmin:read · Readpath idkeys versions, count, project_id-,R
GET/v1/projects/{id}/storybook/{ts}storybook.rs:193 get_versionadmin:read · Readpath id, tsStorybook at that version404-R
GET/v1/projects/{id}/storybook/diffstorybook.rs:213 get_diffadmin:read · Readpath id; query: a (u64, req), b (u64, req)Diff between two versions404-R

POST /v1/projects/{id}/storybook is one of the fifteen call sites in the daemon that requires two scopes at once, admin:read and facts:write (storybook.rs:49). Either alone is a 403.

versions and diff are static segments registered before {ts}.

6.8 Dossiers

Six registrations. A dossier is an agent's published position on a project: claims, uncertainties, contradictions and open questions, with the evidence it stood on.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/projects/{id}/dossiersdossier.rs:199 list_dossiersadmin:read · Readpath idkeys dossiers, dossier_id, agent_passport, count, generated_at_unix_ms, project_id-,R
POST/v1/projects/{id}/dossiersdossier.rs:97 post_publishall-of admin:read and facts:write · Writepath id; body Dossier: dossier_id (String, req), project_id (String, req), agent_passport (String, req), generated_at_unix_ms (u64, req), based_on (BasedOn, req), claims (Vec Claim, req), uncertainties (Vec Uncertainty, req), contradictions (Vec Contradiction, req), open_questions (Vec String, req), stats (DossierStats, req)keys stored, dossier_id, agent, claim_count400, 500-W
POST/v1/projects/{id}/dossiers/autodossier.rs:68 post_autoall-of admin:read and facts:write · Writepath idGenerated dossier404, 500-W
GET/v1/projects/{id}/dossiers/{dossierId}dossier.rs:229 get_dossieradmin:read · Readpath id, dossierIdDossier404-R
GET/v1/projects/{id}/dossiers/diffdossier.rs:249 get_diffadmin:read · Readpath id; query: a (String, req), b (String, req)Diff between two dossiers404-R
GET/v1/projects/{id}/dossiers/reconciledossier.rs:270 get_reconciliationadmin:read · Readpath idReconciliation across dossiers-,R

Both publish routes require admin:read and facts:write together.

The dossier schema requires uncertainties and contradictions as first-class fields, not optional extras. An agent publishing a position must state what it is unsure about and where it disagrees with itself. GET .../reconcile is what makes that useful: it compares dossiers across agents.

diff and reconcile are static segments registered before {dossierId}.

6.9 Failure modes on this plane

SymptomMost likely cause
Setting CORECRUXD_AGENTGRAPH changes nothingThat variable does not exist. Use CORECRUXD_ORCHESTRATORS and CORECRUXD_PUNCHCARD.
501 from every /v1/punchcards/* routeCORECRUXD_PUNCHCARD is off, which is the default. It is a three-valued flag: off, advisory, enforce.
A punchcard conflict that does not stop the other writeradvisory mode tracks and never denies. Check enforce in the check response.
PATCH /v1/work/{id} returns 200 but the state did not changeThe transition was gated. Look at queued in the response and at GET /v1/work/gate/pending.
GET /v1/status-feed returns 200 with enabled: falseNormal. The feed reports its own availability rather than erroring.
An admin:read token creating or deleting a projectExpected under the default shadow route-auth mode: nine project routes and six plane routes check admin:read in the handler. Set CORECRUXD_ROUTE_AUTH=enforce.
403 on POST /v1/projects/{id}/storybook with facts:write aloneStorybook generate and both dossier publish routes need admin:read and facts:write.
404 on /v1/coord/activeCORECRUXD_COORD has been turned off. It defaults on.
404 on /v1/orchestratorsCORECRUXD_ORCHESTRATORS is off. It is off by default.
An overlap warning that did not prevent a clobberCoordination is advisory. Only CORECRUXD_PUNCHCARD=enforce blocks.

Sources