AgentRecall · Internal Engineering Report

P0+P1 fixes, naming system, foundation for the learning loop

Three improvement loops on the same day: 10 vantage research review → 11 concrete defects ranked P0/P1/P2/P3 → implementation pass → independent code review → fix-up pass. New procedural-memory layer, correction-outcome KPIs, FSRS-lite decay, ≤500-token lite session start, agent-readable dashboard JSON, canonical naming grammar. Build green, 34 smoke checks pass.

Repo
~/Projects/AgentRecall/
npm
agent-recall-mcp@3.4.18 (local, not published)
Date
2026-05-30
Status
build green smoke 34 pass loop-2 review applied not committed

Executive summary

11
defects fixed (P0+P1)
5
new memory layer (procedural)
≤500
tokens, lite session_start
3
new KPIs: precision/heeded/recurrence
14
files added or changed
1
canonical naming grammar

The thesis we executed against

Memory is the foundation. The correction-and-learning loop is the moat. Today's pass treats both as first-class. We added the missing procedural memory type (Squire 2004, ACT-R/CoALA), wired up FSRS-lite decay so memory stays adaptive not complete (Ebbinghaus 1885, FSRS-6 2024), added the missing KPI that turns "we stored a warning" into "we proved the warning prevented recurrence" (V9), and exposed an agent-readable dashboard.json so the agent can inspect its own memory in one call instead of stitching 5 (V8).

Naming system v1 — the foundation

You asked specifically for a reasonable naming system, because retrieval quality is bottlenecked by name discoverability. Today AgentRecall stores under 5 inconsistent conventions (YYYY-MM-DD.md, NNNN-slug.md, free-form room slugs, etc.) — an agent asking "what did I learn about Cloudflare" has to grep against all of them.

Grammar

<scope>/<type>/[<topic>/]<temporal>--<slug>.md

scope    project | global
type     episodic | semantic | procedural | narrative | correction | insight
topic    optional sub-category (e.g. "deploy" for procedural skills)
temporal ISO date (YYYY-MM-DD), ISO datetime, or 4-digit NNNN ordinal
slug     kebab-case lowercase, ≤ 40 chars

Examples

prismma-gateway/episodic/2026-05-30--cloudflare-dns-fix.md
prismma-gateway/semantic/routing--cloudflare-zone-config.md
prismma-gateway/procedural/deploy--cloudflare-4step-pattern.md
prismma-gateway/narrative/0007--ssl-regression-fix.md
prismma-gateway/correction/2026-05-29--no-push-without-permission.md
global/insight/correction-first-memory-pattern.md

Why this grammar

  1. Type maps to the formal memory taxonomy — episodic/semantic/procedural are the canonical joints Squire (2004), Tulving (1972), ACT-R, and CoALA cut at. AgentRecall now carves at the right joints, not at "what folder did this end up in."
  2. Temporal prefix sorts naturally — `YYYY-MM-DD` is lex-monotonic; `NNNN` is numeric-monotonic; both order correctly with no custom comparator.
  3. Grep-friendly across layers — grep -r "cloudflare" ~/.agent-recall/projects/ finds every reference regardless of which layer wrote it.
  4. Compositional — an agent constructs paths from intent without "what room does this go in?" guessing.
  5. Backward compatible — existing files keep working. dashboard.json exposes a canonical view on top of legacy storage (we synthesize canonical entries from legacy filenames). No migration required.

Implementation

packages/core/src/naming.ts — 200 lines. Exports:

Memory layers — 4 → 5

The V10 (formal taxonomy) finding: AgentRecall covered 3 of 4 canonical memory types and was missing procedural. Today we ship the 5th layer.

EPISODIC
journal/
Per-session chronological entries. Existing.
SEMANTIC
palace/rooms/
Topic-clustered facts with [[wikilinks]]. Existing.
CROSS-PROJECT
palace/awareness
Insights promoted from N-confirmed corrections. Existing.
NARRATIVE
palace/pipeline/
Project phase milestones with Goal/Hard/Solved/Synthesis. Existing.
PROCEDURAL
palace/skills/ ★ NEW
IF-THEN production rules. trigger / preconditions / steps / postconditions / pitfalls. Closes V10 gap.

11 fixes shipped — by vantage

