API Reference
HTTP and WebSocket API served by irrlichd on localhost:7837.
Base URL
- HTTP:
http://127.0.0.1:7837 - Unix socket:
~/.local/share/irrlicht/irrlichd.sock
Endpoints
GET /api/v1/sessions
Returns a hierarchical DashboardResponse with sessions grouped by project and nested parent-child relationships.
{
"groups": [
{
"name": "irrlicht",
"agents": [
{
"session_id": "52d087d1-...",
"state": "working",
"adapter": "claude-code",
"project_name": "irrlicht",
"pid": 12345,
"subagents": { "total": 3, "working": 2, "waiting": 0, "ready": 1, "error": 0 },
"children": [
{
"session_id": "agent-a1cad...",
"state": "working",
"parent_session_id": "52d087d1-...",
"metrics": { "model_name": "claude-haiku-4-5", ... }
}
],
"metrics": {
"model_name": "claude-opus-4-6",
"context_utilization_percentage": 12.5,
"pressure_level": "safe",
"estimated_cost_usd": 0.18
}
}
]
}
]
}
GET /state
Human-readable state summary. Returns sessionCount plus one count per lifecycle state — workingCount, waitingCount, readyCount, errorCount.
GET /api/v1/sessions/stream
WebSocket upgrade endpoint for real-time session state updates.
Message Types
| Type | Description |
|---|---|
session_created |
New session detected |
session_updated |
Session state or metrics changed |
session_deleted |
Session removed |
focus_requested |
A client asked to raise a session's terminal window (sent after POST /api/v1/sessions/{id}/focus); the macOS app acts on it |
history_snapshot_v2 |
One session's encoded 60-bucket state history across all three granularities; sent on connect, on session creation, and after a reconnect |
history_tick_v2 |
Bulk per-granularity delta: the wire code of the bucket that just rolled, one entry per session |
history_upgrade_v2 |
A state transition landed mid-bucket; clients merge it into the current bucket of all three rings |
permissions_updated |
Consent state changed; carries no payload — clients re-fetch GET /api/v1/permissions |
Message Format
{
"type": "session_updated",
"session": { ... SessionState ... }
}
GET /api/v1/version
Returns the running daemon's version. Used by the web dashboard's header chip and by client integrations (the upcoming VS Code panel, future relay-served clients) to gate behavior on daemon capabilities.
{
"version": "0.6.7"
}
GET /api/v1/agents
Returns the registered adapter branding so frontends can look up an adapter's display name and icon by name instead of hardcoding their own switches. Order mirrors allAgents in core/cmd/irrlichd/main.go; frontends should treat ordering as informational only and key by name.
[
{
"name": "claude-code",
"display_name": "Claude Code",
"icon_svg_light": "<svg ...>...</svg>",
"icon_svg_dark": "<svg ...>...</svg>"
},
{
"name": "opencode",
"display_name": "OpenCode",
"icon_svg_light": "<svg ...>...</svg>",
"icon_svg_dark": "<svg ...>...</svg>"
}
]
GET /api/v1/history
Powers the History cost-analytics surface (cumulative spend, attribution drill-downs, concurrent-agents timeline, productive-vs-reverted yield). Reconstructed from the daemon's on-disk cost rollups and lifecycle recordings; returns an empty-but-valid payload (zeroed series) when no data is available, so the dashboard always renders.
Query parameters (all optional):
| Param | Values | Description |
|---|---|---|
chart |
cost (default), tokens, co2, models, providers, agents, state, yield, dora, autonomy_projects, autonomy_duration |
Which series to build. models / providers are presets that pin the stacking axis; co2 sums the estimated CO2e footprint like cost sums spend; agents returns the concurrent-agents timeline; state returns the per-project, per-state activity matrix; yield returns the per-project productive-vs-reverted aggregate; autonomy_projects / autonomy_duration return the Autonomy section's per-project panels and aggregate percentile chart (windowed by window, below); dora returns one project's DORA metrics (requires project) |
group |
project (default), branch, provider, model, session, token_type |
Stacking dimension. token_type requires chart=tokens; agents/state are always grouped by project (recordings carry no other axis) |
range |
day, week, month, year, this-month |
Time window. Alternatively pass start & end (unix seconds). Not used by chart=state, which resolves its window from granularity instead |
granularity |
1m, 10m, 60m, 8h, 24h (default), 7d, 1mo, 6mo, 1y |
chart=state only. Picks both the bucket width and the trailing window at once (e.g. 1m shows the last 45 minutes, 1y shows the last 8 years) |
window |
30d (default), 1y |
chart=autonomy_projects / autonomy_duration only, which resolve their window from it instead of range: 30d is 30 daily buckets, 1y is 52 weekly buckets |
scope |
field:value |
Drilldown filter re-scoping the series to one contributor (field is one of project / branch / provider / model / session) |
project, provider, token_type |
comma-separated | Orthogonal cross-filters (the active group dimension is never filtered). token_type values: input / output / cache_read / cache_creation. For chart=dora, project instead names the single required repo (not a cross-filter) |
The cost / tokens / co2 / models / providers / agents charts return a time-series payload (range, chart, group, start, end, bucket_seconds, bucket_starts, total, series, top_contributors, with optional forecast, token_split, and scope). chart=agents swaps in a concurrency block. chart=state is shaped differently — a dense grid rather than a sparse series — returning projects (row order, busiest first) and by_state (one key per lifecycle state — working, waiting, ready, error — each a project → per-bucket array map aligned to bucket_starts; the key set is built from the daemon's own state vocabulary, so a future state appears here without a client change), plus the same concurrency block as agents (working+waiting combined). chart=yield returns a per-project shape (productive_cost, reverted_cost, unknown_cost, total_cost, yield, projects). chart=dora returns one project's four metrics (deployment_frequency, lead_time, change_failure_rate, mttr), each with value/unit/sample_size/available. Returns 400 for an unknown chart / group / token_type / granularity or an invalid range.
# Weekly cost by project
curl "http://127.0.0.1:7837/api/v1/history?chart=cost&group=project&range=week" | jq
# Activity Matrix: one row per project, one series per state, last 30 daily buckets
curl "http://127.0.0.1:7837/api/v1/history?chart=state&granularity=24h" | jq
GET /api/v1/permissions
Returns the consent-first permission snapshot: the permission mode (ask or grant-all), and for every agent its detection status plus each declared permission with its state (pending / granted / denied), title, what it touches, and the feature it unlocks. The macOS app and web dashboard drive the permission wizard from this endpoint.
Two fields report a permission whose consent stands but whose effect is not in force — the user said yes and the modification did not happen. effect_error on a permission carries the reason; the top-level unapplied_grants is the aggregate of every granted one, so a surface can show “N permissions are granted but not applied” without walking the tree. Both are omitted when nothing is wrong. An install that failed and a refusal because the agent’s CLI is below its required version are told apart by the reason text, not by a separate code.
curl http://127.0.0.1:7837/api/v1/permissions | jq
{
"mode": "ask",
"agents": [
{
"name": "claude-code",
"detected": true,
"permissions": [
{
"key": "hooks",
"state": "granted",
"title": "Install Claude Code hooks",
"effect_error": "settings.json is malformed: invalid character '}'"
}
]
}
],
"unapplied_grants": [
{
"agent": "claude-code",
"agent_display_name": "Claude Code",
"key": "hooks",
"title": "Install Claude Code hooks",
"reason": "settings.json is malformed: invalid character '}'"
}
]
}
POST /api/v1/permissions/answer
Submits wizard answers: {"answers": [{"agent": "claude-code", "permission": "hooks", "grant": true}]}. Grants are exercised immediately (hook install, watcher start); revokes actively undo (hook uninstall, watcher stop). Returns the updated snapshot. First answer wins when both surfaces have the wizard open — the daemon broadcasts permissions_updated so the other surface dismisses.
POST /api/v1/permissions/reload
Reloads permissions.json after another local process changes stored consent. The request has no body. The route is localhost-only, and it rejects cross-origin browser requests. The caller cannot supply a grant. The daemon only adopts the values in its own store and applies or removes their effects.
Responses: 200 with the updated permission snapshot · 403 for a cross-origin browser request · 500 when the store cannot be read. A read failure keeps the current in-memory state.
curl -X POST http://127.0.0.1:7837/api/v1/permissions/reload | jq
POST /api/v1/sessions/{id}/focus
Activates the host terminal or IDE window of the given session, bringing it to the foreground. This is what the irrlicht-focus CLI calls when you click a session row or notification. The session ID is taken from the path; the request has no body. The daemon broadcasts the focus request and the macOS app activates the launching window.
Responses:
| Status | Meaning |
|---|---|
200 |
Focus request broadcast (the Swift app activates the window) |
400 |
Missing session ID in the path |
404 |
Session not found |
422 |
Session has no captured launcher information to focus |
405 |
Method not allowed (only POST is accepted) |
# Focus a session's terminal/IDE window
curl -X POST http://127.0.0.1:7837/api/v1/sessions/52d087d1-.../focus
GET / POST / DELETE /api/v1/activation/task-eta
Legacy alias (issue #558) over the claude-code adapter’s instructions permission (issue #577), kept so the macOS Settings toggle’s wire shape stays stable. GET reads the state, POST grants, DELETE revokes, each replying {"task_eta_enabled": bool}. Loopback only; the mutating verbs reject cross-origin browser requests, since the granted effect rewrites ~/.claude/CLAUDE.md.
# Grant the task-eta instructions permission
curl -X POST http://127.0.0.1:7837/api/v1/activation/task-eta
# → {"task_eta_enabled":true}
GET /api/v1/relay/publish
Reports the daemon’s outbound relay-publish state. When publishing is off it returns {"enabled":false}; when on, it returns the live forwarder link state: {"enabled":true,"url":...,"state":...,"daemonId":...,"daemonLabel":...} where state is one of connecting / connected / auth_failed / disconnected. The macOS app polls this to render the “Publishing” status dot.
PUT /api/v1/relay/publish
Reconfigures publishing on the running daemon (issue #722): {"enabled":bool,"url":string,"token":string}. The daemon starts, stops, or reconfigures its outbound forwarder live — no relaunch, no interruption to session monitoring — and returns the resulting status (same shape as the GET). Idempotent, so the app can re-send the current config freely. Loopback only (127.0.0.1): it mutates forwarder config and carries the relay token in its body, the same trust boundary as the other daemon-control endpoints. A standalone/headless daemon instead seeds publishing once at startup from IRRLICHT_RELAY_URL / IRRLICHT_RELAY_TOKEN.
# Turn on publishing to a relay
curl -X PUT http://127.0.0.1:7837/api/v1/relay/publish \
-d '{"enabled":true,"url":"wss://relay.example.com","token":"..."}'
POST /api/v1/hooks/claudecode
Receiver for Claude Code hook events (the permission-request integration). Claude Code is configured to POST here on PermissionRequest, PreToolUse, PostToolUse, PostToolUseFailure, PreCompact, Stop, and Notification events; the daemon uses them to surface user-blocking state (permission gates, AskUserQuestion / ExitPlanMode overlays, and blocking Notification types such as idle_prompt), to force working during a manual /compact, and to take Stop as the authoritative turn-done signal. Consent-gated behind the hooks permission — while pending or denied the payload is dropped with 200. The session is keyed off the transcript_path filename, not session_id.
Request body (only the fields the daemon reads; unknown fields are ignored):
| Field | Type | Description |
|---|---|---|
session_id |
string | Claude Code session ID (informational; routing keys off transcript_path) |
transcript_path |
string | Absolute path to the session's JSONL transcript (required — its filename stem is the session ID) |
hook_event_name |
string | PermissionRequest, PreToolUse, PostToolUse, PostToolUseFailure, PreCompact, Stop, or Notification |
tool_name |
string | Tool involved in the event (e.g. AskUserQuestion, ExitPlanMode) |
tool_use_id |
string | ID of the tool call, when present |
permission_mode |
string | Current permission mode, when present |
is_interrupt |
bool | Whether the event is an interrupt |
tool_input |
object | Tool call input; scanned on PreToolUse for an in-band task-estimate marker |
trigger |
string | manual or auto on PreCompact events (the compaction cause); empty otherwise |
last_assistant_message |
string | The turn's final assistant message, on Stop events |
notification_type |
string | Discriminator on Notification events; the blocking types (e.g. idle_prompt) drive state, every other type is accepted and ignored |
Returns 200 with an empty body for recognized and ignored events (an empty PermissionRequest response means Claude Code shows its normal prompt). Returns 400 on invalid JSON or a missing transcript_path, and 405 for non-POST methods.
# Claude Code hook event (PermissionRequest)
curl -X POST http://127.0.0.1:7837/api/v1/hooks/claudecode \
-H 'Content-Type: application/json' \
-d '{
"session_id": "52d087d1-...",
"transcript_path": "/Users/me/.claude/projects/proj/52d087d1-....jsonl",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash"
}'
POST /api/v1/hooks/claudecode/statusline
Receiver for Claude Code's per-tick statusline JSON. Claude Code is configured (via settings.json's statusLine.command) to pipe its statusline payload to a curl that POSTs here. The handler extracts the rate_limits block — subscription quota data available only to Claude.ai Pro/Max accounts — and routes it to the matching session's metrics. API-key, Bedrock, and Vertex users send no rate_limits block; the tick is acknowledged and nothing is recorded. Consent-gated behind the statusline permission — while pending or denied the payload is dropped with 200.
Request body:
| Field | Type | Description |
|---|---|---|
session_id |
string | Claude Code session ID (informational; routing keys off transcript_path) |
transcript_path |
string | Absolute path to the session's JSONL transcript (required) |
rate_limits |
object | Optional. Subscription quota, with five_hour and seven_day windows (each {"used_percentage": float, "resets_at": unix-seconds}) |
Returns 200 with an empty body on success (and when there is no rate_limits block to record). Returns 400 on invalid JSON or a missing transcript_path, and 405 for non-POST methods.
# Claude Code statusline tick (Pro/Max account)
curl -X POST http://127.0.0.1:7837/api/v1/hooks/claudecode/statusline \
-H 'Content-Type: application/json' \
-d '{
"session_id": "52d087d1-...",
"transcript_path": "/Users/me/.claude/projects/proj/52d087d1-....jsonl",
"rate_limits": {
"five_hour": { "used_percentage": 16, "resets_at": 1778761800 },
"seven_day": { "used_percentage": 14.0, "resets_at": 1779188400 }
}
}'
POST /api/v1/hooks/{adapter} (other agents)
Every other hook-capable adapter has its own receiver, one route per adapter: /api/v1/hooks/codex, /api/v1/hooks/copilot, /api/v1/hooks/gemini-cli, /api/v1/hooks/mistral-vibe, /api/v1/hooks/kiro-cli, /api/v1/hooks/pi, /api/v1/hooks/opencode, /api/v1/hooks/hermes, and /api/v1/hooks/antigravity. The body is that agent’s own hook-event JSON, delivered by the hook entry, extension, or plugin the daemon installs for it — for agents that can only run a command, that entry is irrlichd hook-post <adapter>, which reads the payload on stdin and POSTs it to the running daemon’s address. The daemon uses the events to refine that adapter’s session state. Each route accepts POST only and is consent-gated behind the adapter’s hook permission: while that permission is pending or denied, payloads are dropped. These routes are local and unauthenticated; a transcript_path in a body is only opened when it lies under the adapter’s own watched root. See Adapters for what each agent installs.
Every receiver returns 200 with an empty body, including when the payload is dropped for missing consent or for a transcript_path outside the adapter’s root — refusals are logged, never reported by status code. It returns 400 for a body that is not valid JSON (and, on a receiver that confines a transcript_path, for one that has none), and 405 for any method other than POST.
# A Codex Stop hook event, as the installed hook entry delivers it
curl -X POST http://127.0.0.1:7837/api/v1/hooks/codex \
-H 'Content-Type: application/json' \
-d '{"transcript_path": "/Users/me/.codex/sessions/2026/10/11/rollout-2026-10-11T09-00-00-00000000-0000-7000-8000-000000000001.jsonl", "hook_event_name": "Stop"}'
GET /debug/bundle
Collects a redacted diagnostics snapshot for bug reports and returns it as a gzipped tar (Content-Disposition: attachment; filename="irrlicht-diag-<version>.tar.gz"). The bundle captures the daemon's resolved stores and config with sensitive values redacted. Loopback only. Headless installs that can't hit this route can produce the same bundle from the CLI with irrlichd --diagnose, which writes irrlicht-diag.tar.gz to the current directory without starting the daemon.
# Download a diagnostics bundle
curl -OJ http://127.0.0.1:7837/debug/bundle
GET /debug/pprof/
Go runtime profiling from the standard net/http/pprof handlers: /debug/pprof/ (index, including the named heap/goroutine/… profiles), /debug/pprof/cmdline, /debug/pprof/profile (CPU; ?seconds=N), /debug/pprof/symbol, and /debug/pprof/trace. Loopback only.
# 10-second CPU profile of the running daemon
go tool pprof 'http://127.0.0.1:7837/debug/pprof/profile?seconds=10'
GET /
Serves the web dashboard UI from disk. resolveUIDir in core/cmd/irrlichd/paths.go uses the first of these that contains an index.html: $IRRLICHT_UI_DIR, the production .app bundle (<exe>/../Resources/web/), the curl --daemon-only install (~/.local/share/irrlicht/web/, or $IRRLICHT_HOME/web/ when that is set), then a walk up from the executable to the enclosing repo root for platforms/web/index.html (a dev checkout). See Configuration.
Relay endpoints
The standalone relay (irrlichtrelay, default 127.0.0.1:7839) re-serves GET /api/v1/sessions, /api/v1/agents, /api/v1/version, and the WebSocket stream from its cache, and adds its own routes: phone pairing and Web Push (/api/v1/push/*, /pair/{code}/) and desktop enrollment (POST /api/v1/enroll/redeem, POST /api/v1/enroll/requests, GET /enroll/{code}). Those are documented in docs/relay-protocol.md.
SessionState Schema
| Field | Type | Description |
|---|---|---|
version |
int | Session record format version; 1 today |
session_id |
string | Unique session identifier: the agent’s own session ID (usually a UUID; Claude Code subagents use agent-<id>, and adapters with a constant transcript filename derive it from a directory name), or proc-<pid> for a process seen before its transcript |
state |
string | working, waiting, ready, or error |
adapter |
string | One of claude-code, codex, pi, aider, opencode, kiro-cli, gemini-cli, antigravity, mistral-vibe, copilot, hermes, muse, junie, dsh, or empty (legacy / unknown) |
model |
string | Legacy session-level model; the current daemon does not set it — read metrics.model_name |
cwd |
string | Working directory of the session |
transcript_path |
string | Absolute source path. SQLite-backed adapters append ?session=<id> to a database or write-ahead-log path |
git_branch |
string | Current git branch name |
project_name |
string | Project name derived from git root |
head_commit |
string | Full SHA of the working directory’s HEAD, captured when the session transitions to ready; empty outside a git repo |
yield_state |
string | Whether the session’s work survived: productive (shipped a commit that has not been reverted), reverted (its HEAD commit was later git reverted), or unknown (not git-tracked, or not yet evaluated) |
pid |
int | Process ID of the agent |
launcher |
object | The terminal or IDE that launched the agent process, captured when the PID is first assigned — e.g. term_program, tty, tmux_pane, iterm_session_id, vscode_pid, the kitty_* and herdr_* fields, host_bundle_id. Absent when nothing could be captured |
route |
object | Provider-endpoint evidence observed from the process environment when the PID is first assigned: status (observed, absent, unreadable, denied or malformed), plus, when observed, the redacted endpoint, the source variable and local. Evidence only, not a billing verdict |
background |
object | Present for a background agent (Claude Code Agent View): name (its label, may be empty) and detached (true when it has no controlling terminal). Absent for normal sessions |
first_seen |
int64 | Unix timestamp when the session was first detected |
updated_at |
int64 | Unix timestamp of the last state update |
confidence |
string | The detector’s confidence label for the record: medium when it is created from a new transcript, high once its transcript is removed |
event_count |
int | Events recorded on the session: 1 at creation, incremented each time its transcript grows |
last_event |
string | The last event recorded on it, e.g. transcript_new, transcript_activity, transcript_removed |
last_matcher |
string | Legacy field; the current daemon does not set it |
metrics |
object | SessionMetrics object (see below) |
parent_session_id |
string | ID of the parent session, if this is a sub-agent |
subagents |
object | SubagentSummary — aggregate state of all child sessions (see below) |
daemon_version |
string | Version of the daemon that created this session record |
last_transcript_size |
int64 | Transcript size in bytes when the daemon last checked it for growth |
waiting_start_time |
int64 | Unix timestamp at which the session last entered waiting; cleared when it returns to working |
autonomy_span_start |
int64 | Unix timestamp at which the current unbroken stretch of working began; absent when no span is open |
autonomy_span_pending_end |
int64 | Unix timestamp at which an open span provisionally ended by reaching ready, held back by a flicker grace in case working resumes |
autonomy_span_start_lower_bound |
bool | The session was already working when it was discovered, so the span began at or before autonomy_span_start |
SubagentSummary Schema
Present on parent sessions that have active or recently-active child sessions (subagents). Computed by BuildDashboard by merging in-process agents (open Agent tool calls) with file-based child sessions.
| Field | Type | Description |
|---|---|---|
total |
int | Total number of child sessions (in-process + file-based) |
working |
int | Count of children in working state |
waiting |
int | Count of children in waiting state |
ready |
int | Count of children in ready state |
error |
int | Count of children in error state |
The per-state counts sum to total: every child lands in exactly one bucket.
error was added in #1801 for that reason — before it, an errored child
was counted in total and in none of the buckets, so a red subagent was
invisible in the one badge a user checks to find out why a parent is stuck.
SessionMetrics Schema
| Field | Type | Description |
|---|---|---|
elapsed_seconds |
int | Seconds since session started |
total_tokens |
int64 | Total tokens consumed (input + output) |
model_name |
string | Normalized model name |
agent_version |
string | The agent CLI’s own version as its transcript reports it (e.g. Claude Code 2.1.143), distinct from the daemon’s version; absent for adapters whose transcript omits it |
context_window |
int64 | Total context window size for the model |
context_utilization_percentage |
float64 | Percentage of context window used |
pressure_level |
string | Context pressure level (safe, caution, warning, critical, or unknown when there are no tokens or model yet, or no context window is known for the model) |
context_window_unknown |
bool | No context window could be resolved for the model, so context_window stays zero; show tokens without a percentage rather than hiding the context display |
has_open_tool_call |
bool | Whether the agent has an open tool call |
open_tool_call_count |
int | Number of currently open tool calls |
open_subagents |
int | In-process child agents currently running, as the adapter counts them (merged with file-based child sessions in the subagent summary) |
pending_background_agent_count |
int | Claude Code’s own last-reported count of still-running background subagents; an extra signal ORed with child-session tracking, never a replacement for it |
background_process_count |
int | Agent-spawned background processes the transcript shows as still open (the agent’s claimed count, not the daemon’s liveness verdict) |
last_event_type |
string | Type of the most recent transcript event |
last_open_tool_names |
[]string | Names of currently open tools |
last_was_user_interrupt |
bool | Whether the most recent user event was an ESC cancellation |
last_was_tool_denial |
bool | Whether the most recent user event was a tool-permission denial; unlike an ESC cancellation it does not end the turn |
estimated_cost_usd |
float64 | Estimated session cost in USD |
estimated_co2_grams |
float64 | Estimated CO2e footprint in grams, from cumulative tokens and per-model energy coefficients — a model, never a measurement; read it together with co2_tier |
co2_tier |
string | Confidence tier behind estimated_co2_grams: provider_disclosed or fallback |
cum_input_tokens |
int64 | Cumulative input tokens across all API turns |
cum_output_tokens |
int64 | Cumulative output tokens across all API turns |
cum_cache_read_tokens |
int64 | Cumulative cache-read tokens across all API turns |
cum_cache_creation_tokens |
int64 | Cumulative cache-creation tokens across all API turns |
completed_turns |
int | Finished agent turns in this session (each turn ending in ready, or in waiting on a question) |
cache_bloat |
bool | The cache-creation regression detector found this session’s median cache creation per turn above the project’s baseline; both UIs show it as a “↑” badge |
cache_bloat_percent |
int | How far above the project baseline that median sits, as a rounded percentage (e.g. 340); zero when cache_bloat is false |
cache_bloat_tooltip |
string | Daemon-composed hover text for the badge, naming the regressing agent version when one can be attributed |
cache_bloat_explanation |
string | Longer plain-language hover text for the badge; empty when cache_bloat is false |
execution_confidence |
int | How decisive the agent sounds, 0–100 (100 = decisive), scored from hedging and uncertainty language in its recent assistant messages. A real 0 is sent; absent until the first assistant message with prose is scored, and for adapter paths that feed no full assistant text |
execution_confidence_low |
bool | The daemon’s verdict that execution_confidence is below 50 — the condition both UIs show their low-confidence chip on |
execution_confidence_tooltip |
string | Daemon-composed hover text for that chip; empty exactly when execution_confidence is absent |
last_assistant_text |
string | The most recent assistant message, truncated to about 200 characters; surfaces the question while a session is waiting |
task_summary |
string | One-line description of the current task, from the first user message; no UI renders it |
intent_headline |
string | Terse one-line version of task_summary; no UI renders it |
question_headline |
string | Terse pending-question headline shaped <topic>: <question>, rendered in the waiting block; last_assistant_text is its tooltip. Empty when there is no question source |
permission_mode |
string | The session’s permission mode as the transcript records it (e.g. auto, plan); passthrough only, never used to classify state |
transcript_permission_pending |
bool | The transcript itself shows a permission prompt still open (no hook involved); survives a daemon restart |
tasks |
[]object | The session’s task list from TaskCreate / TaskUpdate calls — each with id, subject, status (pending, in_progress, completed) and optional description, active_form, completed_at; absent for sessions that never created a task |
rate_limit |
object | Most recent subscription quota snapshot for this session, including windows (each with used_percent, window_minutes, resets_at), plan_type, credits, reached_type and sampled_at; absent when the provider surfaces no quota or none has arrived yet |
rate_limit_forecast_eta |
int64 | Projected unix-seconds time at which the most imminent rate-limit window reaches 100%; absent when no forecast is possible |
task_estimate |
object | The agent's self-reported task progress (total_rounds, completed_rounds, updated_at, source), parsed from its in-band progress marker or derived from the task list / subagents |
task_completion_eta |
int64 | Projected unix-seconds completion time for the current task, derived from the measured progress rate; absent when no progress has been reported |
session_error |
object | Detail of the failure behind a state of error — phase (retrying, terminal, or absent when the agent reported no classification), class (the agent’s own error token, e.g. rate_limit), message (its verbatim text), and the optional numbers http_status, attempt, max_attempts, retry_in_ms. Every number is omitted rather than zeroed when the agent did not report it, so read each for presence and never for falsiness. Absent while the session is healthy, and cleared when the failure retires — render off this object rather than off state, which moves first on recovery |
WebSocket Client Example
const ws = new WebSocket('ws://127.0.0.1:7837/api/v1/sessions/stream');
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(msg.type, msg.session?.session_id);
};
curl Examples
# List sessions
curl http://127.0.0.1:7837/api/v1/sessions | jq
# Quick status
curl http://127.0.0.1:7837/state
# Via unix socket
curl --unix-socket ~/.local/share/irrlicht/irrlichd.sock \
http://localhost/api/v1/sessions