activegraph 1.0.5.post2__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.0.5.post2/activegraph.egg-info → activegraph-1.2.0}/PKG-INFO +43 -14
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/README.md +26 -13
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/__init__.py +9 -1
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/cli/main.py +281 -13
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/cli/quickstart.py +4 -87
- activegraph-1.2.0/activegraph/cli/renderers.py +97 -0
- activegraph-1.2.0/activegraph/core/__init__.py +8 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/graph.py +108 -96
- activegraph-1.2.0/activegraph/core/graph_store.py +356 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/view.py +11 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/cache.py +9 -5
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/errors.py +14 -14
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/openai.py +92 -27
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/__init__.py +5 -2
- activegraph-1.2.0/activegraph/observability/otel.py +105 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/loader.py +84 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/budget.py +13 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/patterns.py +39 -67
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/registry.py +3 -1
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/runtime.py +342 -64
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/view_builder.py +9 -4
- {activegraph-1.0.5.post2 → 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.0.5.post2 → activegraph-1.2.0}/activegraph/store/memory.py +10 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/decorators.py +16 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/trace/printer.py +16 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0/activegraph.egg-info}/PKG-INFO +43 -14
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/SOURCES.txt +10 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/requires.txt +15 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/pyproject.toml +23 -1
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_cli.py +174 -2
- activegraph-1.2.0/tests/test_cli_docs_flags.py +78 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_diff.py +15 -2
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_doc_links.py +6 -4
- activegraph-1.2.0/tests/test_doc_python_snippets.py +85 -0
- activegraph-1.2.0/tests/test_falkordb_store.py +446 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_fork.py +20 -0
- activegraph-1.2.0/tests/test_graph_store.py +123 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_anthropic.py +6 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_failure.py +159 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_openai.py +235 -26
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_replay.py +81 -1
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llms_txt.py +49 -1
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_observability_metrics.py +63 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_persistence.py +23 -0
- activegraph-1.2.0/tests/test_reason_codes_docs.py +32 -0
- activegraph-1.2.0/tests/test_version_sync.py +84 -0
- activegraph-1.0.5.post2/activegraph/core/__init__.py +0 -1
- activegraph-1.0.5.post2/tests/test_version_sync.py +0 -30
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/LICENSE +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/NOTICE +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/__main__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/behaviors/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/behaviors/base.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/behaviors/decorators.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/cli/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/clock.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/event.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/ids.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/patch.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/errors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/frame.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/anthropic.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/parsing.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/prompt.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/provider.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/recorded.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/types.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/logging.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/metrics.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/migration.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/prometheus.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/status.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/behaviors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/fixtures/companies.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/object_types.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/settings.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/tools.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/scaffold.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/policy.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/_live.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/behavior_graph.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/config_errors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/diff.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/errors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/exec_errors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/queue.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/registration_errors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/scheduler.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/base.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/conformance.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/errors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/postgres.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/serde.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/sqlite.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/url.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/base.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/cache.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/context.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/errors.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/graph_query.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/recorded.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/web_fetch.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/trace/__init__.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/trace/causal.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/dependency_links.txt +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/entry_points.txt +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/top_level.txt +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/setup.cfg +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_activate_after.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_causal_cross_tool.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_clock.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_diligence_pack.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_diligence_with_tools.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_doc_site_reachable.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_errors_format.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_event.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_graph.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_ids.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_license.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_behavior.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_budget.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_causal.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_claim_extraction.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_default_model.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_determinism.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_prompt.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_provider_fixtures.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_tool_loop.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_trace.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_trace_snapshot.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_types.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_migration.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_observability_logging.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_operate_example.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pack_scaffold.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_packs.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_patch.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pattern_matcher.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pattern_parser.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pattern_subscriptions.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_postgres_store.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_quickstart.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_quickstart_snapshot.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_replay.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_replay_trace_snapshot.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_requeue_unfired.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_resume_example.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_runtime.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_runtime_status.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_serde.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_store_conformance.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_store_url.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tool_replay.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tool_trace_snapshot.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tools.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_trace.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tutorial_snippets.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_v1_0_1_patches.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_v1_0_3_behavior_failed_ux.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_v1_0_3_tool_multiturn.py +0 -0
- {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_view.py +0 -0
- {activegraph-1.0.5.post2 → 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.0
|
|
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,8 +36,15 @@ 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"
|
|
45
|
+
Provides-Extra: opentelemetry
|
|
46
|
+
Requires-Dist: opentelemetry-api>=1.25; extra == "opentelemetry"
|
|
47
|
+
Requires-Dist: opentelemetry-sdk>=1.25; extra == "opentelemetry"
|
|
37
48
|
Provides-Extra: all
|
|
38
49
|
Requires-Dist: anthropic>=0.40; extra == "all"
|
|
39
50
|
Requires-Dist: openai>=1.0; extra == "all"
|
|
@@ -41,11 +52,16 @@ Requires-Dist: tiktoken>=0.7; extra == "all"
|
|
|
41
52
|
Requires-Dist: pydantic>=2; extra == "all"
|
|
42
53
|
Requires-Dist: psycopg[binary]<4,>=3.1; extra == "all"
|
|
43
54
|
Requires-Dist: prometheus_client>=0.20; extra == "all"
|
|
55
|
+
Requires-Dist: opentelemetry-api>=1.25; extra == "all"
|
|
56
|
+
Requires-Dist: opentelemetry-sdk>=1.25; extra == "all"
|
|
44
57
|
Provides-Extra: dev
|
|
58
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
45
59
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
46
60
|
Requires-Dist: pydantic>=2; extra == "dev"
|
|
47
61
|
Requires-Dist: prometheus_client>=0.20; extra == "dev"
|
|
48
62
|
Requires-Dist: psycopg[binary]<4,>=3.1; extra == "dev"
|
|
63
|
+
Requires-Dist: opentelemetry-api>=1.25; extra == "dev"
|
|
64
|
+
Requires-Dist: opentelemetry-sdk>=1.25; extra == "dev"
|
|
49
65
|
Provides-Extra: docs
|
|
50
66
|
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
51
67
|
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
@@ -96,14 +112,15 @@ pip install "activegraph[anthropic]" # Anthropic provider only
|
|
|
96
112
|
pip install "activegraph[openai]" # OpenAI provider only (+ tiktoken)
|
|
97
113
|
pip install "activegraph[postgres]" # Postgres-backed event store
|
|
98
114
|
pip install "activegraph[prometheus]" # Prometheus metrics
|
|
115
|
+
pip install "activegraph[opentelemetry]" # OpenTelemetry metrics
|
|
99
116
|
pip install "activegraph[all]" # everything
|
|
100
117
|
```
|
|
101
118
|
|
|
102
119
|
Both LLM providers expose the same `LLMProvider` Protocol surface;
|
|
103
120
|
swap one for the other without touching `@llm_behavior` definitions.
|
|
104
121
|
The [LLM providers reference](https://docs.activegraph.ai/reference/llm-providers/)
|
|
105
|
-
covers the side-by-side surface
|
|
106
|
-
|
|
122
|
+
covers the side-by-side surface, including tool use and provider-specific
|
|
123
|
+
token counting.
|
|
107
124
|
|
|
108
125
|
Python 3.11+. Two hard dependencies (`click` for the CLI, `pydantic`
|
|
109
126
|
for the pack format); persistence backends and provider integrations
|
|
@@ -294,23 +311,34 @@ in [`docs/concepts/relations.md`](https://docs.activegraph.ai/concepts/relations
|
|
|
294
311
|
- Not a rules engine, exactly. Rules engines forward-chain over
|
|
295
312
|
facts. This event-sources over a graph and supports LLM behaviors
|
|
296
313
|
as first-class.
|
|
297
|
-
- Not a production graph database. The
|
|
298
|
-
|
|
299
|
-
|
|
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.
|
|
300
320
|
- Not magic. Bad behaviors produce bad graphs. The runtime makes the
|
|
301
321
|
badness inspectable, not absent.
|
|
302
322
|
|
|
303
323
|
## Status
|
|
304
324
|
|
|
305
|
-
**v1.0 (stable)** (2026-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
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.
|
|
311
330
|
|
|
312
331
|
Major shipped milestones:
|
|
313
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.
|
|
314
342
|
- **v1.0** — error hierarchy rewrite with per-error reference
|
|
315
343
|
pages, doc site at [docs.activegraph.ai](https://docs.activegraph.ai/),
|
|
316
344
|
`activegraph quickstart` command, mypy `--strict` and docstring
|
|
@@ -331,8 +359,9 @@ Major shipped milestones:
|
|
|
331
359
|
patches with optimistic concurrency, views, frames, policies,
|
|
332
360
|
budgets, the trace.
|
|
333
361
|
|
|
334
|
-
Roadmap items
|
|
335
|
-
[
|
|
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).
|
|
336
365
|
|
|
337
366
|
## License
|
|
338
367
|
|
|
@@ -41,14 +41,15 @@ pip install "activegraph[anthropic]" # Anthropic provider only
|
|
|
41
41
|
pip install "activegraph[openai]" # OpenAI provider only (+ tiktoken)
|
|
42
42
|
pip install "activegraph[postgres]" # Postgres-backed event store
|
|
43
43
|
pip install "activegraph[prometheus]" # Prometheus metrics
|
|
44
|
+
pip install "activegraph[opentelemetry]" # OpenTelemetry metrics
|
|
44
45
|
pip install "activegraph[all]" # everything
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
Both LLM providers expose the same `LLMProvider` Protocol surface;
|
|
48
49
|
swap one for the other without touching `@llm_behavior` definitions.
|
|
49
50
|
The [LLM providers reference](https://docs.activegraph.ai/reference/llm-providers/)
|
|
50
|
-
covers the side-by-side surface
|
|
51
|
-
|
|
51
|
+
covers the side-by-side surface, including tool use and provider-specific
|
|
52
|
+
token counting.
|
|
52
53
|
|
|
53
54
|
Python 3.11+. Two hard dependencies (`click` for the CLI, `pydantic`
|
|
54
55
|
for the pack format); persistence backends and provider integrations
|
|
@@ -239,23 +240,34 @@ in [`docs/concepts/relations.md`](https://docs.activegraph.ai/concepts/relations
|
|
|
239
240
|
- Not a rules engine, exactly. Rules engines forward-chain over
|
|
240
241
|
facts. This event-sources over a graph and supports LLM behaviors
|
|
241
242
|
as first-class.
|
|
242
|
-
- Not a production graph database. The
|
|
243
|
-
|
|
244
|
-
|
|
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.
|
|
245
249
|
- Not magic. Bad behaviors produce bad graphs. The runtime makes the
|
|
246
250
|
badness inspectable, not absent.
|
|
247
251
|
|
|
248
252
|
## Status
|
|
249
253
|
|
|
250
|
-
**v1.0 (stable)** (2026-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
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.
|
|
256
259
|
|
|
257
260
|
Major shipped milestones:
|
|
258
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.
|
|
259
271
|
- **v1.0** — error hierarchy rewrite with per-error reference
|
|
260
272
|
pages, doc site at [docs.activegraph.ai](https://docs.activegraph.ai/),
|
|
261
273
|
`activegraph quickstart` command, mypy `--strict` and docstring
|
|
@@ -276,8 +288,9 @@ Major shipped milestones:
|
|
|
276
288
|
patches with optimistic concurrency, views, frames, policies,
|
|
277
289
|
budgets, the trace.
|
|
278
290
|
|
|
279
|
-
Roadmap items
|
|
280
|
-
[
|
|
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).
|
|
281
294
|
|
|
282
295
|
## License
|
|
283
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,
|
|
@@ -87,6 +90,7 @@ from activegraph.observability import (
|
|
|
87
90
|
MigrationReport,
|
|
88
91
|
MigrationRunReport,
|
|
89
92
|
NoOpMetrics,
|
|
93
|
+
OpenTelemetryMetrics,
|
|
90
94
|
PrometheusMetrics,
|
|
91
95
|
RuntimeStatus,
|
|
92
96
|
configure_logging,
|
|
@@ -141,11 +145,14 @@ __all__ = [
|
|
|
141
145
|
"EventNotFoundError",
|
|
142
146
|
"EventStore",
|
|
143
147
|
"ExecutionError",
|
|
148
|
+
"FalkorDBGraphStore",
|
|
144
149
|
"Frame",
|
|
145
150
|
"FrozenClock",
|
|
146
151
|
"Graph",
|
|
152
|
+
"GraphStore",
|
|
147
153
|
"IDGen",
|
|
148
154
|
"InMemoryEventStore",
|
|
155
|
+
"InMemoryGraphStore",
|
|
149
156
|
"IncompatibleRuntimeState",
|
|
150
157
|
"InternalEvaluatorError",
|
|
151
158
|
"InvalidActivateAfter",
|
|
@@ -165,6 +172,7 @@ __all__ = [
|
|
|
165
172
|
"NoOpMetrics",
|
|
166
173
|
"NonSerializableEventError",
|
|
167
174
|
"Object",
|
|
175
|
+
"OpenTelemetryMetrics",
|
|
168
176
|
"ObjectType",
|
|
169
177
|
"Pack",
|
|
170
178
|
"PackConflictError",
|
|
@@ -222,4 +230,4 @@ __all__ = [
|
|
|
222
230
|
"tool",
|
|
223
231
|
]
|
|
224
232
|
|
|
225
|
-
__version__ = "1.0
|
|
233
|
+
__version__ = "1.2.0"
|
|
@@ -234,6 +234,17 @@ def cmd_pack_list() -> None:
|
|
|
234
234
|
"the recorded run was using vs. what's installed today."
|
|
235
235
|
),
|
|
236
236
|
)
|
|
237
|
+
@click.option(
|
|
238
|
+
"--memo",
|
|
239
|
+
is_flag=True,
|
|
240
|
+
help="Render memo objects in the run using the quickstart/operator format.",
|
|
241
|
+
)
|
|
242
|
+
@click.option(
|
|
243
|
+
"--search",
|
|
244
|
+
"search_query",
|
|
245
|
+
default=None,
|
|
246
|
+
help="Search event ids, types, actors, and payload JSON for a substring.",
|
|
247
|
+
)
|
|
237
248
|
def cmd_inspect(
|
|
238
249
|
url: str,
|
|
239
250
|
run_id: Optional[str],
|
|
@@ -242,19 +253,21 @@ def cmd_inspect(
|
|
|
242
253
|
event_id: Optional[str],
|
|
243
254
|
behaviors: bool,
|
|
244
255
|
pack_version: bool,
|
|
256
|
+
memo: bool,
|
|
257
|
+
search_query: Optional[str],
|
|
245
258
|
) -> None:
|
|
246
259
|
"""Print a status snapshot for a run.
|
|
247
260
|
|
|
248
|
-
With
|
|
249
|
-
|
|
261
|
+
With selector flags, prints only that focused section instead of
|
|
262
|
+
the full status. Selectors are
|
|
250
263
|
mutually exclusive — they're selectors, not filters.
|
|
251
264
|
"""
|
|
252
265
|
from activegraph.observability.status import status_to_dict
|
|
253
266
|
from activegraph.runtime.runtime import Runtime
|
|
254
267
|
|
|
255
|
-
if sum([bool(event_id), behaviors, pack_version]) > 1:
|
|
268
|
+
if sum([bool(event_id), behaviors, pack_version, memo, bool(search_query)]) > 1:
|
|
256
269
|
click.echo(
|
|
257
|
-
"--event, --behaviors,
|
|
270
|
+
"--event, --behaviors, --pack-version, --memo, and --search are mutually exclusive.",
|
|
258
271
|
err=True,
|
|
259
272
|
)
|
|
260
273
|
raise SystemExit(EXIT_USAGE_ERROR)
|
|
@@ -278,6 +291,12 @@ def cmd_inspect(
|
|
|
278
291
|
if pack_version:
|
|
279
292
|
_print_pack_versions(rt, as_json)
|
|
280
293
|
return
|
|
294
|
+
if memo:
|
|
295
|
+
_print_memos(rt, as_json)
|
|
296
|
+
return
|
|
297
|
+
if search_query:
|
|
298
|
+
_print_search(rt, search_query, as_json)
|
|
299
|
+
return
|
|
281
300
|
|
|
282
301
|
status = rt.status(recent=tail)
|
|
283
302
|
|
|
@@ -318,15 +337,7 @@ def _print_event(rt, event_id: str, as_json: bool) -> None:
|
|
|
318
337
|
click.echo(f"event {event_id!r} not found in run {rt.run_id}", err=True)
|
|
319
338
|
raise SystemExit(EXIT_NOT_FOUND)
|
|
320
339
|
if as_json:
|
|
321
|
-
click.echo(_json.dumps(
|
|
322
|
-
"id": target.id,
|
|
323
|
-
"type": target.type,
|
|
324
|
-
"actor": target.actor,
|
|
325
|
-
"frame_id": target.frame_id,
|
|
326
|
-
"caused_by": target.caused_by,
|
|
327
|
-
"timestamp": target.timestamp,
|
|
328
|
-
"payload": target.payload,
|
|
329
|
-
}, default=str))
|
|
340
|
+
click.echo(_json.dumps(_event_to_dict(target), default=str))
|
|
330
341
|
return
|
|
331
342
|
click.echo(f"event: {target.id}")
|
|
332
343
|
click.echo(f"type: {target.type}")
|
|
@@ -401,6 +412,95 @@ def _print_pack_versions(rt, as_json: bool) -> None:
|
|
|
401
412
|
click.echo(f" prompt {prompt_name:24s} hash={short}")
|
|
402
413
|
|
|
403
414
|
|
|
415
|
+
def _print_memos(rt, as_json: bool) -> None:
|
|
416
|
+
"""v1.1 CLI follow-on: render memo objects from a run."""
|
|
417
|
+
from activegraph.cli.renderers import company_name_for_memo, print_memo_section
|
|
418
|
+
|
|
419
|
+
memos = [o for o in rt.graph.all_objects() if o.type == "memo"]
|
|
420
|
+
if as_json:
|
|
421
|
+
click.echo(_json.dumps([
|
|
422
|
+
{
|
|
423
|
+
"company": company_name_for_memo(rt, memo),
|
|
424
|
+
"object": memo.to_dict(),
|
|
425
|
+
}
|
|
426
|
+
for memo in memos
|
|
427
|
+
], default=str))
|
|
428
|
+
return
|
|
429
|
+
if not memos:
|
|
430
|
+
click.echo("(no memo objects in this run)")
|
|
431
|
+
return
|
|
432
|
+
write = lambda s: click.echo(s)
|
|
433
|
+
for memo in memos:
|
|
434
|
+
print_memo_section(write, rt, memo)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _print_search(rt, query: str, as_json: bool) -> None:
|
|
438
|
+
"""v1.1 CLI follow-on: substring search across run events."""
|
|
439
|
+
q = query.casefold()
|
|
440
|
+
matches = []
|
|
441
|
+
for e in rt.graph.events:
|
|
442
|
+
fields = {
|
|
443
|
+
"id": e.id,
|
|
444
|
+
"type": e.type,
|
|
445
|
+
"actor": e.actor or "",
|
|
446
|
+
"payload": _json.dumps(e.payload, sort_keys=True, default=str),
|
|
447
|
+
}
|
|
448
|
+
matched_fields = [name for name, value in fields.items() if q in value.casefold()]
|
|
449
|
+
if not matched_fields:
|
|
450
|
+
continue
|
|
451
|
+
payload_text = fields["payload"]
|
|
452
|
+
matches.append(
|
|
453
|
+
{
|
|
454
|
+
"id": e.id,
|
|
455
|
+
"type": e.type,
|
|
456
|
+
"actor": e.actor,
|
|
457
|
+
"timestamp": e.timestamp,
|
|
458
|
+
"matched_fields": matched_fields,
|
|
459
|
+
"snippet": _search_snippet(payload_text, query),
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
if as_json:
|
|
463
|
+
click.echo(_json.dumps(matches, default=str))
|
|
464
|
+
return
|
|
465
|
+
if not matches:
|
|
466
|
+
click.echo(f"(no matches for {query!r})")
|
|
467
|
+
return
|
|
468
|
+
click.echo(f"matches ({len(matches)}):")
|
|
469
|
+
for m in matches:
|
|
470
|
+
actor = m["actor"] or "(none)"
|
|
471
|
+
click.echo(
|
|
472
|
+
f" {m['id']:18s} {m['type']:24s} actor={actor} fields={','.join(m['matched_fields'])}"
|
|
473
|
+
)
|
|
474
|
+
if m["snippet"]:
|
|
475
|
+
click.echo(f" {m['snippet']}")
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _event_to_dict(event) -> dict[str, Any]:
|
|
479
|
+
return {
|
|
480
|
+
"id": event.id,
|
|
481
|
+
"type": event.type,
|
|
482
|
+
"actor": event.actor,
|
|
483
|
+
"frame_id": event.frame_id,
|
|
484
|
+
"caused_by": event.caused_by,
|
|
485
|
+
"timestamp": event.timestamp,
|
|
486
|
+
"payload": event.payload,
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _search_snippet(text: str, query: str, *, width: int = 120) -> str:
|
|
491
|
+
idx = text.casefold().find(query.casefold())
|
|
492
|
+
if idx == -1:
|
|
493
|
+
return text[:width]
|
|
494
|
+
start = max(0, idx - width // 3)
|
|
495
|
+
end = min(len(text), start + width)
|
|
496
|
+
snippet = text[start:end]
|
|
497
|
+
if start > 0:
|
|
498
|
+
snippet = "..." + snippet
|
|
499
|
+
if end < len(text):
|
|
500
|
+
snippet += "..."
|
|
501
|
+
return snippet
|
|
502
|
+
|
|
503
|
+
|
|
404
504
|
# ---- replay -------------------------------------------------------------
|
|
405
505
|
|
|
406
506
|
|
|
@@ -462,6 +562,15 @@ def cmd_replay(url: str, run_id: str, as_json: bool) -> None:
|
|
|
462
562
|
"so the LLM cache and tool cache write-through new entries."
|
|
463
563
|
),
|
|
464
564
|
)
|
|
565
|
+
@click.option(
|
|
566
|
+
"--set",
|
|
567
|
+
"settings_overrides",
|
|
568
|
+
multiple=True,
|
|
569
|
+
help=(
|
|
570
|
+
"Override a pack setting in the fork. Shape: "
|
|
571
|
+
"<pack>.<setting>=<value>. Repeat to compose overrides."
|
|
572
|
+
),
|
|
573
|
+
)
|
|
465
574
|
@click.option("--json", "as_json", is_flag=True, help="Machine-readable output.")
|
|
466
575
|
def cmd_fork(
|
|
467
576
|
url: str,
|
|
@@ -470,6 +579,7 @@ def cmd_fork(
|
|
|
470
579
|
label: Optional[str],
|
|
471
580
|
to_url: Optional[str],
|
|
472
581
|
record: bool,
|
|
582
|
+
settings_overrides: tuple[str, ...],
|
|
473
583
|
as_json: bool,
|
|
474
584
|
) -> None:
|
|
475
585
|
"""Create a new run by copying events up to and including --at-event."""
|
|
@@ -479,6 +589,7 @@ def cmd_fork(
|
|
|
479
589
|
|
|
480
590
|
if to_url is None:
|
|
481
591
|
to_url = url
|
|
592
|
+
parsed_overrides = _parse_fork_settings_overrides(settings_overrides)
|
|
482
593
|
if record:
|
|
483
594
|
# Label suffix is informational; the actual "recording" semantics
|
|
484
595
|
# emerge when the new run is later loaded with replay_strict=False
|
|
@@ -499,6 +610,18 @@ def cmd_fork(
|
|
|
499
610
|
click.echo(str(e), err=True)
|
|
500
611
|
raise SystemExit(EXIT_USAGE_ERROR)
|
|
501
612
|
|
|
613
|
+
if parsed_overrides:
|
|
614
|
+
loaded_packs = _pack_names_loaded_through_event(url, run_id, at_event)
|
|
615
|
+
missing = sorted(set(parsed_overrides) - loaded_packs)
|
|
616
|
+
if missing:
|
|
617
|
+
click.echo(
|
|
618
|
+
"cannot apply --set override: no pack.loaded event for "
|
|
619
|
+
+ ", ".join(repr(p) for p in missing)
|
|
620
|
+
+ f" exists at or before {at_event!r} in run {run_id!r}",
|
|
621
|
+
err=True,
|
|
622
|
+
)
|
|
623
|
+
raise SystemExit(EXIT_USAGE_ERROR)
|
|
624
|
+
|
|
502
625
|
new_run_id = IDGen().run()
|
|
503
626
|
try:
|
|
504
627
|
if parsed.scheme == "sqlite":
|
|
@@ -527,6 +650,15 @@ def cmd_fork(
|
|
|
527
650
|
click.echo(str(e), err=True)
|
|
528
651
|
raise SystemExit(EXIT_NOT_FOUND)
|
|
529
652
|
|
|
653
|
+
override_event_ids: list[str] = []
|
|
654
|
+
if parsed_overrides:
|
|
655
|
+
override_event_ids = _record_fork_settings_overrides(
|
|
656
|
+
to_url,
|
|
657
|
+
run_id=new_run_id,
|
|
658
|
+
caused_by=at_event,
|
|
659
|
+
overrides=parsed_overrides,
|
|
660
|
+
)
|
|
661
|
+
|
|
530
662
|
out = {
|
|
531
663
|
"parent_run_id": run_id,
|
|
532
664
|
"new_run_id": new_run_id,
|
|
@@ -534,12 +666,19 @@ def cmd_fork(
|
|
|
534
666
|
"label": label,
|
|
535
667
|
"events_copied": n,
|
|
536
668
|
}
|
|
669
|
+
if parsed_overrides:
|
|
670
|
+
out["settings_overrides"] = {
|
|
671
|
+
pack: data["overrides"] for pack, data in parsed_overrides.items()
|
|
672
|
+
}
|
|
673
|
+
out["settings_override_events"] = override_event_ids
|
|
537
674
|
if as_json:
|
|
538
675
|
if record:
|
|
539
676
|
out["recording"] = True
|
|
540
677
|
click.echo(_json.dumps(out))
|
|
541
678
|
return
|
|
542
679
|
click.echo(f"forked {run_id} at {at_event} -> {new_run_id} ({n} events)")
|
|
680
|
+
for event_id in override_event_ids:
|
|
681
|
+
click.echo(f" settings override recorded: {event_id}")
|
|
543
682
|
if record:
|
|
544
683
|
click.echo(
|
|
545
684
|
" recording fork: load this run without replay_strict=True to "
|
|
@@ -547,6 +686,135 @@ def cmd_fork(
|
|
|
547
686
|
)
|
|
548
687
|
|
|
549
688
|
|
|
689
|
+
def _parse_fork_settings_overrides(
|
|
690
|
+
raw_values: tuple[str, ...],
|
|
691
|
+
) -> dict[str, dict[str, Any]]:
|
|
692
|
+
"""Parse repeated --set flags into pack-grouped override payloads."""
|
|
693
|
+
parsed: dict[str, dict[str, Any]] = {}
|
|
694
|
+
for raw in raw_values:
|
|
695
|
+
if "=" not in raw:
|
|
696
|
+
click.echo(
|
|
697
|
+
f"--set value {raw!r} must have shape <pack>.<setting>=<value>",
|
|
698
|
+
err=True,
|
|
699
|
+
)
|
|
700
|
+
raise SystemExit(EXIT_USAGE_ERROR)
|
|
701
|
+
key, raw_value = raw.split("=", 1)
|
|
702
|
+
parts = key.split(".")
|
|
703
|
+
if len(parts) < 2 or any(not p for p in parts):
|
|
704
|
+
click.echo(
|
|
705
|
+
f"--set key {key!r} must have shape <pack>.<setting>",
|
|
706
|
+
err=True,
|
|
707
|
+
)
|
|
708
|
+
raise SystemExit(EXIT_USAGE_ERROR)
|
|
709
|
+
pack = parts[0]
|
|
710
|
+
value = _coerce_cli_value(raw_value)
|
|
711
|
+
entry = parsed.setdefault(pack, {"overrides": {}, "assignments": []})
|
|
712
|
+
_set_nested_override(entry["overrides"], parts[1:], value, raw)
|
|
713
|
+
entry["assignments"].append(raw)
|
|
714
|
+
return parsed
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def _coerce_cli_value(raw: str) -> Any:
|
|
718
|
+
"""Best-effort string-to-JSON coercion for --set values.
|
|
719
|
+
|
|
720
|
+
Pydantic performs final validation when the pack is loaded. This
|
|
721
|
+
parser only turns obvious JSON scalars/lists/maps into Python values.
|
|
722
|
+
"""
|
|
723
|
+
lowered = raw.strip().lower()
|
|
724
|
+
if lowered == "none":
|
|
725
|
+
return None
|
|
726
|
+
try:
|
|
727
|
+
return _json.loads(raw)
|
|
728
|
+
except ValueError:
|
|
729
|
+
return raw
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def _set_nested_override(
|
|
733
|
+
root: dict[str, Any],
|
|
734
|
+
path: list[str],
|
|
735
|
+
value: Any,
|
|
736
|
+
raw: str,
|
|
737
|
+
) -> None:
|
|
738
|
+
cursor = root
|
|
739
|
+
for segment in path[:-1]:
|
|
740
|
+
existing = cursor.get(segment)
|
|
741
|
+
if existing is None:
|
|
742
|
+
nested: dict[str, Any] = {}
|
|
743
|
+
cursor[segment] = nested
|
|
744
|
+
cursor = nested
|
|
745
|
+
continue
|
|
746
|
+
if not isinstance(existing, dict):
|
|
747
|
+
click.echo(
|
|
748
|
+
f"--set value {raw!r} conflicts with an earlier override for "
|
|
749
|
+
f"{segment!r}",
|
|
750
|
+
err=True,
|
|
751
|
+
)
|
|
752
|
+
raise SystemExit(EXIT_USAGE_ERROR)
|
|
753
|
+
cursor = existing
|
|
754
|
+
leaf = path[-1]
|
|
755
|
+
if isinstance(cursor.get(leaf), dict):
|
|
756
|
+
click.echo(
|
|
757
|
+
f"--set value {raw!r} conflicts with an earlier nested override",
|
|
758
|
+
err=True,
|
|
759
|
+
)
|
|
760
|
+
raise SystemExit(EXIT_USAGE_ERROR)
|
|
761
|
+
cursor[leaf] = value
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def _pack_names_loaded_through_event(url: str, run_id: str, at_event: str) -> set[str]:
|
|
765
|
+
store = _open_store_or_die(url, run_id)
|
|
766
|
+
loaded: set[str] = set()
|
|
767
|
+
seen_cutoff = False
|
|
768
|
+
try:
|
|
769
|
+
for event in store.iter_events():
|
|
770
|
+
if event.type == "pack.loaded":
|
|
771
|
+
name = (event.payload or {}).get("name")
|
|
772
|
+
if isinstance(name, str) and name:
|
|
773
|
+
loaded.add(name)
|
|
774
|
+
if event.id == at_event:
|
|
775
|
+
seen_cutoff = True
|
|
776
|
+
break
|
|
777
|
+
finally:
|
|
778
|
+
close = getattr(store, "close", None)
|
|
779
|
+
if close is not None:
|
|
780
|
+
close()
|
|
781
|
+
if not seen_cutoff:
|
|
782
|
+
click.echo(f"event {at_event!r} not found in run {run_id!r}", err=True)
|
|
783
|
+
raise SystemExit(EXIT_NOT_FOUND)
|
|
784
|
+
return loaded
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def _record_fork_settings_overrides(
|
|
788
|
+
url: str,
|
|
789
|
+
*,
|
|
790
|
+
run_id: str,
|
|
791
|
+
caused_by: str,
|
|
792
|
+
overrides: dict[str, dict[str, Any]],
|
|
793
|
+
) -> list[str]:
|
|
794
|
+
from activegraph.core.event import Event
|
|
795
|
+
from activegraph.runtime.runtime import Runtime
|
|
796
|
+
|
|
797
|
+
rt = Runtime.load(url, run_id=run_id)
|
|
798
|
+
event_ids: list[str] = []
|
|
799
|
+
for pack, data in overrides.items():
|
|
800
|
+
event = Event(
|
|
801
|
+
id=rt.graph.ids.event(),
|
|
802
|
+
type="pack.settings_overridden",
|
|
803
|
+
payload={
|
|
804
|
+
"pack": pack,
|
|
805
|
+
"overrides": data["overrides"],
|
|
806
|
+
"assignments": list(data["assignments"]),
|
|
807
|
+
},
|
|
808
|
+
actor="runtime",
|
|
809
|
+
frame_id=rt.frame.id if rt.frame else None,
|
|
810
|
+
caused_by=caused_by,
|
|
811
|
+
timestamp=rt.graph.clock.now(),
|
|
812
|
+
)
|
|
813
|
+
rt.graph.emit(event)
|
|
814
|
+
event_ids.append(event.id)
|
|
815
|
+
return event_ids
|
|
816
|
+
|
|
817
|
+
|
|
550
818
|
# ---- diff ---------------------------------------------------------------
|
|
551
819
|
|
|
552
820
|
|