Skip to content
Docs menu

plans

The planner (M16) — a frontier model orchestrates the three layers for one task on one device through a closed step vocabulary; it can only request approvals (a human signs them), never opens a session, never names a joint. Recorded as an orchestration run.

5 operations · 9 schemas

GET /v1/plans

List plans

listPlans · scope key:read

Plans visible to the caller (owner, creator, or org member), newest first. No plan is public.

Parameters

listPlans parameters
NameInTypeDescription
statusqueryPlanStatus
device_idqueryId
run_idqueryId

The plan recorded as this orchestration run (at most one).

limitqueryinteger

default 20 · ≥ 1 · ≤ 100

Page size.

cursorquerystring

length ≤ 512

Opaque cursor from the previous page's next_cursor.

Responses

listPlans responses
StatusDescriptionBody
200

Page of plans.

PlanPage
401

Missing or invalid credentials.

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json

POST /v1/plans

Plan a task for a robot (the planner makes its first decision)

createPlan · scope key:train

{task, device, budget_usd, max_minutes} → a plan. The hub records an orchestration run (with parent_run_id lineage when given), asks the planner model for its first decision synchronously, executes what it can (a request_approval step files an approval request on the device at once) and returns the plan with its steps and timeline.

The planner's vocabulary is closed — request_approval, await_started, open_session, wait, evaluate, hand_back — and six guarantees are enforced in code on every decision: only those kinds; no step text names a joint, a torque, a velocity, a gain or a raw action; a request_approval may only name an endpoint or model the device's registered contracts allow (checked before any request is filed); nothing runs on the device's behalf before the device's own signed started; every step expires inside the plan's window; every step's cumulative cost cap lies within the budget. A violating decision is retried once with the errors quoted back, then the plan is failed and the response is 502 carrying them.

The plan never approves anything (createApproval is session-only), never opens a session (the device does) and never touches an endpoint beyond reading it. It acts as the credential that created it, narrowed to write, for the one write it makes: filing requests. Spends model dollars: key:train, and every decision — this one and each later one — counts against the coach's daily quota (429 with X-Coach-Quota-* headers when the first cannot be made).

Request body

application/json · required · PlanCreate

createPlan request body
FieldTypeDescription
taskrequiredstring

length 1–4000

What the robot should do, in plain words.

devicerequiredstring

length 1–160

A device id (dev_...), a device name, or owner/name among the devices the caller can see.

budget_usdnumber

default 1 · > 0 · ≤ 100

Cap on the plan's model spend in USD; the plan ends budget_exhausted at it.

max_minutesinteger

default 5 · ≥ 1 · ≤ 1440

The plan's window; it hands back when it passes. Every step and every approval fits inside it.

parent_run_idstring | null

