Give coding agents the decisions that apply to a task
Ask by task or by path. In Claude Code, /mneme:context has Claude run mneme test_query with your task, and an MCP host can call decision.applicable_to on Mneme’s local server with the file it’s editing. That tool matches only a decision’s scope, and the CLI lookup scores a scope match highest, so give each decision a scope word your paths contain.
You’ll end up with: a lookup that returns ADR-012 for any file under app/handlers/, from the CLI that /mneme:context uses and from the Mneme MCP server.
Goal
Guide 2 stops a bad write after the agent has decided to make it. Giving the agent ADR-012 before it starts on app/handlers/invoices.py can save that round trip. Mneme can look up the decisions that match a task or a path. Claude Code reaches that lookup through the plugin's /mneme:context command. Agents that speak MCP (Model Context Protocol), Codex among them, can reach it through Mneme's local MCP server; see Limits for what we tested.
Lookup only informs the agent. Enforcement stays with the hook and CI checks from guides 2 and 4.
Prerequisites
- ADR-012 in
docs/adr/and imported into.mneme/project_memory.json, as in Guide 1. - Mneme 0.9.2 with the optional MCP runtime:
pipx install "mneme-hq[mcp]==0.9.2"
- For the Claude Code path, the plugin loaded as in Guide 2.
Configure
1. Give each decision a scope word that appears in your paths
Lookup matches whole words in the query against each decision's scope, constraints, text and rationale. A match on the scope counts most. There is no stemming, so handler doesn't match handlers. ADR-012 uses scope: handlers because every file it governs lives under app/handlers/:
--- id: ADR-012 title: Request handlers do not import the database client status: accepted priority: foundational date: "2026-10-01" scope: handlers ---
For the CLI lookup, words shorter than four letters never match, so api can't work as a scope there. Give every accepted ADR its own scope, too: when two share one, the import keeps only one of them (Guide 1 shows it). A dotted scope such as handlers.invoices keeps related ADRs apart, with one catch for MCP, covered in Troubleshooting.
2. Register the MCP server with your agent
The server runs on your machine and talks over standard input and output. Agents keep MCP servers in their own config files, each with its own format. This is the process definition to put there:
{
"mcpServers": {
"mneme": {
"command": "mneme",
"args": ["decision-mcp", "--proposals", ".mneme/decision_proposals.json", "--adr-dir", "docs/adr"]
}
}
}
--adr-dir loads your ADRs as the decisions the server returns. Paths in args resolve against the folder the host starts the server in, so use absolute paths if your host doesn't start in the repository root. Hosts that don't use this JSON format, such as Codex with its TOML config, take the same command and arguments in their own format. The MCP server reference covers every option.
Run
Claude Code: /mneme:context
Type /mneme:context with a description of the task. The command asks Claude to run mneme test_query with your description. You can run the same lookup yourself, here with the query the Claude Code hook uses for an edit:
mneme test_query --memory .mneme/project_memory.json --query "edit to app/handlers/invoices.py"
Any MCP host: decision.applicable_to
This small client starts the server the way an agent does and calls one tool. Save it as mcp_probe.py in the repository root:
"""Call Mneme's decision-mcp server over stdio, the way an MCP host does.
Usage:
python mcp_probe.py list
python mcp_probe.py <tool-name> '<json arguments>'
"""
import asyncio
import json
import os
import sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
SERVER = StdioServerParameters(
command="mneme",
args=["decision-mcp", "--proposals", "", "--adr-dir", os.environ.get("ADR_DIR", "docs/adr")],
)
async def main(tool: str, arguments: str) -> None:
async with stdio_client(SERVER) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
if tool == "list":
tools = await session.list_tools()
print("\n".join(t.name for t in tools.tools))
return
result = await session.call_tool(tool, json.loads(arguments))
for block in result.content:
print(json.dumps(json.loads(block.text), indent=2, sort_keys=True))
asyncio.run(main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "{}"))
python mcp_probe.py list
python mcp_probe.py decision.applicable_to \
'{"paths": ["app/handlers/invoices.py"]}'
Expected result
The lookup ranks ADR-012 first, matched on its scope, and prints the context an agent receives:
Query: edit to app/handlers/invoices.py
All decisions (ranked by score):
[ADR-012] score=3.50 matched=decision, scope, rationale
Injected (top 3):
[Mneme decisions applied]
DECISION [ADR-012]: Request handlers do not import the database client
Why: Handlers call repositories. A direct client import skips the transaction
and tenancy handling that lives in app/repositories.
## Constraints
- FORBID_LITERAL:
value: from app.db.client import
include_paths:
- "app/handlers/**"
Scope: handlers
Typed rules:
- FORBID_LITERAL: from app.db.client import
Applies to: app/handlers/**
A task description finds ADR-012 only when it uses the ADR’s own words. “handler” isn’t “handlers”, so this query finds nothing:
mneme test_query --memory .mneme/project_memory.json --query "add a list_invoices handler"
Query: add a list_invoices handler All decisions (ranked by score): [ADR-012] score=0.00 matched=(no match)
This one uses “request handlers”, from the ADR’s title, and matches:
mneme test_query --memory .mneme/project_memory.json \ --query "add an invoices endpoint to the request handlers"
Query: add an invoices endpoint to the request handlers All decisions (ranked by score): [ADR-012] score=4.50 matched=decision, scope, rationale
The MCP server lists six tools:
decision.propose decision.propose_batch decision.get decision.search decision.applicable_to decision.trace
decision.applicable_to returns ADR-012, because its scope handlers appears in the path:
{
"canonical_scope_matches": [
{
"context_scope": [
"handlers"
],
"decision_id": "ADR-012",
"lifecycle_status": "active",
"statement": "Request handlers do not import the database client",
"version": "1"
}
],
"proposal_hint_matches": []
}
Evidence
Each result shows why it matched. test_query prints a score and the fields that matched (decision, scope, rationale). The MCP result carries the context_scope that overlapped the path and the decision's lifecycle_status.
Troubleshooting
A lookup for a generic file name returns nothing
A bare file name like utils.py shares no words with any decision:
mneme test_query --memory .mneme/project_memory.json --query "edit to utils.py"
Query: edit to utils.py All decisions (ranked by score): [ADR-012] score=0.00 matched=(no match) Injected (top 3): (none)
Use the full path, or describe the task in words that appear in the decision.
A scope like api never matches in the CLI lookup
Here ADR-012 governs app/api/ with scope: api. The path contains the scope, but three letters is below the CLI lookup's four-letter minimum:
Query: edit to app/api/invoices.py All decisions (ranked by score): [ADR-012] score=0.00 matched=(no match) Injected (top 3): (none)
The MCP tool decision.applicable_to works differently: it matches the scope as a substring of the path, so api matches app/api/invoices.py there, and also rapid_import/. Pick a scope word of four letters or more that appears in the governed paths and not by accident in others, such as handlers.
decision.applicable_to doesn't return a dotted-scope ADR for its file
The tool matches the whole scope string inside the path. With scope: handlers.invoices, the string doesn't appear in app/handlers/invoices.py, so the tool returns nothing for that path, though mneme test_query still finds the ADR:
{
"canonical_scope_matches": [],
"proposal_hint_matches": []
}
Pass the scope in context as well and the ADR comes back:
ADR_DIR=troubleshooting/dotted python mcp_probe.py decision.applicable_to \
'{"paths": ["app/handlers/invoices.py"], "context": ["handlers.invoices"]}'
{
"canonical_scope_matches": [
{
"context_scope": [
"handlers.invoices"
],
"decision_id": "ADR-012",
"lifecycle_status": "active",
"statement": "Request handlers do not import the database client",
Or use scopes that appear in your paths as written. (ADR_DIR only points this guide’s probe at a second ADR folder.)
The MCP server won't start
ERROR: ADR directory troubleshooting/missing does not exist
The server refuses to start when the --adr-dir folder doesn't exist, is given as an empty string, or holds an ADR that fails parsing, validation or precedence checks. An empty folder starts with no decisions. Fix the path or the ADR, or leave --adr-dir out to run with proposals only.
Limits
- Lookup gives the agent context. It doesn't stop anything. Keep the hook and the CI check in place.
- Matching is by shared words, not meaning. A task phrased in words the decision doesn't use won't find it.
- The MCP setup is the generic process definition. We haven't verified it inside Codex or another named agent.
- The MCP server is local and stdio-only. It has no HTTP endpoint and no authentication.
- Over MCP an agent can read decisions and submit proposals. It can't accept a proposal or change enforcement; people review proposals with the CLI.