A temporal fact graph in one SQLite file — schema-optional, attributable, portable, and built for local AI systems.
fgraph stores knowledge as immutable EAV facts ⟨entity, attribute, value, asserted-tx, retracted-tx?⟩. Current state is a view; history, provenance, and transaction receipts are first-class. It needs no server or loadable SQLite extension, yet provides bounded Datalog, keyword/vector search, exact snapshots, portable event replay, schema introspection, shapes, and a read-only-by-default MCP server.
- Universal data model. Add facts immediately; declare only the attributes that need types, refs, cardinality, uniqueness, vector-model identity, or validation shapes.
- Time and provenance by construction.
at,history,diff,why, and durable receipts answer what changed, when, by whom, and from which source. - Agent-safe writes. Operation ids make retries idempotent; basis checks reject stale decisions; cardinality-one CAS supports atomic create/replace/delete;
undowrites an audited compensation. - Inspectable and portable schema. Rich snapshots distinguish declared, effective, and observed behavior. A canonical
schema/1manifest exports, checks, and atomically applies explicit declarations and shapes across runtimes. - Attributable retrieval. FTS5 + caller-provided vectors + exact filters + graph expansion return matched facts and their asserting provenance. Search work and outputs are explicitly bounded.
- Two honest portability paths.
tail/applyreplicate logicalevent/1streams;snapshot/restorereproduce exact retained state, including history, receipts, and excision redactions. - One file, three native runtimes. Python, Go, and TypeScript write the same logical rows and read each other's files. Shared conformance, differential traces, and an immutable format-v2 fixture enforce the contract.
- Designed for local agents. MCP is read-only unless
--writeis explicit, responses are capped, cursors are basis-pinned, and every tool publishes input/output schemas, safety annotations, and server instructions. No core code makes a network call.
From this checkout:
uv sync --project python
uv run --project python python examples/python/quickstart.pyThe stable registry installation is uv add 'fgraph>=1,<2'.
import fgraph
with fgraph.connect("memory.db") as db:
initial = db.transact(
{
"id": "ada",
"person/name": "Ada Lovelace",
"person/city": "London",
},
source="wikipedia",
by="importer",
operation_id="person:ada:v1",
)
db.declare("person/knows", ref=True, many=True)
db.transact({"id": "grace", "person/name": "Grace Hopper"})
db.transact({"id": "ada", "person/knows": {"ref": "grace"}})
move = db.transact({"id": "ada", "person/city": "Lyon"})
assert db.entity("ada")["person/city"] == "Lyon"
assert db.at(initial.tx).entity("ada")["person/city"] == "London"
print(db.history("ada", "person/city"))
print(db.why("ada", "person/city"))
print(db.receipt(move.tx))
result = db.q(
find=["?friend"],
where=[
["ada", "person/knows", "?entity"],
["?entity", "person/name", "?friend"],
],
)
assert result.rows == [["Grace Hopper"]]schema() is the agent/tooling surface; it returns declared, effective, and observed behavior plus shapes and a digest. attributes() remains a compact human discovery API.
For a known cardinality-one attribute, ['cas', entity, attribute, expected, desired] performs an atomic comparison. The exact { "missing": true } sentinel creates an absent fact when used as expected, or deletes a present fact when used as desired.
The Go module is github.com/fmind/fgraph/go and is CGO-free. The strict ESM package is @fmind/fgraph and targets the Node.js 24 LTS line (24.19+).
go get github.com/fmind/fgraph/go@v1.0.0
npm add @fmind/fgraph@^1.0.0db, err := fgraph.Open("memory.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Transact(ctx,
fgraph.E{"id": "ada", "person/name": "Ada Lovelace"},
fgraph.WithOperationID("person:ada:v1"),
)
entity, err := db.Entity(ctx, "ada")import { connect } from "@fmind/fgraph";
using db = connect("memory.db");
db.transact(
{ id: "ada", "person/name": "Ada Lovelace" },
{ operationId: "person:ada:v1" },
);
const rows = db.q({
find: ["?name"],
where: [["ada", "person/name", "?name"]],
}).rows;Run the checked-in examples with mise run test:examples.
All three implementations expose the same v1 commands. The examples below use an installed fgraph; from the checkout, substitute uv run --project python fgraph, go/bin/fgraph, or node typescript/dist/cli.js after building.
fgraph --db project.db add \
--operation-id project-status-v1 \
'{"id":"project","project/status":"v1 candidate"}'
# Bounded, resumable NDJSON loading. Completed batches survive interruption.
fgraph --db project.db add \
--batch-size 500 \
--operation-id-prefix import-project-v1 \
- < entities.ndjson
fgraph --db project.db schema project/
fgraph --db project.db schema-export > project.schema.json
fgraph --db project.db schema-check @project.schema.json
fgraph --db project.db explain \
'{"find":["?status"],"where":[["project","project/status","?status"]]}'
fgraph --db project.db datoms avet --components '["project/status"]'
fgraph --db project.db tx 67
# Portable logical replication.
fgraph --db project.db tail --since 64 > events.ndjson
fgraph --db replica.db apply events.ndjson
# Exact retained-state recovery.
fgraph --db project.db snapshot > snapshot.ndjson
fgraph --db restored.db restore snapshot.ndjson
fgraph --db project.db backup project-backup.db
fgraph --db project-backup.db doctor --jsonIrreversible excision requires an idempotency key and the basis you reviewed:
basis="$(fgraph --db project.db schema --json | jq -r .basis_tx)"
fgraph --db project.db excise private-subject \
--operation-id privacy-request-42 \
--if-basis-tx "$basis"fgraph mcp opens the database read-only and exposes:
recall, about, why, history, query, datoms, receipt, schema, and explain.
Opting into mcp --write adds remember, forget, and undo. Writes require operation ids; destructive calls also require a current basis. Every successful tool response uses {"ok":true,"basis_tx":...,"data":...} and is capped at 256 KiB. MCP initialization tells agents to inspect schema, preserve read bases, paginate, and guard writes; tool discovery includes output schemas and read/destructive/idempotent/open-world annotations.
# Read-only project knowledge base.
claude mcp add --scope project fgraph -- fgraph --db ./project.db mcp
# Explicitly writable agent memory.
claude mcp add --scope project fgraph-memory -- \
fgraph --db ./agent-memory.db mcp --writeAn optional --embed-cmd reads text on stdin and emits one JSON vector on stdout. It is shell-free and bounded; core never downloads models or calls a provider.
Install the small companion skills to teach a coding agent the fgraph model and its safe MCP workflow:
npx skills add fmind/fgraph --skill fgraph --skill fgraph-mcpUse fgraph for data modeling, transactions, temporal reads, and queries. Use fgraph-mcp when configuring or operating an agent memory or project knowledge base over MCP.
The checked-in benchmark exercises each native CLI independently at 1k, 10k, and 100k named entities. The 100k workload writes three scalar application facts per entity plus 5,000 caller-provided 384-dimensional vectors: 305,000 application facts in 500-entity transactions. Read figures are medians of three fresh-process end-to-end CLI runs, so runtime and package startup are included. The harness invokes the installed Python entry point, compiled Go binary, and compiled Node.js CLI directly.
At 100,000 entities:
| Runtime | Ingest entities/s | Point get | Scalar filter | Connected join | Keyword search | Exact vector search |
|---|---|---|---|---|---|---|
| Python | 3,161 | 240 ms | 236 ms | 393 ms | 754 ms | 735 ms |
| Go | 3,762 | 150 ms | 147 ms | 231 ms | 823 ms | 629 ms |
| TypeScript | 3,753 | 218 ms | 263 ms | 288 ms | 707 ms | 553 ms |
| Runtime | Snapshot | Restore | Event tail | Event apply |
|---|---|---|---|---|
| Python | 7.31 s | 30.58 s | 4.69 s | 42.59 s |
| Go | 8.06 s | 23.03 s | 3.94 s | 28.02 s |
| TypeScript | 11.76 s | 23.25 s | 4.22 s | 19.91 s |
The common logical state occupied 92.02 MiB; its snapshot was 60.53 MiB and its event stream 22.52 MiB. Vector search is intentionally exact over the 5,000 vectors, not ANN. These measurements validate the tested 100k envelope, not millions of entities or a service-level objective; SQLite build, filesystem, runtime startup, and hardware affect the numbers.
This release run was generated on 2026-08-26 from clean source commit 2c38061 and source digest sha256:e72df5222bb6af2e07c27192e4c7378d7e12a1bb43946aedb3f2716530105e4f. The raw metadata records the exact runtime, SQLite, platform, workload, and clean-tree provenance.
Reproduce the complete run with mise run benchmark. The harness has no timing pass/fail threshold and writes the raw NDJSON observations plus both accessible SVG charts.
nohistory removes superseded fact rows but does not erase their canonical event payloads. For privacy deletion, excise removes all facts where a target is entity, attribute, or ref value and redacts every retained event that mentions it. The identity registry name remains, so use opaque names and keep sensitive identifiers in facts.
Snapshots/backups and external collectors are separate copies with their own retention policy. Event hashes detect corruption but are not tamper evidence: a writer that controls the file can rewrite it. Put file permissions, encryption, snapshots, and audit collection outside an untrusted agent's control.
Use another system for high-concurrency distributed writers, tamper-evident logs, large blobs, approximate vector retrieval at scale, or billion-edge analytics. fgraph is one durable SQLite writer with many readers and exact linear vector search. Run mise run benchmark on your corpus and hardware.
docs/content/docs/spec.md— normative fgraph v1 / SQLite format-v2 contract.python/,go/,typescript/— native peers.conformance/— shared behavior, differential scenario, and immutable format-v2 fixture.examples/— runnable acceptance examples.docs/— Hugo documentation site.skills/— installable Agent Skills for fgraph users.CONTRIBUTING.md— the cross-runtime contribution contract.SECURITY.md— private reporting and the supported trust boundary.CHANGELOG.md— candidate and released user-visible changes.RELEASING.md— exact proof, publication, and rollback procedure.
The source gate is mise run all. No commit, package publication, or hosted deployment is implied by a local green candidate.
MIT — © 2026 Médéric Hurier (Fmind).