Without session persistence, every restart wipes the conversation. This chapter adds the two components that fix that: a store that survives restarts, and a guard that prevents the LLM from choking on a conversation that’s grown too long.
The Big Picture
In Chapter 1–2 (Tool Use & Structured Output), restarting the program wipes the conversation. Chapter 3 adds two layers:
- SessionStore — persist
messages[]to disk (JSONL) - ContextGuard — protect against context window overflow
Start
│
▼
load_session() ── read JSONL ──> rebuild messages[]
│
▼
[LOOP] User input
│
▼
guard_api_call()
├── Attempt 1: normal
├── Attempt 2: truncate tool results
└── Attempt 3: compact history (LLM summary)
│
▼
save_turn() ── append to JSONL ──> disk
│
▼
[end_turn] Print
Three Key Concepts
1. SessionStore — JSONL Append-Only
One event = one line of JSON:
{"type":"user", "content":"hello", "ts":1234}
{"type":"assistant", "content":[...], "ts":1235}
{"type":"tool_use", "name":"bash", "input":{...},"ts":1236}
{"type":"tool_result","content":"output...", "ts":1236}
Why append-only?
- Never rewrites the file → can’t get corrupted
- Restore = read all lines from top, rebuild
messages[]
Analogy: A notebook where you can only write at the bottom — never erase or edit.
2. ContextGuard — 3-Stage Overflow Protection
Every LLM has a maximum amount of text it can process in one call, measured in tokens (roughly 1 token per word). This limit is called the context window.
LLMs have token limits. If messages[] gets too long → API error.
Attempt 1: normal call
↓ fails (overflow)
Attempt 2: truncate oversized tool results
↓ still fails
Attempt 3: compact — LLM summarizes 50% of old history
↓ still fails
raise error
This all happens inside a single guard function. You call it instead of calling the API directly — it handles the fallback chain for you.
Stage 3 — LLM summarizes old history:
compacted := []map[string]string{
{"role": "user", "content": "[Summary] " + summary},
// summary is generated by asking the LLM: "Summarize this conversation in 3-5 sentences"
{"role": "assistant", "content": "Understood."},
// ... recent messages kept intact
}
3. Short-Term vs Persistent Memory
JSONL file — persistent memory (on disk)
↓
load_session() — read JSONL, rebuild messages[]
↓
messages[] — short-term memory (lives in RAM, lost on restart)
When a session starts, messages[] is not empty — it’s already populated from JSONL. This is how production agents “remember” previous conversations.
Where to Store Sessions
| Option | Good for |
|---|---|
| In-memory only (Chapter 1–2, Tool Use & Structured Output) | Prototypes, single-use agents |
| JSONL SessionStore (Chapter 3) | Production agents, multi-session |
| External DB (Redis, Postgres) | Multi-user, high-scale |
For most personal or small-team agents, JSONL is sufficient and keeps things simple.
If you’re building an agent used by many users simultaneously, you’ll need a shared store so all instances can access the same history. For a personal agent or small team, JSONL on disk is plenty.
Takeaway
- JSONL — one event per line, append-only, corruption-resistant
load_session()— how persistent memory entersmessages[]- ContextGuard — 3-stage safety net when context fills up
- Compact — LLM summarizes old history, keeps context lean