# Agent: Tools

**Page:** api/agent/tools

[Download Raw Markdown](./api/agent/tools.md)

---

{/* AUTO-GENERATED — Do not edit manually. Regenerate with: npm run docs:api:generate */}



## Agent: Tools

The Agent: Tools endpoints expose the agent's tool catalogue, the effective tool set of a live session (including its MCP subset), and a single gated choke point for executing tools. Every execution path — sessionless sync, sessionless async, sessionless SSE, or inside a live session — funnels through the same gate chain (deny list, static-stakes, read-before-edit, dir-scope, write-permission, tool-membership). Use these endpoints to introspect what tools are available, then run them with an explicit confirmation posture when the gate requires it.


The tool-run routes scrub caller-supplied `confirmed` / `_`-prefixed control keys before dispatch — those keys are never trusted from the wire. Confirmation must always be re-issued with the single-use `confirm_token` returned in the prior `409 tool_needs_confirmation`.


---

### Tool catalogue

#### `GET /api/v1/agent/tools`

Lists the agent's built-in tool schemas (name, description, JSON-Schema input, `read_only` flag). Catalogue-wide and realm-independent: a per-request realm header is rejected. Pass `?page=N&limit=M` for a window; omit `limit` for the full set.

### 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 (§6.1). Required by routes that resolve a cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). Rejected (`400`) on routes with no container dimension. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1): `"global"` or a 24-hex id. Rejected (`400 realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm` (read only when the header is absent). |



```bash
curl -sS -X GET \
  -H "X-Hoody-Cwd: /home/dev/projects/app" \
  "https://gateway.hoody.ai/api/v1/agent/tools?page=1&limit=50"
```


```ts
const page = await client.agent.tools.listToolsIterator({
  page: 1,
  limit: 50,
});
for await (const tool of page) {
  console.log(tool.name, tool.read_only);
}
```


```json
{
  "items": [
    {
      "name": "read_file",
      "description": "Read a UTF-8 text file from the workspace.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": {
          "path": { "type": "string" }
        },
        "required": ["path"]
      }
    },
    {
      "name": "bash",
      "description": "Run a shell command in the workspace sandbox.",
      "read_only": false,
      "input_schema": {
        "type": "object",
        "properties": {
          "command": { "type": "string" }
        },
        "required": ["command"]
      }
    }
  ],
  "meta": {
    "total": 42,
    "page": 1,
    "limit": 50
  }
}
```


```json
{
  "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. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`: its source IP is private/local. The gateway has no service-level auth — authorization is owned by `hoody-proxy`. | Reach the agent through `hoody-proxy` (e.g. `hoody agent …` → platform → proxy), not by connecting to the container directly. |


