Specification
Tool contracts
The exact arguments, results and errors of every tool. Part of the specification.
Version core-2. Companion to the specification §4. These shapes are normative. Types use the field notation of the core: a bare type name, ? for optional, [] for lists.
Every result is { ok: true, data: <shape below> } or { ok: false, error: { code, message, issues?: string[] } }. All writes take requestId: string.
JSON Schemas (draft 2020-12) for every tool's arguments and result and for the shared shapes are in schemas/core-2/; index.json lists them and their $ids are under https://agentprocess.io/schemas/core-2/. Where a schema and this file disagree, this file wins and the schema is a bug. The agent skill that teaches the loop is skills/agentprocess.
Shared shapes
Run view (returned by start_run, get_run, submit, decide, escalate, cancel):
{
"run": { "id": "r_8f2", "process": "supplier-onboarding", "version": 3, "mode": "live",
"state": "waiting", "outcome": null, "ended": null, "startedBy": "u_mira", "startedAt": "2026-10-06T09:00:00Z",
"updatedAt": "2026-10-06T09:04:12Z" },
"data": {
"inputs": { "vendorName": "Acme", "amount": 48000 },
"steps": {
"check_vendor": {
"output": { "cleared": true, "summary": "No matches on OFAC or EU lists." },
"summary": "Searched both lists by legal name and two known aliases. No hits. Report attached. Did not check beneficial owners; not in scope.",
"evidence": [ { "kind": "file", "ref": "f_91a", "sha256": "4f1c…", "url": "https://…/files/f_91a?x=…" } ],
"next": "approve", "reason": "Vendor is cleared.",
"by": "a_screener", "at": "2026-10-06T09:04:12Z",
"history": [] } } },
"steps": [
{ "id": "check_vendor", "kind": "agent", "state": "done" },
{ "id": "approve", "kind": "approve", "state": "waiting", "due": "2026-10-08T09:04:12Z", "overdue": false },
{ "id": "decline", "kind": "finish", "state": null },
{ "id": "done", "kind": "finish", "state": null } ],
"workItems": [
{ "id": "w_31", "stepId": "approve", "kind": "approve", "text": "Approve only when the vendor is cleared and the spend is justified.",
"output": null, "evidence": null, "next": null, "handoff": null,
"assignedTo": { "group": "g_finance" }, "snapshot": "9b7e…", "due": "2026-10-08T09:04:12Z",
"escalation": null,
"decide": { "tool": "decide", "arguments": { "runId": "r_8f2", "workItemId": "w_31", "snapshot": "9b7e…", "requestId": "<new UUID>" },
"decisions": ["approved", "rejected"] } } ],
"body": "# Supplier onboarding\n\nStart this when …"
}steps[].stateisnullfor a step that is not currently reached. A step rewound by a rejection isnulland keeps itshistory.failedmarks the step whosefaileddecision ended the run; itsnote,byandatare in run data.run.endedisnullwhile the run is active or waiting, and{ "by": "u_ops", "at": "…", "note": "…" }once it ended or was cancelled: the cancel reason, thefailednote, ornote: nullfor a finish.workItems[].assignedTois{ "group": "<id>" }for a role, or{ "person": <person value> }forinitiatoror apersonpath.workItems[].handoffis the same objectclaimreturns: what the previous attempt left, ornull. A task returned by an operator carrieshandoff.returned; a task bounced by a check carrieshandoff.check.workItems[].escalation, when a check was unsure, also carriescheck(the verdict) andcandidate(the held submission);completedwithoutoutputuses the candidate.workItems[].decideis the exact call to make, withdecisionslisting what this item accepts:["completed", "failed"]on a task,["approved", "rejected", "failed"]on an approval,["returned", "completed", "failed"]on an escalated agent step. The person fillsrequestIdand addsdecisionand whatever that decision requires.workItems[].output,evidenceandnextare the step's requirements exactly asclaim.stepgives them: the output fields, the required evidence kinds, and the routes[{ to, when? }]whennextis a list. They arenullon an approval, which takes none of them.workItems[].snapshotis computed at the time of this read. A later read of the same item may return a different value.workItems[].escalationis{ "note": "…", "by": "a_screener", "at": "…" }on an escalated agent step, elsenull.data.steps.<id>for an approval hasdecision,note,by,at,historyand nothing else. After a rejection it keeps these current until the approval completes again.data.steps.<id>for the step whosefaileddecision ended the run hasdecision: "failed",note,by,at.data.steps.<id>for await_forstep hasoutputset to the eventdata.- In
mode: "test", every work item andget_workitem also carries"mode": "test".
Evidence item (in submit, decide): { "kind": "file" | "link", "ref": string }. The server adds sha256 and url to file items in run data.
describe
Arguments: none.
{ "protocol": "agentprocess", "version": "core-2", "profiles": ["parallel-1", "check-1"],
"leaseSeconds": 600, "person": { "format": "email" }, "tools": ["describe", "list_processes", "…"] }list_processes
Arguments: { cursor?: string }. Paged like get_work.
{ "items": [ { "name": "supplier-onboarding", "description": "Check a new supplier …", "version": 3,
"inputs": { "vendorName": "string", "amount": { "type": "number", "description": "Expected annual spend in USD" } } } ],
"nextCursor": null }An item also carries requires when the process declares it (§2.1).
start_run
Arguments: { process: string, version?: number, inputs: object, mode?: "live" | "test", requestId }. version defaults to the latest published. Undeclared or mistyped inputs → invalid with issues. A process with any step assigned to initiator, when the caller is an agent identity → invalid.
Result: the run view.
get_run
Arguments: { runId: string }. Result: the run view.
get_work
Arguments: { limit?: number (1–100, default 20), cursor?: string, process?: string }.
Called by an agent identity:
{ "items": [ { "runId": "r_8f2", "stepId": "check_vendor", "process": "supplier-onboarding", "version": 3,
"mode": "live", "readySince": "2026-10-06T09:00:00Z", "due": null, "overdue": false,
"handoff": null,
"claim": { "tool": "claim", "arguments": { "runId": "r_8f2", "stepId": "check_vendor", "requestId": "<new UUID>" } } } ],
"nextCursor": null }- Ordered by
readySince, oldest first. handoffisnullon a first attempt, else what the previous attempt left:{ "progress": "…" }after an expired lease,{ "returned": { "note": "…", "by": "u_ops", "at": "…" } }after a person sent it back,{ "issues": ["…"] }after a submission was not accepted,{ "check": { … } }after a check sent it back (check profile). Several may be present.- Lists only steps the caller may claim. A server MAY filter further by its own agent configuration; it MUST NOT list a step the caller cannot claim.
Called by a person:
{ "items": [ { "runId": "r_8f2", "process": "supplier-onboarding", "version": 3, "mode": "live",
"workItem": { "id": "w_31", "stepId": "approve", "kind": "approve", "text": "…", "output": null, "evidence": null, "next": null,
"assignedTo": { "group": "g_finance" }, "snapshot": "9b7e…", "due": "…", "escalation": null,
"decide": { "tool": "decide", "arguments": { "runId": "r_8f2", "workItemId": "w_31", "snapshot": "9b7e…", "requestId": "<new UUID>" },
"decisions": ["approved", "rejected", "failed"] } } } ],
"nextCursor": null }- Lists the open work items whose assignment includes the caller, oldest first.
workItemis the same objectget_runreturns.
claim
Arguments: { runId, stepId, requestId }.
{ "token": "eyJ…", "expiresAt": "2026-10-06T09:10:00Z", "mode": "live",
"step": { "id": "check_vendor", "instructions": "Check the vendor against …",
"output": { "cleared": "boolean", "summary": "string" }, "evidence": ["file"],
"next": [ { "to": "approve", "when": "Vendor is cleared on both lists" }, { "to": "decline", "when": "Any sanctions hit" } ] },
"handoff": null,
"body": "# Supplier onboarding\n\n…",
"data": { "inputs": { … }, "steps": { … } } }extensions?: object carries what a server-specific profile adds for this step, keyed as that profile defines. A client that does not know a key ignores it.
Errors: conflict with issues: ["claimed_by_other"] when another caller holds a live claim, ["claimed_by_you"] when the caller does; conflict with ["not_ready"] otherwise; forbidden for a non-agent step; invalid for a token that does not verify, on any tool that takes one.
Replay with the same requestId: the original result, even if the lease has since ended; read expiresAt.
renew
Arguments: { token, progress?: string, requestId }. progress is kept on the step while claimed and handed to the next claimant if this lease expires.
{ "token": "eyJ…new", "expiresAt": "2026-10-06T09:20:00Z" }The previous token is invalid once this returns. After the lease ended: stale.
upload
Arguments: { fileName: string, contentType: string, contentBase64: string, requestId }. Size limit from describe.maxUploadBytes (default 10 MiB).
{ "id": "f_91a", "sha256": "4f1c…", "bytes": 182311 }submit
Arguments: { token, output: object, evidence?: EvidenceItem[], summary: string, next?: string, reason?: string, requestId }. summary is 1–2000 characters.
Result: the run view. ok: true means the submission was accepted: the step is done, or, when a check was unsure, the step is waiting on a person and the view shows the escalation work item (check profile).
Errors:
not_accepted,issuessuch as"summary: missing","output.summary: missing","output.cleared: expected boolean","output.extra: not declared","evidence: file required","evidence f_000: not found","next: required, one of approve, decline","reason: required". Nothing changed; the claim stands.stalewhen the token's lease ended or the claim was replaced.conflictwithissues: ["run_ended"]when the run has ended or been cancelled, checked before anything else.
escalate
Arguments: { token, note: string, requestId }. Result: the run view. The step is waiting; a work item exists with escalation set; the token is invalid.
decide
Arguments:
{ runId, workItemId, snapshot,
decision: "completed" | "approved" | "rejected" | "returned" | "failed",
output?: object, evidence?: EvidenceItem[], summary?: string, next?: string, reason?: string, note?: string,
requestId }| decision | Allowed on | Requires |
|---|---|---|
completed | task, escalated agent step | output, evidence per the step; next and reason when the step's next is a list |
approved | approve | nothing |
rejected | approve | note |
returned | escalated agent step | note |
failed | any | note |
next and reason on any decision other than completed → invalid; on completed for a step whose next is not a list → not_accepted. note with completed → invalid; output, evidence or summary with anything but completed → invalid.
Result: the run view. Errors, in the order checked: conflict with issues: ["run_ended"] when the run has ended or been cancelled; conflict with issues: ["already_decided"] when the item was decided; not_found when it never existed on this run; stale when snapshot is not current, in which case get_run returns a fresh one and the same decision may be sent again; forbidden for a non-human identity or a person outside the assignment; not_accepted as for submit.
cancel
Arguments: { runId, reason: string, requestId }. Result: the run view with state: "cancelled" and run.ended: { by, at, note: reason }. On an ended run: conflict with issues: ["run_ended"].
send_event
Arguments: { runId, name: string, data?: object, requestId }. From an agent identity or an operator; forbidden otherwise.
{ "consumedBy": "settlement_event" }consumedBy is null when the event is held: for a wait not yet ready, or for no wait at all. A held event with no matching wait stays held until the run ends and is then discarded. Held events with the same name are consumed oldest first. On an ended run: conflict with issues: ["run_ended"].
list_runs
Arguments: { process?: string, state?: string, inputs?: object, limit?: number, cursor?: string }. inputs matches on equality of the given fields.
{ "items": [ { "id": "r_8f2", "process": "supplier-onboarding", "version": 3, "state": "waiting", "outcome": null,
"startedAt": "…", "updatedAt": "…", "inputs": { "vendorName": "Acme", "amount": 48000 } } ],
"nextCursor": null }