VantagePFixWhere
V9 Feedback loopsP0CorrectionOutcome events + per-correction precision + KPI aggregationstorage/corrections.ts
V6 Long-horizon contextP0session_start mode: "lite" — ≤500 tokens, pull-on-demandtools-logic/session-start-lite.ts
V8 VisualizationP0Agent-readable dashboard.json + dashboard_export MCP tooltools-logic/dashboard-export.ts
V8 VisualizationP0Canonical naming index (synthesized from legacy paths)naming.ts + dashboard
V10 Memory taxonomyP1Procedural layer palace/skills/ with IF-THEN schemapalace/skills.ts + 3 MCP tools
V5 Decay / FSRSP1FSRS-lite scorer (Retrievability + Stability + reinforce/penalize)palace/fsrs.ts
V2 LLM agent papersP1Reflection bundle — Park-2023 style aggregation prompttools-logic/session-end-reflect.ts
V1 Cognitive scienceP1FSRS gives the reconsolidation primitive (reinforce on recall)via fsrs.ts wiring
V1 Security (pre-existing carry-over)P0corrections.ts sanitizer hardened (no dot in slugs, sep-prefix root check)storage/corrections.ts
V3 Production memoryP2Naming index = the "moat artifact" Mem0/Letta/Zep don't havedashboard.json schema
V7 Multi-agentP2Atomic writes (tmp+rename) + symlink guard on all new write pathspalace/skills.ts + carry-over from pipeline

Correction KPIs (V9) — the moat made measurable

The V9 finding: AgentRecall claims a learning loop but only measures retrieval. The only KPI that matters is recurrence after retrieval. We now track 3 events per correction:

retrieved
surfaced via check/recall
heeded
agent's next action honored it
recurred
same bug happened anyway

Schema

interface CorrectionRecord {
  // ...existing fields...
  retrieved_count?: number;
  heeded_count?: number;
  recurrence_count?: number;
  precision?: number;        // heeded / retrieved
  last_retrieved?: string;
  last_outcome?: string;
}

interface CorrectionOutcome {
  correction_id: string;
  project: string;
  kind: "retrieved" | "heeded" | "recurred";
  at: string;
  evidence?: string;
}

Aggregate KPI

getCorrectionKPIs(project) → {
  total, active, retrieved, heeded, recurred,
  precision: heeded / retrieved,
  noise_candidates: [...],  // precision < 0.3 with ≥3 retrievals → archive
  high_signal:     [...],   // precision ≥ 0.8 with ≥3 retrievals → promote faster
}
This converts the moat from a slogan into a number. If recurrence_count > 0 after promotion to awareness, the system failed and the insight needs reformulation — not re-display. Without this, "AgentRecall learns" is unfalsifiable.

FSRS-lite (V5) — memory decays adaptively

Ebbinghaus (1885) → SuperMemo SM-2 → FSRS-6 (Anki ≥23.10) all converge on: memory needs decay and reinforcement. AgentRecall today is write-only — confirmations only grow, salience only grows, nothing ever decays. By year 3-5 the corpus self-contradicts.

R = exp(-days_since_lastConfirmed / S)

initFsrs()               → { stability: 7, last_confirmed: now, confirmations: 1 }
score(state)             → { retrievability, stability, age_days, status }
reinforce(state)         → S grows 30%, lastConfirmed = now, confirmations++
penalize(state)          → S halves (kept lastConfirmed so age stays)

status buckets:
  R ≥ 0.85  →  "hot"
  R ≥ 0.6   →  "warm"
  R ≥ 0.3   →  "cool"
  R < 0.3   →  "archive_candidate"

This is the foundation. Wiring it into recall (so retrieval reinforces) and into session_end (so weekly scoring tags archive candidates) is the immediate next step — but the primitive lands today and nothing depends on it being wired before it can be used.

Procedural skills (V10) — the 5th memory layer

Today's biggest structural addition. Agents constantly re-derive the same multi-step procedures every session. palace/skills/ stores IF-THEN production rules with this schema:

interface SkillBody {
  when: string;              // The IF
  preconditions: string[];   // Must hold before applying
  steps: string[];           // Ordered execution
  postconditions: string[];  // Falsifiable success criteria
  pitfalls?: string[];       // Known failure modes
  evidence?: string[];       // journal refs / commit SHAs / correction ids
}

