Skip to main content

ratifact

A local MCP server that turns each repository into the persistent memory of its project. Decisions (ADRs), open items, code tasks and session logs live as Markdown files in a git repository of their own, outside the code repository, so no branch or checkout ever changes them. Any MCP client (Claude Desktop, Claude Code, Pi, Codex (experimental)) reads and writes the same memory through 28 tools. Codex is experimental: ratifact install configures it and the MCP tools answer, but a full /run-task run ($run-task in Codex) has not been validated yet.

  • Decisions are ratified, not assumed. Every decision is recorded as proposed; it only becomes accepted through accept_decision, with the user's literal words, and the client asks the user to approve that call.
  • Tasks travel from the chat to the coding agent. A change agreed in a chat is recorded with create_task; the coding agent runs it with /run-task T-NNN ($run-task T-NNN in Codex), on its own branch, and leaves the result for review.
  • Everything is plain files under git. Views such as DECISIONS.md and OPEN.md are regenerated from the records; nothing is deleted, only changes status.

Linux and macOS (Windows through WSL). Python 3.10 or newer. Spanish documentation: LEEME.md.

Install

uv tool install ratifact      # fixed installation: ~/.local/bin/ratifact
ratifact install              # shows the plan: which clients it found and what it would change
ratifact install --apply      # does it, with a backup of every file it touches

ratifact install configures every client it finds, so you never edit their JSON/TOML by hand:

Client Where
Claude Desktop ~/.config/Claude/claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json)
Claude Code claude mcp add --scope user (or ~/.claude.json if the claude command is not installed)
Pi ~/.pi/agent/mcp.json
Codex (experimental) ~/.codex/config.toml

Each one ends with a server named ratifact that runs <absolute path>/ratifact serve, with no environment variables. Other servers and settings are kept identical. Before writing, the file is backed up as <file>.bak-ratifact-<date> next to it (files inside a project — CLAUDE.md, AGENTS.md, .claude/settings.json — are backed up instead under ~/.local/state/ratifact/backups/<date>/<absolute path>, so nothing is added to your repositories; a file that is not rewritten gets no backup), the new content is validated by parsing it again, and the original is replaced atomically; an invalid file is reported and left untouched. A client that is not installed is skipped; --client claude-desktop|claude-code|pi|codex limits the run to one. A second --apply changes nothing and says so. Restart the clients afterwards.

uvx ratifact … works for one-off commands (uvx ratifact doctor), but clients need a fixed installation: ratifact install refuses to point them at the temporary copy that uvx runs.

From a clone instead of PyPI: uv tool install <path to the clone>. After each update follow the procedure in Version drift: merge → uv tool install --reinstall <path> → restart the clients → ratifact doctor.

First project

With no configuration, ratifact install --apply creates an empty ~/.config/ratifact/projects.json. Then, in any chat with the server connected, ask:

register the project my-app with the code in ~/Projects/my-app

register_project adds it to projects.json, creates its memory (by default ~/Projects/.ratifact/my-app, a git repository of its own), detects the test command and returns two texts to paste:

  1. the Claude project instructions (Claude Desktop / claude.ai project → Project instructions);
  2. the block for the repository's CLAUDE.md (Claude Code) and AGENTS.md (Pi, Codex), which starts with This repository uses the `ratifact` MCP server with project `my-app`.

project_instructions("my-app") returns them again at any time. The projects folder is ~/Projects unless RATIFACT_BASE says otherwise; RATIFACT_CONFIG and RATIFACT_LOG override the configuration and the usage log.

/run-task

ratifact install --apply links the run-task skill from the installed package:

  • Claude Code: ~/.claude/skills/run-task (the older ~/.claude/commands/run-task.md link is removed if it is ours)
  • Pi: ~/.pi/agent/prompts/run-task.md (and ~/.pi/agent/skills/run-task if that folder exists)
  • Codex: ~/.codex/skills/run-task and ~/.agents/skills/run-task (the whole folder: Codex does not follow a linked SKILL.md)

