Skip to content
Changelog

Finished Claude and Codex runs replay from the database

October 12, 20261 min read

Agno's BaseExternalAgent takes store_events and events_to_skip, with the same names and defaults as Agent, and AgentOS switches storage on for every ClaudeAgent and CodexAgent it serves. A streamed run then keeps its start event, tool calls, retry warnings and final RunCompleted, RunError or RunCancelled event on the run row, which gives the AgentOS run view an event timeline.

Say a client watching a long Claude turn loses its connection, comes back after the event stream has dropped the run, and calls /agents/{agent_id}/runs/{run_id}/resume. AgentOS now reads the stored events from the database and replays them in order, starting after the last_event_index the client sends.

Outside AgentOS, set it on the agent yourself:

from agno.agents.claude import ClaudeAgent
from agno.db.sqlite import SqliteDb
 
agent = ClaudeAgent(
    id="claude",
    db=SqliteDb(db_file="tmp/claude.db"),
    store_events=True,  # events_to_skip defaults to [RunEvent.run_content]
)

Content deltas stay out by default because the run already holds the full answer. Runs made with stream=False store no events, as with Agent.

Replaying a run the stream has dropped

We streamed a background ClaudeAgent run through AgentOS with a stand-in claude_agent_sdk, since no Claude login was available; it made one Bash tool call and answered. Then we removed the run from the in-memory event stream, the step its retention period takes, and resumed from index 1 (real output):

live stream: ['0 RunStarted', '1 ToolCallStarted Bash', '2 ToolCallCompleted Bash', '3 RunContent', '4 RunCompleted']
event stream after retention: None
/resume last_event_index=1:
   {'event': 'replay', 'status': 'COMPLETED', 'total_events': 2, 'message': 'Run completed. Replaying stored events from database.'}
   2 ToolCallCompleted Bash
   4 RunCompleted

Each stored event keeps the event_index the stream gave it, so the client gets exactly what came after index 1, and RunCompleted arrives at its real position, 4. The content delta at index 3 was never stored. A run streamed inline through /runs, without background=true, carries no indexes and replays from position 0, as native agents do on that path.

Resuming a run submitted without streaming

A run submitted with stream=false never publishes events, so /resume has nothing to attach to. Earlier releases still answered "Run completed but no events stored.", even while the run was running. AgentOS now reports the run's real status and tells the client to poll. A background ClaudeAgent run, resumed while running and again after a cancel, gave this (real output, stand-in SDK):

/resume: {"event": "replay", "run_id": "46be43ca-d589-49a6-9338-d9a987806726", "status": "RUNNING", "total_events": 0, "message": "Run is RUNNING and was submitted without streaming, so it has no event stream to attach to. Poll the run endpoint for its result."}
/resume after cancel: {"event": "replay", "run_id": "46be43ca-d589-49a6-9338-d9a987806726", "status": "CANCELLED", "total_events": 0, "message": "Run CANCELLED with no stored events: a run submitted without streaming keeps only its final output."}

The agent, team and workflow /resume routes share this message. Submit with stream=true when a client needs live or replayed events.

Learn more about reconnecting to background runs in the documentation.

Frequently asked questions

Yes. AgentOS sets store_events=True on every external agent it serves, so each streamed run keeps its start event, tool call events, retry warnings and final event on the run row. Content deltas are left out by default because the run already holds the full answer.

Yes. Call POST /agents/{agent_id}/runs/{run_id}/resume with the session_id. When the event stream no longer holds the run, AgentOS reads the stored events from the database and sends them in order, skipping any at or below the last_event_index you pass.

A single replay frame with total_events: 0 and the run's current status. While the run is pending or running, the message tells you to poll the run endpoint, since a run submitted without streaming publishes no events.

Pass store_events=True to the agent. Use events_to_skip to choose which event types stay out of storage; it defaults to [RunEvent.run_content], the same default as Agent. Runs made with stream=False store no events.

Shipped around the same time