Skip to main content

Active Graph

The graph is the world. Behaviors are physics. The trace is the proof.

PyPI Python versions Tests License: Apache-2.0 arXiv

An event-sourced reactive graph runtime for long-running, auditable, agentic systems. Behaviors react to a shared graph instead of talking to each other. Every change is traceable. Every run is resumable, forkable, and diff-able from its event log.

If chat-based agents are a group conversation, Active Graph is a shared workspace where everyone can see what changed, who changed it, and why.

🎥 New: Watch the AI Engineer talk — Active Graph introduced by its author in one sitting. The fastest way to get the mental model before you read further.

Where everything lives

This repo is the runtime; the project is spread across a few sites, papers, and sibling repos. Whether you are a person evaluating the framework or an AI assistant gathering context, these are the canonical entry points:

Resource What it is
activegraph.ai Project home — the pitch in one page
The AI Engineer talk Video introduction from the author
docs.activegraph.ai Full documentation: concepts, guides, cookbook, CLI + API reference, per-error catalog
learn.activegraph.ai Interactive tutorials — refactor a familiar agent loop onto the runtime, one substitution at a time
Blog Release write-ups and research notes from the project
"The Log is the Agent" (arXiv:2605.21997) The paper — the design argument for event-sourced agents
"Regimes" (arXiv:2606.10241) Follow-up research paper; code at yoheinakajima/regimes
activegraph-packs Open pack library: ~20 packs, demo bundles, a demo server, a React Inspector UI
activegraph-lab Self-hosted research agent running on the runtime in production
llms.txt · llms-full.txt Machine-readable docs for AI coding assistants
Status · CHANGELOG.md Where the project stands right now

The ecosystem and research sections below say more about each.

Try it in 30 seconds

pip install activegraph
activegraph quickstart

The bundled Diligence pack runs against recorded fixtures: no API key, no configuration, byte-deterministic output. You see what the framework does before you read about how it does it.

Then walk the 10-minute tutorial:

activegraph quickstart --interactive

It scaffolds a behavior, runs it against the same fixtures, and ends with the fork-and-diff workflow — the framework's most differentiated capability.

Install

pip install activegraph                    # core runtime + SQLite store + Diligence pack
pip install "activegraph[llm]"             # Anthropic + OpenAI providers
pip install "activegraph[anthropic]"       # Anthropic provider only
pip install "activegraph[openai]"          # OpenAI provider only (+ tiktoken)
pip install "activegraph[postgres]"        # Postgres-backed event store
pip install "activegraph[prometheus]"      # Prometheus metrics
pip install "activegraph[opentelemetry]"   # OpenTelemetry metrics
pip install "activegraph[all]"             # everything

Both LLM providers expose the same LLMProvider Protocol surface; swap one for the other without touching @llm_behavior definitions. The LLM providers reference covers the side-by-side surface, including tool use and provider-specific token counting.

Python 3.11+. Two hard dependencies (click for the CLI, pydantic for the pack format); persistence backends and provider integrations are opt-in extras.

