Skip to content

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 write nor awrite, or neither read nor aread.

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 seq order. Skip those whose seq you already have. They continue right after what you have (no gaps). May be empty (a State with no history yet).

required
info Mapping[str, Any]

The new info dict: a small JSON dict with the StateInfo fields (all but id) and the format version under "v". No messages, no history. Replace the stored one with it, after the entries. Give it back as it is in Record.info.

required
create bool

The first write of a new State. If state_id already exists, write nothing and raise ValueError. Either everything is written or nothing is.

False

Raises:

Type Description
ValueError

create is true and the id exists.

LookupError

create is false and the id does not exist (deleted while in use).

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.

delete

delete(state_id: str) -> None

Deletes one State. Does nothing if the id does not exist.

adelete async

adelete(state_id: str) -> None

The async version of delete. By default it runs delete on a worker thread.

list

list() -> list[StateInfo]

Every saved State as a StateInfo, most recently updated first.

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 asave), or it was called on the event loop thread of an async run (use asave).

Example
state.add_message(Message.user(input("> ")))
store.save(state)

asave async

asave(state: State) -> None

The async version of save.

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)

aload async

aload(state_id: str) -> State

The async version of load.

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

state_id is not a valid id, create is true and the id exists, or the entries do not continue the log.

LookupError

create is false and the State was deleted.

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

state_id is not a valid id, or the saved State is damaged (no info.json).

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.

id instance-attribute

id: str

The State id. Continue it with store.load(id).

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.

turn instance-attribute

turn: int

Turns so far.

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.

from_info classmethod

from_info(state_id: str, info: Mapping[str, Any]) -> StateInfo

Builds one from an info dict, for Store implementations.

Raises:

Type Description
ValueError

The info was saved by another format version, or is missing keys.

Record dataclass

Record(entries: list[dict[str, Any]], info: dict[str, Any])

What a store has for one State id (Store.read).

entries instance-attribute

entries: list[dict[str, Any]]

History entries in seq order, starting at 0.

info instance-attribute

info: dict[str, Any]

The latest info dict, exactly as write got it (it has the format version under "v").