[trace-mcp]

Decision memory

trace-mcp includes a persistent decision knowledge graph that captures architectural decisions, tech choices, bug root causes, preferences, and conventions — linked to the code they’re about.

Why

Every conversation with an AI agent produces decisions that disappear when the session ends. Six months of daily AI use = thousands of decisions lost. General-purpose memory tools (MemPalace, OpenMemory, Mem0) store these as text. trace-mcp stores them linked to code symbols and files — so when you ask “what breaks if I change this?”, you also see why it was built that way.

What “why” actually looks like

The system tries to capture the kinds of reasoning that disappear from the diff but matter when someone returns to the code months later:

Each of these is linked to the symbol or file it’s about, so the next agent who touches that code sees the reasoning surface automatically through get_change_impact or plan_turn — they don’t have to know the decision exists to find it.

Architecture

              ┌──────────────────────────────────────────┐
              │              CAPTURE PATHS                │
              │                                            │
  add_decision│  remember_decision   │   mine_sessions     │
  (manual,    │  (live agent write,  │   (post-hoc, scans  │
   conf=1.0)  │   confidence-scored) │    JSONL logs,      │
              │                      │    pattern-matched) │
              └────────┬──────────────┬──────────┬─────────┘
                       │              │          │
                       ▼              ▼          ▼
              ┌──────────────────────────────────────────┐
              │  Memoir-style review queue                │
              │   confidence ≥ 0.75 → active (default)    │
              │   0.45 ≤ conf < 0.75 → pending review     │
              │   confidence < 0.45  → dropped            │
              │   (approve_decision / reject_decision)    │
              └────────────────┬──────────────────────────┘
                               │
                               ▼
              ┌──────────────────────────────────────────┐
              │  Decision Store (decisions.db)            │
              │  SQLite + FTS5 (porter stemming)          │
              │  ┌────────────┐  ┌────────────────────┐   │
              │  │ decisions  │  │  session_chunks    │   │
              │  │ code-linked│  │  cross-session     │   │
              │  │ temporal   │  │  content search    │   │
              │  │ branch-aware│  │                    │   │
              │  └────────────┘  └────────────────────┘   │
              └────────────────┬──────────────────────────┘
                               │
                               ▼
              ┌──────────────────────────────────────────┐
              │  Enrichment Layer                         │
              │  get_change_impact  → linked_decisions    │
              │  plan_turn          → related_decisions   │
              │  get_wake_up(resume) → active_decisions   │
              │  get_wake_up        → orientation context │
              └──────────────────────────────────────────┘

Decision types

Type What it captures Example
architecture_decision Structural choices “Migrating from REST to GraphQL”
tech_choice Technology selections “PostgreSQL over MySQL for JSONB support”
bug_root_cause Why bugs happened “Missing null check in auth middleware”
preference Team/personal preferences “Always use named exports”
tradeoff Acknowledged tradeoffs “Accept higher latency for stronger consistency”
discovery New learnings “Discovered that Prisma doesn’t support CTEs”
convention Coding conventions “From now on, use snake_case for DB columns”

Memoir-style review queue

Not everything an agent observes is worth keeping. The capture pipeline routes every write through a three-tier confidence gate so high-signal decisions enter the graph immediately while borderline ones queue for a human, and noise is dropped:

Confidence Routing review_status
review_threshold (default 0.75) Active — visible in query_decisions by default null (auto-approved)
[reject_threshold, review_threshold) Queued for human approval 'pending'
< reject_threshold (default 0.45) Dropped, not persisted n/a

Both thresholds are configurable via the decisions.review_threshold and decisions.reject_threshold keys in config, or per-call on mine_sessions and remember_decision.

Manage the queue with approve_decision / reject_decision; inspect it with query_decisions { include_pending: true } or query_decisions { review_status: "pending" }. add_decision bypasses the gate (it’s the explicit, human-curated path — confidence is pinned to 1.0).

Confidence scoring

The score that drives the review queue combines a base prior with multiplicative boosts for signals that correlate with usefulness:

Signal Effect
Decision is linked to a symbol_id or file_path + code-ref boost
content length ≥ 200 chars + length boost
At least one tag attached + tags boost
Type is high-signal (architecture_decision, bug_root_cause, tradeoff) + type boost
Decision is scoped to a service_name + service boost

For mined decisions, the pattern’s intrinsic confidence (see Extraction patterns) is additionally multiplied by 1 + 0.05 × n where n is the number of context boosters (because, reason, pros and cons, alternative, architecture, design decision) found in the surrounding turn.

The implementation lives in src/memory/decision-confidence.ts.

MCP tools

Capture

Tool Description
add_decision Manually record a decision. Bypasses the review queue (confidence = 1.0). Accepts title, content, type, service_name, symbol_id, file_path, tags, git_branch.
remember_decision Live agent-write path. Confidence-scores the input and routes through the review queue. Per-session dedup + rate-limit so the agent can’t spam the store. Use during a session to capture decisions in real time.
mine_sessions Scan Claude Code / Claw Code JSONL logs and extract decisions via pattern matching (no LLM calls). Skips already-processed sessions. Results also flow through the review queue.
index_sessions Index conversation content (chunked) for cross-session search. Enables search_sessions.