What you get

  • Event-sourced graph runtime. Objects + typed relations + an append-only event log. Every mutation is an event; the trace is the audit trail.
  • Reactive behaviors as first-class. Function, class, LLM-backed, or attached to typed edges (the relation-behavior primitive — edges with logic). Subscriptions are event type + predicate + a Cypher subset for graph-shape patterns.
  • Fork-and-diff. Branch any run at any event into an independent fork, configure it differently, and structurally diff the result against the parent. Cache replay means the shared prefix doesn't re-execute (no new LLM calls). Most agent frameworks can't do this.
  • Packs. A pack bundles object types, behaviors, tools, prompts, and policies for a specific domain. The bundled Diligence pack is the reference: 8 object types, 7 behaviors, 3 tools, recorded fixtures. The open activegraph-packs library is the multi-pack reference architecture.
  • Isolated event export. EventSink streams accepted live events through a bounded per-sink worker without putting adapter I/O on the runtime hot path. JSONLEventSink is the first-party adapter; drops, queue depth, and failures are explicit in status and metrics, and normal replay never redelivers history.
  • Cooperative bounded drains. Single-writer hosts keep reads responsive during large derived-work drains with Runtime.run_quantum(max_queue_events=25, max_seconds=0.25). A yielded quantum never writes a false runtime.idle; repeated quanta are byte-identical to one run_until_idle() under the same deterministic inputs (CONTRACT v1.10 #3).
  • Per-error reference pages. Every error message ends with a More: link to a page that explains when it fires, why, and how to fix it. Catalog at docs.activegraph.ai/reference/errors.

Concepts at a glance

The framework's twelve primitives, in roughly the order you meet them when reading a trace. Each links to its concept page on the doc site; read those when you want depth on one piece.

  • Graph — objects and typed relations forming the world the framework reasons about. The graph is a projection of the event log; every mutation is an event. → concepts/graph
  • Events — the append-only history. Every behavior fires in response to events and produces more events; the trace is the ordered log of all of them. → concepts/events
  • Behaviors — the unit of reactive code. Function, class, or LLM-backed; declares what events it subscribes to and what it produces. The determinism contract is per-behavior. → concepts/behaviors
  • Relations — typed edges between objects, with their own behaviors. The relation-behavior primitive — coordination logic on the edge, not on either endpoint — is uncommon in other agent frameworks. → concepts/relations
  • Patches — proposed mutations with optimistic concurrency. Behaviors propose patches; the runtime applies or rejects them; rejections are events in their own right. → concepts/patches
  • Views — scoped reads of the graph for behavior context. Type filters, depth filters, recent-event windows. Views are how pattern-driven behaviors see only what they need to. → concepts/views
  • Frames — bounded contexts for a run. Goal, constraints, budget, and the registered behaviors for this frame. A run can have one frame or many. → concepts/frames
  • Policies — approval and gating for behavior capabilities. Which behaviors can call which tools, which mutations require human approval, what the runtime refuses. → concepts/policies
  • Patterns — the Cypher subset for pattern subscriptions. Beyond event-type + predicate, behaviors can subscribe to graph shapes (claim-cited-by-evidence, task-blocks-task, …) with NOT EXISTS and temporal predicates. → concepts/patterns
  • Replay — re-execute a run from its event log. Strict mode re-fires every behavior and fails on divergence; permissive mode reconstructs state without re-firing. The LLM replay cache is what makes fork cheap. → concepts/replay
  • Forking — branch any run at any event into an independent fork; structurally diff the fork against the parent. The framework's mechanism for hypothesis testing on agentic systems. → concepts/forking
  • Failure model — a behavior failure is a behavior.failed event, not an exception. The audit trail captures failures as first-class history. Exceptions live at runtime entry points only. → concepts/failure-model

The type system at a glance

What's fixed and what's yours. The framework speaks a small vocabulary of event types — the verbs of what happened. The nouns and edges of your domain are strings you choose.

Event types — fixed. The runtime emits these; the trace, replay, and observability surfaces all key off them.

  • Lifecycle: goal.created, runtime.idle, runtime.budget_exhausted
  • Graph: object.created, object.removed, relation.created, relation.removed
  • Behaviors: behavior.scheduled, behavior.started, behavior.completed, behavior.failed, relation_behavior.started
  • Patterns: pattern.matched
  • LLM: llm.requested, llm.responded
  • Tools: tool.requested, tool.responded
  • Patches: patch.proposed, patch.applied, patch.rejected
  • Approvals: approval.proposed, approval.granted
  • Packs: pack.loaded

Behaviors can also emit custom event types — any string. The task.completed signal in the example below is one: an application-level event the unblock relation behavior subscribes to, flowing through the same log alongside the framework's own.

Object and relation types — yours. Any string works. There is no central schema, no registration step, no enum to extend. graph.add_object("claim", {...}) creates a claim because you said claim; graph.add_relation(a, b, "depends_on") makes a depends_on edge because you said depends_on. Packs can attach optional Pydantic validation per type; absent a pack, the data passes through unchanged. The Diligence pack's object types (claim, evidence, risk, memo, …) and relation types (supports, contradicts, references, …) are an example ontology, not framework base types — you design your own for your domain.

Patch states — fixed. proposed → applied | rejected. Three values, two of them terminal.

The full model — composition, ontology design guidance, the Diligence pack as a worked example — lives at → concepts/type-system.

A small example

The relation-behavior primitive — coordination logic on the edge, not on either endpoint:

from activegraph import Graph, Runtime, behavior, relation_behavior

graph = Graph()
runtime = Runtime(graph, budget={"max_events": 200, "max_seconds": 60})

@behavior(name="planner", on=["goal.created"])
def planner(event, graph, ctx):
    research = graph.add_object("task", {"title": "Research", "status": "open"})
    memo = graph.add_object("task", {"title": "Draft memo", "status": "blocked"})
    graph.add_relation(research.id, memo.id, "depends_on")

@behavior(name="researcher", on=["object.created"], where={"object.type": "task"})
def researcher(event, graph, ctx):
    task = event.payload["object"]
    if task["data"]["status"] != "open" or "Research" not in task["data"]["title"]:
        return
    graph.add_object("claim", {"text": "Market early but growing.", "confidence": 0.7})
    graph.emit("task.completed", {"task_id": task["id"]})

@relation_behavior(name="unblock", relation_type="depends_on", on=["task.completed"])
def unblock(relation, event, graph, ctx):
    if event.payload["task_id"] == relation.source:
        graph.patch_object(relation.target, {"status": "open"})

runtime.run_goal("Evaluate this startup idea")
runtime.print_trace()

The unblock relation behavior fires only for events touching one of its edge endpoints. The conceptual deep-dive on edges-with-logic is in docs/concepts/relations.md.

Documentation

The ecosystem

Active Graph is one repo in a small open-source constellation. The siblings are where the runtime gets exercised for real — and they are good additional context to hand an AI assistant alongside this repo.

  • activegraph-packs — the open pack library: ~20 packs spanning infrastructure (tool gateway, secrets, memory, identity), communication adapters (Telegram, WhatsApp, email), and domain verticals (research, codebase, team ops, meetings), plus composed demo bundles, a Python demo server, and a React Inspector UI. The reference architecture for multi-pack assistants with no central orchestrator — coordination emerges through events on the shared graph.
  • activegraph-lab — a self-hosted autonomous research agent, built as a layered pack on activegraph-packs, whose mission is growing activegraph.ai's evidence base. It crawls, proposes claims and branches, and drafts posts behind human approval gates; the research notes on the blog are its published output, each carrying an evidence-linked provenance trail. It is also the runtime's longest-running production soak, and its findings feed back into the hardening milestones in Status.
  • regimes — research code for the Regimes paper: an autonomous evaluation-improvement loop that diagnoses failing runs into a failure-regime taxonomy, routes the dominant regime to the pipeline seam that can address it, and promotes repairs only through static, sandbox, in-sample, and held-out gates — running natively on the runtime, with every step recorded as an event.
  • FalkorDB graph store — the first external GraphStore backend (native edges, Cypher push-down), contributed by @dudizimber. See the FalkorDB guide and FalkorDB's write-up.

Research

What this is not

  • Not a chat framework. If your problem fits in one conversation, use a chat framework.
  • Not a workflow engine. Workflows model control flow. This models world state.
  • Not a rules engine, exactly. Rules engines forward-chain over facts. This event-sources over a graph and supports LLM behaviors as first-class.
  • Not a production graph database. The event log lives in SQLite (default) or Postgres behind the EventStore protocol; the materialized graph lives behind the GraphStore protocol — in-memory by default, or FalkorDB for a real, traversable graph backend. For a different high-throughput store, plug one in behind either protocol.
  • Not magic. Bad behaviors produce bad graphs. The runtime makes the badness inspectable, not absent.

Status

v1.12.0 (2026-09). v1.0 shipped in May 2026 after a three-rc external user-test gate per CONTRACT v1.0 #C4; the v1.1–v1.12 line followed, driven by a downstream self-modification stack — the activegraph-packs library and a governed fork→test→promote assistant — and hardened against findings from production deployments built on the runtime. See CHANGELOG.md for the full v0 → v1.12 history and per-version migration notes.

Major shipped milestones:

  • v1.12 — projection query planning (CONTRACT v1.12 #1–#4): object reads cross one structured ObjectQuery plan (GraphStore.query_objects), existence checks avoid full materialization, and optional FalkorDBGraphStore(indexed_fields=...) scalar indexes stay explicit projection configuration. Ships together with the staged v1.11 line below; there is no separate 1.11.0 release. Upgrades from 1.10.0 also get that line's integrity changes (ConcurrentWriterError, InvalidPatchOperation, detached events, single-writer runs).
  • v1.11 — (staged unreleased; ships with v1.12.0) trust-boundary integrity (CONTRACT v1.11 #1–#5): accepted events are canonical detached values and the authoritative append precedes projection; one writer per run; closed patch operations; name-based pack identifier ownership.
  • v1.10 — runtime legibility and cooperative hosts: opt-in context-read tracing (Runtime(trace_context_reads=True) — each behavior execution commits one batched context.read event recording what it looked at), reserved-field collisions fail loud with ReservedFieldError, and Runtime.run_quantum cooperative bounded drains. Default-configuration runs stay byte-identical.
  • v1.9 — the canonical action-class authority release: a single ceiling for what behaviors are allowed to do, explicit evaluation order, and an audit surface (CONTRACT v1.9 #1–#3). Published together with the staged v1.8 line below.
  • v1.8 — (staged unreleased; shipped with v1.9.0) isolated EventSink delivery; runtime-owned embedding record/replay; fail-closed direct web_fetch; deterministic wall-stop replay; and the provider-neutral TrialExecutor seam with the existing local subprocess as its honest, non-security-sandbox default; plus explicit, log-backed dev.override receipts bounded below governance authority.
  • v1.7 — subprocess trial-child hardening from downstream soaks: extra_packs for cross-pack interaction trials, the child's stderr captured into the trial report, code discovery made an explicit computed-PYTHONPATH channel (the env allow-list stays closed), sandbox.preflight(), and portable resource limits — RLIMIT_AS enforced on Linux, announced-off (not crashed) on macOS.
  • v1.6 — loader-side manifest validation as a warning tier at load_pack (structured, once per pack, never an error before 2.0); the fork-tail-removal promote invariant pinned as contract.
  • v1.5 — subprocess fork-trial isolation (run_forked_trial: a fresh-interpreter child against a fork, artifacts pinned by bundle hash) and compaction phase 1 (snapshot + archive tier + the retention pin set, never deletion).
  • v1.4 — the pack-manifest validator (load_manifest, verify_surface, content + bundle hashes), declarative Pack.capabilities, and disable_pack deregistration.
  • v1.3 — promote: apply a fork's net structural delta back to its parent, fail-closed on conflicts (the fork→test→promote loop); provider-compatibility hardening; traceback/DX surfacing; the EmbeddingProvider seam.
  • v1.2 — the GraphStore seam: the materialized projection becomes pluggable, with FalkorDBGraphStore (native edges, Cypher query push-down) as the first external backend, contributed by @dudizimber; the test suite becomes a CI gate.
  • v1.1 — bounded LLM retries for transient provider failures, inspect --memo / inspect --search, fork --set, OpenAI tool-shape parity, OpenTelemetryMetrics, and the spec-vs-impl drift gates.
  • v1.0 — error hierarchy rewrite with per-error reference pages, doc site at docs.activegraph.ai, activegraph quickstart command, mypy --strict and docstring coverage CI gates, wheel-completeness and deploy-verification CI gates.
  • v0.9 — pack format and the Diligence reference pack (8 object types, 7 behaviors, 3 tools, recorded fixtures).
  • v0.8 — operator surface: structured logging, Prometheus metrics, runtime.status(), full activegraph CLI, PostgresEventStore.
  • v0.7 — @tool decorator, Cypher-subset pattern subscriptions, temporal predicates.
  • v0.6 — @llm_behavior with structured output, frame-aware prompt construction, cost accounting.
  • v0.5 — full event-log persistence, save/load across processes, fork from any historical event, structural diff between runs.
  • v0 — core runtime: graph, behaviors, relation behaviors, patches with optimistic concurrency, views, frames, policies, budgets, the trace.

ROADMAP.md preserves the v1.3-cycle plan as a dated artifact (that line has since shipped — see its header note); CHANGELOG.md is the authoritative record of what landed. Unscheduled candidates live in FUTURE_IDEAS.md.

License

Active Graph is licensed under the Apache License 2.0. See LICENSE for the full text and NOTICE for the attribution that downstream redistributors must preserve.

Contributing

See CONTRIBUTING.md for the current contribution policy and TRIAGE.md for the invariant-first path from a report to a decision, milestone, and status: ready-for-pr. The framework is in its early public phase: issues are open, documentation PRs are welcome, and non-trivial code changes pass through the issue-first discussion gate.

Test discipline: tests must remain deterministic. No live network calls in CI. LLM and tool tests use recorded fixtures (RecordedLLMProvider, RecordedToolProvider). If a contribution adds a test that would only pass with a live API key or live HTTP, it cannot land.


The graph is the world. Behaviors are physics. The trace is the proof.

Release files for activegraph 1.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for activegraph 1.12.0
File Size Uploaded
activegraph-1.12.0.tar.gz 536.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for activegraph 1.12.0
File Interpreter ABI Platform
activegraph-1.12.0-py3-none-any.whl Python 3 none any Details

Total release size: 934.2 kB

Release files / activegraph-1.12.0.tar.gz

Download URL activegraph-1.12.0.tar.gz
Size 536.8 kB
Tags Source
SHA-256 checksum
How to use checksums
97ab3297069efbfa22321c6ae8e8bc5df4d9283a94156fedadf19ce2e8385ee8
BLAKE2b-256 checksum
How to use checksums
57d330c032adf7b6c2572ba45d53a4ab5ec64d01a0b946204db3a3c6eb416eeb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / activegraph-1.12.0-py3-none-any.whl

Download URL activegraph-1.12.0-py3-none-any.whl
Size 397.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
be908a01229636a4d272d1d841fcefdb40eee8c4c00882ff412f50e28e8d1c28
BLAKE2b-256 checksum
How to use checksums
118056d6660c1c6d39c90ab4248a08e449ae23f48dcd023303a4dea9a7a0bbf1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page