opencomputer sessions tail --json prints, what useAgent reduces to messages,
what the dashboard’s session view shows, and what
GET /sessions/<id>/events returns.
Shape and ordering
Each event is one JSON object:seq is the cursor. A read with after=<seq> returns events with a greater
seq, in ascending order, up to 500 at a time; repeat from the last seq
you received until a page is empty, and keep polling from there to follow a
live session. Because the log is durable and seq only grows, a consumer
that stops can resume from its cursor without missing anything, and a page
that overlaps one already read is harmless: apply events whose seq is
greater than what you have applied. The React hook and the CLI work this
way.
Events at or below the cursor never change. New types can appear; treat an
unknown type as informational and keep reading.
Event types
Session lifecycle
When the session was created with an external
reference, every
session.* event’s
data also carries it as externalReference.
stopping follows an interrupt or an
interrupt-mode turn: the session stays there until the stopped turn’s
commands are confirmed stopped or its computer is terminated, then idle
follows and the next turn can start. See the API’s compatibility note for
sessions that cancel immediately instead.
session.ended is not a command-settlement or memory-revocation receipt.
The end response confirms memory revocation;
remote cleanup continues in the background.
Turns
For an interrupt that waits for commands,
settledAfterMs records the wait,
operationsSettled counts the settled commands, and computerTerminated
says whether stopping required terminating the computer. Cancellation with
reason: "session_ended" has no settlement fields: it records the session’s
decision to end, before remote cleanup finishes. Immediate-cancellation
sessions also omit these fields; see End and interrupt.
A turn that was queued behind a turn that asked a question is cancelled
with reason: "held" and the questionId: its input did not run on its own
and is delivered with the answer as steering.
Public failures
A failure’sdata is a stable code, a fixed message for that code, and
at most one parameter. The runtime’s own error text is never sent; a failure
no rule recognizes is agent_failed.
A model call that fails in flight may be retried inside the turn before
the turn fails. The runtime retries provider errors, rate limits,
incomplete response streams and transport failures that interrupt a
started response on its own schedule, per model call; the host adds one
retry for a transport failure or an invalid response the runtime would
not retry on its own. The agent’s own calls are covered; the runtime’s
auxiliary calls (compacting the conversation, titling it) are not
retried. Each retry appears in the log as a runtime.log milestone with
phase: "model_retry", the attempt and the failure’s class (for example
provider.transport), never the provider’s own text. A rejection is not
retried: credentials, quota, content policy and an invalid request are
model_rejected at once. A model_stream_failed message names its retry
only when one happened.
Questions
turn.completed with outcome: "question" is the turn’s terminal event, so a
consumer that knows only completed turns reads an asking turn correctly, and
outcome subscriptions deliver it as a
completed turn. The open question is also on the session as
question, where each closure reason is
explained. A held input is recorded once, however often its key is repeated,
so a client can rebuild held messages after a reload.
Messages
Tools
The exact fields come from the runtime’s tool record; read them as optional
and key a tool call on
callId when it is present. The memory tools
(memory_save, memory_read, memory_list) appear here like any other
tool; the save itself is reported separately.
input and output are JSON values, never JSON text: an object comes as an
object, and a consumer reads it without parsing. Tool events recorded before
these fields were introduced keep the shape they were recorded with, so the
React hook’s turns shows those calls unnamed and
without output; messages is unaffected.
A successful call of the agent’s result tool
is recorded with result: true; its output is the validated value, the
same JSON value GET /sessions/<id> returns as result.data. The session
writes this event when it commits the result, before the model sees the call
succeed, and there is one per call.
Every tool call ends in the log. The runtime reports a tool.completed or
tool.failed per call; when a turn ends with a call still open, whether
the turn completed, failed or was cancelled, the session records a
tool.failed for that call ahead of the terminal turn event, in the same
write, with settledBy naming the terminal event (turn.completed,
turn.failed or turn.cancelled) and a message saying the turn ended
first. A consumer therefore never sees a settled turn with a running call;
one that keys rows on callId needs no rule of its own for the turn’s end.
This settles the call in the log; it does not strengthen the command-stop
guarantees of the terminal turn event.
Memory
Delivery is best-effort. A save commits independently of the event: a
runtime that loses its connection can commit without ever reporting it, and
the same event can be reported once or twice after a reconnect. Treat the
event as a hint to re-read the document, and also re-read when attaching,
reconnecting and completing work. See
Document memory.
Model and usage
Outbound requests
Runtime
A runtime disconnect does not prove that a command stopped or that its
effects were undone. Follow the subsequent turn events to learn whether
work continued or failed; see Durability and recovery.
Agent renders
One turn can carry several renders, one per model step. The debug inspector
in the playground shows the same data.
Reading the log
From the CLI, with one NDJSON record per event:useAgent, which turns message.received,
message.delta and message.completed into messages, turn.* and
session.* into isRunning and ended, and memory.saved into
memorySaves; every event, these included, also reaches onEvent.