Memory & Capability-Fallback Audit — 2026-04-18

Pod 36. Two related questions from Robert:

  1. Does snappy-os handle memory?
  2. What happens when a capability the agent expects is missing (browser, API, gateway, env)?

Part A — Memory

Two distinct memory systems on this machine

1. Per-project auto-memory (owned by Claude Code harness, NOT snappy-os):

Location: ~/.claude/projects/<project-slug>/memory/*.md

Current state on this machine (Robert's dev box):

Project dirMemory files
-Users-robertboulos-projects-rayFridayUpdate34 (MEMORY.md index + 33 feedback/project files)
-Users-robertboulos-projects-snappyClassroomContent41
-Users-robertboulos--claude15
-Users-robertboulos5
-Users-robertboulos-projects-snappy-os0 (empty)
other 6 project dirsno memory/ subdir

The rayFridayUpdate/memory/MEMORY.md index is 31 lines, each a bullet pointing at a feedback/project file. Auto-memory is strictly per-project per-machine — nothing in snappy-os reads or writes these files at runtime. The skool-partner-cta.md skill is the only file that even mentions the path (state/skills/skool-partner-cta.md:124).

2. Snappy-os's own memory — the state/log/*.ndjson append-only logs:

Top 5 log files by size:

6.6M   symlink-runs.ndjson
896K   lint-sync-integrity-gateway.ndjson
428K   lint-symlink-check.ndjson
268K   frictions.ndjson
260K   migrations.ndjson
176K   evals.ndjson
144K   sync-events.ndjson

These ARE the memory: every skill run logs to chain.ndjson, every eval to evals.ndjson, every defect to frictions.ndjson. The PID loop (state/lint/pid-trends.ts) reads them to decide what regenerates next.

3. Loader rewrites as slow memory. When a loader falls short, ~/.claude/logs/snappy-os-loader-feedback.log gets a line, and the PID loop promotes the rule into a skill's Critical Rules (program.md "per-turn loader sidecars" section). Memory that modifies the system itself.

The memory-consolidation skill (state/skills/memory-consolidation.md)

Cross-machine memory

NOT persisted


Part B — Capability fallback

Dependency inventory

DependencyDetection?Degrade strategyFiles
.env.cacheyes (state/lib/env.ts:28)console.error + return {}; env(key) then throws with a clear "Create .env.cache at project root or symlink" messagestate/lib/env.ts:49-56
Gateway (skills.snappy.ai)yes (curl --max-time 3, content length check)Falls back to local repo ~/projects/snappy-os/state/skills/ — hook still works offline~/.claude/hooks/snappy-os-inject.sh:57-74
HTTP 5xx / retriesyes (built-in retry loop)Retry N times with delay, then throwstate/lib/http.ts:21-36
Browser (agent-browser CLI → Playwright)partial — see belowpartialstate/lib/browse.ts, linkedin-fetcher.ts, skool-fetcher.ts
Anthropic/OpenRouter API keysyes (via env("ANTHROPIC_API_KEY"))env() throws missing-credential error; no rate-limit handling in dispatch.ts beyond provider defaultstate/lib/dispatch.ts:18-160

Browser-dependent skills: 30+ files reference browser/playwright/agent-browser

Production entry point for browser is state/lib/browse.ts (320 lines) wrapping the agent-browser homebrew CLI (which bundles Playwright: ~/Library/Caches/ms-playwright/chromium-* present on this box).

Preflight pattern — inconsistent:

Verified: which agent-browser returns /opt/homebrew/bin/agent-browser on this machine. Preflight failures would manifest on a Joe-machine without Homebrew or without agent-browser installed.

Runtime/CLI absence (well-handled)

state/lib/runtime.ts has the best pattern in the repo: which(bin) check + explicit mode: "absent" return (lines 37-44, 89-91, 101-103, 149-150, 154-157). Every runtime wrapper (claude, codex, gemini, openclaw, cursor, windsurf) preflights via command -v and returns { mode: "absent", exit: 127, stderr: "<bin> not in PATH" } without crashing. This is what browser should look like.

Graceful-degrade count

grep fallback|degrade|offline|preflight|isAvailable across state/lib/ + state/bin/: 25 files match. Majority are file-existence checks (existsSync) returning [] or default. No central "capability registry."

No live probe possible for inbox-sweep --dry-run

state/bin/inbox-sweep/run.ts does not exist (skill is prose; no sidecar yet). Task asks for a probe; skipping rather than inventing a false command.


Gaps (concrete, with paths)

  1. state/lib/browse.ts:26runBrowser has no agent-browser binary preflight. A Joe-machine without Homebrew crashes with "command not found" instead of a clean degrade. Fix: copy the which(bin) pattern from state/lib/runtime.ts:37-44, return { ok: false, reason: "agent-browser not in PATH" } and let callers skip gracefully.
  1. state/skills/memory-consolidation.md — does not specify which memory directory to read. On a multi-project machine (11 .claude/projects/ dirs here), the cron either hits one hardcoded path or iterates without guidance. The skill also ignores snappy-os's own state/log/*.ndjson memory entirely — consolidation is scoped to markdown files only, so the 6.6MB symlink-runs.ndjson never gets pruned.
  1. No ANTHROPIC_API_KEY rate-limit / quota degrade in state/lib/dispatch.ts. dispatch.ts:160 reads env("OPENROUTER_API_KEY"). Provider 429s propagate as thrown errors — no retry, no fallback to a secondary provider, no cost-cap check before dispatch. Every orchestrator relying on dispatch crashes when the quota hits; nothing in the eval log captures "skipped because quota exceeded" vs "failed for real."

Verdict

Memory: snappy-os has TWO memory systems. state/log/*.ndjson is the machine's own append-only memory, synced cross-machine via the gateway. ~/.claude/projects/*/memory/*.md is Claude-Code-harness auto-memory, per-machine only, and NOT synced by snappy-os. The memory-consolidation skill only touches the second kind — the ndjson logs grow unbounded (see 6.6MB symlink-runs).

Capability-fallback: the shape is right (env → explicit throw; gateway → local fallback; sweep → per-channel error isolation; runtime.ts → which preflight). Execution is uneven: browse.ts trusts agent-browser exists, dispatch.ts trusts provider quota exists, memory-consolidation doesn't know which dir. Fix the three gaps above and the system degrades cleanly on a bare Joe-machine.