Skip to content

Memory System

mtecnic edited this page May 27, 2026 · 1 revision

Memory System

How cadillac accumulates and uses experience across builds. Source: cadillac/memory.py + memory.jsonl at the repo root.

Currently: 403 lessons, 222 do / 181 dont, 98.8% reuse rate, avg confidence 0.945.

Lesson schema

@dataclass
class Lesson:
    ts: float                  # unix timestamp written
    type: str                  # error_pattern | architecture | tool_pattern | dependency | performance
    trigger: str               # one-line description of the situation
    fix: str                   # what to do (or NOT do — see polarity)
    confidence: float = 0.5    # [0, 1], reinforced by successful reuse, decayed by backfire
    used: int = 0              # how many times recalled
    polarity: str = "do"       # "do" = positive pattern; "dont" = anti-pattern
    tags: list = []            # ["python", "flask", "sqlite", ...]
    source_task: str = ""      # originating task text (truncated to 200 chars)

JSONL on disk, one lesson per line. Inspect with any jq / grep / python3 -c "json.loads(line)" you want.

Lifecycle

  1. Reflection extracts — after each phase ends (success or failure), _run_phase_reflection() calls the LLM to extract TYPE | TRIGGER | FIX lines from the round's observations.
  2. parse_reflection() — parses LLM output into Lesson records:
    • Fresh confidence: 0.3 (lowered from 0.5 in the 2026-05-26 hygiene pass) — forces 2+ reuses before outweighing scoring noise floor
    • Tags: explicit from caller, or inferred from trigger+fix via infer_tags_from_text() if caller omits
    • source_task: the build's original task text, truncated to 200 chars
  3. save_lesson() — atomic append to memory.jsonl (cross-process locked via fcntl)
  4. deduplicate() — O(n) token-bucket dedup on the trigger text. New lessons whose trigger overlaps an existing lesson by >70% are merged in.

Recall scoring

recall(task_description, limit=10) returns the top-N most relevant lessons for the current task. Scoring formula:

base = (keyword_overlap × 2) + recency_score + confidence + (used × 0.1)

With three filtering / decay layers:

Layer 1: hard tag filter

task_tags = infer_task_tags(task_description)  # known stack words
if task_tags:
    lessons = [l for l in lessons if not l.tags or set(l.tags) & task_tags]

A lesson with tags=["typescript", "react"] is filtered out of a Flask task. Untagged lessons survive the filter — they're treated as stack-agnostic.

Layer 2: source-task decay (5× penalty)

For lessons with a populated source_task field — a lesson whose originating task has zero stack-tag overlap with the current task gets score /= 5.0.

if task_tags and lesson.source_task:
    src_tags = infer_tags_from_text(lesson.source_task)
    if src_tags and not (src_tags & task_tags):
        total /= 5.0

Why: a lesson from an IRC asyncio task should not outrank a stack-matched Flask lesson on a Flask task, even if its confidence + used count are higher. Same-language counts as same-stack (Python-Python transfer is real).

Layer 3: content tag inference (2× penalty)

For legacy lessons without source_task (pre-2026-05-26), apply a softer penalty when the lesson's body text strongly implies a different stack:

if task_tags and not lesson.tags and not lesson.source_task:
    content_tags = infer_tags_from_text(trigger + " " + fix)
    if content_tags and not (content_tags & task_tags):
        total /= 2.0

Catches the case where a lesson mentions tsconfig.json in its body but happens to be untagged — it's clearly a TypeScript lesson and shouldn't outrank a Python-tagged one on a Python task.

Tag inference

Two functions:

infer_task_tags(task)

Strict — only literal matches against the canonical _KNOWN_TAGS set. Used on the task description.

_KNOWN_TAGS = {
    "python", "typescript", "react", "vue", "angular", "electron",
    "go", "rust", "wordpress", "browser_extension", "pytorch",
    "flask", "django", "fastapi", "express", "node",
    "pytest", "vitest", "jest", "phpunit",
    "sqlite", "asyncio", "aiosqlite", ...
    "plan", "deps", "scaffold", "review", "build", "integrate",
    "wiring", "validate", "package",  # phase tags
}

