Recall — see it all working

14 short screencasts. Every clip is a real run: the actual recall CLI, real output, an isolated graph. Each card has the inline video, the full script, and the unedited captured output.

getting started

Install in one command 01-install

`recall claude sync` / `recall codex sync` wires the skill, the MCP server, and the consult-Recall hook, and turns off the host's native note-memory — your agent is armed.

script — screencasts/01-install.sh
#!/usr/bin/env bash
# Screencast 01 — "Install Recall into Claude Code in one command."
# SAFE: runs `claude sync` against a throwaway HOME, so your real ~/.claude is never
# touched. Captures the real output to a transcript, then renders an mp4.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"; source "$HERE/lib/present.sh"
REPO="${RECALL_REPO:-$HOME/Recall-GitHub-Clean}"
CLI="node --disable-warning=ExperimentalWarning $REPO/dist/src/cli.js"
[ -f "$REPO/dist/src/cli.js" ] || { echo "build the repo first: (cd $REPO && npm run build)"; exit 1; }
mkdir -p "$HERE/transcripts" "$HERE/out"
T="$HERE/transcripts/01-install.txt"
SBX="$(mktemp -d)"

{
  title "Install Recall into Claude Code — one command" "the whole integration, wired and armed"
  scene "before"
  narrate "A fresh Claude Code config knows nothing about Recall."
  cmd "recall claude status"
  HOME="$SBX" $CLI claude status 2>/dev/null | out

  scene "one command"
  narrate "Installs the recall skill, registers the MCP server, adds the consult-Recall"
  narrate "hook, and turns OFF Claude's native note-memory so Recall is THE memory layer."
  cmd "recall claude sync"
  HOME="$SBX" $CLI claude sync 2>/dev/null | grep -iE 'installed|registered|updated|disabled|notice|auto-memory' | head -8 | out

  scene "after"
  cmd "recall claude status"
  HOME="$SBX" $CLI claude status 2>/dev/null | out
  narrate "hook ✓  skill ✓  MCP ✓  native auto-memory OFF.  Recall is armed — restart Claude Code and go."
  note "Codex users: same idea — \`recall codex sync\` (skill + MCP in config.toml + AGENTS.md directive)."
  endbox
  printf '\n✦ DONE\n'
} > "$T" 2>&1
rm -rf "$SBX"
echo "captured $(wc -l < "$T") lines -> $T"
bash "$HERE/lib/render.sh" "$T" "$HERE/out/01-install.mp4" 0.55 16 1440 900
captured output (unedited real run)
  Install Recall into Claude Code — one command
  the whole integration, wired and armed

┌─ before ─────────────────────────────────────────
│ ▸ A fresh Claude Code config knows nothing about Recall.
│ $ recall claude status
│   {
│     "claudeHome": "/var/folders/6p/0xjck73s3csczm0pqmqwxsr80000gn/T/tmp.UFDZZYzGtj/.claude",
│     "hookInstalled": false,
│     "skillInstalled": false,
│     "mcpRegistered": false,
│     "autoMemoryDisabled": false,
│     "settingsPath": "/var/folders/6p/0xjck73s3csczm0pqmqwxsr80000gn/T/tmp.UFDZZYzGtj/.claude/settings.json"
│   }

┌─ one command ─────────────────────────────────────────
│ ▸ Installs the recall skill, registers the MCP server, adds the consult-Recall
│ ▸ hook, and turns OFF Claude's native note-memory so Recall is THE memory layer.
│ $ recall claude sync
│     "mcpRegistered": true,
│     "autoMemoryDisabled": true,
│       "hook installed: /var/folders/6p/0xjck73s3csczm0pqmqwxsr80000gn/T/tmp.UFDZZYzGtj/.claude/hooks/recall-session-start.py",
│       "skill installed: /var/folders/6p/0xjck73s3csczm0pqmqwxsr80000gn/T/tmp.UFDZZYzGtj/.claude/skills/recall",
│       "mcp registered: /var/folders/6p/0xjck73s3csczm0pqmqwxsr80000gn/T/tmp.UFDZZYzGtj/.claude.json",
│       "settings.json updated (hooks.SessionStart, hooks.UserPromptSubmit, env.CLAUDE_CODE_DISABLE_AUTO_MEMORY)"
│     "notice": "Claude Code built-in auto-memory DISABLED (CLAUDE_CODE_DISABLE_AUTO_MEMORY=1) so agents adopt Recall instead of native .md memory. Revert anytime: `recall claude enable-auto-memory`. Skip during install with RECALL_KEEP_AUTOMEMORY=1."

┌─ after ─────────────────────────────────────────
│ $ recall claude status
│   {
│     "claudeHome": "/var/folders/6p/0xjck73s3csczm0pqmqwxsr80000gn/T/tmp.UFDZZYzGtj/.claude",
│     "hookInstalled": true,
│     "skillInstalled": true,
│     "mcpRegistered": true,
│     "autoMemoryDisabled": true,
│     "settingsPath": "/var/folders/6p/0xjck73s3csczm0pqmqwxsr80000gn/T/tmp.UFDZZYzGtj/.claude/settings.json"
│   }
│ ▸ hook ✓  skill ✓  MCP ✓  native auto-memory OFF.  Recall is armed — restart Claude Code and go.
│ Codex users: same idea — `recall codex sync` (skill + MCP in config.toml + AGENTS.md directive).
└──────────────────────────────────────────────────────────────

✦ DONE

Durable memory across sessions — autonomously 02-supersession

Three separate `claude` sessions, natural prompts, nobody says "save": the armed agent persists a decision, supersedes it on a correction, and a cold session recalls the current answer + the why-we-changed history.

script — screencasts/02-supersession.sh
#!/usr/bin/env bash
# Screencast 02 — "Memory that stays correct across sessions" (the autonomous demo).
# Reuses the repo's real demo (three live `claude -p` sessions on natural prompts;
# the agent persists, supersedes, and recalls on its own). Captures the real output,
# verifies the isolation tripwire, then renders an mp4.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
REPO="${RECALL_REPO:-$HOME/Recall-GitHub-Clean}"
DEMO="$REPO/scripts/demo-claude-recall.sh"
[ -f "$DEMO" ] || { echo "demo not found: $DEMO"; exit 1; }
mkdir -p "$HERE/transcripts" "$HERE/out"
T="$HERE/transcripts/02-supersession.txt"

echo "capturing the real demo (3 live claude sessions, isolated graph)… this takes a few minutes"
DEMO_PACE=0 bash "$DEMO" > "$T" 2>&1

# isolation gate — never publish a clip that touched the real graph
if grep -q "global graph untouched" "$T"; then echo "✓ tripwire: real graph untouched"
else echo "✗ WARNING: tripwire line missing — DO NOT publish; check isolation"; fi
# sanity — the supersession edge must actually be present
grep -qi "contradicts" "$T" && echo "✓ contradicts edge present" || echo "✗ no contradicts edge — re-run before publishing"

bash "$HERE/lib/render.sh" "$T" "$HERE/out/02-supersession.mp4" 0.30 15 1440 960
captured output (unedited real run)
┌─ MONDAY · sprint planning ─────────────────────────────────────────
│ ▸ A decision gets made in conversation. You never say "save this" —
│ ▸ the armed agent persists it to Recall on its own.
│ 🧑 you → We're finalizing the job-queue architecture — let's go with Redis Streams, it's simple and already in our stack.
│ claude │   Logged the decision to Recall (no prior job-queue decision existed, so nothing to supersede). Redis Streams is a sound call — keeping infra surface flat by reusing what's already deployed is usually worth more than a "purpose-built" broker.
│   
│   `★ Insight ─────────────────────────────────────`
│   - Redis Streams gives you the two primitives a job queue needs — **consumer groups** (competing consumers, load-balanced) and **explicit `XACK`** (at-least-once delivery). That's the part you get for free.
│   - What it does *not* give you, and what every Streams-based queue ends up hand-rolling: a **reclaim path** for messages a worker took but never acked (it crashed mid-job). Those sit in the Pending Entries List forever until you `XAUTOCLAIM` them back. Without that, a crash = a silently lost job.
│   - There's no built-in **dead-letter** concept either — you track delivery count per message and route to a DLQ stream past a threshold, otherwise a poison message redelivers in a hot loop.
│   `─────────────────────────────────────────────────`
│   
│   So the architecture is settled; the part still worth nailing down is the **failure semantics**, not the backend. Two questions decide most of the implementation:
│   
│   1. **Visibility/reclaim** — how long can a message be pending before another worker may steal it? (the `min-idle-time` you pass to `XAUTOCLAIM`)
│   2. **Poison handling** — max deliveries before a job goes to the DLQ, and who drains it.
│   
│   If you point me at where the queue lives in the codebase (or tell me there's no code yet), I can scaffold the producer/consumer-group setup and leave the reclaim-and-DLQ policy as the decision-shaped piece for you to define — that's the spot where your domain knowledge about retry-ability actually matters. Want to go there next?
│ ▸ Unprompted, it wrote a structured decision cell:
│   - Job-queue backend = Redis Streams: Finalized the job-queue architecture on Redis Streams. Rationale: already in the stack (no new infra/ops surface) and simple to adopt. Implication to track: Redis Streams provides consumer groups... [decision:e6731fe5-e116-49c0-b932-8db0bcc7b858]
│   - decision:e6731fe5-e116-49c0-b932-8db0bcc7b858; title=Job-queue backend = Redis Streams; state=active/conf:0.7/eff:0.7/unc:0.1/concern:0.15/stability:stable/source:high; facets=topics:architecture,job-queue|entities:Redis Streams,Redis|lifecycle:decision,finalized; rel=in:0,out:0; policy=expires:2027-06-17T00:00:00Z,reverify:2026-12-17T00:00:00Z,sensitivity:private
│   - Job-queue backend = Redis Streams: low-source or unverified memory; severity=0.45 [stale:e6731fe5-e116-49c0-b932-8db0bcc7b858]
│   - Reverify or archive 1 stale/low-trust memory cell(s), starting with Job-queue backend = Redis Streams.

┌─ WEDNESDAY · load-test post-mortem  (a different session, days later) ─────────────────────────────────────────
│ ▸ A separate session hears the correction. On its own it finds the prior
│ ▸ decision and SUPERSEDES it — a contradicts edge, not an overwrite.
│ 🧑 you → Load test failed — Redis Streams can't keep up at peak. We're switching the job queue to Kafka.
│ claude │   Recorded. The new decision (`187020d3`) supersedes the prior "Redis Streams" decision (`e6731fe5`) — Recall logged a `contradicts` edge, so the old choice is demoted and flagged rather than left competing.
│   
│   `★ Insight ─────────────────────────────────────`
│   - **Supersede, don't overwrite.** The old Redis Streams cell still exists with full rationale — but it's now linked as contradicted. That preserves *why* you originally picked Redis (already in-stack, simple) so the reversal is auditable, not erased.
│   - **The rationale inverts.** Redis won on "no new infra." Kafka loses on exactly that axis (brokers, partitions, KRaft/ZooKeeper to operate) but wins on sustained peak throughput + replay — which is the constraint that just broke you.
│   - **Semantics you're inheriting:** Redis's XAUTOCLAIM/XCLAIM + manual DLQ pattern becomes Kafka's offset-commit + retry-topic/dead-letter-topic pattern, and at-least-once delivery now demands *idempotent consumers*.
│   `─────────────────────────────────────────────────`
│   
│   One thing worth capturing while it's fresh: **the actual peak numbers from the failing load test** (msgs/sec, payload size, consumer lag at failure). Those become your Kafka sizing target — partition count and broker capacity should be derived from them, not guessed.
│   
│   Want me to dig into the codebase and scope the actual migration (where the queue is produced/consumed, retry/DLQ touchpoints), or are you just logging the decision for now?
│ ▸ Kafka is now CURRENT, Redis SUPERSEDED — resolved at read time, history kept:
│   - Job-queue backend switched to Kafka (Redis Streams superseded): Switching the job-queue backend from Redis Streams to Kafka. Trigger: a load test failed -- Redis Streams could not keep up at peak throughput. This supersedes the prior... [decision:187020d3-fc94-4e57-b7cf-34a6fdb4a0d2]
│   - Job-queue backend = Redis Streams: Finalized the job-queue architecture on Redis Streams. Rationale: already in the stack (no new infra/ops surface) and simple to adopt. Implication to track: Redis Streams provides consumer groups... [decision:e6731fe5-e116-49c0-b932-8db0bcc7b858]
│   - Job-queue backend switched to Kafka (Redis Streams superseded) contradicts Job-queue backend = Redis Streams [contradicts:187020d3-fc94-4e57-b7cf-34a6fdb4a0d2->e6731fe5-e116-49c0-b932-8db0bcc7b858]
│   - decision:187020d3-fc94-4e57-b7cf-34a6fdb4a0d2; title=Job-queue backend switched to Kafka (Redis Streams superseded); state=active/conf:0.7/eff:0.7/unc:0.1/concern:0.1/stability:stable/source:high; facets=topics:architecture,job-queue|entities:Kafka,Redis Streams|lifecycle:decision,supersedes; rel=in:0,out:1; policy=expires:2027-06-17T00:00:00Z,reverify:2026-12-17T00:00:00Z,sensitivity:private

┌─ FRIDAY · a new engineer picks up a ticket  (fresh process, ZERO context) ─────────────────────────────────────────
│ ▸ Nobody re-explains Monday or Wednesday. A new session just asks —
│ ▸ and consults Recall on its own before answering.
│ 🧑 you → I'm adding a background job for invoice emails — which queue do I publish to, and any history I should know?
│ claude │   Recall has direct history on this. Here's what the durable memory says, in order:
│   
│   ## Which queue: **Kafka** (current decision)
│   
│   There are two recorded decisions, and the second supersedes the first:
│   
│   1. **`e6731fe5` — "Job-queue backend = Redis Streams"** *(superseded, now challenged)*
│      Originally finalized on Redis Streams because it was *already in the stack* (no new infra) and simple to adopt.
│   
│   2. **`187020d3` — "Job-queue backend switched to Kafka"** *(current, active)*
│      A **load test failed** — Redis Streams couldn't keep up at peak throughput — so the backend was switched to Kafka. This cell explicitly `contradicts` the Redis decision, so Recall has demoted the old one (its effective confidence dropped to ~0.34 and it's flagged `challenged`).
│   
│   **So: publish to Kafka, not Redis Streams.** If you find code or docs still pointing at Redis Streams for the job queue, that's the stale path — treat it as the thing to migrate, not the thing to follow.
│   
│   `★ Insight ─────────────────────────────────────`
│   - **Supersession, not deletion:** Recall didn't erase the Redis decision — it linked the Kafka one with a `contradicts` edge and let the old cell's confidence decay. You keep the *why-we-changed* trail, which is exactly the "history" you asked for.
│   - **The switch changes your delivery semantics:** on Kafka, retry/DLQ moves from Redis's `XAUTOCLAIM`/`XCLAIM` pending-message recovery to **retry topics + a dead-letter topic** (or bounded-retry consumers), and at-least-once delivery means your invoice-email consumer must be **idempotent** (dedupe on invoice id) so a redelivery doesn't double-send.
│   - **Ordering/partitioning is now yours to design:** Kafka gives partitioned ordering — keying messages by something like `customer_id` or `invoice_id` keeps per-entity ordering while still parallelizing.
│   `─────────────────────────────────────────────────`
│   
│   ## History you should know
│   - It was Redis Streams first → **a peak-throughput load test broke it** → moved to Kafka.
│   - The Kafka cell flags an open follow-up: **capture the actual peak load numbers from that failing test** to size partition count + broker capacity. Worth grabbing before you add a new high-volume producer like invoice emails.
│   
│   ## One gap
│   Recall records the **backend** (Kafka) but **not a specific topic name / naming convention** for invoice emails — that detail isn't in memory. Want me to grep the codebase for the existing Kafka producer/topic conventions so I can tell you the exact topic to publish to (and whether there's an established `*.emails` / `notifications.*` pattern to match)?
│ ▸ Kafka — the current decision — with the Redis history. Nobody repeated it.

