Every agent leaves a trace. RemaGraph is a lightweight MCP tool that captures what an AI coding agent leaves behind while it works, so whoever picks up next can follow the trail.
RemaGraph is a lightweight MCP (Model Context Protocol) tool built for AI coding agents that work across many tasks and hand-offs without a human in the loop for every step. Its job is simple: capture what an agent learned, decided, or ran into along the way, and make that trail retrievable later — by the same agent, or by a completely different one picking up the work. It complements CodeGraph: CodeGraph tracks what's structurally wrong with the code; RemaGraph tracks what happened while someone (or something) was working on it.
| Item | Status |
|---|---|
| Version | 0.7.2-beta (pre-1.0 beta; published on PyPI — uv tool install remagraph / pip install remagraph) |
| Status | v2: security / governance / reliability + cross-project collaboration + CLI (init/auto/store/search/status/maintain/link/migrate-project/install-hooks/serve) |
| Task memory convention | docs/task-memory-convention.md |
| Release prep | docs/reviews/v2-release-prep.md |
| Design SOT | DESIGN.md |
| v1 closeout status | docs/reviews/v1-closeout-status.md |
| Architecture | docs/architecture.md |
| Audit contract | docs/audit.md |
| Governance checklist | docs/governance/checklist.md |
| Contributing | CONTRIBUTING.md |
| Changelog | CHANGELOG.md |
RemaGraph is published on PyPI. Recommended install:
uv tool install remagraphor with plain pip:
pip install remagraphPrefer a specific tag, or want the bleeding edge from main? Install straight from the git repo:
# Pinned to a specific release tag
uv tool install git+https://github.com/aiken884/RemaGraph.git@v0.7.2-beta
# Whatever is currently on main
uv tool install git+https://github.com/aiken884/RemaGraph.gitOr install from source for local development:
git clone https://github.com/aiken884/RemaGraph.git
cd RemaGraph
uv pip install -e .Dependencies: Python ≥3.11, model2vec, mcp (FastMCP), pydantic.
- Install (see "Installation" above).
- Initialize (run inside your project folder — the project name defaults to
the folder name, or pass
--projectto choose one):remagraph init --project myproject source ~/.local/state/remagraph-myproject/env.sh
- Run a task in one shot — reads memory, executes, writes memory back:
or use the wrapper script:
remagraph auto --task-id fix-login-001 --agent-id my-ai -- echo "swap this for your actual command"
curl -O https://raw.githubusercontent.com/aiken884/RemaGraph/main/examples/simple/remagraph-task.sh chmod +x remagraph-task.sh TASK_ID=fix-login-001 AGENT_ID=my-ai ./remagraph-task.sh python my_agent.py
If you just want to check memory first, with no execution and no writes:
remagraph auto --recall-only --task-id fix-login-001 --agent-id my-aiNo code required. Full plain-language walkthrough: docs/task-memory-convention.md.
New users may also find docs/internal/alpha-test-playbook.md useful as an onboarding guide — it includes scenarios and a feedback template.
Note: this repository is public and the package is published on PyPI, but it's still a pre-1.0 beta (see BOUNDARIES.md — no frozen public API yet).
Point any MCP client at RemaGraph. Example configs for common clients follow.
Note (since the BUG 1 fix): remagraph serve now must be bound to a single project at startup — via either the --project <id> argument or the REMAGRAPH_PROJECT environment variable. If neither is set, it fails fast and never enters the MCP stdio loop, which prevents a process from silently reading and writing across projects if it happens to inherit another project's environment variables. Each project maps to its own dedicated remagraph serve process; a single running process cannot switch between projects dynamically.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"remagraph": {
"command": "remagraph",
"args": ["serve", "--project", "myproject"],
"env": {
"REMAGRAPH_STATE_DIR": "/home/user/.local/state/remagraph-myproject"
}
}
}
}Cursor (.cursor/mcp.json):
{
"mcpServers": {
"remagraph": {
"command": "remagraph",
"args": ["serve"],
"env": {
"REMAGRAPH_PROJECT": "myproject"
}
}
}
}OpenCode / Claude Code — any client that supports stdio MCP works the same way as above.
| Variable | Description | Default |
|---|---|---|
REMAGRAPH_STATE_DIR |
Directory where the SQLite DB lives | ~/.local/state/remagraph/ |
REMAGRAPH_PROJECT |
The current project binding (remagraph serve requires either this env var or --project at startup) |
none |
REMAGRAPH_FANOUT_CAP |
Max number of "other" project database connections search --cross-project-label/--include-related will open in a single call (--fanout-cap takes precedence over this env var) |
50 |
REMAGRAPH_FANOUT_HARD_CAP |
Hard ceiling for the cap above, only meant for explicit opt-in increases (rarely needs adjusting) | 200 |
REMAGRAPH_RESTRICTED_PREFIXES |
Comma-separated list of project_id prefixes (e.g. "team-a-,team-b-") that a deployment wants to forbid from ever using the default DB; the safety valve raises SafetyValveError if a matching project_id resolves to the default state dir. Empty by default — RemaGraph imposes no naming convention of its own |
none (no prefix restricted) |
Directories are created automatically when missing (mode 0700); DB files are created at mode 0600. Paths are checked against a security denylist that rejects system directories.
# Start the stdio MCP server (must be bound to a single project via --project or REMAGRAPH_PROJECT)
remagraph serve --project myproject
# Initialize / run a task in one shot
remagraph init --project myproject
remagraph auto --task-id T001 --agent-id my-agent -- make test
# Query (task-id alone is fine)
remagraph search --task-id T001
remagraph search --query "FastMCP lifecycle" --top-k 5
remagraph status --limit 10RemaGraph exposes five tools over MCP (stdio transport), compatible with Claude Desktop, Cursor, and other mainstream MCP clients: remagraph_store, remagraph_search, remagraph_status, remagraph_maintain, and remagraph_migrate_project.
An agent writes a memory record; it must pass five arbitration rules before landing in SQLite + the FTS5 index.
| Parameter | Type | Description |
|---|---|---|
project_id |
str |
Project identifier (same format rules as task_id, required) |
task_id |
str |
Task identifier (alphanumeric plus -_, max 64 characters) |
agent_id |
str |
Agent identifier (same format constraints as task_id) |
kind |
"task_handoff" | "status_update" | "discovered_constraint" | "fleet_member" |
Memory kind (fleet_member is recorded/recycled by the dispatching coordinator) |
summary |
str |
One-line summary (indexed for FTS5 full-text search) |
learnings |
list[str] |
Key takeaways |
handoff_note |
str |
Hand-off note (required when kind is task_handoff) |
tags |
list[str] |
Free-form classification tags (optional) |
invalidates |
list[str] |
Memory IDs to invalidate (used with discovered_constraint) |
labels |
list[str] |
Namespaced labels (optional), in namespace:value form (e.g. dep:opencode, topic:auth, kind:bug). Convention is a small, controlled set of namespace prefixes such as dep:/topic:/kind:; max length 64 characters. This is a distinct concept from tags — tags is free-form, labels is a controlled vocabulary, and a batch with any malformed label is rejected outright (reason: "invalid_label"). Used for exact matching by remagraph_search's cross_project_label; see the "Cross-Project Collaboration" section of DESIGN.md |
Behavior of the four kind values (PPLX Priority B):
task_handoff— a task hand-off record, carrieshandoff_notestatus_update— a status update; automatically supersedes the prior record for the sametask_iddiscovered_constraint— a newly discovered constraint; caninvalidatesexisting, now-wrong memoriesfleet_member— owned by the dispatching coordinator, records/recycles fleet members (task_id=fleetauto-supersedes)
FTS5 BM25 full-text search (trigram tokenizer, CJK-aware) plus tag/kind/agent_id/task_id filtering.
| Parameter | Type | Description |
|---|---|---|
query |
str |
Search keywords (Chinese, English, Japanese, and Korean all supported) |
top_k |
int |
Max results to return (default 20, max 100) |
kind |
str |
Filter by memory kind (optional) |
status |
"active" | "superseded" | "invalidated" |
Filter by status (optional) |
tags |
list[str] |
Filter by tags (optional) |
project_id |
str |
Restrict to a single project (optional) |
agent_id |
str |
Filter by agent (optional) |
task_id |
str |
Filter by task (optional) |
all_projects |
bool |
Default false; when true, removes the project_id filter within "this one database file" — each project is its own independent SQLite file, and this flag never opens any other file |
cross_project_label |
str |
Optional. When provided, this switches entirely to the cross-project label search path: via the shared project registry, it runs an exact label match across the current project plus every other known project, each its own independent database file (full-text/filter params like query/kind/tags don't apply on this path). Orthogonal to all_projects — the two are independent dimensions. Fan-out is capped at 50 "other" known projects; beyond that, the response is flagged cross_project_fanout_capped: true (see response fields below) instead of silently truncating and pretending the result set is complete. See the "Cross-Project Collaboration" section of DESIGN.md |
include_related |
bool |
Default false. A third, fully independent dimension from cross_project_label/all_projects: when true, fans out — in addition to the normal FTS query against the current project — to projects found within related_hops via a project_edges traversal (db.recall_related()), i.e. projects explicitly declared as related through the remagraph link CLI subcommand. Unlike cross_project_label, matching is still by the normal FTS query, not exact label match. Requires project_id as the traversal starting point; if project_id is omitted, this gracefully degrades to an ordinary search (no related fan-out) rather than raising an error |
related_hops |
int |
Default 1 (only directly-declared relations). Max 5. BFS traversal depth used when include_related=true; has no effect otherwise |
Short queries (≤2 characters) return an empty result set instead of raising an error. Beyond results/has_more, every response always carries cross_project_fanout_capped (bool; only meaningful when cross_project_label is used — ordinary queries always get false). When cross_project_label is used, each result also carries source_project_id marking which project it came from. Every result includes the full field set (id/project_id/summary/agent_id/kind/task_id/timestamp/score/learnings/handoff_note/tags/status/created_at/updated_at — everything except embedding).
Returns all active status_update memories, deduplicated by task_id (only the newest record per task survives). Also includes version-compatibility handshake info, so callers don't have to wait for a remagraph_store write to fail before learning about a version mismatch.
| Parameter | Type | Description |
|---|---|---|
project_id |
str |
Restrict to a single project (optional) |
limit |
int |
Max results to return (default 20, max 100) |
all_projects |
bool |
Default false; true removes the project_id filter |
Beyond the existing latest array, the response always includes these compatibility handshake fields:
| Field | Type | Description |
|---|---|---|
server_code_version |
int |
Schema version of the code currently running |
db_schema_version |
int | null |
The schema version actually recorded in the database's _meta table (a defensive read) |
min_reader_version |
int | null |
Oldest code version allowed to read this database; null if the database predates this mechanism |
min_writer_version |
int | null |
Oldest code version allowed to write to this database; same null behavior as above |
upgrade_hint |
str | null |
Upgrade guidance text embedded in the database; null if absent |
read_only |
bool |
Whether the current connection is in read-only degraded mode (see "Governance & Security" below) |
Runs automatic database maintenance (WAL checkpoint, prune superseded/invalidated memories, FTS5 optimize, VACUUM, integrity check), gated by the same safety valve (maintenance.safety_validate_project) used by the CLI maintain subcommand.
| Parameter | Type | Description |
|---|---|---|
project_id |
str |
Project to run maintenance on (required) |
force |
bool |
Default false; when true, every maintenance step runs unconditionally instead of only the ones whose threshold has actually been crossed |
Note: unlike the CLI maintain subcommand, this MCP tool has no dry_run option — every call executes for real.
Response (success): {"status": "ok", "stats": {...}}. Response (failure): {"status": "error", "reason": "..."}.
stats is produced by maintenance.run_maintenance(); most fields only appear when the corresponding step actually ran:
| Field | Type | Description |
|---|---|---|
project_id |
str |
Echoes the project maintained |
started_at |
str |
ISO-8601 UTC timestamp when maintenance started |
wal_checkpoint |
str |
"done" if PRAGMA wal_checkpoint(TRUNCATE) ran |
pruned_count |
int |
Number of superseded/invalidated memories deleted (older than prune_superseded_age_days, default 90 days; capped per task by prune_superseded_max_per_task, default 5) |
fts_optimized |
bool |
true if the FTS5 index was optimized |
vacuum |
str |
"done" if VACUUM ran (triggered once DB file size exceeds vacuum_threshold_mb, default 50 MB) |
size_before_mb |
float |
DB file size in MB immediately before VACUUM; only present when vacuum ran |
integrity |
str |
Result of PRAGMA quick_check; expected "ok" — any other value raises and is recorded as an audit violation |
skipped |
bool |
true when the connection was already downgraded to the read-only schema tier (see "Version Compatibility" in DESIGN.md); every write step above is skipped entirely |
skip_reason |
str |
"read_only_schema_tier" when skipped is true |
One-time migration of memories from a source project to a target project's independent DB (e.g. default → project-a), marking the originals invalidated in the source. Performs a real migration — this tool and the CLI's migrate-project subcommand (cli.cmd_migrate_project) both call the same shared core implementation (store.migrate_project_memories), so they always produce the same end state for the same inputs.
| Parameter | Type | Description |
|---|---|---|
from_project |
str |
Source project (required) |
to_project |
str |
Target project (required) |
dry_run |
bool |
Default false; when true, only computes and reports how many records would be migrated — no writes happen, and the count uses the exact same match query as a real run, so it always agrees with the count a subsequent real run reports |
How it works:
- The target project is validated through the same
safety_validate_project(to_project, require_env_match=False)safety valve used elsewhere (project-id naming-convention rules,project.jsonmetadata consistency, etc). - The source project's
state_diris resolved via the shared project registry (db.get_registered_state_dir(from_project)) — not a hardcoded path. Iffrom_projecthas never been registered (no priorremagraphcommand has resolved a state_dir for it), the call fails with a clear error rather than silently treating it as zero migratable records.from_project == "default"is the one exception: it resolves via the ambientREMAGRAPH_STATE_DIR/REMAGRAPH_HOMEthe same way any other "default"-project usage does, since"default"is deliberately never registered in the normal course of things. - Records that heuristically look like they belong to
to_project(aLIKEmatch againsttask_id/tags/agent_id/summary) are copied into the target project's own DB withproject_idforced toto_project, and the originals are markedstatus='invalidated'in the source with amigrated-to:<to_project>breadcrumb appended tolearnings. - If either the source or target database is currently in the read-only degraded schema-compatibility tier (see "Version Compatibility" below), the migration is rejected with a clear error instead of failing silently or partially.
Response (success): {"status": "ok" | "dry-run", "from": "...", "to": "...", "dry_run": true|false, "migrated_count": N, "skipped_ids": [...]}. Response (failure): {"status": "error", "reason": "..."}.
- Rate limiting — per-agent token bucket (60 calls / 60 seconds) to prevent abuse
- Input validation —
task_id/agent_idare checked against format rules via Pydantic validators - Path safety —
REMAGRAPH_STATE_DIRrejects system directory paths - Audit rotation —
audit-YYYYMM.jsonl, split automatically by month - DB size cap — SQLite
max_page_countset to a 100MB soft limit - Migration — built-in schema version tracking and a migration chain
- Version-compatibility degradation — when a database's schema version is newer than the running code, the database's own
min_reader_version/min_writer_versiondecide one of three outcomes: fully compatible (normal reads and writes), read-only degraded (writes rejected, reads unaffected), or refused outright. Callers can learn this ahead of time viaremagraph_status's compatibility handshake fields, without waiting for a write to fail. See the "Version Compatibility" section ofDESIGN.md - Cross-project registry —
project_registryautomatically records known projects and theirstate_dir, backingremagraph_search'scross_project_labelread-only cross-project lookups. See the "Cross-Project Collaboration" section ofDESIGN.md - Stale-record cleanup —
cleanup_superseded()can purge non-active records older than 90 days
Besides MCP mode, remagraph supports the following CLI subcommands (all with JSON output):
# One-shot, most recommended: read memory → run the command → write memory
remagraph auto --task-id task-001 --agent-id my-agent -- make test
# Initialize
remagraph init --project myproject
# Write memory
remagraph store \
--task-id task-001 --agent-id my-agent --kind status_update \
--summary "Task complete, all tests passing, no regressions found" \
--learnings '["Watch out for FastMCP lifecycle handling"]' \
--tags '["python","mcp"]'
# Query (task-id alone is fine, query is not required)
remagraph search --task-id task-001
remagraph search --query "FastMCP lifecycle" --top-k 5
# Query latest status
remagraph status --limit 10Plain-language convention: docs/task-memory-convention.md.
Full spec: DESIGN.md; audit contract: docs/audit.md.
# uv is recommended
uv sync --all-extras
uv run ruff check src tests
uv run mypy src/
uv run pytest -m 'not slow'
REMAGRAPH_STATE_DIR=$(mktemp -d) uv run pytest tests/smoke- CI pipeline: smoke → adversarial → lint (ruff + mypy) → test (coverage ≥80%); plus gitleaks, pip-audit, and mutmut (non-blocking).
- Never let tests default to writing production state — smoke tests must use
REMAGRAPH_STATE_DIRor pytest'stmp_path.
DESIGN.md— the design source of truth: schema, arbitration rules, cross-project collaboration, version compatibilityCHANGELOG.md— version historyCONTRIBUTING.md— how to contributeSECURITY.md— how to report a vulnerabilitydocs/architecture.md— system architecturedocs/task-memory-convention.md— plain-language guide to the task-memory conventiondocs/audit.md— the audit log contractdocs/governance/checklist.md— the governance checklist
Apache-2.0. RemaGraph is an independent project — its source code is entirely self-authored and does not contain or derive from any other project's code.
If RemaGraph is quietly keeping your agents' memory straight, you can support its development on Ko-fi (card or PayPal) or directly via PayPal. Entirely optional — RemaGraph is, and stays, free.