# 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 Tool endpoints expose the agent's tool catalogue, surface a live session's effective tool set, and let you execute a tool synchronously, asynchronously, or as a Server-Sent Events stream. Use these endpoints when you need to introspect what tools are available to an agent, inspect a single tool's JSON-Schema input, or invoke a tool directly — either against an open session (with full gating against the session's frozen scope) or sessionlessly (with the gateway creating an ephemeral session from per-request scope headers).

All endpoints in this group go through the container-scoped agent endpoint and must be reached through `hoody-proxy`.


The catalogue and single-tool endpoints (`GET /tools`, `GET /tools/{name}`, `GET /tools/read-only`) are catalogue-wide and realm-independent — a per-request `X-Hoody-Realm` header on these routes is rejected with `realm_scope_unsupported`.


---

## Catalogue

### `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

| 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/write resolves (HoodyPaths). |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `"global"` or a 24-hex id (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on this route. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on this route. |

#### Response



```json
{
  "items": [
    {
      "name": "read_file",
      "description": "Read the contents of a file under the current directory scope.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": {
          "path": { "type": "string" }
        },
        "required": ["path"]
      }
    },
    {
      "name": "bash",
      "description": "Execute a shell command under the session's tool mode.",
      "read_only": false,
      "input_schema": {
        "type": "object",
        "properties": {
          "cmd": { "type": "string" },
          "cwd": { "type": "string" }
        },
        "required": ["cmd"]
      }
    }
  ],
  "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 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. |


```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 reach the service through the public endpoint. | 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. |



#### SDK usage

```js
await client.agent.tools.listToolsIterator({ page: 1, limit: 50 });
```

#### cURL

```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/tools?page=1&limit=50" \
  -H "X-Hoody-Cwd: /home/user/project"
```

---

### `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

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Path identifier. |
| `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 id. Rejected (400 `realm_scope_unsupported`) on this route. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on this route. |

#### Response



```json
{
  "name": "bash",
  "description": "Execute a shell command under the session's tool mode.",
  "read_only": false,
  "input_schema": {
    "type": "object",
    "properties": {
      "cmd": { "type": "string", "description": "Shell command line to execute." },
      "cwd": { "type": "string", "description": "Working directory; defaults to the session cwd." },
      "timeout_ms": { "type": "integer", "description": "Execution timeout in milliseconds." }
    },
    "required": ["cmd"]
  }
}
```


```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 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. |


```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 reach the service through the public endpoint. | 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; 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. |



#### SDK usage

```js
await client.agent.tools.getTool("bash");
```

#### cURL

```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/tools/bash" \
  -H "X-Hoody-Cwd: /home/user/project"
```

---

### `GET /api/v1/agent/tools/read-only`

Lists the read-only tools — those safe for planning and sessionless runs. Pagination is 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. |
| `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 id. Rejected (400 `realm_scope_unsupported`) on this route. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on this route. |

#### Response



```json
{
  "items": [
    {
      "name": "read_file",
      "description": "Read the contents of a file under the current directory scope.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": { "path": { "type": "string" } },
        "required": ["path"]
      }
    },
    {
      "name": "list_dir",
      "description": "List a directory under the current directory scope.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": { "path": { "type": "string" } }
      }
    }
  ],
  "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 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. |


```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 reach the service through the public endpoint. | 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; 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. |



#### SDK usage

```js
await client.agent.tools.listReadOnlyToolsIterator({ page: 1, limit: 50 });
```

#### cURL

```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/tools/read-only?page=1&limit=50" \
  -H "X-Hoody-Cwd: /home/user/project"
```

---

## Session tools

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

Returns the complete effective tool set for a **live** session — tools that are config-enabled and on `PATH`, gated by the session's tool mode / agent allowlist / headless exclusions. Returns `404` when the session is not live.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Session 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. |
| `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 id. Rejected (400 `realm_scope_unsupported`) on active-only routes. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on active-only routes. |

#### Response



```json
{
  "items": [
    {
      "name": "read_file",
      "description": "Read the contents of a file under the current directory scope.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": { "path": { "type": "string" } },
        "required": ["path"]
      }
    },
    {
      "name": "write_file",
      "description": "Write content to a file under the current directory scope.",
      "read_only": false,
      "input_schema": {
        "type": "object",
        "properties": {
          "path": { "type": "string" },
          "content": { "type": "string" }
        },
        "required": ["path", "content"]
      }
    }
  ],
  "meta": {
    "total": 24,
    "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 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 reach the service through the public endpoint. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "code": "not_found",
  "message": "session not live"
}
```
| 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; 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. |


```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. |



#### SDK usage

```js
await client.agent.tools.listSessionToolsIterator("sess_01HMZ8XJ5K7Y9P3Q4R6T8V0WAB", {
  page: 1,
  limit: 50,
});
```

#### cURL

```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/sessions/sess_01HMZ8XJ5K7Y9P3Q4R6T8V0WAB/tools?page=1&limit=50" \
  -H "X-Hoody-Cwd: /home/user/project"
```

---

### `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` when the session is not live.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Session 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. |
| `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 id. Rejected (400 `realm_scope_unsupported`) on active-only routes. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on active-only routes. |

#### Response



```json
{
  "items": [
    {
      "name": "mcp__filesystem__read",
      "description": "Read a file via the filesystem MCP server.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": { "path": { "type": "string" } },
        "required": ["path"]
      }
    },
    {
      "name": "mcp__github__list_issues",
      "description": "List issues from a GitHub repository.",
      "read_only": true,
      "input_schema": {
        "type": "object",
        "properties": {
          "owner": { "type": "string" },
          "repo": { "type": "string" }
        },
        "required": ["owner", "repo"]
      }
    }
  ],
  "meta": {
    "total": 6,
    "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 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 reach the service through the public endpoint. | Reach the agent through `hoody-proxy`, not by connecting to the container directly. |


```json
{
  "code": "not_found",
  "message": "session not live"
}
```
| 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; 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. |


```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. |



#### SDK usage

```js
await client.agent.tools.listSessionMCPToolsIterator("sess_01HMZ8XJ5K7Y9P3Q4R6T8V0WAB", {
  page: 1,
  limit: 50,
});
```

#### cURL

```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/sessions/sess_01HMZ8XJ5K7Y9P3Q4R6T8V0WAB/tools/mcp?page=1&limit=50" \
  -H "X-Hoody-Cwd: /home/user/project"
```

---

## Run a tool sessionlessly

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

Runs a tool through the single gated policy gate with no open session: the gateway creates an ephemeral local session from the scope headers (`X-Hoody-Cwd` / `X-Hoody-Config-Dir`) and runs the full permission checks. Sessionless runs are read-only by default — a non-read-only tool requires `allow_mutations:true` or `confirm:true` (else `400 tool_mutation_refused`). A parked confirmation returns `409 tool_needs_confirmation` with the echoed `tool+params`; re-issue with `confirm:true`.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Path identifier (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 (e.g. `read_only` / `full`); default per the daemon. |
| `X-Hoody-Dir-Scope` | header | string | No | Sessionless directory-access scope for the ephemeral session (e.g. `home` / `full`); default `home`. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (the ephemeral session resolves paths 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 id. Rejected (400 `realm_scope_unsupported`) on active-only routes. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on active-only routes. |

#### 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` from the prior 409. |
| `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 permission checks (else a sessionless mutating run is refused `400 tool_mutation_refused`). |

Caller-supplied `confirmed`/`_`-prefixed control keys are ignored — they are never trusted from the wire.

```json
{
  "params": {
    "cmd": "ls -la /home/user/project",
    "timeout_ms": 5000
  },
  "allow_mutations": false
}
```

#### Response



```json
{
  "tool": "bash",
  "is_error": false,
  "content": [
    {
      "type": "text",
      "text": "total 48\ndrwxr-xr-x  6 user user 4096 May 12 09:14 .\ndrwxr-xr-x 18 user user 4096 May 12 09:14 ..\n-rw-r--r--  1 user user 2204 May 12 09:14 README.md\ndrwxr-xr-x  3 user user 4096 May 12 09:14 src\n"
    }
  ]
}
```


```json
{
  "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 an active-only 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 reach the service through the public endpoint. | 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": "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 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; 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. |



#### SDK usage

```js
await client.agent.tools.runTool("bash", {
  params: { cmd: "ls -la /home/user/project", timeout_ms: 5000 },
  allow_mutations: false,
});
```

#### cURL

```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/tools/bash/run" \
  -H "Content-Type: application/json" \
  -H "X-Hoody-Cwd: /home/user/project" \
  -H "X-Hoody-Tool-Mode: read_only" \
  -d '{
    "params": { "cmd": "ls -la /home/user/project", "timeout_ms": 5000 },
    "allow_mutations": false
  }'
```

---

### `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}` 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

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Path identifier (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 (e.g. `read_only` / `full`); default per the daemon. |
| `X-Hoody-Dir-Scope` | header | string | No | Sessionless directory-access scope for the ephemeral session (e.g. `home` / `full`); default `home`. |
| `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 id. Rejected (400 `realm_scope_unsupported`) on active-only routes. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on active-only routes. |

#### 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` from the prior 409. |
| `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 permission checks. |

```json
{
  "params": {
    "cmd": "find /home/user/project -name '*.ts' -newer /tmp/marker",
    "timeout_ms": 30000
  },
  "allow_mutations": false
}
```

#### Response



```json
{
  "job_id": "job_01HN5K7Y9P3Q4R6T8V0WAB"
}
```


```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 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 reach the service through the public endpoint. | 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`). 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; 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. |



#### SDK usage

Submit the run:

```js
const { job_id } = await client.agent.tools.runToolAsync("bash", {
  params: { cmd: "find /home/user/project -name '*.ts' -newer /tmp/marker", timeout_ms: 30000 },
  allow_mutations: false,
});
```

Fetch the result via `client.agent.jobs.getJobResult`:

```js
const result = await client.agent.jobs.getJobResult({ id: job_id });
```


The async form returns `{job_id}` and runs in the background. Poll `client.agent.jobs.getJobResult({ id })` for the final tool result, a parked `needs_confirmation`, or the execution error.


#### cURL

```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/tools/bash/runAsync" \
  -H "Content-Type: application/json" \
  -H "X-Hoody-Cwd: /home/user/project" \
  -H "X-Hoody-Tool-Mode: read_only" \
  -d '{
    "params": { "cmd": "find /home/user/project -name \u0027*.ts\u0027 -newer /tmp/marker", "timeout_ms": 30000 },
    "allow_mutations": false
  }'
```

---

### `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 (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 (e.g. `read_only` / `full`); default per the daemon. |
| `X-Hoody-Dir-Scope` | header | string | No | Sessionless directory-access scope for the ephemeral session (e.g. `home` / `full`); default `home`. |
| `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 id. Rejected (400 `realm_scope_unsupported`) on active-only routes. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on active-only routes. |

#### 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` from the prior 409. |
| `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 permission checks. |

```json
{
  "params": {
    "cmd": "wc -l /home/user/project/src/**/*.ts",
    "timeout_ms": 10000
  },
  "allow_mutations": false
}
```

#### Response



```text
event: start
data: {"tool":"bash","started_at":"2026-05-12T09:14:02.114Z"}

event: result
data: {"tool":"bash","is_error":false,"content":[{"type":"text","text":"  142 src/cli.ts\n   88 src/agent.ts\n  214 src/router.ts\n"}]}

event: end
data: {"tool":"bash","finished_at":"2026-05-12T09:14:02.487Z"}
```


```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 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 reach the service through the public endpoint. | 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`). 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; 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. |


```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. |



#### SDK usage

```js
for await (const frame of client.agent.tools.streamTool("bash", {
  params: { cmd: "wc -l /home/user/project/src/**/*.ts", timeout_ms: 10000 },
  allow_mutations: false,
})) {
  // frame.event ∈ {"start", "result", "needs_confirmation", "error", "end"}
}
```

#### cURL

```bash
curl -N -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/tools/bash/stream" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -H "X-Hoody-Cwd: /home/user/project" \
  -H "X-Hoody-Tool-Mode: read_only" \
  -d '{
    "params": { "cmd": "wc -l /home/user/project/src/**/*.ts", "timeout_ms": 10000 },
    "allow_mutations": false
  }'
```

---

## Run a tool inside a live session

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

Runs a tool through the gated policy gate on a **live** session, against the session's frozen realm/container/cwd/tool-mode/dir-scope, with the full permission checks. 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`. 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 dangerous bash shapes (`rm -rf /`, `curl|sh`, `git push --force`) and outside-cwd file writes — those return `200 is_error`, no `409 confirm` round. The 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 | Session identifier. |
| `name` | path | string | Yes | Path identifier (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-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 id. Rejected (400 `realm_scope_unsupported`) on active-only routes. |
| `realm` | query | string | No | Per-request realm selector — the `in:query` alias of `X-Hoody-Realm`. Rejected (400 `realm_scope_unsupported`) on active-only routes. |

#### 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` from the prior 409. |
| `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 permission checks (no effect on the in-session run, which inherits the session's frozen scope). |

Caller-supplied `confirmed`/`_`-prefixed control keys are ignored — they are never trusted from the wire.

```json
{
  "params": {
    "path": "/home/user/project/src/agent.ts",
    "max_lines": 200
  }
}
```

#### Response



```json
{
  "tool": "read_file",
  "is_error": false,
  "content": [
    {
      "type": "text",
      "text": "import { createAgent } from \"./create.js\";\nexport const agent = createAgent({\n  model: \"hoody-1\",\n  tools: [\"read_file\", \"write_file\", \"bash\"],\n});\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 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 reach the service through the public endpoint. | 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": "turn_in_flight",
  "message": "a turn is already running on this session"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `tool_needs_confirmation` | Tool needs confirmation | The tool was NOT executed: the permission checks 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; 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. |



#### SDK usage

```js
await client.agent.tools.runSessionTool("sess_01HMZ8XJ5K7Y9P3Q4R6T8V0WAB", "read_file", {
  params: { path: "/home/user/project/src/agent.ts", max_lines: 200 },
});
```

#### cURL

```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu/api/v1/agent/sessions/sess_01HMZ8XJ5K7Y9P3Q4R6T8V0WAB/tools/read_file/run" \
  -H "Content-Type: application/json" \
  -H "X-Hoody-Cwd: /home/user/project" \
  -d '{
    "params": { "path": "/home/user/project/src/agent.ts", "max_lines": 200 }
  }'
```