Getting started
Install
No published package yet — install from a source checkout, next to a checkout of
fg-agent-id (the only runtime dependency; everything else is stdlib):
python -m venv .venv
.venv/bin/pip install -e ../fg-agent-id -e ".[dev]"
# with the optional MCP stdio server
.venv/bin/pip install -e ../fg-agent-id -e ".[dev,mcp]"
Requires Python ≥ 3.11. The mcp extra pulls mcp>=1.0; only the MCP server
imports it — the core never does.
Verify with the test suite (hermetic — no network, no model):
.venv/bin/python -m pytest -q
Two calls
from fg_agent_memory import Memory
memory = Memory("./memory") # readable directory you can commit to git
memory.remember("John's favorite editor is Zed.")
print(memory.recall("what editor does John use?").as_prompt_block())
That's the whole porcelain: remember writes, recall reads a budgeted,
prompt-ready block. Remembering the same content twice deduplicates automatically —
the repeat reinforces the record and merges tags instead of duplicating it.
The ghost-memory demo
The problem: you learn X, later learn not-X, and stale X haunts retrieval forever. Watch what happens here instead:
from fg_agent_memory import Memory
memory = Memory("./memory")
a = memory.remember("The staging database is Postgres.")
b = memory.remember("The staging database is not Postgres.")
memory.consolidate() # detects the contradiction; neither side dropped
print(memory.recall("staging database").as_prompt_block())
# Relevant memories (query: staging database):
# - The staging database is Postgres. [disputed: one side negates the other]
# - The staging database is not Postgres. [disputed: one side negates the other]
Both sides are surfaced, explicitly marked as disputed. The dispute stays visible until you resolve it, with a reason:
memory.resolve(a.record_id, b.record_id,
"checked infra: staging moved off Postgres in March")
print(memory.recall("staging database").as_prompt_block())
# Relevant memories (query: staging database):
# - The staging database is not Postgres.
The loser isn't deleted — it is superseded and rides the history trail:
print(memory.recall("staging database", include_history=True).as_prompt_block())
# Relevant memories (query: staging database):
# - The staging database is not Postgres.
# - The staging database is Postgres. [superseded — kept for history]
Under the hood the loser's full version trail is preserved
(active → transitional → superseded), with superseded_by pointing at the winner and
your reason recorded as state_reason.
What just happened
rememberran the write stage: a create-only extractor turned text into records and appended them to a versioned, never-delete store.consolidateran the deterministic pipeline — dedup, near-dup merge, contradiction detection, promotion, rule-flag sweep. The model-free heuristic operators detected the negation and opened a transition pair.resolvewas the human step. The porcelain default has no auto-resolver — contradictions stay visible until you settle them.recallwas state-aware: disputed records surfaced both sides atomically, superseded ones only appeared underinclude_history.
MCP server
With the [mcp] extra installed, expose a memory directory over stdio:
fg-agent-memory-mcp --path ~/agent-memory
Client configuration:
{ "mcpServers": { "memory": { "command": "fg-agent-memory-mcp",
"args": ["--path", "/home/me/agent-memory"] } } }
The server exposes five tools: remember, recall, consolidate, redact, status.
There is deliberately no resolve tool — settling a contradiction requires the Python
API, keeping dispute resolution a deliberate act. Memory holds one lock around every
public operation, so sharing an instance across threads or MCP worker pools is safe.