ActiveGraph v1.2.0 is live – x30 speedup

gkorland1 pts1 comments

Active Graph v1.2.0: The Graph Projection Becomes Pluggable — activegraph

← back to blog2026-07-03·Yohei Nakajima<br>Active Graph v1.2.0: The Graph Projection Becomes Pluggable<br>v1.2.0 makes the materialized graph a pluggable seam with FalkorDB as the first external backend — native edges, Cypher query push-down, a conformance suite for future backends — plus a CI gate for the full test suite and one license everywhere.<br>release<br>activegraph<br>graph<br>falkordb<br>community<br>announcement<br>Active Graph v1.2.0 is now live on PyPI.

This release has a different origin story than the last one. v1.1.0 was a planned tightening pass. v1.2.0 arrived from the outside: an external contributor, @dudizimber, opened four issues over one week, then shipped the pull requests that answered them. The result is the biggest architectural addition since packs — the materialized graph projection is now a pluggable seam, with FalkorDB as the first external backend.

TL;DR

New GraphStore seam: choose where the current-state graph materializes. The event log remains the source of truth; the projection is now a pluggable, disposable view of it.

FalkorDBGraphStore backs the projection with a real graph database — native edges, Cypher queries, a connected graph you can actually see in FalkorDB Browser.

Structural queries push down into the database. A 2-hop pattern match over 20,000 objects drops from ~8.9 s to ~300 ms, because cost now scales with the result, not the graph.

GraphStoreConformance is the extension contract: any future backend passes the same executable suite.

The full test suite is now a CI gate, on a Python 3.11 + 3.12 matrix, with the FalkorDB and Postgres conformance suites running instead of skipping.

Every architectural decision is locked in CONTRACT.md § v1.2, and one license — Apache 2.0 — now appears everywhere, including the website.

Install or upgrade:

code<br>pip install --upgrade activegraph

Where This Release Came From

Active Graph has always kept the materialized graph — objects, relations, patches — in process memory, rebuilt from the event log on every run. That is the right default. But it left three things hard once a run's current-state view got interesting: you couldn't query it with a graph query language, you couldn't share it across processes, and it had to fit on the heap.

The event log already had a seam for this — EventStore, with SQLite and Postgres behind it. The projection had no equivalent. Issue #38 named the gap, and the first PR introduced the GraphStore seam: an interface for where the projection lives, with InMemoryGraphStore as the unchanged default and FalkorDBGraphStore as the first external backend.

code<br>from activegraph import Graph, Runtime, FalkorDBGraphStore

store = FalkorDBGraphStore(host="localhost", port=6379, graph_name="run-42")<br>graph = Graph(graph_store=store)<br>rt = Runtime(graph=graph, behaviors=[...])

# or rematerialize a recorded run into FalkorDB:<br>rt = Runtime.load("runs.db", run_id="run-42", graph_store=store)

The invariant that makes this safe: the event log stays the source of truth, and the projection is disposable. Wiping a GraphStore loses nothing — replaying the log rebuilds it. Durability and audit continue to come from the EventStore. That asymmetry is what let the rest of this release move fast.

Install the backend with pip install 'activegraph[falkordb]' (server client) or pip install 'activegraph[falkordb-embedded]' (embedded engine, Python 3.12+). Neither is pulled in by [all] — opting into a graph database is always explicit.

Relations Became Real Edges

The first FalkorDB implementation stored each relation as a standalone node carrying source/target properties. That preserved a subtle Active Graph feature — dangling relations, where a relation can reference objects that don't exist yet — but it defeated the point of a graph database. The Browser rendered a cloud of disconnected nodes. Native traversal and graph algorithms didn't apply. Every hop was a manual property join.

Issue #41 called this out, and the follow-up PR remodeled the store. Relations are now native edges with a single fixed relationship type, carrying the relation's kind as an edge property:

code<br>MATCH (s:AGObject)-[r:AGRelation {type: 'knows'}]->(t:AGObject)<br>RETURN t.id, t.data

Dangling relations survive the remodel through a shared :AGNode endpoint label: real objects are :AGNode:AGObject, and a dangling endpoint is a bare :AGNode placeholder that promotes when its object arrives. The fixed relationship type also keeps every value a bound parameter — relation kinds never enter query text, so there is no injection surface in the write path.

And because the projection is disposable, the layout change needed no migration tooling. Point Runtime.load at the event log and it rematerializes in the new shape.

Queries Push Down Into the Database

Backing the projection with FalkorDB is only useful if queries run there. Initially they didn't: every Graph query pulled the whole projection...

graph projection falkordb activegraph from active

Related Articles