MCP · 13. Tool reference, part 2

This is the complete contract for the remaining 49 of the daemon's 118 MCP tools. The first 70 are in chapter 12, whose section 12.0 defines every column used below and is worth reading first.

This chapter is reference.

Two things to know before you use anything here. Ten of these tools are advertised in tools/list but their daemon endpoints return 501 until the orchestrators and punchcards work ships, 13.14 and 13.15 say which. And 24 of the 49 tools in this chapter appear nowhere in the Crux repository's own agent documentation; this reference is their first published description. See chapter 15.

13.1 Coordination: projects, work kanban, live sessions

All nine tools proxy corecruxd over loopback with scope admin:read,facts:write (coordination.rs:18).

ToolDefinitionHandlerSurfacetoken_budgetFlag
list_projectsmod.rs:1753coordination.rs:269[local]-,
get_project_contextmod.rs:1758coordination.rs:275[local]-,
list_workmod.rs:1768coordination.rs:289[local]-ExecPlan projection needs CRUX_EXECPLANS_ROOT
create_workmod.rs:1785coordination.rs:306[local]-,
update_work_statemod.rs:1807coordination.rs:353[local]-,
comment_on_workmod.rs:1827coordination.rs:393[local]-,
status_feedmod.rs:1843coordination.rs:432[local]-CORECRUXD_FEATURE_STATUS_FEED, default off
coord_statusmod.rs:1855coordination.rs:450[local]-CORECRUXD_COORD, default on
coord_announcemod.rs:1866coordination.rs:464[local]-CORECRUXD_COORD, default on

CORECRUXD_COORD defaults on (config.rs:1336); only an explicit 0 or false disables it. The presence window is set by CORECRUXD_COORD_PRESENCE_TTL_SECS (config.rs:1337).

list_projects

No parameters. Output: {projects: [{id, name, planning_target?, default_passport_id, created_at_unix_ms}]}.

get_project_context

ParamTypeRequired
project_idstringyes

Output: {id, name, planning_target?, default_passport_id, members: [{passport_id, role}], tenants: [{tenant_id, default_passport_id?}]}.

list_work

All four parameters are optional; a bare {} lists everything.

ParamTypeRequiredConstraints
project_idstringno
statestringnoOne of planned, in_progress, blocked, archive, complete, deployed
tenant_idstringno
assignee_passportstringno

Output: {count, work: [{id, project_id, state, title, body, assignee_passport?, tenant_id?, linked_pr?, linked_issue?, blocker_reason?, created_by_passport, created_at_unix_ms, updated_at_unix_ms}]}.

The response merges kanban rows with a read-time projection over ExecPlan markdown files, but only when CRUX_EXECPLANS_ROOT is set in the daemon's process environment (work_execplans.rs:58). Unset means no ExecPlan items, silently.

create_work

ParamTypeRequiredConstraints
project_idstringyesMust be an existing project id. There is no implicit default project; an unknown id returns "project not found"
titlestringyes
created_by_passportstringyesTypically your bound session passport
bodystringno
statestringnoThe same six-value set as list_work; defaults to planned
assignee_passportstringnoDefaults to the current passport
tenant_idstringno
linked_prstringno
linked_issuestringno

Output: the created work item.

update_work_state

ParamTypeRequiredConstraints
work_idstringyes
statestringyesThe same six-value set
by_passportstringyes
blocker_reasonstringnoRequired when transitioning to blocked, enforced server-side, not by the schema
blocker_kindstringnoneeds_info or needs_approval; defaults to needs_info on a blocked transition

Output: {applied, work?, queued?} where queued is {action_id, work_id, requested_by_passport, target_state, status: 'pending', requested_at_unix_ms}.

If the caller's passport has agent_work_gate = true, the move queues for human approval (HTTP 202) instead of applying. Check applied, a false there is not an error.

comment_on_work

ParamTypeRequired
work_idstringyes
author_passportstringyes
bodystringyes

