Chapter 15 · Storage: Session Persistence
Learning objectives: Understand Agno's unified database layer (introduced in 2.x, continued in 3.x), learn to use
SqliteDbfor development andPostgresDbfor production, and verify that "conversations survive process restarts."
15.1 What Problem Does Storage Solve?
Chat history and session state from the previous chapter both depend on one prerequisite: a database. An Agent without storage is like a goldfish — forgetful on every process restart; with storage, the same session_id can resume conversations across processes and even across machines. The official docs put it plainly:
Storage gives Agents cross-session memory: as long as you use the same
session_id, the conversation can continue from where it left off even after a process restart. (Chinese paraphrase of the official documentation)
Starting from 2.x (carried over in 3.x), Agno unified its storage layer: no longer distinguishing between the old Storage class and separate memory libraries. Instead, session runs, session state, user memories, and session summaries are all managed by a single db object (corresponding tables include agno_sessions, agno_memories, etc.). You just need to pick the right database driver.
15.2 Development Environment: SqliteDb
SQLite is zero-deployment and single-file — the default choice for local development:
# storage_basic.py —— Minimal session persistence example
import os
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIChat
from agno.tools.yfinance import YFinanceTools
db = SqliteDb(db_file="tmp/agents.db") # One file is the entire database
agent = Agent(
model=OpenAIChat(
id="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
),
tools=[YFinanceTools(enable_stock_price=True)],
db=db,
add_history_to_context=True,
num_history_runs=5,
markdown=True,
)
session_id = "finance-session"
agent.print_response("Give a brief analysis of NVIDIA's stock trend", session_id=session_id)
agent.print_response("Compare it with AMD", session_id=session_id) # "it" = NVIDIA
agent.print_response("Which is the better investment?", session_id=session_id) # full contextOn the third question the model understands what "both" refers to — because the full messages (including tool call results) from the first two rounds are in the database.
15.3 Restart Verification: The True Test of Persistence
There is only one standard for verifying whether storage works: kill the process and run again — can the conversation still resume?
# resume_check.py —— First run: write
import os
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIChat
agent = Agent(
model=OpenAIChat(
id="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
),
db=SqliteDb(db_file="tmp/agent.db"),
add_history_to_context=True,
)
agent.print_response(
"Remember this secret code: pineapple pizza.",
session_id="resume-demo",
)
print("First run complete. Now please end the process and run resume_check_2.py")# resume_check_2.py —— Second run (new process): read
import os
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIChat
agent = Agent(
model=OpenAIChat(
id="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
),
db=SqliteDb(db_file="tmp/agent.db"), # Points to the same file
add_history_to_context=True,
)
agent.print_response(
"What was the secret code? Where did we leave off?",
session_id="resume-demo",
) # Model should be able to recount the code and context — persistence workingNo matter how much time passes between the two runs (even on a different machine with the same agent.db file), the conversation resumes seamlessly.
What's actually stored in the database
Open tmp/agents.db (sqlite3 tmp/agents.db ".tables") to see tables like agno_sessions, agno_runs: every round's message list, tool calls, metrics, and session_state snapshots are stored as JSON in row records. Understanding this helps diagnose "why does the model know things it shouldn't."
15.4 Production Environment: PostgresDb
SQLite doesn't support concurrent writes or horizontal scaling — switch to PostgresDb for production. Thanks to the unified interface, migration typically requires only one line change:
# storage_postgres.py —— Production-grade storage
import os
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIChat
db = PostgresDb(
db_url=os.getenv(
"DB_URL",
"postgresql+psycopg://ai:ai@localhost:5432/ai",
),
)
agent = Agent(
model=OpenAIChat(
id="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
),
db=db,
add_history_to_context=True,
update_memory_on_run=True, # Memory also goes through the same db (Chapter 16)
)
# Under multi-instance deployment, as long as all instances share the same Postgres,
# any instance can serve requests for the same session_id.
agent.print_response("Let's continue our last topic", session_id="user-42-thread-1")The official docs also provide an async variant AsyncPostgresDb to pair with Agent.arun(). Besides Postgres, the same db abstraction offers MySQL, MongoDB, Redis, DynamoDB, and more — choose whichever aligns with your team's existing ops stack.
| Scenario | Recommended | Reason |
|---|---|---|
| Local development / testing / demos | SqliteDb | Zero deployment, single file easy to backup |
| Production API service (multiple replicas) | PostgresDb / AsyncPostgresDb | Concurrency-safe, horizontally scalable |
| Existing Redis/Mongo stack | Corresponding Db driver | Reuse existing ops capabilities |
15.5 Storage, State, and History in Concert
Tie everything together into a complete mental model:
# full_stack.py —— Storage + History + State working together
import os
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIChat
from agno.run import RunContext
def advance_stage(run_context: RunContext) -> str:
"""Move the order workflow to its next stage.
Args:
(No model-visible parameters)
"""
stages = ["Gather requirements", "Confirm plan", "Quotation", "Complete"]
current = run_context.session_state.get("stage_idx", 0)
if current >= len(stages) - 1:
return "Workflow complete"
run_context.session_state["stage_idx"] = current + 1
return f"Current stage: {stages[current + 1]}"
agent = Agent(
model=OpenAIChat(
id="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
),
db=SqliteDb(db_file="tmp/workflow.db"), # The foundation of all persistence
session_state={"stage_idx": 0}, # State persists with the session
tools=[advance_stage],
instructions="Order stage: {stage_idx} maps to Gather/Confirm/Quote/Complete",
add_history_to_context=True, # History persists with the session
)
agent.print_response("I want to customize a batch of T-shirts", session_id="order-9001") # Advance to Confirm plan
# ……process restart……
agent.print_response("Where are we in the process? What's next?", session_id="order-9001")After a run ends, the database stores: message history (including tool calls), a session state snapshot, and token metrics. db is the common carrier for all three — which is why Chapter 14 says "the prerequisite for history injection is configuring a database."
Chapter Summary
- Storage lets Agents maintain memory across processes and machines; the verification standard is "same session can resume after restart";
- Agno's unified storage layer (introduced in 2.x, carried in 3.x): sessions, state, memories, and summaries all share one
dbobject; - Development uses
SqliteDb(db_file=...), production usesPostgresDb(AsyncPostgresDbfor async), migration cost is roughly one line change; - You can inspect
agno_sessions/agno_runstables in the database — messages, state snapshots, and metrics are all stored there.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. What is the core config responsible for session persistence in Agno?
2. What is the most reliable way to verify that storage is truly working?
3. Migrating from SQLite to Postgres — how much Agent code needs to change?
4. Which of the following is NOT written to the database along with a session after a run completes?
🛠️ Hands-On Exercises
- Run the two scripts from 15.3 to complete the restart verification; then use
sqlite3 tmp/agent.db ".tables"and.schema agno_runsto inspect the table structure — screenshot or excerpt the key column names. - Extend the order workflow in 15.5 to 5 stages, and add guidance in
instructionsabout "what to ask the user at the current stage"; test cross-restart progression. - Start a Postgres container with Docker, switch both examples in this chapter to
PostgresDb, and repeat the restart verification — record whether there are any behavioral differences between the two storages.
After completing the hands-on exercises, proceed to Chapter 16: Memory — User Profiles and Summaries.