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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/work | work.rs:154 get_work | admin:read · Read | query: 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, approvals | 400 | - | R |
POST | /v1/work | work.rs:334 post_work | facts:write, tenant-bound · Write | body: 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 item | 400, 404 | - | W · facts |
GET | /v1/work/{id} | work.rs:317 get_work_item | admin:read · Read | path id | Work item | 404 | - | R |
PATCH | /v1/work/{id} | work.rs:366 patch_work | facts:write, tenant-bound · Write | path 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, queued | 400, 404 | - | W · facts |
GET | /v1/work/{id}/comments | work.rs:435 get_comments | admin:read · Read | path id | keys comments, work_id | - | , | R |
POST | /v1/work/{id}/comments | work.rs:413 post_comment | facts:write, tenant-bound · Write | path id; body: author_passport (String, req), body (String, req) | Comment record | 400, 404 | - | W · facts |
GET | /v1/work/{id}/transitions | work.rs:453 get_transitions | admin:read · Read | path id | keys transitions, work_id | - | , | R |
GET | /v1/work/gate/pending | work.rs:471 get_pending_gates | admin:read · Read | query: by_passport (String, opt) | keys pending, count | - | , | R |
POST | /v1/work/gate/{actionId}/approve | work.rs:489 post_gate_approve | facts:write, tenant-bound · Write | path actionId; body: approver_passport (String, dflt) | Gate resolution | - | , | W |
POST | /v1/work/gate/{actionId}/reject | work.rs:498 post_gate_reject | facts:write, tenant-bound · Write | path actionId; body: approver_passport (String, dflt) | Gate resolution | - | , | W |
GET | /v1/status-feed | work.rs:688 get_status_feed | admin:read · Read | query: 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/coord/active | coord.rs:115 get_coord_active | admin:read · FeatureGated | query: project_id (String, opt) | Live intents | - | CORECRUXD_COORD, default on | R |
POST | /v1/coord/announce | coord.rs:165 post_coord_announce | any-of facts:write, admin:write · FeatureGated | body: 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, cleared | 400, 500 | CORECRUXD_COORD, default on | W · 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/orchestrators | orchestrators.rs:268 list_orchestrators | any-of facts:read, admin:read · FeatureGated | query: assignee (String, opt), tenant_id (String, opt), state (String, opt), limit (usize, opt) | keys orchestrators, count | - | CORECRUXD_ORCHESTRATORS, default off | R |
POST | /v1/orchestrators | orchestrators.rs:211 create_orchestrator | any-of facts:write, admin:write · FeatureGated | body: name (String, req), created_by_passport (String, dflt), assignee_passport (String, dflt), tenant_id (String, dflt), state (String, dflt) | keys orchestrator | 400 | CORECRUXD_ORCHESTRATORS, default off | W |
GET | /v1/orchestrators/{id} | orchestrators.rs:314 get_orchestrator | any-of facts:read, admin:read · FeatureGated | path id | keys orchestrator | 404 | CORECRUXD_ORCHESTRATORS, default off | R |
PATCH | /v1/orchestrators/{id} | orchestrators.rs:342 patch_orchestrator | any-of facts:write, admin:write · FeatureGated | path id; body: name (String, dflt), assignee_passport (String, dflt), state (String, dflt) | keys orchestrator | 400, 404 | CORECRUXD_ORCHESTRATORS, default off | W |
POST | /v1/orchestrators/{id}/members | orchestrators.rs:509 add_member | any-of facts:write, admin:write · FeatureGated | path id; body: member_type (String, dflt) | keys orchestrator | 400, 404 | CORECRUXD_ORCHESTRATORS, default off | W |
DELETE | /v1/orchestrators/{id}/members/{ref} | orchestrators.rs:618 remove_member | any-of facts:write, admin:write · FeatureGated | path id, ref | keys orchestrator | 400, 404 | CORECRUXD_ORCHESTRATORS, default off | W |
GET | /v1/orchestrators/{id}/work | orchestrators.rs:681 list_orchestrator_work | any-of facts:read, admin:read · FeatureGated | path id | keys members, count, orchestrator_id | 404 | CORECRUXD_ORCHESTRATORS, default off | R |
6.4 Punchcards (leases)
Five registrations, gated by CORECRUXD_PUNCHCARD, which is a three-valued flag, not a boolean.
| Value | Behaviour |
|---|---|
off | The default. All five routes return 501 (punchcards.rs:598). |
advisory | Leases are tracked and reported. A conflicting writer is never denied. |
enforce | Leases are tracked and enforced. A conflicting writer is denied. |
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/punchcards | punchcards.rs:550 list_punchcards | any-of facts:read, admin:read · FeatureGated | query: resource (String, opt), holder (String, opt), holder_passport (String, opt), status (String, opt), tenant_id (String, opt) | keys punchcards, count | 501 when off | CORECRUXD_PUNCHCARD, default off | R |
POST | /v1/punchcards/acquire | punchcards.rs:322 acquire | any-of facts:write, admin:write · FeatureGated | body: 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, error | 400, 409, 500, 501 when off | CORECRUXD_PUNCHCARD, default off | W · receipt |
POST | /v1/punchcards/check | punchcards.rs:594 check | any-of facts:read, admin:read · FeatureGated | body: resource (String, req), mode (String, dflt default_mode), passport (String, dflt) | keys enforce, held_by_other, holder_passport, mode, resource, expires_at_unix_ms | 501 when off | CORECRUXD_PUNCHCARD, default off | R, mounted as a write-class POST |
POST | /v1/punchcards/release | punchcards.rs:468 release | any-of facts:write, admin:write · FeatureGated | body: id (String, dflt), resource (String, dflt), release_commit_sha (String, dflt), holder_passport (String, dflt) | keys punchcard | 400, 404, 501 when off | CORECRUXD_PUNCHCARD, default off | W · receipt |
POST | /v1/punchcards/{id}/force-release | punchcards.rs:667 force_release | any-of facts:write, admin:write · FeatureGated | path id; body: confirm (bool, dflt), reason (String, dflt), by_passport (String, dflt) | keys punchcard | 400, 404, 501 when off | CORECRUXD_PUNCHCARD, default off | W · 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/projects | projects.rs:50 get_projects | admin:read · Read | - | keys projects | - | , | R |
POST | /v1/projects | projects.rs:77 post_project | admin:read in the handler · Write | body: id (String, req), default_passport_id (String, req), name (String, dflt), planning_target (String, dflt), working_tenants (Vec String, dflt) | Project record | 400, 404, 409 | - | W · facts |
GET | /v1/projects/{id} | projects.rs:60 get_project | admin:read · Read | path id | Project record | 404 | - | R |
PATCH | /v1/projects/{id} | projects.rs:134 patch_project | admin:read in the handler · Write | path id; body: name (String, dflt), planning_target (nullable String, dflt), default_passport_id (String, dflt), archived (bool, dflt), is_default (bool, dflt) | Project record | 400, 404 | - | W · facts |
DELETE | /v1/projects/{id} | projects.rs:169 delete_project | admin:read in the handler · Write | path id | Deletion result | 400, 404 | - | W · facts |
POST | /v1/projects/{id}/passports | projects.rs:189 post_project_member | admin:read in the handler · Write | path id; body: passport_id (String, req), role (String, dflt default_role) | Membership record | 400, 404 | - | W · facts |
DELETE | /v1/projects/{id}/passports/{passportId} | projects.rs:213 delete_project_member | admin:read in the handler · Write | path id, passportId | Deletion result | 400 | - | W · facts |
POST | /v1/projects/{id}/tenants | projects.rs:230 post_project_tenant | admin:read in the handler · Write | path id; body: tenant_id (String, req), default_passport_id (String, dflt) | Tenant link | 400, 404 | - | W · facts |
DELETE | /v1/projects/{id}/tenants/{tenantId} | projects.rs:257 delete_project_tenant | admin:read in the handler · Write | path id, tenantId | Deletion result | 400 | - | W · facts |
GET | /v1/projects/{id}/repos | projects.rs:303 get_project_repos | admin:read · Read | path id | keys links, count, project_id | - | , | R |
POST | /v1/projects/{id}/repos | projects.rs:325 post_project_repo | facts:write · Write | path id; body: repo (String, req, owner/repo), plane_id (String, dflt), role (String, dflt default_link_role) | Repo link | 400, 500 | - | W · facts |
DELETE | /v1/projects/{id}/repos/{owner}/{repo} | projects.rs:359 delete_project_repo | facts:write · Write | path id, owner, repo | Deletion result | 400 | - | W · facts |
GET | /v1/projects/{id}/layers | projects.rs:421 get_project_layers | admin:read · Read | path id | keys layers, content, count, fact_id, project_id, stored_at, version | - | , | R |
PUT | /v1/projects/{id}/layers/{layer} | projects.rs:486 put_project_layer | facts:write · Write | path id, layer; body: content (String, req) | keys bytes, fact_id, layer, private, project_id, version | 400 | - | W · receipt, facts |
DELETE | /v1/projects/{id}/layers/{layer} | projects.rs:568 delete_project_layer | facts:write · Write | path id, layer | keys cleared, layer, project_id, version | - | , | W · receipt, facts |
GET | /v1/projects/{id}/context-graph | projects.rs:546 get_context_graph | admin:read · Read | path 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/projects/{id}/planes | planes.rs:61 get_planes | admin:read · Read | path id | keys planes, count, project_id | - | , | R |
POST | /v1/projects/{id}/planes | planes.rs:100 post_plane | admin:read in the handler · Write | path id; body: id (String, req), name (String, dflt), description (String, dflt), default_passport_id (String, dflt) | Plane record | 400, 409 | - | W · facts |
GET | /v1/projects/{id}/planes/{planeId} | planes.rs:83 get_plane | admin:read · Read | path id, planeId | Plane record | 404 | - | R |
DELETE | /v1/projects/{id}/planes/{planeId} | planes.rs:132 delete_plane | admin:read in the handler · Write | path id, planeId | Deletion result | 400, 404 | - | W · facts |
POST | /v1/projects/{id}/planes/{planeId}/passports | planes.rs:150 post_plane_member | admin:read in the handler · Write | path id, planeId; body: passport_id (String, req), role (String, dflt default_role) | Membership record | 400, 404 | - | W · facts |
DELETE | /v1/projects/{id}/planes/{planeId}/passports/{passportId} | planes.rs:176 delete_plane_member | admin:read in the handler · Write | path id, planeId, passportId | Deletion result | 400 | - | W · facts |
POST | /v1/projects/{id}/planes/{planeId}/tenants | planes.rs:193 post_plane_tenant | admin:read in the handler · Write | path id, planeId; body: tenant_id (String, req), default_passport_id (String, dflt) | Tenant link | 400, 404 | - | W · facts |
DELETE | /v1/projects/{id}/planes/{planeId}/tenants/{tenantId} | planes.rs:219 delete_plane_tenant | admin:read in the handler · Write | path id, planeId, tenantId | Deletion result | 400 | - | W · facts |
GET | /v1/projects/{id}/planes/{planeId}/layers | planes.rs:236 get_plane_layers | admin:read · Read | path id, planeId | keys 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_layer | facts:write · Write | path id, planeId, layer; body: content (String, req) | keys bytes, fact_id, layer, plane_id, private, project_id, version | 400 | - | W · receipt, facts |
DELETE | /v1/projects/{id}/planes/{planeId}/layers/{layer} | planes.rs:400 delete_plane_layer | facts:write · Write | path id, planeId, layer | keys cleared, layer, plane_id, project_id, version | - | , | W · receipt, facts |
POST | /v1/projects/{id}/planes/sync-layers | planes.rs:363 post_sync_layers | facts:write · Write | path id; body: source_path (String, req), layer (String, req), max_bytes (usize, dflt default_sync_max_bytes), confirm (bool, dflt) | Sync result | 400, 500 | - | W · facts |
GET | /v1/projects/{id}/planes/{planeId}/repos | projects.rs:377 get_plane_repos | admin:read · Read | path id, planeId | keys 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/projects/{id}/storybook | storybook.rs:149 get_latest | admin:read · Read | path id | Latest storybook | 404, 500 | - | R |
POST | /v1/projects/{id}/storybook | storybook.rs:44 post_generate | all-of admin:read and facts:write · Write | path id | keys bytes, section_count, stats, generated_at_unix_ms, generated_by_passport, project_id | 404, 500 | - | W · receipt, facts |
GET | /v1/projects/{id}/storybook/versions | storybook.rs:173 list_versions | admin:read · Read | path id | keys versions, count, project_id | - | , | R |
GET | /v1/projects/{id}/storybook/{ts} | storybook.rs:193 get_version | admin:read · Read | path id, ts | Storybook at that version | 404 | - | R |
GET | /v1/projects/{id}/storybook/diff | storybook.rs:213 get_diff | admin:read · Read | path id; query: a (u64, req), b (u64, req) | Diff between two versions | 404 | - | 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/projects/{id}/dossiers | dossier.rs:199 list_dossiers | admin:read · Read | path id | keys dossiers, dossier_id, agent_passport, count, generated_at_unix_ms, project_id | - | , | R |
POST | /v1/projects/{id}/dossiers | dossier.rs:97 post_publish | all-of admin:read and facts:write · Write | path 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_count | 400, 500 | - | W |
POST | /v1/projects/{id}/dossiers/auto | dossier.rs:68 post_auto | all-of admin:read and facts:write · Write | path id | Generated dossier | 404, 500 | - | W |
GET | /v1/projects/{id}/dossiers/{dossierId} | dossier.rs:229 get_dossier | admin:read · Read | path id, dossierId | Dossier | 404 | - | R |
GET | /v1/projects/{id}/dossiers/diff | dossier.rs:249 get_diff | admin:read · Read | path id; query: a (String, req), b (String, req) | Diff between two dossiers | 404 | - | R |
GET | /v1/projects/{id}/dossiers/reconcile | dossier.rs:270 get_reconciliation | admin:read · Read | path id | Reconciliation 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
| Symptom | Most likely cause |
|---|---|
Setting CORECRUXD_AGENTGRAPH changes nothing | That variable does not exist. Use CORECRUXD_ORCHESTRATORS and CORECRUXD_PUNCHCARD. |
501 from every /v1/punchcards/* route | CORECRUXD_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 writer | advisory mode tracks and never denies. Check enforce in the check response. |
PATCH /v1/work/{id} returns 200 but the state did not change | The transition was gated. Look at queued in the response and at GET /v1/work/gate/pending. |
GET /v1/status-feed returns 200 with enabled: false | Normal. The feed reports its own availability rather than erroring. |
An admin:read token creating or deleting a project | Expected 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 alone | Storybook generate and both dossier publish routes need admin:read and facts:write. |
404 on /v1/coord/active | CORECRUXD_COORD has been turned off. It defaults on. |
404 on /v1/orchestrators | CORECRUXD_ORCHESTRATORS is off. It is off by default. |
| An overlap warning that did not prevent a clobber | Coordination is advisory. Only CORECRUXD_PUNCHCARD=enforce blocks. |
Sources
- crates/corecruxd/src/http/route_auth.rs:528, the
CORECRUXD_AGENTGRAPHmislabel - crates/corecruxd/src/agentgraph_kinds.rs:144,
CORECRUXD_ORCHESTRATORS, the real flag - crates/corecruxd/src/agentgraph_kinds.rs:161,
CORECRUXD_PUNCHCARDand its three modes - crates/corecruxd/src/http/punchcards.rs:598, the
501when punchcards are off - crates/corecruxd/src/http/coord.rs:123, coord read scope
- crates/corecruxd/src/http/coord.rs:173, coord announce scopes
- crates/corecruxd/src/config.rs:1336,
CORECRUXD_COORDdefaults on - crates/corecruxd/src/http/work.rs:575, tenant-bound
facts:writeon work mutations - crates/corecruxd/src/http/orchestrators.rs:219, orchestrator write scopes
- crates/corecruxd/src/http/punchcards.rs:330, punchcard write scopes
- crates/corecruxd/src/http/storybook.rs:49, all-of
admin:readandfacts:write - crates/corecruxd/src/http/dossier.rs:73, all-of
admin:readandfacts:write