Output: {id, work_id, author_passport, body, posted_at_unix_ms}.

status_feed

A glance feed in six verbs: CLAIMED, BLOCKED, HUMAN_HOLD, RESUMED, DONE, FAILED. Read-only.

ParamTypeRequiredDefault
work_idstringno, omit for all items
limitintegerno200, most recent kept

Output: {enabled, events: [{work_id, verb, transition_id, from_state, to_state, by_passport, at_unix_ms}], note?}.

With CORECRUXD_FEATURE_STATUS_FEED unset it returns a disabled notice rather than an error (status_feed.rs:40); check enabled.

coord_status

Who is live right now, and what each session is doing.

ParamTypeRequiredNotes
project_idstringnoSessions bound to no project are always included

Output: {now_unix_ms, presence_ttl_secs, project_id?, active_sessions: [{session_id_hex, passport_id, tenant_id, project_id?, bound_at_unix_ms, last_seen_at_unix_ms, active_until_unix_ms, intent?, leases?}], work_in_flight: [WorkItem]}. An intent is {execplan_slug?, milestone?, paths, note?, announced_at_unix_ms, expires_at_unix_ms}; a lease is {punchcard_id, resource, mode, holder_passport, expires_at_unix_ms}.

coord_announce

Declares this session's focus. A re-announce replaces the previous declaration.

ParamTypeRequiredDefaultConstraints
session_idstringyes-Your bound session id in hex, as minted by cuecrux_session
project_idstringyes-
by_passportstringno-The session binding's passport wins when the session is bound
execplan_slugstringno-
milestonestringno-
deploy_targetstringno-For example deploy:crux. A live peer on the same target produces a deploy_target warning in overlaps[]
pathsarray<string>no-Repo-relative files or directories. Informational only, use punch_in for an enforceable lease
notestringno-
ttl_secondsintegerno14400, four hoursMaximum 86400. 0 clears the intent

Output: {intent, cleared, live_peer_intents, overlaps: [{peer_session_id_hex, peer_passport_id, kind, theirs, yours}]}. kind is execplan, deploy_target, intent_path or lease.

The intent is stored as a private fact attributed to the session's passport. Everything here is advisory. Overlaps never block. The only enforcement seam in the coordination plane is check_punchcard, 13.15.

13.2 GitHub indexed corpus

Loopback scope admin:read (github.rs:16). These tools read a corpus populated by repositories selected through /v1/integrations/github/repos.

ToolDefinitionHandlerSurfacetoken_budgetFlag
github_searchmod.rs:1891github.rs:136[local]-none
github_recent_commitsmod.rs:1908github.rs:154[local]-none
github_open_prsmod.rs:1921github.rs:170[local]-none
github_open_issuesmod.rs:1934github.rs:195[local]-none
github_comments_sincemod.rs:1948github.rs:237[local]-none

None of the five has a feature flag. tools/github.rs contains zero environment reads. They are always listed and always callable, and simply return empty results when nothing has been indexed. Their only precondition is a configured daemon_base_url (github.rs:33). What populates the corpus is separately gated by CORECRUXD_INTEGRATIONS_ENABLED, which defaults on (config.rs:1377).

ToolParamTypeRequiredDefault
github_searchquerystringyes-
repostringno, an owner/repo filter
top_kintegerno20
github_recent_commitsrepostringyes-
limitintegerno20
github_open_prsrepostringyes-
limitintegerno20
github_open_issuesrepostringyes-
labelstringno, exact match
limitintegerno20
github_comments_sincelimitintegerno50

github_comments_since takes no since parameter despite its name. limit is its only parameter. It is "recent comments across selected repos", ordered newest first.

All five return {count, facts: [Fact]}. The value strings hold JSON-encoded records, and the entity prefix tells you which.

