Agent: Tools
Section titled “Agent: Tools”Inspect the agent’s tool catalogue, list the tool set available in a live session, and run tools sessionless or inside a live session through the same gated RPC the agent itself uses.
Tool catalogue and inspection
Section titled “Tool catalogue and inspection”GET /api/v1/agent/tools
Section titled “GET /api/v1/agent/tools”Lists the agent’s built-in tool schemas (name, description, JSON-Schema input, read_only flag). Pagination: pass ?page=N&limit=M for a window; omit limit for the full set.
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). |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read resolves against. |
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 tool of client.agent.tools.listIterator()) { console.log(tool.name, tool.read_only);}curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/tools' \ -H 'Authorization: Bearer <token>'{ "items": [ { "name": "read_file", "description": "Read the contents of a file under the working directory.", "input_schema": { "type": "object", "properties": { "path": { "type": "string" }, "reason": { "type": "string" } }, "required": ["path", "reason"] }, "read_only": true }, { "name": "bash", "description": "Run a shell command in a sandboxed shell.", "input_schema": { "type": "object", "properties": { "command": { "type": "string" }, "reason": { "type": "string" } }, "required": ["command", "reason"] }, "read_only": false } ], "meta": { "total": 42 }}Responses
Section titled “Responses”A page of tools plus pagination metadata. Each items[] element is the daemon’s row shape (name, description, JSON-Schema input, read_only). meta.total is the full count; meta.page and meta.limit are present only when limit>0.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "realm_scope_unsupported", "message": "realm scope not supported on this route"}| 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 a 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. |
{ "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. |
GET /api/v1/agent/tools/read-only
Section titled “GET /api/v1/agent/tools/read-only”Lists the read-only subset of the catalogue (safe for planning / sessionless runs). Pagination honored.
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. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
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 tool of client.agent.tools.listReadOnlyIterator()) { console.log(tool.name);}curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/tools/read-only' \ -H 'Authorization: Bearer <token>'{ "items": [ { "name": "read_file", "description": "Read the contents of a file under the working directory.", "input_schema": { "type": "object", "properties": { "path": { "type": "string" }, "reason": { "type": "string" } }, "required": ["path", "reason"] }, "read_only": true } ], "meta": { "total": 17 }}Responses
Section titled “Responses”A page of read-only tool schemas plus pagination metadata. meta.total is the full count; meta.page and meta.limit are present only when limit>0.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "realm_scope_unsupported", "message": "realm scope not supported on this route"}| 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 a 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. |
{ "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. |
GET /api/v1/agent/tools/{name}
Section titled “GET /api/v1/agent/tools/{name}”Returns the schema for a single built-in tool by name. Returns 404 tool_not_found for an unknown name.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The tool name. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
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.tools.get('read_file');console.log(result.data.tool);curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/tools/read_file' \ -H 'Authorization: Bearer <token>'{ "tool": { "name": "read_file", "description": "Read the contents of a file under the working directory.", "input_schema": { "type": "object", "properties": { "path": { "type": "string", "description": "Absolute or cwd-relative file path." }, "reason": { "type": "string", "description": "Why the file is being read." } }, "required": ["path", "reason"] }, "read_only": true }}Responses
Section titled “Responses”The schema is returned under tool (the gateway re-keys the daemon reply). input_schema is the JSON-Schema the run body must conform to.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "realm_scope_unsupported", "message": "realm scope not supported on this route"}| 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 a 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. |
{ "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": "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. | List the catalogue (GET /tools) and use a valid name. |
{ "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. |
Session tool listings
Section titled “Session tool listings”GET /api/v1/agent/sessions/{id}/tools
Section titled “GET /api/v1/agent/sessions/{id}/tools”Returns the complete effective tool set for a live session: the built-in tools narrowed by the session’s tool mode, agent allowlist and headless exclusions, plus the MCP, skill and other tools the session’s configuration enables. Returns 404 when the session is not live.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
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. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
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 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, returns 404 not_found. |
realm | query | string | No | Query alias of the X-Hoody-Realm header (read only when the header is absent). |
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 tool of client.agent.sessions.listToolsIterator(sessionId)) { console.log(tool.name);}curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/tools' \ -H 'Authorization: Bearer <token>'{ "items": [ { "name": "read_file", "description": "Read the contents of a file under the working directory.", "input_schema": { "type": "object", "properties": { "path": { "type": "string" }, "reason": { "type": "string" } }, "required": ["path", "reason"] }, "read_only": true }, { "name": "bash", "description": "Run a shell command in a sandboxed shell.", "input_schema": { "type": "object", "properties": { "command": { "type": "string" }, "reason": { "type": "string" } }, "required": ["command", "reason"] }, "read_only": false }, { "name": "mcp__filesystem__list_dir", "description": "List a directory via the filesystem MCP server.", "input_schema": { "type": "object" }, "read_only": true } ], "meta": { "total": 38 }}Responses
Section titled “Responses”A page of the session’s effective tools plus pagination metadata. meta.total is the full count; meta.page and meta.limit are present only when limit>0.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "invalid_realm", "message": "invalid realm selector"}| 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 a 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. |
GET /api/v1/agent/sessions/{id}/tools/mcp
Section titled “GET /api/v1/agent/sessions/{id}/tools/mcp”Returns the MCP (mcp__*) tools available in a live session (drawn from the same effective tool set as the session’s tool listing). Returns 404 when the session is not live.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
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. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
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 realm id. |
realm | query | string | No | Query alias of the X-Hoody-Realm header. |
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 tool of client.agent.sessions.listMcpToolsIterator(sessionId)) { console.log(tool.name);}curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/tools/mcp' \ -H 'Authorization: Bearer <token>'{ "items": [ { "name": "mcp__filesystem__list_dir", "description": "List a directory via the filesystem MCP server.", "input_schema": { "type": "object" }, "read_only": true }, { "name": "mcp__github__create_issue", "description": "Create an issue on a GitHub repository.", "input_schema": { "type": "object", "properties": { "repo": { "type": "string" }, "title": { "type": "string" }, "body": { "type": "string" } }, "required": ["repo", "title"] }, "read_only": false } ], "meta": { "total": 7 }}Responses
Section titled “Responses”A page of MCP tools plus pagination metadata. Each items[] element has the same tool-row shape as the session tool listing. meta.total is the full count.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "invalid_realm", "message": "invalid realm selector"}| 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 a 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. |
Run a tool sessionless
Section titled “Run a tool sessionless”The three routes under POST /api/v1/agent/tools/{name}/... run a tool with no open session: an ephemeral local session is built from the scope headers and the tool goes through the same permission checks as the agent’s own tool calls. The work is not queued behind any open session; confirm and allow_mutations are wired straight onto the tool-run envelope.
POST /api/v1/agent/tools/{name}/run
Section titled “POST /api/v1/agent/tools/{name}/run”Runs a tool synchronously and returns the result envelope. Caller-supplied control keys (confirmed, _-prefixed keys) are ignored; pass confirm, confirm_token, and allow_mutations only through the documented fields.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The tool name. |
confirm | query | boolean | No | Query alias of the body confirm field (re-issue a previously-parked confirmation; pair with confirm_token). |
confirm_token | query | string | No | Query alias of the body confirm_token field (the single-use token returned in the 409 tool_needs_confirmation details). |
X-Hoody-Tool-Mode | header | string | No | Sessionless tool-mode for the ephemeral session: standard (default) or orchestrator. Any other value is refused with 400 invalid_tool_mode. |
X-Hoody-Dir-Scope | header | string | No | Sessionless directory-access scope: home (default) or full. Any other value is refused with 400 invalid_dir_scope. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. Paths the tool touches resolve under this subtree. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
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 realm id. |
realm | query | string | No | Query alias of the X-Hoody-Realm header. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
params | object | No | The tool’s input parameters (its JSON-Schema body). |
confirm | boolean | No | Re-issue a previously-parked confirmation. Must be paired with confirm_token. |
confirm_token | string | No | The single-use token returned in the prior 409 tool_needs_confirmation details, bound to the tool+params that parked. |
allow_mutations | boolean | No | Opt a non-read-only tool into running with every permission check applied (else the run is refused with 400 tool_mutation_refused). |
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.tools.run('read_file', { params: { path: '/home/user/project/README.md', reason: 'read the project overview before making changes', },});console.log(result.data.status, result.data.output);curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/tools/read_file/run' \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -H 'X-Hoody-Cwd: /home/user/project' \ -d '{ "params": { "path": "/home/user/project/README.md", "reason": "read the project overview before making changes" } }'{ "status": "ok", "output": " 1\t# Project\n 2\t\n 3\tInternal tooling for the team.\n 4\t", "is_error": false}Responses
Section titled “Responses”The tool result envelope. status is "ok" once the run completed; a tool that ran and failed is still "ok", so read is_error. error_code is present on a typed tool failure; hint and hint_cmd are present when the tool suggested a recovery; code and current_revision are present on a guarded write refused by an expected-revision check.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "tool_mutation_refused", "message": "sessionless run refused for a non-read-only tool"}| 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 a 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_tool_mode | Invalid tool mode header | X-Hoody-Tool-Mode is not standard or orchestrator. Nothing was run. | Send standard or orchestrator, or omit the header. |
invalid_dir_scope | Invalid directory scope header | X-Hoody-Dir-Scope is not home or full. Nothing was run. | Send home or full, or omit the header. |
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, and this session’s realm is not one of them. The session can still be read; nothing was run. | Continue in a session of a realm the login serves, or log 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": "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": "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. |
approver_lease_required | Approver lease required | This session’s approver lease was minted, so every decision must present the current capability in X-Hoody-Approver-Lease. | Acquire the lease (POST /sessions/{id}/approver-lease) and re-issue the confirmation with X-Hoody-Approver-Lease. |
approver_lease_invalid | Approver lease invalid | The presented capability does not verify for this session (wrong session, a fenced generation, or a daemon restart). | Re-acquire the lease (POST /sessions/{id}/approver-lease). |
{ "code": "approver_lease_expired", "message": "the approver lease expired"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
approver_lease_expired | Approver lease expired | The presented lease lapsed; an expired lease never authorises a decision. | Re-acquire the lease (POST /sessions/{id}/approver-lease). |
{ "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 before the handler reads it. | 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, so it runs no work in any session until it can. Nothing was run. | Retry after the restriction is readable. |
POST /api/v1/agent/tools/{name}/runAsync
Section titled “POST /api/v1/agent/tools/{name}/runAsync”Async form of /run: returns {job_id} immediately and runs the gated tool in the background. Poll GET /jobs/{id} or GET /jobs/{id}/result for the result, a parked confirmation, or an execution error. A non-read-only sessionless run still requires an explicit posture.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The tool name. |
confirm | query | boolean | No | Query alias of the body confirm field. |
confirm_token | query | string | No | Query alias of the body confirm_token field. |
X-Hoody-Tool-Mode | header | string | No | Sessionless tool-mode for the ephemeral session (standard or orchestrator). |
X-Hoody-Dir-Scope | header | string | No | Sessionless directory-access scope (home or full). |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). |
X-Hoody-Realm | header | string | No | Per-request realm selector: "global" or a 24-hex realm id. |
realm | query | string | No | Query alias of the X-Hoody-Realm header. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
params | object | No | The tool’s input parameters. |
confirm | boolean | No | Re-issue a previously-parked confirmation (paired with confirm_token). |
confirm_token | string | No | Single-use token from the prior 409 tool_needs_confirmation details. |
allow_mutations | boolean | No | Opt a non-read-only tool into running with every permission check applied. |
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 started = await client.agent.tools.start('read_file', { params: { path: '/home/user/project/README.md', reason: 'read the project overview before making changes', },});const jobId = started.data.job_id;if (!jobId) throw new Error('The tool run did not start');const result = await client.agent.jobs.getResult(jobId);# Start the async runcurl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/tools/read_file/runAsync' \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -H 'X-Hoody-Cwd: /home/user/project' \ -d '{ "params": { "path": "/home/user/project/README.md", "reason": "read the project overview before making changes" } }'# Poll the resultcurl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/jobs/{job_id}/result' \ -H 'Authorization: Bearer <token>'{ "job_id": "job-4f8c2a1d3b6e7890"}The tool run is complete when GET /jobs/{job_id}/result answers the same tool-result envelope /run returns:
{ "status": "ok", "output": " 1\t# Project\n 2\t\n 3\tInternal tooling for the team.\n 4\t", "is_error": false}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
Responses
Section titled “Responses”The gateway-minted job id. Poll GET /jobs/{id} or GET /jobs/{id}/result for the tool result, a parked confirmation (re-issue /runAsync with confirm:true plus the confirm_token), or an execution error.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "invalid_tool_mode", "message": "X-Hoody-Tool-Mode must be \"standard\" or \"orchestrator\""}| 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 a 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_tool_mode | Invalid tool mode header | X-Hoody-Tool-Mode is not standard or orchestrator. Nothing was run. | Send standard or orchestrator, or omit the header. |
invalid_dir_scope | Invalid directory scope header | X-Hoody-Dir-Scope is not home or full. Nothing was run. | Send home or full, or omit the header. |
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": "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. | 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. |
POST /api/v1/agent/tools/{name}/stream
Section titled “POST /api/v1/agent/tools/{name}/stream”Streaming form of /run over Server-Sent Events: a start frame, then a result / needs_confirmation / error frame, then end. The work is a one-shot gated RPC, so there is no per-token stream; the SSE shape mirrors the kit streaming convention for symmetry with the session stream.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | The tool name. |
confirm | query | boolean | No | Query alias of the body confirm field. |
confirm_token | query | string | No | Query alias of the body confirm_token field. |
X-Hoody-Tool-Mode | header | string | No | Sessionless tool-mode for the ephemeral session (standard or orchestrator). |
X-Hoody-Dir-Scope | header | string | No | Sessionless directory-access scope (home or full). |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). |
X-Hoody-Realm | header | string | No | Per-request realm selector: "global" or a 24-hex realm id. |
realm | query | string | No | Query alias of the X-Hoody-Realm header. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
params | object | No | The tool’s input parameters. |
confirm | boolean | No | Re-issue a previously-parked confirmation (paired with confirm_token). |
confirm_token | string | No | Single-use token from the prior 409 tool_needs_confirmation details. |
allow_mutations | boolean | No | Opt a non-read-only tool into running with every permission check applied. |
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 stream = await client.agent.tools.run( 'read_file', { params: { path: '/home/user/project/README.md', reason: 'read the project overview before making changes', }, }, { stream: true },);for await (const frame of stream) { console.log(frame.event, frame.data);}curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/tools/read_file/stream' \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -H 'X-Hoody-Cwd: /home/user/project' \ -d '{ "params": { "path": "/home/user/project/README.md", "reason": "read the project overview before making changes" } }'event: startdata: {"tool":"read_file"}
event: resultdata: {"status":"ok","output":" 1\t# Project\n 2\t\n 3\tInternal tooling for the team.\n 4\t","is_error":false}
event: enddata: {}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
Responses
Section titled “Responses”SSE stream of the tool run: an opening start frame, a result / needs_confirmation / error frame, then a closing end frame.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "invalid_tool_mode", "message": "X-Hoody-Tool-Mode must be \"standard\" or \"orchestrator\""}| 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 a 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_tool_mode | Invalid tool mode header | X-Hoody-Tool-Mode is not standard or orchestrator. Nothing was run. | Send standard or orchestrator, or omit the header. |
invalid_dir_scope | Invalid directory scope header | X-Hoody-Dir-Scope is not home or full. Nothing was run. | Send home or full, or omit the header. |
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": "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. | 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. |
Run a tool inside a live session
Section titled “Run a tool inside a live session”POST /api/v1/agent/sessions/{id}/tools/{name}/run
Section titled “POST /api/v1/agent/sessions/{id}/tools/{name}/run”Runs a tool on a live session through the same permission checks as the agent’s own tool calls: caller-supplied control keys are ignored, and the tool runs in the session’s own scope (its realm, container, cwd, tool mode and dir scope) with every check applied. It claims the session’s single serial turn slot, so a direct tool run never races the turn loop (409 turn_in_flight if a turn is running; 409 gate_parked if a gate is parked). Because it occupies the turn slot it is also cancellable the same way a turn is: a concurrent POST /sessions/{id}/cancel (or a WS cancel frame, or session close) aborts the in-flight run. A parked confirmation returns 409 tool_needs_confirmation with the echoed params; re-issue with confirm:true. A tool-call rule is checked here too: a limit the call breaks returns 200 is_error; a check_in it falls under, or a rules check that could not complete, returns 409 tool_needs_confirmation with details.gate_cause: "rules" and details.reason naming the rule. Returns 404 tool_not_found when the tool is not in the session’s effective tool list.
Some tools need the session’s model loop or a human and cannot be run directly: spawn_agent, run_workflow, review_with_panel, and ask_question_to_user are refused with 422 tool_not_directly_runnable and nothing runs; ask the agent for them in a turn instead.
On a session whose approval policy is "always", a tool that is itself a nested executor (spawn_agent, run_workflow, review_with_panel, tool_orchestrator, run_codex, …) returns 409 approval_policy_unsatisfiable instead; every other side-effecting tool parks for confirmation as usual, and the confirmation must carry the approver lease (X-Hoody-Approver-Lease) when one was minted.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
name | path | string | Yes | The tool name. |
confirm | query | boolean | No | Query alias of the body confirm field (re-issue a previously-parked confirmation; pair with confirm_token). |
confirm_token | query | string | No | Query alias of the body confirm_token field. |
X-Hoody-Approver-Lease | header | string | No | The approver-lease capability returned by POST /sessions/{id}/approver-lease. Required on every confirmation on an "always" session whose lease was minted. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
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 realm id. |
realm | query | string | No | Query alias of the X-Hoody-Realm header. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
params | object | No | The tool’s input parameters (its JSON-Schema body). |
confirm | boolean | No | Re-issue a previously-parked confirmation. Must be paired with confirm_token. |
confirm_token | string | No | Single-use token from the prior 409 tool_needs_confirmation details. |
allow_mutations | boolean | No | Opt a non-read-only tool into running with every permission check applied on a sessionless run. |
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.runTool(sessionId, 'bash', { params: { command: 'git status', reason: 'check the working tree before committing', },});console.log(result.data.status, result.data.output);curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/tools/bash/run' \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -d '{ "params": { "command": "git status", "reason": "check the working tree before committing" } }'{ "status": "ok", "output": "On branch main\nYour branch is up to date with 'origin/main'.\n\nnothing to commit, working tree clean", "is_error": false}Responses
Section titled “Responses”The tool result envelope. status is "ok" once the run completed; a tool that ran and failed is still "ok", so read is_error. error_code is present on a typed tool failure; hint and hint_cmd are present when the tool suggested a recovery; code and current_revision are present on a guarded write refused by an expected-revision check.
| Error Code | Title | Description | Resolution |
|---|
{ "code": "invalid_realm", "message": "invalid realm selector"}| 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 a 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": "realm_not_allowed", "message": "realm 0123456789abcdef01234567 is not allowed for this realm-limited login"}| 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, and this session’s realm is not one of them. The session can still be read; nothing was run. | Continue in a session of a realm the login serves, or log 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": "tool_not_found", "message": "no tool with that name"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
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. |
{ "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. |
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. |
approver_lease_required | Approver lease required | This session’s approver lease was minted, so every decision, including a gated tool-run confirmation, must present the current capability in X-Hoody-Approver-Lease. | Acquire the lease (POST /sessions/{id}/approver-lease) and re-issue the confirmation with X-Hoody-Approver-Lease. |
approver_lease_invalid | Approver lease invalid | The presented capability does not verify for this session (wrong session, a fenced generation, or a daemon restart). | Re-acquire the lease (POST /sessions/{id}/approver-lease). |
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": "approver_lease_expired", "message": "the approver lease expired"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
approver_lease_expired | Approver lease expired | The presented lease lapsed; an expired lease never authorises a decision. | Re-acquire the lease (POST /sessions/{id}/approver-lease). |
{ "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. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "tool_not_directly_runnable", "message": "tool \"spawn_agent\" needs the session's model loop or a human; it cannot be run directly"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
tool_not_directly_runnable | Tool not directly runnable | The named tool is in the session’s tool list but needs the session’s model loop or a human (spawn_agent, run_workflow, review_with_panel, ask_question_to_user), so a direct run cannot honour it. Nothing ran. | Ask the agent for it in a turn (POST /sessions/{id}/turns) instead of running the tool directly. |
{ "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, so it runs no work in any session until it can. Nothing was run. | Retry after the restriction is readable. |