Skip to main content

Your agents forget why.Rationale remembers.

Before an agent changes your code, Rationale hands it the rules, decisions and relationships that govern that code. After the change, what stays true is written back to your repository automatically, and the rules you pin stay out of any agent's reach.

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Ragosorio/Rationale/releases/latest/download/rationale-installer.sh | sh

v1.1.0 stable · Git and Codebase Memory · no account · no telemetry · 127.0.0.1

The Rationale loopFive steps run clockwise: locate, prepare, change, capture and pin. Prepare hands the agent a packet from the canon; capture and pin write Records back to it.PACKETRECORDSPINNEDLocatecodebase memoryPrepareprepare_changeChangeagent + testsCapturefinalize_changePinrationale pinCanon.rationale/ · Git
Next stepPacket the agent receivesWritten back to the canonReserved for a person

You explained the rule. The next chat never heard it.

It works in Monday’s conversation. On Thursday a fresh agent sees only the code, removes the guard and calls it a cleanup.

Monday, you

“Never retry a payment that already reached the bank.”

Thursday, a fresh agent
commit: cleanup- if payment.reached_bank { return }

Rationale keeps the rule next to the code it governs and hands it to every agent before that code is touched.

Context before the change. Memory after it.

There is no approval queue. Agents capture what they learn; you keep authority over what must not move.

  1. 01

    Locate

    Codebase Memory

    Find the symbol, its callers and the files around it.

  2. 02

    Prepare

    prepare_change

    Governing rules, explained relationships, the structural neighborhood and the target’s code, within a token ceiling.

  3. 03

    Change

    your tests

    The smallest change consistent with that context.

  4. 04

    Capture

    finalize_change

    Durable knowledge becomes canonical Records in the same call. Noise and duplicates are discarded with a reason.

  5. 05

    Pin

    rationale pin

    Fix the rules no agent may replace. Any attempt becomes a conflict only you resolve.

Up and running in five minutes.

Install once per machine, initialize once per repository, then keep working the way you already do. Updating or uninstalling never touches .rationale/.

  1. 1Install Codebase Memory (recommended)

    curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash
  2. 2Install Rationale on macOS or Linux

    curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Ragosorio/Rationale/releases/latest/download/rationale-installer.sh | sh

    The installer verifies checksums, uses the stable channel and registers the MCP server for the agents it finds.

  3. 3Initialize your repository

    rationale init

    Creates the canon in .rationale/ and writes the protocol into CLAUDE.md, AGENTS.md or the Cursor rule.

  4. 4Restart your agent and check the connection

    In Claude Code run /rationale-health. In Codex ask “Check Rationale health.”

  5. 5Ask for a real change

    “Make payment retries stop after three attempts.”

    You don’t have to mention Rationale. The agent prepares the change, makes it, and captures what stays true.

  6. 6Watch it live, and pin what must not move

    rationale ui
    rationale pin <record-id>
  7. On Windows, the PowerShell installer is in the quickstart.

One skill teaches your agent the whole job.

The protocol in CLAUDE.md and AGENTS.md tells an agent to prepare and capture. The rationale skill shows it how. A short router loads only the playbook for the operation at hand, so the depth costs nothing until a task needs it.

Picked up when it applies
Claude Code and Codex can load it on their own when a task matches. /rationale capture or $rationale explain <target> call it by name.
Checked before it writes
A validator mirrors the capture gate, so a candidate that would be discarded gets fixed before finalize_change.
English inside, your language outside
The agent answers in the language you write in and keeps tool names, Record ids and commands verbatim. New Records follow the language your canon already uses.
One folder for every agent
It follows the open Agent Skills format, so the same directory works in Claude Code, Codex and any agent that reads skills.
OperationWhen the agent uses it
preflightBefore it changes, moves or deletes non-trivial code
explainWhen code looks redundant or odd, or you ask why it exists
captureWhen a change is done and tested
conflictsWhen a candidate collides with a pinned rule
healthWhen tools are missing or results look degraded
adoptWhen setting Rationale up and seeding the first Records
maintainWhen bindings go stale or rationale doctor reports findings