ToolEntity formFields in the value JSON
github_recent_commitsgithub::owner/repo::commit/{sha}sha, message, author_name, author_login?, committed_at, parents[], html_url
github_open_prsgithub::owner/repo::pr/{number}title, state, author_login?, head_sha, base_branch, body, merged_at?, closed_at?, html_url
github_open_issuesgithub::owner/repo::issue/{number}title, state, labels[], body, closed_at?, html_url
github_comments_sincegithub::owner/repo::comment/{id}author_login?, body, posted_at, parent_number, html_url
github_searchany of the aboveCommit, PR, issue or comment record, by prefix

13.3 Workspace storyline and repo registration

ToolDefinitionHandlerSurfacetoken_budgetFlag
get_workspace_storylinemod.rs:1960storyline.rs:55[local]-,
register_repomod.rs:1965repos.rs:168[local]-,
list_reposmod.rs:1970repos.rs:198[local]-,

get_workspace_storyline

Per-endpoint call tree from the latest workspace scan. It requires a scan to have been run first, via POST /v1/workspace/scan. Without one there is nothing to return.

ParamTypeRequiredDefaultConstraints
endpointstringno-A 'METHOD PATH' filter, for example 'POST /v1/projects'. Omit for every route
formatstringnotreetree or json. Validated in the handler (storyline.rs:55); anything else returns INVALID_PARAMS
include_testsbooleannofalseInclude edges pointing at test files. Off by default because test code skews density metrics

Output with format=tree: plaintext ASCII tree art as text/plain. With format=json: {files: [{p, c, m, d, f, t}], edges: [[from_id, to_id, count, to_symbol]], routes: [{m, p, h, f, chain: [file_ids]}]}.

register_repo

ParamTypeRequiredDefaultNotes
tenant_idstringyes-Owner of the registration
repo_idstringnoa slug derived from root_path or clone_url
root_pathstringno-Local absolute path. Scanned immediately, and sets last_scan_id
clone_urlstringno-Recorded without cloning; the scan is deferred
languagesarray<string>no[]

Output, proxied from POST /v1/repos: {repo_id, tenant_id, root_path?, clone_url?, languages: [string], enabled, added_at_unix_ms, last_scan_id?}.

list_repos

ParamTypeRequired
tenant_idstringyes

Output, proxied from GET /v1/repos: {repos: [{repo_id, tenant_id, root_path?, clone_url?, languages, enabled, last_scan_id?}]}, scoped to the caller's tenant.

13.4 Substrate: entities

Banner: "Substrate: entities / edges / kinds" (mod.rs:1974). No feature flag gates any substrate tool.

ToolDefinitionHandlerSurfacetoken_budgetFlag
entity_upsertmod.rs:1976entities.rs:31[local]-,
entity_getmod.rs:1993entities.rs:66[local]-,
entity_listmod.rs:2007entities.rs:88[local]-,
entity_deletemod.rs:2020entities.rs:115[local]-,
entity_historymod.rs:2033entities.rs:103[local]-,
ToolParamTypeRequiredDefaultConstraints
entity_upsertkindstringyes-Must be a registered kind; lens crates register kinds at startup
idstringyes-Entity id within its kind
payloadobjectyes-Validated against the kind's JSON-Schema
entity_getkindstringyes-
idstringyes-
include_deletedbooleannofalse
entity_listkindstringno-Omit for all kinds
limitintegerno-No documented cap
include_deletedbooleannofalse
entity_deletekindstringyes-Soft delete; the version chain is preserved
idstringyes-
entity_historykindstringyes-
idstringyes-

Outputs, all inside content: entity_upsert and entity_delete return {entity}; entity_get returns {entity} which may be null; entity_list returns {entities, count} sorted by kind then id; entity_history returns {versions, count} oldest first, where the last entry has deleted: true if the entity was deleted.

An entity record is {kind, id, payload, created_at, updated_at, version, deleted, actor}.

13.5 Substrate: edges

