Skip to main content

Decision ledger MCP server — ingests meeting transcripts, maps decisions to code, tracks drift

Project description

    ██████╗ ██╗ ██████╗ █████╗ ███╗   ███╗███████╗██████╗  █████╗ ██╗
    ██╔══██╗██║██╔════╝██╔══██╗████╗ ████║██╔════╝██╔══██╗██╔══██╗██║
    ██████╔╝██║██║     ███████║██╔████╔██║█████╗  ██████╔╝███████║██║
    ██╔══██╗██║██║     ██╔══██║██║╚██╔╝██║██╔══╝  ██╔══██╗██╔══██║██║
    ██████╔╝██║╚██████╗██║  ██║██║ ╚═╝ ██║███████╗██║  ██║██║  ██║███████╗
    ╚═════╝ ╚═╝ ╚═════╝╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝

        ┌────────────────┐                    ┌────────────────┐
        │   DECISIONS    │  ◀── drift ──▶    │      CODE      │
        │   what was     │    detection       │  what actually │
        │     said       │                    │     shipped    │
        └────────┬───────┘                    └────────┬───────┘
                 │           ┌──────────┐              │
                 └──────────▶│  ledger  │◀─────────────┘
                             └──────────┘
                         local · deterministic

Bicameral MCP

PyPI version Python License: MIT CI

A provenance-aware decision layer for your codebase -- paste a transcript, get a living map of what was decided and what was actually built.

Bicameral MCP is a local-first Model Context Protocol server that ingests meeting transcripts, PRDs, and design documents, builds a structured graph of decisions mapped to code symbols, and continuously tracks whether those decisions are reflected, drifting, or lost as the codebase evolves. No data leaves your machine. No LLM required -- all retrieval is deterministic. No API keys needed.


Table of Contents


The Problem

Every software team makes hundreds of verbal decisions per week -- in meetings, PRDs, Slack threads, and huddles. None of those decisions are linked to the code that implements them. This disconnect creates five specific SDLC friction points:

# Smell What Happens Bicameral Fix
1 CONSTRAINT_LOST A rate limit or compliance rule surfaces mid-sprint instead of at design time bicameral.search -- pre-flight context before coding
2 CONTEXT_SCATTERED The "why" behind a decision is split across Slack, Notion, and someone's memory bicameral.ingest -- normalizes intent from any source into a unified graph
3 DECISION_UNDOCUMENTED A verbal "let's do X" never lands in a ticket or ADR bicameral.status -- tracks what was decided vs. what was built
4 REPEATED_EXPLANATION Same context tax paid twice -- once to design, once to engineering bicameral.search -- retrieves full decision provenance on demand
5 TRIBAL_KNOWLEDGE Only one person knows why the system works the way it does bicameral.drift -- surfaces institutional memory tied to code

Bicameral's core value is drift detection -- knowing that a decision made three weeks ago is now inconsistent with what actually shipped, or that a decision made today is inconsistent with the codebase reality.


Collaboration Modes

Bicameral runs in one of two modes, set during bicameral-mcp setup or in .bicameral/config.yaml:

Solo (default) Team
Who Individual testing or evaluation Any mix of roles -- devs, PMs, designers
Data Local DB only Local DB + git-committed event files
Shared via Nothing -- fully isolated, zero impact on teammates Normal git push / git pull
Merge conflicts N/A Zero -- per-user directories, append-only files

Solo mode is ideal for trying Bicameral without affecting your team's workflow. All data stays in a gitignored local DB -- no event files, no commits, no side effects. Switch to team mode when you're ready to share.

Team mode enables cross-role collaboration through git. A PM ingests a PRD and sprint transcript; when a developer pulls, bicameral.search surfaces those decisions as coding context and bicameral.status shows what still needs implementation. The PM never touches the code; the developer never sits through the meeting. The decision graph is the handoff.

.bicameral/
├── events/              ← committed to git (shared decisions)
│   ├── pm@co.com/       ← PM's ingested PRDs and transcripts
│   └── dev@co.com/      ← developer's commit syncs
├── config.yaml          ← committed (mode: solo | team)
└── local/               ← gitignored (materialized state)

Switching modes: Set mode: team or mode: solo in .bicameral/config.yaml. No data migration needed.


Tool Composition

The nine tools compose into three workflows that follow the natural lifecycle of a decision — captured in a meeting, pulled as context during coding, checked at review time. Each workflow below uses the same running example (a checkout-flow sprint) so you can see a single decision move through the pipeline.

1. Ingestion — after a meeting

