activegraph 1.1.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {activegraph-1.1.0/activegraph.egg-info → activegraph-1.2.0}/PKG-INFO +32 -12
- {activegraph-1.1.0 → activegraph-1.2.0}/README.md +23 -11
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/__init__.py +7 -1
- activegraph-1.2.0/activegraph/core/__init__.py +8 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/core/graph.py +108 -96
- activegraph-1.2.0/activegraph/core/graph_store.py +356 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/core/view.py +11 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/budget.py +13 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/patterns.py +39 -67
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/registry.py +3 -1
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/runtime.py +39 -2
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/view_builder.py +9 -4
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/__init__.py +5 -0
- activegraph-1.2.0/activegraph/store/falkordb.py +647 -0
- activegraph-1.2.0/activegraph/store/graph_conformance.py +491 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/memory.py +10 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/decorators.py +16 -0
- {activegraph-1.1.0 → activegraph-1.2.0/activegraph.egg-info}/PKG-INFO +32 -12
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph.egg-info/SOURCES.txt +5 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph.egg-info/requires.txt +6 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/pyproject.toml +17 -1
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_doc_links.py +6 -4
- activegraph-1.2.0/tests/test_falkordb_store.py +446 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_fork.py +20 -0
- activegraph-1.2.0/tests/test_graph_store.py +123 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llms_txt.py +49 -1
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_persistence.py +23 -0
- activegraph-1.1.0/activegraph/core/__init__.py +0 -1
- {activegraph-1.1.0 → activegraph-1.2.0}/LICENSE +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/NOTICE +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/__main__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/behaviors/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/behaviors/base.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/behaviors/decorators.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/cli/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/cli/main.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/cli/quickstart.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/cli/renderers.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/core/clock.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/core/event.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/core/ids.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/core/patch.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/frame.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/anthropic.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/cache.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/openai.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/parsing.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/prompt.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/provider.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/recorded.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/llm/types.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/observability/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/observability/logging.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/observability/metrics.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/observability/migration.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/observability/otel.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/observability/prometheus.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/observability/status.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/behaviors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/fixtures/companies.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/object_types.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/settings.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/diligence/tools.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/loader.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/packs/scaffold.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/policy.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/_live.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/behavior_graph.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/config_errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/diff.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/exec_errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/queue.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/registration_errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/runtime/scheduler.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/base.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/conformance.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/postgres.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/serde.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/sqlite.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/store/url.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/base.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/cache.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/context.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/graph_query.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/recorded.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/tools/web_fetch.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/trace/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/trace/causal.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph/trace/printer.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph.egg-info/dependency_links.txt +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph.egg-info/entry_points.txt +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/activegraph.egg-info/top_level.txt +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/setup.cfg +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_activate_after.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_causal_cross_tool.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_cli.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_cli_docs_flags.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_clock.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_diff.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_diligence_pack.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_diligence_with_tools.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_doc_python_snippets.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_doc_site_reachable.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_errors_format.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_event.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_graph.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_ids.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_license.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_anthropic.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_behavior.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_budget.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_causal.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_claim_extraction.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_default_model.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_determinism.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_failure.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_openai.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_prompt.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_provider_fixtures.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_replay.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_tool_loop.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_trace.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_trace_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_llm_types.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_migration.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_observability_logging.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_observability_metrics.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_operate_example.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_pack_scaffold.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_packs.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_patch.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_pattern_matcher.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_pattern_parser.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_pattern_subscriptions.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_postgres_store.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_quickstart.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_quickstart_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_reason_codes_docs.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_replay.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_replay_trace_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_requeue_unfired.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_resume_example.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_runtime.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_runtime_status.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_serde.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_store_conformance.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_store_url.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_tool_replay.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_tool_trace_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_tools.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_trace.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_tutorial_snippets.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_v1_0_1_patches.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_v1_0_3_behavior_failed_ux.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_v1_0_3_tool_multiturn.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_version_sync.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_view.py +0 -0
- {activegraph-1.1.0 → activegraph-1.2.0}/tests/test_wheel_completeness.py +0 -0
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: activegraph
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: An event-sourced reactive graph runtime for long-running, auditable, agentic systems.
|
|
5
5
|
Author: Active Graph contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/yoheinakajima/activegraph
|
|
8
|
+
Project-URL: Documentation, https://docs.activegraph.ai/
|
|
9
|
+
Project-URL: Repository, https://github.com/yoheinakajima/activegraph
|
|
10
|
+
Project-URL: Changelog, https://docs.activegraph.ai/about/changelog/
|
|
11
|
+
Project-URL: Issues, https://github.com/yoheinakajima/activegraph/issues
|
|
8
12
|
Keywords: graph,agents,event-sourcing,runtime
|
|
9
13
|
Classifier: Programming Language :: Python :: 3
|
|
10
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -32,6 +36,10 @@ Requires-Dist: pydantic>=2; extra == "openai"
|
|
|
32
36
|
Provides-Extra: sqlite
|
|
33
37
|
Provides-Extra: postgres
|
|
34
38
|
Requires-Dist: psycopg[binary]<4,>=3.1; extra == "postgres"
|
|
39
|
+
Provides-Extra: falkordb
|
|
40
|
+
Requires-Dist: falkordb>=1.0; extra == "falkordb"
|
|
41
|
+
Provides-Extra: falkordb-embedded
|
|
42
|
+
Requires-Dist: falkordblite>=0.10; extra == "falkordb-embedded"
|
|
35
43
|
Provides-Extra: prometheus
|
|
36
44
|
Requires-Dist: prometheus_client>=0.20; extra == "prometheus"
|
|
37
45
|
Provides-Extra: opentelemetry
|
|
@@ -303,23 +311,34 @@ in [`docs/concepts/relations.md`](https://docs.activegraph.ai/concepts/relations
|
|
|
303
311
|
- Not a rules engine, exactly. Rules engines forward-chain over
|
|
304
312
|
facts. This event-sources over a graph and supports LLM behaviors
|
|
305
313
|
as first-class.
|
|
306
|
-
- Not a production graph database. The
|
|
307
|
-
|
|
308
|
-
|
|
314
|
+
- Not a production graph database. The event log lives in SQLite
|
|
315
|
+
(default) or Postgres behind the `EventStore` protocol; the
|
|
316
|
+
materialized graph lives behind the `GraphStore` protocol —
|
|
317
|
+
in-memory by default, or [FalkorDB](https://docs.activegraph.ai/guides/using-falkordb/)
|
|
318
|
+
for a real, traversable graph backend. For a different
|
|
319
|
+
high-throughput store, plug one in behind either protocol.
|
|
309
320
|
- Not magic. Bad behaviors produce bad graphs. The runtime makes the
|
|
310
321
|
badness inspectable, not absent.
|
|
311
322
|
|
|
312
323
|
## Status
|
|
313
324
|
|
|
314
|
-
**v1.0 (stable)** (2026-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
per-version migration notes.
|
|
325
|
+
**v1.2.0 (stable)** (2026-07). v1.0 shipped in May 2026 after a
|
|
326
|
+
three-rc external user-test gate per
|
|
327
|
+
[CONTRACT v1.0 #C4](CONTRACT.md#v10-c4-v10-ships-as-v10-rc1-first-time-user-gate-is-owned-externally);
|
|
328
|
+
v1.1.0 and v1.2.0 followed. See [CHANGELOG.md](CHANGELOG.md) for the
|
|
329
|
+
full v0 → v1.2 history and per-version migration notes.
|
|
320
330
|
|
|
321
331
|
Major shipped milestones:
|
|
322
332
|
|
|
333
|
+
- **v1.2** — the `GraphStore` seam: the materialized projection
|
|
334
|
+
becomes pluggable, with `FalkorDBGraphStore` (native edges, Cypher
|
|
335
|
+
query push-down) as the first external backend, contributed by
|
|
336
|
+
[@dudizimber](https://github.com/dudizimber); the test suite
|
|
337
|
+
becomes a CI gate.
|
|
338
|
+
- **v1.1** — bounded LLM retries for transient provider failures,
|
|
339
|
+
`inspect --memo` / `inspect --search`, `fork --set`, OpenAI
|
|
340
|
+
tool-shape parity, `OpenTelemetryMetrics`, and the
|
|
341
|
+
spec-vs-impl drift gates.
|
|
323
342
|
- **v1.0** — error hierarchy rewrite with per-error reference
|
|
324
343
|
pages, doc site at [docs.activegraph.ai](https://docs.activegraph.ai/),
|
|
325
344
|
`activegraph quickstart` command, mypy `--strict` and docstring
|
|
@@ -340,8 +359,9 @@ Major shipped milestones:
|
|
|
340
359
|
patches with optimistic concurrency, views, frames, policies,
|
|
341
360
|
budgets, the trace.
|
|
342
361
|
|
|
343
|
-
Roadmap items
|
|
344
|
-
[
|
|
362
|
+
Roadmap items for the current cycle are tracked in
|
|
363
|
+
[ROADMAP.md](ROADMAP.md); unscheduled candidates live in
|
|
364
|
+
[FUTURE_IDEAS.md](FUTURE_IDEAS.md).
|
|
345
365
|
|
|
346
366
|
## License
|
|
347
367
|
|
|
@@ -240,23 +240,34 @@ in [`docs/concepts/relations.md`](https://docs.activegraph.ai/concepts/relations
|
|
|
240
240
|
- Not a rules engine, exactly. Rules engines forward-chain over
|
|
241
241
|
facts. This event-sources over a graph and supports LLM behaviors
|
|
242
242
|
as first-class.
|
|
243
|
-
- Not a production graph database. The
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
- Not a production graph database. The event log lives in SQLite
|
|
244
|
+
(default) or Postgres behind the `EventStore` protocol; the
|
|
245
|
+
materialized graph lives behind the `GraphStore` protocol —
|
|
246
|
+
in-memory by default, or [FalkorDB](https://docs.activegraph.ai/guides/using-falkordb/)
|
|
247
|
+
for a real, traversable graph backend. For a different
|
|
248
|
+
high-throughput store, plug one in behind either protocol.
|
|
246
249
|
- Not magic. Bad behaviors produce bad graphs. The runtime makes the
|
|
247
250
|
badness inspectable, not absent.
|
|
248
251
|
|
|
249
252
|
## Status
|
|
250
253
|
|
|
251
|
-
**v1.0 (stable)** (2026-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
per-version migration notes.
|
|
254
|
+
**v1.2.0 (stable)** (2026-07). v1.0 shipped in May 2026 after a
|
|
255
|
+
three-rc external user-test gate per
|
|
256
|
+
[CONTRACT v1.0 #C4](CONTRACT.md#v10-c4-v10-ships-as-v10-rc1-first-time-user-gate-is-owned-externally);
|
|
257
|
+
v1.1.0 and v1.2.0 followed. See [CHANGELOG.md](CHANGELOG.md) for the
|
|
258
|
+
full v0 → v1.2 history and per-version migration notes.
|
|
257
259
|
|
|
258
260
|
Major shipped milestones:
|
|
259
261
|
|
|
262
|
+
- **v1.2** — the `GraphStore` seam: the materialized projection
|
|
263
|
+
becomes pluggable, with `FalkorDBGraphStore` (native edges, Cypher
|
|
264
|
+
query push-down) as the first external backend, contributed by
|
|
265
|
+
[@dudizimber](https://github.com/dudizimber); the test suite
|
|
266
|
+
becomes a CI gate.
|
|
267
|
+
- **v1.1** — bounded LLM retries for transient provider failures,
|
|
268
|
+
`inspect --memo` / `inspect --search`, `fork --set`, OpenAI
|
|
269
|
+
tool-shape parity, `OpenTelemetryMetrics`, and the
|
|
270
|
+
spec-vs-impl drift gates.
|
|
260
271
|
- **v1.0** — error hierarchy rewrite with per-error reference
|
|
261
272
|
pages, doc site at [docs.activegraph.ai](https://docs.activegraph.ai/),
|
|
262
273
|
`activegraph quickstart` command, mypy `--strict` and docstring
|
|
@@ -277,8 +288,9 @@ Major shipped milestones:
|
|
|
277
288
|
patches with optimistic concurrency, views, frames, policies,
|
|
278
289
|
budgets, the trace.
|
|
279
290
|
|
|
280
|
-
Roadmap items
|
|
281
|
-
[
|
|
291
|
+
Roadmap items for the current cycle are tracked in
|
|
292
|
+
[ROADMAP.md](ROADMAP.md); unscheduled candidates live in
|
|
293
|
+
[FUTURE_IDEAS.md](FUTURE_IDEAS.md).
|
|
282
294
|
|
|
283
295
|
## License
|
|
284
296
|
|
|
@@ -61,7 +61,10 @@ from activegraph.store import (
|
|
|
61
61
|
DuplicateEventError,
|
|
62
62
|
EventNotFoundError,
|
|
63
63
|
EventStore,
|
|
64
|
+
FalkorDBGraphStore,
|
|
65
|
+
GraphStore,
|
|
64
66
|
InMemoryEventStore,
|
|
67
|
+
InMemoryGraphStore,
|
|
65
68
|
InvalidStoreURL,
|
|
66
69
|
NonSerializableEventError,
|
|
67
70
|
RunRecord,
|
|
@@ -142,11 +145,14 @@ __all__ = [
|
|
|
142
145
|
"EventNotFoundError",
|
|
143
146
|
"EventStore",
|
|
144
147
|
"ExecutionError",
|
|
148
|
+
"FalkorDBGraphStore",
|
|
145
149
|
"Frame",
|
|
146
150
|
"FrozenClock",
|
|
147
151
|
"Graph",
|
|
152
|
+
"GraphStore",
|
|
148
153
|
"IDGen",
|
|
149
154
|
"InMemoryEventStore",
|
|
155
|
+
"InMemoryGraphStore",
|
|
150
156
|
"IncompatibleRuntimeState",
|
|
151
157
|
"InternalEvaluatorError",
|
|
152
158
|
"InvalidActivateAfter",
|
|
@@ -224,4 +230,4 @@ __all__ = [
|
|
|
224
230
|
"tool",
|
|
225
231
|
]
|
|
226
232
|
|
|
227
|
-
__version__ = "1.
|
|
233
|
+
__version__ = "1.2.0"
|
|
@@ -28,6 +28,7 @@ from typing import Any, Callable, Optional
|
|
|
28
28
|
|
|
29
29
|
from activegraph.core.clock import Clock
|
|
30
30
|
from activegraph.core.event import Event
|
|
31
|
+
from activegraph.core.graph_store import GraphStore, InMemoryGraphStore
|
|
31
32
|
from activegraph.core.ids import IDGen
|
|
32
33
|
from activegraph.core.patch import Patch
|
|
33
34
|
|
|
@@ -104,16 +105,19 @@ class Graph:
|
|
|
104
105
|
ids: Optional[IDGen] = None,
|
|
105
106
|
clock: Optional[Clock] = None,
|
|
106
107
|
run_id: Optional[str] = None,
|
|
108
|
+
graph_store: Optional[GraphStore] = None,
|
|
107
109
|
) -> None:
|
|
108
110
|
self.ids = ids or IDGen()
|
|
109
111
|
self.clock = clock or Clock()
|
|
110
112
|
# CONTRACT v0.5 #6: every graph has a run_id.
|
|
111
113
|
self.run_id: str = run_id or self.ids.run()
|
|
112
114
|
|
|
113
|
-
# projected state — touched ONLY by apply_event (CONTRACT v0.5 #15)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
self.
|
|
115
|
+
# projected state — touched ONLY by apply_event (CONTRACT v0.5 #15).
|
|
116
|
+
# v1.2: the projection lives behind a GraphStore so it can be kept
|
|
117
|
+
# in process memory (the default) or pushed into an external graph
|
|
118
|
+
# database. `self._state` is the materialized current-state view; it
|
|
119
|
+
# is NOT the durable event log (that is `self._store`, an EventStore).
|
|
120
|
+
self._state: GraphStore = graph_store or InMemoryGraphStore()
|
|
117
121
|
|
|
118
122
|
# the log
|
|
119
123
|
self._events: list[Event] = []
|
|
@@ -147,19 +151,19 @@ class Graph:
|
|
|
147
151
|
return frozenset(self._replayed_ids)
|
|
148
152
|
|
|
149
153
|
def get_object(self, id_: str) -> Optional[Object]:
|
|
150
|
-
return self.
|
|
154
|
+
return self._state.get_object(id_)
|
|
151
155
|
|
|
152
156
|
def get_relation(self, id_: str) -> Optional[Relation]:
|
|
153
|
-
return self.
|
|
157
|
+
return self._state.get_relation(id_)
|
|
154
158
|
|
|
155
159
|
def get_patch(self, id_: str) -> Optional[Patch]:
|
|
156
|
-
return self.
|
|
160
|
+
return self._state.get_patch(id_)
|
|
157
161
|
|
|
158
162
|
def all_objects(self) -> list[Object]:
|
|
159
|
-
return
|
|
163
|
+
return self._state.all_objects()
|
|
160
164
|
|
|
161
165
|
def all_relations(self) -> list[Relation]:
|
|
162
|
-
return
|
|
166
|
+
return self._state.all_relations()
|
|
163
167
|
|
|
164
168
|
def get_relations(
|
|
165
169
|
self,
|
|
@@ -167,19 +171,25 @@ class Graph:
|
|
|
167
171
|
type: Optional[str] = None,
|
|
168
172
|
direction: str = "both",
|
|
169
173
|
) -> list[Relation]:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
174
|
+
# Pushed down to the store via find_relations (see graph_store.py).
|
|
175
|
+
# Decompose the (object_id, direction) axis into source/target slots:
|
|
176
|
+
# "outgoing"/"incoming" map to a single directional filter; "both"
|
|
177
|
+
# needs source-OR-target, which find_relations (AND-only) can't express,
|
|
178
|
+
# so we push the type filter and apply the membership test here. An
|
|
179
|
+
# unrecognized direction keeps the v0 quirk of ignoring object_id.
|
|
180
|
+
if object_id is None:
|
|
181
|
+
return self._state.find_relations(type=type)
|
|
182
|
+
if direction == "outgoing":
|
|
183
|
+
return self._state.find_relations(source=object_id, type=type)
|
|
184
|
+
if direction == "incoming":
|
|
185
|
+
return self._state.find_relations(target=object_id, type=type)
|
|
186
|
+
if direction == "both":
|
|
187
|
+
return [
|
|
188
|
+
r
|
|
189
|
+
for r in self._state.find_relations(type=type)
|
|
190
|
+
if object_id in (r.source, r.target)
|
|
191
|
+
]
|
|
192
|
+
return self._state.find_relations(type=type)
|
|
183
193
|
|
|
184
194
|
def relations(
|
|
185
195
|
self,
|
|
@@ -197,40 +207,27 @@ class Graph:
|
|
|
197
207
|
relation. ``Graph.get_relations(object_id=, type=, direction=)``
|
|
198
208
|
stays as a backward-compatible alias.
|
|
199
209
|
"""
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if source is not None and r.source != source:
|
|
203
|
-
continue
|
|
204
|
-
if target is not None and r.target != target:
|
|
205
|
-
continue
|
|
206
|
-
if type is not None and r.type != type:
|
|
207
|
-
continue
|
|
208
|
-
out.append(r)
|
|
209
|
-
return out
|
|
210
|
+
# Pushed down to the store (find_relations applies the same AND filter).
|
|
211
|
+
return self._state.find_relations(source=source, target=target, type=type)
|
|
210
212
|
|
|
211
213
|
def neighborhood(self, object_id: str, depth: int = 1) -> tuple[list[Object], list[Relation]]:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
break
|
|
230
|
-
return (
|
|
231
|
-
[self._objects[i] for i in seen_objs if i in self._objects],
|
|
232
|
-
[self._relations[i] for i in seen_rels if i in self._relations],
|
|
233
|
-
)
|
|
214
|
+
# Pushed down to the store (default is the same BFS; FalkorDB uses a
|
|
215
|
+
# native variable-length path query). See GraphStore.neighborhood.
|
|
216
|
+
return self._state.neighborhood(object_id, depth)
|
|
217
|
+
|
|
218
|
+
def match_chain(self, node_types, rels):
|
|
219
|
+
# Pushed down to the store: the default mirrors the matcher's
|
|
220
|
+
# structural chain walk; FalkorDB resolves the whole chain in one
|
|
221
|
+
# Cypher query. Node {prop: value} equality and WHERE stay in the
|
|
222
|
+
# matcher (Python). See GraphStore.match_chain.
|
|
223
|
+
return self._state.match_chain(node_types, rels)
|
|
224
|
+
|
|
225
|
+
def objects_in_types(self, types: list[str]) -> list[Object]:
|
|
226
|
+
# Pushed down to the store: OR-of-types in one query (FalkorDB uses
|
|
227
|
+
# ``type IN [...]``); the default single-pass scan preserves order.
|
|
228
|
+
# Used by the view builder for type-scoped views. See
|
|
229
|
+
# GraphStore.find_objects_in_types.
|
|
230
|
+
return self._state.find_objects_in_types(types)
|
|
234
231
|
|
|
235
232
|
def objects(
|
|
236
233
|
self,
|
|
@@ -244,10 +241,10 @@ class Graph:
|
|
|
244
241
|
and outside behaviors. `Graph.query(object_type=...)` is kept
|
|
245
242
|
as a backward-compatible alias.
|
|
246
243
|
"""
|
|
244
|
+
# Type filter is pushed down to the store via find_objects; the
|
|
245
|
+
# ``where`` predicate is evaluated in Python over that subset.
|
|
247
246
|
out: list[Object] = []
|
|
248
|
-
for o in self.
|
|
249
|
-
if type is not None and o.type != type:
|
|
250
|
-
continue
|
|
247
|
+
for o in self._state.find_objects(type):
|
|
251
248
|
if where and not _eval_where_on_object(where, o):
|
|
252
249
|
continue
|
|
253
250
|
out.append(o)
|
|
@@ -267,7 +264,8 @@ class Graph:
|
|
|
267
264
|
return self.objects(type=object_type, where=where)
|
|
268
265
|
|
|
269
266
|
def has_object_of_type(self, type_: str) -> bool:
|
|
270
|
-
|
|
267
|
+
# Pushed down via find_objects (scopes the scan to ``type_``).
|
|
268
|
+
return bool(self._state.find_objects(type_))
|
|
271
269
|
|
|
272
270
|
# ---------- listener API (runtime hooks here) ----------
|
|
273
271
|
|
|
@@ -399,7 +397,7 @@ class Graph:
|
|
|
399
397
|
timestamp=self.clock.now(),
|
|
400
398
|
)
|
|
401
399
|
self.emit(event)
|
|
402
|
-
return self.
|
|
400
|
+
return self._state.get_object(obj_id)
|
|
403
401
|
|
|
404
402
|
def add_relation(
|
|
405
403
|
self,
|
|
@@ -418,8 +416,8 @@ class Graph:
|
|
|
418
416
|
clean = _strip_provenance(copy.deepcopy(data or {}))
|
|
419
417
|
# v0.9: relation type validation (source/target type rules).
|
|
420
418
|
if self._pack_relation_validator is not None:
|
|
421
|
-
src_obj = self.
|
|
422
|
-
tgt_obj = self.
|
|
419
|
+
src_obj = self._state.get_object(source)
|
|
420
|
+
tgt_obj = self._state.get_object(target)
|
|
423
421
|
self._pack_relation_validator(
|
|
424
422
|
type,
|
|
425
423
|
src_obj.type if src_obj else None,
|
|
@@ -456,7 +454,7 @@ class Graph:
|
|
|
456
454
|
timestamp=self.clock.now(),
|
|
457
455
|
)
|
|
458
456
|
self.emit(event)
|
|
459
|
-
return self.
|
|
457
|
+
return self._state.get_relation(rel_id)
|
|
460
458
|
|
|
461
459
|
def remove_relation(
|
|
462
460
|
self,
|
|
@@ -466,7 +464,7 @@ class Graph:
|
|
|
466
464
|
caused_by: Optional[str] = None,
|
|
467
465
|
frame_id: Optional[str] = None,
|
|
468
466
|
) -> None:
|
|
469
|
-
if relation_id
|
|
467
|
+
if self._state.get_relation(relation_id) is None:
|
|
470
468
|
return
|
|
471
469
|
event = Event(
|
|
472
470
|
id=self.ids.event(),
|
|
@@ -487,7 +485,7 @@ class Graph:
|
|
|
487
485
|
caused_by: Optional[str] = None,
|
|
488
486
|
frame_id: Optional[str] = None,
|
|
489
487
|
) -> None:
|
|
490
|
-
if object_id
|
|
488
|
+
if self._state.get_object(object_id) is None:
|
|
491
489
|
return
|
|
492
490
|
event = Event(
|
|
493
491
|
id=self.ids.event(),
|
|
@@ -514,7 +512,7 @@ class Graph:
|
|
|
514
512
|
tool_request_event_ids: Optional[list[str]] = None,
|
|
515
513
|
) -> Patch:
|
|
516
514
|
"""Auto-apply shortcut: build patch, version-check, emit applied/rejected."""
|
|
517
|
-
obj = self.
|
|
515
|
+
obj = self._state.get_object(target)
|
|
518
516
|
if obj is None:
|
|
519
517
|
raise KeyError(f"unknown object: {target}")
|
|
520
518
|
clean = _strip_provenance(copy.deepcopy(updates))
|
|
@@ -552,7 +550,7 @@ class Graph:
|
|
|
552
550
|
timestamp=self.clock.now(),
|
|
553
551
|
)
|
|
554
552
|
self.emit(event)
|
|
555
|
-
return self.
|
|
553
|
+
return self._state.get_patch(patch.id)
|
|
556
554
|
|
|
557
555
|
def propose_patch(
|
|
558
556
|
self,
|
|
@@ -570,7 +568,7 @@ class Graph:
|
|
|
570
568
|
) -> Patch:
|
|
571
569
|
# Strip "object:" / "relation:" prefix if present (README sugar).
|
|
572
570
|
normalized = target.split(":", 1)[1] if ":" in target else target
|
|
573
|
-
obj = self.
|
|
571
|
+
obj = self._state.get_object(normalized)
|
|
574
572
|
expected_version = obj.version if obj else 0
|
|
575
573
|
clean = _strip_provenance(copy.deepcopy(value))
|
|
576
574
|
patch = Patch(
|
|
@@ -602,7 +600,7 @@ class Graph:
|
|
|
602
600
|
timestamp=self.clock.now(),
|
|
603
601
|
)
|
|
604
602
|
self.emit(event)
|
|
605
|
-
return self.
|
|
603
|
+
return self._state.get_patch(patch.id)
|
|
606
604
|
|
|
607
605
|
def apply_patch(
|
|
608
606
|
self,
|
|
@@ -612,7 +610,7 @@ class Graph:
|
|
|
612
610
|
caused_by: Optional[str] = None,
|
|
613
611
|
frame_id: Optional[str] = None,
|
|
614
612
|
) -> Event:
|
|
615
|
-
patch = self.
|
|
613
|
+
patch = self._state.get_patch(patch_id)
|
|
616
614
|
if patch is None:
|
|
617
615
|
raise KeyError(f"unknown patch: {patch_id}")
|
|
618
616
|
if patch.status != "proposed":
|
|
@@ -620,7 +618,7 @@ class Graph:
|
|
|
620
618
|
raise InvalidPatchLifecycleState(
|
|
621
619
|
patch_id=patch_id, current_status=patch.status,
|
|
622
620
|
)
|
|
623
|
-
target_obj = self.
|
|
621
|
+
target_obj = self._state.get_object(patch.target)
|
|
624
622
|
current_version = target_obj.version if target_obj else 0
|
|
625
623
|
if current_version != patch.expected_version:
|
|
626
624
|
return self._reject(
|
|
@@ -667,8 +665,8 @@ class Graph:
|
|
|
667
665
|
caused_by: Optional[str],
|
|
668
666
|
frame_id: Optional[str],
|
|
669
667
|
) -> Event:
|
|
670
|
-
patch = self.
|
|
671
|
-
current = self.
|
|
668
|
+
patch = self._state.get_patch(patch_id)
|
|
669
|
+
current = self._state.get_object(patch.target)
|
|
672
670
|
event = Event(
|
|
673
671
|
id=self.ids.event(),
|
|
674
672
|
type="patch.rejected",
|
|
@@ -732,57 +730,71 @@ def apply_event(graph: Graph, event: Event) -> None:
|
|
|
732
730
|
|
|
733
731
|
if t == "object.created":
|
|
734
732
|
o = p["object"]
|
|
735
|
-
graph.
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
733
|
+
graph._state.put_object(
|
|
734
|
+
Object(
|
|
735
|
+
id=o["id"],
|
|
736
|
+
type=o["type"],
|
|
737
|
+
data=copy.deepcopy(o["data"]),
|
|
738
|
+
version=o["version"],
|
|
739
|
+
provenance=copy.deepcopy(o["provenance"]),
|
|
740
|
+
)
|
|
741
741
|
)
|
|
742
742
|
|
|
743
743
|
elif t == "object.removed":
|
|
744
|
-
graph.
|
|
745
|
-
# cascade: drop relations touching it
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
744
|
+
graph._state.remove_object(p["id"])
|
|
745
|
+
# cascade: drop relations touching it. Pushed down to two scoped
|
|
746
|
+
# lookups (out-edges + in-edges) instead of scanning every relation;
|
|
747
|
+
# on FalkorDB these are index-backed queries. Dedupe by id so a
|
|
748
|
+
# self-loop (source == target) is removed once.
|
|
749
|
+
touching = graph._state.find_relations(
|
|
750
|
+
source=p["id"]
|
|
751
|
+
) + graph._state.find_relations(target=p["id"])
|
|
752
|
+
seen: set[str] = set()
|
|
753
|
+
for r in touching:
|
|
754
|
+
if r.id in seen:
|
|
755
|
+
continue
|
|
756
|
+
seen.add(r.id)
|
|
757
|
+
graph._state.remove_relation(r.id)
|
|
750
758
|
|
|
751
759
|
elif t == "relation.created":
|
|
752
760
|
r = p["relation"]
|
|
753
|
-
graph.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
761
|
+
graph._state.put_relation(
|
|
762
|
+
Relation(
|
|
763
|
+
id=r["id"],
|
|
764
|
+
source=r["source"],
|
|
765
|
+
target=r["target"],
|
|
766
|
+
type=r["type"],
|
|
767
|
+
data=copy.deepcopy(r["data"]),
|
|
768
|
+
provenance=copy.deepcopy(r["provenance"]),
|
|
769
|
+
)
|
|
760
770
|
)
|
|
761
771
|
|
|
762
772
|
elif t == "relation.removed":
|
|
763
|
-
graph.
|
|
773
|
+
graph._state.remove_relation(p["id"])
|
|
764
774
|
|
|
765
775
|
elif t == "patch.proposed":
|
|
766
776
|
patch_dict = p["patch"]
|
|
767
|
-
graph.
|
|
777
|
+
graph._state.put_patch(_patch_from_dict(patch_dict))
|
|
768
778
|
|
|
769
779
|
elif t == "patch.applied":
|
|
770
780
|
patch_dict = p["patch"]
|
|
771
781
|
patch = _patch_from_dict({**patch_dict, "status": "applied"})
|
|
772
|
-
graph.
|
|
773
|
-
obj = graph.
|
|
782
|
+
graph._state.put_patch(patch)
|
|
783
|
+
obj = graph._state.get_object(patch.target)
|
|
774
784
|
if obj is not None:
|
|
775
785
|
if patch.op == "update":
|
|
776
786
|
obj.data.update(patch.value)
|
|
777
787
|
elif patch.op == "replace":
|
|
778
788
|
obj.data = copy.deepcopy(patch.value)
|
|
779
789
|
obj.version += 1
|
|
790
|
+
graph._state.put_object(obj)
|
|
780
791
|
|
|
781
792
|
elif t == "patch.rejected":
|
|
782
|
-
existing = graph.
|
|
793
|
+
existing = graph._state.get_patch(p["patch_id"])
|
|
783
794
|
if existing is not None:
|
|
784
795
|
existing.status = "rejected"
|
|
785
796
|
existing.rejection_reason = p["reason"]
|
|
797
|
+
graph._state.put_patch(existing)
|
|
786
798
|
|
|
787
799
|
|
|
788
800
|
def _patch_from_dict(d: dict[str, Any]) -> Patch:
|