ToolDefinitionHandlerSurfacetoken_budgetFlag
edge_upsertmod.rs:2046edges.rs:33[local]-,
edge_getmod.rs:2063edges.rs:55[local]-,
edge_listmod.rs:2079edges.rs:69[local]-,
edge_deletemod.rs:2096edges.rs:88[local]-,

edge_upsert, edge_get and edge_delete take the full five-tuple, all five required: from_kind, from_id, edge_kind, to_kind, to_id, all strings. edge_upsert additionally accepts an optional payload object.

edge_list takes any prefix of the tuple; every parameter is optional.

ParamTypeRequiredDefault
from_kindstringno-
from_idstringno-
to_kindstringno-
to_idstringno-
edge_kindstringno-
limitintegerno-
include_deletedbooleannofalse

Outputs: edge_upsert and edge_delete return {edge}; edge_get returns {edge} which may be null; edge_list returns {edges, count}.

An edge record is {edge_id, from_kind, from_id, edge_kind, to_kind, to_id, payload, created_at, updated_at, version, deleted, actor}.

13.6 Features lens

ToolDefinitionHandlerSurfacetoken_budgetFlag
feature_file_searchmod.rs:2113features.rs:29[local]-none
feature_coverage_reportmod.rs:2123features.rs:62[local]-none
feature_trigger_auditmod.rs:2128features.rs:74[local]-none
feature_suggest_nextmod.rs:2143features.rs:144[local]-none

tools/features.rs reads no environment variables. All four are always available.

ToolParamTypeRequiredDefaultConstraints
feature_file_searchpathstringyes-Substring-matched against each capability's files list
feature_coverage_reportnone
feature_trigger_auditidstringyes-Capability id
statusstringyes-One of audited, gap, waived, blocked
auditorstringno-
notesstringno-
feature_suggest_nextlimitintegerno5

Outputs: feature_file_search returns {capabilities: [{id, name, system, files}], count}; feature_coverage_report returns {report} where the report holds total_capabilities, total_tested, total_audited, maturity and systems; feature_trigger_audit returns {capability, version} where capability is the updated payload; feature_suggest_next returns {suggestions: [{kind, capability_id?, gap_type?, severity?, promise?, rationale}], count}, derived from gap analysis and the weakest promise.

13.7 Substrate: kinds

Physically these two sit after the features tools, under the Features lens banner (mod.rs:2111), though they belong to the substrate.

ToolDefinitionHandlerSurfacetoken_budgetFlag
kind_listmod.rs:2152kinds.rs:16[local]-,
kind_getmod.rs:2157kinds.rs:37[local]-,

kind_list, no parameters. Output: {kinds: [{kind, description, allowed_outgoing_edges, allowed_incoming_edges}], count}.

kind_get, kind (string, required). Output: {registration}, which may be null, and which includes the kind's JSON-Schema.

13.8 Typed action traces

ToolDefinitionHandlerSurfacetoken_budgetFlag
tool_trace_recentmod.rs:2168tools/traces.rs:73[local]default 2000CORECRUXD_FEATURE_TOOL_TRACES, default on

Per-passport. Reserved-prefix effects are stripped. Schema at tools/traces.rs:42.

ParamTypeRequiredDefaultSource
turn_idstringno-Only traces tagged with this turn
top_kintegerno50DEFAULT_TOP_K, tools/traces.rs:36
token_budgetintegerno2000DEFAULT_TOKEN_BUDGET, tools/traces.rs:40

Output: {content, traces: [{tool, ts_us, turn_id?, predicted_effects: [{kind, entity, key, ts_us?}], outcome}], count, feature_disabled?}.

This is the cleanest token_budget contract in the crate: the default is a named constant, declared in the schema, and applied in the handler at tools/traces.rs:78.

Flag default is on, Err(_) => true at traces.rs:80. Trace retention is CORECRUXD_FEATURE_TOOL_TRACES_TTL_SECS, default 3600 (traces.rs:53). With the flag off the tool returns {traces: [], count: 0, feature_disabled: true} rather than an error.

13.9 Session mining and loop weighting