Scenario: Your PM wraps a 30-minute sprint planning in #product-planning. The transcript contains three decisions. You paste it into Claude and say "ingest this."

// bicameral.ingest
{
  "source": "slack",
  "title": "Sprint 14 Planning — 2026-03-12",
  "decisions": [
    { "title": "Apply 10% discount on orders over $100",
      "description": "Marketing confirmed at offsite. No upper bound." },
    { "title": "Cache user sessions in Redis, not local memory",
      "description": "Arch review: local memory breaks horizontal scaling." },
    { "title": "Rate-limit checkout to 100 req/min per user",
      "description": "Legal/compliance ask. Not yet built." }
  ]
}

Outcome. The discount rule and the Redis session decision anchor to real symbols (pricing/discount.py:DiscountService.calculate, auth/session_store.py:SessionStore.put) and are born reflected. Auto-grounding can't find code for the rate-limit rule — because it hasn't been written yet — so it lands as ungrounded. The ledger now knows a decision exists with no corresponding code, and the next bicameral.status call will show exactly that.


2. Pre-flight — before writing new code

Scenario: A dev picks up the ticket "add rate limiting to checkout." Before writing a single line, they ask Claude for context.

// bicameral.search
{ "query": "rate limit checkout", "max_results": 5 }

Outcome. Before writing any code, the dev sees the prior rate-limit decision with its compliance rationale, learns that it's still ungrounded (so they're the first implementer), and discovers an adjacent pricing/discount.py:DiscountService.calculate region their new code will need to coexist with. No re-litigating a decided rule, no Slack archaeology, no ambushing the PM in standup tomorrow.


3. Code review — before merging

Scenario: Three weeks later, a different dev opens PR #241 with a 50-line diff touching pricing/discount.py. Reviewer asks Claude "any drift in this file?"

// bicameral.drift
{ "file_path": "pricing/discount.py", "use_working_tree": false }

Outcome. The reviewer learns that DiscountService.calculate:42-67 has drifted from the Sprint 14 Planning decision — threshold raised $100 → $500, rate lowered 10% → 5%. Either the change is intentional, in which case a new decision must be ingested before merge, or it's accidental and gets reverted. The conversation happens at PR time, not three sprints later in an incident post-mortem.


How It Works

Status Derivation Model

Decision status is a pure function computed at query time -- never stored. This is the key differentiator: because status is derived from (intent, git_ref), Bicameral is immune to rebase, squash, and cherry-pick. There is no stale state to reconcile.

Condition Status Meaning
No code_region mapped ungrounded Intent captured, but no matching code found
Symbol absent at git ref pending Code not yet written
content_hash differs drifted Code changed since the decision was recorded
content_hash matches reflected Code matches intent

Auto-Grounding

When decisions are ingested, Bicameral automatically attempts to anchor them to code:

  1. BM25 file-level search -- ranks candidate files by textual relevance to the decision description
  2. Symbol expansion -- extracts all symbols from top-ranked files via tree-sitter
  3. Fuzzy token matching -- matches decision terminology against the symbol index using rapidfuzz

This is a deterministic, two-stage retrieval pipeline. No embeddings, no LLM calls.


Architecture

Bicameral is composed of three layers:

Layer diagram and data flow
                        MCP Client (Claude Code, etc.)
                                    |
                              stdio transport
                                    |
                    +-------------------------------+
                    |        MCP Server Layer        |
                    |          (server.py)           |
                    |   9 tools, stdio transport,    |
                    |   Pydantic response contracts  |
                    +-------+---------------+-------+
                            |               |
              +-------------+               +-------------+
              |                                           |
   +----------v-----------+                 +-------------v-----------+
   |   Decision Ledger    |                 |      Code Locator       |
   |      (ledger/)       |                 |    (code_locator/)      |
   |                      |                 |                         |
   |  SurrealDB embedded  |                 |  tree-sitter parsing    |
   |  Graph: intent -->   |                 |  BM25 text search       |
   |    maps_to -->       |                 |  RRF fusion ranking     |
   |    symbol -->        |                 |  Structural graph       |
   |    implements -->    |                 |  traversal              |
   |    code_region       |                 |                         |
   +----------------------+                 +-------------------------+

Data flow:

Operation Flow
Ingest Transcript/PRD --> bicameral.ingest --> SurrealDB graph (intents, symbols, code_regions, edges)
Sync Code change --> bicameral.link_commit --> content hash update, drift re-evaluation
Query bicameral.status / drift / search --> derives status from (intent, git_ref) at query time

Supported languages (tree-sitter grammars): Python, JavaScript, JSX, TypeScript, TSX, Java, Go, Rust, C#

Core Technologies

