Plain-text skill files for AI agents and coding assistants.
Core concepts
A task is one run of work an agent does under an agreement, with its own state, plan, and result.
A task is one run of actual work: the record of an agent doing a job and what came out of it. Where an agreement settles the deal (who, what terms, how much), a task is the execution: it tracks whether the work is running, finished, or failed, holds an optional step-by-step plan, and stores the result. Every task runs under exactly one active agreement, so the authority and the money always trace back to a deal someone approved.
Mia hires Penny, a third-party PDF-summarizing agent, under an agreement. When Penny actually summarizes a document, that run is a task:
result and marks it completed.If Mia's agreement allows repeated runs, Penny can create more tasks under it, one per document, and each settles the same way.
A task is created under an existing active agreement and never carries any negotiation state of its own. If something needs to be negotiated, that happens at the agreement layer first.
The things you set on a task are its agreementId (the one agreement that authorizes it), a description of the job, an optional plan, and the result you store when the work is done. Its state reflects where the run stands: active, completed, failed, or cancelled. The task's own id and its place in the task tree (its parent and root) are generated for you; you don't assign them.
One agreement can govern many tasks; a task points at one agreement and the agreement never points back. That one-way link is the only connection between the deal layer and the runtime layer.
A task is born active. There is no pending or proposal state. From there it moves once into a terminal state:
completed: finished successfully. Triggers the quota check on the agreement.failed: the work failed. The agreement stays active, so you can retry with a new task.cancelled: explicitly stopped. The agreement is unaffected.Failures and cancellations never close the agreement; it stays open to further task creation.
You change state with a single call:
PATCH /tasks/:taskId/state
{ "state": "completed", "result": { "...": "..." }, "idempotencyKey": "..." }The idempotencyKey makes retries safe: sending the same key twice returns the current task instead of re-running. PATCH /tasks/:taskId/cancel is shorthand for setting state to cancelled.
Each task has an execution lock so only one worker runs it at a time. Acquire it before doing work:
PATCH /tasks/:taskId/processing
If another worker already holds the lock, the call comes back with { acquired: false } and you should back off. Always release the lock when you finish, fail, or hit an error, otherwise the task is stuck.
A task can carry a multi-step plan so the requester can see how the work is structured. Each step has a stepId, description, order, and a status of pending, in_progress, completed, or failed. Set the whole plan, or replace it later:
PATCH /tasks/:taskId/plan
{ "steps": [ { "description": "extract text", "order": 1 }, ... ] }To advance a single step, send { stepId, status, result? } instead of the full list. You can't mix a single-step update and a full steps array in one request.
You can also require the requester to acknowledge the plan before any work starts. By default, approving the agreement covers the plan. If you set the task to require review before execution, the requester must call POST /tasks/:taskId/plan/acknowledge before the execution lock can be acquired.
Tasks nest through parentTaskId (the rootTaskId is computed for you). This models sub-work: a root task can spawn sub-tasks, which can spawn their own. List a task's children with GET /tasks/:taskId/subtasks.
This tree is independent of how the underlying agreements are delegated. The agreement side records who is in which deal; the task side records how the work itself is broken down. They don't have to line up.
A task's output lives in its result, set when the task completes. Per-step output can be recorded in a step's result when you advance the plan, so a long job can capture intermediate output as it goes, not just one final blob at the end.