# Agent: Workflows

**Page:** api/agent/workflows

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

---

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



Agent Workflows let you define, manage, and run multi-step automations on a live session. A workflow definition is the static recipe (an ordered set of steps with an entry point); a workflow run is a live execution of that recipe against a particular session. Use these endpoints to list, read, create, replace, delete, or hide workflow definitions, to dispatch a workflow onto a session, and to inspect or cancel in-flight runs.

All routes are realm-scoped to the GATEWAY's own realm and resolve definitions and runs through the per-request `X-Hoody-Cwd` / `X-Hoody-Config-Dir` headers. A client-supplied `X-Hoody-Realm` / `?realm=` is accepted on read-only listing routes but rejected on the routing tables that have no realm dimension to scope.

## Workflow definitions

### `GET /api/v1/agent/workflows`

List the workflow definitions visible to the requesting `cwd` / `config_dir` (name, summary, step briefs, system flag). Read one with `GET /workflows/{name}`; create or replace with `PUT /workflows/{name}`; delete with `DELETE /workflows/{name}`; hide with `POST /workflows/{name}/hide`.

### 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): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Response




```json
{
  "items": [
    {
      "name": "build",
      "summary": "Build the project and run smoke tests",
      "system": false,
      "steps": [
        { "id": "compile", "summary": "Run the build" },
        { "id": "test", "summary": "Run smoke tests" }
      ]
    },
    {
      "name": "deploy",
      "summary": "Deploy the built artifact to staging",
      "system": true,
      "steps": [
        { "id": "upload", "summary": "Upload artifact" },
        { "id": "promote", "summary": "Promote to staging" }
      ]
    }
  ],
  "meta": {
    "total": 12,
    "page": 1,
    "limit": 50
  }
}
```




