/improve: How pydantic-deep Agents Learn from Their Own Session History
Table of Contents
For three months my AI agent made the same wrong query every morning.
Search “pydantic”, zero results. Search “pydantic-ai”, forty-seven. Click the right repo. Every. Single. Session.
I would watch it happen, sigh, and move on, because I knew correcting the agent was pointless. By tomorrow it would forget the correction anyway. This is the default state of most AI agents today, and it’s the reason we built /improve.
Kacper Włodarczyk, AI engineering consultant at Vstorm. We’ve shipped 30+ production AI agent systems. pydantic-deep is our open-source agent runtime for Python, built on what we’ve learned from those deployments.
1. The problem: AI agents are stateless by default
Every time you start an agent session, the agent starts from zero.
It doesn’t remember that last Tuesday it tried searching GitHub for “pydantic” and got 0 results, then tried “pydantic-ai” and got 47. It doesn’t remember that you prefer async patterns over sync. It doesn’t remember that the API you’re working with returns paginated results and needs special handling.
For a single-use tool, this is fine. For a coding agent you use every day, it’s a significant limitation. You end up re-explaining the same context repeatedly. The agent makes the same mistakes across sessions.
/improve is our solution to this.
2. What /improve does: the pipeline
/improve is a post-session pipeline that extracts what happened in a session, distills learnings, and writes them to a persistent knowledge file (MEMORY.md) that future sessions load automatically.
Here’s the flow:
- 1
Session ends
tool_log.jsonl is saved with the raw tool calls (up to 20K chars).
- 2
/improve command triggered
Manually or as a post-session hook.
- 3
Extraction agent reads raw traces
Pulls structured UserFactInsight + AgentLearningInsight records.
- 4
Synthesis agent merges insights with raw traces
Outputs concrete proposed changes to context files.
- 5
MEMORY.md is updated
Calibrated confidence scores; 0.7 threshold filters out speculation.
- 6
Next session loads MEMORY.md on startup
Agent begins with everything the previous session learned.
The output isn’t a log file you read later. It’s living documentation that changes how the agent behaves in the next session.
3. UserFactInsight vs AgentLearningInsight
We designed two distinct insight categories because they serve different purposes.
UserFactInsight captures facts about you:
- “User prefers async Python patterns over sync”
- “User is working on pydantic-deep, an AI coding agent”
- “User wants concise responses, not extensive explanations”
- “User’s projects live in ~/Projects, not ~/code”
AgentLearningInsight captures what the agent learned about the domain, tools, and patterns:
- “GitHub search works better with ‘pydantic-ai’ than ‘pydantic’”
- “The Anthropic API returns usage data in stream chunks, not just at completion”
- “Tool X fails silently on empty input, check before calling”
UserFactInsight personalizes behavior. AgentLearningInsight improves correctness. Both live in MEMORY.md, organized by category.
One design decision worth noting: we relaxed the synthesis rules so that UserFactInsight can be extracted from a single session. Previously, a fact needed to appear across multiple sessions to be included. In practice, a user preference stated clearly once should be remembered.
4. The Meta-Harness finding: raw traces beat summaries
This is the part that surprised us most during development.
We tested two approaches for what input the synthesis agent receives:
Approach A: Extraction agent produces summaries → synthesis works from summaries.
Approach B: Extraction agent produces insights AND passes raw tool traces → synthesis works from both.
Approach B won clearly. We called this the Meta-Harness finding.
Here’s why. Consider a session where the agent searched GitHub:
Summary: “Agent searched GitHub and found the relevant repository.”
Raw trace:
browser.navigate("github.com/search?q=pydantic+agent")# → 0 results
browser.navigate("github.com/search?q=pydantic-ai+agent")# → 47 results
browser.click("vstorm-co/pydantic-deep")# → successThe summary is accurate but loses everything interesting: what the first query was, that it failed, what the agent tried next. The raw trace preserves the full decision path.
From the raw trace, the synthesis agent can extract: “Use ‘pydantic-ai’ not ‘pydantic’ for GitHub searches, the exact package name gets significantly more results.”
From the summary: “GitHub search succeeded.” Useless.
Summaries optimize for readability, but synthesis needs the decision path. The raw trace is the actual evidence. Summaries are lossy compression of that evidence.
Practical implication: tool_log.jsonl now stores up to 20K characters of raw tool call data per session. (The previous limit was 2K, which silently truncated most real sessions.)
5. MEMORY.md as evolving agent knowledge base
MEMORY.md lives at .pydantic-deep/main/MEMORY.md. The agent loads it on startup before any user prompt is processed.
After a few sessions with /improve, it might look like:
# Memory
## User Facts
- Prefers async Python patterns- Working on pydantic-deep (github.com/vstorm-co/pydantic-deep)- Projects live in ~/Projects/- Prefers minimal output, not verbose explanations
## Agent Learnings
- GitHub: use "pydantic-ai" not "pydantic" for better search results- Anthropic API usage data available in stream (not just completion)- Project uses underscores in filenames, not hyphens- Check for empty input before calling file read toolsThis file grows and refines with each /improve run. Conflicting information gets resolved. Stale facts get updated.
Key constraint: /improve writes to this file, it doesn’t read from it during extraction. This prevents circular reinforcement of existing beliefs.
6. The bugs we fixed, shipping reality
/improve v0.3.4 shipped with significant reliability fixes:
Crash on {} in prompt templates
Template strings passed through str.format(). Any {} in user content caused a KeyError. Fix: escape { → {{ before format, unescape after.
Silent failures
/improve could fail mid-session and report success. We added: failed_sessions count and last_error message.
API key not loaded The synthesis agent was created before the keystore was queried. Fix: load API keys before instantiating agents.
Wrong model used
The synthesis agent was hardcoded to an OpenRouter fallback model. Fixed to use the model from config.toml.
MEMORY.md path mismatch
/improve was writing to ./MEMORY.md (project root) rather than .pydantic-deep/main/MEMORY.md (where the agent reads it). The writes were succeeding, but the agent wasn’t reading them.
This last one is the kind of bug that’s invisible until you check: “wait, why isn’t the agent remembering anything?“
7. Closing the arc
This week, pydantic-deep got:
- StuckLoopDetection, stops agents from running 47 identical tool calls
- LimitWarnerCapability, warns before context fills and degrades
- curl install, one command, no pip environment management
- Docker sandbox + CI runner, isolated execution, safe for automation
- Browser automation, real web access, not just API calls
- TUI observability, per-turn tokens, live thinking, cost tracking
- /improve, session analysis, insight extraction, MEMORY.md updates
Combine all of these and you have an agent that doesn’t get stuck, doesn’t go blind, runs safely in isolation, can see the web, shows you everything it does and costs, and gets better every time you use it.
Key Takeaways
- AI agents are stateless by default, every session starts from zero without explicit memory
/improveruns post-session, readstool_log.jsonl, extracts insights, updates MEMORY.md- Two insight types:
UserFactInsight(about you) andAgentLearningInsight(about the domain) - Raw traces beat summaries (Meta-Harness finding), the full decision path is the evidence; summaries are lossy
- MEMORY.md is the agent’s evolving knowledge base, loaded on startup, updated after /improve
- Reliability matters, 5 bugs fixed in v0.3.4 that made /improve unreliable in real conditions
- No retraining required, improvement comes from context, not model weights
This whole week was about pydantic-deep. If you want it plugged into a full-stack app, full-stack-ai-agent-template ships it integrated, from FastAPI backend to Next.js frontend, with pydantic-deep wired in.
Try it
curl -fsSL https://oss.vstorm.co/install.sh | bashGitHub: github.com/vstorm-co/pydantic-deep
What sessions would you run /improve on first?