```json
{
  "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. |


```json
{
  "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}`

Returns the schema for a single built-in tool by name. Returns `404 tool_not_found` for an unknown name. Catalogue-wide and realm-independent: a per-request realm header is rejected.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |



```bash
curl -sS -X GET \
  "https://gateway.hoody.ai/api/v1/agent/tools/read_file"
```


```ts
const tool = await client.agent.tools.getTool({ name: "read_file" });
console.log(tool.name, tool.input_schema);
```


```json
{
  "name": "read_file",
  "description": "Read a UTF-8 text file from the workspace.",
  "read_only": true,
  "input_schema": {
    "type": "object",
    "properties": {
      "path": { "type": "string", "description": "Absolute workspace path." }
    },
    "required": ["path"]
  }
}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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`

Lists the read-only tools (safe for planning / sessionless runs). Catalogue-wide and realm-independent: a per-request realm header is rejected. Pagination honored.

### 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 (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |



```bash
curl -sS -X GET \
  "https://gateway.hoody.ai/api/v1/agent/tools/read-only?page=1&limit=50"
```


```ts
const page = await client.agent.tools.listReadOnlyToolsIterator({
  page: 1,
  limit: 50,
});
for await (const tool of page) {
  console.log(tool.name);
}
```


```json
{
  "items": [
    {
      "name": "read_file",
      "description": "Read a UTF-8 text file from the workspace.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": { "path": { "type": "string" } },
        "required": ["path"]
      }
    },
    {
      "name": "list_dir",
      "description": "List entries in a workspace directory.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": { "path": { "type": "string" } },
        "required": ["path"]
      }
    }
  ],
  "meta": {
    "total": 18,
    "page": 1,
    "limit": 50
  }
}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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

#### `GET /api/v1/agent/sessions/{id}/tools`

Returns the complete effective tool set for a **live** session (`computeTools = config-enabled AND on PATH`, gated by the session's tool mode / agent allowlist / headless exclusions). Returns `404 not_found` when the session is not live.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `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 (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |



```bash
curl -sS -X GET \
  "https://gateway.hoody.ai/api/v1/agent/sessions/sess_8f2a1c7e9b0d4f3a/tools?page=1&limit=50"
```


```ts
const page = await client.agent.tools.listSessionToolsIterator({
  id: "sess_8f2a1c7e9b0d4f3a",
  page: 1,
  limit: 50,
});
for await (const tool of page) {
  console.log(tool.name);
}
```


```json
{
  "items": [
    {
      "name": "read_file",
      "description": "Read a UTF-8 text file from the workspace.",
      "read_only": true
    },
    {
      "name": "bash",
      "description": "Run a shell command in the workspace sandbox.",
      "read_only": false
    },
    {
      "name": "mcp__github__list_issues",
      "description": "List issues in a GitHub repository (MCP).",
      "read_only": true
    }
  ],
  "meta": {
    "total": 31,
    "page": 1,
    "limit": 50
  }
}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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. |


```json
{
  "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`

Returns the MCP (`mcp__*`) tools available in a **live** session (drawn from the same `computeTools` snapshot). Returns `404 not_found` when the session is not live.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `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 (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |



```bash
curl -sS -X GET \
  "https://gateway.hoody.ai/api/v1/agent/sessions/sess_8f2a1c7e9b0d4f3a/tools/mcp?page=1&limit=50"
```


```ts
const page = await client.agent.tools.listSessionMCPToolsIterator({
  id: "sess_8f2a1c7e9b0d4f3a",
  page: 1,
  limit: 50,
});
for await (const tool of page) {
  console.log(tool.name);
}
```


```json
{
  "items": [
    {
      "name": "mcp__github__list_issues",
      "description": "List issues in a GitHub repository.",
      "read_only": true
    },
    {
      "name": "mcp__github__create_issue",
      "description": "Open a new GitHub issue.",
      "read_only": false
    }
  ],
  "meta": {
    "total": 7,
    "page": 1,
    "limit": 50
  }
}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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. |


```json
{
  "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. |



---

### Sessionless tool runs

These three routes share the same gate chain. They mint an ephemeral local session from the scope headers (`X-Hoody-Cwd` / `X-Hoody-Config-Dir` / `X-Hoody-Tool-Mode` / `X-Hoody-Dir-Scope`) and run `executeToolDirect` with the full gate chain (deny list / static-stakes / read-before-edit / dir-scope / write-permission / tool-membership). Sessionless runs are read-only by default — a non-read-only tool requires `allow_mutations:true` or `confirm:true`.


The flat, ungated `tool.*` socket handlers are never reachable through these routes. The operator `deny_list` is the floor of the dir-scope — size it to cover sensitive trees (e.g. `~/.ssh`, `~/.aws`) since a caller may point `X-Hoody-Cwd` anywhere it can read.


#### `POST /api/v1/agent/tools/{name}/run`

Runs a tool through the single gated choke point with no open session and returns the result inline. A parked confirmation returns `409 tool_needs_confirmation` with the echoed tool + params; re-issue with `confirm:true` (and the `confirm_token`).

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Path identifier. |
| `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 (e.g. `read_only` / `full`); default per the daemon. Ignored on the in-session run. |
| `X-Hoody-Dir-Scope` | header | string | No | Sessionless directory-access scope for the ephemeral session (e.g. `home` / `full`); default `home`. Ignored on the in-session run. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |

### Request Body

| Field | 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`; a bare `confirm:true` with no valid token does NOT bypass the gate. |
| `confirm_token` | string | No | The single-use token returned in the `409 tool_needs_confirmation` details. Bound to the tool / session / params it was minted for. |
| `allow_mutations` | boolean | No | Sessionless only: opt a non-read-only tool into running under the full gate chain (else a sessionless mutating run is refused `400 tool_mutation_refused`). |



```bash
curl -sS -X POST \
  -H "Content-Type: application/json" \
  -H "X-Hoody-Cwd: /home/dev/projects/app" \
  -d '{
    "params": { "path": "/home/dev/projects/app/src/index.ts" }
  }' \
  "https://gateway.hoody.ai/api/v1/agent/tools/read_file/run"
```


```ts
const result = await client.agent.tools.runTool({
  name: "read_file",
  data: { params: { path: "/home/dev/projects/app/src/index.ts" } },
  "X-Hoody-Cwd": "/home/dev/projects/app",
});
console.log(result);
```


```json
{
  "ok": true,
  "output": "export const app = { name: 'app' };\n"
}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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. |


```json
{
  "code": "tool_needs_confirmation",
  "message": "tool requires confirmation before execution",
  "details": {
    "tool": "write_file",
    "params": { "path": "/home/dev/projects/app/.env", "content": "..." },
    "confirm_token": "ctok_8f2a1c7e9b0d4f3a"
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `tool_needs_confirmation` | Tool needs confirmation | The tool was NOT executed: the gate chain (write-permission / dangerous-shape / outside-dir) requires confirmation first. The echoed tool + params let the caller re-issue the run with `confirm:true`. | Re-issue the run with `confirm:true` (or `?confirm=true`), preserving the echoed params. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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. |



---

#### `POST /api/v1/agent/tools/{name}/runAsync`

Async form of `runTool`: returns `{job_id}` immediately and runs the gated tool in the background. Poll `GET /jobs/{id}/result` for the result (or a parked confirmation). A non-read-only sessionless run still requires an explicit posture.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Path identifier. |
| `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 (e.g. `read_only` / `full`); default per the daemon. Ignored on the in-session run. |
| `X-Hoody-Dir-Scope` | header | string | No | Sessionless directory-access scope for the ephemeral session (e.g. `home` / `full`); default `home`. Ignored on the in-session run. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |

### Request Body

| Field | 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 `409 tool_needs_confirmation` details. |
| `allow_mutations` | boolean | No | Sessionless only: opt a non-read-only tool into running under the full gate chain. |



```bash
curl -sS -X POST \
  -H "Content-Type: application/json" \
  -H "X-Hoody-Cwd: /home/dev/projects/app" \
  -d '{
    "params": { "path": "/home/dev/projects/app/src/index.ts" }
  }' \
  "https://gateway.hoody.ai/api/v1/agent/tools/read_file/runAsync"
```


```ts
const { job_id } = await client.agent.tools.runToolAsync({
  name: "read_file",
  data: { params: { path: "/home/dev/projects/app/src/index.ts" } },
  "X-Hoody-Cwd": "/home/dev/projects/app",
});

// Poll for the result.
const result = await client.agent.jobs.getJobResult({ id: job_id });
console.log(result);
```


```json
{
  "job_id": "job_8f2a1c7e9b0d4f3a"
}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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`). | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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. |



---

#### `POST /api/v1/agent/tools/{name}/stream`

Streaming form of `runTool` 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. A non-read-only sessionless run still requires an explicit posture.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Path identifier. |
| `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 (e.g. `read_only` / `full`); default per the daemon. Ignored on the in-session run. |
| `X-Hoody-Dir-Scope` | header | string | No | Sessionless directory-access scope for the ephemeral session (e.g. `home` / `full`); default `home`. Ignored on the in-session run. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |

### Request Body

| Field | 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 `409 tool_needs_confirmation` details. |
| `allow_mutations` | boolean | No | Sessionless only: opt a non-read-only tool into running under the full gate chain. |



```bash
curl -sS -N -X POST \
  -H "Content-Type: application/json" \
  -H "X-Hoody-Cwd: /home/dev/projects/app" \
  -d '{
    "params": { "path": "/home/dev/projects/app/README.md" }
  }' \
  "https://gateway.hoody.ai/api/v1/agent/tools/read_file/stream"
