Resuming sessions¶
caw can resume a conversation later — in the same process, after a restart, or in a completely
different process. Grab a resume_handle (a string), store it anywhere (a database, a file, a
queue), and resume from it.
# Process 1: start, communicate, persist the handle.
agent = Agent(provider="claude_code")
session = agent.start_session()
session.send("My deploy target is staging-eu. Remember that.")
handle = session.resume_handle # store this string
session.end()
# Process 2 (later, after a restart): resume by handle.
agent = Agent(provider="claude_code")
session = agent.resume_session(handle)
print(session.send("Where am I deploying?").result) # -> "staging-eu"
session.end()
The handle is self-contained¶
The handle is a JSON string carrying the backend's own resume key, so resuming works even with
no data_dir — the underlying CLI still has the conversation:
resume_key is Claude's session id, Codex's thread_id, or opencode's session id — for
codex/opencode it differs from session_id. Resuming works across all three providers.
Treat the handle like a secret
The handle grants resume access to the conversation — it is not an opaque random id. Store it with the same care as a credential.
Send before reading the handle
The backend assigns its resume key on the first exchange, so send at least one message
before reading session.resume_handle. Reading it earlier raises.
data_dir is optional and additive¶
Whether you resume with or without the original data_dir changes only how much caw-side
history you get back — the backend conversation resumes either way:
without data_dir |
with the original data_dir |
|
|---|---|---|
| backend conversation | resumed | resumed |
| caw trajectory | starts empty | full history restored |
| new turns | not persisted | appended to the original session dir |
A bare session id is also accepted in place of a full handle, but only when data_dir is set
(the resume key is then read from disk).
Full example¶
examples/resume.py shows both
the with- and without-data_dir paths:
"""Resuming a session across processes.
Start a session, store its ``resume_handle`` (a string), then resume the
conversation later with a brand-new Agent — as if a new process picked it up.
The handle is self-contained, so resume works even without a ``data_dir``
(the backend CLI still has the conversation). With a ``data_dir`` you also get
the full trajectory restored and new turns appended. This example shows both.
"""
import os
os.environ["CAW_LOG"] = "full"
from caw import Agent
DATA_DIR = "caw_data"
def main():
# --- "Process 1": start a session and grab a handle to store somewhere. ---
agent = Agent(data_dir=DATA_DIR)
session = agent.start_session()
session.send("My favorite number is 42. Acknowledge it.")
handle = session.resume_handle
session.end()
print(f"\nStored resume_handle: {handle}\n")
# In a real app you'd persist `handle` (DB, file, queue) and exit here.
# --- "Process 2a": a fresh Agent WITH the same data_dir restores history. ---
agent2 = Agent(data_dir=DATA_DIR)
resumed = agent2.resume_session(handle)
turn = resumed.send("What is my favorite number?")
resumed.end()
print(f"\n[with data_dir] recalled: {turn.result!r}")
print(f"[with data_dir] turns in trajectory: {resumed.trajectory.num_turns}")
# --- "Process 2b": a fresh Agent with NO data_dir still resumes the chat. ---
agent3 = Agent(data_dir=None)
resumed2 = agent3.resume_session(handle)
turn2 = resumed2.send("And what is my favorite number again?")
resumed2.end()
print(f"\n[without data_dir] recalled: {turn2.result!r}")
# Trajectory starts empty here, so only this turn is recorded.
print(f"[without data_dir] turns in trajectory: {resumed2.trajectory.num_turns}")
if __name__ == "__main__":
main()