For any other agent that reads skills

npx skills add Ragosorio/Rationale

rationale init and install-agent already install it for Claude Code and Codex.

Skill guide

What the agent must know, and nothing more.

A bounded packet compiled for one target and one intent. Governing knowledge is never truncated, and the budget is a ceiling, not a goal.

  • Constraints and decisions, each with authority and provenance
  • Why relationships exist, with their live structural state
  • A bounded subgraph: callers, callees, dependencies, tests
  • Intent conflicts, risks and honest unknowns
packet.jsonabridged example
{
  "operation_id": "op_7f3c…",
  "critical_constraints": [{
    "id": "constraint.payments-idempotent",
    "authority": "pinned", "provenance": "human_stated"
  }],
  "decisions": [{
    "id": "decision.charge-after-reservation",
    "authority": "normal", "provenance": "agent_asserted"
  }],
  "relationships": [
    { "edge": "charge → bank_client", "state": "observed" },
    { "edge": "retry_worker → charge", "state": "indirect" }
  ],
  "structure": { "nodes": "12 / 52", "edges": "11 / 51" },
  "estimated_tokens": 1707, // ≤ 2400
  "known_unknowns": ["refunds → charge: orphaned"]
}

Five moments where context breaks, and what an agent gets instead.

Each panel shows a call and an abridged response. Switch panels with the arrow keys; where a panel has steps, step through them.

Simulated example. An invented shipping project from Rationale's evaluation, run with the development binary. No customer data, and no numbers here are customer results.

Thursday’s agent never saw Monday’s chat.

On Monday an agent learned that a cancellation must wait for the carrier and captured it. On Thursday a fresh session is asked to cancel locally when the carrier is down.

It receives Monday’s decision with its reason, bound to the exact function, and owes a verdict before editing.

call and responseabridged
prepare_change(target="src/shipping/service.ts::cancelShipment",
  intent="Allow cancelling locally when the carrier API is down")
{
  "retrieval": { "status": "found", "scope": "symbol" },
  "decisions": [{
    "id": "decision.cancel-requires-carrier-ack",
    "statement": "A shipment is cancelled only after the carrier acknowledges the cancellation.",
    "rationale": "Cancelling locally … left paid labels in transit with no record.",
    "provenance": "agent_asserted", "why": "bound to this symbol"
  }],
  "intent_conflicts": [{ "record_id": "decision.cancel-requires-carrier-ack",
    "detection": "governs-target" }],
  "governance_verdict_required": true
}

Only the current rule governs.

The quote cache once kept prices for five minutes; a later decision cut it to sixty seconds.

The five-minute rule is history, with its replacement named. It is never served as current next to the rule that replaced it.

What governsabridged
explain_target(target="src/shipping/rates.ts::quoteRate")
{
  "governing_records": [{
    "id": "decision.rate-cache-ttl-60s",
    "statement": "Carrier rate quotes are cached for 60 seconds per route and weight.",
    "replaces": ["decision.rate-cache-ttl-5m"]
  }],
  "history": [{ "id": "decision.rate-cache-ttl-5m",
    "status": "superseded", "replaced_by": "decision.rate-cache-ttl-60s" }]
}
With historyabridged
explain_target(target="src/shipping/rates.ts::quoteRate", include_history=true)
{
  "history": [{
    "id": "decision.rate-cache-ttl-5m", "status": "superseded",
    "statement": "Carrier rate quotes are cached for 5 minutes per route and weight.",
    "rationale": "Carrier APIs rate-limit quote calls; five minutes kept us under the limit at launch."
  }]
}

The incident’s rule reaches billing.

After an incident, a person pinned “money is integer cents” on the shipping helper. Billing never bound it, but it calls that helper.

The pinned invariant arrives as an inherited constraint, with the observed call that brought it. Similarity never brings one.