┌─ the receipt ─────────────────────────────────────────
│ global graph untouched: before=332 after=332   the whole story lived in a throwaway db
└──────────────────────────────────────────────────────────────

✦ DEMO_COMPLETE

core memory & corrections

Supersession mechanics 03-supersede

A correction is a `contradicts` edge, not an overwrite: the superseded cell is demoted to a low effective confidence and flagged `challenged`, the current one stays high — resolved at read time, across a chain.

script — screencasts/03-supersede.sh
#!/usr/bin/env bash
# Screencast 03 — "Supersession: the old value is preserved-but-demoted, not overwritten."
# Real `recall` CLI against a FRESH ISOLATED graph. Admit v1, contradict with v2,
# compile to see the demotion, then extend to a 3-link chain. Tripwire asserts the
# user's real graph was never touched.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/lib/present.sh"
HELP=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# --- isolation: unique fresh db; never honor cwd routing -----------------------
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"; recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
trap 'rm -f "$DB" /tmp/sc3_*.json' EXIT

write(){ # kind title body conf topics [contradicts]
  local extra=""; [ -n "${6:-}" ] && extra="--contradicts $6"
  python3 "$HELP" --kind "$1" --title "$2" --body "$3" --confidence "$4" --topics "$5" $extra > /tmp/sc3_p.json 2>/dev/null
  recall admit --json /tmp/sc3_p.json --db "$DB" 2>/dev/null | jq -r '.node.id'
}

title "Supersession — change your mind without losing the past" "recall: the old value is preserved-but-demoted, resolved at read time"

scene "v1 — the first decision"
narrate "We record a decision. At write time it's the only answer, full strength."
cmd 'recall admit  # decision: "Cache TTL is 60s"  conf 0.80'
V1=$(write decision "Cache TTL is 60s" "Set the cache TTL to 60 seconds for the API response cache." 0.80 "cache,ttl")
recall compile "cache ttl" --db "$DB" 2>/dev/null \
  | grep '^- decision:' | grep -F "$V1" \
  | sed -E 's/^- decision:([0-9a-f]{8}).*title=/\1  /; s/(eff:[0-9.]+(\([a-z, -]+\))?).*/\1/; s/state=//' | out
note "one cell, no conflicts — eff (effective weight) tracks confidence."
endbox

scene "v2 — we change our mind (--contradicts v1)"
narrate "Later we learn 60s is too aggressive. We DON'T edit v1 — we admit a new"
narrate "cell that explicitly --contradicts it. Supersession is a graph EDGE."
cmd "recall admit --contradicts ${V1:0:8}…  # decision: \"Cache TTL is 300s\""
V2=$(write decision "Cache TTL is 300s" "Raise the cache TTL to 300 seconds after load testing showed staleness was acceptable." 0.85 "cache,ttl" "$V1")
recall compile "cache ttl" --db "$DB" 2>/dev/null \
  | grep -A1 '^conflicts:' | grep contradicts | sed -E 's/ \[contradicts.*//' | out
endbox

scene "read it back — the conflict resolves automatically"
narrate "compile() doesn't return both 60s and 300s as equals. It demotes the"
narrate "superseded cell: v1 drops to eff:0.29(challenged); v2 stays high."
cmd 'recall compile "cache ttl"   # cell_state section'
recall compile "cache ttl" --db "$DB" 2>/dev/null \
  | grep '^- decision:' | sed -E 's/^- decision:([0-9a-f]{8}).*title=/\1  /; s/(eff:[0-9.]+(\([a-z, -]+\))?).*/\1/; s/state=//' | out
note "v1 is still IN the graph — preserved, queryable, just down-weighted."
endbox

scene "extend to a chain — v3 --contradicts v2"
narrate "Decisions keep evolving. v3 supersedes v2, which already superseded v1."
cmd "recall admit --contradicts ${V2:0:8}…  # decision: \"Cache TTL is 600s\""
V3=$(write decision "Cache TTL is 600s" "Bump cache TTL to 600 seconds; CDN now absorbs origin load so longer staleness is fine." 0.90 "cache,ttl" "$V2")
recall compile "cache ttl" --db "$DB" 2>/dev/null \
  | grep '^- decision:' | sed -E 's/^- decision:([0-9a-f]{8}).*title=/\1  /; s/(eff:[0-9.]+(\([a-z, -]+\))?).*/\1/; s/state=//' | out
note "only the latest link is current. v1 and v2 both collapse to eff:0 —"
note "a 3-link chain, resolved at read time to exactly one live answer."
endbox

scene "isolated"
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
note "global graph untouched: ${G0:-?} -> ${G1:-?}  (demo ran in a throwaway db)"
endbox
printf '\n✦ DONE\n'
captured output (unedited real run)
  Supersession — change your mind without losing the past
  recall: the old value is preserved-but-demoted, resolved at read time

┌─ v1 — the first decision ─────────────────────────────────────────
│ ▸ We record a decision. At write time it's the only answer, full strength.
│ $ recall admit  # decision: "Cache TTL is 60s"  conf 0.80
│   c42f8dcf  Cache TTL is 60s; active/conf:0.7/eff:0.7
│ one cell, no conflicts — eff (effective weight) tracks confidence.
└──────────────────────────────────────────────────────────────

┌─ v2 — we change our mind (--contradicts v1) ─────────────────────────────────────────
│ ▸ Later we learn 60s is too aggressive. We DON'T edit v1 — we admit a new
│ ▸ cell that explicitly --contradicts it. Supersession is a graph EDGE.
│ $ recall admit --contradicts c42f8dcf…  # decision: "Cache TTL is 300s"
│   - Cache TTL is 300s contradicts Cache TTL is 60s
└──────────────────────────────────────────────────────────────

┌─ read it back — the conflict resolves automatically ─────────────────────────────────────────
│ ▸ compile() doesn't return both 60s and 300s as equals. It demotes the
│ ▸ superseded cell: v1 drops to eff:0.29(challenged); v2 stays high.
│ $ recall compile "cache ttl"   # cell_state section
│   6837ae13  Cache TTL is 300s; active/conf:0.85/eff:0.85
│   c42f8dcf  Cache TTL is 60s; active/conf:0.7/eff:0.29(challenged)
│ v1 is still IN the graph — preserved, queryable, just down-weighted.
└──────────────────────────────────────────────────────────────

┌─ extend to a chain — v3 --contradicts v2 ─────────────────────────────────────────
│ ▸ Decisions keep evolving. v3 supersedes v2, which already superseded v1.
│ $ recall admit --contradicts 6837ae13…  # decision: "Cache TTL is 600s"
│   efef03eb  Cache TTL is 600s; active/conf:0.9/eff:0.45(actor-discounted)
│   6837ae13  Cache TTL is 300s; active/conf:0.85/eff:0(challenged,actor-discounted)
│   c42f8dcf  Cache TTL is 60s; active/conf:0.7/eff:0(challenged,actor-discounted)
│ only the latest link is current. v1 and v2 both collapse to eff:0 —
│ a 3-link chain, resolved at read time to exactly one live answer.
└──────────────────────────────────────────────────────────────

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334  (demo ran in a throwaway db)
└──────────────────────────────────────────────────────────────

✦ DONE

Rollback a write 04-rollback

Every admit is journaled. `recall rollback apply` cleanly undoes a write (archives the node, strips its relations) — auditable, reversible, no SQLite surgery.

script — screencasts/04-rollback.sh
#!/usr/bin/env bash
# Screencast 04 — "Rollback: every write is journaled and reversible."
# Fresh isolated graph. Admit a cell, see it retrievable, then undo the admit
# from the journal — archiving the node and stripping its relations — with no
# manual surgery. Captures REAL recall output and asserts the real graph is
# untouched (tripwire) before publishing.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/lib/present.sh"
HELP=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# ── isolation: fresh throwaway DB; real graph never touched ──────────────────
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"
recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
cleanup(){ rm -f "$DB" /tmp/p.json; }
trap cleanup EXIT

title "Rollback — undo a write" "every admit is journaled, so it's cleanly reversible"

# ── scene 1: write a cell ────────────────────────────────────────────────────
scene "write a cell"
narrate "We admit one decision into a fresh graph. Watch the node count."
python3 "$HELP" --kind decision \
  --title "Adopt the WAL journal format" \
  --body "Use SQLite WAL mode for the journal so writes are durable and reversible." \
  --confidence 0.8 --topics "storage,journal" > /tmp/p.json 2>/dev/null
ID=$(recall admit --json /tmp/p.json --db "$DB" 2>/dev/null | jq -r '.node.id')
cmd "recall admit --json proposal.json"
printf 'admitted decision %s\n' "${ID:0:8}" | out
cmd "recall status"
recall status --db "$DB" 2>/dev/null | jq -c '{nodes:.stats.nodes, journal:.stats.rollbackEntries}' | out
note "nodes: 1 — and one journal entry was recorded for the insert."

# ── scene 2: it's retrievable ────────────────────────────────────────────────
scene "it's live"
narrate "compile finds the cell — it's part of working memory now."
cmd "recall compile \"WAL journal storage decision\""
recall compile "WAL journal storage decision" --db "$DB" 2>/dev/null \
  | grep -iE 'selected_cells|^- Adopt the WAL' | sed 's/^- //' | cut -c1-82 | out

# ── scene 3: the journal ─────────────────────────────────────────────────────
scene "the journal"
narrate "Every write leaves a reversible journal entry. Here's the insert."
JID=$(recall rollback list --db "$DB" 2>/dev/null | jq -r '.rollback[0].id')
cmd "recall rollback list"
recall rollback list --db "$DB" 2>/dev/null \
  | jq -c '.rollback[0] | {id:.id[0:8], action, target:.targetId[0:8]}' | out
cmd "recall rollback show $JID"
recall rollback show "$JID" --db "$DB" 2>/dev/null \
  | jq -c '{id:.id[0:8], applied}' | out
note "action=insert_node, not yet applied — ready to undo on demand."

# ── scene 4: undo it ─────────────────────────────────────────────────────────
scene "undo the write"
narrate "Apply the journal entry. Recall archives the node and strips its relations."
cmd "recall rollback apply $JID"
recall rollback apply "$JID" --db "$DB" 2>/dev/null \
  | jq -r '.actions[]' | sed 's/^/• /' | cut -c1-86 | out

# ── scene 5: gone, cleanly ───────────────────────────────────────────────────
scene "gone — cleanly"
narrate "The cell is no longer retrievable: compile selects zero cells."
cmd "recall compile \"WAL journal storage decision\""
recall compile "WAL journal storage decision" --db "$DB" 2>/dev/null \
  | grep -oE 'selected_cells=[0-9]+' | sed 's/^/now: /' | out
note "No surgery, no orphan edges — the journal did the reversal for you."

# ── tripwire: real graph never touched ───────────────────────────────────────
scene "isolated"
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
if [ "$G0" = "$G1" ]; then
  note "global graph untouched: $G0 -> $G1"
else
  note "WARNING global graph changed: $G0 -> $G1 — DO NOT publish"
fi
endbox
printf '\n✦ DONE\n'
captured output (unedited real run)
  Rollback — undo a write
  every admit is journaled, so it's cleanly reversible

┌─ write a cell ─────────────────────────────────────────
│ ▸ We admit one decision into a fresh graph. Watch the node count.
│ $ recall admit --json proposal.json
│   admitted decision c0163b47
│ $ recall status
│   {"nodes":1,"journal":1}
│ nodes: 1 — and one journal entry was recorded for the insert.

┌─ it's live ─────────────────────────────────────────
│ ▸ compile finds the cell — it's part of working memory now.
│ $ recall compile "WAL journal storage decision"
│   retrieval=fts5-bm25; query="WAL journal storage decision"; selected_cells=1; budge
│   Adopt the WAL journal format: Use SQLite WAL mode for the journal so writes are du

