Everything validates: syntax OK, all five samples land in their intended states (three high matches at 87/82/80%, one tentative at 60%, one deliberate no-match with a complete copy-ready payload), scope exclusion works, degraded mode is now capped at the tentative tier, and normalization is idempotent. Here is the complete self-contained artifact: Error-triggered lesson recall · ContextStream StreamPilot sandbox

StreamPilot prototype · CLI-first · sandbox-only

Error-triggered lesson recall

Lessons should fire when the failure happens — not at the start of the task. recall pipes a failing command's stderr through your project's ContextStream memory and hands back the fix you already captured, at the exact moment you need it. If nothing matches, the failure is packaged for one-tap capture so it's caught next time. Nothing to configure — it runs in the sandbox with a single command.

CLI-runnable, no UI required Project-scoped hybrid search One-tap capture on miss No production MCP changes
01 · normalize
Raw stderr in
Paths, timestamps, addresses, ports, UUIDs and line numbers become placeholders. Error class and stable tokens survive.
02 · search
Scoped hybrid recall
The stable signature queries lessons and incidents — semantic + keyword, strictly inside the current project.
03 · match
Top-3 with confidence
Each candidate returns its captured fix, a confidence percentage, and a plain-language tier.
04 · capture
Fallback on miss
No useful match → a prefilled save-lesson payload, ready to review. Never saved automatically.
somecmd 2>&1 | recall match --stdin --project streampilot-demo-app

Demo harness

Five failures, one seed command

The harness seeds a small sandbox lesson set (idempotent — re-running never duplicates), then pushes five realistic failures through the full pipeline. Every sample prints the same anatomy: raw excerpt, stable signature, scoped search, result, confidence, and either the captured fix or a capture payload.

seeds 8 sandbox lessons · 7 in scope · deterministic run
streampilot-demo-app — node bin/recall.js demo sandbox fixtures
$ node bin/recall.js demo --project streampilot-demo-app
idle — press Run demo to seed the sandbox lessons and replay the five sample failures.

Interactive matcher

Pipe in an error of your own

Paste any stderr below — or load one of the fixtures — and run it through the same normalizer and matcher the harness uses. Toggle the degraded mode to see how the CLI behaves when ContextStream search is unreachable: it falls back to local keyword-only matching over the seeds and says so, instead of pretending the lesson doesn't exist. Degraded results are capped at the tentative tier — keyword overlap alone never claims high confidence.

project scope locked to streampilot-demo-app
recall match --stdin --project streampilot-demo-app waiting for input
no input yet — pick a fixture above or paste stderr and press Normalize & match.

Component anatomy

Four small parts, one honest pipeline

Everything ships as a standalone CLI in the sandbox. ContextStream search is called through a thin client with a local mock fallback, so the demo runs offline and the production MCP server is never touched.

src/normalizer.js

Error normalizer

An ordered regex pipeline turns volatile noise into inspectable placeholders — deterministic, and idempotent, so normalizing a signature twice changes nothing.

<path><timestamp><addr> <port><uuid><line>

Kept intact:

error classHTTP statusexit codes env var namesfile basenamesstable message tokens

Same failure family, different machine → identical signature. Different failure kinds never collapse into one.

src/matcher.js

Lesson matcher

One hybrid query against ContextStream lesson & incident search, always constrained to the active project. At most three candidates come back, ranked by a blended score:

confidence = 0.50·semantic + 0.35·keyword + 0.15·class
≥ 78%high — lead with the captured fix
55 – 77%possible — shown, flagged as tentative
< 55%weak — treated as no useful match

A weak best-candidate never suppresses the capture fallback, and is never presented as an authoritative fix.

src/fallback.js

No-match fallback

When nothing clears the threshold, the failure is packaged for capture on the spot: a short neutral title built from the error class, the stable signature, a bounded raw excerpt, the project id, and source: error-triggered-recall.

The payload is printed and written to out/pending-lessons/, shaped for one-tap capture by the real save-lesson tool. It is never saved automatically — capture stays a deliberate act.

bin/recall.js

Demo harness & exit codes

Subcommands: seed · match · demo · replay. Human output stays readable at 80 columns; --json emits the same information machine-readably. Search failure is a distinct state — it is never mislabeled as “no match.”

exit 0useful match found exit 2invalid or empty input exit 3no match — payload emitted exit 4search unavailable — degraded keyword-only exit 5configuration failure (unknown project)

Sandbox seed set

Eight seeded entries, seven in scope

recall seed loads these synthetic fixtures into the sandbox index — re-running skips existing ids. One entry deliberately lives in another project to prove scope discipline: it would match sample five perfectly, and the matcher must refuse to use it.

README · replay study

Measuring recall against 30 days of real sessions

The matcher is only worth shipping if it catches failures that actually repeat. The README documents a read-only replay over ~30 days of session transcripts that answers one question: when a failure recurred, did recall surface the lesson we had already captured?

  1. Export transcripts locallyReference ~30 days of session transcripts in a local directory. The replay only ever reads them.
  2. Detect error eventsAn error event is a non-zero exit paired with a stderr block; excerpts are bounded to 400 characters before processing.
  3. Walk chronologically, per projectEvents are processed in timestamp order within each project so history unfolds exactly as it did live.
  4. Time-fence every queryOnly lessons captured before the event's timestamp may participate — a lesson from the future can never inflate the score.
  5. Record outcomesFor each event: top-1 hit, top-3 hit, or no-match, with confidence and candidate ids.
  6. Compare against the observed fixThe suggested fix is checked against the fix actually applied later in the same session — that's the relevance judgment.
  7. Aggregate and discardThe report keeps counts and distributions only; raw transcript content is never persisted.
Time-fenced by construction.

Future lessons cannot leak into earlier events, and every query carries the project id — replay measures what the matcher would really have known at that moment.

Primary metric

Repeat-failure hit rate

repeats with a relevant prior lesson in top-3
÷ repeats where a prior captured fix existed
  • top-1 hit rate
  • false-positive rate (confident but irrelevant)
  • no-match rate
  • confidence distribution per tier
  • normalization collision rate
  • project-scope leakage — must be zero
replay commands
# read-only replay over local transcripts
$ recall replay ./transcripts \
    --project streampilot-demo-app --days 30

# machine-readable aggregate report
$ recall replay ./transcripts --days 30 \
    --json > out/replay-report.json
prototype layout
streampilot-demo-app/
├─ bin/recall.js         seed · match · demo · replay
├─ src/normalizer.js     signature builder
├─ src/matcher.js        hybrid client + blend scoring
├─ src/fallback.js       save-lesson payload generator
├─ src/render.js         80-column terminal output
├─ seeds/lessons.json    8 sandbox lessons, fixes included
├─ samples/*.txt         5 fixture errors
└─ out/pending-lessons/  payloads awaiting capture
report schema — populated by your replay run
{
  "window_days": 30,
  "error_events": ,
  "repeat_failures": ,
  "repeat_failure_hit_rate": ,
  "top1_hit_rate": ,
  "false_positive_rate": ,
  "no_match_rate": ,
  "collision_rate": ,
  "scope_leakage": 0 expected
}