RepoPlane is a local Model Context Protocol server for coding agents: bounded repository search, durable task evidence, and gated execution of registered commands. It also explains paths and queries structured data. A partial scan reports what it did not observe instead of presenting a guess as a complete result.
Discovery tools read workspace content. Record tools write local evidence; Runner executes only registered commands after preparation, preflight checks, and any required approval. The server includes both read and gated write or execution tools.
| When an agent needs to... | Without RepoPlane | With RepoPlane |
|---|---|---|
| Search a large repository | Inspect tool output and infer whether a scan finished | Get bounded matches with scope, count relation, and partial-state evidence |
| Resume work across sessions | Reconstruct prior decisions from conversation or files | Query durable checkpoints, memos, and verification records |
| Run a known project check | Find and invoke the command separately | Inspect a registered plan, then run it through preflight and approval gates |
Text search and file inspection support UTF-8, CP949, and EUC-KR for repositories with legacy
Korean encodings. Filename and text search use ripgrep (rg).
Download a Windows or Linux amd64 archive and SHA256SUMS.txt from
Releases. Verify the checksum and put
repoplane on PATH. Starting with v1.1.7, the Windows archive includes rg.exe; keep it next
to repoplane.exe.
On Linux, install ripgrep as rg to enable filename and
text search. Starting with v1.1.7, RepoPlane also starts without rg: those search modes report
unsupported with ripgrep_unavailable, while Git history, configured symbol search, records,
and Runner remain available. path_explain marks its basename scan partial. Git on PATH enables
optional history search. Building from source requires Go 1.26 or newer:
go test ./...
go build -trimpath -o bin/repoplane ./cmd/repoplaneOn Windows, build to bin/repoplane.exe. If the installed executable is serving an active MCP
session, rename that copy to old_repoplane_<timestamp>.exe before replacing it, and retain the
old copy until its process exits. New sessions use the replacement.
Register the default local stdio server with Codex:
codex mcp add repoplane -- repoplane
With no flags, the server uses its process working directory as the workspace and the user cache for private state. To pin both locations in a generic MCP client, use paths specific to your host; the state directory must be outside the workspace:
{
"mcpServers": {
"repoplane": {
"command": "repoplane",
"args": ["--workspace", "WORKSPACE", "--state-dir", "STATE_DIRECTORY"]
}
}
}For local stdio, if the state directory is inside the workspace (including a resolved link),
MCP still connects and lists its tools, but repository operations return workspace_state_overlap.
No repository services or private databases are opened in this blocked mode.
Call runtime_config with {"action":"status"} to inspect status: "blocked" and its warning,
then recover on the same connection with either of these requests, using an absolute host path:
{"action":"select","target":"workspace","values":["ABSOLUTE_PROJECT_DIRECTORY"]}
{"action":"select","target":"state_dir","values":["ABSOLUTE_STATE_DIRECTORY_OUTSIDE_WORKSPACE"]}Only status and these recovery selections are available while blocked. Invalid selections leave it blocked; a valid selection activates the normal tools without reconnecting. HTTP startup continues to reject overlapping paths. No additional enable flag is needed.
Start a new MCP session. Call catalog_query with {"mode":"status"} to see configured
catalog sources and whether registered execution is available. Then call workspace_search:
{"mode":"exact","pattern":"TODO","root":".","item_limit":20}Search, list, and query replies state their scope, count relation, truncation, warnings, and next
cursor. A stopped scan reports a lower bound or unknown count. Do not treat partial as complete.
The canonical repo-memory Codex skill is included at skills/repo-memory.
Install it by copying that directory to $CODEX_HOME/skills/repo-memory, then start a new Codex
session. Treat the repository copy as the source of truth and resync installed copies after it
changes. The skill retains verified feature and configuration knowledge before completion and
handles write recovery: correct invalid inputs, accept time warnings as saved, read before retrying
uncertain writes, and merge conflicts only when content differs. Changes to memo kind/schema or
topic identity require a successor; permission denials are never bypassed.
The default typed.v1 interface exposes 14 tools:
| Task | Tool |
|---|---|
| Find registered checks, commands, and missing catalog roots | catalog_query |
| Search names, text, Git history, and configured symbol indexes | workspace_search |
| Explain Git, symlink, encoding, newline, and repository-rule facts | path_explain |
| Read bounded ranges and query JSON, JSONL, logs, CSV, or TSV | data_query |
| Search durable verification, checkpoint, memo, and run evidence | project_records |
| Write intentions or import a local verification report | checkpoint_write, memo_write, check_report_import |
| Inspect and change session grants or live configuration | runtime_access, runtime_config |
| Prepare, execute, and inspect a registered capability | run_prepare, run_execute, run_inspect |
| Export durable memory and retained Runner evidence | memory_backup |
The typed tool schemas expose fixed choices for common search, query, and write fields. A cursor
can replace mode on paginated reads. memo_write requires a source value; use
user_asserted for a user-provided fact or llm_proposed for an agent proposal.
project_records(query="terms") infers mode=search. Search now defaults to eight brief cards
within 8 KiB: ID, title, one-line summary, topic key when present, validity, and time markers.
Clients that relied on the old implicit preview must request response_view=discovery explicitly.
It ranks current records first, then records matching more search terms; match_mode=all requires
every term. Search supports exact topic_key, scope, and configuration filters,
updated_after (inclusive), updated_before (exclusive), and validity. Use next_cursor for
the next page. The 30-minute cursor snapshot contains at most 1,000 candidates; a larger match
set is explicitly partial. response_view=discovery requests the earlier 320-character preview,
and response_view=full or payload_fields requests payload data. list keeps its existing
50-item, 64 KiB default. An empty call returns mode required.
Use project_records for background knowledge, including saved code, design decisions, and past
verification. Use catalog_query for registered commands/checks, and workspace_search or
data_query to inspect present source when a claim depends on its current state. A saved code
example can answer why an approach was chosen; it does not by itself prove that the current
implementation still matches. Check record evidence, scope, and invalidation conditions before
relying on it for that claim. validity=current on a memo means active, not freshly verified code.
Find a topic key with a brief search, then open one record with mode=get and id. If the key is
already known, mode=get_topic with an exact topic_key fetches the one current memo directly;
add exact scope and configuration when the key names several current memos. Ambiguity returns
short candidates rather than selecting one. A record's validity says whether the record is
active; temporal_kind and as_of say whether its content was a historical observation or
asserted as current guidance, and when. Older memos without this information say unknown.
Creating a non-host memo without both time fields succeeds but returns memo_time_unknown in
structured warnings and the short text receipt. It is valid to leave the content time unknown.
If evidence later establishes it, read the memo by ID and update it with expected_revision,
temporal_kind, and as_of. Updates replace the memo payload and evidence refs, so resubmit the
existing content, identity, invalidation condition, discovery fields, and evidence refs as well.
Never copy automatic created_at or updated_at into as_of without evidence that the content
was confirmed then. Typed host facts use their separate confirmed_at field.
Reading a superseded memo points to its successor and preserves the old content and evidence.
When a detailed memo cites a git:<commit> evidence ref, its read warns if the current commit
differs, the worktree is dirty, or Git cannot be observed. This compares the cited basis only;
it does not claim that relevant code changed or that the memo is incorrect. Brief searches do not
run this check, and source should be inspected before using an old procedure.
checkpoint_write can store a concise change_summary alongside the next action, evidence
refs, and background_refs to relevant current memos. project_records with mode=resume and
an exact checkpoint id, or a distinctive goal query, returns those fields in one call.
resume first requires every query term to match, while general record search ranks partial
matches. If no checkpoint matches every term, resume returns short partial-match candidates with
a warning; match_mode=any explicitly requests the broader match. If the goal matches several
checkpoints, it returns
short candidates. change_summary is written by the caller and is never inferred from Git:
committed history, uncommitted changes, and workspaces without Git can all be described. If it
was not recorded, change_state=unknown says so. Verify present files and checks before relying
on a previous agent's summary.
Checkpoint, memo, and report-import writes now default to response_view=receipt, returning the
record ID and revision without echoing the payload. Request response_view=full for the full
write result; clients that relied on full write echoes must opt in. Typed MCP replies include
repoplane/usage.v1 metadata with serialized structured result bytes, text-content bytes, and
server duration in milliseconds. These are server measurements, not model-token counts or
end-to-end network time. Compact search, resume, and write
replies use a short text fallback rather than duplicating the whole structured result. Detailed
reads carry the full JSON in text in the default compatible mode for text-only clients. This
is not proof that a host includes both representations in model input or bills both as tokens.
For a host that consumes structuredContent, start RepoPlane with
--result-content=deduplicated. Each ordinary MCP tools/call then automatically removes plain
text blocks containing the same JSON as the structured result, after SDK output processing.
No LLM decision, extra tool call, shell command, or response file is required. The typed surface
stays at 14 tools and the toolbox surface at five; both stdio and HTTP use the same middleware.
{
"mcpServers": {
"repoplane": {
"command": "repoplane",
"args": ["--workspace", "WORKSPACE", "--state-dir", "STATE_DIRECTORY", "--result-content=deduplicated"]
}
}
}For a TOML host configuration, add the flag to the existing server arguments (preserving any other arguments):
[mcp_servers.repoplane]
command = "repoplane"
args = ["--result-content=deduplicated"]Restart the MCP connection after changing startup arguments. The initialization instructions
report the effective result content mode. Only enable this mode after confirming that the host
reads structuredContent: a text-only consumer would lose the removed copy. Omit the flag or
use --result-content=compatible for the previous wire behavior. Client/protocol version alone
is not treated as evidence of structured-result consumption.
Structured payloads retain their original types and exact numbers. Unique text, compact search
cards, annotated/extended content, media, warnings, errors, approval handshakes, and metadata
remain. An all-duplicate content array becomes []. Typed per-call text_bytes reflects retained
text; the server measurements remain byte counts, not model-token accounting. Ambiguous JSON
is preserved. The mode is startup-only and does not change authorization or execution gates.
render-result remains available for captured responses and client-wrapper development; ordinary
MCP calls in deduplicated mode do not need it. To filter a captured whole CallToolResult:
repoplane render-result < result.jsonFrom PowerShell, use native input redirection without re-encoding the captured JSON:
cmd /c "repoplane render-result < result.json"The command reads one UTF-8 JSON CallToolResult object (not a JSON-RPC envelope) from stdin,
with a fixed 16 MiB input limit. It does not start the server, access a workspace, or open a
database. The output retains structuredContent and removes only plain text blocks containing
equivalent JSON; object key order and whitespace may differ. Unique text, media/resource blocks,
annotated or extended text blocks, errors, approval fields, and all envelope metadata remain.
Numbers are compared without floating-point conversion; ambiguous JSON with duplicate keys is
kept. An all-duplicate content array becomes []. Invalid or oversized input fails without
partial output. This filter changes wrapper display output, not MCP wire replies, and is not
automatically installed into an external host. Route captured results through the filter;
do not pipe the live MCP protocol stream through it.
Select payload data when the full body is unnecessary, independently of result deduplication. For example:
{"mode":"get_topic","topic_key":"build-policy","payload_fields":["content","invalidation_condition"]}payload_fields selects exact top-level fields: checks/counts for a verification record,
content/invalidation_condition for a memo, or title/summary when recorded. It does not select
sections inside prose or nested JSON paths. Record metadata, evidence refs, and warnings remain
available. These are separate savings from eliminating an identical text echo.
Records do not infer semantic similarity between differently worded memos. A caller can assign a
stable topic_key to a memo and explicitly update or supersede that identity.
update is a partial edit: supply id, expected_revision, and only the fields being changed.
Omitted memo and checkpoint fields, evidence refs, discovery metadata, and temporal evidence are
preserved. Explicit empty strings or arrays clear optional mutable fields; required content and
topic identity cannot be cleared. An explicitly supplied host object replaces that whole object
and must satisfy its complete validation contract. For example, a content-only edit needs no
repeat of scope, topic key, memo kind, title, or evidence:
{"mode":"update","id":"MEMO_ID","expected_revision":1,"content":"Revised guidance."}The same partial-edit rules apply to checkpoint_write; stale revisions are rejected rather
than overwriting newer content. Source provenance, memo kind, and supersession links are preserved.
A topic memo's
scope, configuration, and topic_key form an immutable identity. To replace a memo, create
its successor with supersedes and the old expected_revision; that atomically supersedes the
old record and links the new one.
Do not switch to supersession to work around an update error: inspect its field-specific message
and read the current record when necessary. mode=supersede by itself only retires the record;
it does not write a successor. Use successor creation only for an intentional identity/kind/schema
change or replacement. No new tools, flags, or database migration are needed for partial updates.
New decision or procedure memos can carry title, summary,
temporal_kind, and as_of for reliable discovery. Host facts retain their separate schema.
Memo titles and summaries allow at most 160 and 240 Unicode characters, respectively, matching
the schema's maxLength rules. Memo text fields together have a separate 32 KiB UTF-8 byte
budget. A rejected write identifies the exceeded field or budget and reports the observed limit.
A typed host fact uses memo_kind=host_fact and is stored as memo.v2 (not memo.v3). Its
host object requires alias, role, os, tier, services, paths, and an RFC3339
confirmed_at; the memo also requires invalidation_condition. services and paths may be
null when unknown, but when supplied their entries must be bounded, unique, and non-empty. A
host fact cannot use topic_key. Each list allows 64 entries independently, with at most 1024
Unicode characters per entry. For example:
{
"mode": "create",
"memo_kind": "host_fact",
"scope": "operations/hosts",
"source": "user_asserted",
"host": {
"alias": "host-a",
"role": "worker",
"os": "linux",
"tier": "production",
"services": ["worker"],
"paths": ["/srv/worker"],
"confirmed_at": "2026-01-01T00:00:00Z"
},
"invalidation_condition": "the host is rebuilt or its role changes",
"response_view": "receipt"
}Tool failures return a JSON object in the MCP error text with stable code, safe message, and
correlation_id fields. Mutation failures also include mutation_state: not_applied means the
write was rejected before commit, while unknown means a storage, deadline, or unexpected failure
requires a read-back before retrying. Durable writes distinguish invalid_argument,
invalid_transition, revision_conflict, and storage_failure; for storage_failure,
internal_error, and deadline_exceeded, server diagnostics log the correlation ID and the failing
stage on stderr without exposing database details or host paths to the client.
Catalog entries are YAML, JSON, or Markdown with YAML frontmatter under a configured catalog root. A minimal discovery-only entry is:
id: project.check
revision: 1
summary: Check the repository
use_when: [before a release]
tags: [verification]
cache_policy: disabledA runnable entry additionally declares its executable, argument template, working directory,
inputs, outputs, limits, and trusted_for_run: true; see
catalog/dev.verify.yaml for a working example. RepoPlane never
accepts an arbitrary executable or shell command from an MCP request. Runner validates a
registered ID with run_prepare, executes the returned plan ID once with run_execute, and
retains status and bounded evidence for run_inspect. Local execution requires user approval
unless the host pre-authorized Runner.
run_prepare returns a concise plan. run_inspect(action=status) returns a compact run summary.
detail, stdout, stderr, and artifact return stored references and sizes by default;
set response_view=bytes only when a full receipt or bounded output page must enter the MCP
response. A state: file reference is relative to the server's local state directory, available
from runtime_config(action=status). HTTP clients cannot open that server-local file directly.
Captured process output is raw and is not automatically redacted.
Runner launches executable_ref and passes argv_template as arguments from cwd; it does
not infer a Python interpreter from a .py file association. For a Python script with cwd: .,
register an available Python interpreter, for example executable_ref: python and
argv_template: [tools/example.py, "{message}"]. Verify the interpreter on the host first.
On Windows, register PowerShell explicitly as the executable and put the workspace-relative
script path in argv_template, for example executable_ref: powershell with
argv_template: [-NoProfile, -File, tools/Example.ps1]. On Windows, direct .py and .ps1
executable references are rejected during run_prepare with unsupported_script_type; the MCP
error explains which interpreter to register and where to put the script path and arguments.
Input and output globs are bounded;
a matched-file limit failure includes the responsible pattern, configured maximum, observed lower
bound, and a source-only-glob hint. Gitignored and build-output paths are included unless the
manifest narrows its patterns; RepoPlane only excludes its internal metadata directories.
Local stdio reads stay inside the resolved workspace unless an external path receives an exact,
read-only session grant. Record writes, report import, Runner, cache reuse, memory export, and
external reads require the relevant host setting or user approval. Grants last only for the MCP
process and can be inspected or revoked with runtime_access.
runtime_config(action=status) shows live catalog roots, candidate roots, rule files, symbol
indexes, workspace, state directory, HTTP transport, and tool surface. Local stdio operations
apply directly without an additional server approval prompt or enable flag, so unattended clients
can change sources or switch the workspace and state directory without waiting for a person.
Input/path validation, atomic service replacement, and active Runner/snapshot checks still apply.
Source-list action=replace requires an explicit values array: [] intentionally clears the
selected list, while omitting values is rejected without changing the configuration.
HTTP clients cannot call this tool. Use startup flags for fixed host defaults:
--workspace PATH primary workspace
--workspace-origin PATH former canonical absolute path; one-time legacy adoption after a move
--state-dir PATH private local state outside the workspace
--catalog-root PATH workspace-relative catalog source; repeatable
--candidate-root PATH executable candidate directory; repeatable
--rule-file NAME repository rule filename; repeatable
--symbol-index PATH configured symbol index; repeatable
--transport MODE stdio (default) or http
--http-profile PATH ignored local HTTP profile
--tool-surface SURFACE typed.v1 (default) or toolbox.v1
For a private registered deployment capability, check catalog_query(mode=status) and then
catalog_query(mode=search, query="deploy"). If the ID is absent, inspect the configured and
candidate roots with runtime_config(action=status). The server cannot discover an external
private profile whose path was never configured. Supply that path in the host's ignored local
configuration, restart or update the catalog root through local stdio runtime configuration, and
query the catalog again before calling run_prepare. A missing capability error names these next
tools and reports the profile location as unknown when appropriate. Search/list first pages also
warn about unconfigured candidate roots and summarize generation-wide audit issues with
catalog_issues_present. These issues can explain unavailable items, but do not prove why a
particular query missed. Use catalog_query(mode=audit) for file references and exact parse or
validation errors. Missing-ID errors include catalog_issue_count when nonzero and recommend
that audit for both catalog_query(mode=get) and Runner capability resolution. Cursor pages
retain their original result snapshot; diagnostics are reported on the first page. No extra
opt-in flag is required. Keep private profile content
and host paths out of tracked files. A copyable host configuration is:
{
"mcpServers": {
"repoplane": {
"command": "repoplane",
"args": ["--workspace", "WORKSPACE", "--state-dir", "STATE_DIRECTORY",
"--catalog-root", "catalog", "--catalog-root", "PRIVATE_CATALOG_ROOT",
"--candidate-root", "PRIVATE_TOOLS_ROOT"]
}
}
}The optional toolbox.v1 startup surface exposes five fixed tools:
repoplane_read, repoplane_write, repoplane_import, repoplane_runner, and
repoplane_state. Describe an allowlisted operation before calling it with the returned
content-bound schema handle. Changing surfaces requires a new session.
HTTP is opt-in. The example local and OAuth profiles show loopback binding, authentication, and scopes. Keep actual tokens, key files, host paths, and profiles outside the tracked tree. HTTP authorization is separate from local stdio session grants.
On the first successful upgraded startup, RepoPlane pins the existing path-derived ID in
the private .repoplane/workspace-id file. Existing memos, checkpoints, and other records
keep their IDs; no database migration is needed. Keep .repoplane/ ignored by Git in every
workspace (add /.repoplane/ to .gitignore) and never publish the identity file. Move this
file with the directory, retain the same --state-dir, and update --workspace or use:
{"action":"select","target":"workspace","values":["NEW_ABSOLUTE_WORKSPACE_PATH"]}runtime_config(action=status) reports workspace_identity: ["pinned", ".repoplane/workspace-id"].
The new directory remains the filesystem trust boundary; read grants do not travel with it.
Fresh Git clones without this ignored file remain separate projects. A full directory copy
including the file intentionally shares record identity; omit the file for an independent copy.
Keep only one active server per identity/state directory. The workspace must be writable for
initial pinning. The marker is published only after service initialization, using complete synced
bytes without replacing a competing opener's marker. Failed state initialization leaves no pinned
ID, so legacy adoption can still be retried. Oversized, non-regular, or damaged files are rejected
rather than silently creating a new identity. Only the small identity file is created inside the
workspace; databases and process output stay in the external state directory.
If a legacy workspace was already moved before upgrading, start once with the old canonical absolute path, even if that path no longer exists. Use the same original state directory:
repoplane --workspace NEW_WORKSPACE --state-dir ORIGINAL_STATE_DIRECTORY --workspace-origin FORMER_CANONICAL_ABSOLUTE_PATH
Copyable MCP host configuration for this one-time adoption:
{"mcpServers":{"repoplane":{"command":"repoplane","args":["--workspace","NEW_WORKSPACE","--state-dir","ORIGINAL_STATE_DIRECTORY","--workspace-origin","FORMER_CANONICAL_ABSOLUTE_PATH"]}}}Normal moves need no origin option or agent intervention: move the whole directory, including
.repoplane/ even when a copy tool skips dot directories, and keep using the existing state.
Agents must not add --workspace-origin on every move. Remove it after the first successful
legacy adoption. It cannot replace a conflicting
saved ID or merge separately created histories. The former path must match the old resolved
path (including the original drive/platform); Git remotes and folder names are not used to guess
identity. Retain the state databases: the small identity file does not contain the records.
If it is lost after a move, restore it or adopt the original pre-pin canonical path, not an
intermediate location. Machine changes also require transferring state or memory export/restore.
RepoPlane keeps regenerable search indexes and durable records in separate SQLite files under
--state-dir, never inside the workspace. Runner streams and captured artifacts are retained
there under bounded policies. Removing the state directory also removes local memos, checkpoints,
imported verification, run receipts, streams, artifacts, and cursors.
Each new query snapshot atomically removes up to 64 snapshots that have expired at the query's creation time, including their result items. Unexpired pagination snapshots and durable records are preserved. Cleanup runs on snapshot writes, not an idle timer; an existing backlog drains across subsequent queries. SQLite reuses the freed pages, so the database file need not shrink. This does not impose a total disk quota or prune durable record revisions.
Catalog refreshes also remove up to 16 retired generations in the current workspace, even when catalog contents are unchanged. A generation must have been replaced for at least one hour and have no unexpired query snapshot references; the current generation is always retained. Retirement is tracked separately from creation, and reactivation starts a fresh grace period on the next replacement. Runner plans retain execution details and revalidate against the current declaration, so they do not depend on retaining an old catalog generation. Durable execution receipts remain.
Retirement metadata uses an additive auxiliary table while preserving the index database's schema 3 compatibility marker and original catalog columns. Database triggers also track replacements and reactivations made by schema-3 publishers. Existing inactive generations start a fresh one-hour grace period when first observed. The known pre-release retirement schema 4 is recovered to this layout without dropping catalog data; unknown future schemas are still rejected. Running processes load the new cleanup code on their next restart. Cleanup runs during catalog refreshes, not an idle timer, and freed pages are reused without automatically compacting the file.
Use memory_backup or the CLI to export durable records and retained Runner evidence before a
machine reset:
repoplane memory export --destination BACKUP_DIRECTORY --workspace WORKSPACE --state-dir STATE_DIRECTORY
repoplane memory restore --archive BACKUP_ARCHIVE --workspace RESTORED_WORKSPACE --state-dir NEW_STATE_DIRECTORY
The archive may contain sensitive record or process output; store it privately. It excludes local HTTP tokens, host profiles, regenerable indexes, and audit data.
repoplane usage --workspace WORKSPACE --state-dir STATE_DIRECTORY
usage reports observed operation calls, serialized request/result bytes, duration, errors,
approvals, and reused runs. It does not measure model tokens or estimate savings.
go run ./cmd/repoplane-dev preflight
go run ./cmd/repoplane-dev verify
go run ./cmd/repoplane-dev public-release-checkThese commands write bounded reports under ignored .tmp/reports. The release workflow builds
versioned archives after successful CI and generates release notes from the matching
CHANGELOG.md section. RepoPlane is maintainer-led; public issues and pull requests are closed
for now. See SECURITY.md for vulnerability reporting.
CI runs on pushes to main and on pull requests, cancelling superseded runs for the same ref.
Source, schema, catalog, and workflow changes retain Ubuntu and Windows verification; main also
runs race tests and the public-release privacy check. Documentation-only changes, including a
release changelog promotion, use the lightweight scope check plus the main privacy check so the
release guard still receives a successful CI result for the exact release commit.