call and responseabridged
prepare_change(target="src/billing/invoice.ts::issueInvoice",
  intent="Sum invoice lines as floats and round once at the end")
{
  "decisions": [{ "id": "decision.invoice-rounding-bankers", "why": "bound to this symbol" }],
  "inherited_constraints": [{
    "id": "constraint.money-in-integer-cents",
    "statement": "Monetary amounts are integers in cents from quote to invoice; never store, sum or compare floating-point currency.",
    "authority": "pinned", "provenance": "human_stated", "severity": "critical",
    "via": "issueInvoice calls toCents"
  }]
}

The ceiling covers everything the agent reads.

A small budget leaves out the structure; the packet names what it left out and how to get it back.

Governing knowledge is never cut. What did not fit is listed, with the call that returns it.

800 tokensabridged
prepare_change(target="src/shipping/service.ts::cancelShipment", max_tokens=800)
{
  "decisions": [{ "id": "decision.cancel-requires-carrier-ack", … }],
  "omitted": [{ "section": "structure", "count": 1, "expand": "raise max_tokens" }],
  "budget": { "max_tokens": 800, "estimated_tokens": 754, "estimator": "upper-bound" }
}
2,400 tokensabridged
prepare_change(target="src/shipping/service.ts::cancelShipment", max_tokens=2400)
{
  "decisions": [{ "id": "decision.cancel-requires-carrier-ack", … }],
  "structure": { "nodes": ["cancelShipment", …], "considered_nodes": 4 },
  "relevant_code": { "file_path": "src/shipping/service.ts", "start_line": 10, … },
  "budget": { "max_tokens": 2400, "estimated_tokens": 1037, "estimator": "upper-bound" }
}

Nothing stays open, nothing is faked as done.

An agent prepares a change, explores, and decides not to make it.

The Claude Code Stop hook asks once; the agent closes the operation without memory and without claiming a change.

Prepareabridged
prepare_change(target="src/shipping/rates.ts::applySurcharge",
  intent="Make the remote surcharge a percentage")
{ "operation_id": "op_1a13b671…", "packet": { … } }
Stop hookabridged
Stop hook → the agent tries to stop without closing it
{ "decision": "block", "reason": "Rationale: this session prepared 1 change(s) that are still open: op_1a13b671… If you did not change the code, call finalize_change(operation_id, abandon=true, summary=\"why\"). Do not finalize as done a change that did not happen." }
Abandonabridged
finalize_change(operation_id="op_1a13b671…", abandon=true,
  summary="Only explored; the surcharge stays flat.")
{ "state": "abandoned", "memory_written": false }

Ships in the release after v1.1.0. With v1.1.0 today, prepare and capture work as described in the loop above.What changes, and the evidence

See what every agent was given, as it happens.

rationale ui opens a local, read-only Control Room: the working subgraph in 3D, the memory that explains it, and every session’s activity as it streams.

Working subgraph of pipeline::preparepipeline::prepare in Rationale's own code: called by the CLI, the MCP server and a CLI test; it calls binding_match::governing, storage::list_records, context::gather, retrieval::compile and operations::save. Amber badges count the Records that govern each symbol.src/main.rs2 recordscmd_preparesrc/mcp/server.rs3 recordscall_prepare_changetests/cli.rs1 recordprepare_with_a_relative_…src/pipeline.rs1 recordprepare92 outgoing calls5 incomingsrc/binding_match.rsgoverningsrc/storage.rs2 recordslist_recordssrc/context.rsgathersrc/retrieval.rscompilesrc/operations.rssave
targetcallercalleetestObserved edgeRecords that govern itRationale's own pipeline::prepare from Codebase Memory at revision 4be2ecd: 3 of its callers and 5 of its 92 calls. Drawn flat here; the Control Room renders it in 3D.
Working subgraph
Nodes colored by their role in the change, edges by structural state.
Causal overlay
Rings mark what the canon explains. Select any node to read why.
Live activity
Claude Code, Codex, Cursor and the CLI, streamed over SSE.
Memory browser
Provenance, authority, bindings and pending conflicts.

Served on 127.0.0.1 only, GET requests only, embedded in the binary.Control Room docs

Agents write memory. You keep authority.

Every Record carries where it came from and how far it can move. The capture gate enforces that boundary.

