Agent workflows
Section titled “Agent workflows”Define, read, replace, hide, and delete multi-step agent workflows, run them onto a session, and snapshot, inspect, or cancel workflow runs. The endpoints group into two families: workflow definitions (the saved YAML/JSON in the realm’s workflow store) and workflow runs (live executions of a definition, with their own state machine).
Workflow definitions
Section titled “Workflow definitions”GET /api/v1/agent/workflows
Section titled “GET /api/v1/agent/workflows”List workflow definitions visible to the requesting cwd/config_dir (name, summary, step briefs, system flag). The realm is named by X-Hoody-Realm or ?realm=; omitted, the agent’s own realm.
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows?page=1&limit=50" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.workflows.listIterator({ page: 1, limit: 50 })) { console.log(item);}{ "items": [ { "name": "lint", "summary": "Run the linter and report what it found.", "system": false, "steps": [ { "id": "lint", "type": "bash" } ] } ], "meta": { "total": 1, "page": 1, "limit": 50 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | 1-based page number for pagination. |
limit | query | integer | No | Maximum items per page (0 = no pagination). |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "items": [], "meta": { "total": 0 }}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read — retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
GET /api/v1/agent/workflows/{name}
Section titled “GET /api/v1/agent/workflows/{name}”Read one workflow definition. Returns the tool envelope {output, is_error} carrying the definition JSON in output. With ?include_revision=true, the first line of output is revision: <opaque> to use as a CAS baseline for client.agent.workflows.set() or client.agent.workflows.setSummary(). An unknown name returns is_error: true with output "workflow X not found".
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/lint?include_revision=true" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.workflows.get('lint', { include_revision: true });console.log(result);{ "status": "ok", "output": "{\"name\":\"lint\",\"summary\":\"Run the linter.\",\"entry_point\":{\"id\":\"lint\"},\"steps\":{\"lint\":{\"type\":\"bash\",\"command\":\"make lint\"}}}", "is_error": false}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The name. |
include_revision | query | boolean | No | If “true”, the tool output’s first line is revision: <opaque> — pass that value as the expected_revision of client.agent.workflows.set() to guard against concurrent edits; the JSON below it is unchanged. Strictly parsed: exactly one value, “true” or “false”; anything else (empty, “TRUE”, “1”, repeated) is a 400 bad_request. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "output": "{\"name\":\"lint\"}", "is_error": false}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
tool_mutation_refused | Tool mutation refused | A sessionless tool run resolved to a mutating tool with no confirmation posture. Sessionless runs are read-only by default. | Open a session and run the tool there, or supply allow_mutations:true / confirm:true on the sessionless run. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
realm_not_allowed | Realm not allowed for this login | The agent is logged in with a token limited to some realms (GET /hoody/auth/status lists them in realm_ids), and this session’s realm is not one of them, or is the global scope (a session restored from before the login, or a realm the login was narrowed away from). The session can still be read; nothing was run. | Continue in a session of a realm the login serves, or log the agent in with a token that covers this session’s realm. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "tool_not_found", "message": "no tool with that name"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
tool_not_found | Tool not found | No tool with the given name exists in the catalogue or in the session’s effective tool list. | List the catalogue (GET /tools) or the session’s tools (GET /sessions/{id}/tools) and use a valid name. |
not_found | Not found | The workflow does not exist, or the realm is not one this login serves. | Check the workflow name and the realm. |
{ "code": "tool_needs_confirmation", "message": "tool requires confirmation before execution"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
tool_needs_confirmation | Tool needs confirmation | The tool was NOT executed: the permission checks require confirmation first. The echoed tool+params let the caller re-issue the run with confirm:true. When a tool-call rule parked it, details.gate_cause is “rules” and details.reason names the rule. | Re-issue the run with confirm:true (or ?confirm=true), preserving the echoed params. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read (introspection failed) — retry, or log in again"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it runs no work in any session until it can. Nothing was run. | Retry after the restriction is readable. |
PUT /api/v1/agent/workflows/{name}
Section titled “PUT /api/v1/agent/workflows/{name}”Create or replace one workflow definition. The body carries the definition object; the {name} path value is authoritative. The daemon validates the definition strictly before writing atomically (unknown fields rejected, the loader’s structural gate enforced), so a malformed definition is refused (is_error:true) rather than corrupting workflow.json. The system flag is authoritative from the embedded defaults. The 200 status also covers CAS refusals, which return is_error: true with a typed code (revision_conflict or already_exists) and the entry’s current_revision when available.
curl -X PUT "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/lint" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody" \ -H "Content-Type: application/json" \ -d '{ "definition": { "name": "lint", "summary": "Run the linter and report what it found.", "entry_point": { "id": "lint" }, "steps": { "lint": { "type": "bash", "command": "make lint" } } } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.workflows.set('lint', { definition: { name: 'lint', summary: 'Run the linter and report what it found.', entry_point: { id: 'lint' }, steps: { lint: { type: 'bash', command: 'make lint' } } }});{ "status": "ok", "output": "workflow lint updated", "is_error": false}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The name. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
definition | object | Yes | The full workflow definition object (steps, entry_point, summary). Validated strictly by the agent before an atomic write. |
expected_revision | string | No | Optional optimistic-concurrency guard: the revision: value from client.agent.workflows.get() (?include_revision=true). If the stored workflow changed since that read, the upsert is refused with [revision_conflict] and nothing is written. Omit to save unconditionally. The revision is bound to the realm this request resolves to: on a gateway without a realm pin, a revision read while another realm was active is refused [revision_conflict] (current_revision is this realm’s), never written into the other realm. |
expected_absent | boolean | No | Optional create-only guard: refuse with [already_exists] (writing nothing) if any workflow with this name already exists. Use when creating a new workflow that must not overwrite an existing one. Mutually exclusive with expected_revision. |
{ "definition": { "name": "lint", "summary": "Run the linter and report what it found.", "entry_point": { "id": "lint" }, "steps": { "lint": { "type": "bash", "command": "make lint" } } }}Response statuses
Section titled “Response statuses”{ "status": "ok", "output": "workflow lint updated", "is_error": false}| Error Code | Title | Description | Resolution |
|---|---|---|---|
revision_conflict | Revision conflict | The workflow changed (or was deleted) since the revision the request was based on, so nothing was written. | Re-read the workflow (client.agent.workflows.get() with include_revision=true), re-apply the change, and retry with the new revision. |
already_exists | Already exists | The expected_absent guard failed: a workflow with this name already exists, so nothing was written. | Omit expected_absent to overwrite, or use a different name. |
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
tool_mutation_refused | Tool mutation refused | A sessionless tool run resolved to a mutating tool with no confirmation posture. Sessionless runs are read-only by default. | Open a session and run the tool there, or supply allow_mutations:true / confirm:true on the sessionless run. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
reserved_name | Reserved workflow name | The workflow name collides with a literal /workflows/ route segment (e.g. “runs”), so its definition GET (GET /workflows/{name}) would be permanently shadowed by that route and the definition could never be read back or used as a CAS baseline. | Choose a workflow name that is not a reserved /workflows/ route segment. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
realm_not_allowed | Realm not allowed for this login | The agent is logged in with a token limited to some realms (GET /hoody/auth/status lists them in realm_ids), and this session’s realm is not one of them, or is the global scope (a session restored from before the login, or a realm the login was narrowed away from). The session can still be read; nothing was run. | Continue in a session of a realm the login serves, or log the agent in with a token that covers this session’s realm. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "tool_not_found", "message": "no tool with that name"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
tool_not_found | Tool not found | No tool with the given name exists in the catalogue or in the session’s effective tool list. | List the catalogue (GET /tools) or the session’s tools (GET /sessions/{id}/tools) and use a valid name. |
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read (introspection failed) — retry, or log in again"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it runs no work in any session until it can. Nothing was run. | Retry after the restriction is readable. |
PUT /api/v1/agent/workflows/{name}/summary
Section titled “PUT /api/v1/agent/workflows/{name}/summary”Replace one workflow’s summary (the one-line description shown in the Workflows tab and in client.agent.workflows.list()), or clear it with summary:"". Only that field is written, atomically, so a concurrent edit to the steps is never overwritten. Setting the summary of a shipped system workflow the realm has no copy of yet saves a copy of it first. A project-supplied workflow (listed with project:true) is refused (400): copy it explicitly first.
curl -X PUT "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/lint/summary" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody" \ -H "Content-Type: application/json" \ -d '{ "summary": "Run the linter and report what it found." }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.workflows.setSummary('lint', { summary: 'Run the linter and report what it found.'});{ "status": "ok", "name": "lint", "revision": "rev_01HMZX7P5V3J8N9K4Q2WXABCDE"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The name. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
summary | string | Yes | The new summary. An empty string clears it. Required — omitting it is a 400, not a clear. |
expected_revision | string | No | Optional optimistic-concurrency guard: the revision: value from client.agent.workflows.get() (?include_revision=true). If the workflow changed or was deleted since that read, the write is refused 409 revision_conflict and nothing is written. Omit to write unconditionally. The revision is bound to the realm this request resolves to: on a gateway without a realm pin, a revision read while another realm was active is refused 409 revision_conflict (details.current_revision is this realm’s), never written into the other realm. |
{ "summary": "Run the linter and report what it found."}Response statuses
Section titled “Response statuses”{ "status": "ok", "name": "lint", "revision": "rev_01HMZX7P5V3J8N9K4Q2WXABCDE"}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "revision_conflict", "message": "workflow \"lint\" was modified since you loaded it"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
revision_conflict | Revision conflict | The workflow changed (or was deleted) since the revision the request was based on, so nothing was written. details.current_revision carries the entry’s current revision; it is absent when the workflow no longer exists. | Re-read the workflow (client.agent.workflows.get() with include_revision=true), re-apply the change, and retry with the new revision. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked or written, so the operation did not complete. The message names the store (for example “the agent’s settings file is damaged and was left unchanged”). A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log (GET /logs) records a “store failure” entry with the store, the failure kind (io, corrupt or lock) and a path-free cause such as “open: permission denied” or “invalid JSON at offset 12”; it does not name the file’s path. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read — retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
DELETE /api/v1/agent/workflows/{name}
Section titled “DELETE /api/v1/agent/workflows/{name}”Delete one user workflow definition. System workflows are refused (is_error:true) — they re-seed on boot; hide them instead via client.agent.workflows.setHidden().
curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/lint" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.workflows.delete('lint');{ "status": "ok", "output": "workflow lint deleted", "is_error": false}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The name. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "status": "ok", "output": "workflow lint deleted", "is_error": false}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
tool_mutation_refused | Tool mutation refused | A sessionless tool run resolved to a mutating tool with no confirmation posture. Sessionless runs are read-only by default. | Open a session and run the tool there, or supply allow_mutations:true / confirm:true on the sessionless run. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
realm_not_allowed | Realm not allowed for this login | The agent is logged in with a token limited to some realms (GET /hoody/auth/status lists them in realm_ids), and this session’s realm is not one of them, or is the global scope (a session restored from before the login, or a realm the login was narrowed away from). The session can still be read; nothing was run. | Continue in a session of a realm the login serves, or log the agent in with a token that covers this session’s realm. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "tool_not_found", "message": "no tool with that name"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
tool_not_found | Tool not found | No tool with the given name exists in the catalogue or in the session’s effective tool list. | List the catalogue (GET /tools) or the session’s tools (GET /sessions/{id}/tools) and use a valid name. |
not_found | Not found | The workflow does not exist, or the realm is not one this login serves. | Check the workflow name and the realm. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read (introspection failed) — retry, or log in again"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it runs no work in any session until it can. Nothing was run. | Retry after the restriction is readable. |
POST /api/v1/agent/workflows/{name}/hide
Section titled “POST /api/v1/agent/workflows/{name}/hide”Hide or un-hide a workflow. Hiding is the only way to remove a system workflow from view (system workflows are re-seeded on every boot and can never be deleted). Pass hidden:false to un-hide. A hide stays private to its realm, named by X-Hoody-Realm or ?realm= (omitted, the agent’s own realm).
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/lint/hide" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody" \ -H "Content-Type: application/json" \ -d '{ "hidden": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.workflows.setHidden('lint', { hidden: true });{ "hidden_workflows": ["lint"]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The name. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
hidden | boolean | No | true (default) to hide the workflow; false to un-hide it. |
{ "hidden": true}Response statuses
Section titled “Response statuses”{ "hidden_workflows": ["lint"]}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked or written, so the operation did not complete. The message names the store (for example “the agent’s settings file is damaged and was left unchanged”). A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log (GET /logs) records a “store failure” entry with the store, the failure kind (io, corrupt or lock) and a path-free cause such as “open: permission denied” or “invalid JSON at offset 12”; it does not name the file’s path. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
Workflow runs
Section titled “Workflow runs”GET /api/v1/agent/workflows/runs
Section titled “GET /api/v1/agent/workflows/runs”Snapshot in-flight and recent workflow runs. The registry exposes Snapshot() rather than Subscribe(). Poll this to track a run’s progress; live events also flow on the owning session’s stream.
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/runs?page=1&limit=50" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.workflows.listRunsIterator({ page: 1, limit: 50 })) { console.log(item);}{ "items": [ { "run_id": "3b1f0c9e-7a2d-4e58-9c61-0d4b8a7e2f13", "workflow_name": "release-check", "session_id": "9f2c4e1a-5b7d-4c3e-8a21-6d0f3b9e7c55", "cwd": "/home/user/projects/my-app", "started_at": "2026-09-28T10:02:11.482913Z", "current_step": "run-tests", "current_idx": 1, "total_steps": 3, "status": "running", "input_tokens": 1840, "output_tokens": 312, "cost": 0.0123, "realm_id": "global" } ], "meta": { "total": 1 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | 1-based page number for pagination. |
limit | query | integer | No | Maximum items per page (0 = no pagination). |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
realm | query | string | No | The realm to list: “global” (runs not tied to a realm), a 24-hex realm id, or “all” for every realm this login serves. Also accepted as the X-Hoody-Realm header, except “all”. Omitted, the agent’s current realm. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "items": [], "meta": { "total": 0 }}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read — retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
GET /api/v1/agent/workflows/runs/{run_id}
Section titled “GET /api/v1/agent/workflows/runs/{run_id}”Get one workflow run by id, including its per-step outcomes (step id, status, duration, tokens, and error). Returns 404 not_found when the run id is no longer retained (evicted or never existed) or belongs to another realm/account (never revealed as existing).
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/runs/3b1f0c9e-7a2d-4e58-9c61-0d4b8a7e2f13" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.workflows.getRun('3b1f0c9e-7a2d-4e58-9c61-0d4b8a7e2f13');console.log(result);{ "run": { "run_id": "wfr_01HMZX7P5V3J8N9K4Q2WXABCDE", "workflow_name": "lint", "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "cwd": "/home/user/projects/my-app", "started_at": "2026-01-13T09:00:00Z", "finished_at": "2026-01-13T09:01:12Z", "current_idx": 1, "total_steps": 1, "status": "completed", "input_tokens": 1284, "output_tokens": 412, "steps": [ { "step_id": "lint", "success": true, "duration_ms": 71840, "input_tokens": 1284, "output_tokens": 412 } ] }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
run_id | path | string | Yes | The run id. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "run": { "run_id": "wfr_01HMZX7P5V3J8N9K4Q2WXABCDE", "status": "running" }}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
POST /api/v1/agent/workflows/{name}/runs
Section titled “POST /api/v1/agent/workflows/{name}/runs”Open a new session and dispatch the named workflow onto it in one call, returning 202 {job_id, session_id, run_id, turn_id}. Equivalent to POST /sessions followed by POST /sessions/{id}/workflows/{name}/runs: the run’s events flow on that session’s stream, a gate it parks is answered through that session, and job.run_id is null until the workflow loop registers the run.
The name is checked against the new session’s own realm and project: an unknown name is a 404 and the session is closed without being recorded. The check is best-effort: if the workflow list cannot be read the run is dispatched anyway and an unknown name fails on the session stream. An Idempotency-Key header is refused (400): a retry-safe run needs a keyed POST /sessions followed by client.agent.sessions.startWorkflow().
The session is an ordinary session: it stays listed, and the gateway releases it once it is idle with no attached client. A parked gate counts as idle, so a run that parks a gate while nobody is attached is cancelled after the idle timeout (5 minutes). Attach to the session’s stream to answer its gates, or start it with headless:true to have them decided without a human. If the request ends after the run was sent but before the daemon confirmed it, the session is closed (cancelling the run) since its ids were never returned.
The POST /sessions fields attach, fork, fork_turn_idx, backend, delegated_agent, approval and expected_binding are refused (400), never ignored: create the session first and use client.agent.sessions.startWorkflow().
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/release-check/runs" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Tag and publish v1.4.2", "inputs": { "version": "1.4.2" }, "cwd": "/home/user/projects/my-app", "model": "anthropic/claude-sonnet-4", "tool_mode": "standard", "dir_scope": "home", "headless": false }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.workflows.start('release-check', { prompt: 'Tag and publish v1.4.2', inputs: { version: '1.4.2' }, cwd: '/home/user/projects/my-app', model: 'anthropic/claude-sonnet-4', tool_mode: 'standard', dir_scope: 'home', headless: false});console.log(result);{ "job_id": "job_01HMZX7P5V3J8N9K4Q2WXABCDE", "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "run_id": null, "turn_id": "turn_01HMZX7P5V3J8N9K4Q2WXFGHIJ"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The name. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” or a 24-hex id (also accepted as ?realm=). Rejected (400 realm_scope_unsupported) on active-only / no-realm routes. |
realm | query | string | No | Per-request realm selector. The in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” or a 24-hex id. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
prompt | string | No | Optional input text fed to the workflow run ($(workflow.prompt)). |
inputs | object | No | Optional run-time values for the workflow’s declared input parameters (declared name -> string value; resolves to $(input.<name>) in every step). A workflow with a required declared parameter cannot run without these. Values must be strings; a non-string value is a 400. |
realm | string | No | Realm selector for the new session (frozen at start). A pinned gateway always uses its pin. |
container | string | No | Container id/selector to run tools on (frozen at start). |
cwd | string | No | Working directory for the session (frozen at start); it also selects the project workflow layer. |
config_dir | string | No | Config directory override. |
model | string | No | Model for this session; overrides the chat agent’s pinned model for this session only. An unknown model is refused 422 unknown_model. |
agent | string | No | Initial chat-agent name. |
tool_mode | string | No | Initial tool mode (frozen at start): standard (the default) or orchestrator. Any other value is refused 400 invalid_tool_mode. |
dir_scope | string | No | Initial directory-access scope (frozen at start): home or full. Any other value is refused 400 invalid_dir_scope. |
headless | boolean | No | Start the session in headless posture. |
{ "prompt": "Tag and publish v1.4.2", "inputs": { "version": "1.4.2" }, "cwd": "/home/user/projects/my-app", "model": "anthropic/claude-sonnet-4", "tool_mode": "standard", "dir_scope": "home", "headless": false}Response statuses
Section titled “Response statuses”{ "job_id": "job_01HMZX7P5V3J8N9K4Q2WXABCDE", "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "run_id": null, "turn_id": "turn_01HMZX7P5V3J8N9K4Q2WXFGHIJ"}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_blocked | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
realm_corrupt | Realm record corrupt | The attach target’s persisted realm binding is corrupt; the session cannot be re-bound and the request fails closed. | Recreate the session in a valid realm. |
realm_conflict | Realm conflict | The supplied realm conflicts with the parent’s (fork) or the session’s frozen (attach) realm binding. | Omit the realm header to inherit, or match the existing binding. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
container_conflict | Container conflict | The supplied container conflicts with the parent’s (fork) or the session’s frozen (attach) container binding. | Omit the container header to inherit, or match the existing binding. |
invalid_tool_mode | Invalid tool mode | tool_mode is not standard or orchestrator. Nothing was started. details.field is “tool_mode”. | Send standard or orchestrator, or omit tool_mode. |
invalid_dir_scope | Invalid directory scope | dir_scope is not home or full. Nothing was started. details.field is “dir_scope”. | Send home or full, or omit dir_scope. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
realm_not_allowed | Realm not allowed for this login | The agent is logged in with a token limited to some realms (GET /hoody/auth/status lists them in realm_ids), and the session’s realm is not one of them, or is the global scope. Nothing was started. | Use a realm the login serves, or log the agent in with a token that covers this realm. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
workflow_not_found | Workflow not found | No workflow definition with the requested name is visible to this cwd/config_dir. | List the available workflows (GET /workflows) and use an existing name. |
{ "code": "turn_in_flight", "message": "a turn is already running on this session"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
session_busy | Session busy | The session is already live in another connection (single-writer-per-session). | Close the other attach, or attach a fork instead. |
session_not_attachable | Session not attachable | The attach target is a daemon-managed session (e.g. a todo worker) and cannot be attached over HTTP. | Attach a user session, or fork the target instead of attaching it. |
approval_policy_unsatisfiable | Approval policy unsatisfiable | The approval assertion cannot be honoured: an attach asserted a posture the shared session does not have (an attach never changes a shared session), the backend cannot park on a decision (delegated/acp), or the session’s posture is already open (automatic write permission). | Attach without the assertion (or with the session’s actual posture), or create a fresh session with the policy you need. |
approval_policy_locked | Approval policy locked | The session’s approval policy is locked for its lifetime: the mode, the lock, YOLO, allow rules, session-scoped remembered approvals and auto-reply arming cannot be changed by any client. | Create a new session with the posture you need; a locked policy is never unlocked. |
approver_lease_invalid | Approver lease not issued | The session could not be started: its approver lease could not be issued. Nothing was run. | Run the workflow again. |
turn_in_flight | Turn in flight | A turn is already running on this session; the single serial turn slot is occupied, so a new turn/workflow run is refused. | Wait for the running turn to finish (observe agent_done on the session stream), then retry. |
gate_parked | Gate parked | A confirm/question gate is parked on this session, so a new turn/workflow run is refused until it is answered. The parked gate is surfaced under details.pending_gate. | Answer the parked gate (/confirm or /answer, echoing its gate_id) and retry; read it from details.pending_gate, from pending_gate on GET /sessions/{id}, or from the gate field of the stream frame that parked it. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "cwd_not_found", "message": "working directory does not exist"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
cwd_not_found | Working directory not found | The requested working directory does not exist on the daemon host (validated at session start for a self-bound session). | Create the directory or pass an existing cwd. |
container_gone | Bound container no longer exists | The session’s frozen REMOTE container no longer exists in its realm (validated at session start). | Start a new session and pick another container (or this computer). |
acp_container_unsupported | Delegated session cannot bind this container | A delegated (ACP) session was bound to a container this box can’t run ACP agents in. | Omit container_id to run the agent locally, or enable the Hoody integration. |
unknown_model | Unknown model | The model has no known provider prefix, or names a fusion composite that does not exist. No session was created. | Pick a model from GET /api/v1/agent/models, or omit model to use the agent’s own. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
policy_commit_failed | Policy commit failed | The daemon could not durably write the policy, so the change was NOT acknowledged (a locked policy is never acknowledged unlocked). | Retry; if it persists, check the daemon’s session store (disk / permissions). |
session_terminating | Session terminating | The session is shutting down: a concurrent open lost a race with its teardown, or a hard delete waited for the teardown past its bound. Nothing was opened or deleted. | Retry shortly. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it starts no session until it can. Nothing was started. | Retry after the restriction is readable. |
admission_write_failed | Admission write failed | The daemon could not durably record the turn’s admission, so the turn was NOT run (fail-closed: nothing runs unrecorded). | Retry; if it persists, check the daemon’s session store (disk / permissions). |
admission_unconfirmed | Admission unconfirmed | The workflow run was sent but the daemon’s admission receipt did not arrive within the wait. It may still have been accepted: details.session_id, details.turn_id, details.job_id and details.result_url name it. The job stays pending and settles when the daemon’s answer arrives — failed if the daemon refused the run, running if it accepted it. | Poll GET /jobs/{details.job_id} or details.result_url; start a new run only after the job reports failed. |
POST /api/v1/agent/sessions/{id}/workflows/{name}/runs
Section titled “POST /api/v1/agent/sessions/{id}/workflows/{name}/runs”Dispatch a workflow run onto a live session (a live session_id is required) and returns {job_id}. job.run_id is null during the brief dispatch window and is populated once the workflow loop registers the run (observe workflow_start on the session’s stream). Run events flow on the owning session’s WS/SSE attach; there is no per-run event bus.
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/67e89abc123def456789abcd-890abcdef12345678901cdef/workflows/release-check/runs" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Run the release checklist", "inputs": { "version": "1.4.2" } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.sessions.startWorkflow( '67e89abc123def456789abcd-890abcdef12345678901cdef', 'release-check', { prompt: 'Run the release checklist', inputs: { version: '1.4.2' } });console.log(result);{ "job_id": "job_01HMZX7P5V3J8N9K4Q2WXABCDE", "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "run_id": null}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
name | path | string | Yes | The name. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” or a 24-hex id (also accepted as ?realm=). Rejected (400 realm_scope_unsupported) on active-only / no-realm routes. |
realm | query | string | No | Per-request realm selector. The in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” or a 24-hex id. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
prompt | string | No | Optional input text fed to the workflow run ($(workflow.prompt)). |
inputs | object | No | Optional run-time values for the workflow’s declared input parameters (declared name -> string value; resolves to $(input.<name>) in every step). A workflow with a required declared parameter cannot run without these. Values must be strings; a non-string value is a 400. |
{ "prompt": "Run the release checklist", "inputs": { "version": "1.4.2" }}Response statuses
Section titled “Response statuses”{ "job_id": "job_01HMZX7P5V3J8N9K4Q2WXABCDE", "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "run_id": null}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
realm_not_allowed | Realm not allowed for this login | The agent is logged in with a token limited to some realms (GET /hoody/auth/status lists them in realm_ids), and this session’s realm is not one of them, or is the global scope (a session restored from before the login, or a realm the login was narrowed away from). The session can still be read; nothing was run. | Continue in a session of a realm the login serves, or log the agent in with a token that covers this session’s realm. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
workflow_not_found | Workflow not found | No workflow definition with the requested name is visible to this cwd/config_dir. | List the available workflows (GET /workflows) and use an existing name. |
{ "code": "turn_in_flight", "message": "a turn is already running on this session"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
turn_in_flight | Turn in flight | A turn is already running on this session; the single serial turn slot is occupied, so a new turn/workflow run is refused. | Wait for the running turn to finish (observe agent_done on the session stream), then retry. |
gate_parked | Gate parked | A confirm/question gate is parked on this session, so a new turn/workflow run is refused until it is answered. The parked gate is surfaced under details.pending_gate. | Answer the parked gate (/confirm or /answer, echoing its gate_id) and retry; read it from details.pending_gate, from pending_gate on GET /sessions/{id}, or from the gate field of the stream frame that parked it. |
approval_policy_unsatisfiable | Approval policy unsatisfiable | The approval assertion cannot be honoured: an attach asserted a posture the shared session does not have (an attach never changes a shared session), the backend cannot park on a decision (delegated/acp), or the session’s posture is already open (automatic write permission). | Attach without the assertion (or with the session’s actual posture), or create a fresh session with the policy you need. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
admission_write_failed | Admission write failed | The daemon could not durably record the turn’s admission, so the turn was NOT run (fail-closed: nothing runs unrecorded). | Retry; if it persists, check the daemon’s session store (disk / permissions). |
admission_unconfirmed | Admission unconfirmed | The workflow run was sent but the daemon’s admission receipt did not arrive within the wait. It may still have been accepted: details.session_id, details.turn_id, details.job_id and details.result_url name it. The job stays pending and settles when the daemon’s answer arrives — failed if the daemon refused the run, running if it accepted it. | Poll GET /jobs/{details.job_id} or details.result_url; start a new run only after the job reports failed. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it runs no work in any session until it can. Nothing was run. | Retry after the restriction is readable. |
POST /api/v1/agent/workflows/runs/{run_id}/cancel
Section titled “POST /api/v1/agent/workflows/runs/{run_id}/cancel”Cancel an in-flight workflow run by run id.
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/runs/3b1f0c9e-7a2d-4e58-9c61-0d4b8a7e2f13/cancel" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.workflows.cancelRun('3b1f0c9e-7a2d-4e58-9c61-0d4b8a7e2f13');{ "status": "ok"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
run_id | path | string | Yes | The run id. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response statuses
Section titled “Response statuses”{ "status": "ok"}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
POST /api/v1/agent/workflows/runs/{run_id}/resume
Section titled “POST /api/v1/agent/workflows/runs/{run_id}/resume”Resume a terminal (failed/cancelled) run in a live session: committed steps replay from the run’s recorded journal and execution continues live from the first incomplete step. Body: {"session_id"}. The resuming session must match the run’s realm, owner, working directory, and container binding.
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/workflows/runs/3b1f0c9e-7a2d-4e58-9c61-0d4b8a7e2f13/resume" \ -H "X-Hoody-Cwd: /home/user/projects/my-app" \ -H "X-Hoody-Config-Dir: /home/user/.hoody" \ -H "Content-Type: application/json" \ -d '{ "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.workflows.resumeRun('3b1f0c9e-7a2d-4e58-9c61-0d4b8a7e2f13', { session_id: '67e89abc123def456789abcd-890abcdef12345678901cdef'});{ "status": "ok", "run_id": "9c2e4a71-5d3b-4f8e-b6a0-1e7d2c9f4b35"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
run_id | path | string | Yes | The run id. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd (e.g. POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request —config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | A live session matching the run’s realm, owner, working directory, and container binding. |
{ "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef"}Response statuses
Section titled “Response statuses”{ "status": "ok", "run_id": "9c2e4a71-5d3b-4f8e-b6a0-1e7d2c9f4b35"}| Error Code | Title | Description | Resolution |
|---|
{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "approval_policy_unsatisfiable", "message": "session policy is default/locked=false, asserted always/locked=true"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
approval_policy_unsatisfiable | Approval policy unsatisfiable | The approval assertion cannot be honoured: an attach asserted a posture the shared session does not have (an attach never changes a shared session), the backend cannot park on a decision (delegated/acp), or the session’s posture is already open (automatic write permission). | Attach without the assertion (or with the session’s actual posture), or create a fresh session with the policy you need. |
session_busy | Session busy | The session has a turn running or is waiting on a confirmation or question, so the resume was not queued. | Let the turn finish (answer or cancel its confirmation or question), then retry the resume. |
{ "code": "payload_too_large", "message": "request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
payload_too_large | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |