Problem statement
Active Graph keeps the materialized graph — objects, relations, and patches —
in process memory (InMemoryGraphStore), rebuilt from the event log on every
run. That's the right default, but it leaves three things hard to do once a
run's current-state view gets interesting:
- Query current state with a graph query language. There's no way to ask
"what does the graph look like right now?" with Cypher — dashboards, ad-hoc
exploration, and graph-algorithm work all have to walk Python objects.
- Share the projection across processes. A live run and a separate
read-only inspector (a dashboard, a sidecar) can't look at the same
current-state view; each would rebuild its own in-memory copy.
- Keep a large projection off the heap. Projections that don't fit
comfortably in process memory have nowhere else to go.
The event log already has a durable home behind the EventStore seam
(SQLite/Postgres). The projection has no equivalent seam — it's hardwired to
process memory. The goal is to make where the current-state view is
materialized a pluggable choice, without touching the "log is the source of
truth" model.
Current workaround
There isn't a clean one. Today you'd have to manually export the projection
into your own store after a run and re-query it out of band — with no
read-through, so it drifts the moment anything mutates:
rt.run_goal("…")
# Hand-roll an export into whatever external store you have.
for obj in rt.graph.all_objects():
my_graph_db.upsert_node(obj.id, obj.type, obj.data)
for rel in rt.graph.all_relations():
my_graph_db.upsert_edge(rel.id, rel.source, rel.target, rel.type)
This duplicates the projector logic, has to be re-run on every change, and
can't be used to inspect a recorded run (Runtime.load / Runtime.fork
always rebuild in memory). For larger graphs there's no workaround at all — the
projection must fit in the heap.
Proposed solution
Introduce a GraphStore seam for the materialized projection, parallel to the
existing EventStore seam for the log, plus a FalkorDB-backed implementation.
Concretely:
GraphStore abstraction with the current in-memory behavior as the default
InMemoryGraphStore, and a FalkorDBGraphStore that backs the projection
with a FalkorDB graph (objects/relations/patches as labelled nodes; all
values passed as bound Cypher params).
- Two FalkorDB connection modes: a running server via the
falkordb client
(explicit url=/host= args or FALKORDB_URL / FALKORDB_HOST
(_PORT / _USERNAME / _PASSWORD) env vars), falling back to the embedded
falkordblite engine. Behind optional extras
(activegraph[falkordb] / activegraph[falkordb-embedded]), kept out of
[all]/[dev].
- Wire the seam in at the library level only —
Graph(graph_store=…), and
threaded through Runtime.load(…, graph_store=…) and
Runtime.fork(…, graph_store=…) so recorded runs and forks can rematerialize
in FalkorDB too. No CLI flag: the CLI's read commands operate on the log
and are intentionally infrastructure-light, so the projection backend stays a
programmatic choice.
from activegraph import Graph, Runtime, FalkorDBGraphStore
store = FalkorDBGraphStore(host="localhost", port=6379, graph_name="run-42")
graph = Graph(graph_store=store)
rt = Runtime(graph=graph, behaviors=[...])
# … or replay an existing run into FalkorDB:
rt = Runtime.load("runs.db", run_id="run-42", graph_store=store)
Explicitly out of scope: the event log stays the source of truth. The
GraphStore is a disposable projection — durability and audit continue to come
from the EventStore, and a wiped FalkorDB graph is fully rebuilt by replaying
the log. This is additive and the in-memory default is unchanged.
Problem statement
Active Graph keeps the materialized graph — objects, relations, and patches —
in process memory (
InMemoryGraphStore), rebuilt from the event log on everyrun. That's the right default, but it leaves three things hard to do once a
run's current-state view gets interesting:
"what does the graph look like right now?" with Cypher — dashboards, ad-hoc
exploration, and graph-algorithm work all have to walk Python objects.
read-only inspector (a dashboard, a sidecar) can't look at the same
current-state view; each would rebuild its own in-memory copy.
comfortably in process memory have nowhere else to go.
The event log already has a durable home behind the
EventStoreseam(SQLite/Postgres). The projection has no equivalent seam — it's hardwired to
process memory. The goal is to make where the current-state view is
materialized a pluggable choice, without touching the "log is the source of
truth" model.
Current workaround
There isn't a clean one. Today you'd have to manually export the projection
into your own store after a run and re-query it out of band — with no
read-through, so it drifts the moment anything mutates:
This duplicates the projector logic, has to be re-run on every change, and
can't be used to inspect a recorded run (
Runtime.load/Runtime.forkalways rebuild in memory). For larger graphs there's no workaround at all — the
projection must fit in the heap.
Proposed solution
Introduce a
GraphStoreseam for the materialized projection, parallel to theexisting
EventStoreseam for the log, plus a FalkorDB-backed implementation.Concretely:
GraphStoreabstraction with the current in-memory behavior as the defaultInMemoryGraphStore, and aFalkorDBGraphStorethat backs the projectionwith a FalkorDB graph (objects/relations/patches as labelled nodes; all
values passed as bound Cypher params).
falkordbclient(explicit
url=/host=args orFALKORDB_URL/FALKORDB_HOST(
_PORT/_USERNAME/_PASSWORD) env vars), falling back to the embeddedfalkordbliteengine. Behind optional extras(
activegraph[falkordb]/activegraph[falkordb-embedded]), kept out of[all]/[dev].Graph(graph_store=…), andthreaded through
Runtime.load(…, graph_store=…)andRuntime.fork(…, graph_store=…)so recorded runs and forks can rematerializein FalkorDB too. No CLI flag: the CLI's read commands operate on the log
and are intentionally infrastructure-light, so the projection backend stays a
programmatic choice.
Explicitly out of scope: the event log stays the source of truth. The
GraphStoreis a disposable projection — durability and audit continue to comefrom the
EventStore, and a wiped FalkorDB graph is fully rebuilt by replayingthe log. This is additive and the in-memory default is unchanged.