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.
~/Projects/AgentRecall/agent-recall-mcp@3.4.18 (local, not published)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).
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.
<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
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
grep -r "cloudflare" ~/.agent-recall/projects/ finds every reference regardless of which layer wrote it.dashboard.json exposes a canonical view on top of legacy storage (we synthesize canonical entries from legacy filenames). No migration required.packages/core/src/naming.ts — 200 lines. Exports:
toSlug(input) — safe kebab-case, strips dots (no traversal), ≤40 chars.canonicalPath(name) — build path from CanonicalName struct.parseCanonicalName(path) — roundtrip, returns null on mismatch.buildIndexEntry(path, headline) — produce a NamingIndexEntry (dashboard schema).legacyToCanonicalType(path) — translator for in-place dashboard view.The V10 (formal taxonomy) finding: AgentRecall covered 3 of 4 canonical memory types and was missing procedural. Today we ship the 5th layer.
| Vantage | P | Fix | Where |
|---|---|---|---|
| V9 Feedback loops | P0 | CorrectionOutcome events + per-correction precision + KPI aggregation | storage/corrections.ts |
| V6 Long-horizon context | P0 | session_start mode: "lite" — ≤500 tokens, pull-on-demand | tools-logic/session-start-lite.ts |
| V8 Visualization | P0 | Agent-readable dashboard.json + dashboard_export MCP tool | tools-logic/dashboard-export.ts |
| V8 Visualization | P0 | Canonical naming index (synthesized from legacy paths) | naming.ts + dashboard |
| V10 Memory taxonomy | P1 | Procedural layer palace/skills/ with IF-THEN schema | palace/skills.ts + 3 MCP tools |
| V5 Decay / FSRS | P1 | FSRS-lite scorer (Retrievability + Stability + reinforce/penalize) | palace/fsrs.ts |
| V2 LLM agent papers | P1 | Reflection bundle — Park-2023 style aggregation prompt | tools-logic/session-end-reflect.ts |
| V1 Cognitive science | P1 | FSRS gives the reconsolidation primitive (reinforce on recall) | via fsrs.ts wiring |
| V1 Security (pre-existing carry-over) | P0 | corrections.ts sanitizer hardened (no dot in slugs, sep-prefix root check) | storage/corrections.ts |
| V3 Production memory | P2 | Naming index = the "moat artifact" Mem0/Letta/Zep don't have | dashboard.json schema |
| V7 Multi-agent | P2 | Atomic writes (tmp+rename) + symlink guard on all new write paths | palace/skills.ts + carry-over from pipeline |
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:
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;
}
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
}
recurrence_count > 0 after promotion to awareness, the system failed and the insight needs reformulation — not re-display. Without this, "AgentRecall learns" is unfalsifiable.
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.
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
}
| Tool | Purpose |
|---|---|
skill_write | Save an IF-THEN production rule with trigger keywords |
skill_recall | Find skills matching an intent (deterministic trigger-keyword ranking — no LLM) |
skill_list | Browse all skills in a project |
git rm --cached ≠ history rewrite. From prismma-gateway Phase E.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.
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.
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.
skill_write or session_end. Matches AgentRecall's "agent-as-author" stance.
pipeline_open/pipeline_close fires syncToSupabase(filePath, content, slug, "palace", "pipeline") (added in earlier pass).syncToSupabase(filePath, content, slug, "palace", "skills") at end of writeSkill. ~2 lines.corrections.json on every write and outcome event. Surfaces precision/heeded/recurrence to the dashboard's remote backend so trends are visible across projects.dashboard_export so the web dashboard can pull it without local fs access.ar_naming_index table (one row per entry) for FTS + filtered queries by type/topic/temporal.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)
);
A code-reviewer subagent ran against the loop-1 diff. Findings + fixes applied in the same session:
| Severity | Finding | Fix 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.
| Item | P | Reason |
|---|---|---|
| V7 LangGraph reducer + version vector for shared writes | P2 | Multi-agent CRDT design needs a real spec week, not a day. Existing mkdir locks survive AAM at ≤ 5 workers. |
Wire FSRS into recall reinforcement | P1 | Primitive shipped; wiring requires touching recall-backend hot path. Do in a follow-up isolated PR. |
| Cytoscape interactive graph card in dashboard.html | P1 | Existing 4884-line dashboard.html requires careful surgery — better as a focused next session. |
| Correction Timeline + Promotion Funnel UI card | P2 | Data is now there (via KPIs). UI can land standalone. |
| Roam-style block references / transclusion | P3 | Architectural change — defer until lite mode + skills usage data justifies it. |
| Surprise-score reconsolidation (V1) | P3 | Needs check() outcome wiring to compute surprise — chain after V9 outcomes accumulate. |
# 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'
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"}).
recall is one line in a follow-up.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.