Skip to content

Checkpoints

Checkpoints are thin ReAct-loop bookmarks stored under an agent’s session. They answer: where did this agent last successfully complete a loop boundary (after_model or after_tools)?

They do not store conversation messages (Session does), orchestrator position (Graph/Swarm session does), or human-in-the-loop pauses (Interrupts do).

Default model is Amazon Bedrock

Agent() with no model= uses Amazon Bedrock (Claude Sonnet 4.6 in us-west-2). Configure AWS credentials (aws configure or AWS_* env vars), or pass an explicit model — see Installation and Model Providers.

Session vs Checkpoint

FeaturePersistsResume unit
SessionMessages + agent stateConversation continuity
CheckpointLoop boundary bookmarkMid-tool-cycle continue
Graph/Swarm sessionOrchestrator cursor + node resultsWhich node runs next
InterruptHITL pause stateHuman response path

Rules to remember:

  1. Checkpoint requires a session_manager — otherwise Agent construction raises ValueError.
  2. There is no public resume() API — rebuild the Agent with the same session_id + agent_id and invoke again.
  3. Pass CheckpointConfig on AgentConfig — do not pass a Checkpoint instance.
  4. Side-effecting tools may retry after a crash — keep them idempotent.

Quickstart

python
from elsai import Agent, tool
from elsai.agent import AgentConfig
from elsai.checkpoint import CheckpointConfig
from elsai.session import FileSessionManager

SESSION_ID = "my-job"   # persist with your chat/job id — do not rotate every turn
AGENT_ID = "worker"

@tool
def get_weather(city: str) -> str:
    """Return weather for a city."""
    return f"Sunny, 72F in {city}"

agent = Agent(
    tools=[get_weather],
    config=AgentConfig(
        agent_id=AGENT_ID,
        session_manager=FileSessionManager(
            session_id=SESSION_ID,
            storage_dir="./sessions",
        ),
        checkpoint=CheckpointConfig(position="both"),
    ),
)

result = agent("What's the weather in Paris? Use get_weather.")
print(result.resumed_from_checkpoint, result.resume_position)

# After a crash / process restart: rebuild with the same ids
agent = Agent(
    tools=[get_weather],
    config=AgentConfig(
        agent_id=AGENT_ID,
        session_manager=FileSessionManager(
            session_id=SESSION_ID,
            storage_dir="./sessions",
        ),
        checkpoint=CheckpointConfig(position="both"),
    ),
)
result = agent("continue")
print(result.resumed_from_checkpoint, result.resume_position)

How it works

Crash after after_model (tools not finished):

Invariant: at each boundary, messages are written first; the checkpoint bookmark is written last.

Configure save boundaries

python
from elsai.checkpoint import CheckpointConfig

CheckpointConfig(position="after_tools")  # default
CheckpointConfig(position="after_model")
CheckpointConfig(position="both")         # recommended for tool-heavy agents
positionWhen it savesTypical use
"after_tools" (default)After tool results are durableContinue after tools complete
"after_model"After model tool_use is durable (tools may still be running)Recover mid-tool crashes
"both"Both boundariesProduction tool agents

The on-disk bookmark always has a single position ("after_model" or "after_tools"). "both" exists only on config.

Storage layout

Checkpoint state is one JSON file per agent (checkpoint.json), stored next to that agent’s messages. It is a thin bookmark — it does not embed conversation messages or tool payloads (those stay under messages/).

Under FileSessionManager / S3SessionManager:

text
session_<session_id>/
  session.json
  agents/
    agent_<agent_id>/
      agent.json
      messages/
        message_0.json
        ...
      checkpoint.json          # single overwrite file

Example checkpoint.json (active mid-tool bookmark):

json
{
  "position": "after_model",
  "cycle_index": 0,
  "schema_version": "1.0",
  "created_at": "2026-08-18T06:00:00Z",
  "message_count": 2,
  "agent_id": "worker",
  "execution_status": "active"
}
FieldMeaning
positionBoundary just completed: "after_model" or "after_tools"
cycle_index0-based ReAct cycle at save
schema_versionMust match the SDK ("1.0")
created_atISO-8601 UTC timestamp
message_countlen(messages) at save — used by the resume gate
agent_idAgent that wrote the bookmark
execution_status"active" while work may be unfinished; "completed" after a clean end_turn
BehaviourDetail
OverwriteEach save replaces the entire checkpoint.json
Missing fileNo bookmark — normal turn
Corrupt / bad schemaRaises CheckpointException
Clean end_turnSame file overwritten with execution_status="completed" (not deleted)

Resume semantics

Auto-resume runs only when the loaded bookmark is active and passes consistency checks (message count and pending-tool shape match).

BookmarkWhat happens on next invoke
after_model (active, allowed)New user text is queued; pending tools run first; then the queued text is applied
after_tools (active, allowed)New user text appends normally; the loop continues
completed or gate denyMessages-only continue (safe fallback; warning logged on deny)

AgentResult fields reflect action, not mere load:

python
result = agent("continue")
print(result.resumed_from_checkpoint)  # True only if this invoke acted on a bookmark
print(result.resume_position)          # "after_model" | "after_tools" | None

