Skip to main content

Lesson 10: History

Add conversation history so the agent remembers what you said earlier in the session.

Topics Covered
  • Session Storage: How Agno stores conversations in PostgreSQL.
  • Ephemeral Sessions: History that resets when you restart the script.
  • Persistent Sessions: History that survives restarts.
  • Context Injection: What actually gets sent to the LLM.

What History Actually Does

In Lesson 9, each turn was independent. The LLM received only the system prompt and current message. Now we add history:

Turn 1: "My name is Bob"
→ LLM receives: [system, "My name is Bob"]
→ Response: "Nice to meet you, Bob!"

Turn 2: "What is my name?"
→ LLM receives: [system, "My name is Bob", "Nice to meet you, Bob!", "What is my name?"]
→ Response: "Your name is Bob."

The agent stores each exchange in PostgreSQL. Before the next turn, it loads previous messages and prepends them to the context. The LLM sees the full conversation, so it can answer follow-up questions.

Ephemeral Sessions

Each script run gets a new session ID. History accumulates during the run but resets when you restart.

10-history-ephemeral.py
"""
Lesson 10a: Ephemeral Sessions

Adds chat history storage in Postgres. Previous messages are injected into the
context window before each turn. Session is ephemeral - new session ID on each
script run, so history resets when you restart.

Run: uv run 10-history-ephemeral.py
Try: "My name is Bob" → "What is my name?"

Observe in Phoenix (http://localhost:6006):
- Prior messages from same session in LLM input
- Current datetime injected into context
- History grows with each turn

Reset: uv run tools/reset_data.py
"""

import os
from dotenv import load_dotenv
from phoenix.otel import register
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.db.postgres import PostgresDb

load_dotenv()

register(project_name="10-history-ephemeral", auto_instrument=True, batch=True, verbose=True)

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")

agent = Agent(
name="Assistant",
model=OpenAIChat(id=os.getenv("OPENAI_MODEL_ID")),
instructions="You are a helpful assistant. Be concise.",
db=db,
add_history_to_context=True,
num_history_runs=5,
add_datetime_to_context=True,
markdown=True,
)

agent.cli_app(stream=True)

What's New

Database connection:

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")

Connects to the ai database. Agno creates tables automatically on first run.

History settings:

db=db,
add_history_to_context=True,
num_history_runs=5,
add_datetime_to_context=True,
  • db: Where to store sessions
  • add_history_to_context: Load previous messages before each turn
  • num_history_runs: How many previous exchanges to include (5 = last 5 user/assistant pairs)
  • add_datetime_to_context: Inject current timestamp so the agent knows "now"

Try It

uv run 10-history-ephemeral.py
> My name is Bob
Nice to meet you, Bob!

> What is my name?
Your name is Bob.

> Ctrl+C (stop script)

Now restart and try again:

uv run 10-history-ephemeral.py
> What is my name?
I don't know your name. You haven't told me yet.

New run = new session = no history. The agent genuinely doesn't know—the previous session's data exists in Postgres, but this run has a different session ID.

Persistent Sessions

Use a fixed session ID to maintain history across restarts.

10-history-persistent.py
"""
Lesson 10b: Persistent Sessions

Uses fixed session_id to persist history across script restarts. Unlike 10a,
this maintains the same conversation thread in Postgres between runs. Stop the
script, restart it, and the agent still remembers the conversation.

Run: uv run 10-history-persistent.py
Try: "My name is Alice" → Ctrl+C → re-run script → "What's my name?"

Observe in Phoenix (http://localhost:6006):
- Same session_id across multiple script runs
- Full conversation history restored on restart
- Compare with 10a traces

Reset: uv run tools/reset_data.py
"""

import os
from dotenv import load_dotenv
from phoenix.otel import register
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.db.postgres import PostgresDb

load_dotenv()

register(project_name="10-history-persistent", auto_instrument=True, batch=True, verbose=True)

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")

agent = Agent(
name="Assistant",
model=OpenAIChat(id=os.getenv("OPENAI_MODEL_ID")),
instructions="You are a helpful assistant. Be concise.",
db=db,
session_id="demo-session-001", # fixed session = persistent history
add_history_to_context=True,
num_history_runs=10,
add_datetime_to_context=True,
markdown=True,
)

agent.cli_app(stream=True)

The Key Difference

session_id="demo-session-001",  # fixed session = persistent history

That's it. One line. Same session ID means Agno loads the same conversation from Postgres, regardless of when you run the script.

Try It

uv run 10-history-persistent.py
> My name is Alice
Nice to meet you, Alice!

> Ctrl+C (stop script)

Wait a minute. Restart:

uv run 10-history-persistent.py
> What's my name?
Your name is Alice.

The agent remembers because it loaded the previous session from the database.

When to Use Which

Ephemeral sessions (no fixed session_id):

  • Chat widgets where each visit is independent
  • Parallel processing where each item needs isolation
  • Testing and development

Persistent sessions (fixed session_id):

  • User accounts where you want conversation continuity
  • Long-running tasks that might be interrupted
  • Support threads that span multiple interactions

In production, you'd typically generate session IDs per user or per conversation thread, not hardcode them.

Observe in Phoenix

Open http://localhost:6006 and compare traces between the two scripts.

Ephemeral (10-history-ephemeral):

  • Each run shows a new session
  • Message arrays grow within a run, reset between runs

Persistent (10-history-persistent):

  • Same session across runs
  • Message arrays include history from previous runs
  • You can see the conversation accumulating over time

Look at the LLM input for any trace—you'll see the prior messages injected before the current one. That's history in action.

What's Stored in Postgres

Agno creates tables in the ai database with the agno_ prefix. Runs are stored as a JSONB array inside agno_sessions—not as separate rows.

-- View your sessions
SELECT session_id, created_at FROM agno_sessions ORDER BY created_at DESC;

-- View conversation history for a session
SELECT
session_id,
run->>'run_id' AS run_id,
run->'input'->>'input_content' AS user_prompt,
run->>'content' AS ai_response,
(run->'metrics'->>'total_tokens')::int AS total_tokens,
(run->'metrics'->>'duration')::float AS duration_seconds,
to_timestamp((run->>'created_at')::bigint) AS created_at
FROM
agno_sessions,
jsonb_array_elements(runs) AS run
ORDER BY created_at DESC;

Each "run" is one user input + agent response pair, stored as a JSON element in the runs array. The num_history_runs parameter controls how many of these get loaded into context.

Key Concepts

ConceptThis Lesson
SessionContainer for a conversation thread
HistoryPrevious messages replayed to the LLM
EphemeralNew session ID each run (default)
PersistentFixed session ID across runs
num_history_runsHow much history to load

What's Next

History lets the agent remember this conversation. But what about facts that should persist across all conversations? In Lesson 11, we add memory—the agent will remember things about you even in new sessions.