```json
{
  "code": "realm_scope_unsupported",
  "message": "realm scope not supported on this route"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to 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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | 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

```ts
await client.agent.workflows.listWorkflowsIterator({ limit: 50 });
```

---

### `GET /api/v1/agent/workflows/{name}`

Read one workflow definition as JSON. The result rides the `{output, is_error}` tool envelope; `is_error: true` with `output` `"workflow X not found"` for an unknown name.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Workflow name (path identifier). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `include_revision` | query | boolean | No | If "true", the tool output's first line is `revision: `&lt;opaque&gt;`` — pass that value as putWorkflow's expected_revision to guard against concurrent edits; the JSON below it is unchanged. Strictly parsed: exactly one value, "true" or "false"; anything else (empty, "TRUE", "1", repeated) is a 400 bad_request. |

### Response




```json
{
  "output": {
    "name": "build",
    "summary": "Build the project and run smoke tests",
    "system": false,
    "entry_point": "compile",
    "steps": [
      { "id": "compile", "summary": "Run the build" },
      { "id": "test", "summary": "Run smoke tests" }
    ]
  },
  "is_error": false
}
```




```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, 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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | Reach the agent through hoody-proxy (e.g. `hoody agent …` → platform → 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 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": "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

```ts
await client.agent.workflows.getWorkflow({ name: "build" });
```

---

### `PUT /api/v1/agent/workflows/{name}`

Create or replace one workflow definition. The body carries the `definition` object; the `{name}` path value is authoritative. The daemon validates the definition strictly before writing atomically (unknown fields rejected, the loader's structural gate enforced), so a malformed definition is refused (`is_error: true`) rather than corrupting `workflow.json`. The `system` flag is authoritative from the embedded defaults — a caller cannot forge it.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Workflow name (path identifier). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `definition` | object | Yes | The full workflow definition object (steps, entry_point, summary). Validated strictly daemon-side before an atomic write. |

```json
{
  "definition": {
    "summary": "Build the project and run smoke tests",
    "entry_point": "compile",
    "steps": [
      { "id": "compile", "summary": "Run the build" },
      { "id": "test", "summary": "Run smoke tests" }
    ]
  }
}
```

### Response




```json
{
  "output": "workflow build upserted",
  "is_error": false
}
```




```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, 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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | Reach the agent through hoody-proxy (e.g. `hoody agent …` → platform → 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": "payload_too_large",
  "message": "request body exceeds the configured size limit"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). The gateway rejects an oversized body at the edge (`http.MaxBytesReader`) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |




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

```ts
await client.agent.workflows.putWorkflow({
  name: "build",
  data: {
    definition: {
      summary: "Build the project and run smoke tests",
      entry_point: "compile",
      steps: [
        { id: "compile", summary: "Run the build" },
        { id: "test", summary: "Run smoke tests" }
      ]
    }
  }
});
```

---

### `DELETE /api/v1/agent/workflows/{name}`

Delete one USER workflow definition. System workflows are refused (`is_error: true` — they re-seed on boot; hide them instead via `hideWorkflow`).

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Workflow name (path identifier). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Response




```json
{
  "output": "workflow build deleted",
  "is_error": false
}
```




```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, 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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | Reach the agent through hoody-proxy (e.g. `hoody agent …` → platform → 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

```ts
await client.agent.workflows.deleteWorkflow({ name: "build" });
```

---

### `POST /api/v1/agent/workflows/{name}/hide`

Hide or un-hide a workflow from the Workflows tab. Hiding is the only way to remove a SYSTEM workflow from view — system workflows are re-seeded on every boot and can never be deleted. User workflows can be hidden too. Pass `hidden: false` in the body to un-hide. The hide stays realm-private (the gateway's own realm only); a client-supplied `X-Hoody-Realm` / `?realm=` is ignored.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `name` | path | string | Yes | Workflow name (path identifier). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `hidden` | boolean | No | `true` (default) to hide the workflow; `false` to un-hide it. |

```json
{
  "hidden": true
}
```

### Response




```json
{
  "hidden_workflows": ["build", "deploy"]
}
```




```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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | Reach the agent through hoody-proxy (e.g. `hoody agent …` → platform → 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": "payload_too_large",
  "message": "request body exceeds the configured size limit"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). The gateway rejects an oversized body at the edge (`http.MaxBytesReader`) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |




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

```ts
await client.agent.workflows.hideWorkflow({ name: "build", data: { hidden: true } });
```

---

## Workflow runs

### `POST /api/v1/agent/sessions/{id}/workflows/{name}/runs`

Dispatch a workflow run onto a live session. The daemon returns no `RunID` synchronously, so the gateway mints its own `job_id` and correlates the daemon `RunID` lazily — `job.run_id` is `null` during the brief dispatch window and is populated once the workflow loop registers the run (observe `workflow_start` on the session's stream). Run events flow on the owning session's WS/SSE attach; there is no per-run event bus.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Session id (path identifier). |
| `name` | path | string | Yes | Workflow name (path identifier). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `prompt` | string | No | Optional input text fed to the workflow run. |

```json
{
  "prompt": "Build main and run smoke tests on the staging artifact"
}
```

### Response




```json
{
  "job_id": "job-9c1f5e2a",
  "session_id": "sess-3a4b6c8d",
  "run_id": null
}
```




```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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | Reach the agent through hoody-proxy (e.g. `hoody agent …` → platform → proxy), not by connecting to the container directly. |




```json
{
  "code": "workflow_not_found",
  "message": "no workflow named build"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `workflow_not_found` | Workflow not found | No workflow definition with the requested name is visible to this cwd/config_dir. | List the available workflows (`GET /workflows`) and use an existing name. |




```json
{
  "code": "gate_parked",
  "message": "a gate is parked on this session",
  "details": {
    "pending_gate": {
      "generation": 3,
      "id": "gate-3",
      "type": "confirm"
    }
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `turn_in_flight` | Turn in flight | A turn is already running on this session; the single serial turn slot is occupied, so a new turn/workflow run is refused. | Wait for the running turn to finish (observe `agent_done` on the session stream), then retry. |
| `gate_parked` | Gate parked | A confirm/question gate is parked on this session, so a new turn/workflow run is refused until it is answered. The parked gate is surfaced under `details.pending_gate`. | Answer the parked gate (`/confirm` or `/answer`) 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 (`http.MaxBytesReader`) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |




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

```ts
await client.agent.workflows.runSessionWorkflow({
  id: "sess-3a4b6c8d",
  name: "build",
  data: { prompt: "Build main and run smoke tests on the staging artifact" }
});
```

---

### `GET /api/v1/agent/workflows/runs`

Snapshot of in-flight and recently-finished workflow runs. The registry exposes `Snapshot()`, not `Subscribe()`, so poll this endpoint to track a run's progress. Live events also flow on the owning session's stream.

### 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): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Response




```json
{
  "items": [
    {
      "run_id": "run-7a2b1c3d",
      "workflow": "build",
      "session_id": "sess-3a4b6c8d",
      "status": "running",
      "started_at": "2026-01-12T09:14:33Z",
      "current_step": "test"
    },
    {
      "run_id": "run-1f9e0a4b",
      "workflow": "deploy",
      "session_id": "sess-3a4b6c8d",
      "status": "finished",
      "started_at": "2026-01-12T08:50:02Z",
      "finished_at": "2026-01-12T08:52:11Z"
    }
  ],
  "meta": {
    "total": 2,
    "page": 1,
    "limit": 50
  }
}
```




```json
{
  "code": "realm_scope_unsupported",
  "message": "realm scope not supported on this route"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to 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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | 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

```ts
await client.agent.workflows.listWorkflowRunsIterator({ limit: 50 });
```

---

### `GET /api/v1/agent/workflows/runs/{run_id}`

Get a single workflow run row by run id from the registry snapshot. Returns `404 not_found` when the run id is no longer in the snapshot (evicted or never existed).

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `run_id` | path | string | Yes | Run id (path identifier). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Response




```json
{
  "run_id": "run-7a2b1c3d",
  "workflow": "build",
  "session_id": "sess-3a4b6c8d",
  "status": "running",
  "started_at": "2026-01-12T09:14:33Z",
  "current_step": "test"
}
```




```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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | Reach the agent through hoody-proxy (e.g. `hoody agent …` → platform → 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; 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

```ts
await client.agent.workflows.getWorkflowRun({ run_id: "run-7a2b1c3d" });
```

---

### `POST /api/v1/agent/workflows/runs/{run_id}/cancel`

Cancel an in-flight workflow run by run id.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `run_id` | path | string | Yes | Run id (path identifier). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope (§6.1): the `.hoody` project layer / record cwd / tool+workflow cwd. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override (§6.1) selecting which on-disk `.hoody` install a stateless read/write resolves (HoodyPaths). |
| `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 (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |
| `realm` | query | string | No | Per-request realm selector (§6.1) — the in:query alias of the `X-Hoody-Realm` header (read only when the header is absent): `"global"` or a 24-hex id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. |

### Response




```json
{
  "cancelled": true,
  "run_id": "run-7a2b1c3d"
}
```




```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 (in-container loopback, the bridge, the host, or a sibling container). The gateway has no service-level auth — authorization is owned by hoody-proxy, which is the only party that can reach it (TPROXY preserves the real public client IP). | Reach the agent through hoody-proxy (e.g. `hoody agent …` → platform → 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": "payload_too_large",
  "message": "request body exceeds the configured size limit"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). The gateway rejects an oversized body at the edge (`http.MaxBytesReader`) before the handler reads it — a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |




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

```ts
await client.agent.workflows.cancelWorkflowRun({ run_id: "run-7a2b1c3d" });
```