Always attributedprovenance
An agent’s assertion records its client, session and operation. It is never presented as something a person stated.
Rules that holdpinned
Pin a rule and every agent can use it. None can replace it.
You decideconflicts
An agent that tries to supersede a pinned rule writes nothing. You keep the rule or adopt the new one.
$ rationale pin constraint.payments-idempotent# agents can use it, never replace it
$ rationale conflicts# assertions that collided with a pinned rule
$ rationale resolve conflict_… keep-pinned# your call, recorded for audit

Human-only commands: they require an interactive terminal and declared authority.

What calls what, and why it has to.

A Record can explain a relationship. On every call Rationale re-derives its state from the provider, and it never deletes the explanation.

StateEdgeWhat it means
observedThe direct relationship exists in the current index.
indirectNo longer direct, but a bounded path still connects both ends.
orphanedIt can’t be located. The explanation may be stale, so it is flagged and never deleted.
unknownThe provider couldn’t verify it. Absence is not proof.

Structure tells you where. Git tells you what. Rationale protects why.

Agents reach the canon only through the capture gate, and the Control Room only reads. This is the binary’s real wiring, drawn from its own call graph.

How Rationale is wiredAgents and the CLI reach the pipeline. The pipeline reads Codebase Memory, Git and the canon, compiles the packet, records local traces and sends candidates to the capture gate, which writes Records. The Control Room only reads.COMPILECANDIDATESMCP TOOLSREVISIONWRITESTRACESPROVIDERCodebase Memorywhere · how it connectsGITGitwhat · whenRECORDSCanon.rationale/ · whyMODULEContext compilercontext · retrievalMODULEPipelineprepare · finalizeMODULECapture gatecanon · storageBOUNDARYMCP server · CLImcp::server · mainLOCALActivity.rationale-local/READ-ONLYControl Roomui · 127.0.0.1
Call between modulesReadWrites Records to the canonEdges from Codebase Memory's call graph of this repository, revision 4be2ecd.

Architecture docs

Works with the agents you already use.

The installer registers one MCP server per agent with its absolute path; install-agent writes the protocol into your project. Remove it all with uninstall-agent.

Claude Code

The skill and five shortcuts

  • /rationale [operation]
  • /rationale-preflight <target> <intent>
  • /rationale-explain <target>
  • /rationale-capture [statement]
  • /rationale-conflicts
  • /rationale-health

The agent picks the skill on its own; the shortcuts are for you. /rationale-conflicts hands the decision to you.

Codex

Ask in plain language

  • “Prepare this change with Rationale for <target> with intent <intent>.”
  • “Explain <target> before changing it.”
  • “Capture this change with Rationale.”
  • “Check Rationale health.”

Codex reads the protocol from AGENTS.md, calls the MCP tools and loads the skill from .agents/skills/. $rationale calls it by name.

Cursor

An always-on rule

Cursor gets .cursor/rules/rationale.mdc in the project and a user-scoped MCP server that works from the Dock.

Agents and MCP docs

Local by default. Honest about uncertainty.

Canon in Git
.rationale/ is plain YAML, reviewed in the same pull request as the code.
Traces stay home
Activity keeps identifiers and a one-line intent. Never code, prompts or Record content.
Read-only mission control
127.0.0.1, GET-only, Host-validated, embedded assets.
Provider boundary
Codebase Memory only through its public tools. Unavailable means unknown, never complete.

5 MCP tools · 6 MCP prompts · 1 skill with 7 operations · 3 supported agents · 5 platforms · no accounts, no telemetry

1.1 is stable. Here is what that means.

The full loop has been implemented, tested and used on Rationale’s own repository since 1.0, and 1.1 adds the rationale skill.

Verifiedv1.1.0

  • 386 Rust tests on macOS, Linux and Windows, with Clippy and RustSec
  • The skill checked against its directory, the Agent Skills limits and the capture gate
  • Every tag re-verified from source before it is packaged
  • 1.0: packaged binary, registration migration and an end-to-end dogfood change
Read the evidence

Still open

  • Lexical intent-conflict polarity is a noisy hint
  • Skill selection is probabilistic; the protocol stays the floor
  • Some ADRs are implemented but still proposed
Known limits