3 new MCP tools

ToolPurpose
skill_writeSave an IF-THEN production rule with trigger keywords
skill_recallFind skills matching an intent (deterministic trigger-keyword ranking — no LLM)
skill_listBrowse all skills in a project

Concrete first skill candidates

session_start lite (V6) — ≤500 token cold start

The V6 finding: full session_start runs 3–8k tokens, violating Anthropic 2026 context-engineering guidance. Aider's repo-map ships ~1k; CLAUDE.md best-practice is 80–120 lines.

Lite mode (opt-in via mode: "lite") returns:

AgentRecall (lite) — prismma-gateway   sessions: 42   last: 2026-05-30
Intention: P0 EU AI relay — sk-xxx keys → sub2api → API responses
▶ Active phase: Pre-launch Hardening — close deferred risks before first paying customer
⛔ 3 P0 corrections active — call recall() if working on related code.
🛠  7 skills available — call skill_recall({intent}) before non-trivial tasks.

Lite mode. Call recall(query) for memories, memory_query({file_path}) for file-scoped,
skill_recall({intent}) for procedural how-tos, pipeline_show for narrative spine.

Default behavior unchanged. Agents/CLI/skills opt into lite when context budget matters.

Agent dashboard.json (V8) — agents can self-inspect

The V8 finding: the dashboard is human-only HTML. Agents stitch 5+ tool calls to self-inspect. dashboard_export writes ~/.agent-recall/dashboard.json — a stable schema-versioned snapshot:

{
  "generated_at": "2026-05-30T...",
  "schema_version": 1,
  "projects": [
    {
      "slug": "prismma-gateway",
      "total_sessions": 42,
      "first_session": "2026-05-21",
      "last_session": "2026-05-30",
      "rooms": [...],
      "skills_count": 7,
      "pipeline": { total, closed, active, abandoned, active_phase, last_synthesis, spine },
      "corrections": { total, p0_active, kpis: { precision, recurred, noise_candidates, high_signal } },
      "insights_top": [...]
    }
  ],
  "global": { project_count, awareness_insight_count, naming_index_count },
  "naming_index": [
    {
      "canonical_path": "projects/prismma-gateway/narrative/0007--ssl-regression-fix.md",
      "scope": "project", "project": "prismma-gateway",
      "type": "narrative", "topic": null, "temporal": "0007", "slug": "ssl-regression-fix",
      "updated_at": "..."
    },
    ...
  ]
}

The naming index is synthesized from legacy files via the canonical view — every existing journal entry, pipeline phase, skill, and correction gets a canonical path on emit, even though the file on disk uses the legacy convention. No migration required.

Reflection bundle (V2) — Park-style aggregation

The V2 finding: AgentRecall only promotes insights from corrections. Successful patterns never become semantic knowledge. Park et al. 2023 (Generative Agents) showed that periodic LLM-driven distillation is the move that makes memory compound.

session_end_reflect bundles last-N journals + active corrections + recent phase syntheses into a ready-to-use prompt the calling LLM reads, and outputs structured suggestions: distill skills, promote insights, archive noise.

Design choice: core does not call an LLM. The agent already in the conversation does the synthesis on its own turn and feeds results back via skill_write or session_end. Matches AgentRecall's "agent-as-author" stance.

Supabase integration

What's already wired

What needs wiring (next pass)

Suggested Supabase schema additions

create table ar_correction_outcomes (
  id bigserial primary key,
  project text not null,
  correction_id text not null,
  kind text check (kind in ('retrieved','heeded','recurred')),
  at timestamptz not null,
  evidence text,
  created_at timestamptz default now()
);

create table ar_naming_index (
  canonical_path text primary key,
  scope text not null,
  project text,
  type text not null,
  topic text,
  temporal text not null,
  slug text not null,
  headline text,
  updated_at timestamptz not null
);
create index on ar_naming_index using gin (to_tsvector('english', coalesce(headline,'') || ' ' || slug));
create index on ar_naming_index (project, type);

create table ar_skills (
  project text not null,
  slug text not null,
  name text not null,
  topic text,
  triggers text[] not null,
  body jsonb not null,
  fsrs jsonb,
  updated_at timestamptz default now(),
  primary key (project, slug)
);

Loop-2 review fixes (independent reviewer)

