# How Rationale works, and what leaves your machine · Rationale

> The four steps of Rationale in depth: capture on your machine with your own agent, the decision record, context by #handle and MCP, warnings before the edit. One table with what leaves your machine at every event, checked against the client's source.

Source: https://rationalehq.com/how-it-works
Language: en

How it works

# How Rationale works, and what leaves your machine

Each step of Rationale, what it runs on your machine, and what leaves it. The home page shows the four steps; this page says what each one does, which commands run, which hooks are installed and, for every event, what leaves your machine and what never does, checked against the client's source (0.9.0) and the API it talks to.

Checked October 6, 2026 · client 0.9.0

In short

-   Decisions are extracted on each person's machine by the agent CLI they already use (`claude -p` or `codex exec`). The transcript goes to that agent's provider, as it did anyway, and never to Rationale.
-   Rationale receives the extracted decisions, short quotes of the person's own words and metadata: session id and title, turn numbers, repository, branch, commit, time, model and cost.
-   Before an agent edits a file, the matching decisions reach it. Paths are matched on your machine; a path that matches nothing never leaves it.
-   Four hooks, written once to Claude Code's settings. `rationale uninstall` removes them.
-   Claude Code today, Codex in beta, any MCP client for reading and recording. Cursor next.

On this page

1.  [1 · Capture: when a turn ends](#capture)
2.  [2 · Record: what a decision holds](#record)
3.  [3 · Give context: #handle, notes and MCP](#context)
4.  [4 · Warn: before the edit, not in review](#warn)
5.  [What leaves your machine](#data)
6.  [Install in two minutes](#install)
7.  [The hooks, and how to remove them](#hooks)
8.  [Agents and connectors today](#agents)

## 1 · Capture: when a turn ends

Install the client once per laptop. From then on, each time your agent finishes a turn, it runs the client's `Stop` hook (Claude Code; Codex once you trust the hooks). The hook starts a capture in the background and returns at once, so the agent never waits. A watcher does the same every minute for any turn a hook missed: a crash, a closed window, a session idle for a minute with new turns.

The capture reads the session's transcript on your machine and builds a compact delta of the new turns: your messages, the agent's replies and the tools it called, with their input cut to 200 characters. Tool output, thinking and the text the agent injects by itself are left out. Up to 120 turns go in one block, with up to 30 earlier turns as read-only context, so a short block is not read in isolation.

The delta goes to the agent's own CLI on your machine, signed in as you: `claude -p` for a Claude Code session, `codex exec` for a Codex session. A Codex session never goes to Anthropic, and a Claude Code session never goes to OpenAI. For Claude Code the call is lean: a system prompt, a JSON schema, no tools, no settings, no hooks, no MCP servers, no session file, so it never shows up in your history. For Codex it is the documented `codex exec`, ephemeral, read-only, in an empty folder, with the client's own hooks off.

Only sessions in a repository one of your projects lists are captured. The client asks Rationale which of your workspaces lists the repository's git remote and caches the answer for ten minutes. No project: nothing is extracted and nothing is sent. Two workspaces: nothing either, and `rationale status` names both. A failed extraction is retried after 5, then 15, then 60 minutes while the same block keeps failing, and never blocks the agent.

What reaches Rationale is the result: the decisions, plus session id and title, turn numbers, repository, branch, commit, the time of the last turn, agent, model, duration, tokens and equivalent cost. The endpoint that receives a capture has no field that could carry a transcript.

## 2 · Record: what a decision holds

A decision is not a summary of the session. It has a question, the choice, the criteria that drove it with their weight, the options ruled out and why, assumptions, and what would make the team revisit it. It is anchored to paths in the repository and to identifiers (a class, a table, a ticket key), and it says what it is about: business, product or technical. The person's own words that decided it are quoted.

Every decision is recorded as inferred: an agent extracted it. It becomes confirmed only through a person: a click in the app, their own words in a session (the client checks the quote against the transcript before sending it, so an agent saying someone confirmed is not enough), or the merge of the pull request that carried it. Both origins stay visible, and agents see the difference.

Decisions are numbered per workspace (D-42), kept as immutable versions and encrypted at rest. The server reads only identifiers, states, anchors and timestamps. Every read and every confirmation is written to the workspace's audit log. The extractor also reports when the person confirmed, corrected or retired an earlier decision in conversation, and when the agent asked about one.

A session that named no task sends its decisions to the project's inbox, where the team links, keeps or retires them. A repeat of a decision already recorded is linked, not recorded twice.

## 3 · Give context: #handle, notes and MCP

A task is a `#handle`: a readable slug, unique in the workspace, that everything attaches to: sessions, decisions, handoffs, pull requests, tickets. Type it in any prompt and the `UserPromptSubmit` hook sends Rationale the handle, the repository and the branch, nothing else from the prompt. Back comes the task's context: its state, its active decisions by area, its latest handoffs and its open pull requests. The session is linked to the task, so its later captures land there.

A branch named after the handle links the session too, with nothing typed. When a session starts, the `SessionStart` hook fetches your notes: standing instructions you gave your agents ("answer me in Spanish", "never touch the v1 controllers") for this repository's workspace and project, within 300 tokens and two seconds. Only the repository's remote leaves your machine for that.

Assistants that have no hooks connect over MCP: Claude (claude.ai and the desktop app), ChatGPT, Cursor, Claude Code and Codex themselves, and any client that speaks the protocol. The remote server at `https://mcp.rationalehq.com/mcp` uses OAuth 2.1 with PKCE: paste the URL, sign in once, allow your workspaces. There are no API keys. Its tools are `context_for`, `search`, `list_tasks`, `list_inbox`, `list_notes`, `pull_request_for`, `record_decision`, `record_outcome`, `record_handoff`, `record_note`, `remove_note`, `open_task`, `set_visibility`, `answer_overlap` and `suggest_same_person`; each one declares whether it reads or writes, so your client can ask you before a write.

Business decisions come first, framed as constraints that product and technical choices do not override. Everything an agent records over MCP is inferred, with the person's words quoted, because nothing can check a conversation Rationale did not see.

## 4 · Warn: before the edit, not in review

The `PostToolUse` hook runs after every tool call and exits in a few milliseconds for tools it does not watch. It watches Read, Edit, Write, MultiEdit and NotebookEdit, and Bash commands that name a file, because agents read with `cat`, `sed` and `grep` as often as with Read; for Codex, shell commands and `apply_patch`. Reading is the moment that matters: the model still has to write its change.

The hook holds an index of the repository's anchored decisions, refreshed in the background at session start and after each turn: for each decision its number, paths, identifiers, area, commit and gist (question and choice). It matches the file's path against that index on your machine and, for decisions that name a class, a table or a ticket key, reads the file here (text, 512 KB at most) to look for them. The content never leaves. A file that matches nothing causes no request at all.

On a match, one request asks Rationale for the warning text: the repository, the repo-relative path, how each decision matched, which decisions the agent already has in its context and, after an edit of a file that someone else's open pull request also changes, the lines just changed. The answer arrives within two seconds: at most three decisions in full and the rest by number, who decided each, whether a person confirmed it, and how many commits touched the file since. If Rationale is slow or down, the agent gets the gist from the local index instead. Each warning shown is an audited read.

Once per file per session, again if the decisions change. A file whose decisions are all already in the agent's context gets no warning. It is context, never a block: the hook never changes the agent's permissions and never stops it. A hook that fails exits silently.

## What leaves your machine

One row per event. Every row was checked against the client's source (rationale-client 0.9.0) and the API that receives each request. Where your own CLI runs, the row says what goes to your agent's provider: that traffic goes where your sessions already go, under your own account.

When

Leaves your machine

Never leaves

**A turn ends** (`Stop` hook; the watcher for missed turns)

**To your agent's provider**, through your own `claude -p` or `codex exec`: the new turns in compact form (your messages, the agent's replies, the tools it called with their input cut short), with up to 30 earlier turns as context. **To Rationale**: the decisions found, with the person's words quoted and the paths they are anchored to; which earlier decisions the person confirmed, corrected or retired, with the quote and its turn; session id and title, turn numbers, repository remote, branch, commit, time of the last turn, agent, model, duration, tokens, equivalent cost, client version.

The transcript. Tool output and thinking, not even to your provider. Secret values: refused when written, redacted from quotes. A Codex session never goes to Anthropic; a Claude Code session never to OpenAI.

**The agent reads or edits a file** (`PostToolUse`: Read, Edit, Write, MultiEdit, NotebookEdit, Bash commands naming a file; Codex shell commands and `apply_patch`)

Nothing, unless the path or the file's content matches the local index, or the path is in a teammate's open pull request. Then, to Rationale: the repository remote, the repo-relative path, how each decision matched (number, kind, anchor, commits since), the decisions already in the agent's context, the tool, the session id and, after an edit of a file in someone's pull request, the line ranges just changed.

The file's content: identifiers are matched by reading it here, 512 KB at most. Paths that match nothing. The command and its output.

**You type `#handle` in a prompt** (`UserPromptSubmit`)

The handle (up to three per prompt), the repository remote, the branch, the session id, the agent. The task's context comes back.

The prompt. A prompt with no `#handle` causes no request.

**A session starts** (`SessionStart`, also after `/clear` or a compaction)

The repository remote, the session id and the agent, to fetch your notes (two seconds at most). In the background: the remote and the digest of the index it holds, to refresh the anchored decisions (`unchanged` comes back when nothing moved); and the remote alone, to ask which workspace lists it (cached ten minutes).

The working folder, your files, the environment.

**An MCP client calls a tool** (Claude, ChatGPT, Cursor or any client, over `mcp.rationalehq.com`)

What the agent passes to the tool: a `#handle`, paths or refs, a project name, decision numbers, search words, or what it records (a decision, an outcome, a handoff, a note, a task, with the person's words quoted). Over MCP there is no local index: the paths the agent passes to `context_for` reach Rationale as they are. Every decision read or written is an audited event.

The conversation. The code. A search query is not stored or logged.

**A digest is due** (the watcher asks once an hour; once a day per project, or after ten new decisions)

**To Rationale**: a lease request, nothing in it. Rationale hands back the project's tasks, latest handoffs, previous topics and active decisions in short form. **To your agent's provider**, through `claude -p` (or `codex exec` on a machine without Claude Code): that input, never a transcript. **To Rationale**: the status (done, in progress, next), the topics with the decisions each covers and where the AI placed them, where each task stands, tensions between decisions, an area and identifiers for decisions that have none, the revisit conditions now met, plus model, duration, tokens and cost.

Transcripts, code, files.

**A session goes quiet** (the watcher, 30 minutes with no new turn, only sessions with decisions)

**To your agent's provider**: the session's last 60 turns in compact form, through the same CLI. **To Rationale**: the handoff it wrote (summary, next steps, open questions), the session id and the agent.

The turns themselves. A session with no decisions gets no handoff; one Rationale has no task for is not asked again.

**You deploy** (`rationale deploy production`, optional, for teams that deploy from their machines)

The repository remote, the environment, the commit sha and its branch, up to 500 commit shas reachable from it, the tool's name. Rationale marks the pull requests and decisions that are live.

Commit messages, diffs, code.

**You sign in** (`rationale init`, `rationale login`)

Your machine's name (the computer name on macOS, else the hostname) and the client version, to start the device flow. You enter a code in the browser, signed in to Rationale. The device's token comes back once and is saved readable only by you (`~/.config/rationale/config.json`, mode 600).

A password: there is none. Rationale signs people in by email link.

**You paste a thread** (`rationale record`, a Slack thread or meeting notes, optional)

**To your agent's provider**: the text, through the same extractor. **To Rationale**: the decisions found and the title you gave, as a one-turn capture marked `paste`.

The text itself.

**Every request to Rationale**

The client's version (`User-Agent`), which agents run its hooks on this machine (`claude_code=4/4, codex=0/4`: names and counts), and once after each update its result code (ok, bad signature, rolled back). Like every server, Rationale sees the IP address a request comes from. With the index refresh, counters per repository: reads and edits checked and matched, repeats, timeouts, warning ids the agent cited or followed with another edit, fetch times.

Anything from a session. Paths: the counters are counts and ids only.

**Every six hours, the update check** (the watcher, never a hook)

A `GET` of the signed release manifest and its signature from `app.rationalehq.com/client`, which redirects to the files on GitHub, with the client version in the `User-Agent`. The binary is downloaded next to the current one, verified (minisign signature, size, sha256, a two-second `--version` run) and swapped; the previous one is kept for `rationale update --rollback`.

Anything about you or your sessions. Homebrew installs only say a newer version exists.

Decision content, notes and handoffs are encrypted at rest with keys kept apart from the database. Our staff see identifiers, states and anchors, never the content, and each staff view is written into your audit log. The [security page](https://rationalehq.com/security) has the engineering view; the [privacy policy](https://rationalehq.com/privacy) the legal one.

## Install in two minutes

One command, no sudo, macOS or Linux. It puts one binary at `~/.local/bin/rationale` and nothing else: no hooks yet, no sign-in.

```
curl -fsSL https://rationalehq.com/install.sh | sh
```

Or with Homebrew:

```
brew tap rationalehq/tap https://github.com/jorgebasilio/homebrew-tap
brew install rationalehq/tap/rationale
```

Then, inside a repository your workspace lists:

```
rationale init
```

1.  **Sign in from your browser.** The terminal shows a code and opens the sign-in page of the app. One sign-in per workspace; `rationale login` adds another.
2.  **This repository.** It checks which of your workspaces lists this repository's git remote. Listed by one: captured. By none: it says so and asks whether you listed it just now.
3.  **Agents on this machine.** It writes the four hooks to Claude Code's `~/.claude/settings.json` (a backup is kept; your own entries are untouched) and, if Codex is here, to `~/.codex/hooks.json`, and tells you about the trust step Codex requires. Cursor is detected and reported, not captured yet.
4.  **Watcher.** A launchd job on macOS, a systemd user timer on Linux, every minute: captures any turn a hook missed, writes handoffs, runs digests and checks for updates. Without a systemd user session the hooks still capture every turn.

Done. Work as usual. `rationale status` shows the sign-ins, this repository's workspace, the hooks, the watcher and the latest session, any time. To read and record from Claude, ChatGPT or Cursor, add the MCP server; the [documentation](https://rationalehq.com/docs#mcp) has each client's steps. For Claude Code:

```
claude mcp add --scope user --transport http rationale https://mcp.rationalehq.com/mcp
```

## The hooks, and how to remove them

`rationale init` adds four entries to the `hooks` object of `~/.claude/settings.json`. Each runs the installed binary with the event's name; the binary decides what to do, so an update never rewrites your settings. The timeouts are the most a hook may take; they return far sooner.

Hook

Runs when

What it does

Timeout

`Stop`

The agent finishes a turn

Starts a capture of the new turns in the background and returns

10 s

`UserPromptSubmit`

You send a prompt

With a `#handle`: links the session to the task and adds the task's context. Otherwise nothing.

15 s

`PostToolUse`

After every tool call (no matcher)

A read or an edit of a file: matched locally, the anchored decisions added on a match. Any other tool: exits at once.

3 s

`SessionStart`

A session starts or resumes, or after `/clear` or a compaction

Adds your notes; refreshes the anchor index in the background

5 s

An entry looks like this; the command is the binary's absolute path, never a PATH lookup:

```
"Stop": [{ "hooks": [{ "type": "command", "command": "/Users/you/.local/bin/rationale hook claude-code stop", "timeout": 10 }] }]
```

A hook never blocks the agent: every error is swallowed and the hook exits 0; a crash exits with a code Claude Code shows as a non-blocking error. Hooks do nothing in a repository no project lists. For Codex the four entries go to `~/.codex/hooks.json`, with no matcher either; Codex runs them only after you trust them (`/hooks` in the CLI, Review hooks in the app), and the client never writes that trust for you.

To remove everything:

```
rationale uninstall
```

It removes the four hooks from Claude Code and from Codex, keeping a backup of each settings file, and removes the watcher. Then revoke the device on the Devices page of the app, which invalidates its token. The config stays in `~/.config/rationale/` and the local state in `~/.local/state/rationale/` until you delete them; the binary is one file you can remove.

## Agents and connectors today

What is live, what is in beta and what is next. We name what we do not cover rather than imply it.

Agent or tool

Status

What works

Claude Code

Live

Capture after every turn, warnings before edits (Read, Edit, Write, MultiEdit, NotebookEdit, Bash), `#handle` context, notes at session start, handoffs, digests

Codex (the CLI, the ChatGPT desktop app and the IDE extension)

Beta

Capture and context once you trust the hooks; warnings on shell commands and `apply_patch`; extraction with `codex exec` on your own ChatGPT sign-in, which pauses itself for a day if OpenAI reports unusual activity

Claude (claude.ai and the desktop app), ChatGPT, Cursor, any MCP client

Live

Read your team's decisions and record new ones through the remote MCP server. No capture and no edit-time warnings: those come from hooks.

Cursor capture

Next

Cursor is detected and reported by `rationale init`; its sessions are not captured yet

Gemini CLI

Next

Not built

GitHub

Live

A GitHub App on the organizations you choose: pull requests, reviews and issues linked to the decisions on the files they change; the merge of a pull request confirms the decisions captured on its branch

Jira

Live

OAuth with your Atlassian site: tickets and epics become tasks, decisions follow the ticket

No tracker

Live

Tasks live in Rationale; agents open them when you ask

Linear, Slack

Next

Not built. Tell us what your team uses when you request access.

Early access: [request access](https://app.rationalehq.com/request-access) and we set up your workspace with you, within a working day.

Anything else: [support@rationalehq.com](mailto:support@rationalehq.com)