In the repository, /run-task T-001 (Codex: $run-task T-001) reads the task with read_task, creates its branch from main, implements it, runs the project's tests and leaves the task in_review (or failed) with the result. The run-task skill is linked into Claude Code, Pi and Codex by ratifact install --apply. A link that exists as a real file or folder is never replaced: the plan says to remove it by hand. The links point inside the installed package; if a reinstall moves it (for example uv tool install --reinstall with another Python version), ratifact doctor reports the links as pending and ratifact install --apply fixes them.

Commands

Command What it does
ratifact serve The MCP server over stdio (what the clients launch).
ratifact install [--apply] [--client NAME] Configures the MCP clients and links /run-task (plan unless --apply).
ratifact doctor Read-only diagnosis: version and where it runs from, configuration, each project's code, memory, schema and git status, integrations, the state of each client (configured with ratifact / old configuration / not configured / not found), the build id, whether a local source is newer than the installation, the running ratifact serve processes that started before it (see Version drift) and the pending actions. Exit code 1 if something is pending.
ratifact upgrade [--apply] One-time upgrade from memoria-proyectos (below).
ratifact report [--days N] [--project P] [--log PATH] Usage summary from the local telemetry log (~/.local/state/ratifact/uso.jsonl: tool, size, duration and error of each call — never content).
ratifact --version Installed version.

Integrations

Optional integrations add conditional guidance to get_context, create_task and update_task; ratifact never calls another MCP server.

Integration Detected by Role
codegraph a .codegraph/ folder in the project code structure for planning and checking a task's scope
gentle-ai a .atl/ folder in the project the task runs as its delegated-direct route (never SDD), is its ODD feature document, and RDD (if on) reviews it before in_review; whether to delegate follows ratifact's delegation policy
engram never detected its guidance is conditional ("if you have it")

The default is auto (used when detected). ratifact install reports what it detected per project and never asks. To turn one off: update_project("my-app", integrations="codegraph=off") (=auto turns it back on).

Model routing

You choose which model each delegated role uses (explore, write, verify), independent of any integration. The global value lives in settings.json, next to the active projects.json (e.g. ~/.config/ratifact/settings.json); you create it by hand (ratifact install never writes it):

{"models": {"explore": "haiku", "write": "sonnet", "verify": "sonnet"}}

Per-project exceptions: update_project("my-app", models="explore=haiku,write=sonnet") stores "models" in that project's entry of projects.json; they win role by role and the other roles inherit the global value. models="write=inherit" removes the project's exception for that role. Unknown roles and empty values are rejected with the list of valid roles. Model names are free text for your client (aliases or full ids).

When at least one role is set, update_task(..., "in_progress") adds a "Model routing" block (each role, its model and whether it comes from global or project): it is your explicit request to set that model when the agent delegates the role; a client that cannot choose a sub-agent's model ignores it, and roles without a model use the session's model. With no configuration the response is unchanged. An invalid settings.json never breaks a tool (it is ignored with a warning in that block); ratifact doctor reports it without changing it and shows each project's effective models (or models: session default).

Delegation

