How-to guide

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.

Tested with mneme-hq 0.9.2 · Claude Code, any MCP host

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.

Next