```


```ts
const stream = await client.agent.tools.streamTool({
  name: "read_file",
  data: { params: { path: "/home/dev/projects/app/README.md" } },
  "X-Hoody-Cwd": "/home/dev/projects/app",
});

for await (const frame of stream) {
  if (frame.type === "start") {
    console.log("started");
  } else if (frame.type === "result") {
    console.log("result:", frame.payload);
  } else if (frame.type === "needs_confirmation") {
    console.log("needs confirmation:", frame.payload);
  } else if (frame.type === "error") {
    console.error("error:", frame.payload);
  } else if (frame.type === "end") {
    break;
  }
}
```


```text
event: start
data: {"job":"run","name":"read_file"}

event: result
data: {"ok":true,"output":"# app\n\nShort description.\n"}

event: end
data: {}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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`). | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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. |


```json
{
  "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. |



---

### In-session tool runs

#### `POST /api/v1/agent/sessions/{id}/tools/{name}/run`

Runs a tool through the gated choke point on a **live** session. The gateway scrubs caller-supplied control keys and routes the run through `executeToolDirect` against the session's frozen realm / container / cwd / tool-mode / dir-scope, with the full gate chain. 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 tool + params; re-issue with `confirm:true` (and the `confirm_token`). Returns `404 tool_not_found` when the tool is not in the session's effective tool list.


A session created with `headless:true` hard-denies the static-stakes bash shapes (`rm -rf /`, `curl|sh`, `git push --force`) and outside-cwd file writes (a `200 is_error`, not a `409` confirm round). Those confirm-gated shapes are parkable only on a non-headless session or the sessionless ephemeral path; a headless live session has no interactive confirmer for them.


### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `name` | path | string | Yes | Path identifier. |
| `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-Cwd` | header | string | No | Per-request working-directory scope (§6.1). |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1). Rejected (`400 realm_scope_unsupported`). |
| `realm` | query | string | No | Query alias of `X-Hoody-Realm`. |

