Persistence

Persistence lets a session survive process restarts and gives product surfaces a stable session ID.

A resumed session can repopulate task lists, execution history, artifacts, and delivery summaries without replaying the completed run.

A3S Code persists three related but different things:

ObjectWritten byResumed byPurpose
SessionSnapshotV1session.save() or autoSaveagent.resumeSession(id, options)Rehydrate one versioned generation containing the conversation, artifacts, traces, run records, verification reports, and subagent task snapshots.
Loop checkpointAgent loop while a run is in progresssession.resumeRun(runId)Continue an interrupted run from the last completed tool-round boundary. Completed in-process runs delete this checkpoint.
Workflow checkpointparallelResumable / workflow phasesparallelResumable(specs, workflowId)Skip already-completed orchestration steps after a process restart.

File Session Store

Rust
Node.js
Python
Go
Rust
use a3s_code_core::{Agent, SessionOptions};
let options = SessionOptions::new()
.with_session_id("release-review")
.with_file_session_store("./.a3s/sessions")
.with_auto_save(true);
let session = agent
.session_builder("/repo")
.options(options)
.build()
.await?;
session.send("Review release readiness", None).await?;
session.save().await?;

Go selects the built-in file store with FileSessionStoreDir; custom SessionStore trait implementations remain a Rust embedding capability.

Atomic Snapshot Generations

session.save() gathers the current persisted state into one SessionSnapshotV1 and calls SessionStore::save_snapshot once. The envelope contains:

  • schema_version and SessionData
  • tool artifacts
  • trace events and run records
  • verification reports
  • delegated subagent task snapshots

The file store writes one complete JSON envelope to a synced temporary file and atomically replaces <session-id>.json. Readers therefore observe the previous generation or the next one, not a new conversation paired with old run or trace fragments. The memory store publishes the same aggregate under one lock. Both report SessionStoreCapabilities { atomic_session_snapshots: true }.

Historical files remain readable. A bare SessionData file is combined with the legacy artifact/trace/run/verification/subagent fragment locations during load, then restored through the v1 in-memory shape. Once a new aggregate is saved, the single envelope is authoritative. A document that already looks like an aggregate but has a malformed or unsupported schema is rejected rather than reinterpreted as legacy data.

Custom stores must implement save_snapshot explicitly. Its default returns an error; it does not fan an aggregate out into independent writes or silently acknowledge a no-op. The default load_snapshot exists only as best-effort legacy assembly, and capabilities() lets a host distinguish that behavior from an atomic backend.

Resume

Rust
Node.js
Python
Go
Rust
use a3s_code_core::SessionOptions;
let resumed = agent
.resume_session_async(
"release-review",
SessionOptions::new().with_file_session_store("./.a3s/sessions"),
)
.await?;

resumeSession restores a saved session snapshot. It is not the same as resumeRun: use resumeSession when the user is continuing a saved conversation, and use resumeRun(runId) only when a run checkpoint exists for an interrupted run. Resume validates the snapshot schema before restoring any history or runtime evidence.

Memory And Sessions

Session persistence stores conversation and replay evidence. Memory stores reusable task facts. Use both when you want resumable workflows that also learn from repeated tasks.

Loop Checkpoints and Run Resumption

When a SessionStore is configured, the agent loop persists a LoopCheckpoint after each completed tool round. The boundary policy is strict: checkpoints are taken only between tool rounds, never mid-tool. If a process dies while a tool is executing, that round's work is lost on resume and the LLM re-deliberates from the previous checkpoint — re-running a non-idempotent tool (write, bash) on the wrong side of the boundary is worse than re-asking the LLM.

session.resumeRun(runId) (Node) / session.resume_run(run_id) (Python) / session.ResumeRun(ctx, runID) (Go) — core AgentSession::resume_run(checkpoint_run_id) — loads the latest checkpoint stored under that run ID and replays the loop from the last boundary. Because the checkpoint lives in the shared store, resume can happen on any node that shares it. Cumulative accounting continues rather than restarting at zero: total_usage and tool_calls_count carry forward from the checkpoint. A new run ID is allocated for the resumed work; the old-to-new relationship is host metadata, not interpreted by the framework.

Completed runs are not resumed through resumeRun; their final state is available through runs(), runEvents(runId), artifacts, verification reports, and session snapshots.

TypeScript
const result = await session.resumeRun('run-abc123');
console.log(result.totalTokens);
Python
result = session.resume_run('run-abc123')
print(result.total_tokens)
Go
result, err := session.ResumeRun(ctx, "run-abc123")
if err != nil {
return err
}
fmt.Println(result.Usage.TotalTokens)

Go also exposes workflow checkpoint orchestration through ParallelResumable(ctx, specs, workflowID).

resume_run rejects when the session has no sessionStore configured (or when no checkpoint exists for the given ID). SessionStore gains save_loop_checkpoint / load_loop_checkpoint / delete_loop_checkpoint; the file store's writes are crash-atomic. LoopCheckpoint::ensure_loadable() is called right after deserialization and rejects checkpoints from a future, incompatible schema version, so neither resume_run nor the live-run sink acts on an unreadable checkpoint.

See CHANGELOG [3.3.0] — "Loop checkpoints + run resumption" — and [3.4.0] "LoopCheckpoint::ensure_loadable()".

Workflow Checkpoints

WorkflowCheckpoint is the step-boundary analogue of the tool-round LoopCheckpoint, one level up: it journals completed orchestration steps so an interrupted workflow resumes from the longest completed prefix. Its fields are schema_version, workflow_id, steps, and checkpoint_ms, with the schema pinned by the WORKFLOW_CHECKPOINT_SCHEMA_VERSION constant. A resuming run skips the recorded steps and re-dispatches only the rest.

SessionStore gains save_workflow_checkpoint / load_workflow_checkpoint / delete_workflow_checkpoint (default no-ops; the file store writes crash-atomically). Loads from a future, incompatible schema version are rejected via WorkflowCheckpoint::ensure_loadable().

This pairs with the orchestration grammar — see Orchestration and Multi-Machine.

See CHANGELOG [3.4.0] — "WorkflowCheckpoint".

Resuming on a Different Node

Both checkpoint types are serializable. Combined with a shared SessionStore and a pluggable executor, this lets a host resume an interrupted run or workflow on a different node from the one that started it — the framework owns the serializable contract, the host owns placement and transport.

Operational Notes

Keep session stores out of public commits when they may contain prompts, tool output, or private file paths.