Hanzo

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 URLhttps://api.hanzo.ai
Operations31
AuthAuthorization: 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.

ParameterInTypeRequiredDescription
refpathstringyes

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.

ParameterInTypeRequiredDescription
refpathstringyesRef is the agent's public id or its org-unique name, from the path.
limitqueryintegerLimit 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.

ParameterInTypeRequiredDescription
refpathstringyesRef 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.

ParameterInTypeRequiredDescription
refpathstringyesRef is the agent to update — its public id or org-unique name, from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
computeRefstring
descriptionstring
executionModestring
instructionsstring
modelstring
refstringRef is the agent to update — its public id or org-unique name, from the path.
schedulestring
serviceAccountIdstring
toolsstring[]

DELETE /v1/agents/{ref}

Removes an agent and every run recorded against it. Answers 204.

ParameterInTypeRequiredDescription
refpathstringyesRef 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.

ParameterInTypeRequiredDescription
orgpathstringyesOrg is the org that published the build, from the path.
projectpathstringyesProject 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.

ParameterInTypeRequiredDescription
limitqueryintegerLimit 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.

ParameterInTypeRequiredDescription
rangequerystringRange 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.

ParameterInTypeRequiredDescription
idpathstringyesID is the session whose commands are being drained, from the path.
afterqueryintegerAfter 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.

ParameterInTypeRequiredDescription
idpathstringyes

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.

ParameterInTypeRequiredDescription
idpathstringyes

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.

ParameterInTypeRequiredDescription
idpathstringyes

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.

ParameterInTypeRequiredDescription
idpathstringyes

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.

ParameterInTypeRequiredDescription
idpathstringyes

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.

ParameterInTypeRequiredDescription
idpathstringyesID 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.

ParameterInTypeRequiredDescription
idpathstringyesID 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).

ParameterInTypeRequiredDescription
idpathstringyesID is the session to update, from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
cwdstringCwd is where the session is working NOW. It was write-once — captured at register and never again — which is right for…
idstringID is the session to update, from the path.
projectstringProject tags the product this session built; Published is the author's decision to let anyone read the story…
publishedboolean
statusstring
targetstringTarget re-dispatches a session to a run-target (the #48 association).
terminalstringTerminal publishes (or, with "", withdraws) the URL this session's live terminal can be watched at.
titlestring

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.

ParameterInTypeRequiredDescription
rootquerystringRoot scopes the page to one subagent tree (its root session id).
parentquerystringParent scopes the page to the direct children of one session.
statusquerystringStatus filters to running, paused, done or error.
projectquerystringProject filters to the sessions tagged with one product slug.
limitqueryintegerLimit 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 bodyapplication/json (required)

FieldTypeRequiredDescription
accountstring
actorstring
agentstring
cwdstring
hoststringExecution context — where this session runs (all optional).
parentSessionIdstring
projectstringThe readable build (provenance.go): which product this session builds, and whether its story may be read by the world.
providerstringAccount tag — the linked AI account this session ran under (login manager).
publishedboolean
repostring
statusstring
targetstring
taskRunIdstring
taskWorkflowIdstring
terminalstringTerminal is the URL this session's live terminal is published at, so the console can watch it.
titlestring

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.

ParameterInTypeRequiredDescription
idpathstringyesID 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.

ParameterInTypeRequiredDescription
idpathstringyesID 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.

ParameterInTypeRequiredDescription
idpathstringyesID is the machine reporting, from the path.
runIdpathstringyesRunID is the routed run being completed, from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
branchstringBranch, CommitSha and Diffstat describe what the run produced; Error is the failure when OK is false.
changedboolean
commitShastring
diffstatstring
errorstring
idstringID is the machine reporting, from the path.
okbooleanOK is whether the run succeeded; Changed whether it produced any commit.
runIdstringRunID is the routed run being completed, from the path.

GET /v1/agents/targets/{id}

Returns one registered machine, with its live session load.

ParameterInTypeRequiredDescription
idpathstringyesID 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.

ParameterInTypeRequiredDescription
idpathstringyesID is the target to update, from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
capacitystring
hoststring
idstringID is the target to update, from the path.
kindstring
labelstring
metricsMetricspresent => a heartbeat; the server stamps its time
specSpec
statusstring

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.

ParameterInTypeRequiredDescription
idpathstringyesID 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 bodyapplication/json (required)

FieldTypeRequiredDescription
capacitystring
hoststring
kindstring
labelstring
metricsMetrics
specSpec
statusstring

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 bodyapplication/json (required)

FieldTypeRequiredDescription
computeRefstring
descriptionstring
executionModestring
instructionsstring
modelstring
namestring
schedulestring
serviceAccountIdstring
toolsstring[]

Agents guide · All Hanzo APIs · Interactive reference

How is this guide?

On this page