Skip to content
AGH RuntimeSessions

Event Streaming

How AGH records session events, streams them over SSE, and persists them in per-session SQLite databases.

Audience
Operators running durable agent work
Focus
Sessions guidance shaped for scanability, day-two clarity, and operator context.

Events are the durable record of a session. They drive:

  • live session views in clients
  • replay and transcript reconstruction
  • turn history
  • token usage tracking
  • permission audit trails

AGH stores session events in a per-session SQLite database and exposes them through both query and SSE surfaces.

Two event surfaces

AGH exposes two different streaming models for session work:

SurfaceEndpointWhat you get
Persisted session streamGET /api/sessions/:id/streamStored SessionEventPayload rows with stable sequence numbers and SSE replay support
Prompt streamPOST /api/sessions/:id/promptRaw live prompt events in the AI SDK UI stream format (x-vercel-ai-ui-message-stream: v1)

Use the persisted session stream when you need auditability or reconnect support. Use the prompt stream when you are driving one interactive prompt call.

Outer persisted event envelope

Every stored event row is returned as SessionEventPayload:

{
  "id": "evt-000042",
  "session_id": "sess-1234",
  "sequence": 42,
  "turn_id": "turn-9abc",
  "type": "tool_result",
  "agent_name": "general",
  "workspace_id": "repo-alpha",
  "workspace_path": "/absolute/path/to/repo",
  "content": {
    "schema": "agh.session.event.v1",
    "type": "tool_result",
    "session_id": "acp-session-77",
    "turn_id": "turn-9abc",
    "timestamp": "2026-04-16T01:10:06Z",
    "tool_call_id": "tool-call-1",
    "tool_name": "Read",
    "tool_result": {
      "content": "package session"
    },
    "tool_error": false,
    "raw": {
      "status": "completed"
    }
  },
  "timestamp": "2026-04-16T01:10:06Z"
}

Two IDs matter here:

  • outer session_id: the stable AGH session ID
  • inner content.session_id: the ACP runtime session ID carried by the underlying event payload

Those IDs can diverge after a resume that falls back to a fresh ACP session.

Event type catalog

The persisted event type values currently emitted by AGH are:

TypeWhen it is recordedKey content fields
user_messageBefore AGH submits a prompt to ACPschema, type, session_id, turn_id, timestamp, text
agent_messageAgent text chunktext, plus the shared canonical fields
thoughtAgent reasoning chunktext, plus the shared canonical fields
tool_callTool call started or updated before completiontitle, tool_name, tool_call_id, optional tool_input, optional raw
tool_resultTool call completed or failedtool_call_id, tool_name, tool_result, tool_error, optional raw
planACP plan updateoptional raw; AGH stores the canonical envelope even when the payload is sparse
permissionACP permission request or resolved decisionrequest_id, action, resource, decision, title, tool_call_id, raw
usageToken or context usage updateusage with token counts, context counts, cost fields, and timestamp
runtime_progressLow-frequency long-running prompt progress updatetext, runtime activity payload
runtime_warningRuntime supervision warning or inactivity timeout noticetext, runtime activity payload, optional raw
systemAvailable-command updates, mode updates, or other non-chat ACP system updatestitle, optional raw
doneEnd of a prompt turnstop_reason, optional usage, optional raw
errorPrompt processing errorerror, optional raw
session_stoppedSession finalizationstop_reason, optional failure, optional error, optional text

Runtime supervision events

runtime_progress and runtime_warning are separate from agent_message. They are progress projections for clients and bridges, not assistant-authored text to append to the final answer.

AGH does not write heartbeat ticks into the event log. Heartbeats update session liveness metadata; only lower-frequency progress, warning, and timeout notices become stored events.

Example runtime_progress content:

{
  "schema": "agh.session.event.v1",
  "type": "runtime_progress",
  "turn_id": "turn-9abc",
  "timestamp": "2026-04-16T02:10:06Z",
  "text": "Still working... (10 min elapsed)",
  "runtime": {
    "turn_id": "turn-9abc",
    "turn_source": "user",
    "last_activity_at": "2026-04-16T02:10:06Z",
    "last_activity_kind": "agent_waiting",
    "last_activity_detail": "waiting for session/prompt response",
    "current_tool": "Bash",
    "tool_call_id": "tool-call-1",
    "last_progress_at": "2026-04-16T02:10:06Z",
    "idle_seconds": 0,
    "elapsed_seconds": 600
  }
}

