Store¶
Learn: Save and resume. Guide: Survive crashes and restarts.
Store
¶
Bases: ABC
Keeps States outside the process. Used by Agent(store=...), which saves as the run goes, and directly
with store.save(state) and store.load(id).
A subclass implements write and read (and list, delete if it can), or their async
versions, or both. save calls write from one thread at a time for a given State, outside the State
lock. FileStore is the implementation that ships with alpineagents.
A State is saved in one store only. save compares stores with ==, so define __eq__ if two
objects can stand for the same storage.
A store that implements only the async methods works only with arun and the a* methods
(asave, aload); a sync store also works with arun, where its methods run on a worker thread.
Sync save called on the event loop thread of an async run raises TypeError. The saved format has a
version: load refuses a State saved by another version (alpineagents 0.4 States do not load in 0.5).
Raises:
| Type | Description |
|---|---|
TypeError
|
When creating an instance of a subclass that implements neither |
Example
class RedisStore(Store):
async def awrite(self, state_id, entries, info, *, create=False): ...
async def aread(self, state_id): ...
write
¶
write(state_id: str, entries: Sequence[dict[str, Any]], info: Mapping[str, Any], *, create: bool = False) -> None
Adds history entries and replaces the info dict of one State.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state_id
|
str
|
The State id. |
required |
entries
|
Sequence[dict[str, Any]]
|
New history entries in |
required |
info
|
Mapping[str, Any]
|
The new info dict: a small JSON dict with the |
required |
create
|
bool
|
The first write of a new State. If |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
LookupError
|
|
awrite
async
¶
awrite(state_id: str, entries: Sequence[dict[str, Any]], info: Mapping[str, Any], *, create: bool = False) -> None
The async version of write. By default it runs write on a worker thread.
read
¶
read(state_id: str) -> Record | None
Everything saved for one State, or None if the id does not exist.
aread
async
¶
aread(state_id: str) -> Record | None
The async version of read. By default it runs read on a worker thread.
adelete
async
¶
adelete(state_id: str) -> None
The async version of delete. By default it runs delete on a worker thread.
alist
async
¶
alist() -> list[StateInfo]
The async version of list. By default it runs list on a worker thread.
save
¶
save(state: State) -> None
Writes what this store does not have yet of state: its new history entries and its info.
The Agent calls it at its save points (Agent(store=...)). Call it yourself to save something else right
away, such as the person's next message in a chat loop, before the next run. The first save of a State
refuses an id the store already has, and a State is saved in one store only. Call it from one thread at a
time for a given State.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The State to save. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
The State is saved in another store, or it is new and this store already has a State with its id. |
TypeError
|
A value in the State cannot be saved as JSON, the store only works asynchronously
(use |
Example
state.add_message(Message.user(input("> ")))
store.save(state)
load
¶
load(state_id: str) -> State
Rebuilds a saved State, ready to continue with agent.run(state).
Every saved entry is replayed. A State saved in the middle of a turn is closed: tool calls whose results
were never saved get a result telling the model that they may or may not have run, and a request that was
waiting on the model is taken back. The first run warns with ResumeWarning if its Agent differs
from the one that saved the State.
Raises:
| Type | Description |
|---|---|
LookupError
|
No State with this id. |
ValueError
|
The saved State is damaged, or was saved by another version of alpineagents. |
Example
state = store.load("bug-hunt-1")
agent.run(state)
FileStore
¶
FileStore(path: str | PathLike[str])
Bases: Store
Saves each State in a folder of its own: {path}/{id}/log.jsonl (history, one entry per line) and
info.json (what list shows).
Files are readable only by their owner (history holds tool results and messages), and each write is flushed to
disk (fsync) before it returns. One process writes a State at a time: two processes running the same State
id are not supported. Two FileStore objects for the same folder are equal, so a State loaded with one can
be saved with the other. Images are saved as base64, so a run with many screenshots makes large files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str]
|
The folder to keep States in. Created on the first write. |
required |
Example
store = FileStore(".agent-runs")
agent = Agent(model="claude-sonnet-5", store=store)
agent.run(State(messages=[Message.user("Find the bug")], id="bug-hunt-1"))
write
¶
write(state_id: str, entries: Sequence[dict[str, Any]], info: Mapping[str, Any], *, create: bool = False) -> None
Appends the new entries to {state_id}/log.jsonl and replaces info.json, flushing both to disk.
With create=True the folder is built under a temporary name and renamed into place, so creating is
all or nothing and never overwrites another State.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
LookupError
|
|
read
¶
read(state_id: str) -> Record | None
The entries and info saved for state_id, or None if there is no such folder.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
list
¶
list() -> list[StateInfo]
Every saved State, most recently updated first. A folder without info.json is not listed: that
includes States saved by alpineagents 0.4 (load says why it cannot open them).
delete
¶
delete(state_id: str) -> None
Deletes the folder of state_id. The folder is renamed first, so a half-deleted State is never listed.
Does nothing if the id does not exist.
StateInfo
dataclass
¶
StateInfo(id: str, first_message: str | None, created_at: datetime | None, updated_at: datetime | None, turn: int, stopped: Stopped | None, finished: bool, parent_id: str | None = None)
One saved State, as Store.list shows it.
first_message
instance-attribute
¶
first_message: str | None
The text of the first user message, or None if the State has none (or it has no text).
created_at
instance-attribute
¶
created_at: datetime | None
When the first thing was recorded (UTC), or None for a State saved with no history.
updated_at
instance-attribute
¶
updated_at: datetime | None
When the last saved step was recorded (UTC), or None for a State saved with no history.
stopped
instance-attribute
¶
stopped: Stopped | None
Why its last run stopped (state.stopped: StoppedByUntil, StoppedByLimit, StoppedByFinish
or StoppedByPermission), or None if it ended with an exception or was still running.
finished
instance-attribute
¶
finished: bool
Whether finish() was called. A finished State cannot run again.
parent_id
class-attribute
instance-attribute
¶
parent_id: str | None = None
The id of the State that started this one as a subagent (state.parent_id), or None for a State
that no tool call started. Keep the None ones to list only the top-level runs.
Record
dataclass
¶
Record(entries: list[dict[str, Any]], info: dict[str, Any])