Component Technology Role
Decision store SurrealDB v2 (embedded, in-process) Graph storage for intents, symbols, code regions, and edges
Symbol extraction tree-sitter (9 language grammars) AST-level function/class extraction
Text search BM25 via bm25s File and symbol ranking
Fuzzy matching rapidfuzz Token-level matching for auto-grounding
Response types Pydantic v2 Strict MCP response contracts
Transport MCP protocol (stdio) IDE/agent integration

Quickstart

One-command setup

pipx install bicameral-mcp
bicameral-mcp setup

This launches an interactive wizard that:

  1. Detects your repo (from cwd or prompts you)
  2. Installs the MCP config into Claude Code using your pipx binary path

That's it. The server builds its code index on first tool call.

Manual config

Run from your repo root:

# Install
pipx install bicameral-mcp

# Add to Claude Code
claude mcp add-json bicameral --scope local '{
  "command": "bicameral-mcp",
  "args": [],
  "env": {
    "REPO_PATH": "/path/to/your/repo",
    "SURREAL_URL": "surrealkv:///path/to/your/repo/.bicameral/ledger.db"
  }
}'

Local development

# Option A: pipx editable install (puts bicameral-mcp on PATH, uses local source)
pipx install -e . --force

# Option B: pip editable install (venv-local, includes test deps)
pip install -e ".[test]"

After either option:

bicameral-mcp setup           # interactive config
bicameral-mcp --smoke-test    # verify all 9 tools register correctly
bicameral-mcp                 # start MCP server (stdio)

Verify installation

bicameral-mcp --smoke-test

Expected output:

bicameral-mcp 0.2.13 smoke test passed
bicameral.status
bicameral.search
bicameral.drift
bicameral.link_commit
bicameral.ingest
validate_symbols
search_code
get_neighbors
extract_symbols

No LLM provider credentials needed -- all retrieval is deterministic.


MCP Tools Reference

Ledger Tools (5)

Tool Purpose
bicameral.status Surface implementation status of all tracked decisions (reflected / drifted / pending / ungrounded)
bicameral.search Pre-flight: find past decisions relevant to a feature before writing code. Auto-syncs to HEAD.
bicameral.drift Code review: surface decisions that touch symbols in a file, flagging divergence
bicameral.link_commit Sync a commit into the ledger -- updates content hashes, re-evaluates drift. Idempotent.
bicameral.ingest Ingest a normalized source payload (transcript, PRD, Slack export) and advance the source cursor

Code Locator Tools (4)

Tool Purpose
validate_symbols Fuzzy-match candidate symbol names against the code index. Returns confidence scores and symbol IDs.
search_code BM25 text search + structural graph traversal with RRF fusion. Optionally seed with symbol IDs.
get_neighbors 1-hop structural graph traversal around a symbol (callers, callees, imports, inheritance).
extract_symbols Tree-sitter symbol extraction from a source file. No index required.
Full tool input schemas

bicameral.status

{
  "type": "object",
  "properties": {
    "filter": {
      "type": "string",
      "enum": ["all", "drifted", "pending", "reflected", "ungrounded"],
      "default": "all",
      "description": "Filter decisions by status"
    },
    "since": {
      "type": "string",
      "description": "ISO date — only decisions ingested after this date"
    },
    "ref": {
      "type": "string",
      "default": "HEAD",
      "description": "Git ref to evaluate against"
    }
  }
}

bicameral.search

{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Natural language description — e.g. 'add retry with backoff'"
    },
    "max_results": {
      "type": "integer",
      "default": 10
    },
    "min_confidence": {
      "type": "number",
      "default": 0.5,
      "description": "Minimum BM25 confidence score (0-1)"
    }
  },
  "required": ["query"]
}

bicameral.drift

{
  "type": "object",
  "properties": {
    "file_path": {
      "type": "string",
      "description": "File path relative to repo root"
    },
    "use_working_tree": {
      "type": "boolean",
      "default": true,
      "description": "True = compare against disk (pre-commit), False = compare against HEAD"
    }
  },
  "required": ["file_path"]
}

bicameral.link_commit

{
  "type": "object",
  "properties": {
    "commit_hash": {
      "type": "string",
      "default": "HEAD",
      "description": "Git commit hash or ref to sync (default: HEAD)"
    }
  }
}

bicameral.ingest

{
  "type": "object",
  "properties": {
    "payload": {
      "type": "object",
      "description": "Normalized ingest payload matching the internal code-locator handoff shape"
    },
    "source_scope": {
      "type": "string",
      "default": "default",
      "description": "Source stream identifier, e.g. Slack channel or Notion database"
    },
    "cursor": {
      "type": "string",
      "description": "Optional upstream checkpoint (timestamp, event id, updated_at)"
    }
  },
  "required": ["payload"]
}