infer_tags_from_text(text)

Broader — used on arbitrary text (lesson body, source_task). Adds:

  • Synonym expansion: "aiosqlite" → {python, sqlite, asyncio}, "tsc" → {typescript}, "cargo" → {rust}, "uvicorn" → {python, fastapi}
  • File-extension hints: .py → python, .ts/.tsx → typescript, .rs → rust, .go → go

So a lesson whose source_task is "async IRC server with websocket fanout" infers {python, asyncio} even though the literal word "python" isn't in it.

Reinforcement and penalization

boost_confidence(applied_triggers)

After a build succeeds, the lessons whose triggers matched a recalled-and-applied pattern get confidence += 0.1 (clamped to 1.0) and used += 1.

penalize_backfired(applied_triggers, error_text)

After a build that consumed a lesson THEN hit an error containing that lesson's trigger words, the lesson gets confidence -= 0.1. This catches a lesson that backfired — it advised something that turned out to be wrong in the current context.

Returns the list of penalized triggers for emit/visibility — you can see in the build log when a recalled lesson got dinged.

decay(lessons)

Called on every recall. Lessons older than 90 days lose confidence linearly until they're either reinforced or drop off relevance. Prevents long-tail lessons from staying authoritative forever.

Prompt format

format_for_prompt(lessons) renders the recalled lessons into the system prompt:

## Lessons from past builds (apply these):
- [error_pattern] When: vitest 0.34 + jsdom on Node 18 -> Do: pin vitest to ^1 in devDependencies
- [architecture] When: SQLite write during async handler -> Do: use aiosqlite with async context manager
...

## Anti-patterns from past builds (NEVER do these):
- NEVER: hardcoding CORS origins in source code (Reason: breaks builds on different machines)
- NEVER: f-string SQL with user input (Reason: SQL injection — use parameterized queries)

The do/dont split is honored — anti-patterns get the explicit NEVER framing so they don't get accidentally adopted as positive guidance.

Meta-lessons from the improve cycle

When cadillac improve lands a patch and the matrix score lifts by ≥0.05, commit_applied() writes an architecture-class lesson:

Lesson(
    type="architecture",
    trigger=f"weakness {weakness_id} in {target_file}",
    fix=f"matrix score improved by +{delta:.3f} on iter {iter}: {reason}",
    confidence=0.6,
    polarity="do",
    tags=["cadillac", "self"],
    source_task="cadillac improve cycle",
)

Future cadillac improve iterations recall these and see "what kinds of changes have moved the score before." The autobuilder accumulates institutional memory about its own evolution.

Phase-budget history

A second JSONL file at the repo root: phase_budgets.jsonl. Schema:

{"ts", "phase", "rounds", "n_files", "tags"}

After each completed phase, record_phase_outcome() appends one row. Next build's budget for that phase is computed by compute_budgets():

matching = filter(history, lambda r: tag_overlap(r.tags, task_tags))
budget[phase] = max(DEFAULT_BUDGET[phase], p90(matching.rounds) * 1.2)

Two failure modes this fixes:

  • Static-too-small — pygame BUILD historically needs 45 rounds; static budget of 30 was a premature abort
  • Static-too-large — simple Python CLI BUILD p90 is 8 rounds; 30 was wasted LLM time

Inspecting the corpus

The corpus is just JSONL — interrogate it directly:

# Total lessons
wc -l memory.jsonl

# Lessons by type
python3 -c "import json; from collections import Counter; print(Counter(json.loads(l)['type'] for l in open('memory.jsonl')))"

# Top-used lessons
python3 -c "
import json
lessons = sorted([json.loads(l) for l in open('memory.jsonl')], key=lambda x: -x['used'])
for l in lessons[:10]:
    print(f\"{l['used']:3d}× {l['trigger'][:80]}\")
"

# Lessons matching a tag
python3 -c "
import json
for line in open('memory.jsonl'):
    l = json.loads(line)
    if 'flask' in l.get('tags', []):
        print(l['trigger'][:80])
"

See also

Clone this wiki locally