runtime_warning is emitted once when session.supervision.inactivity_warning_after is crossed. If session.supervision.inactivity_timeout is crossed, AGH cancels the active prompt cooperatively. If the prompt does not finish within timeout_cancel_grace, AGH stops the session with stop reason timeout and stop detail activity_timeout.

Shared canonical fields inside content

The stored canonical envelope can include these fields when they apply:

FieldMeaning
schemaCurrently agh.session.event.v1
typeInner event type
session_idACP session ID from the runtime event
turn_idPrompt turn ID
request_idPermission request ID
timestampEvent timestamp
textUser text, assistant text, or thought text
titleTool or update title
tool_nameDerived tool name
tool_call_idStable tool correlation ID
tool_inputStructured tool input when present
tool_resultStructured tool output (stdout, stderr, file_path, content, structured_patch, error, raw_output)
tool_errorWhether AGH classified the tool result as failed
stop_reasonTurn or session termination reason
failureTyped lifecycle failure object with kind, redacted summary, and optional crash_bundle_path
actionPermission action name
resourcePermission resource target
decisionPermission decision
errorError text
usageToken and cost payload
runtimeLong-running prompt activity payload with current tool, last activity, progress, idle, and elapsed fields
rawRaw ACP update payload AGH preserved

Querying stored events

Read recent events from the CLI:

agh session events sess-1234 --last 20

Filter to one type:

agh session events sess-1234 --type tool_call

Group by turn:

agh session history sess-1234

The HTTP query surfaces accept these filters:

  • type
  • agent_name
  • turn_id
  • since
  • limit
  • after_sequence

For HTTP, since must be RFC3339 or RFC3339Nano. The CLI is more convenient: agh session events --since 5m converts the duration into an absolute UTC timestamp before calling the API.

Subscribing over SSE

Follow persisted events live:

agh session events sess-1234 --follow

Open the raw SSE stream directly:

curl -N http://localhost:2123/api/sessions/sess-1234/stream

The stream writes:

  • id: as the persisted event sequence number
  • event: as the persisted event type
  • data: as one SessionEventPayload

Example SSE frame:

id: 42
event: tool_result
data: {"id":"evt-000042","session_id":"sess-1234","sequence":42,"turn_id":"turn-9abc","type":"tool_result","agent_name":"general","content":{"schema":"agh.session.event.v1","type":"tool_result","tool_call_id":"tool-call-1","tool_result":{"content":"package session"}},"timestamp":"2026-04-16T01:10:06Z"}

Reconnect and backlog replay

Reconnect from a known sequence:

curl -N \
  -H "Last-Event-ID: 42" \
  http://localhost:2123/api/sessions/sess-1234/stream

Last-Event-ID must be a numeric persisted sequence. AGH first replays backlog rows after that sequence and then continues polling for new rows.

If the session is already stopped and no new stored rows arrive, AGH emits a terminal synthetic session_stopped SSE event and closes the stream. When the stopped session has failure diagnostics, that terminal payload includes the same failure object stored in session metadata.

Example terminal failure payload:

{
  "id": "session-stopped-sess-1234",
  "session_id": "sess-1234",
  "type": "session_stopped",
  "stop_reason": "agent_crashed",
  "failure": {
    "kind": "process_exit",
    "summary": "provider exited with status 1",
    "crash_bundle_path": "/Users/you/.agh/logs/crash-bundles/sess-1234-process_exit-1770000000000000000.json"
  },
  "timestamp": "2026-04-16T01:10:06Z"
}

SQLite persistence

Each session stores its durable history here:

~/.agh/sessions/<session-id>/events.db

The per-session database contains:

TablePurpose
eventsEvery persisted session event row
token_usageOne merged usage record per turn
hook_runsLifecycle hook execution history for that session

The events table stores:

  • id
  • sequence
  • turn_id
  • type
  • agent_name
  • content
  • timestamp

Queries are returned in ascending sequence order. If you request limit, AGH selects the newest matching rows first and then re-sorts them ascending before returning them.

Relationship to replay

The resume and replay path depends on this stored event log:

  • agh session history groups the stored rows by turn_id
  • GET /api/sessions/:id/transcript assembles canonical replay messages from these rows
  • resume validation requires events.db to exist and contain at least one event

Next steps

  • Use Session Lifecycle to understand how stop and resume interact with the state machine.
  • Use Permissions to understand the approval events you will see in this stream.

On this page