Agents
Package agents is autonomous agents for your org: define them, run them, keep every run.
Package agents is autonomous agents for your org: define them, run them, keep every run.
| Base URL | https://api.hanzo.ai |
| Operations | 31 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
agents
POST /v1/agents/{ref}/run
Run one of your org's agents and get the recorded run back.
Composes the agent's stored instructions with the caller's input, executes one real chat completion through the same in-process AI client the rest of the console uses, and answers with the run that was recorded: its id, status, model, output, duration and error. Every run this returns reflects an execution that actually happened — a model failure is recorded and reported, never hidden and never fabricated. A transient upstream failure (429, 5xx, empty choices) is retried up to three times with jittered backoff, and a configured failover model is tried before the run is called an error.
ref is the agent's public agent_… id or its org-unique name; either resolves the same agent, and it must belong to the caller's org, so an agent in another tenant is a 404 exactly like one that does not exist. A validated principal is required and the check is made twice on purpose: this route MOVES MONEY, so the debit's principal requirement is asserted where the money moves rather than inherited from the tenant lookup.
The org's balance is authorized BEFORE any inference, so an unfunded tenant gets 402 and no free compute, and a billing plane that cannot answer gets 503 rather than a free run. The flat per-run fee is an operator knob; setting it to zero makes runs free and removes the balance gate with them. Only a SUCCESSFUL run is billed, attributed to the model actually used — a failover run bills the model it fell over to, not the one it started on. A deployment with no inference wired answers 503 before any of this.
THE RULE A READER GETS WRONG: a failed run is a 502 whose body is the RUN, not an error envelope. The execution happened, the run was persisted to this agent's history, and its error field is the product — so a client that treats every non-2xx as an opaque failure throws away the only account of what went wrong. Each run also opens a root session in the live session registry, best-effort: a bookkeeping failure there never fails the run, because the run and its billing already happened.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ref | path | string | yes |
GET /v1/agents/{ref}/runs
Returns one agent's execution history, newest first — each run's input, its output or its error, and how long it took. Every row is a run that actually happened.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ref | path | string | yes | Ref is the agent's public id or its org-unique name, from the path. |
limit | query | integer | — | Limit caps how many runs come back, newest first. |
GET /v1/agents/{ref}
Returns one agent with its system prompt and its 20 most recent runs. The ref is the agent's public id or its org-unique name — a created agent is immediately gettable by whatever create handed back.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ref | path | string | yes | Ref is the agent's public id (the agent_… handle create and list return) or its org-unique name, from the path. |
PATCH /v1/agents/{ref}
Changes an agent in place. Every field is optional; a field the request omits keeps its stored value. The resulting mode+schedule are re-validated together, so a partial update can never leave a long-running agent without the cron the scheduler needs to fire it, and a transition INTO long-running counts against the per-org cap on scheduled agents.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ref | path | string | yes | Ref is the agent to update — its public id or org-unique name, from the path. |
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
computeRef | string | — | |
description | string | — | |
executionMode | string | — | |
instructions | string | — | |
model | string | — | |
ref | string | — | Ref is the agent to update — its public id or org-unique name, from the path. |
schedule | string | — | |
serviceAccountId | string | — | |
tools | string[] | — |
DELETE /v1/agents/{ref}
Removes an agent and every run recorded against it. Answers 204.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ref | path | string | yes | Ref is the agent's public id (the agent_… handle create and list return) or its org-unique name, from the path. |
GET /v1/agents/activity
Serves the org-wide recent-activity feed. Events are REAL: each recorded run is an invoked (ok) or failed (error) event; each agent's own create/update timestamps are created/updated events. Merged, newest first, capped. Nothing is invented — an org with no agents and no runs gets [].
GET /v1/agents/builds/{org}/{project}
Returns the readable build of one product: the agent session that
produced it, turn by turn — the prompts, the reasoning, the commits each turn
produced — plus the exact git log that re-derives every commit binding from
git itself, so nothing here has to be taken on trust.
PUBLIC, no tenancy: it answers only for a session its author explicitly published, which is what makes it safe to be anonymous. An unpublished session is invisible here no matter who asks; its owner reads it through the org-scoped /v1/agents/sessions routes, which need a validated principal.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string | yes | Org is the org that published the build, from the path. |
project | path | string | yes | Project is the product's slug, from the path. |
GET /v1/agents/builds
Returns the public index of every published build, most recently updated first, so a gallery can link straight to the story behind each product. PUBLIC, no tenancy: publishing is the author's act, and only published root sessions appear here.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | — | Limit caps the page. |
GET /v1/agents/metrics
Serves the invocations-over-time histogram for the org's Agents dashboard. Every point is a REAL count of recorded runs in that time bucket — one series line per agent that ran in the window. The Resource Usage rollup is all-null because this store meters no CPU/memory/storage/cost; the console renders those as "—" rather than a fabricated figure. No runs => empty series (an honest "not connected / no activity yet"), never a synthesized trend.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
range | query | string | — | Range is the window to bucket: 24H, 7D or 30D. |
GET /v1/agents/sessions/{id}/control
Returns the steering commands (pause/resume/stop/message)
recorded against the caller's own session that are newer than the cursor,
oldest first, with the cursor to poll from next. It is how a locally started
hanzo code session — which is not task-backed, so nothing forwards its
commands to an execution engine — consumes what the dashboard posted. Read-only
and bounded at 200 per poll, so a steady poll is cheap and an applied command is
never redelivered.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the session whose commands are being drained, from the path. |
after | query | integer | — | After is the last seq this poller applied; only commands newer than it come back. |
POST /v1/agents/sessions/{id}/events
Append one turn to a session's ordered log.
Records a message, tool-call, spawn, log, status or control turn against the session and answers 201 with the stored event, including the monotonic seq the store assigned — the cursor every reader pages from. The same turn is fanned out live to every stream subscriber watching that session's tree.
Requires a validated principal carrying an org, and the session must already exist IN THAT ORG: an id belonging to another tenant is a 404 exactly like one that does not exist, so the log can never be written across a tenant boundary. actor defaults to the calling principal when the body names none. kind must be one of the six above, and payload must be valid JSON of at most 64 KiB.
The payload is scanned for credentials BEFORE it is stored, and a hit REFUSES the write with 422 rather than redacting it: {status, code: "secret_in_transcript", error, findings:[…]}, each finding naming the rule, severity, line, a masked preview and a SHA-256 fingerprint the author can match against the value they rotate. The detected value itself appears nowhere in that body, because it was never stored. That in-band findings array is the reason this operation cannot be typed.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
POST /v1/agents/sessions/{id}/message
Send text into a running session.
Records message as a durable control event carrying the caller's text and answers 200 with {command, event, forwarded} — this is how a dashboard steers an agent mid-run. It is the one command with a required body: a message (up to 16 KiB) or a payload, and 400 with neither. The credential scan that guards an appended turn covers payload here; message is bounded but not scanned.
Requires a validated principal carrying an org, and the session must exist IN THAT ORG — a foreign id is a 404, so no tenant can steer another's agents. A FINISHED session (done or error) refuses every command with 409: a run that has ended cannot be steered.
THE COMMAND IS AN INTENT, NOT A STATE CHANGE. Nothing here writes the session's status. A 200 means the command was durably recorded and delivered, never that the agent has actually paused, resumed or stopped; the status becomes paused, done or error only when the surface running the agent reports it back through a session update. That surface learns of the command in one of two ways: a task-backed session (one carrying a workflow id, with a tasks backend wired) has it forwarded to the durable-execution engine, and forwarded says so; everything else is record-only, and the running surface — a locally started hanzo code session, for one — drains it by polling the session's control endpoint. Today that is every session: the only controller wired forwards nothing, so forwarded is false and polling is how a command arrives. If a forward is attempted and fails, the answer is 502 stating that the command was recorded but not forwarded: the intent is never lost.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
POST /v1/agents/sessions/{id}/pause
Ask a running session to pause.
Records pause as a durable control event on the session and answers 200 with {command, event, forwarded} — the stored event carries the seq that orders it against every other turn.
Requires a validated principal carrying an org, and the session must exist IN THAT ORG — a foreign id is a 404, so no tenant can steer another's agents. A FINISHED session (done or error) refuses every command with 409: a run that has ended cannot be steered.
THE COMMAND IS AN INTENT, NOT A STATE CHANGE. Nothing here writes the session's status. A 200 means the command was durably recorded and delivered, never that the agent has actually paused, resumed or stopped; the status becomes paused, done or error only when the surface running the agent reports it back through a session update. That surface learns of the command in one of two ways: a task-backed session (one carrying a workflow id, with a tasks backend wired) has it forwarded to the durable-execution engine, and forwarded says so; everything else is record-only, and the running surface — a locally started hanzo code session, for one — drains it by polling the session's control endpoint. Today that is every session: the only controller wired forwards nothing, so forwarded is false and polling is how a command arrives. If a forward is attempted and fails, the answer is 502 stating that the command was recorded but not forwarded: the intent is never lost.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
POST /v1/agents/sessions/{id}/resume
Ask a paused session to carry on.
Records resume as a durable control event on the session and answers 200 with {command, event, forwarded}. The session is NOT required to be paused first: the only status this refuses is a finished one, because the live status is the running surface's to report rather than this endpoint's to enforce.
Requires a validated principal carrying an org, and the session must exist IN THAT ORG — a foreign id is a 404, so no tenant can steer another's agents. A FINISHED session (done or error) refuses every command with 409: a run that has ended cannot be steered.
THE COMMAND IS AN INTENT, NOT A STATE CHANGE. Nothing here writes the session's status. A 200 means the command was durably recorded and delivered, never that the agent has actually paused, resumed or stopped; the status becomes paused, done or error only when the surface running the agent reports it back through a session update. That surface learns of the command in one of two ways: a task-backed session (one carrying a workflow id, with a tasks backend wired) has it forwarded to the durable-execution engine, and forwarded says so; everything else is record-only, and the running surface — a locally started hanzo code session, for one — drains it by polling the session's control endpoint. Today that is every session: the only controller wired forwards nothing, so forwarded is false and polling is how a command arrives. If a forward is attempted and fails, the answer is 502 stating that the command was recorded but not forwarded: the intent is never lost.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
POST /v1/agents/sessions/{id}/stop
Ask a session to stop for good.
Records stop as a durable control event on the session and answers 200 with {command, event, forwarded}. Stop is the one command that CANCELS a task-backed session's durable workflow instead of signalling it — pause, resume and message are cooperative signals the workflow decides how to act on, while this tears it down, with the request's message recorded as the cancellation reason (a default stands in when none is given).
Requires a validated principal carrying an org, and the session must exist IN THAT ORG — a foreign id is a 404, so no tenant can steer another's agents. A FINISHED session (done or error) refuses every command with 409: a run that has ended cannot be steered.
THE COMMAND IS AN INTENT, NOT A STATE CHANGE. Nothing here writes the session's status. A 200 means the command was durably recorded and delivered, never that the agent has actually paused, resumed or stopped; the status becomes paused, done or error only when the surface running the agent reports it back through a session update. That surface learns of the command in one of two ways: a task-backed session (one carrying a workflow id, with a tasks backend wired) has it forwarded to the durable-execution engine, and forwarded says so; everything else is record-only, and the running surface — a locally started hanzo code session, for one — drains it by polling the session's control endpoint. Today that is every session: the only controller wired forwards nothing, so forwarded is false and polling is how a command arrives. If a forward is attempted and fails, the answer is 502 stating that the command was recorded but not forwarded: the intent is never lost.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
GET /v1/agents/sessions/{id}/tree
Returns the subagent-flow graph rooted at this session: the session, its children, their children, each node carrying its own event count. One indexed read pulls the whole flow (every node of a flow shares a root id), so the shape is assembled in memory rather than by walking the store per node.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the session to act on, from the path. |
GET /v1/agents/sessions/{id}
Returns one session with its direct child sessions and its 50 most recent events, oldest of those first.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the session to act on, from the path. |
PATCH /v1/agents/sessions/{id}
Updates a session's surface-owned truth: its status, its title, the run-target it is dispatched to, and the product it built plus whether that build's story is public. A FINISHED session stays finished — reopening a done/error run would fabricate liveness — and publishing is refused unless the session names the project it built, because the public build route is keyed on (org, project).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the session to update, from the path. |
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
cwd | string | — | Cwd is where the session is working NOW. It was write-once — captured at register and never again — which is right for… |
id | string | — | ID is the session to update, from the path. |
project | string | — | Project tags the product this session built; Published is the author's decision to let anyone read the story… |
published | boolean | — | |
status | string | — | |
target | string | — | Target re-dispatches a session to a run-target (the #48 association). |
terminal | string | — | Terminal publishes (or, with "", withdraws) the URL this session's live terminal can be watched at. |
title | string | — |
GET /v1/agents/sessions/stream
Live session and event updates for the caller's org, as Server-Sent Events.
Holds the connection open as text/event-stream and pushes a frame each time the org's registry moves: an event: session frame carrying the same session shape the list and detail reads answer with (a registration, an update, or a login-manager revoke tearing a session down), and an event: event frame carrying one appended turn. Optional ?root=<session id> narrows the feed to a single subagent tree.
Requires a validated principal carrying an org; 403 without one. Org-scoped fail-closed: the bus filters on tenant before it fans out, so a subscriber only ever receives its own org's updates, and ?root= narrows that further but can never widen it.
Delivery is best-effort and the GET reads remain the source of truth. A subscriber that falls more than 256 frames behind is DROPPED — its channel is closed and the stream ends — so one stuck dashboard can never back-pressure a session write; the client reconnects and re-reads the session endpoints to resynchronise. A : ping comment every 25 seconds holds the connection open through proxies and is how a departed client is noticed.
GET /v1/agents/sessions
Returns the caller org's live sessions, newest first — each with its event count, its direct-child count and a one-line preview of its latest event. With no filter it returns ROOT sessions only, so a dashboard shows one row per flow rather than one per subagent; ?root= or ?parent= descends.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
root | query | string | — | Root scopes the page to one subagent tree (its root session id). |
parent | query | string | — | Parent scopes the page to the direct children of one session. |
status | query | string | — | Status filters to running, paused, done or error. |
project | query | string | — | Project filters to the sessions tagged with one product slug. |
limit | query | integer | — | Limit caps the page. |
POST /v1/agents/sessions
Opens a live agent session in the caller's org — the row every surface (the CLI's outer agent, hanzo.bot, the console, chat) hangs its activity off. A session with a parentSessionId becomes a subagent of that session and inherits its root, so one flow is one tree; without one it is itself a root. Registering with a terminal status records a session that has already finished.
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
account | string | — | |
actor | string | — | |
agent | string | — | |
cwd | string | — | |
host | string | — | Execution context — where this session runs (all optional). |
parentSessionId | string | — | |
project | string | — | The readable build (provenance.go): which product this session builds, and whether its story may be read by the world. |
provider | string | — | Account tag — the linked AI account this session ran under (login manager). |
published | boolean | — | |
repo | string | — | |
status | string | — | |
target | string | — | |
taskRunId | string | — | |
taskWorkflowId | string | — | |
terminal | string | — | Terminal is the URL this session's live terminal is published at, so the console can watch it. |
title | string | — |
POST /v1/agents/targets/{id}/claim
ClaimRoutedRun is the machine's long poll for work: it authenticates the daemon, stamps the liveness the dispatch gate reads (the poll IS the proof a runner is listening), and waits up to 25 seconds for the next run addressed to THIS machine. It answers the run when one arrives and 204 with no body when the window elapses, on which the daemon re-polls immediately.
TWO independent proofs are required and both fail closed to the same 403: the caller must own this machine (or be an org admin) AND present its claim key in X-Target-Key. A run offered to one machine is unreachable from another's claim.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the target to act on, from the path. |
POST /v1/agents/targets/{id}/key
Mints (or rotates) the claim key a hanzo code --serve
daemon presents to claim work for this machine, and returns it ONCE: only its
SHA-256 hash is stored. Rotating supersedes any prior daemon, so only the
machine's owner — or an org admin — may call it; every other caller gets the
same not-found an unknown id gets, and learns nothing about what exists.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the target to act on, from the path. |
POST /v1/agents/targets/{id}/runs/{runId}/report
Completes a claimed run: it delivers the terminal result to the run's durable owner, which is what lets that workflow finish. Scoped to (org, target, run) and claim-key authenticated, so a machine can only ever report a run it legitimately holds. Idempotent — a report for an unknown or already-finished run answers delivered:false rather than failing, because the session's terminal state was already set by the machine's own stream.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the machine reporting, from the path. |
runId | path | string | yes | RunID is the routed run being completed, from the path. |
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
branch | string | — | Branch, CommitSha and Diffstat describe what the run produced; Error is the failure when OK is false. |
changed | boolean | — | |
commitSha | string | — | |
diffstat | string | — | |
error | string | — | |
id | string | — | ID is the machine reporting, from the path. |
ok | boolean | — | OK is whether the run succeeded; Changed whether it produced any commit. |
runId | string | — | RunID is the routed run being completed, from the path. |
GET /v1/agents/targets/{id}
Returns one registered machine, with its live session load.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the target to act on, from the path. |
PATCH /v1/agents/targets/{id}
Updates one machine in place. Every field is optional; a field the request omits is left alone. A metrics patch IS a heartbeat — the server stamps its own clock, so a client can neither forge nor backdate staleness.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the target to update, from the path. |
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
capacity | string | — | |
host | string | — | |
id | string | — | ID is the target to update, from the path. |
kind | string | — | |
label | string | — | |
metrics | Metrics | — | present => a heartbeat; the server stamps its time |
spec | Spec | — | |
status | string | — |
DELETE /v1/agents/targets/{id}
Deregisters one machine. Only its owner, or an org admin, may remove it; an unknown id, a cross-org id and a machine owned by someone else all answer the same not-found, so a probe learns nothing about what exists.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the target to act on, from the path. |
GET /v1/agents/targets
Returns every machine registered to the caller's org, newest first, each with its live session load.
POST /v1/agents/targets
Registers a machine as an agent target, or re-links one that is already registered. Re-linking is idempotent and keyed on org+host+owner, so a machine that reconnects refreshes its own row rather than piling up duplicates; it answers 200, while a first registration answers 201.
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
capacity | string | — | |
host | string | — | |
kind | string | — | |
label | string | — | |
metrics | Metrics | — | |
spec | Spec | — | |
status | string | — |
GET /v1/agents
Returns every agent defined in the caller's org, each with the number of runs recorded against it.
POST /v1/agents
Defines an agent in the caller's org: a model, a system prompt (instructions) and a set of tool names. The name must be unique in the org and match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$. An omitted model takes the deployment's configured default; a named one is checked against the gateway's served catalog, so a model this deployment never serves is refused here rather than failing at run time. A long-running agent must carry a 5-field cron schedule (the scheduler would otherwise never fire it) and counts against a per-org cap on scheduled agents.
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
computeRef | string | — | |
description | string | — | |
executionMode | string | — | |
instructions | string | — | |
model | string | — | |
name | string | — | |
schedule | string | — | |
serviceAccountId | string | — | |
tools | string[] | — |
How is this guide?