Skip to content

History

Messages are plain dicts (typed as UserMessage, AssistantMessage and ToolMessage). The same format goes in and out:

{"role": "user", "content": "Hi"}
{"role": "user", "content": [{"type": "text", "text": "What is this?"}, {"type": "image", "url": "https://..."}]}
{"role": "assistant", "content": "", "tool_calls": [{"id": "call_1", "name": "add", "arguments": {"a": 1}}]}
{"role": "tool", "tool_call_id": "call_1", "name": "add", "content": "3", "is_error": False}

Assistant messages also record the model that wrote them. With the Responses API they also keep the model's encrypted reasoning and the response_id. Keep these fields when you store the history: they are what lets the next request reuse the reasoning and the prompt cache.

Continue a conversation

Pass the messages of a previous run as history=:

first = await agent.run("I am Dani")
second = await agent.run("What is my name?", history=first.messages)

history= also takes JSON text (str or bytes), which is parsed in Rust without building Python dicts. Use it when the messages come from a database:

await agent.run("What is my name?", history=first.messages_json())

RunFinished.new_messages (and new_messages_json()) hold only what the run added, to append to your storage.

History stores

Or let the agent load and save conversations for you. Give it a history store once, and name the conversation on each run:

agent = Agent(model="gpt-5-mini", history_store=store)
await agent.run("Hi again", conversation_id="user-42")

Before the run, the agent calls store.load(conversation_id); after it, store.save(conversation_id, new_messages_json). Failed runs are not saved. If the store fails or returns something invalid, the run raises (HistoryError for invalid data).

Any object with a load method (and optionally save), sync or async. A plain load(conversation_id) function works too. load returns message dicts, or JSON text (faster: parsed in Rust).

--8 < --"examples/python_history.py"

Implement the HistoryStore trait from pyox-tools on a struct holding your connection pool (sqlx, diesel, SeaORM...). The agent calls it directly in Rust, so loading never goes through Python:

struct SqliteHistory {
    pool: SqlitePool,
}

impl HistoryStore for SqliteHistory {
    async fn load(&self, conversation_id: &str) -> Result<Vec<Message>, String> { ... }
    async fn save(&self, conversation_id: &str, new_messages: &[Message]) -> Result<(), String> { ... }
}

Expose it with a #[pyfunction] that returns RustHistoryStore::new(your_store), and use it from Python:

--8 < --"examples/rust_history.py"

examples/rust_history_store is a complete SQLite store to copy.

Try both from the repository root (run each twice: the second time, the agent remembers):

uv sync
uv run examples/python_history.py
uv run examples/rust_history.py

In production

Create the store once per process and share it between agents and runs: it owns the connection pool. Keep one row per message, so saving a run only appends what it added.