Skip to main content
@opencomputer/react provides useAgent: a session’s conversation, turn activity and input controls. Your server creates sessions and checks access; the hook reads their event log and sends turns through your routes. The API key stays on the server.

Create the session on your server

Use the management client from trusted code. Address the agent by its environment alias, with one key per submission:
taskId belongs to one submission. The platform chooses the deployment active in the environment and records it on the session, so retry an uncertain create with the same parameters and key: it returns the same session even after a redeploy. Nothing in your application handles deployment ids; pinning one is an advanced option of sessions.create. Creating a session and sending its first turn are separate calls with separate keys.

Expose three routes

The hook needs these management API routes, relative to its basePath: Authenticate each request and check access to the session before forwarding it with your organization API key. For example, a Next.js route handler:
requireUser and userOwnsSession are your application’s access checks. The latter must enforce the application’s project, environment and agent scope as well as the user’s access. Forward JSON bodies and response statuses unchanged: the hook supplies the turn key in the body and reads the admission receipt or error envelope from the response.

Attach and submit

Keep each submission’s text and key until its receipt arrives. If the reply is lost, Retry sends the same submission; it must not generate a new key.
This example retains the submission for the life of the component. To retry across navigation or a reload, retain the same envelope there too. Include payload in it when sending structured input. An explicit rejection such as session_ended requires resolving that error; a new key starts new work and is not recovery of an uncertain request. Turns sent while another runs are queued. send resolves on admission, not when the agent finishes. It returns { sessionId, turnId, status, duplicate }:
  • A new turn is queued or running.
  • A repeated key returns the existing turn and its current status, including completed, failed or cancelled, with duplicate: true.
  • Keep both the text and payload unchanged when retrying a key.
The input appears in messages after admission. Its event-log record confirms that message instead of adding a second one.

Send options and errors

send rejects with a SendError. Rejection alone does not establish whether a turn was admitted: The hook also puts the message in error. A turn that fails after admission is separate: turn.failed sets the turn’s failure and the hook’s error.

Display activity and results

turns comes from the same durable event log as messages. Use its statuses to distinguish queued work from running and settled turns:
Reporting a result does not finish a turn. The session’s latest result may also belong to an earlier turn; see result provenance before treating a session as ready for review. A tool call is running, completed, failed or cancelled. Calls still running when their turn settles are settled too. The hook exposes failure status but does not currently retain tool.failed.message or settledBy on ToolCall; those details remain in the event log, also available through onEvent. Older tool records may lack a name or output. isRunning is an activity hint, not the session’s lifecycle status. It also includes this hook instance’s unsettled admission receipts. After reopening, a queued turn is visible in turns but does not by itself set isRunning. Use turns for queued/running counts and the session API for session status.

Answer a question

An agent can end a turn by asking a question. The hook exposes the open question as question, { id, text, options, turnId? }, or null, and answer(questionId, text, options?) sends the reply. The asking turn’s outcome is "question".
answer(id, text, options) is send(text, { ...options, answers: id }) and resolves with the same receipt. options is empty for a free-text question; answer it with the person’s text. question clears when the log records question.answered or question.closed for it. Answering a question that is no longer open rejects with a SendError whose code is question_stale; the hook’s question shows the current one. While a question is open, send without answers resolves with a held receipt, { status: "held", questionId } and no turnId: the message stays in messages, rebuilt from the log’s message.held events after a reload, and reaches the agent with the answer. dismiss(questionId) closes the question without an answer; held messages then run as ordinary turns.

Stop and reopen

stop() requests an interrupt without spending a model turn. It resolves once the request was accepted and rejects with a SendError when it was not, after setting error, so a control that showed a stopping state can be re-enabled and the request retried; a resolved promise is not confirmation that execution has stopped. Watch the running turn settle in turns. Stop affects that turn, so the next queued turn can start afterwards. The interrupt contract describes settlement and remote-command guarantees. The hook does not expose stopping or ended session status. Read the session through your server when your interface needs that lifecycle detail. It also never suspends, resumes or ends an attached session; those operations belong to the server that created it. Reopen by mounting the hook with the same sessionId. It reads history from after (default 0), including work completed while the page was closed, then polls for new events. A failed poll sets error, backs off and resumes from the same cursor; a successful recovery clears that polling error. Changing sessionId replays the new session. A send still in flight for the previous session settles for its caller without changing the new session’s view. Remount your composer for the new session, for example <Chat key={sessionId} sessionId={sessionId} />, so a retained submission cannot be retried into another session. after skips earlier events; it is not a snapshot. Start at 0 to reconstruct the conversation and turn activity. Use another cursor only when the earlier view is already retained elsewhere. See Sessions and turns for the distinction between reopening a conversation and recovering failed execution.

Bind memory

Choose memory bindings during server-side session creation; the browser cannot change them. To create a document and bind it in one helper call, see startOnDocument and its retry limits. For pinned session creation, use the explicit document and session calls. Pass onMemorySaved to refresh your document panel through an authenticated owner route:
memorySaves also contains those events, with resource, documentId, revision and bytes. Save events are best-effort; refresh on opening the panel too, because documents can change without an event in this session.

Local development

For a local application, pass agent-id@alias instead of a session ID:
The first send creates a session through the authenticated development bridge. Later calls resume that session; each call streams a turn and then suspends it. Unlike attach mode, it normally waits through the turn and suspension before resolving. Failures after admission appear through error; the returned receipt still describes admission, not successful completion. Created sessions have no memory bindings, and the hook does not recover their identity across a page reload. Use attach mode for an application users will reopen. Run the watched agent deployment and web application separately:
The first provides the development bridge at the default basePath; the second starts the web application. Credentials are not bundled into browser code.

Hook reference