validate_symbols

{
  "type": "object",
  "properties": {
    "candidates": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Symbol name hypotheses to validate (e.g. ['CheckoutController', 'processOrder'])"
    }
  },
  "required": ["candidates"]
}

search_code

{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Text search query (e.g. 'checkout rate limit middleware')"
    },
    "symbol_ids": {
      "type": "array",
      "items": { "type": "integer" },
      "description": "Symbol IDs from validate_symbols to use as graph traversal seeds"
    }
  },
  "required": ["query"]
}

get_neighbors

{
  "type": "object",
  "properties": {
    "symbol_id": {
      "type": "integer",
      "description": "Symbol ID from validate_symbols results"
    }
  },
  "required": ["symbol_id"]
}

extract_symbols

{
  "type": "object",
  "properties": {
    "file_path": {
      "type": "string",
      "description": "Absolute or repo-relative path to the source file"
    }
  },
  "required": ["file_path"]
}

Testing

Bicameral has 42 test files organized into three phases, all using real adapters with SURREAL_URL=memory:// (embedded, in-process SurrealDB -- no external services required).

pip install -e ".[test]"
pytest tests/ -v
Phase Entry Point Scope
Phase 1 test_phase1_code_locator.py Code locator tools against a real indexed repository
Phase 2 test_phase2_ledger.py SurrealDB ledger adapter with memory:// -- CRUD, graph traversal, cursor management
Phase 3 test_phase3_integration.py Full end-to-end: structured around the 5 SDLC failure modes

Additional test suites cover adversarial inputs (test_stress_adversarial.py), failure modes (test_stress_failure_modes.py), grounding state machine gaps (test_grounding_state_machine_gaps.py), and server smoke tests (test_server_smoke.py).

Phase 3 tests produce JSON artifacts (test-results/e2e/) with full tool responses and SurrealDB graph dumps for qualitative review. CI runs via GitHub Actions on PRs to main, with JUnit XML and HTML reports uploaded as artifacts.


Configuration

Variable Default Description
REPO_PATH . Path to the repository being analyzed
SURREAL_URL surrealkv://~/.bicameral/ledger.db SurrealDB connection URL. Use memory:// for tests (no persistence).
CODE_LOCATOR_SQLITE_DB (auto) Optional override for the local code index database path

All data is stored locally. The embedded SurrealDB instance runs in-process -- no separate database server to manage.


Contributing

Contributions are welcome. To get started:

git clone https://github.com/BicameralAI/bicameral-mcp.git
cd bicameral-mcp
pip install -e ".[test]"
pytest tests/ -v

Please open an issue before submitting large changes.


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bicameral_mcp-0.4.5.tar.gz (147.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bicameral_mcp-0.4.5-py3-none-any.whl (135.4 kB view details)

Uploaded Python 3

File details

Details for the file bicameral_mcp-0.4.5.tar.gz.

File metadata

  • Download URL: bicameral_mcp-0.4.5.tar.gz
  • Upload date:
  • Size: 147.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bicameral_mcp-0.4.5.tar.gz
Algorithm Hash digest
SHA256 268e3e384256a15db5fc773aeedd8a184319940681a9e719de5ab79049bc9bdf
MD5 cf61d9c594fde0ae7bb5cba28fa7b3a9
BLAKE2b-256 5e63926c6a541af82cc92c068ec9a2c369914c7168c3d4c1e3d7ad27adc48f52

See more details on using hashes here.

Provenance

The following attestation bundles were made for bicameral_mcp-0.4.5.tar.gz:

Publisher: publish.yml on BicameralAI/bicameral-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file bicameral_mcp-0.4.5-py3-none-any.whl.

File metadata

  • Download URL: bicameral_mcp-0.4.5-py3-none-any.whl
  • Upload date:
  • Size: 135.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bicameral_mcp-0.4.5-py3-none-any.whl
Algorithm Hash digest
SHA256 5c2b02c3c4346d03ddd721c3868d68b774f78dac5a832709cc5395ee162f452c
MD5 8c0fff819c57d229ee217a3fc5348ec6
BLAKE2b-256 f13834b43d1231e03fbdd7eb6e27e0682eb4ab72c93b151c46715451e751f0a8

See more details on using hashes here.

Provenance

The following attestation bundles were made for bicameral_mcp-0.4.5-py3-none-any.whl:

Publisher: publish.yml on BicameralAI/bicameral-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page