Lineage — the run (a training run or another plan's run) this plan follows from. An unreadable one is 422.

Responses

createPlan responses
StatusDescriptionBody
201

Plan created; its first decision was made and executed as far as it could go.

Plan
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json
502

The planner's first decision failed validation twice. The plan exists, is failed, and its timeline carries the errors; errors[] here carries them too.

Problemapplication/problem+json
503

A service this endpoint needs is not configured in this deployment (M0's boot guarantee: the API boots with zero secrets, and an endpoint that needs one says so instead of returning a stack trace).

Problemapplication/problem+json

GET /v1/plans/{plan_id}

Get a plan (steps, timeline, what it is waiting on)

getPlan · scope key:read

Parameters

getPlan parameters
NameInTypeDescription
plan_idrequiredpathId

Plan id (plan_...).

Responses

getPlan responses
StatusDescriptionBody
200

The plan.

Plan
401

Missing or invalid credentials.

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json

POST /v1/plans/{plan_id}/advance

Run one orchestrator tick on a plan (idempotent)

advancePlan · scope key:train

The orchestrator's beat, callable by hand. Re-reads the device's events, the endpoint session metering and the approval state; expires a step whose window passed; executes the current step when it can; and asks the model for the next decision only when no step is pending or the last one failed or expired. Takes a compare-and-set lock on the plan, so two ticks at once cannot both decide, and a tick with nothing due changes nothing. The same function runs on a schedule (the arq cron, or the API's in-process ticker when PLAN_TICK_INTERVAL_S is set), so calling this is never required. A decision made here counts against the coach's daily quota; when the account's quota is exhausted the plan hands back rather than answering 429.

Parameters

advancePlan parameters
NameInTypeDescription
plan_idrequiredpathId

Plan id (plan_...).

Responses

advancePlan responses
StatusDescriptionBody
200

The plan after the tick (unchanged when nothing was due).

Plan
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
502

A decision made during this tick failed validation twice; the plan is failed.

Problemapplication/problem+json
503

A service this endpoint needs is not configured in this deployment (M0's boot guarantee: the API boots with zero secrets, and an endpoint that needs one says so instead of returning a stack trace).

Problemapplication/problem+json

POST /v1/plans/{plan_id}/cancel

Cancel a plan

cancelPlan · scope key:train

Ends an active plan (canceled) and its run. A pending approval request the plan filed stays for a person to deny in the web app, and an issued approval ends on its own window — the plan can request consent but cannot revoke it, and says so in its final event. 409 when the plan already ended.

Parameters

cancelPlan parameters
NameInTypeDescription
plan_idrequiredpathId

Plan id (plan_...).

Responses

cancelPlan responses
StatusDescriptionBody
200

The canceled plan.

Plan
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json

Schemas (9)

The schemas these operations reach before any other tag’s do. A type that links elsewhere is rendered on that tag’s page.

PlanPage

object

PlanPage fields
FieldTypeDescription
itemsrequiredarray of Plan
next_cursorrequiredstring | null

PlanCreate

object

PlanCreate fields
FieldTypeDescription
taskrequiredstring

length 1–4000

What the robot should do, in plain words.

devicerequiredstring

length 1–160

A device id (dev_...), a device name, or owner/name among the devices the caller can see.

budget_usdnumber

default 1 · > 0 · ≤ 100

Cap on the plan's model spend in USD; the plan ends budget_exhausted at it.

max_minutesinteger

default 5 · ≥ 1 · ≤ 1440

The plan's window; it hands back when it passes. Every step and every approval fits inside it.

parent_run_idstring | null

Lineage — the run (a training run or another plan's run) this plan follows from. An unreadable one is 422.

Plan

object

One orchestration and its whole timeline. run_id is the kind: orchestration run it is recorded as; cost_usd is model spend metered per decision (retries included); steps are in execution order; events oldest first.

Plan fields
FieldTypeDescription
idrequiredId
run_idrequiredId
parent_run_idstring | null
statusrequiredPlanStatus
status_reasonstring | null
taskrequiredstring
devicerequiredPlanDevice
ownerrequiredRepoOwner
created_byUserPublic | null
budget_usdrequirednumber
cost_usdrequirednumber

Model spend so far, USD (6 decimal places).

pricedrequiredboolean

False when the model has no rate row; usage is still recorded.

max_minutesrequiredinteger
expires_atrequiredstring (date-time)
decisionsrequiredinteger
max_decisionsrequiredinteger

The hub's cap on decisions per plan (PLAN_MAX_DECISIONS).

attemptsrequiredinteger

Model calls including retries.

modelstring | null
backendstring | null

anthropic or recorded; a replayed plan must never read as a real one.

current_request_idstring | null
current_approval_idstring | null
waiting_onPlanWaiting | null
stepsrequiredarray of PlanStep
eventsrequiredarray of PlanEvent
next_tick_atstring (date-time) | null (date-time)
created_atrequiredstring (date-time)
updated_atrequiredstring (date-time)
finished_atstring (date-time) | null (date-time)

PlanDevice

object

PlanDevice fields
FieldTypeDescription
idstring | null

Null once the device row is gone; the name stays.

namerequiredstring

PlanWaiting

object

What the plan is blocked on right now — the card the plan page shows.

PlanWaiting fields
FieldTypeDescription
kindrequiredstring

one of approval · started · session · event · time · step

step_idrequiredId
messagerequiredstring
request_idstring | null
approval_idstring | null
untilstring (date-time) | null (date-time)

When the waiting step expires.

approve_inrequiredstring

Where a person approves — the web app's Devices page. No API key can.

PlanStep

object

PlanStep fields
FieldTypeDescription
idrequiredId
indexrequiredinteger

Position in the plan (0-based, execution order).

decisionrequiredinteger

Which decision (1-based) proposed the step.

kindrequiredPlanStepKind
paramsrequiredobject

The kind-specific fields the planner set: model_or_endpoint, minutes, approval_ref, endpoint_ref, seconds, until_event, condition, complete.

reasonrequiredstring

The planner's task-level reason. Never names a joint, a torque, a velocity or a raw action (enforced).

statusrequiredPlanStepStatus
expires_in_srequiredinteger

Seconds the step may stay active once it starts.

expires_atrequiredstring (date-time)

The plan's window until the step activates, then started_at + expires_in_s clamped to it. Never null.

cost_cap_usdrequirednumber

Cumulative model spend the plan may reach after this step; enforced before the step runs.

started_atstring (date-time) | null (date-time)
finished_atstring (date-time) | null (date-time)
resultobject | null

What happened — the request id filed, the approval seen, the session opened, the condition's observed value.

created_atrequiredstring (date-time)

PlanEvent

object

PlanEvent fields
FieldTypeDescription
idrequiredId
kindrequiredstring

length ≤ 32

created, decision (carries the model's summary, observations, flags, usage, cost and latency in data), flagged, rejected, step_started, waiting, step_done, step_failed, step_expired, steps_skipped, finished.

step_idstring | null
messagerequiredstring
dataobject | null
cost_usdnumber | null

Model spend this event represents (decisions and rejections).

created_atrequiredstring (date-time)

PlanStepKind

string

one of request_approval · await_started · open_session · wait · evaluate · hand_back

The closed vocabulary. request_approval files an approval request (a human approves it); await_started waits for the approval and the device's signed started; open_session waits for the device to open its endpoint session; wait waits for seconds or an event; evaluate tests one telemetry metric; hand_back ends the plan.

PlanStepStatus

string

one of pending · active · done · failed · expired · skipped