Delegation policy: ratifact, not the executing agent, decides whether a task is delegated. "delegation": "required" | "suggested" | "off" goes in settings.json next to "models" (global; default required); a project exception is set with update_project("my-app", delegation="off") (inherit removes it), and a task exception with create_task(..., delegation="off") or amend_task(..., delegation="required") while planning (stored as delegation: in the task's frontmatter). Precedence: task > project > global > required. Use off for a task that touches security, is cross-cutting, or depends on diagnosing a cause the spec does not know yet.

  • required: update_task(..., "in_progress") returns an imperative "Delegation" block — the main session orchestrates and does not write the code; exploration in an explore worker, implementation in write workers, verification in a verify worker that did not see the implementation, each with its role's model (or the session's); briefs carry the full spec; the orchestrator reviews the complete diff before in_review; after two failed verifications the orchestrator takes over and declares escalated: write.
  • suggested: the block is a suggestion. off: no delegation block.
  • The result carries its own line, separate from Integrations used: (which lists integrations only, never role names): Workers: explore/haiku×1, write/sonnet×2, verify/sonnet×1 (plus escalated: write), or Workers: none — reason: <why>. With required, update_task(..., "in_review") without a valid Workers: line still changes the status but answers with a warning.
  • Extended format: Workers: <role>/<model>×<n>, …[, rework: <role>×<n>, …][, escalated: <role>][ — note: <free text>]. <role>/<model>×<n> counts initial briefs only, even with several workers of the same role in parallel (two parallel writers are Workers: write/sonnet×2); rework: <role>×<n> counts re-sends of that role caused by a defect found by the verifier or by the orchestrator's diff review (the role must appear among the workers, n ≥ 1). A note starts at — note: (canonical) or at - note: / -- note:, and none - reason: / none -- reason: are accepted (Workers: write/sonnet×1 - note: x); an ASCII hyphen anywhere else (Workers: write/sonnet×1 - resumed once) separates nothing and is an invalid token. The parser ignores the note's text. No parentheses: rework goes in rework:, comments in — note:. Example: Workers: explore/haiku×1, write/sonnet×1, verify/sonnet×1, rework: write×1 — note: diff review found a missing test. Lines in the earlier format (without rework: or a note) stay valid.
  • Fixing a line without duplicating: if the warning says the line is invalid it proposes a corrected one when it can derive it; sending only that line with update_task(..., "in_review") replaces the Workers: line of the latest result block (or appends it there) instead of creating a new block.
  • Audit: in_progress records routing: explore=<m>,write=<m>,verify=<m> · delegation=<policy> · build=<build> · at=<ISO 8601 time, to the minute> in the task's frontmatter (session for a role without a model; overwritten on re-runs; build= and at= are written on newer tasks — older tasks keep the shorter form and are still read). ratifact report takes the period from at= when present (updated otherwise). ratifact report measures compliance per project (required tasks with real workers, escalations) and compares delegated and non-delegated tasks in reviews and in_progress → in_review duration; tasks without routing: are counted apart. Routing also ends with · client=<name>/<version>, taken from the MCP clientInfo of the initialize handshake (sanitized: no spaces, no ·, no newlines, max 64 chars; client=unknown if unavailable), and every new uso.jsonl row has a client field. Values are recorded as received, without normalization. ratifact report also measures, among delegated tasks (last valid Workers: line of each), how many had rework, the total re-sends per role, escalations and the rework rate (with rework / delegated), plus a per-client breakdown (client= from routing, unknown if missing) with tasks, delegation compliance and rework rate. ratifact doctor shows each project's effective policy (e.g. delegation=required (global)).

Version drift

There are three copies of ratifact: the source (a clone), the installation (the uv tool directory, which the /run-task skills link to) and the running server (one ratifact serve per client, which keeps the code it started with). They drift apart when you merge without reinstalling, or reinstall without restarting a client. The procedure after every change is:

# 1. merge into main   2. reinstall
uv tool install --reinstall <path to the clone>
# 3. restart every client (Claude Desktop, Claude Code, Codex, Pi)   4. check
ratifact doctor