ToolDefinitionHandlerSurfacetoken_budgetFlag
learnmod.rs:2174tools/learn.rs:60[local]-CORECRUXD_FEATURE_TOOL_TRACES, default on, the same flag as its data source

Mines the calling passport's recent traces for looping re-fetches, a signature repeated three or more times, with pagination variants folded together, and proposes guardrails ranked by measured token waste. Read-only and propose-only: it writes nothing.

ParamTypeRequiredDefaultConstraints
min_repeatsintegerno3minimum: 2; values below 2 are rejected in the handler
scanintegerno1000: DEFAULT_SCAN at tools/learn.rs:33How many recent traces to mine, newest first

Output: {content, proposals: [{signature, occurrences, wasted_tokens, draft_guardrail}], count, scanned, propose_only: true, feature_disabled?}.

learn has no token_budget despite being a scan-shaped tool. It is bounded by scan instead.

13.10 Holdout savings

ToolDefinitionHandlerSurfacetoken_budgetFlag
token_savingsmod.rs:2180token_savings.rs:32[local]-CRUX_OUTPUT_HOLDOUT, a numeric fraction; 0 is off and is the default

No parameters at all, the schema is {"type":"object","properties":{},"examples":[{}]} (token_savings.rs:21).

Output: {content, holdout_enabled, holdout_fraction?, n_control, n_treatment, reduction_pct, ci95_low_pct, ci95_high_pct, control_tokens, treatment_tokens}.

It reports live shaped-versus-unshaped token saving as a point estimate with a 95% confidence interval, never a bare counterfactual. With CRUX_OUTPUT_HOLDOUT at or below zero there is no live control arm, so the tool returns a disabled stub carrying the literal hint set CRUX_OUTPUT_HOLDOUT=0.05 to sample a live control arm (token_savings.rs:35).

The flag is a float clamped to the range 0 to 1 (holdout.rs:38). Setting it also forces the unshaped path in query (query.rs:98) and query_facts (facts.rs:471) for the sampled fraction; it is a live experiment, not a report.

13.11 Activity log

ToolDefinitionHandlerSurfacetoken_budgetFlag
activity_recentmod.rs:2186tools/activity.rs:81[local]default 500CORECRUXD_FEATURE_ACTIVITY_LOG, default off (daemon-side)

Loopback scope facts:read (tools/activity.rs:37). A cheap "what just happened in this session" pull across the activity categories.

ParamTypeRequiredDefaultConstraints
session_idstringyes-
tenant_idstringnodefault
sincestringno-Return only entries with a seq greater than this, exclusive
kindsarray<string>no-question, answer, reasoning, command, fact, execplan, handoff, error. Also accepts a comma-separated string, despite the schema declaring type: array
token_budgetintegerno500Caps response size. An explicit 0 also falls back to 500 (tools/activity.rs:116)

Output: {content, session_id, tenant_id, feature_enabled, token_budget, returned, truncated, rows: [{turn_id?, seq, ts, kind, intent?, tool?, confidence?, fact_refs, receipt_ids, preview}]}, newest first, reserved-prefix stripped, privacy-scoped, budget-trimmed.

The gate is daemon-side (activity.rs:58); MCP maps the resulting HTTP 404 to feature_enabled: false rather than an error (tools/activity.rs:137). Retention is CORECRUXD_FEATURE_ACTIVITY_LOG_TTL_SECS, default 365 days (activity.rs:68).

The tool description at mod.rs:2189 claims "Required: session_id, token_budget". Only session_id is required. The budget defaults to 500.

To dereference a turn to verbatim content, use the daemon's /v1/activity/turn/{turn_id} HTTP route. It is not exposed as an MCP tool.

13.12 Token accounting

ToolDefinitionHandlerSurfacetoken_budgetFlag
session_token_usagemod.rs:2216token_usage.rs:42[local]ignored-
ParamTypeRequiredDefaultNotes
token_budgetintegerno500 in the schemaThe payload is fixed size, so any positive integer is fine. The handler signature ignores its arguments entirely (token_usage.rs:42), the parameter is accepted for conformance and discarded

