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
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
- Collaboration Modes
- Tool Composition
- How It Works
- Architecture
- Quickstart
- MCP Tools Reference
- Testing
- Configuration
- Contributing
- License
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:
- BM25 file-level search -- ranks candidate files by textual relevance to the decision description
- Symbol expansion -- extracts all symbols from top-ranked files via tree-sitter
- 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:
- Detects your repo (from cwd or prompts you)
- Installs the MCP config into Claude Code using your
pipxbinary 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
268e3e384256a15db5fc773aeedd8a184319940681a9e719de5ab79049bc9bdf
|
|
| MD5 |
cf61d9c594fde0ae7bb5cba28fa7b3a9
|
|
| BLAKE2b-256 |
5e63926c6a541af82cc92c068ec9a2c369914c7168c3d4c1e3d7ad27adc48f52
|
Provenance
The following attestation bundles were made for bicameral_mcp-0.4.5.tar.gz:
Publisher:
publish.yml on BicameralAI/bicameral-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bicameral_mcp-0.4.5.tar.gz -
Subject digest:
268e3e384256a15db5fc773aeedd8a184319940681a9e719de5ab79049bc9bdf - Sigstore transparency entry: 1293024304
- Sigstore integration time:
-
Permalink:
BicameralAI/bicameral-mcp@0617e7a61f383c893a7f61fda82c75c896305799 -
Branch / Tag:
refs/tags/v0.4.5 - Owner: https://github.com/BicameralAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0617e7a61f383c893a7f61fda82c75c896305799 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c2b02c3c4346d03ddd721c3868d68b774f78dac5a832709cc5395ee162f452c
|
|
| MD5 |
8c0fff819c57d229ee217a3fc5348ec6
|
|
| BLAKE2b-256 |
f13834b43d1231e03fbdd7eb6e27e0682eb4ab72c93b151c46715451e751f0a8
|
Provenance
The following attestation bundles were made for bicameral_mcp-0.4.5-py3-none-any.whl:
Publisher:
publish.yml on BicameralAI/bicameral-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bicameral_mcp-0.4.5-py3-none-any.whl -
Subject digest:
5c2b02c3c4346d03ddd721c3868d68b774f78dac5a832709cc5395ee162f452c - Sigstore transparency entry: 1293024373
- Sigstore integration time:
-
Permalink:
BicameralAI/bicameral-mcp@0617e7a61f383c893a7f61fda82c75c896305799 -
Branch / Tag:
refs/tags/v0.4.5 - Owner: https://github.com/BicameralAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0617e7a61f383c893a7f61fda82c75c896305799 -
Trigger Event:
release
-
Statement type: