Chuyển tới nội dung chính

Sessions and human approval

Riverbeam records every invocation in a small hierarchy. The same records let an agent pause for a person to approve an action, then resume.

The execution hierarchy​

RecordCreated whenStatus values
SessionFirst request with a new session_idnone; last_active_at is updated after each run
InteractionEach request; reused when you pass its interaction_idin_progress, completed, failed
RunEach pass through the pipelinerunning, completed, failed, interrupted
MessageInput turn, agent reply, resume resultrole human, ai or tool; type message or action_result
ActionThe agent asks for approvalpending, resolved (cancelled exists but is never set)

Messages carry a per-session sequence number. Agents are identified by agent_id_for(name), a UUIDv5 derived from the agent name, so the same name always maps to the same id.

Sessions are not tied to a user

A session is created by the first request that uses its session_id, and any later request with that id continues it, whoever sends it. The user id stored on the session is not checked. If session ids can leak or be guessed, check in your API that the session belongs to the caller, for example with get_session(session_id) and its user_id.

Stores​

The BeamStore trait in beam_store persists the hierarchy. Two implementations ship:

StoreUse
InMemoryStoreThe default in RiverBeam; tests and single-process development
PgBeamStorePostgres, schema ref_beam
worker/src/main.rs
use std::sync::Arc;

use beam_store::PgBeamStore;

let store = PgBeamStore::connect(&database_url).await?; // creates the execution tables if missing
beam.set_store(Arc::new(store));

PgBeamStore::connect builds a connection pool and creates the tables it uses. Use PgBeamStore::new(pool) when you already have a pool and manage the schema yourself. The table layout is in Database schema.

You can read the records back through the trait: get_session, get_interaction, get_run, get_action and list_messages(session_id).

Human approval and resume​

An agent pauses by returning an AgentResult with interrupted: true and a list of action_required entries. The built-in RigAgentRunner does this before running a tool marked confirm_required, such as git_push.

1. The agent asks for approval​

The final done frame carries the request:

{
"agent_name": "author",
"interrupted": true,
"action_required": [
{
"action_id": "9c1b2d3e-4f50-4a61-8273-849506172839",
"kind": "confirmation",
"tool": "git_push",
"args": {"ref": "docs/update"}
}
],
"content": [{"type": "text", "text": "…"}]
}

Riverbeam saves one pending action per entry, closes the run as interrupted, and leaves the interaction open. Keep the interaction_id from the result.

2. The client resumes​

Send the resume command with the interaction and one ActionResponseEnvelope per action:

{
"agent_name": "author",
"session_id": "5f8915eb-31eb-4f8a-9517-e2c15ce7bc41",
"interaction_id": "40744652-2720-4b10-86ee-7333c1be8165",
"content": [],
"actions": [
{
"action_id": "9c1b2d3e-4f50-4a61-8273-849506172839",
"kind": "confirmation",
"request_id": "2b7c4d5e-6f70-4a81-9293-a4b5c6d7e8f9",
"status": "submitted",
"payload": {"tool": "git_push", "args": {"ref": "docs/update"}}
}
],
"auth_context": {"user_id": "u1"}
}

resume always streams: it returns a StreamAck and the frames arrive on the stream channel.

3. Riverbeam resolves the actions​

For each action, the pipeline checks it and marks it resolved, then saves an action_result message and logs agent_resumed:

CheckError
action_id is a UUIDBEM-063 (400)
The action existsBEM-027 (404)
The action is still pendingBEM-066 (400)

The agent then receives the actions in AgentInput.actions. RigAgentRunner runs the tool named in each response's payload.tool with payload.args, and returns the tool output as the result.

Actions are marked resolved before the agent runs. If the tool then fails, for example a push rejected with BEM-330, the action cannot be resumed again (BEM-066); ask the agent again in a new message.

Validate responses before you resume

RigAgentRunner runs the tool and arguments from the response payload, not the ones stored with the pending action, and it does not look at the response status. A cancelled response still runs the tool, and the action is stored as resolved. Until this is enforced in the library, check in your API layer that each response is submitted, and replace its payload with the stored tool and args. Tutorial step 5 shows a resume route that does this.

Current limitations​

  • Resume does not check that an action belongs to the interaction being resumed. BEM-064 and BEM-065 are reserved for these checks but not raised yet. BEM-062 (missing interaction_id) cannot occur through invoke, which fills in a missing id.
  • Checking and resolving an action are separate steps, so two resumes of one action at the same time can both run its tool. Allow one resume per action in your API.
  • PgBeamStore allocates message sequence numbers with a count query, so two concurrent writes to one session can get the same number. Its writes are not wrapped in transactions.
  • PgBeamStore stores user_id and profile_id only when they are UUIDs; other values are dropped.