Output: {content, passport, used, tokens_in, tokens_out, declared_budget_in, calls, estimator, limit?, pct?}.

used is the sum of estimated argument and result tokens across every tools/call this daemon process has dispatched for the passport, at roughly four characters per token. It is comparable, not exact, and it resets when the process restarts. limit comes from CORECRUXD_SESSION_TOKEN_BUDGET; unset or 0 means no limit and pct is omitted. Unauthenticated callers accumulate under the __anon__ sentinel.

CORECRUXD_SESSION_TOKEN_BUDGET is report-only. Exceeding it neither fails nor truncates anything; it only makes pct exceed 100.

13.13 Risk-tiered human-in-the-loop approvals

ToolDefinitionHandlerSurfacetoken_budgetFlag
approval_requestmod.rs:2222approvals.rs:231[local]requiredCORECRUXD_FEATURE_APPROVAL_QUEUE, default off
approval_decidemod.rs:2268approvals.rs:375[local]-same

Slack notification, when configured, is CORECRUXD_APPROVALS_SLACK_WEBHOOK_URL (approvals.rs:191).

approval_request

ParamTypeRequiredDefaultConstraints
action_summarystringyes-
risk_tierstringyes-One of low, medium, high
scopestringyes-Tenant id or scoped resource path
token_budgetintegeryes-Any positive integer. The check is presence only, so 0 passes
tenant_idstringnothe value of scope
payloadobjectno-For example predicted effects, or the original tool call

Output: {content, request_id, status, risk_tier, tenant_id, feature_enabled}. status is pending or feature_disabled.

It returns immediately. High-tier requests block on an operator decision; medium and low may auto-approve per tenant policy. Pending entries appear in list_work(state='pending_approval').

Ordering quirk worth knowing at 3am: the token_budget presence check runs at approvals.rs:279, before the feature-flag check at :317. A call against a disabled feature that omits token_budget reports the missing budget, not the disabled feature. The error is real but it is not the reason nothing happened.

approval_decide

Requires an operator-tier passport. Forwards through the cross-tenant guard: a reviewer in tenant A cannot decide for tenant B.

ParamTypeRequiredConstraints
request_idstringyesMinted by approval_request
decisionstringyesapprove or reject
reviewer_notesstringnoEmbedded in the receipt body
reviewer_tierstringnoIntended to be stamped by the daemon HTTP layer. Tests pass elite or operator
reviewer_tenant_idstringnoUsed to enforce the cross-tenant guard

Output: {content, ok, request_id, status, reviewer_passport, decided_at, receipt_id, receipt_body_hash_hex, tenant_id, risk_tier}. status is approved or rejected. Emits an ApprovalDecision CROWN receipt; the daemon HTTP layer attaches the Ed25519 signature. Non-operator callers receive a 403-style JSON-RPC error carrying why_denied.

Disclosure: reviewer_tier and reviewer_tenant_id are caller-supplied strings in the MCP schema even though they are meant to be stamped by the daemon HTTP layer. An MCP caller can set them directly. The authorisation decision is made downstream at approvals.rs:375; do not treat the MCP-side values as trusted.

13.14 Orchestrators (Package S scaffold)

Status: SCAFFOLD. Every daemon endpoint behind these five tools is stubbed and returns 501 until the orchestrators work ships (mod.rs:2837). They are advertised in tools/list regardless. Calling one is not an error you can fix by configuring anything.

ToolDefinitionHandlerSurfacetoken_budgetFlag
create_orchestratormod.rs:2250orchestrators.rs:52[local]-CORECRUXD_ORCHESTRATORS, default off
attach_to_orchestratormod.rs:2294orchestrators.rs:69[local]-same
detach_from_orchestratormod.rs:2311orchestrators.rs:89[local]-same
list_orchestratorsmod.rs:2326orchestrators.rs:98[local]-same
update_orchestratormod.rs:2338orchestrators.rs:115[local]-same

