Tutorial — Skipper's Long-Term Memory¶
This tutorial walks through Skipper's cross-session memory (Phase 25): how to enable it, teach the agent, verify what it learned, and administer/erase it. Everything runs self-hosted and offline — no cloud embeddings.
Background:
docs/guides/agent.md(Long-Term Memory), ADR 0033 (architecture), ADR 0034 (governance).
1. What memory is for¶
Skipper already remembers each conversation (the LangGraph checkpointer). Long-term memory is different — it persists across sessions and stores the agent's experience:
| Kind | Namespace | Example |
|---|---|---|
| Procedural | proc |
"safe-promote: validate → drift check → canary 10% → promote" |
| Episodic | episode |
"JPCP drift went CRITICAL — cause was a PM100 schema change; fixed by rebaseline" |
| Preference | pref |
"alice always uses 5% canary" |
| KB | kb |
"operators call mbwidth memory bandwidth" |
Memory holds experience + preferences + stable facts only. Current platform state (versions, drift values, cost, approvals) is always queried live — never remembered — because it changes constantly and the platform DB is the source of truth.
2. Enable it¶
Memory is on by default and additive. You only need an embedding backend:
# Option A — local Ollama (default). Start the tunnel and pull the model once:
ollama-tunnel start # or a local `ollama serve`
ollama pull nomic-embed-text
# Option B — fully offline, in-process (no Ollama needed):
export AGENT_EMBED_BACKEND=sentence-transformers
export AGENT_EMBED_MODEL=all-MiniLM-L6-v2
export AGENT_EMBED_DIMS=384 # MUST match the model
If neither is available, the agent still runs — with short-term memory only (it logs a
warning). Start Skipper as usual: make skipper.
3. Teach it¶
In a chat session:
you> Remember that I always want a 5% canary before any promotion.
skipper> Noted preference for alice: canary percentage = 5%
After a successful multi-step operation, let Skipper capture it as a procedure. Because a procedure write is confirmation-gated, you'll be asked to approve:
you> That promotion workflow worked well — save it as a procedure.
skipper> [confirm] record_procedure: Save a reusable procedure for 'safe-promote' (4 steps)
Proceed? [y/N] y
skipper> Recorded procedure for 'safe-promote' (4 steps).
4. See it recalled¶
In a new session, Skipper recalls what it learned before planning:
you> Promote MACK to production.
skipper> (recall_memory → found procedure 'safe-promote' + preference canary=5%)
I'll follow your usual safe-promote flow with a 5% canary:
1. validate_model_serving MACK 2. get_drift_status MACK
3. serve traffic 5% canary 4. promote_model MACK
Shall I start with the validation step?
Note: Skipper still calls the live tools to check MACK's current state — memory guides the plan, the platform DB decides the facts.
5. Administer & erase (governance)¶
Every memory write is audited:
Enumerate, export, and erase memory (deletions cascade and are audited; the audit log itself is a separate store and is preserved):
exa agent memory stats # authenticated, remote counts per kind
exa agent memory list proc # list procedures
exa agent memory export --out memory-backup.json # GDPR export
exa agent memory delete pref # erase owned preferences (audited)
exa agent memory review list # owned pending procedures
exa agent memory review approve 42 # approve and audit one proposal
These commands use AGENT_URL and the configured bearer token. The server derives the principal
and tenant from that verified credential, so one caller cannot enumerate or mutate another
caller's memory. Use --local only for offline recovery of a legacy AGENT_MEMORY_DB, for example
exa agent memory stats --local; local mode has no authenticated principal boundary. Export files
can contain personal data and should be protected accordingly.
6. Safety notes¶
- Poisoned memory can't cause an unsafe action. Memory tools only store/recall data; every dangerous tool (promote, retrain, restart, …) stays confirmation-gated regardless of memory content. A red-team test enforces this invariant.
- Turn it off anytime:
AGENT_MEMORY_ENABLED=false(short-term memory only), orAGENT_MEMORY_REQUIRE_CONFIRM=falseto let procedures save without a prompt (not recommended in shared/regulated environments).
7. Evaluating whether memory helps¶
Memory is evaluated on task success, not conversational recall: run a fixed
ops-scenario suite with memory on vs off and compare success rate, tool-call count,
clarifying questions, tokens, and latency (skipper/memory_eval.py holds the scenario
suite + the safety invariant; the full memory-on/off run needs a live LLM).
8. Beyond the four kinds — the full 7-tier stack¶
The procedural / episodic / preference / KB kinds above are tier T1. The next-gen memory architecture (ADRs 0101/0104/0105/0106) adds:
- T2 Knowledge / docs-RAG — semantic search over the documentation (
search_knowledge); build the index withmake skipper-knowledge-ingest. Check its exit code: the tier needs a reachable embedding backend, and an ingest that indexed nothing exits1and names what was missing. Exit0with no files means only that you switched the tier off deliberately. - T3 Monitoring / baseline — recall "what's normal" for a model (
recall_baseline), auto-fed by theskipper-watchdaemon (make skipper-watch). - T4 Outcome — the reactive loop records tool telemetry so
tool_success_rateis real. - T5 Consolidation —
make skipper-consolidatepromotes recurring incidents into review-gated candidate procedures and deprecates procedures that rely on failing tools. - X Tenant scoping —
AGENT_MEMORY_TENANT_SCOPED=trueisolates memory per project (opt-in).
See docs/guides/agent.md → Next-gen architecture for the full table.