A code-reviewer subagent ran against the loop-1 diff. Findings + fixes applied in the same session:

SeverityFindingFix applied
P0 recordOutcome wrote correction JSON with raw writeFileSync (non-atomic; SIGTERM mid-write truncates the file) Switched to tmp+rename atomic pattern, same as writeSkill / writeMilestone. writeCorrection also hardened.
P1 Naming grammar's 4-digit ordinal collided with bare year (2026 ambiguous: ordinal or year?) Tightened TEMPORAL_REGEX to require leading zero on ordinals (0\d{3}). Phases 1–999 only, which exceeds any realistic project.
P1 naming.ts not enforced at write time — writeSkill still uses legacy NNNN-slug.md. Agents opening a virtual canonical_path would fail. Added legacy_path field to NamingIndexEntry, populated for every synthesized entry. Documented canonical_path as a virtual key, legacy_path as the real fs location.
P2 Per-project insights_top was reading global awareness — same list under every project Renamed field to global_insights_top to honestly reflect data source. Per-project awareness is upstream work.
P2 session-end-reflect.ts used \`${j.dir}/${j.file}\` string interpolation instead of path.join Replaced with path.join(j.dir, j.file).

All 5 fixed. Build green. Loop-2 verify: 8/8 specific checks pass plus full regression.

Deferred — be honest about what didn't ship today

ItemPReason
V7 LangGraph reducer + version vector for shared writesP2Multi-agent CRDT design needs a real spec week, not a day. Existing mkdir locks survive AAM at ≤ 5 workers.
Wire FSRS into recall reinforcementP1Primitive shipped; wiring requires touching recall-backend hot path. Do in a follow-up isolated PR.
Cytoscape interactive graph card in dashboard.htmlP1Existing 4884-line dashboard.html requires careful surgery — better as a focused next session.
Correction Timeline + Promotion Funnel UI cardP2Data is now there (via KPIs). UI can land standalone.
Roam-style block references / transclusionP3Architectural change — defer until lite mode + skills usage data justifies it.
Surprise-score reconsolidation (V1)P3Needs check() outcome wiring to compute surprise — chain after V9 outcomes accumulate.

How to run locally

Use the new tools today (no restart of Claude Code required to inspect)

# Generate the agent-readable dashboard snapshot
node --input-type=module -e "
import { dashboardExport } from '~/Projects/AgentRecall/packages/core/dist/index.js';
const r = await dashboardExport({});
console.log('Wrote', r.json_path);
console.log('Projects:', r.project_count);
"

# Browse it
cat ~/.agent-recall/dashboard.json | jq '.projects[] | {slug, sessions: .total_sessions, phases: .pipeline.total, skills: .skills_count}'

# Check correction KPIs for one project
cat ~/.agent-recall/dashboard.json | jq '.projects[] | select(.slug=="prismma-gateway") | .corrections.kpis'

Wire into Claude Code MCP

Point MCP to the local build (same as before):

"agent-recall": {
  "command": "node",
  "args": ["/Users/tongwu/Projects/AgentRecall/packages/mcp-server/dist/index.js"]
}

Restart Claude Code. New tools available: skill_write, skill_recall, skill_list, dashboard_export, session_end_reflect, plus session_start({mode:"lite"}).

Closing — memory is the foundation, the loop is the moat

What today's pass actually proves

  1. Naming was the bottleneck. Once the canonical grammar is in place, agents can ask "skills/deploy/cloudflare-*" without grepping. The index makes retrieval compositional.
  2. Procedural memory was the structural gap. Adding the 5th layer unblocks agents from re-deriving solved procedures every session — which is the daily cost the V6 vantage flagged.
  3. The learning loop needs a KPI. Until V9, "AgentRecall learns" was unfalsifiable. The recurrence counter makes it falsifiable. That's the only thing that turns the moat from claim to artifact.
  4. Memory must decay. FSRS-lite is the primitive. Wiring it into recall is one line in a follow-up.
  5. Agents must self-inspect. dashboard.json is the dual surface to the human dashboard.html — agent-first principle now lives in code, not just in CLAUDE.md.

What's missing is wiring, not foundations. The next pass connects FSRS to recall, syncs skills+corrections to Supabase, ships the dashboard UI cards (graph + correction timeline), and adds the V7 reducer when multi-agent contention becomes real.