CORECRUXD_ORCHESTRATORS is read at agentgraph_kinds.rs:144; the route class is at route_auth.rs:527. It uses parsing dialect 5, which accepts on, see 15.1.

ToolParamTypeRequiredConstraints
create_orchestratornamestringyes
created_by_passportstringyes
assignee_passportstringno
tenant_idstringno
statestringnoOne of planned, active, done, archived
attach_to_orchestratororchestrator_idstringyes
member_refstringyesA work item (w_…), an execplan (execplan:…), a handoff (ho_…), or a passport
member_typestringnoOne of passport, work, execplan, handoff; inferred from member_ref when omitted
detach_from_orchestratororchestrator_idstringyes
member_refstringyes
list_orchestratorstenant_idstringno
statestringnoThe same four-value set
update_orchestratororchestrator_idstringyes
namestringno
assignee_passportstringno
statestringnoThe same four-value set; archived closes it out

An orchestrator record is {id, name, assignee_passport, created_by_passport, tenant_id, state, members[], created_at_unix_ms, updated_at_unix_ms}. list_orchestrators returns {count, orchestrators}.

13.15 Punchcards (Package S scaffold)

Status: MIXED. punch_in, punch_out and list_punchcards are documented as 501-until-shipped scaffolds (mod.rs:2842). check_punchcard is live, and is the tool the PreToolUse hook calls before every edit.

ToolDefinitionHandlerSurfacetoken_budgetFlag
punch_inmod.rs:2356punchcards.rs:53[local]-CORECRUXD_PUNCHCARD, default off
punch_outmod.rs:2375punchcards.rs:76[local]-same
list_punchcardsmod.rs:2392punchcards.rs:99[local]-same
force_releasemod.rs:2406punchcards.rs:116[local]-same
check_punchcardmod.rs:2423punchcards.rs:142[local]-same

CORECRUXD_PUNCHCARD is a mode enum, advisory or enforce, off by default (agentgraph_kinds.rs:159). enforce is what makes check_punchcard blocking. Route at punchcards.rs:75.

ToolParamTypeRequiredConstraints
punch_inresourcestringyesfile://<path> or a deploy-target id
holder_passportstringyes
modestringnomodify or deploy
tenant_idstringno
reasonstringno
expires_at_unix_msintegerno
punch_outresourcestringyes
holder_passportstringyes
release_commit_shastringno
tenant_idstringno
list_punchcardsresourcestringno
holder_passportstringno
tenant_idstringno
statusstringnoOne of held, released, expired, force_released
force_releasepunchcard_idstringyes
confirmbooleanyesMust be true. Force-release is destructive and this is the deliberate friction
reasonstringno
by_passportstringnoThe operator passport performing the override
check_punchcardresourcestringyesfile://<path>, tree://<subtree> or service://<name>
modestringnomodify or deploy
passportstringnoThe probing passport; defaults to the calling passport

Outputs: punch_in returns {id, resource, mode, holder_passport, tenant_id, status, acquired_at_unix_ms, expires_at_unix_ms?, receipt_acquire}; punch_out returns the released record with released_at_unix_ms, release_commit_sha? and receipt_release; list_punchcards returns {count, punchcards}; force_release returns the force-released record with force_released_by and receipt_release.

check_punchcard returns {held_by_other, enforce, holder_passport, resource, mode, expires_at_unix_ms}. It always returns 200 and fails open; the PreToolUse hook denies only when held_by_other and enforce are both true. This is the enforcement seam for the whole coordination plane; everything in 13.1 is advisory by comparison.

One schema inconsistency: punch_in's own schema example (mod.rs:2361) shows only the file:// scheme, while tree:// is documented on check_punchcard (mod.rs:2428). tree:// works on both.

Sources

All line references were verified at commit 93b41a7.