Memory pipeline: capture to distill to recall to effectiveness¶
Argus's universal memory hub. Capture is pure transport (no LLM call); distillation and dedup are the two LLM-gated write paths; recall has two independent entry points (context injection at session start, and relevance-gated prompt injection per turn); effectiveness is an observational grading loop, not an automatic feedback loop -- nothing here writes graded results back into the ranking weights.
flowchart TD
CAP["Capture hook\n(Stop/SessionEnd/PreCompact)"] --> POST["POST /capture"]
POST --> STORE["Store.Capture"]
STORE --> DUPE{"dedup_key\nalready seen?"}
DUPE -->|yes| RETURN["return existing id\nduplicate=true"]
DUPE -->|no| RAW[("raw_items")]
RAW --> SEG["segment.go:\nturns -> episodes\n(size/time-gap/topic-shift)"]
SEG --> PROC["ProcessPending\n(advisory-lock orchestrator)"]
PROC --> GATE{"distill.go/gate.go:\npoison / grounding /\ntitle-length checks"}
GATE -->|fail| REJ[("gate_rejections")]
GATE -->|pass| EMB["embed.go:\nembedNotesBatch (768-d)"]
PROC -->|"infra/content error"| PARK{"DistillFailureCap\nexceeded (5)?"}
PARK -->|yes| DEAD[("distill_failures\ndead-letter")]
DEAD -.->|"UnparkUnit"| PROC
EMB --> DEDUP["dedup.go:dedupOne\ncosine kNN, floor 0.80"]
DEDUP -->|distinct| KEEPBOTH["keep both, log only"]
DEDUP -->|duplicate| TOMBNEW["tombstone new"]
DEDUP -->|refine| REPLACE["keep new, tombstone old\nconfidence bump"]
DEDUP -->|"supersede,\nold confidence < 0.70"| SUPER["tombstone old"]
DEDUP -->|"supersede,\nold confidence >= 0.70"| FLAG["flagged for\nhuman review"]
KEEPBOTH --> NOTES[("distilled_notes")]
REPLACE --> NOTES
SUPER --> NOTES
NOTES --> CTX["ContextNotes:\nSessionStart, deterministic\nimportance/recency, limit 12"]
CTX --> INJ1["ContextInject\nprompt_mode=false"]
NOTES --> PR["PromptRecall:\nUserPromptSubmit"]
PR --> FLOOR{"substanceScore < 5\nor system-turn tag?"}
FLOOR -->|yes, skip| SKIP1["no recall call"]
FLOOR -->|no| HYBRID["HybridRecall:\nsemantic + FTS, RRF fuse"]
HYBRID --> BOOST["type-prior -> recency ->\nproject-boost -> co-retrieval ->\nimportance-demotion -> rerank"]
BOOST --> CUTOFF["adaptive-K cutoff\nor MMR diversify"]
CUTOFF --> INJ2["RecallInject\nprompt_mode=true"]
INJ2 --> CFLOOR{"client-side\nrelevance floor 0.015?"}
CFLOOR -->|below| DROP["dropped before\nadditionalContext"]
CFLOOR -->|above| EMIT["emitted as\nadditionalContext"]
INJ1 --> INJROW[("injections")]
INJ2 --> INJROW
INJROW --> GRADE["POST /recall/grade:\nGradeRecall (scheduled)"]
GRADE --> JUDGE["judgeRelevance:\nLLM precision judge"]
GRADE --> PROBE["miss-probe: wide rerun,\njudge non-injected notes"]
JUDGE --> HIST[("recall_grading\n+ recall_grade_history")]
PROBE --> HIST
HIST --> ROLLUP["InjectionRollup:\nSessionsWithNotesPct,\nDeadWeightPct, HotNotes"]
ROLLUP --> DASH["/memory/effectiveness.json"]
DASH -.->|"human reads dashboard,\nissues PATCH /config"| TUNE["config_store.go:\nrecall_floor, project_boost,\ndedup_threshold"]
TUNE -.-> HYBRID
Node annotations¶
Capture hook / POST /capture¶
cmd/capture-hook/main.go reads the transcript JSONL by byte offset,
filters tool blocks and poison text, and posts to POST /capture ->
internal/memory/server.go:handleCapture -> internal/memory/capture.go:
Store.Capture (L105). source is one of claude-code, codex,
telegram, task, bookmark, mcp-http; kind is one of turn,
document, event, synthesis (validSources/validKinds, L50-64). Pure
transport: no LLM call at this stage.
dedup_key check¶
capture.go:105-140. A matching dedup_key returns the existing row id
with duplicate=true rather than inserting again. deriveProject (L88)
uses the explicit project field if given, else path.Base(cwd).
segment.go¶
internal/memory/segment.go, invoked from /process. Groups raw turns into
episodes using a size cap (20), a time gap (30 minutes), or a topic-shift
cosine boundary -- sitting between capture and distillation.
ProcessPending¶
internal/memory/process.go:ProcessPending (L88), self-excluded via a
Postgres advisory lock (processAdvisoryLock, L67) so only one worker
distills at a time. Branches on unit type: closed episodes (kind=turn, whole
episode as context) vs non-turn raw items (processed per-item).
gate checks¶
Poison-regex, grounding (source_ref must be a verbatim span of the
source), and title-length checks in distill.go/gate.go. Rejections go to
gate_rejections -- never silently dropped.
DistillFailureCap / dead-letter¶
process.go:23 (DistillFailureCap=5). Infra vs content errors are
classified separately; a unit that keeps failing is parked in
distill_failures (retryable via UnparkUnit, L549) rather than retried
forever inline.
embed.go¶
internal/memory/embed.go:embedNotesBatch. 768-dimension embeddings
(EmbedDim=768), batched, one transaction per batch, called from within
ProcessPending.
dedup.go: dedupOne¶
internal/memory/dedup.go:dedupOne (L140). Exact-duplicate pre-check
(L162), then cosine kNN over live notes above dedupSimilarityFloor=0.80
(L27-36), judged by an LLM into one of four verdicts (L250-330):
distinct (keep both), duplicate (tombstone the new note),
refine (keep new, tombstone old, bump confidence), supersede
(tombstone old unless its confidence is >= dedupFlagConfidence=0.70,
L323, in which case it is flagged for human review in review.go instead of
auto-applied). Every verdict is logged to dedup_log.
ContextNotes (session-start recall)¶
cmd/recall-hook/main.go (context mode) -> GET /recall/context ->
internal/memory/context.go:ContextNotes. Deterministic: importance DESC,
then valid_from DESC, own-project-first-then-global, capped at 12.
inject.go:ContextInject (L45) logs an injections row with
prompt_mode=false.
PromptRecall (per-turn recall)¶
cmd/recall-hook/main.go (prompt mode) ->
internal/memory/prompt_hook.go:PromptRecall (L205). Gated in
recallMemoryLines (L280): system-turn tags (<task-notification,
<system-reminder) skip recall outright (L294); a substanceScore below
promptSubstanceFloor=5 also skips (L308).
HybridRecall¶
internal/memory/hybrid.go:HybridRecall (L159), reached via
POST /recall/notes -> server.go:handleRecallNotes (L323). Fuses a
semantic (pgvector cosine) track and a keyword (FTS) track by
reciprocal-rank fusion (rrfK=60), then applies, in order: type-prior,
recency half-life, same-project boost (DefaultProjectBoost=1.25, itself
floor-gated so it can reorder but never admit a sub-floor note, L285),
structural co-retrieval boost (co_retrieval.go, same floor gate, L292),
importance demotion (DefaultImportanceWeight=0.5, L298), an optional
reranker (rerank.go), then either an adaptive-K cutoff (L318) or MMR
diversification (mmrSelect, L339) for the final cap.
Client-side relevance floor¶
inject.go:RecallInject (L20) logs every call (even zero-hit) to
injections (L31, prompt_mode=true). Back in the hook client, a second
gate (PromptRelevanceFloor=0.015) drops any still-sub-floor notes
(recallMemoryLines, L361-363) before they reach
hookSpecificOutput.additionalContext.
GradeRecall (effectiveness)¶
internal/memory/recall_grade.go:GradeRecall (L124), reached only via
POST /recall/grade -- not called automatically from ProcessPending or
HybridRecall. For up to recallGradeBudget=10 ungraded injections:
judgeRelevance (L70) LLM-judges precision of what was injected; a
miss-probe (L185-227) reruns HybridRecall wide
(missProbeLimit=20) and judges the notes that were not injected, to
count relevant-but-dropped misses.
InjectionRollup / effectiveness.json¶
Results persist to recall_grading and roll into recall_grade_history
(project + global trend, L259-274), surfaced via RecallGradeTrend (L296),
RecallCallRates (hit rate / noise rate, L354), and
audit_rollup.go:InjectionRollup (SessionsWithNotesPct,
DeadWeightPct, HotNotes), feeding /memory/effectiveness.json
(ARGUS-614).
The loop is not closed automatically¶
Nothing in the code path writes grading results back into hybrid.go's
ranking weights or config_store.go's tunables (recall_floor,
project_boost, dedup_threshold, read via ConfigValues/
configDefault, L46-64). Those only change via a human-issued
PATCH /config after reading the dashboard -- effectiveness here is a
human-in-the-loop calibration signal, not an auto-tuning loop.
Documentation drift found (and left for a human to fix)¶
docs/reference/MEMORY-PIPELINE.mdstatespromptHookMaxNotes = 5(constants table) and "top-5 injected"; the code (prompt_hook.go, L38) has raised this to 12 (comment cites ARGUS-37/75, 5 -> 8 -> 12). The doc's constants table is stale.internal/memory/recall.go:Recall(keyword-only FTS overraw_items) is a legacy/simple path explicitly superseded byhybrid.go's notes-based hybrid recall, per its own code comment -- still reachable atGET /recallbut not part of the hook-driven pipeline the docs describe.
Related pages¶
- Notification/push fan-out -- not directly wired to this pipeline; memory events do not currently push.