┌─ the journal ─────────────────────────────────────────
│ ▸ Every write leaves a reversible journal entry. Here's the insert.
│ $ recall rollback list
│   {"id":"f6bc4565","action":"insert_node","target":"c0163b47"}
│ $ recall rollback show f6bc4565-4e9a-4a7a-be96-f85d0de3cdcd
│   {"id":"f6bc4565","applied":false}
│ action=insert_node, not yet applied — ready to undo on demand.

┌─ undo the write ─────────────────────────────────────────
│ ▸ Apply the journal entry. Recall archives the node and strips its relations.
│ $ recall rollback apply f6bc4565-4e9a-4a7a-be96-f85d0de3cdcd
│   • archive inserted node c0163b47-2ed8-465c-bd4f-594d68ef64a8
│   • delete relations attached to c0163b47-2ed8-465c-bd4f-594d68ef64a8
│   • delete derivation index rows for c0163b47-2ed8-465c-bd4f-594d68ef64a8

┌─ gone — cleanly ─────────────────────────────────────────
│ ▸ The cell is no longer retrievable: compile selects zero cells.
│ $ recall compile "WAL journal storage decision"
│   now: selected_cells=0
│ No surgery, no orphan edges — the journal did the reversal for you.

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

Inception — grounded idea synthesis 05-incept

`recall incept` compiles related cells and hands back a directive + a template pre-linked (`depends_on`) to its sources, so a new hypothesis is grounded in existing memory, not hallucinated — and gated like any write.

script — screencasts/05-incept.sh
#!/usr/bin/env bash
# Screencast 05 — "Inception: grounded idea synthesis."
# Seeds two related observations in a fresh isolated graph, runs `recall incept`,
# shows the compiled packet + DIRECTIVE + writebackTemplate (whose evidence.depends_on
# already grounds the new hypothesis in its sources), then admits the synthesized
# hypothesis and compiles to show the new cell linked back to the cells it came from.
# Inception turns existing memory into a new, gated, grounded hypothesis — not a hallucination.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/lib/present.sh"
HELP=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# ── isolation: fresh temp graph, never the user's real one ──────────────────
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"; recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

write_cell(){ # kind title body conf topics [depends]
  local kind="$1" title="$2" body="$3" conf="$4" topics="$5" dep="${6:-}"
  if [ -n "$dep" ]; then
    python3 "$HELP" --kind "$kind" --title "$title" --body "$body" \
      --confidence "$conf" --topics "$topics" --depends-on "$dep" > /tmp/sc05_p.json 2>/dev/null
  else
    python3 "$HELP" --kind "$kind" --title "$title" --body "$body" \
      --confidence "$conf" --topics "$topics" > /tmp/sc05_p.json 2>/dev/null
  fi
  recall admit --json /tmp/sc05_p.json --db "$DB" 2>/dev/null | jq -r '.node.id'
}

title "recall incept — turn memory into a new grounded hypothesis" "synthesis over your own cells, gated like any other belief"

# ── scene 1: seed two related observations ──────────────────────────────────
scene "two facts land in memory, separately"
narrate "A load test left us two unconnected observations. Neither is a conclusion yet."
ID1=$(write_cell observation \
  "Postgres pool capped at 20 after load test" \
  "Load test showed the connection pool maxes at 20 connections; beyond that, requests queue." \
  0.85 "postgres,pool,load-test")
ID2=$(write_cell observation \
  "p99 latency spiked under connection contention" \
  "During the same load test, p99 latency tripled exactly when connection contention began at the pool ceiling." \
  0.8 "latency,p99,contention")
cmd "recall search 'pool latency' --db \$DB"
recall search "pool latency" --db "$DB" 2>/dev/null \
  | jq -r '.results[]? // .[]? | select(.title) | "observation  \(.title)"' 2>/dev/null \
  | cut -c1-86 | out
note "source A: ${ID1:0:8}    source B: ${ID2:0:8}"
endbox

# ── scene 2: ask the open question ──────────────────────────────────────────
scene "ask an open question across them"
you "is our pool cap actually a latency lever?"
narrate "incept compiles a packet over the matching cells — not a chat, a grounded brief."
cmd "recall incept 'is our pool cap actually a latency lever?' --db \$DB"
recall incept "is our pool cap actually a latency lever?" --db "$DB" 2>/dev/null > /tmp/sc05_incept.json
jq -r '.sources[] | "source  \(.kind)  \(.id[0:8])  \(.title)"' /tmp/sc05_incept.json | cut -c1-86 | out
note "the packet selected exactly the 2 related cells as evidence"
endbox

# ── scene 3: the DIRECTIVE ──────────────────────────────────────────────────
scene "the directive: synthesize ONE new cell"
narrate "Not 'write anything'. Synthesize one cell from the sources, gated, honest confidence."
jq -r '.directive' /tmp/sc05_incept.json | fold -s -w 84 | head -6 | out
endbox

# ── scene 4: the writebackTemplate is pre-grounded ──────────────────────────
scene "the writeback template already grounds the idea"
narrate "evidence.depends_on is filled with the source ids BEFORE we write a word."
cmd "jq '.writebackTemplate.evidence.depends_on'"
jq -r '.writebackTemplate.intent.kind as $k | "intent.kind = \($k)  (lands as a hypothesis, unverified)"' /tmp/sc05_incept.json | out
jq -r '.writebackTemplate.evidence.depends_on[] | "depends_on  \(.)"' /tmp/sc05_incept.json | out
note "fill the title/body/summary — the grounding edges are already wired"
endbox

# ── scene 5: admit the synthesis ────────────────────────────────────────────
scene "admit the filled synthesis"
narrate "We name the two sources in the body and set an honest, deflated 0.55 confidence."
HID=$(write_cell hypothesis \
  "Pool cap of 20 is the dominant p99 latency lever" \
  "Combining the load-test observation (pool caps at 20) with the latency observation (p99 tripled at the pool ceiling): the cap itself is the contention point, so raising it is likely the highest-leverage latency fix. Grounded in both source cells." \
  0.55 "postgres,pool,latency,p99" "$ID1,$ID2")
note "new hypothesis ${HID:0:8} — conf 0.55, unverified, gated like any other cell"
endbox

# ── scene 6: compile proves the grounding ───────────────────────────────────
scene "compile: the new hypothesis is linked to its sources"
narrate "Two depends_on edges back to the originals — provenance you can audit, not a hallucination."
cmd "recall compile 'pool cap latency hypothesis' --db \$DB"
recall compile "pool cap latency hypothesis" --db "$DB" 2>/dev/null \
  | grep -iE "depends_on" | sed -E 's/; handle=.*//' | cut -c1-86 | out
endbox

# ── tripwire ────────────────────────────────────────────────────────────────
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
scene "isolated"
note "global graph untouched: $G0 -> $G1"
endbox
rm -f "$DB" /tmp/sc05_p.json /tmp/sc05_incept.json
printf '\n✦ DONE\n'
captured output (unedited real run)
  recall incept — turn memory into a new grounded hypothesis
  synthesis over your own cells, gated like any other belief

┌─ two facts land in memory, separately ─────────────────────────────────────────
│ ▸ A load test left us two unconnected observations. Neither is a conclusion yet.
│ $ recall search 'pool latency' --db $DB
│   observation  p99 latency spiked under connection contention
│   observation  Postgres pool capped at 20 after load test
│ source A: 37fbbc47    source B: 3ffddc58
└──────────────────────────────────────────────────────────────

┌─ ask an open question across them ─────────────────────────────────────────
│ 🧑 you → is our pool cap actually a latency lever?
│ ▸ incept compiles a packet over the matching cells — not a chat, a grounded brief.
│ $ recall incept 'is our pool cap actually a latency lever?' --db $DB
│   source  observation  37fbbc47  Postgres pool capped at 20 after load test
│   source  observation  3ffddc58  p99 latency spiked under connection contention
│ the packet selected exactly the 2 related cells as evidence
└──────────────────────────────────────────────────────────────

┌─ the directive: synthesize ONE new cell ─────────────────────────────────────────
│ ▸ Not 'write anything'. Synthesize one cell from the sources, gated, honest confidence.
│   INCEPTION. Synthesize exactly ONE new cell from the 2 source cell(s) above. Aim for 
│   something not already written: a novel method, a connection across distant cells, 
│   or an insight implied by a contradiction between them. Fill 
│   writebackTemplate.content (title, body, summary); in the body, name which sources 
│   combined into the idea, and add real topics/subject tags. Set an honest confidence. 
│   This is a hypothesis until the graph vets it, so do not inflate it. Leave 
└──────────────────────────────────────────────────────────────

┌─ the writeback template already grounds the idea ─────────────────────────────────────────
│ ▸ evidence.depends_on is filled with the source ids BEFORE we write a word.
│ $ jq '.writebackTemplate.evidence.depends_on'
│   intent.kind = hypothesis  (lands as a hypothesis, unverified)
│   depends_on  37fbbc47-97bf-4e17-b65d-4d96ae76eb9a
│   depends_on  3ffddc58-4506-4191-821d-8a39959938b9
│ fill the title/body/summary — the grounding edges are already wired
└──────────────────────────────────────────────────────────────

┌─ admit the filled synthesis ─────────────────────────────────────────
│ ▸ We name the two sources in the body and set an honest, deflated 0.55 confidence.
│ new hypothesis d168c913 — conf 0.55, unverified, gated like any other cell
└──────────────────────────────────────────────────────────────

┌─ compile: the new hypothesis is linked to its sources ─────────────────────────────────────────
│ ▸ Two depends_on edges back to the originals — provenance you can audit, not a hallucination.
│ $ recall compile 'pool cap latency hypothesis' --db $DB
│   - Pool cap of 20 is the dominant p99 latency lever depends_on Postgres pool capped at 
│   - Pool cap of 20 is the dominant p99 latency lever depends_on p99 latency spiked under
└──────────────────────────────────────────────────────────────

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

Effective confidence — living trust 13-effective-confidence

Stated confidence is immutable; effective confidence is recomputed from the graph on every read — supports raise it, contradicts/concerns lower it, actor calibration discounts it — so challenged cells sink in ranking automatically.

script — screencasts/13-effective-confidence.sh
#!/usr/bin/env bash
# Screencast 13 — "Effective confidence: living, graph-computed trust."
# Fresh isolated graph. Admit a base decision C, show its compiled cell_state
# (eff == stated). Admit a SUPPORTING cell (--supports C) and watch C's eff RISE
# and flag (supported). Admit a CONTRADICTING cell (--contradicts C) and watch
# C's eff DROP and flag (challenged). Stated confidence never changes; effective
# confidence is recomputed from the graph at every read. All output is REAL
# recall output. An isolation tripwire asserts the user's real graph is untouched.
set -u
source /Users/hendrixx./recall-youtube/screencasts/lib/present.sh

HELPER=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py
TASK="event store decision"

# --- fresh isolated graph (CLI routes by cwd + ignores RECALL_DB, so pass --db everywhere)
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"; recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

# pull just C's one-line cell_state (title + conf/eff/...) out of a compile packet
stateC() {
  recall compile "$TASK" --db "$DB" 2>/dev/null \
    | grep -oE "${C}; title=[^;]*; state=[^;]*" \
    | sed -E "s/^${C}; title=//; s/; state=active\// -> /; s#/unc:[^ ]*##"
}

title "recall — effective confidence: living, graph-computed trust" \
      "stated confidence is immutable; effective confidence is recomputed at every read"

scene "admit a base claim"
narrate "A decision lands with a STATED confidence of 0.70. That number is immutable."
you "we'll use Postgres as the event store"
python3 "$HELPER" --kind decision \
  --title "Adopt Postgres for the event store" \
  --body "Use Postgres with logical replication as the primary event store for the pipeline." \
  --confidence 0.70 --topics "datastore,arch" > /tmp/sc13p.json 2>/dev/null
C=$(recall admit --json /tmp/sc13p.json --db "$DB" 2>/dev/null | jq -r '.node.id')
note "admitted decision $C"
cmd "recall compile \"$TASK\""
stateC | out
note "eff:0.7 == stated 0.7 — no evidence yet, so effective equals stated."
endbox

scene "supporting evidence raises eff"
narrate "A load test confirms the claim. We admit it with --supports C."
you "load test: Postgres sustains 12k events/s, replication lag under 200ms"
cmd "recall_helper --kind observation ... --supports $C"
python3 "$HELPER" --kind observation \
  --title "Load test confirms Postgres sustains 12k events/s" \
  --body "Sustained 12k events/s with logical replication lag under 200ms in the load test." \
  --confidence 0.85 --topics "datastore,perf" --supports "$C" > /tmp/sc13p.json 2>/dev/null
S=$(recall admit --json /tmp/sc13p.json --db "$DB" 2>/dev/null | jq -r '.node.id')
recall maintenance --derive --db "$DB" >/dev/null 2>&1
note "admitted supporting observation $S"
stateC | out
note "eff rose to 0.8 and is flagged (supported) — stated is still 0.7."
endbox

scene "contradicting evidence drops eff"
narrate "Then a risk surfaces that contradicts the claim. We admit it with --contradicts C."
you "logical replication stalls on schema migrations — events get dropped"
cmd "recall_helper --kind risk ... --contradicts $C"
python3 "$HELPER" --kind risk \
  --title "Postgres logical replication stalls on schema migrations" \
  --body "Logical replication halts during DDL, dropping events during migrations." \
  --confidence 0.80 --topics "datastore,reliability" --contradicts "$C" > /tmp/sc13p.json 2>/dev/null
X=$(recall admit --json /tmp/sc13p.json --db "$DB" 2>/dev/null | jq -r '.node.id')
recall maintenance --derive --db "$DB" >/dev/null 2>&1
note "admitted contradicting risk $X"
stateC | out
note "eff collapsed and is flagged (challenged,...) — a contradiction outweighs support."
endbox

