Sessions and turns
Understand durable conversations and multi-turn agent work
A session is a durable conversation with a deployed agent. Each turn appends input and streamed runtime events to the session, allowing clients to resume a conversation without rebuilding its history locally. The events are listed on Session events.
A session keeps the deployment it was created on, chosen by the platform from the alias it was addressed to. Advancing Development or promoting to Production changes which deployment new sessions get; turns sent to an existing session keep running the code it started with, including its declared tools and memory resources. To run new code, start a new session. Memory documents are not pinned: they belong to the project and environment, so a new session bound to the same document reads what the old one saved.
Agent playground
Open a project in the dashboard and choose Agent playground. Select the agent and environment independently, then create a new session or resume a previous playground session.
Playground sessions stay in the playground list. Sessions started through an API appear under Sessions, where their durable turn history can be inspected.
See Playground and debugging for target selection and the debug inspector.
Finding sessions
Give a session labels when you create it, such as the ticket, repository or user it works for, and list sessions by project, environment, agent, deployment, status, creation or update time and label instead of keeping your own index of session ids. The list returns rows with the session’s status, its current turn activity and its latest result, and never the prompts or messages; open a session for its turns and events. The query, the row shape and the label bounds are on the API reference.
When one session stands for one record in your application, give it that
record’s id as its externalReference at creation. The reference is stored
as given, returned on the session and its lifecycle events, and is an exact
list filter: after a lost create response, retry with the same request key
first, then GET /sessions?externalReference=<ref> finds the session
however many unrelated sessions have been created since. Retrying the key
with a different reference is refused, so a record cannot be attached to
the wrong session. Bounds and semantics are on the API
reference.
The list is paged: at most 100 rows per request, 50 by default, newest
created first. Follow nextCursor with the same filters until it is
null; every session matching the filters appears exactly once, however
many were created in the same instant. The TypeScript
client does this for you with
sessions.iterate(filters).
Execution, labels and results
These describe different parts of a session:
| Field | What it answers |
|---|---|
status and activity | Is work queued, running, stopping or finished? |
labels | How does your application organize the session? A ticket ID or archived label is your convention; changing it does not stop work. |
result | What did the agent last report? Its data is validated against the result tool’s output schema; turnId and callId identify the report. |
question | Is the agent waiting on a person? The open question, or null. |
A report does not finish a turn, and each report replaces the whole result.
Later work can run, fail or be cancelled while an earlier result remains.
For a review queue, check current activity, the last settled turn’s status,
and whether result.turnId matches that turn before treating the report as
ready. Your application decides what the reported data means.
Questions
A turn can end by asking. When the agent calls the
ask tool, the turn completes with
outcome: "question" and the session’s question is set:
{
"question": {
"id": "<question-id>",
"text": "Plan first, or go ahead?",
"options": [{ "label": "Go", "value": "go" }, { "label": "Plan first", "value": "plan" }],
"askedAt": "2026-10-02T10:00:00.000Z"
}
}options is empty for a free-text question. question is null when
nothing is open, and it is independent of result: a session can hold a
result and an open question at the same time. A consumer that only knows
completed turns still reads an asking turn correctly, because its status is
completed.
Answer by sending a turn that names the question with answers:
await oc.sessions.turns.send(sessionId, {
input: "Go",
answers: session.question!.id,
idempotencyKey: "answer-1",
});The agent reads the reply as useInput().answer.
Naming a question that is not the open one is refused with
409 question_stale; read the session again and answer its current
question.
While a question is open, a turn sent without answers does not run on its
own. It is held, and delivered with the answer, in order, as steering, so
the turn that interprets the answer sees every constraint written in the
meantime. steering carries a bounded number of held inputs, up to 64 KiB;
the rest run as ordinary turns after the answer, in order. The API answers a
held input with 202 { status: "held", questionId } and no turnId; see
Turns. A turn already queued behind the asking turn
settles as cancelled with reason held and keeps its turn id. Held inputs
are in the event log as message.held, then
message.delivered or message.discarded.
One question is open at a time. The answering turn may ask again, which opens
a new question. A question closes without an answer with
question.closed { reason }:
reason | Cause | Held inputs |
|---|---|---|
stopped | The session was stopped from Linear | Discarded |
dismissed | Dismiss was called | Run as ordinary turns, in order |
ended | The session was ended | Not run |
undeliverable | The question could not be posted to Linear | Run as ordinary turns, in order |
Interrupting a turn does not close a question. In Linear, messages typed while the agent works usually arrive after the answer; see Ask before acting.
Durability and recovery
Closing the browser or redeploying your application does not discard work the API has accepted. Reopen the session and resume reading its durable event log. If a submission’s response was lost, retry with the same request key and body; see Starting work from an application.
Durable history does not make every command resumable. A lost computer can leave a command’s outcome unknown; OpenComputer does not automatically replay that command or roll back its effects. The turn can continue with a tool error or fail. Inspect the recorded outcome and verify external effects before asking the agent to retry.
Interrupt stops the current turn while keeping the session available for more work. End closes the session and revokes its memory writes. Neither operation undoes completed writes; their different acknowledgement and cleanup guarantees are described in the API reference.
CLI
Start a session against the development agent:
npm run sessionThe session command always uses the current project’s bound Development deployment. It does not select between local and remote runtimes.
Projects with multiple agents may select another member of the bound project. The environment remains Development:
npm run session -- --agent reviewer "Review this change"Add --verbose to stream non-message lifecycle, runtime, tool, egress, usage,
and turn events. Assistant message text continues to stream normally:
npm run session -- --verbose --agent reviewer "Review this change"Or manage the durable session lifecycle explicitly:
npm run session -- create "Draft a response"
npm run session -- list
npm run session -- inspect <session-id>
npm run session -- attach <session-id>
npm run session -- send <session-id> "Continue"
npm run session -- end <session-id>list shows the newest sessions first, one page at a time; narrow it with
exact filters and continue from the cursor the previous page printed. With
--json the page is printed as { sessions, nextCursor } for scripts:
opencomputer session list --status suspended --agent reviewer --limit 20
opencomputer session list --external-reference order-42 --json
opencomputer session list --limit 2 --cursor <next-cursor> --json--agent takes the agent’s id, --limit is 1 to 100, and --cursor
continues only with the filters that produced it.
Bind memory documents when creating a session; add
--create-document to create the ones that do not exist yet:
npm run session -- create --memory notes=workshop --create-document "Plan the workshop"Follow the durable event log directly. With --json, each event is one NDJSON
record suitable for a coding agent or log processor:
opencomputer sessions tail <session-id> --after 0 --json
opencomputer sessions tail <session-id> --after 42 --no-follow --jsonProject scripts resolve the opencomputer binary from the installed
@opencomputer/cli package. For a one-off invocation outside those scripts,
select the scoped package explicitly:
npx --package @opencomputer/cli opencomputer session "Draft a response"Use --keep on supported commands when the session should remain active after
the command exits.
Workspace files
Files an agent writes under /workspace (reports, captures, screenshots, tool
output) can be listed and downloaded with session files. Newly written files
appear gradually and may take a few moments to finish syncing. Refresh or list
the workspace again if a file has just been created.
Downloads never pass through the model or the session transcript: OpenComputer authorizes the exact versioned workspace object for a short-lived direct download through its file-delivery edge. The CLI streams that object to disk, checks its byte count, and computes SHA-256 locally before the file is saved.
# List the files in the session's /workspace
opencomputer session files <session-id>
opencomputer session files ls <session-id>
# Download one file (dest may be a file path or an existing directory)
opencomputer session files download <session-id> report/summary.md
opencomputer session files download <session-id> /workspace/captures/run.har ./evidence/
# Download the whole workspace, mirroring its layout
opencomputer session files download <session-id> --all ./workspace-copy
# Machine-readable output with size and locally computed sha256 per file
opencomputer session files download <session-id> --all ./workspace-copy --jsoncp is an alias for download. Workspace paths may be written relative to
/workspace or with the /workspace/ prefix; paths that leave the workspace
are refused before any request is made. Each download is written to a
temporary file and renamed into place only after the expected byte count is
received; SHA-256 is reported from the completed local file. An interrupted
transfer never leaves a partial file behind. The versioned S3-backed workspace
remains the source of the file in the
CLI, the session page’s Files tab, and the
playground debug inspector.
From an application
Applications create sessions with the management API: POST /api/managed-agents/sessions with an API key, then turns and the event log
under that session. The
TypeScript client wraps those
routes; its sessions.startOnDocument creates a memory document and a
session bound to it in one call. In the browser, the
useAgent hook attaches to a session your server created
and renders its messages.
Input sources
Inside an agent, useInput().source identifies how the current work arrived.
This lets the same agent distinguish direct work from delegated subagent work
without defining another session type.