Agent: Logs
Section titled “Agent: Logs”Query, read, list, export, and stream the agent’s redacted log set. Every route on this page is active-only, meaning a realm header returns 400 realm_scope_unsupported. Local sources live in an in-daemon ring (maximum page size 500); platform sources (activity, events, proxy) are queried separately and can be exported. Entries are always redacted before they leave the daemon.
Querying logs
Section titled “Querying logs”GET /api/v1/agent/logs
Section titled “GET /api/v1/agent/logs”Queries the active log stream. Filters ride query params; only the parameters declared below reach the daemon, and any other query key is discarded before the call. Paginate by sequence cursor, not by page. ?since and ?until are time bounds (RFC3339 or a relative 1h/30m/7d), NOT cursors; a bare number is rejected 400. ?limit=N caps the page: omitted it is 200, and any explicit value is clamped to the ring maximum of 500 (limit=0 also means 500). Source accepts one local source, or exactly one platform source (activity/events/proxy); mixing them is rejected.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
source | query | string | No | Filter to a log source/facet (see client.agent.logs.listSources()). One local source, or exactly one platform source (activity/events/proxy); mixing them is rejected. |
level | query | string | No | Filter to a minimum log level. |
host | query | string | No | Filter to a host. |
since | query | string | No | Lower time bound: RFC3339, or a relative duration like 1h/30m/7d. This is NOT a cursor; a bare sequence number is rejected 400 (use since_seq). Unparseable values are rejected the same way. |
until | query | string | No | Upper time bound, same forms as since. Paging backwards by repeatedly lowering until works, but it is coarse (rows sharing a timestamp repeat); before_seq is the exact backwards cursor. |
since_seq | query | integer | No | Forward cursor: return only entries NEWER than this gateway seq. Take it from the previous reply’s latest_seq to poll incrementally without re-reading rows. A non-numeric value is rejected 400. |
before_seq | query | integer | No | Backward cursor: return only entries OLDER than this seq. Take it from the seq of the oldest entry this page returned, not from oldest_seq (which is the oldest sequence still retained in the ring and is usually far older than the page you just read). Paging back from oldest_seq jumps past every entry in between and returns an empty page. A non-numeric value is rejected 400. |
limit | query | integer | No | Caps this page. Omitted it is the daemon default 200; any explicit value is clamped to the ring maximum of 500, and limit=0 means 500 rather than 200. A non-numeric value is rejected 400. |
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 against. |
session_id | query | string | No | Only entries correlated with this session id (exact match on the entry’s session_id). |
run_id | query | string | No | Only entries correlated with this workflow/task run id (exact match on the entry’s run_id). |
Response
Section titled “Response”{ "entries": [ { "seq": 1042, "ts": "2026-01-15T10:32:18.123Z", "level": "info", "source": "daemon", "host": "host-a", "message": "tool completed", "attrs": { "tool": "bash", "duration_ms": 42 } }, { "seq": 1043, "ts": "2026-01-15T10:32:19.001Z", "level": "warn", "source": "daemon", "host": "host-a", "message": "retry", "attrs": { "attempt": 2 } } ], "latest_seq": 1043, "oldest_seq": 512}{ "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. |
{ "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": "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 samples
Section titled “Code samples”curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/logs?source=daemon&level=info&limit=100' \ -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.logs.list({ source: 'daemon', level: 'info', limit: 100 });GET /api/v1/agent/logs/entries/{ref}
Section titled “GET /api/v1/agent/logs/entries/{ref}”Reads one log entry by ref. Entries are always redacted. The route is active-only; a realm header is rejected.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
ref | path | string | Yes | The ref. |
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 against. |
Response
Section titled “Response”{ "seq": 1042, "ts": "2026-01-15T10:32:18.123Z", "level": "info", "source": "daemon", "host": "host-a", "message": "tool completed", "attrs": { "tool": "bash", "duration_ms": 42 }}{ "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. |
{ "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": "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 samples
Section titled “Code samples”curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/logs/entries/llm:20261008_093015_482113' \ -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.logs.get('llm:20261008_093015_482113');Log metadata
Section titled “Log metadata”GET /api/v1/agent/logs/sources
Section titled “GET /api/v1/agent/logs/sources”Lists the available log sources and facets. The route is active-only; a realm header is rejected.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
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 against. |
Response
Section titled “Response”{ "sources": [ "daemon", "session", "tool", "llm", "activity", "events", "proxy" ]}{ "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. |
{ "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": "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 samples
Section titled “Code samples”curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/logs/sources' \ -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.logs.listSources();GET /api/v1/agent/logs/stats
Section titled “GET /api/v1/agent/logs/stats”Returns log volume and level statistics. The route is active-only; a realm header is rejected.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
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 against. |
Response
Section titled “Response”{ "total": 12453, "by_level": { "debug": 0, "info": 10200, "warn": 1900, "error": 353 }, "by_source": { "daemon": 5400, "session": 2200, "tool": 1800, "llm": 1200, "activity": 1100, "events": 553, "proxy": 200 }}{ "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. |
{ "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": "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 samples
Section titled “Code samples”curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/logs/stats' \ -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.logs.getStats();Exporting logs
Section titled “Exporting logs”GET /api/v1/agent/logs/export
Section titled “GET /api/v1/agent/logs/export”Streams the filtered, always-redacted log set as a download (Content-Disposition: attachment). The media type follows ?format: jsonl (the default, one wire row per line) is served as application/x-ndjson, and txt (human-readable) as text/plain; charset=utf-8. Local sources export a point-in-time-consistent snapshot of the in-daemon ring (bounded at the seq observed when the export starts); source=activity|events|proxy exports one platform query page (default limit 2000) and marks the export partial when more remained. The route is active-only; a realm header returns 400 realm_scope_unsupported.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
source | query | string | No | Log source to export (see client.agent.logs.listSources(); one local source, one platform source, or omitted for all local sources). |
min_level | query | string | No | Minimum log level (debug/info/warn/error). |
comp | query | string | No | Component filter (daemon source). |
session_id | query | string | No | Session id filter. |
text | query | string | No | Case-insensitive substring filter over message+attrs. |
since | query | string | No | Lower time bound (RFC3339 or relative like 1h/7d). |
until | query | string | No | Upper time bound (RFC3339 or relative). |
event | query | string | No | Session lifecycle event filter (session source). |
tool | query | string | No | Tool name filter (tool source). |
model | query | string | No | Model filter (llm source). |
status | query | string | No | Tool outcome filter: ok/error/cancelled (tool source). |
method | query | string | No | HTTP method filter (activity source). |
min_status | query | integer | No | Minimum HTTP status (activity source). |
max_status | query | integer | No | Maximum HTTP status (activity source). |
errors_only | query | boolean | No | Only error rows (activity source; true/false). |
event_type | query | string | No | Event type filter (events source). |
resource_type | query | string | No | Resource type filter (events source). |
container | query | string | No | Container filter (proxy source; empty = all running realm containers). Maps to the daemon’s container_id filter. |
kind | query | string | No | Proxy row kind: request/response/event (proxy source). |
host | query | string | No | Proxy URL host filter (exact or dot-aligned suffix). |
since_seq | query | integer | No | Exclusive lower seq bound for incremental exports (local sources). |
limit | query | integer | No | Total row cap across the export (default: everything the snapshot matches; platform default 2000). |
format | query | string | No | Export format: jsonl (default) or txt. |
filename | query | string | No | Download filename override (reduced to a safe basename). |
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 against. |
Response
Section titled “Response”The export body, served as application/x-ndjson for the default jsonl form or text/plain; charset=utf-8 for ?format=txt. The default form below shows two redacted log rows followed by the in-band terminator:
{"seq":1042,"ts":"2026-01-15T10:32:18.123Z","level":"info","source":"daemon","host":"host-a","message":"tool completed","attrs":{"tool":"bash","duration_ms":42}}{"seq":1043,"ts":"2026-01-15T10:32:19.001Z","level":"warn","source":"daemon","host":"host-a","message":"retry","attrs":{"attempt":2}}{"_hoody_export":{"rows":2,"gap":0,"partial":false,"complete":true}}{ "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. |
{ "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": "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. |
Code samples
Section titled “Code samples”curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/logs/export?source=daemon&format=jsonl&since=1h' \ -H 'Authorization: Bearer <token>' \ -o logs.jsonlimport { 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.logs.export({ source: 'daemon', format: 'jsonl', since: '1h' });Streaming logs
Section titled “Streaming logs”GET /api/v1/agent/logs/stream
Section titled “GET /api/v1/agent/logs/stream”Tails the active log stream over Server-Sent Events. Pagination advances each round (since_seq is set to the previous reply’s next_seq), so no row is skipped under load. The tail uses the same seq/replay convention as the session stream: an entry event per redacted row carrying id: <seq> (so a reconnecting client resumes via the Last-Event-ID header, which overrides ?since_seq), a lagged event with code:replay_gap when the caller’s cursor fell behind the ring, periodic heartbeats, and end on disconnect. Filters ride query params (source, level, host, since_seq, …); ?limit caps each poll batch. Local sources only. The route is active-only; a realm header returns 400 realm_scope_unsupported. Entries are always redacted.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
source | query | string | No | Filter the tail to a log source/facet. |
level | query | string | No | Filter to a minimum log level. |
host | query | string | No | Filter to a host. |
since_seq | query | integer | No | Initial resume cursor (the Last-Event-ID header overrides it). A non-numeric value is rejected 400. |
limit | query | integer | No | Caps each poll batch. A non-numeric value is rejected 400. |
Last-Event-ID | header | string | No | SSE resume cursor: the gateway int64 seq to resume from. OVERRIDES the ?since_seq query param. Sent automatically by an SSE client on reconnect. |
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 against. |
session_id | query | string | No | Only entries correlated with this session id. |
run_id | query | string | No | Only entries correlated with this workflow/task run id. |
Response
Section titled “Response”SSE stream of the redacted log tail. The body is a text/event-stream feed: event: entry with an id: line carrying the row seq, periodic heartbeats (lines beginning with :), a lagged event when the caller’s cursor falls behind the ring, and an end event on disconnect.
event: entryid: 1042data: {"seq":1042,"ts":"2026-01-15T10:32:18.123Z","level":"info","source":"daemon","host":"host-a","message":"tool started","attrs":{"tool":"bash"}}
: heartbeat
event: entryid: 1043data: {"seq":1043,"ts":"2026-01-15T10:32:19.250Z","level":"info","source":"daemon","host":"host-a","message":"tool completed","attrs":{"tool":"bash","duration_ms":248}}
event: enddata: {}{ "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. |
{ "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": "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. |
Code samples
Section titled “Code samples”curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/logs/stream?source=daemon&since_seq=1042' \ -H 'Authorization: Bearer <token>' \ -H 'Accept: text/event-stream'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.logs.stream({ source: 'daemon', since_seq: 1042 });