scene "why eff != stated — the annotations"
narrate "The flags read straight off the graph: supports raise, contradicts lower,"
narrate "plus per-actor calibration (actor-discounted). Stated 0.7 never moved."
recall compile "$TASK" --db "$DB" 2>/dev/null \
  | grep -oE "${C}; title=[^;]*; state=[^;]*" \
  | grep -oE 'conf:[0-9.]+/eff:[^/]*\([^)]*\)' | out
note "conf = the immutable stated value; eff = the live graph-computed trust."
endbox

scene "challenged cells sink in ranking"
narrate "Because eff is recomputed at read time, a challenged claim auto-demotes —"
narrate "the open contradiction shows up in the packet's conflicts/next-actions."
recall compile "$TASK" --db "$DB" 2>/dev/null \
  | grep -iE 'contradict|review' | head -2 | cut -c1-88 | out
endbox

# --- isolation tripwire: the real graph must be byte-for-byte untouched
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
scene "isolated"
note "global graph untouched: $G0 -> $G1"
endbox
rm -f "$DB" /tmp/sc13p.json
printf '\n✦ DONE\n'
captured output (unedited real run)
  recall — effective confidence: living, graph-computed trust
  stated confidence is immutable; effective confidence is recomputed at every read

┌─ admit a base claim ─────────────────────────────────────────
│ ▸ A decision lands with a STATED confidence of 0.70. That number is immutable.
│ 🧑 you → we'll use Postgres as the event store
│ admitted decision bba582ba-b5a1-4ef8-9504-bd0420d11cab
│ $ recall compile "event store decision"
│   Adopt Postgres for the event store -> conf:0.7/eff:0.7
│ eff:0.7 == stated 0.7 — no evidence yet, so effective equals stated.
└──────────────────────────────────────────────────────────────

┌─ supporting evidence raises eff ─────────────────────────────────────────
│ ▸ A load test confirms the claim. We admit it with --supports C.
│ 🧑 you → load test: Postgres sustains 12k events/s, replication lag under 200ms
│ $ recall_helper --kind observation ... --supports bba582ba-b5a1-4ef8-9504-bd0420d11cab
│ admitted supporting observation e6e1af8b-63ac-421e-9aea-7067c73912e3
│   Adopt Postgres for the event store -> conf:0.7/eff:0.8(supported)
│ eff rose to 0.8 and is flagged (supported) — stated is still 0.7.
└──────────────────────────────────────────────────────────────

┌─ contradicting evidence drops eff ─────────────────────────────────────────
│ ▸ Then a risk surfaces that contradicts the claim. We admit it with --contradicts C.
│ 🧑 you → logical replication stalls on schema migrations — events get dropped
│ $ recall_helper --kind risk ... --contradicts bba582ba-b5a1-4ef8-9504-bd0420d11cab
│ admitted contradicting risk 8d44f232-98ad-40d9-83af-491f0e4178de
│   Adopt Postgres for the event store -> conf:0.7/eff:0.14(challenged,supported,actor-discounted)
│ eff collapsed and is flagged (challenged,...) — a contradiction outweighs support.
└──────────────────────────────────────────────────────────────

┌─ why eff != stated — the annotations ─────────────────────────────────────────
│ ▸ The flags read straight off the graph: supports raise, contradicts lower,
│ ▸ plus per-actor calibration (actor-discounted). Stated 0.7 never moved.
│   conf:0.7/eff:0.14(challenged,supported,actor-discounted)
│ conf = the immutable stated value; eff = the live graph-computed trust.
└──────────────────────────────────────────────────────────────

┌─ challenged cells sink in ranking ─────────────────────────────────────────
│ ▸ Because eff is recomputed at read time, a challenged claim auto-demotes —
│ ▸ the open contradiction shows up in the packet's conflicts/next-actions.
│   - health=beliefs:0, contradictions:2, stale_or_low_trust:0, critical_warnings:0
│   - Recall memory health report: 0 belief pressures, 0 stale findings, 1 contradiction fin
└──────────────────────────────────────────────────────────────

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

retrieval & context

Compile — context packets, not dumps 06-compile

`recall compile` returns an ids-first, task-scoped packet (compact summaries + handles), and you expand a full cell or a single field only when you actually need it.

script — screencasts/06-compile.sh
#!/usr/bin/env bash
# Screencast 06 — "Compile: context packets, not dumps."
# Seeds a fresh isolated graph with ~6 varied auth cells, then runs `recall compile`
# to produce an IDS-FIRST, task-scoped context packet. Shows lazy expansion of a
# single handle and a single field. All output is REAL recall output. Isolation
# tripwire asserts the user's real global graph is never touched.
set -u
source /Users/hendrixx./recall-youtube/screencasts/lib/present.sh

HELPER=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# --- fresh isolated graph (CLI routes by cwd + ignores RECALL_DB, so pass --db everywhere)
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"; recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

# write a cell: build a proposal, admit it, echo the new id
seed() { # kind title body conf topics
  python3 "$HELPER" --kind "$1" --title "$2" --body "$3" --confidence "$4" --topics "$5" \
    > /tmp/sc06p.json 2>/dev/null
  recall admit --json /tmp/sc06p.json --db "$DB" 2>/dev/null | jq -r '.node.id'
}

title "recall compile — context packets, not dumps" \
      "task-scoped, IDS-first memory; pull full content only when needed"

scene "seed"
narrate "Six varied cells across the auth work — decisions, risks, observations."
D1=$(seed decision    "Use canary rollout for auth deploy" \
        "Roll the new auth service to 5% of traffic first, watch error rate, then ramp." 0.8 "auth,deploy")
D2=$(seed decision    "Deploy auth during low-traffic window" \
        "Schedule the auth rollout for 02:00 UTC to minimize blast radius." 0.75 "auth,deploy")
R1=$(seed risk        "Auth token TTL too short under load" \
        "Under high load the 5-minute access token TTL causes refresh storms against the IdP." 0.7 "auth,reliability")
R2=$(seed risk        "Secrets rotation not automated" \
        "The auth service still uses a manually rotated signing key; rotation is a runbook step." 0.6 "auth,security")
O1=$(seed observation "Staging auth latency p99 is 340ms" \
        "Measured p99 latency for the auth service in staging is 340ms, within the 500ms SLO." 0.9 "auth,perf")
O2=$(seed observation "Rollback playbook tested last week" \
        "The auth rollback playbook was dry-run tested on 2026-06-09 and completed in 4 minutes." 0.85 "auth,deploy")
note "seeded 6 cells: $(echo "$D1" | cut -c1-8) $(echo "$D2" | cut -c1-8) $(echo "$R1" | cut -c1-8) $(echo "$R2" | cut -c1-8) $(echo "$O1" | cut -c1-8) $(echo "$O2" | cut -c1-8)"
endbox

scene "compile a task-scoped packet"
narrate "Don't dump the whole graph at the agent. Compile to the task."
you "prepare the auth deploy"
cmd 'recall compile "prepare the auth deploy" --words 200'
# objective + compiler_state: retrieval engine, graph shape, health line, ids-first policy
recall compile "prepare the auth deploy" --words 200 --db "$DB" 2>/dev/null \
  | sed -n '1,9p' | out
endbox

scene "relevant_memory = compact summaries + [kind:id] handles"
narrate "Each line is a one-line summary ending in a handle you can expand."
recall compile "prepare the auth deploy" --words 200 --db "$DB" 2>/dev/null \
  | sed -n '/^relevant_memory:/,/^$/p' | grep '^-' | cut -c1-88 | out
note "…and risks come back the same way, as handles:"
recall compile "prepare the auth deploy" --words 200 --db "$DB" 2>/dev/null \
  | sed -n '/^risks:/,/^$/p' | grep '^-' | cut -c1-88 | out
endbox

scene "suggested_next_actions + expansion handles"
narrate "The packet tells the agent what to do next and what it can pull."
recall compile "prepare the auth deploy" --words 200 --db "$DB" 2>/dev/null \
  | sed -n '/^suggested_next_actions:/,/^$/p' | grep '^-' | out
note "expansion_handles (first 3 of 6):"
recall compile "prepare the auth deploy" --words 200 --db "$DB" 2>/dev/null \
  | sed -n '/^expansion_handles:/,$p' | grep '^-' | head -3 | out
endbox

scene "lazy-expand one handle"
narrate "Agent picked the canary decision. Pull the full cell only now."
cmd "recall cell show $D1"
recall cell show "$D1" --db "$DB" 2>/dev/null \
  | jq -r '.node | "kind:    \(.kind)\ntitle:   \(.title)\nbody:    \(.body)\ntopics:  \(.tags.topics | join(", "))"' | out
endbox

scene "expand just one field"
narrate "Even cheaper: address a single field with #path — no full payload."
cmd "recall cell show $D1#body"
recall cell show "${D1}#body" --db "$DB" 2>/dev/null \
  | jq -r '.requestedField | "field: \(.path)\nvalue: \(.valuePreview)"' | out
endbox

# --- isolation tripwire: the real graph must be byte-for-byte untouched
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
scene "isolated"
note "global graph untouched: $G0 -> $G1"
endbox
rm -f "$DB" /tmp/sc06p.json
printf '\n✦ DONE\n'
captured output (unedited real run)
  recall compile — context packets, not dumps
  task-scoped, IDS-first memory; pull full content only when needed

┌─ seed ─────────────────────────────────────────
│ ▸ Six varied cells across the auth work — decisions, risks, observations.
│ seeded 6 cells: 6089f620 4421e84b 5b5e901a 98c00a58 54f8b0b0 fd022edf
└──────────────────────────────────────────────────────────────

┌─ compile a task-scoped packet ─────────────────────────────────────────
│ ▸ Don't dump the whole graph at the agent. Compile to the task.
│ 🧑 you → prepare the auth deploy
│ $ recall compile "prepare the auth deploy" --words 200
│   objective:
│   prepare the auth deploy
│   
│   compiler_state:
│   - retrieval=fts5-bm25; query="prepare the auth deploy"; selected_cells=6; budget_words=200
│   - graph=nodes:6, relations:0, hyperedges:0, programs:0, dag_overlays:0, eval_runs:0
│   - health=beliefs:0, contradictions:0, stale_or_low_trust:0, critical_warnings:0
│   - policy=ids-first; translated_references return handles; call recall_cell/recall_search to expand only what is needed
│   
└──────────────────────────────────────────────────────────────

┌─ relevant_memory = compact summaries + [kind:id] handles ─────────────────────────────────────────
│ ▸ Each line is a one-line summary ending in a handle you can expand.
│   - Deploy auth during low-traffic window: Schedule the auth rollout for 02:00 UTC to mini
│   - Use canary rollout for auth deploy: Roll the new auth service to 5% of traffic first, 
│   - Rollback playbook tested last week: The auth rollback playbook was dry-run tested on 2
│   - Staging auth latency p99 is 340ms: Measured p99 latency for the auth service in stagin
│ …and risks come back the same way, as handles:
│   - Auth token TTL too short under load: Under high load the 5-minute access token TTL cau
│   - Secrets rotation not automated: The auth service still uses a manually rotated signing
└──────────────────────────────────────────────────────────────

┌─ suggested_next_actions + expansion handles ─────────────────────────────────────────
│ ▸ The packet tells the agent what to do next and what it can pull.
│   - No urgent belief, stale-memory, or contradiction pressure detected.
│ expansion_handles (first 3 of 6):
│   - 4421e84b-544b-44fa-83bb-53906ed682fd
│   - 6089f620-e4c4-4ab7-b8f1-aa7adc7023e9
│   - fd022edf-e75a-4c0e-96e3-2b12b77c7511
└──────────────────────────────────────────────────────────────

┌─ lazy-expand one handle ─────────────────────────────────────────
│ ▸ Agent picked the canary decision. Pull the full cell only now.
│ $ recall cell show 6089f620-e4c4-4ab7-b8f1-aa7adc7023e9
│   kind:    decision
│   title:   Use canary rollout for auth deploy
│   body:    Roll the new auth service to 5% of traffic first, watch error rate, then ramp.
│   topics:  auth, deploy
└──────────────────────────────────────────────────────────────

┌─ expand just one field ─────────────────────────────────────────
│ ▸ Even cheaper: address a single field with #path — no full payload.
│ $ recall cell show 6089f620-e4c4-4ab7-b8f1-aa7adc7023e9#body
│   field: body
│   value: Roll the new auth service to 5% of traffic first, watch error rate, then ramp.
└──────────────────────────────────────────────────────────────

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

Retrieval — BM25 + semantic 07-search-semantic

FTS5 + BM25 with porter stemming and identifier-aware tokenization, plus a semantic path. Honest about the lexical-by-default limit.

script — screencasts/07-search-semantic.sh
#!/usr/bin/env bash
# Screencast 07 — "Retrieval: lexical BM25 + semantic ranking."
# Seeds a FRESH ISOLATED graph with ~8 distinct cells, then demonstrates
# `recall search` (FTS5 + BM25 + porter stemming, identifier-aware) and
# `recall semantic`. Honest about the default being lexical. Captures a clean
# narrated transcript to stdout and asserts the real graph is untouched.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"; source "$HERE/lib/present.sh"
mkdir -p "$HERE/transcripts"
HELPER=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# --- isolation: fresh temp DB, never the user's real graph ---
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"
recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

# seed helper: emit proposal, admit, swallow output
seed(){ python3 "$HELPER" --kind "$1" --title "$2" --body "$3" --confidence "$4" --topics "$5" > /tmp/sc07.json 2>/dev/null
        recall admit --json /tmp/sc07.json --db "$DB" >/dev/null 2>&1; }

seed observation "rotate the db-east-1 read replica on failover" \
  "Promote the db-east-1 standby and rotate its credentials during a region failover." 0.8 "db,failover"
seed decision   "adopt feature flag ff_42 for the checkout path" \
  "Roll out ff_42 to 10% of checkout traffic, then ramp to 100% over a week." 0.78 "flags,checkout"
seed lemma      "FTS5 BM25 ranking favors rare terms" \
  "Recall search uses SQLite FTS5 with BM25, so rarer query terms get more weight." 0.82 "search,ranking"