### Request Body

| Field | 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`; a bare `confirm:true` with no valid token does NOT bypass the gate. |
| `confirm_token` | string | No | The single-use token returned in the `409 tool_needs_confirmation` details. Bound to the tool / session / params it was minted for. |
| `allow_mutations` | boolean | No | Sessionless only — ignored on the in-session run (it inherits the session's frozen posture). |



```bash
curl -sS -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "params": { "path": "/home/dev/projects/app/src/index.ts" }
  }' \
  "https://gateway.hoody.ai/api/v1/agent/sessions/sess_8f2a1c7e9b0d4f3a/tools/read_file/run"
```


```ts
const result = await client.agent.tools.runSessionTool({
  id: "sess_8f2a1c7e9b0d4f3a",
  name: "read_file",
  data: { params: { path: "/home/dev/projects/app/src/index.ts" } },
});
console.log(result);
```


```json
{
  "ok": true,
  "output": "export const app = { name: 'app' };\n"
}
```


```json
{
  "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. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "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. |


```json
{
  "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 gate chain (write-permission / dangerous-shape / outside-dir) requires confirmation first. The echoed tool + params let the caller re-issue the run with `confirm:true`. | 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. |
| `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`) and retry; read it via `GET /sessions/{id}` or `details.pending_gate`. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "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. |