Back to projects

case study

RECALL-Agent

A coding agent that learns reusable lessons from its own runs

Most coding agents start from zero on every task. RECALL-Agent keeps short, typed lessons from earlier runs and checks every answer by actually executing it, so I could measure whether memory helps instead of taking the model's word for it.

Type
Research project
Stack
Python, LangGraph, ChromaDB, SQLite, FastAPI, Streamlit
Models
OpenAI, Anthropic, and a deterministic offline provider
Source code on GitHub

The problem

Two things bothered me about "self-improving" agent demos. First, the same model often solves the task and grades it, so any reported gain can't be checked. Second, memory is usually a dump of old conversations, which fills the context without saying what to do differently next time.

I wanted an agent where success comes from running code, and where memory stores compact lessons with a measurable usefulness score.

How it works

A Decision Layer supervises a Planner, Worker and Critic. The Worker's solution is run against the task's check() function in an isolated, time-limited Python subprocess, and that pass or fail result overrides an over-optimistic critic.

After notable runs (a failure, low confidence, a repeated mistake or something new) a Reflection step writes exactly one lesson: its scope, a trigger condition, a recommended action, an anti-pattern to avoid and a confidence score. Lessons are stored in SQLite and ChromaDB, retrieved by scope, semantic relevance and learned utility, merged when they are near-duplicates, and pruned when they stop helping.

RECALL-Agent architecture: user task, interface, orchestrator, planner, worker, critic, decision and final answer, with an execution verifier giving a hard pass or fail and a memory loop of reflection, memory agent, SQLite and a vector index.
Architecture diagram from the repository.

Key decisions

  • Execution decides success. A model grading itself can't be falsified; running check() can.
  • Lessons, not transcripts. Each lesson is a typed record with a trigger and an anti-pattern, so retrieval returns advice rather than old chat.
  • Utility is learned. Each lesson's score is updated with a relevance-weighted moving average of the reward from runs that used it, and low-utility lessons are pruned.
  • A real ablation. With memory off there is no retrieval, no lesson writing and no utility update, so the comparison is clean.

How I evaluated it

The benchmark has nine coding task families, each with an executable check. I ran it with memory on and off. In the offline setup the deterministic provider can only pass a task by using a stored lesson, which isolates the memory loop. I also ran a smaller live experiment with an OpenAI model.

Offline benchmark: 4 rounds × 3 seeds × 9 task families
ConditionOverallRound 1Round 2Round 3Round 4Avg. retries
Memory off0%0%0%0%0%2.0
Memory on75%0%100%100%100%0.5
Live run with an OpenAI model: 3 rounds × 2 seeds × 9 task families
ConditionOverall successSpread across seeds
Memory off48%± 4 points
Memory on54%± 9 points
Two charts comparing memory on and memory off. Cumulative success rises to 0.75 with memory on and stays at 0 with memory off; per-round success is 0 in round 1 and 1.0 in rounds 2 to 4 with memory on.
Learning curve from the offline benchmark.

What the results do and don't show

The offline result shows that the memory loop works end to end: every task family failed on first sight, produced a lesson, and was solved in every later round, with retries cut from 2.0 to 0.5. It is not a claim about real models, because the offline baseline is 0% by construction.

The live run is the honest external check. Success went from 48% to 54%, which is a small gain, and with two seeds I wouldn't call it significant. Showing a real effect on a real model needs more seeds and harder, unseen task families.

Limitations and next steps

  • Only tasks that ship a check() get a hard signal; open-ended tasks fall back to the critic's score.
  • Offline runs use a hashing embedding, so semantic retrieval is conservative and relies on tag-scoping.
  • Lessons are family-specific. Measuring transfer across task families is the next experiment.
  • Planned: a held-out generalisation suite, counterfactual credit assignment (which lesson caused a win) and a Qdrant backend for scale.
  • The verifier strips credentials and enforces a timeout, but it is not a security boundary. Untrusted code should run in a locked-down container.