seed observation "porter stemming folds rotate / rotating / rotated" \
  "The FTS porter tokenizer stems word forms to a common root before matching." 0.8 "search,stemming"
seed risk       "leaking ff_42 to all users skips the canary" \
  "If ff_42 defaults on, checkout users bypass the canary cohort entirely." 0.6 "flags,risk"
seed decision   "store cache in redis-cache-2 with a 30s TTL" \
  "Hot read path is fronted by redis-cache-2 with a short TTL to bound staleness." 0.75 "cache,redis"
seed hypothesis "the gateway p99 spike is GC pause, not the db" \
  "Latency tail correlates with JVM GC, not db-east-1 query time." 0.55 "latency,gc"
seed observation "the runbook lives at ops/runbook-failover.md" \
  "Failover steps for db-east-1 are documented in ops/runbook-failover.md." 0.85 "ops,docs"

{
  title "Recall Retrieval — lexical BM25 + semantic, on a real graph" \
        "search finds the cell even when your words don't match exactly"

  scene "the graph"
  narrate "Eight distinct cells are seeded into a fresh, isolated graph: decisions,"
  narrate "observations, lemmas, a risk, a hypothesis. Now we go find them."
  cmd "recall status --db \$DB"
  recall status --db "$DB" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | out

  scene "lexical search + BM25"
  narrate "recall search runs SQLite FTS5 with BM25 ranking — exact terms, ranked hits."
  cmd "recall search \"checkout flag\""
  recall search "checkout flag" --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.kind | ascii_upcase | .[0:4])  \(.title)"' | out

  scene "porter stemming"
  narrate "Query \"rotating\" — no cell says rotating. The porter tokenizer stems it to"
  narrate "\"rotate\", so it still hits the cell titled \"rotate the db-east-1 read replica\"."
  cmd "recall search \"rotating\""
  recall search "rotating" --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.kind | ascii_upcase | .[0:4])  \(.title)"' | out

  scene "identifier search"
  narrate "Identifiers survive tokenization — exact strings like ff_42 and db-east-1"
  narrate "match as units, so you can find a cell by the symbol you remember."
  cmd "recall search \"ff_42\""
  recall search "ff_42" --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.kind | ascii_upcase | .[0:4])  \(.title)"' | out
  cmd "recall search \"db-east-1\""
  recall search "db-east-1" --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.kind | ascii_upcase | .[0:4])  \(.title)"' | out

  scene "semantic ranking"
  narrate "recall semantic scores cells by relevance and returns them ranked by score."
  cmd "recall semantic \"how do we ship checkout safely\""
  recall semantic "how do we ship checkout safely" --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.score | .*1000 | round / 1000)  \(.item.title)"' | head -4 | out

  scene "honest limits"
  narrate "Retrieval is LEXICAL by default. A true paraphrase with NO shared vocabulary"
  narrate "— \"gradual production release\" vs \"roll out ff_42\" — strains it."
  narrate "Pure lexical search finds zero exact-term matches:"
  cmd "recall search \"gradual production release\"   # lexical hits:"
  printf '%s\n' "$(recall search "gradual production release" --db "$DB" 2>/dev/null | jq -r '.results | length')" | out
  narrate "semantic still ranks the graph, but only on incidental token overlap —"
  narrate "low scores, and the ff_42 decision we meant is not even the top hit:"
  cmd "recall semantic \"gradual production release\""
  recall semantic "gradual production release" --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.score | .*1000 | round / 1000)  \(.item.title)"' | head -2 | out
  note "Low scores = no real lexical overlap. To match pure paraphrase, share"
  note "vocabulary or plug in an embedding backend (recall semantic reindex)."

  scene "isolated"
  G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
  note "global graph untouched: $G0 -> $G1"
  endbox
  printf '\n✦ DONE\n'
}

# tripwire + cleanup
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
if [ "$G0" = "$G1" ]; then printf '\n[tripwire] OK: global graph unchanged (%s)\n' "$G0" >&2
else printf '\n[tripwire] FAIL: global %s -> %s\n' "$G0" "$G1" >&2; fi
rm -f "$DB" /tmp/sc07.json
captured output (unedited real run)
  Recall Retrieval — lexical BM25 + semantic, on a real graph
  search finds the cell even when your words don't match exactly

┌─ the graph ─────────────────────────────────────────
│ ▸ Eight distinct cells are seeded into a fresh, isolated graph: decisions,
│ ▸ observations, lemmas, a risk, a hypothesis. Now we go find them.
│ $ recall status --db $DB
│   "nodes": 8

┌─ lexical search + BM25 ─────────────────────────────────────────
│ ▸ recall search runs SQLite FTS5 with BM25 ranking — exact terms, ranked hits.
│ $ recall search "checkout flag"
│   DECI  adopt feature flag ff_42 for the checkout path
│   RISK  leaking ff_42 to all users skips the canary

┌─ porter stemming ─────────────────────────────────────────
│ ▸ Query "rotating" — no cell says rotating. The porter tokenizer stems it to
│ ▸ "rotate", so it still hits the cell titled "rotate the db-east-1 read replica".
│ $ recall search "rotating"
│   OBSE  porter stemming folds rotate / rotating / rotated
│   OBSE  rotate the db-east-1 read replica on failover

┌─ identifier search ─────────────────────────────────────────
│ ▸ Identifiers survive tokenization — exact strings like ff_42 and db-east-1
│ ▸ match as units, so you can find a cell by the symbol you remember.
│ $ recall search "ff_42"
│   DECI  adopt feature flag ff_42 for the checkout path
│   RISK  leaking ff_42 to all users skips the canary
│ $ recall search "db-east-1"
│   OBSE  rotate the db-east-1 read replica on failover
│   OBSE  the runbook lives at ops/runbook-failover.md
│   HYPO  the gateway p99 spike is GC pause, not the db

┌─ semantic ranking ─────────────────────────────────────────
│ ▸ recall semantic scores cells by relevance and returns them ranked by score.
│ $ recall semantic "how do we ship checkout safely"
│   0.201  FTS5 BM25 ranking favors rare terms
│   0.195  adopt feature flag ff_42 for the checkout path
│   0.177  leaking ff_42 to all users skips the canary
│   0.103  the runbook lives at ops/runbook-failover.md

┌─ honest limits ─────────────────────────────────────────
│ ▸ Retrieval is LEXICAL by default. A true paraphrase with NO shared vocabulary
│ ▸ — "gradual production release" vs "roll out ff_42" — strains it.
│ ▸ Pure lexical search finds zero exact-term matches:
│ $ recall search "gradual production release"   # lexical hits:
│   0
│ ▸ semantic still ranks the graph, but only on incidental token overlap —
│ ▸ low scores, and the ff_42 decision we meant is not even the top hit:
│ $ recall semantic "gradual production release"
│   0.204  porter stemming folds rotate / rotating / rotated
│   0.142  Cache layer corrupts stale reads in prod
│ Low scores = no real lexical overlap. To match pure paraphrase, share
│ vocabulary or plug in an embedding backend (recall semantic reindex).

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

[tripwire] OK: global graph unchanged (334)

Subgraph — compose by tags 11-subgraph

Beyond search: slice the graph by structured facets — every decision about auth, everything tagged risk — for precise context.

script — screencasts/11-subgraph.sh
#!/usr/bin/env bash
# Screencast 11 — "Subgraph: compose memory by structured tags."
# Seeds a FRESH ISOLATED graph with cells across varied facets (topics, types,
# projects), then demonstrates `recall subgraph` composing exact slices of the
# graph by tag family: --topic, --category, --type. Beyond fuzzy search, you ask
# for precise sets — "all decisions about auth", "everything tagged risk".
# Captures a clean narrated transcript to stdout; asserts the real graph is untouched.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"; source "$HERE/lib/present.sh"
mkdir -p "$HERE/transcripts"
HELPER=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# --- isolation: fresh temp DB, never the user's real graph ---
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"
recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

# seed helper: emit proposal, admit, swallow output
seed(){ python3 "$HELPER" --kind "$1" --title "$2" --body "$3" --confidence "$4" --topics "$5" --project "$6" > /tmp/sc11.json 2>/dev/null
        recall admit --json /tmp/sc11.json --db "$DB" >/dev/null 2>&1; }

# --- AUTH facet (Gateway project) ---
seed decision    "Adopt Argon2id for password hashing" \
  "Replace bcrypt with Argon2id (memory-hard) for the auth login path." 0.85 "auth,security" "Gateway"
seed decision    "Issue JWT access tokens with a 15m TTL" \
  "Short-lived access tokens paired with rotating refresh tokens." 0.80 "auth,tokens" "Gateway"
seed risk        "Refresh-token replay is not yet detected" \
  "A stolen refresh token can be reused; no replay/reuse detection in place." 0.55 "auth,risk" "Gateway"
# --- PERF / CHECKOUT facet (Store project) ---
seed observation "Checkout p99 latency measured at 240ms" \
  "Production p99 for the checkout endpoint sits at 240ms under peak load." 0.90 "perf,checkout" "Store"
seed decision    "Front the hot read path with redis-cache-2" \
  "Add a 30s-TTL Redis cache in front of the product read path." 0.78 "perf,cache" "Store"
seed risk        "Cache stampede on redis-cache-2 expiry" \
  "Simultaneous TTL expiry could thunder the origin on a cold key." 0.50 "perf,risk" "Store"
# --- INFRA facet (Platform project) ---
seed lemma       "FTS5 BM25 ranking favors rare terms" \
  "Recall search weights rarer query terms more heavily under BM25." 0.82 "infra,search" "Platform"
seed observation "The failover runbook lives at ops/runbook.md" \
  "Region-failover steps are documented at ops/runbook.md." 0.85 "infra,docs" "Platform"

{
  title "Recall Subgraph — compose exact slices of memory by tag" \
        "beyond search: ask for the precise set of cells you want"

  scene "the graph"
  narrate "Eight cells across three facets are seeded into a fresh, isolated graph:"
  narrate "auth, perf/checkout, infra — a mix of decisions, risks, observations, a lemma."
  cmd "recall status --db \$DB"
  recall status --db "$DB" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | out
  note "Search ranks by relevance. Subgraph composes by structure — exact, not fuzzy."

  scene "everything tagged: --topic auth"
  narrate "\"What does the graph hold about auth?\" — one topic facet, every matching cell,"
  narrate "regardless of kind. Decisions and the open risk come back together."
  cmd "recall subgraph --topic auth --db \$DB"
  recall subgraph --topic auth --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.kind | ascii_upcase | .[0:4])  \(.title)"' | out

  scene "narrow by kind: --type decision"
  narrate "Now slice by cell TYPE instead — every decision in the graph, across all"
  narrate "facets. No risks, no observations: just the choices that were committed."
  cmd "recall subgraph --type decision --db \$DB"
  recall subgraph --type decision --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.tags.topics | join(",") )  ::  \(.title)"' | out

  scene "everything tagged risk: --type risk"
  narrate "\"Show me every open risk\" — one structured query, the whole risk register,"
  narrate "spanning auth and perf. This is targeted context you can hand to an agent."
  cmd "recall subgraph --type risk --db \$DB"
  recall subgraph --type risk --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "RISK  \(.title)  (conf \(.data.confidence.value))"' | out

  scene "the whole memory layer: --category memory"
  narrate "--category is the coarsest facet. Every durable memory cell, regardless of"
  narrate "topic or type — the full set, ready to compile into context."
  cmd "recall subgraph --category memory --db \$DB  | count"
  printf 'memory cells: %s\n' \
    "$(recall subgraph --category memory --db "$DB" 2>/dev/null | jq -r '.results | length')" | out

  scene "compose facets: --topic perf"
  narrate "Switch facet to perf and you get a different exact slice — checkout latency,"
  narrate "the cache decision, the stampede risk. Same graph, composed a different way."
  cmd "recall subgraph --topic perf --db \$DB"
  recall subgraph --topic perf --db "$DB" 2>/dev/null \
    | jq -r '.results[] | "\(.kind | ascii_upcase | .[0:4])  \(.title)"' | out
  note "Search asks \"what's relevant?\". Subgraph asks \"give me exactly this set.\""

  scene "isolated"
  G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
  note "global graph untouched: $G0 -> $G1"
  endbox
  printf '\n✦ DONE\n'
}

# tripwire + cleanup
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
if [ "$G0" = "$G1" ]; then printf '\n[tripwire] OK: global graph unchanged (%s)\n' "$G0" >&2
else printf '\n[tripwire] FAIL: global %s -> %s\n' "$G0" "$G1" >&2; fi
rm -f "$DB" /tmp/sc11.json
captured output (unedited real run)
  Recall Subgraph — compose exact slices of memory by tag
  beyond search: ask for the precise set of cells you want

┌─ the graph ─────────────────────────────────────────
│ ▸ Eight cells across three facets are seeded into a fresh, isolated graph:
│ ▸ auth, perf/checkout, infra — a mix of decisions, risks, observations, a lemma.
│ $ recall status --db $DB
│   "nodes": 8
│ Search ranks by relevance. Subgraph composes by structure — exact, not fuzzy.

┌─ everything tagged: --topic auth ─────────────────────────────────────────
│ ▸ "What does the graph hold about auth?" — one topic facet, every matching cell,
│ ▸ regardless of kind. Decisions and the open risk come back together.
│ $ recall subgraph --topic auth --db $DB
│   RISK  Refresh-token replay is not yet detected
│   DECI  Issue JWT access tokens with a 15m TTL
│   DECI  Adopt Argon2id for password hashing

┌─ narrow by kind: --type decision ─────────────────────────────────────────
│ ▸ Now slice by cell TYPE instead — every decision in the graph, across all
│ ▸ facets. No risks, no observations: just the choices that were committed.
│ $ recall subgraph --type decision --db $DB
│   perf,cache  ::  Front the hot read path with redis-cache-2
│   auth,tokens  ::  Issue JWT access tokens with a 15m TTL
│   auth,security  ::  Adopt Argon2id for password hashing