Review queue

Tool Description
approve_decision Promote a pending decision to active.
reject_decision Mark a pending decision as rejected (kept for audit, hidden by default).

Read & query

Tool Description
query_decisions Query with filters: type, service_name, symbol_id, file_path, tag, search (FTS5), as_of (temporal), git_branch, include_pending, review_status.
invalidate_decision Mark a decision as superseded. It remains in the graph for historical queries.
get_decision_timeline Chronological view of decisions for a project, symbol, or file.
get_decision_stats Overview: total/active/invalidated, by type, by source, mined/indexed session counts.

Search & orientation

Tool Description
search_sessions Full-text search across all past session conversations. “What did we discuss about auth last month?”
get_wake_up Compact orientation (~300 tokens) at session start: project identity + active decisions + memory stats. Auto-mines on first call if the store is empty.
get_wake_up { scope: "resume" } Cross-session context carryover: focus files, key searches, and dead-end queries from recent past sessions, alongside active decisions.

Code linkage

Decisions can be linked to:

When linked, decisions automatically surface in code intelligence tools:

get_change_impact(symbol_id="src/auth/provider.ts::AuthProvider#class")
→ {
    ...impact analysis...,
    linked_decisions: [
      { id: 42, title: "Use Clerk for auth", type: "tech_choice",
        symbol: "src/auth/provider.ts::AuthProvider#class", when: "2025-06-01" }
    ]
  }

Temporal validity

Every decision has a valid_from timestamp (when it was made) and an optional valid_until (when it was superseded):

Query modes:

query_decisions()                              # active only (default)
query_decisions(as_of="2025-01-15T00:00:00Z")  # what was active on Jan 15
query_decisions(include_invalidated=true)       # full history

Service scoping

In projects with multiple services (subprojects), decisions can be scoped to a specific service:

add_decision(
  title="Use JWT for service-to-service auth",
  service_name="auth-api",
  type="tech_choice"
)

query_decisions(service_name="auth-api")     # only auth-api decisions
query_decisions()                            # all project decisions
get_decision_stats()                         # shows available_services

Extraction patterns

The conversation miner uses 8 regex-based patterns to extract decisions from assistant messages:

Pattern Matches Type Confidence
“decided to”, “going with”, “chose X” Architecture choices architecture_decision 0.85
“using X because”, “picked X for” Technology selections tech_choice 0.80
“X instead of Y”, “X over Y” Comparisons tech_choice 0.75
“the bug was”, “root cause”, “caused by” Bug analysis bug_root_cause 0.85
“prefer”, “always use”, “never use” Preferences preference 0.70
“tradeoff”, “downside is” Tradeoffs tradeoff 0.75
“discovered that”, “turns out” Learnings discovery 0.80
“from now on”, “the rule is” Conventions convention 0.80

Context boosters (“because”, “reasoning”, “pros and cons”, “architecture”) increase confidence by 5% each.

Auto-tagging detects topics: auth, database, api, testing, performance, security, devops, typescript, refactoring, migration.

What we deliberately don’t record

Decision memory is for content that disappears when the chat log is gone. It explicitly avoids storing things that can be recovered from the code or git history:

The mining pipeline also filters non-user content before it reaches the store. Block-tagged regions stripped during ingestion:

Tag Reason
<private>…</private> User-curated “do not remember this”
<persisted-output>…</persisted-output> Tool-output capture, often hundreds of KB of file contents
<system-reminder>…</system-reminder> Runtime nudges, not user-authored
<ide_selection>…</ide_selection> IDE selection echo (may contain sensitive code)
<task-notification>…</task-notification> Autonomous protocol payloads from background agents
<local-command-stdout>…</local-command-stdout> Captured shell output (may contain secrets)

<command-message> and <command-name> are kept — those wrap real user slash-commands and are part of the conversation. Implementation: stripPrivacyTags in src/memory/conversation-miner.ts.

CLI

trace-mcp memory mine [--project=.] [--force] [--min-confidence=0.6]
trace-mcp memory index [--project=.] [--force]
trace-mcp memory search "query" [--project=.] [--limit=20]
trace-mcp memory decisions [--project=.] [--type=tech_choice] [--search="query"] [--json]
trace-mcp memory stats [--project=.] [--json]
trace-mcp memory timeline [--project=.] [--file=path] [--symbol=id]

Storage

All decision memory is stored in ~/.trace-mcp/decisions.db (SQLite, WAL mode). Tables:

Key columns on decisions:

Column Purpose
title, content, type The decision itself
project_root, service_name Where it applies (project + optional subproject)
symbol_id, file_path What code it’s about (drives auto-surfacing in impact tools)
tags JSON array for categorization and filtering
valid_from, valid_until Temporal validity (valid_until = NULL means active)
git_branch Branch scoping (NULL = branch-agnostic, visible everywhere)
source 'manual' (added via add_decision), 'mined' (extracted from logs), or 'auto' (live agent write)
confidence 0..1 score driving the review queue (always 1.0 for 'manual')
review_status NULL = auto-approved, 'pending' = awaiting review, 'approved', 'rejected'
session_id Provenance — which session produced this decision

Last updated: August 30, 2026