Agent: Loops
Section titled “Agent: Loops”Loops are recurring agent prompts scheduled on a session. Each loop fires its prompt on a dedicated lane (separate from the session’s command queue) at a fixed interval, until it stops on a predicate, hits max_runs, or is paused or stopped. Use the endpoints below to list, create, fire on demand, update, and delete loops on a session.
All loop routes live on the agent container host:
https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.comAll-session loop listing
Section titled “All-session loop listing”GET /api/v1/agent/loops
Section titled “GET /api/v1/agent/loops”List the recurring agent loops of every live session the caller may see, oldest first. The response is the kit list envelope {items, meta:{total}}, with optional page/limit pagination applied gateway-side. A loop is listed only when its session passes the same realm and owner check as GET /api/v1/agent/sessions/{id}/loops, so loops in another realm’s or another account’s session are absent, never marked. Loops exist only as long as their session, so a closed session’s loops are gone. Each item carries its session_id; manage a loop through the per-session /sessions/{id}/loops routes.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | 1-based page number for pagination. |
limit | query | integer | No | Maximum items per page (0 = no pagination). |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
realm | query | string | No | The realm to list: “global” (loops not tied to a realm), a 24-hex realm id, or “all” for every realm this login serves. Also accepted as the X-Hoody-Realm header, except “all”. Omitted, the agent’s current realm. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response
Section titled “Response”{ "items": [ { "created_at": "2026-01-12T14:22:05Z", "id": "loop_01HMZX7P5V3J8N9K4Q2WXABCDE", "interval_ms": 1800000, "last_run_at": "2026-01-13T09:00:00Z", "max_runs": 50, "next_in_ms": 1680000, "paused": false, "prompt": "Check the build and report anything newly broken.", "run_count": 7, "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "skip_count": 0, "state": "running" } ], "meta": { "total": 1 }}The envelope has two top-level fields:
items: array of loop rows (each forwarded daemon row keeps its own shape).meta: pagination metadata.totalis always present;pageandlimitappear only whenlimit > 0.
{ "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. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read — retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
SDK Usage
Section titled “SDK Usage”curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/loops?page=1&limit=20" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.loops.listIterator({ page: 1, limit: 20 })) { console.log(item);}Loop collection
Section titled “Loop collection”GET /api/v1/agent/sessions/{id}/loops
Section titled “GET /api/v1/agent/sessions/{id}/loops”List the recurring agent loops scheduled for a session. The response is the kit list envelope {items, meta:{total}}, with optional page/limit pagination applied gateway-side.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
page | query | integer | No | 1-based page number for pagination. |
limit | query | integer | No | Maximum items per page (0 = no pagination). |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Response
Section titled “Response”{ "items": [ { "created_at": "2026-01-12T14:22:05Z", "id": "loop_01HMZX7P5V3J8N9K4Q2WXABCDE", "interval_ms": 1800000, "last_run_at": "2026-01-13T09:00:00Z", "max_runs": 50, "next_in_ms": 1680000, "paused": false, "prompt": "Check the build and report anything newly broken.", "run_count": 7, "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "skip_count": 0, "state": "running" } ], "meta": { "total": 1 }}The envelope has two top-level fields:
items: array of loop rows (each forwarded daemon row keeps its own shape).meta: pagination metadata.totalis always present;pageandlimitappear only whenlimit > 0.
{ "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. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "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. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
SDK Usage
Section titled “SDK Usage”curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/loops?page=1&limit=20" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.sessions.listLoopsIterator('{id}', { page: 1, limit: 20 })) { console.log(item);}POST /api/v1/agent/sessions/{id}/loops
Section titled “POST /api/v1/agent/sessions/{id}/loops”Create a recurring agent loop on a session. The loop fires its run on a dedicated loop lane, separate from the session’s command queue.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Request Body
Section titled “Request Body”Loop definition. The session_id comes from the path.
| Name | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | The prompt fired each loop run. |
interval | string | Yes | Run interval as a duration token, for example "30m". Required with no default. The minimum accepted interval is 60 seconds; a shorter one is rejected rather than rounded up. |
max_runs | integer | No | Stop after this many runs. Default: 50. Omitted, 0, or negative applies the default ceiling of 50 runs; there are no unbounded loops. |
stop_when | object | No | Optional stop predicate, evaluated after each run. Omit the field entirely when there is no predicate: a string value is rejected, including the empty string. See the stop_when shape below. |
max_cost_usd | number | No | Cost ceiling (0 = unlimited). |
max_wall_ms | integer | No | Wall-clock ceiling in ms (0 = unlimited). |
expires_in | string | No | Optional expiration as a duration from now, in the same grammar as interval (for example "2h" or "3 days"). Omitted, "", "never", "none", or "off" (case-insensitive) means no expiration. An expired loop stops firing. Without fire_now an expiry shorter than one interval is rejected, because the loop would never fire; with fire_now it must be at least 2 seconds, so the immediate first run can happen. |
fire_now | boolean | No | Run the first fire on the next scheduler tick instead of one interval out; later fires keep the normal cadence. Default: false. |
stop_when shape
| Name | Type | Required | Description |
|---|---|---|---|
kind | string | Yes (inside stop_when) | Which kind of predicate this is. One of expression, toolCall, judgeRef, convergence. |
expr | string | No | For kind: expression: the expression to evaluate. |
tool | string | No | For kind: toolCall: the tool to call. |
check | string | No | For kind: toolCall: the check applied to the tool result. |
judge | string | No | For kind: judgeRef: the judge to consult. |
args | object | No | Arguments passed to the tool or judge. |
threshold | number | No | For kind: convergence: the convergence threshold. |
min_iters | integer | No | For kind: convergence: the minimum iterations before the predicate can stop the loop. |
allow_network | boolean | No | Whether the predicate may reach the network. |
Response
Section titled “Response”{ "loop": { "created_at": "2026-01-12T14:22:05Z", "id": "loop_01HMZX7P5V3J8N9K4Q2WXABCDE", "interval_ms": 1800000, "last_run_at": "2026-01-13T09:00:00Z", "max_runs": 50, "next_in_ms": 1680000, "paused": false, "prompt": "Check the build and report anything newly broken.", "run_count": 7, "session_id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "skip_count": 0, "state": "running" }, "status": "ok"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters (for example an interval under 60 seconds, or exceeding the 8 active loops ceiling). | 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. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "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. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
SDK Usage
Section titled “SDK Usage”curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/loops" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Check the build and report anything newly broken.", "interval": "30m", "max_runs": 50 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.loops.create('{id}', { prompt: 'Check the build and report anything newly broken.', interval: '30m', max_runs: 50});Single loop operations
Section titled “Single loop operations”POST /api/v1/agent/sessions/{id}/loops/{loopId}/run-now
Section titled “POST /api/v1/agent/sessions/{id}/loops/{loopId}/run-now”Fire a loop’s next run immediately, off the dedicated loop lane. A loop you paused fires once and stays paused. A loop the circuit breaker paused after repeated errors is reset and resumes its schedule, since run-now is its retry. An unknown loop id (or a loop in another realm) returns 404 not_found.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
loopId | path | string | Yes | The loop id. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
This endpoint reads no body fields; the session id and loop id come from the path.
Response
Section titled “Response”{ "status": "ok"}{ "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. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "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. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
SDK Usage
Section titled “SDK Usage”curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/loops/{loopId}/run-now" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.loops.startRun('{id}', '{loopId}');PATCH /api/v1/agent/sessions/{id}/loops/{loopId}
Section titled “PATCH /api/v1/agent/sessions/{id}/loops/{loopId}”Update a loop by body shape. Set exactly one intent per request: its run state via paused, its expiry via expires_in, or a budget revive via max_cost_usd/max_wall_ms. This endpoint does NOT change interval or prompt in v1.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
loopId | path | string | Yes | The loop id. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
Request Body
Section titled “Request Body”Exactly one update intent per request: run state, expiry, or budget.
| Name | Type | Required | Description |
|---|---|---|---|
paused | boolean | No | Run-state intent (true pauses, false resumes). |
expires_in | string | No | Expiry intent: a duration-from-now token, e.g. "2h"; "" or "never" clears it. |
max_cost_usd | number | No | Budget intent: the cost ceiling (0 = unlimited). |
max_wall_ms | integer | No | Budget intent: the wall-clock ceiling in ms, or a duration token; 0 = unlimited. |
Response
Section titled “Response”{ "status": "ok"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed, or it mixed more than one update intent (run state, expiry, or budget). | Send exactly one intent, or 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. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "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. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
SDK Usage
Section titled “SDK Usage”Pause a loop:
curl -X PATCH "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/loops/{loopId}" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "paused": true }'Set an expiry:
curl -X PATCH "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/loops/{loopId}" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "expires_in": "2h" }'Revive budgets (cost and wall):
curl -X PATCH "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/loops/{loopId}" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "max_cost_usd": 5, "max_wall_ms": 600000 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
// Pause a loop.await client.agent.loops.update('{id}', '{loopId}', { paused: true });
// Set an expiry.await client.agent.loops.update('{id}', '{loopId}', { expires_in: '2h' });
// Revive cost and wall budgets (one intent per request).await client.agent.loops.update('{id}', '{loopId}', { max_cost_usd: 5, max_wall_ms: 600000 });DELETE /api/v1/agent/sessions/{id}/loops/{loopId}
Section titled “DELETE /api/v1/agent/sessions/{id}/loops/{loopId}”Delete a recurring loop from a session.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
loopId | path | string | Yes | The loop id. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
realm | query | string | No | Per-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
The body is optional. Every _-prefixed key in the body is ignored, the request scope (cwd/config_dir) is added from the scope headers, and other keys are passed through verbatim. The session id and loop id come from the path.
Response
Section titled “Response”{ "status": "ok"}{ "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. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with hoody agent …. |
{ "code": "not_found", "message": "resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
{ "code": "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. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction “unknown”), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
SDK Usage
Section titled “SDK Usage”curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/loops/{loopId}" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.loops.delete('{id}', '{loopId}');