┌─ everything tagged risk: --type risk ─────────────────────────────────────────
│ ▸ "Show me every open risk" — one structured query, the whole risk register,
│ ▸ spanning auth and perf. This is targeted context you can hand to an agent.
│ $ recall subgraph --type risk --db $DB
│   RISK  Cache stampede on redis-cache-2 expiry  (conf 0.5)
│   RISK  Refresh-token replay is not yet detected  (conf 0.55)

┌─ the whole memory layer: --category memory ─────────────────────────────────────────
│ ▸ --category is the coarsest facet. Every durable memory cell, regardless of
│ ▸ topic or type — the full set, ready to compile into context.
│ $ recall subgraph --category memory --db $DB  | count
│   memory cells: 8

┌─ compose facets: --topic perf ─────────────────────────────────────────
│ ▸ Switch facet to perf and you get a different exact slice — checkout latency,
│ ▸ the cache decision, the stampede risk. Same graph, composed a different way.
│ $ recall subgraph --topic perf --db $DB
│   RISK  Cache stampede on redis-cache-2 expiry
│   DECI  Front the hot read path with redis-cache-2
│   OBSE  Checkout p99 latency measured at 240ms
│ Search asks "what's relevant?". Subgraph asks "give me exactly this set."

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

[tripwire] OK: global graph unchanged (334)

integrity & trust

Memory health 08-beliefs-maintenance

`recall beliefs` and `recall maintenance --derive` audit the graph between turns — surfacing contradictions, stale/low-trust cells, and what to reverify.

script — screencasts/08-beliefs-maintenance.sh
#!/usr/bin/env bash
# Screencast 08 — "Memory health: beliefs + maintenance"
# Recall audits its own graph between turns: surfaces conflicts, stale cells,
# low-trust calibration, and what to reverify. Fresh isolated graph; the real
# user graph is never touched (tripwire asserts the global node count is unchanged).
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/lib/present.sh"
H=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# ---- isolation: fresh temp DB; record real global node count for the tripwire ----
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"
recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

write(){ # kind title body conf topics [contradicts]
  if [ -n "${6:-}" ]; then
    python3 "$H" --kind "$1" --title "$2" --body "$3" --confidence "$4" --topics "$5" --contradicts "$6" > /tmp/sc08p.json 2>/dev/null
  else
    python3 "$H" --kind "$1" --title "$2" --body "$3" --confidence "$4" --topics "$5" > /tmp/sc08p.json 2>/dev/null
  fi
  recall admit --json /tmp/sc08p.json --db "$DB" 2>/dev/null | jq -r '.node.id'
}

title "Memory health — Recall audits its own graph between turns" \
      "beliefs + maintenance: conflicts, stale cells, low trust, and what to reverify"

# ---------------------------------------------------------------------------
scene "seed a graph with a known conflict + a weak belief"
narrate "Three cells. A decision, a newer decision that supersedes it, and a low-confidence hunch."
cmd "recall admit  (decision)            # original choice"
ID1=$(write decision "Use bcrypt for password hashing" \
  "Adopt bcrypt with cost 12 for all password storage." 0.85 "auth,security")
note "→ $ID1" | out
cmd "recall admit  (decision --contradicts)  # supersedes the original"
ID2=$(write decision "Switch to argon2id for password hashing" \
  "Argon2id is the recommended KDF now; supersedes the bcrypt decision." 0.9 "auth,security" "$ID1")
note "→ $ID2  contradicts ${ID1:0:8}" | out
cmd "recall admit  (hypothesis, conf 0.30)   # unverified, low trust"
ID3=$(write hypothesis "Rate limiting may reduce credential stuffing" \
  "Unverified hunch that per-IP rate limits cut stuffing attempts." 0.3 "auth,security")
note "→ $ID3  (low confidence / unverified)" | out
endbox

# ---------------------------------------------------------------------------
scene "recall beliefs — the health report"
narrate "Between turns, Recall doesn't just store cells. It grades the graph it's holding."
cmd "recall beliefs --db \$DB"
recall beliefs --db "$DB" 2>/dev/null \
  | jq -r '.report.stats | "stats   nodes=\(.nodes)  relations=\(.relations)  rollback=\(.rollbackEntries)"' | out
recall beliefs --db "$DB" 2>/dev/null \
  | jq -r '.report.contradictions | "conflicts   \(length) open contradiction(s)"' | out
endbox

# ---------------------------------------------------------------------------
scene "the conflict it found"
narrate "It pairs the two decisions: the newer one contradicts the older, with a severity."
cmd "recall beliefs … | jq '.report.contradictions[]'"
recall beliefs --db "$DB" 2>/dev/null \
  | jq -r '.report.contradictions[] | "[sev \(.severity)]  \(.sourceTitle)\n           ⇒ contradicts ⇒  \(.targetTitle)"' | out
endbox

# ---------------------------------------------------------------------------
scene "calibration — who is over-confident?"
narrate "Stated confidence vs how often that actor's cells got contradicted (Brier = lower is better)."
cmd "recall beliefs … | jq '.report.calibration[]'"
recall beliefs --db "$DB" 2>/dev/null \
  | jq -r '.report.calibration[] | "actor=\(.actor)  cells=\(.cells)  contradicted=\(.contradicted)  meanConf=\(.meanConfidence)  brier=\(.brierScore)"' | out
note "low-confidence hunch drags meanConf down — exactly what reverification should target" | out
endbox

# ---------------------------------------------------------------------------
scene "nextActions — what to do about it"
narrate "The report ends in a worklist: curiosity targets, then concrete next actions."
cmd "recall beliefs … | jq '.report.curiosityTargets[], .report.nextActions[]'"
recall beliefs --db "$DB" 2>/dev/null \
  | jq -r '.report.curiosityTargets[0] | "target   • \(.title)  (priority \(.priority))\n           \(.suggestedAction)"' | out
recall beliefs --db "$DB" 2>/dev/null \
  | jq -r '.report.nextActions[] | "next     → \(. | gsub(", starting with.*$"; ", starting with the bcrypt cell."))"' | out
endbox

# ---------------------------------------------------------------------------
scene "recall maintenance --derive — run the pass"
narrate "Maintenance runs the derive pass and writes a signed health witness back into the graph."
cmd "recall maintenance --derive --db \$DB"
MJSON=$(recall maintenance --derive --db "$DB" 2>/dev/null)
echo "$MJSON" | jq -r '.result.node.summary | "summary   \(.)"' | out
echo "$MJSON" | jq -r '.result.node | "witness   \(.title)   [\(.provenance.verification), by \(.provenance.produced_by)]"' | out
echo "$MJSON" | jq -r '.result.relations[] | "relation  \(.kind)  → concern raised on the contradicted cell"' | out
note "the derive pass closes the loop: the pressure it found is now itself a tracked cell" | out
endbox

# ---------------------------------------------------------------------------
scene "isolated"
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
if [ "$G0" = "$G1" ]; then
  note "global graph untouched: $G0 -> $G1  ✓ (demo ran on a throwaway db)"
else
  note "TRIPWIRE FAILED: global graph changed $G0 -> $G1 — DO NOT publish"
fi
endbox

rm -f "$DB" /tmp/sc08p.json
printf '\n✦ DONE\n'
captured output (unedited real run)
  Memory health — Recall audits its own graph between turns
  beliefs + maintenance: conflicts, stale cells, low trust, and what to reverify

┌─ seed a graph with a known conflict + a weak belief ─────────────────────────────────────────
│ ▸ Three cells. A decision, a newer decision that supersedes it, and a low-confidence hunch.
│ $ recall admit  (decision)            # original choice
│   │ → c303e686-95e1-437d-81e4-b9cf58f1907c
│ $ recall admit  (decision --contradicts)  # supersedes the original
│   │ → 106f1dd7-18ca-41c0-90b4-6a8d0ba84496  contradicts c303e686
│ $ recall admit  (hypothesis, conf 0.30)   # unverified, low trust
│   │ → 3d2835cd-5141-4d11-9d96-7037af3ce481  (low confidence / unverified)
└──────────────────────────────────────────────────────────────

┌─ recall beliefs — the health report ─────────────────────────────────────────
│ ▸ Between turns, Recall doesn't just store cells. It grades the graph it's holding.
│ $ recall beliefs --db $DB
│   stats   nodes=3  relations=1  rollback=4
│   conflicts   1 open contradiction(s)
└──────────────────────────────────────────────────────────────

┌─ the conflict it found ─────────────────────────────────────────
│ ▸ It pairs the two decisions: the newer one contradicts the older, with a severity.
│ $ recall beliefs … | jq '.report.contradictions[]'
│   [sev 0.9]  Switch to argon2id for password hashing
│              ⇒ contradicts ⇒  Use bcrypt for password hashing
└──────────────────────────────────────────────────────────────

┌─ calibration — who is over-confident? ─────────────────────────────────────────
│ ▸ Stated confidence vs how often that actor's cells got contradicted (Brier = lower is better).
│ $ recall beliefs … | jq '.report.calibration[]'
│   actor=claude-code  cells=3  contradicted=1  meanConf=0.633  brier=0.33
│   │ low-confidence hunch drags meanConf down — exactly what reverification should target
└──────────────────────────────────────────────────────────────

┌─ nextActions — what to do about it ─────────────────────────────────────────
│ ▸ The report ends in a worklist: curiosity targets, then concrete next actions.
│ $ recall beliefs … | jq '.report.curiosityTargets[], .report.nextActions[]'
│   target   • Use bcrypt for password hashing  (priority 0.9)
│              resolve, supersede, or add a concern/contradiction witness
│   next     → Review 1 contradiction/concern relation(s), starting with the bcrypt cell.
└──────────────────────────────────────────────────────────────

┌─ recall maintenance --derive — run the pass ─────────────────────────────────────────
│ ▸ Maintenance runs the derive pass and writes a signed health witness back into the graph.
│ $ recall maintenance --derive --db $DB
│   summary   0 belief pressures, 0 stale findings, 1 contradiction findings.
│   witness   Recall memory health report   [checked, by recall-maintenance]
│   relation  concerns  → concern raised on the contradicted cell
│   │ the derive pass closes the loop: the pressure it found is now itself a tracked cell
└──────────────────────────────────────────────────────────────

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334  ✓ (demo ran on a throwaway db)
└──────────────────────────────────────────────────────────────

✦ DONE

Calibration — per-actor trust 09-calibration

A proper Brier score per actor: how well stated confidence matched reality. Overconfident-and-wrong writers get discounted, feeding effective-confidence ranking.

script — screencasts/09-calibration.sh
#!/usr/bin/env bash
# Screencast 09 — "Calibration: per-actor trust via Brier score."
# Fresh isolated graph. One actor (Ada) writes four evidence-backed, high-confidence
# cells; then three corrections each --contradicts a REAL prior cell id. We run
# `recall calibration` and read the per-actor row: cells, contradicted, rate,
# meanConfidence, brierScore — Recall's measure of how well stated confidence
# matched reality. Real CLI output only; isolation tripwire asserts the real graph
# is untouched.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"; source "$HERE/lib/present.sh"
mkdir -p "$HERE/transcripts"
T="$HERE/transcripts/09-calibration.txt"
HELP=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# --- isolation: unique fresh DB; never honor the user's real graph ---
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"; recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

AID="agent:ada"; ADISP="Ada"

# write helper: emit proposal -> admit -> echo new id (no nested $() in a function)
propose(){ # kind title body conf topics srcfiles [contradicts]
  python3 "$HELP" --kind "$1" --title "$2" --body "$3" --confidence "$4" \
    --topics "$5" --source-files "$6" --actor-id "$AID" --actor-display "$ADISP" \
    ${7:+--contradicts $7} > "$PJ" 2>/dev/null
}

{
title "Calibration — per-actor trust via Brier score" "Recall scores how well each actor's stated confidence matched reality"

scene "four confident, evidence-backed claims"
narrate "Ada records four findings, each cited to a source file and asserted at high confidence."
PJ="$(mktemp)"; propose observation "Auth uses JWT in headers" "Service reads a bearer token from the Authorization header." 0.9 auth "src/auth.ts#L10"
A=$(recall admit --json "$PJ" --db "$DB" 2>/dev/null | jq -r '.node.id'); rm -f "$PJ"
PJ="$(mktemp)"; propose observation "Primary store is Postgres 14" "All persistence is on Postgres 14." 0.88 db "infra/db.yaml"
B=$(recall admit --json "$PJ" --db "$DB" 2>/dev/null | jq -r '.node.id'); rm -f "$PJ"
PJ="$(mktemp)"; propose decision "Cache sessions in Redis" "Sessions cached in Redis with a 1h TTL." 0.9 sessions "src/session.ts#L5"
C=$(recall admit --json "$PJ" --db "$DB" 2>/dev/null | jq -r '.node.id'); rm -f "$PJ"
PJ="$(mktemp)"; propose observation "API rate limit is 100 rpm" "Gateway caps clients at 100 requests/min." 0.85 api "gw/config.yaml"
D=$(recall admit --json "$PJ" --db "$DB" 2>/dev/null | jq -r '.node.id'); rm -f "$PJ"
cmd "recall calibration   # baseline: nothing contradicted yet"
recall calibration --db "$DB" 2>/dev/null \
  | jq -r '.calibration[] | "\(.actor)  cells=\(.cells) contradicted=\(.contradicted) meanConf=\(.meanConfidence) brier=\(.brierScore)"' | out
note "4 cells, 0 contradicted, brier ~0.014 — well-calibrated so far (confident, all standing)."
endbox

scene "reality disagrees — three corrections supersede prior claims"
narrate "Later evidence shows three of Ada's claims were wrong. Each correction is admitted"
narrate "with --contradicts pointing at the REAL prior cell id (so the edge resolves)."
PJ="$(mktemp)"; propose observation "Auth uses httpOnly cookies, not headers" "The header claim was wrong; token lives in an httpOnly cookie." 0.8 auth "src/auth.ts#L42" "$A"
recall admit --json "$PJ" --db "$DB" 2>/dev/null | jq -r '"contradicts \(.relations[0].targetId[0:8])  <- \(.node.title)"' | out; rm -f "$PJ"
PJ="$(mktemp)"; propose decision "Sessions live in Postgres, not Redis" "Redis was dropped; sessions moved to a Postgres table." 0.8 sessions "src/session.ts#L60" "$C"
recall admit --json "$PJ" --db "$DB" 2>/dev/null | jq -r '"contradicts \(.relations[0].targetId[0:8])  <- \(.node.title)"' | out; rm -f "$PJ"
PJ="$(mktemp)"; propose observation "API rate limit is 1000 rpm, not 100" "Gateway config corrected to 1000/min." 0.8 api "gw/config.yaml#L8" "$D"
recall admit --json "$PJ" --db "$DB" 2>/dev/null | jq -r '"contradicts \(.relations[0].targetId[0:8])  <- \(.node.title)"' | out; rm -f "$PJ"
note "Three of Ada's four confident claims now carry a resolved contradiction edge."
endbox

scene "calibration re-scored — overconfidence is now visible"
narrate "Brier is a proper scoring rule: a cell that attracted a resolved contradiction"
narrate "scored outcome 0; one still standing scored 1. You can't game it by hedging."
cmd "recall calibration"
recall calibration --db "$DB" 2>/dev/null | jq -r '.calibration[]
  | "\(.actor)  cells=\(.cells)  contradicted=\(.contradicted)  rate=\(.contradictedRate)",
    "  meanConf=\(.meanConfidence)  meanConfContradicted=\(.meanConfidenceContradicted)  brier=\(.brierScore)"' | out
endbox

scene "what the row means"
narrate "~0.43 of Ada's claims went wrong; meanConfidenceContradicted stayed high (~0.88)"
narrate "— overconfident-and-wrong. brierScore jumps from ~0.014 to ~0.35 (lower is better)."
note "This per-actor score feeds effective-confidence ranking: an actor who is confident"
note "AND wrong gets discounted, so their future writes weigh less than a calibrated peer's."
endbox

scene "isolated"
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
note "global graph untouched: ${G0:-?} -> ${G1:-?}"
endbox
printf '\n✦ DONE\n'
} > "$T" 2>&1

