How KAOS Agents Learn
Across Sessions

Agents write compact, searchable memories — results, skills, errors. Every future agent retrieves them with BM25 full-text search. A meta-harness run that took 6 iterations now starts at 1.

The problem: agents start from zero every time

You run a meta-harness search on a text classification benchmark. It takes 6 iterations to find the winning approach — ensemble voting with numbered chain-of-thought, JSON parsing wrapped in try/except. On iteration 3, your agent discovers that temperature=0 helps. On iteration 5, it hits the JSON parsing bug. On iteration 6, it finally puts it all together and hits 0.872 accuracy.

Next week you run the same search on a slightly different dataset. Your agent starts at 0 again. It re-discovers chain-of-thought on iteration 2. It hits the JSON bug on iteration 4. It finds ensemble voting on iteration 6. You just paid for 6 more iterations of work your agents already did.

This is the problem cross-agent memory solves.


The solution: a shared, searchable memory store

KAOS v0.6.0 adds a MemoryStore — a SQLite FTS5 table shared across all agents, all sessions, and (optionally) all projects. Any agent writes to it. Any agent searches it. Results are ranked by BM25 relevance, not insertion order.

Five memory types: result (what worked), skill (how to do something), error (what broke and why), observation (neutral findings), insight (derived conclusions).

from kaos.memory import MemoryStore
from kaos import Kaos

db  = Kaos("project.db")
mem = MemoryStore(db.conn)

# Agent A finishes a search — writes what it learned
mem.write(
    "search-agent-01",
    "Ensemble voting with 3 Sonnet calls hit accuracy=0.872 on math_rag. "
    "Key: temperature=0, numbered chain-of-thought, try/except around JSON parse.",
    type="result",
    key="math_rag:iter6:best"
)

# Agent B discovers a recurring error
mem.write(
    "search-agent-01",
    "JSON decode error in 40% of cases when model outputs extra text before the dict. "
    "Fix: always wrap in try/except with regex fallback.",
    type="error",
    key="json-parse-fail"
)

# Next week — a brand new agent, different search session
hits = mem.search("ensemble accuracy math_rag", limit=5)
# => [result] math_rag:iter6:best — accuracy=0.872, ensemble, CoT...
# => [error]  json-parse-fail — JSON decode 40%, try/except fix...

# Agent immediately applies what it found — skips 5 iterations of trial-and-error
Inspired by claude-mem. The core idea — agents writing compact memories for cross-session retrieval — comes directly from claude-mem by Alex Newman (@thedotmack, AGPL-3.0). KAOS adapts it for SQLite FTS5, multi-agent access, and typed entries.

How the search works: FTS5 + BM25

Memory entries are stored in a standard SQLite table and indexed in a parallel FTS5 virtual table with a porter unicode61 tokenizer. Three sync triggers (INSERT / UPDATE / DELETE) keep them in sync atomically.

When you call mem.search("ensemble accuracy"), KAOS runs:

SELECT m.*, fts.rank
FROM memory_fts fts
JOIN memory m ON m.memory_id = fts.rowid
WHERE memory_fts MATCH ?
  AND (type_filter IS NULL OR m.type = type_filter)
ORDER BY fts.rank   -- BM25: higher rank = more relevant
LIMIT ?

BM25 accounts for term frequency in the document and inverse document frequency across the whole corpus. "ensemble accuracy" will rank the entry about ensemble voting above a generic entry that only mentions "accuracy" once. No embeddings, no vectors, no external service. Pure SQLite, zero new dependencies.

Exact key lookup

If you know the key, you don't need to search:

entry = mem.get_by_key("json-parse-fail")
# => MemoryEntry(memory_id=6, type='error', key='json-parse-fail', ...)

What it looks like in practice

Here's the before/after on the math_rag benchmark across two consecutive search sessions:

MetricSession 1 (no memory)Session 2 (with memory)
Starting accuracy0.641 (seed eval)0.641 (same seeds)
Iteration 1 score0.6540.864
Iterations to 0.86+61
JSON parse errorsHit on iter 5Avoided (error memory)
Final best accuracy0.8720.871
API cost$19.40$3.20

Session 2's proposer read the memory store before proposing. It found the ensemble approach, the temperature=0 finding, and the JSON error. Its iteration-1 harness applied all three and scored 0.864 — what session 1 needed 6 iterations to discover.

Cross-Agent Memory demo — proposer reads prior session results and skips 5 iterations

Proposer reads 4 prior-session memory entries, applies them, and scores 0.864 on iteration 1.


Meta-harness integration: automatic, non-intrusive

You don't have to manage memory manually. The meta-harness does it for you:

# In search.py — auto-persists after each iteration
def _persist_to_memory(self, harness, result, iteration, attempt_status):
    mem = MemoryStore(self.db.conn)
    if attempt_status == "improved":
        mem.write(agent_id, content, type="result",
                  key=f"{benchmark}:iter{iteration}:{harness_id[:8]}")
    elif attempt_status == "failed":
        mem.write(agent_id, content, type="error",
                  key=f"{benchmark}:iter{iteration}:fail")

The CLI

# Write a memory entry
kaos memory write agent-a "ensemble voting hit 0.87" \
  --type result --key iter6-best

# Search across all agents and sessions (BM25)
kaos memory search "ensemble accuracy"
kaos memory search "json error" --type error

# List by type
kaos memory ls --type skill
kaos memory ls --agent search-agent-01 --limit 10

# JSON output for composability
kaos --json memory search "ensemble" | jq '.[].content'

MCP tools

All four memory operations are available as MCP tools for Claude Code, Cursor, and compatible clients:

Ask your AI assistant: "with kaos, search memory for past results on text classification" and it will call agent_memory_search directly.


The schema

CREATE TABLE memory (
    memory_id   INTEGER PRIMARY KEY AUTOINCREMENT,
    agent_id    TEXT NOT NULL REFERENCES agents(agent_id),
    type        TEXT NOT NULL DEFAULT 'observation'
                CHECK (type IN ('observation','result','skill','insight','error')),
    key         TEXT,          -- optional human-readable handle
    content     TEXT NOT NULL,
    metadata    TEXT NOT NULL DEFAULT '{}',
    created_at  TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
);

CREATE VIRTUAL TABLE memory_fts USING fts5(
    content, key, type UNINDEXED, agent_id UNINDEXED,
    memory_id UNINDEXED, created_at UNINDEXED,
    tokenize = 'porter unicode61'
);
-- Three sync triggers keep memory_fts in sync automatically

The FTS table indexes content and key. Unindexed columns (type, agent_id, etc.) ride along in the FTS row so they can be read without a JOIN — but they don't inflate the inverted index.


Where to go next

Credits. Cross-Agent Memory in KAOS is inspired by claude-mem by Alex Newman (@thedotmack), AGPL-3.0. The core idea — agents writing compact, searchable memories for cross-session retrieval — is taken directly from claude-mem. KAOS adapts it for SQLite FTS5, multi-agent access, and typed entries.