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