Crash recovery recipe

Rebuild the Agent with the same session_id and agent_id:

python
from elsai import Agent, tool
from elsai.agent import AgentConfig
from elsai.checkpoint import CheckpointConfig
from elsai.session import FileSessionManager

SESSION_ID = "order-42"
AGENT_ID = "fulfillment"

@tool
def process_order(order_id: str) -> str:
    """Process an order by id."""
    return f"Processed {order_id}"

agent = Agent(
    tools=[process_order],
    config=AgentConfig(
        agent_id=AGENT_ID,
        session_manager=FileSessionManager(
            session_id=SESSION_ID,
            storage_dir="./sessions",
        ),
        checkpoint=CheckpointConfig(position="both"),
    ),
)
result = agent("Process this order")

# After process death: same ids → auto-resume if bookmark is active
agent = Agent(
    tools=[process_order],
    config=AgentConfig(
        agent_id=AGENT_ID,
        session_manager=FileSessionManager(
            session_id=SESSION_ID,
            storage_dir="./sessions",
        ),
        checkpoint=CheckpointConfig(position="both"),
    ),
)
result = agent("continue")

Do not rotate session_id every turn. Persist it with your chat or job id.

With Graph and Swarm

Orchestrator session and node checkpoint are complementary:

  • Graph/Swarm session_manager → which node runs next (multi_agent.json)
  • Node session_manager + CheckpointConfig → mid-loop tool resume (checkpoint.json)

Use one durable job session_id and a unique agent_id per node:

python
from elsai import Agent, tool
from elsai.agent import AgentConfig
from elsai.checkpoint import CheckpointConfig
from elsai.multiagent import GraphBuilder
from elsai.session import FileSessionManager

JOB_ID = "job-9"
STORAGE = "./sessions"

@tool
def get_weather(city: str) -> str:
    """Return weather for a city."""
    return f"Sunny, 72F in {city}"

researcher = Agent(
    tools=[get_weather],
    system_prompt="Use get_weather when asked about weather. Keep answers short.",
    config=AgentConfig(
        agent_id="researcher",
        session_manager=FileSessionManager(session_id=JOB_ID, storage_dir=STORAGE),
        checkpoint=CheckpointConfig(position="both"),
    ),
)
writer = Agent(
    system_prompt="Turn the upstream research note into one short sentence.",
    config=AgentConfig(
        agent_id="writer",
        session_manager=FileSessionManager(session_id=JOB_ID, storage_dir=STORAGE),
        checkpoint=CheckpointConfig(position="both"),
    ),
)

builder = GraphBuilder()
builder.add_node(researcher, "researcher")
builder.add_node(writer, "writer")
builder.add_edge("researcher", "writer")
builder.set_entry_point("researcher")
# Orchestrator cursor (which node next) — keep this in addition to node CP
builder.set_session_manager(FileSessionManager(session_id=JOB_ID, storage_dir=STORAGE))
graph = builder.build()

result = graph("What's the weather in Paris? Use get_weather, then summarize.")

The same dual-layer pattern applies to Swarm: pass session_manager= on the Swarm and configure each node Agent with session + checkpoint.

Tool retries

Checkpoint recovery can retry a tool that was in flight at process death. Keep side-effecting tools idempotent or provide your own exactly-once protocol.

Interactions with other features

FeatureHow Checkpoint fits
ToolsPrimary value of checkpoints; text-only turns may leave no bookmark or only a finalized one
StreamingSame semantics inside stream_async; the final event’s AgentResult carries resume flags
Structured outputInternal SO tool cycles can leave an active bookmark briefly; next invoke may auto-resume or finalize
HooksOuter invoke hooks still fire on resume invokes
Conversation managersRebuild with the same CM config; bookmark message_count is against stored session messages
InterruptsOrthogonal HITL pause — do not confuse with checkpoint auto-resume; both can coexist
Agent as toolPer-agent session + CP; agents passed as tools need as_tool(preserve_context=True) (not mode="spawn")

Errors and troubleshooting

SituationBehaviour
checkpoint without session_managerValueError at Agent init
Wrong type for checkpoint= (e.g. a Checkpoint instance)TypeError
Checkpoint write fails mid-loopException propagates (fail loud)
Corrupt file / schema mismatchCheckpointException
Consistency gate denyWarning log; messages-only continue
Finalize after end_turn failsWarning log; turn still succeeds

Best practices

  • Use stable session_id / agent_id values tied to your chat or job.
  • Prefer CheckpointConfig(position="both") for tool-heavy agents.
  • Keep tools idempotent when crash recovery matters.
  • Use FileSessionManager or S3SessionManager (repository backends that implement checkpoint I/O).
  • Check result.resumed_from_checkpoint when you need to know if a half-cycle was recovered.

Avoid:

  • Expecting a public resume() method
  • Rotating session_id every turn
  • Relying on Graph/Swarm session alone to recover mid-tool loops
  • Importing from elsai.experimental.checkpoint

Copyright © 2026 elsai foundry.