# Agent: Headless

**Page:** api/agent/headless

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

---

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



## Agent: Headless

The Headless agent endpoint drives the full agent loop once over an ephemeral, gateway-owned session. It is a one-shot entry point that supports two output forms:

- **Async job (default, `format: text | json`)** — the gateway returns `202` with a `job_id`; the run executes after the ack and the outcome is delivered through the Jobs API (`GET /jobs/{id}` and `GET /jobs/{id}/result`).
- **SSE stream (`format: stream-json` or `stream: true`)** — the run is streamed over Server-Sent Events; the initial `200` ack is followed by `start → result/error → end` frames.


`headless.run` grants arbitrary code execution on the bind. Every confirm, plan, and question auto-approves, and `bash`/`exec` are not write-gated. The HTTP edge has no service-level auth — `hoody-proxy` is the authorization boundary, reached via kit network-position trust.



The initial HTTP exchange can only return pre-dispatch statuses (`400`, `403`, `413`, `429`, `503`, `500`). A daemon-side failure (e.g. `admin_unauthorized`, timeout) is delivered **after** the response: in async form it appears in `GET /jobs/{id}/result` as `{status: "failed", error}`; in streaming form it is the `error` SSE frame.


## `POST /api/v1/agent/headless/runs`

Create a headless one-shot agent run.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `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. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (§6.1; omitted = local). Rejected (`400`) on routes with no container dimension. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector (§6.1): `"global"` or a 24-hex id. Rejected (`400 realm_scope_unsupported`) on routes without a realm dimension. |
| `realm` | query | string | No | In-query alias of the `X-Hoody-Realm` header (read only when the header is absent). |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `prompt` | string | Yes | The prompt to drive the ephemeral session. |
| `workflow` | string | No | Optional workflow name to run instead of (or alongside) the prompt. |
| `model` | string | No | Optional model spec for the run. |
| `format` | string | No | Output rendering: `text` \| `json` \| `stream-json`. `stream-json` (or `stream: true`) streams the run over SSE; otherwise the run is an async job. |
| `stream` | boolean | No | Force SSE streaming (equivalent to `format: stream-json`). |
| `timeout_ms` | integer | No | Optional run timeout in milliseconds (clamped to the hard ceiling). |

### Request Example

```json
{
  "prompt": "Refactor src/api/handlers.ts to extract the validation logic into a separate module, and add unit tests for the new module.",
  "model": "claude-sonnet-4",
  "format": "json",
  "timeout_ms": 120000
}
```

### Responses



SSE stream of the headless run (`format: stream-json` / `stream: true`).

```
event: start
data: {"job_id": "run_01HZX7W8M3K5PBV6A2FQY9JT4C"}

event: result
data: {"status": "ok", "text": "Refactored 3 files; created src/api/validation.ts and tests/api/validation.test.ts."}

event: end
data: {}
```

This status has no error codes.


Run accepted as an async job (`format: text | json`). The run executes after this ack; its outcome — including any daemon-side failure (`admin_unauthorized`, `timeout`) — is delivered via `GET /jobs/{id}/result`, not as an HTTP status on this operation.

```json
{
  "job_id": "run_01HZX7W8M3K5PBV6A2FQY9JT4C"
}
```

This status has no error codes.



```json
{
  "code": "bad_request",
  "message": "prompt is required"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |



```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`. | Reach the agent through `hoody-proxy` (e.g. `hoody agent …` → platform → 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

```ts
const { job_id } = await client.agent.headless.createHeadlessRun({
  prompt: "Refactor src/api/handlers.ts to extract the validation logic into a separate module, and add unit tests for the new module.",
  model: "claude-sonnet-4",
  format: "json",
  timeout_ms: 120000
});

// Poll for the captured result
const result = await client.jobs.getResult({ id: job_id });
```

```ts
// Streaming form — returns an SSE stream
const stream = await client.agent.headless.createHeadlessRun({
  prompt: "Summarize the diff in src/api/handlers.ts.",
  format: "stream-json"
});

for await (const event of stream) {
  if (event.type === "result") console.log(event.data);
  if (event.type === "error") throw new Error(event.data.message);
}
```