What reveals a drift:

  • Build id. Each server computes <version>+<7 hex> at startup (content hash of its package, skills included, bytecode excluded). get_context shows ratifact <build> on its first line and routing: records it per task.
  • Stale server. The server re-checks its own package files (stat only, cached 5 s). If they changed or vanished since it started, get_context shows a ⚠ line below the build asking to restart that client, every other tool prepends the same warning, and update_task(..., "in_progress") is refused without changing anything: restart the client and repeat it (a task never starts with guidance that may be out of date).
  • Source newer than the installation. When uv installed ratifact from a local folder (its tool receipt), doctor and get_context compare that folder's package with the installed one and ask to reinstall, with the command. Installations from PyPI do not show it.
  • ratifact doctor also lists the running ratifact serve processes (PID, start time, parent process). A reinstall rewrites every file even when nothing changed, so a process that started before the installation is flagged only when its content differs: each server records its startup build in ~/.local/state/ratifact/servers/<pid>.json (removed on exit, dead PIDs pruned at startup). Same content → "nothing to do"; no record (a server older than this) → "the content may be identical", and get_context in that client gives the definitive answer. doctor only reads: it never writes, signals or stops anything (Linux and macOS; elsewhere it says the list is unavailable). Only a process with that record counts as a server: the wrapper through which Claude Desktop on Linux launches the MCP (its parent, also ratifact serve on the command line, without a record) is shown inside its server's entry ("wrapper , "), and any other process without a record is listed apart as "Unregistered processes" and not counted. doctor also warns when it runs on native Windows (not supported: install ratifact inside WSL2, with the repositories on the WSL filesystem, not under /mnt/c), notes when it runs inside WSL, and warns about a registered repository under /mnt/ (slow, with git differences).
  • The server's own check works the same way: a changed fingerprint is confirmed by the content hash once, so a reinstall with identical code does not trigger the warning or the in_progress refusal.

ratifact install --apply and ratifact upgrade --apply end by reminding you to restart the clients and run ratifact doctor. A server that predates the build/PID registry cannot diagnose itself; doctor still shows it.

Upgrade from memoria-proyectos

ratifact was called memoria-proyectos before 1.0. With every client closed:

uv tool install ratifact
ratifact upgrade              # plan: nothing changes
ratifact upgrade --apply      # memories to .ratifact/, projects.json to English keys, with backups
ratifact install --apply      # server key memoria-proyectos → ratifact in every client
ratifact doctor               # should end with "Pending actions: none"

ratifact install replaces the memoria-proyectos entry with ratifact (never both), renames the Claude Code permissions mcp__memoria-proyectos__<tool> → mcp__ratifact__<tool> in ~/.claude/settings.json and in the .claude/settings.json of each registered project, moves Codex per-tool approval tables to the new key, and rewrites the generated CLAUDE.md/AGENTS.md block (only that exact text). It lists what it cannot change: Claude Desktop keeps tool approvals per server name, so mark propose_decision, add_pending, create_task, create_document, register_project, accept_decision and accept_decisions as requires approval again. If the default configuration does not exist but a server still runs with MEMORIA_PROYECTOS_CONFIG, doctor and upgrade print the exact command (RATIFACT_CONFIG=<path> ratifact upgrade). The MEMORIA_PROYECTOS_* variables keep working with a deprecation notice until 1.1. Details: docs/upgrade-v1.md.

Development

uv run --with pytest --with "mcp>=1.2,<2" pytest -q

The tests run against temporary folders and a temporary HOME: they never touch your configuration, clients or projects. Publishing steps: docs/publish.md.

Source: https://github.com/EmaSleal/ratifact.

License

MIT — see LICENSE.

Metadata

Release files for ratifact 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ratifact 1.0.0
File Size Uploaded Provenance Actions
ratifact-1.0.0.tar.gz 131.6 kB With provenance Details for ratifact-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ratifact 1.0.0
File Interpreter ABI Platform Actions
ratifact-1.0.0-py3-none-any.whl Python 3 none any Details for ratifact-1.0.0-py3-none-any.whl

Total release size: 275.7 kB

Release files / ratifact-1.0.0.tar.gz

Download URL ratifact-1.0.0.tar.gz
Size 131.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0891427eefb9a659910bba19176997064c8b1bae9460fe80785a672d1f1a1715
BLAKE2b-256 checksum
How to use checksums
396b18d21e6ef65bf3f8f854a88874f2cd47a668bd6ce8210ea4d64dd4dfbd32
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

Release files / ratifact-1.0.0-py3-none-any.whl

Download URL ratifact-1.0.0-py3-none-any.whl
Size 144.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4d4fd96d300e0a480e3ef7ca13edf1da3f687e3eeddff3ae4ebd79cf0320eda1
BLAKE2b-256 checksum
How to use checksums
624a86e0c6863d4f25a5ce3a9c37f2d8c52fae608ae6071f6043e94cb6e84731
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

Security for ratifact 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ratifact-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page