rm -f "$DB"
echo "captured $(wc -l < "$T") lines -> $T"
captured output (unedited real run)
  Calibration — per-actor trust via Brier score
  Recall scores how well each actor's stated confidence matched reality

┌─ four confident, evidence-backed claims ─────────────────────────────────────────
│ ▸ Ada records four findings, each cited to a source file and asserted at high confidence.
│ $ recall calibration   # baseline: nothing contradicted yet
│   agent:ada  cells=4 contradicted=0 meanConf=0.883 brier=0.014
│ 4 cells, 0 contradicted, brier ~0.014 — well-calibrated so far (confident, all standing).
└──────────────────────────────────────────────────────────────

┌─ reality disagrees — three corrections supersede prior claims ─────────────────────────────────────────
│ ▸ Later evidence shows three of Ada's claims were wrong. Each correction is admitted
│ ▸ with --contradicts pointing at the REAL prior cell id (so the edge resolves).
│   contradicts 91c321b6  <- Auth uses httpOnly cookies, not headers
│   contradicts f0629592  <- Sessions live in Postgres, not Redis
│   contradicts 299fc992  <- API rate limit is 1000 rpm, not 100
│ Three of Ada's four confident claims now carry a resolved contradiction edge.
└──────────────────────────────────────────────────────────────

┌─ calibration re-scored — overconfidence is now visible ─────────────────────────────────────────
│ ▸ Brier is a proper scoring rule: a cell that attracted a resolved contradiction
│ ▸ scored outcome 0; one still standing scored 1. You can't game it by hedging.
│ $ recall calibration
│   agent:ada  cells=7  contradicted=3  rate=0.429
│     meanConf=0.847  meanConfContradicted=0.883  brier=0.354
└──────────────────────────────────────────────────────────────

┌─ what the row means ─────────────────────────────────────────
│ ▸ ~0.43 of Ada's claims went wrong; meanConfidenceContradicted stayed high (~0.88)
│ ▸ — overconfident-and-wrong. brierScore jumps from ~0.014 to ~0.35 (lower is better).
│ This per-actor score feeds effective-confidence ranking: an actor who is confident
│ AND wrong gets discounted, so their future writes weigh less than a calibrated peer's.
└──────────────────────────────────────────────────────────────

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

Standing watch programs 12-watch-program

Attach a non-LLM monitor to a belief via a hyperedge; it trips when the graph turns against the watched claim.

script — screencasts/12-watch-program.sh
#!/usr/bin/env bash
# 12-watch-program.sh — Standing WATCH programs over a hyperedge.
# Self-contained capture script: runs REAL recall commands against a fresh
# isolated graph and narrates a clean transcript to stdout.
set -uo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/lib/present.sh"
H=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# --- ISOLATION: fresh temp DB; recall routes by --db, never the real graph ---
DB="$(mktemp -t sc12.XXXXXX).sqlite3"; rm -f "$DB"
recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

cleanup(){ rm -f "$DB" /tmp/p1_$$.json /tmp/p2_$$.json /tmp/p3_$$.json /tmp/he_$$.json /tmp/prog_$$.json; }
trap cleanup EXIT

title "recall watch programs — standing monitors that fire when the graph turns" \
      "attach a non-LLM watch to a belief; it trips when that belief is overruled"

# ---------------------------------------------------------------------------
scene "1 · admit a claim and its supporting evidence"
narrate "We record a decision (the watched claim) plus a benchmark backing it."
CLAIM=$(python3 "$H" --kind decision --title "Ship v2 caching layer" \
  --body "Adopt a write-through cache for the API path." \
  --confidence 0.85 --topics "cache,api" > /tmp/p1_$$.json 2>/dev/null
  recall admit --json /tmp/p1_$$.json --db "$DB" 2>/dev/null | jq -r '.node.id')
EV=$(python3 "$H" --kind observation --title "Bench confirms cache hit rate" \
  --body "Load test confirms a 95% cache hit rate." \
  --confidence 0.8 --topics "cache" > /tmp/p2_$$.json 2>/dev/null
  recall admit --json /tmp/p2_$$.json --db "$DB" 2>/dev/null | jq -r '.node.id')
cmd "recall admit  # claim (decision) + evidence (observation)"
{ printf 'claim     %s  Ship v2 caching layer\n' "${CLAIM:0:8}"
  printf 'evidence  %s  Bench confirms cache hit rate\n' "${EV:0:8}"; } | out

# ---------------------------------------------------------------------------
scene "2 · bundle them into an evidence hyperedge"
narrate "A hyperedge groups the claim with its verification into one unit."
cat > /tmp/he_$$.json <<EOF
{"kind":"evidence-bundle","title":"Caching decision bundle","members":[{"nodeId":"$CLAIM","role":"claim"},{"nodeId":"$EV","role":"verification"}]}
EOF
cmd "recall hyperedge add --json he.json"
HE=$(recall hyperedge add --json /tmp/he_$$.json --db "$DB" 2>/dev/null | jq -r '.id')
recall hyperedge show "$HE" --db "$DB" 2>/dev/null \
  | jq -r '"bundle "+(.id[0:8])+"  ["+.kind+"]  "+(.title)+"\nmembers: "+([.members[]|.role]|join(" + "))' | out

# ---------------------------------------------------------------------------
scene "3 · attach a standing WATCH program"
narrate "watch fires when the watched claim's effective confidence drops below minEff."
cat > /tmp/prog_$$.json <<EOF
{"schemaVersion":"recall.program.v1","operation":"watch","params":{"concernTarget":"$CLAIM","minEff":0.7}}
EOF
cmd "recall program add <hyperedge> --json prog.json  # operation=watch, minEff=0.7"
PROG=$(recall program add "$HE" --json /tmp/prog_$$.json --db "$DB" 2>/dev/null | jq -r '.id')
recall program show "$PROG" --db "$DB" 2>/dev/null \
  | jq -r '.spec as $s | "program "+(.id[0:8])+"  op="+$s.operation+"  minEff="+($s.params.minEff|tostring)+"  target="+($s.params.concernTarget[0:8])' | out
note "no LLM in the loop — this is a deterministic monitor over the graph."

# ---------------------------------------------------------------------------
scene "4 · baseline run — belief healthy, watch quiet"
narrate "Right now the claim holds, so effective confidence is above the floor."
cmd "recall program run <program> --derive"
recall program run "$PROG" --derive --db "$DB" 2>/dev/null \
  | jq -r '.run.output | "effective="+(.current|tostring)+"  floor=0.7  tripped="+(.tripped|tostring)' | out

# ---------------------------------------------------------------------------
scene "5 · the graph turns: a contradiction overrules the claim"
narrate "A prod incident contradicts the decision, collapsing its effective confidence."
CON=$(python3 "$H" --kind observation --title "Cache corrupts stale reads in prod" \
  --body "Incident: write-through cache served stale data; do not ship." \
  --confidence 0.95 --topics "cache,incident" --contradicts "$CLAIM" > /tmp/p3_$$.json 2>/dev/null
  recall admit --json /tmp/p3_$$.json --db "$DB" 2>/dev/null | jq -r '.node.id')
cmd "recall admit --contradicts <claim>   # high-confidence counter-evidence"
printf 'contradiction %s  Cache corrupts stale reads in prod\n' "${CON:0:8}" | out
recall maintenance --derive --db "$DB" >/dev/null 2>&1 || true

# ---------------------------------------------------------------------------
scene "6 · re-run — the watch TRIPS"
narrate "Same standing program. Effective confidence fell through the floor; it fires."
cmd "recall program run <program> --derive"
recall program run "$PROG" --derive --db "$DB" 2>/dev/null \
  | jq -r '.run.output | "effective="+(.current|tostring)+" (was "+(.previous|tostring)+")  change="+(.change|tostring)+"\nfloor=0.7  TRIPPED="+(.tripped|tostring|ascii_upcase)' | out
note "a monitor you attached once now warns you the moment the graph turns against the belief."

# ---------------------------------------------------------------------------
scene "isolated"
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
note "global graph untouched: ${G0} -> ${G1}  (all writes hit a throwaway db)"
endbox
printf '\n✦ DONE\n'
captured output (unedited real run)
  recall watch programs — standing monitors that fire when the graph turns
  attach a non-LLM watch to a belief; it trips when that belief is overruled

┌─ 1 · admit a claim and its supporting evidence ─────────────────────────────────────────
│ ▸ We record a decision (the watched claim) plus a benchmark backing it.
│ $ recall admit  # claim (decision) + evidence (observation)
│   claim     93d0fc5c  Ship v2 caching layer
│   evidence  d5355c19  Bench confirms cache hit rate

┌─ 2 · bundle them into an evidence hyperedge ─────────────────────────────────────────
│ ▸ A hyperedge groups the claim with its verification into one unit.
│ $ recall hyperedge add --json he.json
│   bundle f9beb37a  [evidence-bundle]  Caching decision bundle
│   members: claim + verification

┌─ 3 · attach a standing WATCH program ─────────────────────────────────────────
│ ▸ watch fires when the watched claim's effective confidence drops below minEff.
│ $ recall program add <hyperedge> --json prog.json  # operation=watch, minEff=0.7
│   program f727e064  op=watch  minEff=0.7  target=93d0fc5c
│ no LLM in the loop — this is a deterministic monitor over the graph.

┌─ 4 · baseline run — belief healthy, watch quiet ─────────────────────────────────────────
│ ▸ Right now the claim holds, so effective confidence is above the floor.
│ $ recall program run <program> --derive
│   effective=0.7  floor=0.7  tripped=false

┌─ 5 · the graph turns: a contradiction overrules the claim ─────────────────────────────────────────
│ ▸ A prod incident contradicts the decision, collapsing its effective confidence.
│ $ recall admit --contradicts <claim>   # high-confidence counter-evidence
│   contradiction bdf38669  Cache corrupts stale reads in prod

┌─ 6 · re-run — the watch TRIPS ─────────────────────────────────────────
│ ▸ Same standing program. Effective confidence fell through the floor; it fires.
│ $ recall program run <program> --derive
│   effective=0.275 (was 0.7)  change=-0.425
│   floor=0.7  TRIPPED=TRUE
│ a monitor you attached once now warns you the moment the graph turns against the belief.

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334  (all writes hit a throwaway db)
└──────────────────────────────────────────────────────────────

✦ DONE

operations

Secrets — firewall + encrypted side graph 10-secrets

The primary graph refuses credential-shaped content (a high-recall heuristic, not a guarantee); real secrets go in the encrypted side graph, gated by an explicit confirm + passphrase.

script — screencasts/10-secrets.sh
#!/usr/bin/env bash
# 10-secrets.sh — Recall screencast: the secret firewall + encrypted side graph.
# Self-contained. Uses a FRESH isolated main DB and a FRESH secrets DB.
# Every recall command passes --db / --secrets-db so the real graph is never touched.
set -uo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/lib/present.sh"
HELPER=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py

# --- Isolation: fresh main DB + fresh secrets DB ---
DB="$(mktemp -t sc.XXXXXX).sqlite3";          rm -f "$DB"
SECDB="$(mktemp -t sc-secrets.XXXXXX).sqlite3"; rm -f "$SECDB"
recall init --db "$DB" >/dev/null 2>&1

GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

cleanup(){ rm -f "$DB" "$SECDB" /tmp/sc-p.json /tmp/sc-save.json; }
trap cleanup EXIT

title "Secrets — the firewall + the encrypted side graph" \
      "The primary graph refuses credentials; real secrets live encrypted, behind a passphrase."

# ---------------------------------------------------------------------------
scene "the firewall"
narrate "Try to admit an ordinary cell whose body contains a live credential."
you "remember: prod db password is hunter2pass1, AWS key AKIAIOSFODNN7EXAMPLE"
cmd "recall admit --json proposal.json   # body has a password + AWS key"
python3 "$HELPER" --kind observation --title "DB creds" \
  --body "prod db password: hunter2pass1 and AWS key AKIAIOSFODNN7EXAMPLE" \
  --confidence 0.8 --topics "infra,db" > /tmp/sc-p.json 2>/dev/null
recall admit --json /tmp/sc-p.json --db "$DB" 2>/dev/null \
  | jq -c '{accepted, issues: [.issues[] | {code, message}]}' \
  | sed 's/},{/},\n{/g' | out
narrate "REJECTED — issue code secret_pattern. The write never lands."
endbox

# ---------------------------------------------------------------------------
scene "nodes stays 0"
narrate "The firewall is a high-recall heuristic, not a guarantee — but it caught this."
cmd "recall status --db \$DB"
recall status --db "$DB" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | out
endbox

# ---------------------------------------------------------------------------
scene "the right way: save to the encrypted side graph"
narrate "stdin: first line is the passphrase, the rest is the secret value."
cmd "printf 'PASS\\nhunter2pass1\\n' | recall secrets save --title 'prod db password' \\"
note "          --confirm-secret-save --password-stdin --value-stdin --tags db,prod"
printf 'correct-horse-battery\nhunter2pass1\n' \
  | recall secrets save --title "prod db password" --confirm-secret-save \
      --password-stdin --value-stdin --tags "db,prod" --secrets-db "$SECDB" \
      > /tmp/sc-save.json 2>&1
jq -c '{saved, id: .secret.id, title: .secret.title, scope: .secret.scope}' /tmp/sc-save.json | out
ID=$(jq -r '.secret.id' /tmp/sc-save.json)
narrate "Gated by an explicit --confirm-secret-save + a passphrase. It lands encrypted."
endbox

# ---------------------------------------------------------------------------
scene "list — metadata only, never the value"
cmd "recall secrets list --secrets-db \$SECDB"
recall secrets list --secrets-db "$SECDB" 2>/dev/null \
  | jq -c '.secrets[] | {id, title, tags}' | out
note "titles + tags are visible; the secret value is not."
endbox

# ---------------------------------------------------------------------------
scene "get — decrypt with the passphrase"
cmd "printf 'PASS\\n' | recall secrets get \$ID --password-stdin --secrets-db \$SECDB"
printf 'correct-horse-battery\n' \
  | recall secrets get "$ID" --password-stdin --secrets-db "$SECDB" 2>/dev/null \
  | jq -c '{title, plaintext}' | out
narrate "Wrong passphrase = no plaintext (AES-GCM auth fails, decrypt refused):"
cmd "printf 'wrong-pass\\n' | recall secrets get \$ID --password-stdin ..."
printf 'wrong-pass\n' \
  | recall secrets get "$ID" --password-stdin --secrets-db "$SECDB" 2>&1 \
  | grep -m1 -E 'authenticate' | sed 's/^.*: /decrypt refused: /' | out
endbox

# ---------------------------------------------------------------------------
scene "encrypted at rest"
narrate "Grep the raw secrets DB file for the plaintext — it is not there."
cmd "strings \$SECDB | grep -c hunter2pass1"
printf '%s  (0 = ciphertext only, plaintext absent)\n' \
  "$(strings "$SECDB" | grep -c 'hunter2pass1')" | out
endbox

# ---------------------------------------------------------------------------
scene "isolated"
note "global graph untouched: $G0 -> $(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')"
endbox
printf '\n✦ DONE\n'
captured output (unedited real run)
  Secrets — the firewall + the encrypted side graph
  The primary graph refuses credentials; real secrets live encrypted, behind a passphrase.

┌─ the firewall ─────────────────────────────────────────
│ ▸ Try to admit an ordinary cell whose body contains a live credential.
│ 🧑 you → remember: prod db password is hunter2pass1, AWS key AKIAIOSFODNN7EXAMPLE
│ $ recall admit --json proposal.json   # body has a password + AWS key
│   {"accepted":false,"issues":[{"code":"secret_pattern","message":"Secret-looking content detected: AWS access key id"},
│   {"code":"secret_pattern","message":"Secret-looking content detected: secret-named assignment"}]}
│ ▸ REJECTED — issue code secret_pattern. The write never lands.
└──────────────────────────────────────────────────────────────

┌─ nodes stays 0 ─────────────────────────────────────────
│ ▸ The firewall is a high-recall heuristic, not a guarantee — but it caught this.
│ $ recall status --db $DB
│   "nodes": 0
└──────────────────────────────────────────────────────────────

┌─ the right way: save to the encrypted side graph ─────────────────────────────────────────
│ ▸ stdin: first line is the passphrase, the rest is the secret value.
│ $ printf 'PASS\nhunter2pass1\n' | recall secrets save --title 'prod db password' \
│           --confirm-secret-save --password-stdin --value-stdin --tags db,prod
│   {"saved":true,"id":"a2a61a08-11b5-40e5-b331-b8cb87135cf7","title":"prod db password","scope":"local"}
│ ▸ Gated by an explicit --confirm-secret-save + a passphrase. It lands encrypted.
└──────────────────────────────────────────────────────────────

┌─ list — metadata only, never the value ─────────────────────────────────────────
│ $ recall secrets list --secrets-db $SECDB
│   {"id":"a2a61a08-11b5-40e5-b331-b8cb87135cf7","title":"prod db password","tags":["db","prod"]}
│ titles + tags are visible; the secret value is not.
└──────────────────────────────────────────────────────────────

┌─ get — decrypt with the passphrase ─────────────────────────────────────────
│ $ printf 'PASS\n' | recall secrets get $ID --password-stdin --secrets-db $SECDB
│   {"title":"prod db password","plaintext":"hunter2pass1\n"}
│ ▸ Wrong passphrase = no plaintext (AES-GCM auth fails, decrypt refused):
│ $ printf 'wrong-pass\n' | recall secrets get $ID --password-stdin ...
│   decrypt refused: Unsupported state or unable to authenticate data
└──────────────────────────────────────────────────────────────

┌─ encrypted at rest ─────────────────────────────────────────
│ ▸ Grep the raw secrets DB file for the plaintext — it is not there.
│ $ strings $SECDB | grep -c hunter2pass1
│   0  (0 = ciphertext only, plaintext absent)
└──────────────────────────────────────────────────────────────

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE

Diff-aware resume 14-diff-resume

One query summarizes what's new, updated, and retracted since you left — cheap context recovery at session start.

script — screencasts/14-diff-resume.sh
#!/usr/bin/env bash
# Screencast 14 — Diff-aware resume: "what changed since I left"
# Self-contained capture script. Real recall CLI against a FRESH ISOLATED graph.
set -uo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/lib/present.sh"
H=/Users/hendrixx./.claude/skills/recall/scripts/recall_helper.py
DIFF=/Users/hendrixx./.claude/skills/recall/scripts/recall_diff.py

# --- isolation: fresh temp DB, never touch the real graph ---------------------
DB="$(mktemp -t sc.XXXXXX).sqlite3"; rm -f "$DB"
recall init --db "$DB" >/dev/null 2>&1
GLOBAL="$HOME/.recall/db/global.sqlite3"
G0=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')

# helper: write a proposal then admit it, echo the new cell id
admit(){ # kind title body conf topics [extra-flags...]
  local kind="$1" title="$2" body="$3" conf="$4" topics="$5"; shift 5
  python3 "$H" --kind "$kind" --title "$title" --body "$body" \
      --confidence "$conf" --topics "$topics" "$@" > /tmp/sc14_p.json 2>/dev/null
  recall admit --json /tmp/sc14_p.json --db "$DB" 2>/dev/null | jq -r '.node.id'
}

title "Diff-aware resume — one query for what changed since you left" \
      "Cheap context recovery at session start"

# --- Scene 1: yesterday's session -------------------------------------------
scene "yesterday: you left some memory behind"
narrate "Before you logged off, the graph captured a few cells about the work."
D1=$(admit decision    "Use Postgres for sessions" "Adopt a Postgres-backed session store" 0.80 "infra,db")
O1=$(admit observation "Latency spike at peak"     "P99 doubled during peak traffic"        0.70 "perf")
R1=$(admit risk        "Session store is the bottleneck" "Store choice gates throughput"     0.65 "infra,perf")
cmd "recall search 'sessions' --db \$DB | jq cell titles"
recall search "sessions" --db "$DB" 2>/dev/null \
  | jq -r '.results[]? // .[]? | "  ["+(.kind // "?")+"] "+(.title // .body // "")' 2>/dev/null \
  | head -3 | cut -c1-72 | out
note "you close the laptop here — that timestamp is your 'since' marker"

# --- Scene 2: a teammate (or future you) changes things ----------------------
scene "overnight: the picture changes"
narrate "New evidence lands, and one earlier decision gets corrected."
O2=$(admit observation "Redis benchmark wins" "Redis p99 5x better than Postgres for sessions" 0.85 "perf,db")
# the correction: a new decision that contradicts the Postgres one
D2=$(admit decision "Use Redis for sessions" "Switch session store to Redis; Postgres too slow" 0.88 "infra,db" --contradicts "$D1")
cmd "recall hyperedge add  # record the supersede edge"
cat > /tmp/sc14_he.json <<EOF
{ "kind": "code-superseded-by", "title": "Redis supersedes Postgres session decision",
  "members": [ { "nodeId": "$D1", "role": "superseded" },
               { "nodeId": "$D2", "role": "supersedes" } ] }
EOF
recall hyperedge add --json /tmp/sc14_he.json --db "$DB" 2>/dev/null \
  | jq -r '"edge " + .kind + ": " + .title' | out
# a non-supersede edge too: evidence bundle linking the benchmark to the decision
cat > /tmp/sc14_eb.json <<EOF
{ "kind": "evidence-bundle", "title": "Benchmark supports Redis decision",
  "members": [ { "nodeId": "$D2", "role": "claim" },
               { "nodeId": "$O2", "role": "verification" } ] }
EOF
recall hyperedge add --json /tmp/sc14_eb.json --db "$DB" 2>/dev/null \
  | jq -r '"edge " + .kind + ": " + .title' | out

# --- Scene 3: you come back — one query summarizes everything ----------------
scene "you return: 'what changed since I left?'"
narrate "One diff query, scoped to the last hour, recovers the whole delta."
you "what's new since yesterday?"
cmd "python3 recall_diff.py --db \$DB --since 1h --summary"
python3 "$DIFF" --db "$DB" --since 1h --summary 2>&1 \
  | grep -vE '^#|^\(' | sed '/^[[:space:]]*$/d' | cut -c1-80 | out

# --- Scene 4: the four buckets, called out separately ------------------------
scene "the delta, broken into four buckets"
narrate "new_cells, updated_cells, new_hyperedges and supersede_events — each separate."
cmd "python3 recall_diff.py --db \$DB --since 1h | jq .summary"
python3 "$DIFF" --db "$DB" --since 1h 2>/dev/null \
  | jq -c '.summary' | sed 's/,/, /g' | cut -c1-86 | out
narrate "supersede_events is the retraction — the Postgres call was replaced."
python3 "$DIFF" --db "$DB" --since 1h 2>/dev/null \
  | jq -r '.supersede_events[] | "  retracted: " + .title' | out

# --- Scene 5: isolation tripwire --------------------------------------------
scene "isolated"
G1=$(recall status --db "$GLOBAL" 2>/dev/null | grep -oE '"nodes": *[0-9]+' | head -1 | grep -oE '[0-9]+')
note "global graph untouched: $G0 -> $G1"
endbox

rm -f "$DB" /tmp/sc14_p.json /tmp/sc14_he.json /tmp/sc14_eb.json
printf '\n✦ DONE\n'
captured output (unedited real run)
  Diff-aware resume — one query for what changed since you left
  Cheap context recovery at session start

┌─ yesterday: you left some memory behind ─────────────────────────────────────────
│ ▸ Before you logged off, the graph captured a few cells about the work.
│ $ recall search 'sessions' --db $DB | jq cell titles
│     [decision] Use Postgres for sessions
│     [risk] Session store is the bottleneck
│ you close the laptop here — that timestamp is your 'since' marker

┌─ overnight: the picture changes ─────────────────────────────────────────
│ ▸ New evidence lands, and one earlier decision gets corrected.
│ $ recall hyperedge add  # record the supersede edge
│   edge code-superseded-by: Redis supersedes Postgres session decision
│   edge evidence-bundle: Benchmark supports Redis decision

┌─ you return: 'what changed since I left?' ─────────────────────────────────────────
│ ▸ One diff query, scoped to the last hour, recovers the whole delta.
│ 🧑 you → what's new since yesterday?
│ $ python3 recall_diff.py --db $DB --since 1h --summary
│   recall_diff: 5 new, 0 updated, 1 new edges, 1 supersedes (since 2026-06-17T08:30
│   **Summary:** 5 new cells · 0 updated · 1 new edges · 1 supersede events
│   By kind: decision=2, observation=2, risk=1
│   - `3b254f91` [decision] Use Redis for sessions
│   - `f55283c9` [observation] Redis benchmark wins
│   - `34fc6e5c` [risk] Session store is the bottleneck
│   - `77eed3a6` [observation] Latency spike at peak
│   - `03fac3cb` [decision] Use Postgres for sessions
│   - `03fac3cb` → `3b254f91` (code-superseded-by) — Redis supersedes Postgres sessi
│   By kind: evidence-bundle=1
│   - [evidence-bundle] `3b254f91`(claim) → `f55283c9`(verification)

┌─ the delta, broken into four buckets ─────────────────────────────────────────
│ ▸ new_cells, updated_cells, new_hyperedges and supersede_events — each separate.
│ $ python3 recall_diff.py --db $DB --since 1h | jq .summary
│   {"new_cells":5, "updated_cells":0, "new_hyperedges":1, "supersede_events":1}
│ ▸ supersede_events is the retraction — the Postgres call was replaced.
│     retracted: Redis supersedes Postgres session decision

┌─ isolated ─────────────────────────────────────────
│ global graph untouched: 334 -> 334
└──────────────────────────────────────────────────────────────

✦ DONE