activegraph 1.1.0__tar.gz → 1.3.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.3.0}/PKG-INFO +32 -12
- {activegraph-1.1.0 → activegraph-1.3.0}/README.md +23 -11
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/__init__.py +19 -1
- activegraph-1.3.0/activegraph/_signature.py +222 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/behaviors/base.py +35 -6
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/behaviors/decorators.py +43 -8
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/cli/main.py +121 -2
- activegraph-1.3.0/activegraph/core/__init__.py +8 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/core/clock.py +21 -4
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/core/event.py +10 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/core/graph.py +153 -102
- activegraph-1.3.0/activegraph/core/graph_store.py +356 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/core/ids.py +9 -1
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/core/patch.py +11 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/core/view.py +11 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/errors.py +22 -3
- activegraph-1.3.0/activegraph/frame.py +24 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/__init__.py +8 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/anthropic.py +86 -13
- activegraph-1.3.0/activegraph/llm/embedding.py +94 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/errors.py +38 -0
- activegraph-1.3.0/activegraph/llm/native.py +173 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/openai.py +114 -20
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/prompt.py +24 -2
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/provider.py +16 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/recorded.py +46 -2
- activegraph-1.3.0/activegraph/llm/wire.py +140 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/observability/logging.py +4 -4
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/observability/migration.py +26 -7
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/observability/otel.py +2 -2
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/observability/prometheus.py +4 -4
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/observability/status.py +11 -1
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/__init__.py +111 -25
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/settings.py +1 -1
- activegraph-1.3.0/activegraph/packs/manifest.py +503 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/policy.py +11 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/budget.py +13 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/diff.py +32 -4
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/exec_errors.py +117 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/patterns.py +58 -76
- activegraph-1.3.0/activegraph/runtime/promote.py +411 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/registration_errors.py +4 -1
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/registry.py +3 -1
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/runtime.py +749 -62
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/view_builder.py +9 -4
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/__init__.py +5 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/base.py +23 -3
- activegraph-1.3.0/activegraph/store/falkordb.py +647 -0
- activegraph-1.3.0/activegraph/store/graph_conformance.py +491 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/memory.py +10 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/postgres.py +3 -3
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/serde.py +2 -1
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/sqlite.py +10 -3
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/url.py +8 -1
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/base.py +14 -2
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/context.py +11 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/decorators.py +40 -3
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/trace/printer.py +85 -0
- {activegraph-1.1.0 → activegraph-1.3.0/activegraph.egg-info}/PKG-INFO +32 -12
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph.egg-info/SOURCES.txt +18 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph.egg-info/requires.txt +6 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/pyproject.toml +61 -6
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_cli.py +118 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_doc_links.py +6 -4
- activegraph-1.3.0/tests/test_embedding_provider.py +97 -0
- activegraph-1.3.0/tests/test_falkordb_store.py +446 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_fork.py +20 -0
- activegraph-1.3.0/tests/test_graph_store.py +123 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_anthropic.py +100 -0
- activegraph-1.3.0/tests/test_llm_native_structured_output.py +466 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_openai.py +183 -4
- activegraph-1.3.0/tests/test_llm_wire.py +186 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llms_txt.py +49 -1
- activegraph-1.3.0/tests/test_pack_manifest.py +247 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_persistence.py +98 -0
- activegraph-1.3.0/tests/test_promote.py +821 -0
- activegraph-1.3.0/tests/test_registration_validation.py +301 -0
- activegraph-1.3.0/tests/test_trace_accessors.py +75 -0
- activegraph-1.1.0/activegraph/core/__init__.py +0 -1
- activegraph-1.1.0/activegraph/frame.py +0 -15
- {activegraph-1.1.0 → activegraph-1.3.0}/LICENSE +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/NOTICE +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/__main__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/behaviors/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/cli/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/cli/quickstart.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/cli/renderers.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/cache.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/parsing.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/llm/types.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/observability/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/observability/metrics.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/behaviors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/fixtures/companies.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/object_types.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/diligence/tools.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/loader.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/packs/scaffold.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/_live.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/behavior_graph.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/config_errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/queue.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/runtime/scheduler.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/conformance.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/store/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/cache.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/errors.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/graph_query.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/recorded.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/tools/web_fetch.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/trace/__init__.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph/trace/causal.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph.egg-info/dependency_links.txt +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph.egg-info/entry_points.txt +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/activegraph.egg-info/top_level.txt +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/setup.cfg +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_activate_after.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_causal_cross_tool.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_cli_docs_flags.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_clock.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_diff.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_diligence_pack.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_diligence_with_tools.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_doc_python_snippets.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_doc_site_reachable.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_errors_format.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_event.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_graph.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_ids.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_license.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_behavior.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_budget.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_causal.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_claim_extraction.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_default_model.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_determinism.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_failure.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_prompt.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_provider_fixtures.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_replay.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_tool_loop.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_trace.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_trace_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_llm_types.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_migration.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_observability_logging.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_observability_metrics.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_operate_example.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_pack_scaffold.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_packs.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_patch.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_pattern_matcher.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_pattern_parser.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_pattern_subscriptions.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_postgres_store.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_quickstart.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_quickstart_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_reason_codes_docs.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_replay.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_replay_trace_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_requeue_unfired.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_resume_example.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_runtime.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_runtime_status.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_serde.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_store_conformance.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_store_url.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_tool_replay.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_tool_trace_snapshot.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_tools.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_trace.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_tutorial_snippets.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_v1_0_1_patches.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_v1_0_3_behavior_failed_ux.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_v1_0_3_tool_multiturn.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_version_sync.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.0}/tests/test_view.py +0 -0
- {activegraph-1.1.0 → activegraph-1.3.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.3.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
|
|
|
@@ -42,6 +42,11 @@ from activegraph.llm.errors import LLMBehaviorError, MissingProviderError
|
|
|
42
42
|
from activegraph.policy import Policy
|
|
43
43
|
from activegraph.runtime.budget import Budget
|
|
44
44
|
from activegraph.runtime.diff import Diff, DivergentObject, DivergentRelation
|
|
45
|
+
from activegraph.runtime.promote import (
|
|
46
|
+
PromoteConflict,
|
|
47
|
+
PromotePlan,
|
|
48
|
+
PromoteResult,
|
|
49
|
+
)
|
|
45
50
|
from activegraph.runtime.config_errors import (
|
|
46
51
|
IncompatibleRuntimeState,
|
|
47
52
|
InvalidArgumentType,
|
|
@@ -52,6 +57,8 @@ from activegraph.runtime.exec_errors import (
|
|
|
52
57
|
ApprovalNotFoundError,
|
|
53
58
|
InternalEvaluatorError,
|
|
54
59
|
InvalidPatchLifecycleState,
|
|
60
|
+
PromoteConflictError,
|
|
61
|
+
PromoteLineageError,
|
|
55
62
|
RuntimeContextRequiredError,
|
|
56
63
|
)
|
|
57
64
|
from activegraph.runtime.patterns import UnsupportedPatternError
|
|
@@ -61,7 +68,10 @@ from activegraph.store import (
|
|
|
61
68
|
DuplicateEventError,
|
|
62
69
|
EventNotFoundError,
|
|
63
70
|
EventStore,
|
|
71
|
+
FalkorDBGraphStore,
|
|
72
|
+
GraphStore,
|
|
64
73
|
InMemoryEventStore,
|
|
74
|
+
InMemoryGraphStore,
|
|
65
75
|
InvalidStoreURL,
|
|
66
76
|
NonSerializableEventError,
|
|
67
77
|
RunRecord,
|
|
@@ -142,11 +152,14 @@ __all__ = [
|
|
|
142
152
|
"EventNotFoundError",
|
|
143
153
|
"EventStore",
|
|
144
154
|
"ExecutionError",
|
|
155
|
+
"FalkorDBGraphStore",
|
|
145
156
|
"Frame",
|
|
146
157
|
"FrozenClock",
|
|
147
158
|
"Graph",
|
|
159
|
+
"GraphStore",
|
|
148
160
|
"IDGen",
|
|
149
161
|
"InMemoryEventStore",
|
|
162
|
+
"InMemoryGraphStore",
|
|
150
163
|
"IncompatibleRuntimeState",
|
|
151
164
|
"InternalEvaluatorError",
|
|
152
165
|
"InvalidActivateAfter",
|
|
@@ -184,6 +197,11 @@ __all__ = [
|
|
|
184
197
|
"PendingApproval",
|
|
185
198
|
"Policy",
|
|
186
199
|
"PrometheusMetrics",
|
|
200
|
+
"PromoteConflict",
|
|
201
|
+
"PromoteConflictError",
|
|
202
|
+
"PromoteLineageError",
|
|
203
|
+
"PromotePlan",
|
|
204
|
+
"PromoteResult",
|
|
187
205
|
"Relation",
|
|
188
206
|
"RelationBehavior",
|
|
189
207
|
"RegistrationError",
|
|
@@ -224,4 +242,4 @@ __all__ = [
|
|
|
224
242
|
"tool",
|
|
225
243
|
]
|
|
226
244
|
|
|
227
|
-
__version__ = "1.
|
|
245
|
+
__version__ = "1.3.0"
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Registration-time handler-signature validation.
|
|
2
|
+
|
|
3
|
+
Shared by the ``@behavior`` / ``@relation_behavior`` / ``@llm_behavior``
|
|
4
|
+
decorators (both the global registry in
|
|
5
|
+
:mod:`activegraph.behaviors.decorators` and the pack-scoped variants in
|
|
6
|
+
:mod:`activegraph.packs`) and the ``@tool`` decorators.
|
|
7
|
+
|
|
8
|
+
Rationale (July 2026 agent-readiness review): a handler with the wrong
|
|
9
|
+
arity — ``def h(ctx)``, ``def h(event, ctx)`` — previously registered
|
|
10
|
+
fine and failed at first invocation with a ``TypeError`` swallowed into
|
|
11
|
+
a ``behavior.failed`` event, painful to notice during pack development.
|
|
12
|
+
Validating at decoration time names the mistake at the line that made
|
|
13
|
+
it, matching the precedent set by ``@llm_behavior``'s
|
|
14
|
+
``output_schema=`` strict validation (CONTRACT v1.0.3 #2).
|
|
15
|
+
|
|
16
|
+
The check is deliberately permissive:
|
|
17
|
+
|
|
18
|
+
* Objects without an inspectable signature (builtins, some callables,
|
|
19
|
+
mocks with ``(*args, **kwargs)``) pass through unvalidated — the
|
|
20
|
+
goal is catching the common authoring mistake, not gatekeeping
|
|
21
|
+
every callable Python can construct.
|
|
22
|
+
* ``*args`` satisfies any positional arity.
|
|
23
|
+
* Extra parameters beyond the contract are allowed when the call can
|
|
24
|
+
still succeed: they need a default, or — for behaviors, where the
|
|
25
|
+
pack loader injects typed settings by keyword
|
|
26
|
+
(``packs/loader.py:_wrap_with_injection``) — a type annotation
|
|
27
|
+
that marks them as injection candidates.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import inspect
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_handler_signature(
|
|
37
|
+
fn: Any,
|
|
38
|
+
*,
|
|
39
|
+
expected_params: tuple[str, ...],
|
|
40
|
+
decorator: str,
|
|
41
|
+
allow_annotated_extras: bool,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Raise ``TypeError`` when ``fn`` cannot be invoked with the
|
|
44
|
+
decorator's positional calling convention.
|
|
45
|
+
|
|
46
|
+
``expected_params`` names the contract's positional parameters
|
|
47
|
+
(documentation for the error message; only arity is checked, not
|
|
48
|
+
names). ``decorator`` is the user-facing decorator name for the
|
|
49
|
+
message (e.g. ``"@tool"``). ``allow_annotated_extras=True`` treats
|
|
50
|
+
a type annotation on an extra parameter as evidence it will be
|
|
51
|
+
keyword-injected at call time (the pack settings pattern
|
|
52
|
+
``*, settings: MyPackSettings``); ``False`` (tools) requires extras
|
|
53
|
+
to carry defaults, because tools are always invoked as exactly
|
|
54
|
+
``fn(args, ctx)``.
|
|
55
|
+
|
|
56
|
+
Callables whose signature cannot be inspected are skipped, and
|
|
57
|
+
``*args`` / ``**kwargs`` satisfy the positional / extra checks
|
|
58
|
+
respectively — the validation is a loud guard for the common
|
|
59
|
+
mistake, not an exhaustive gate.
|
|
60
|
+
"""
|
|
61
|
+
if not callable(fn):
|
|
62
|
+
raise TypeError(
|
|
63
|
+
f"{decorator} must decorate a callable, got "
|
|
64
|
+
f"{type(fn).__name__}."
|
|
65
|
+
)
|
|
66
|
+
try:
|
|
67
|
+
sig = inspect.signature(fn)
|
|
68
|
+
except (TypeError, ValueError):
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
params = list(sig.parameters.values())
|
|
72
|
+
has_var_positional = any(
|
|
73
|
+
p.kind is inspect.Parameter.VAR_POSITIONAL for p in params
|
|
74
|
+
)
|
|
75
|
+
positional = [
|
|
76
|
+
p
|
|
77
|
+
for p in params
|
|
78
|
+
if p.kind
|
|
79
|
+
in (
|
|
80
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
81
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
82
|
+
)
|
|
83
|
+
]
|
|
84
|
+
n_expected = len(expected_params)
|
|
85
|
+
expected_sig = f"({', '.join(expected_params)})"
|
|
86
|
+
|
|
87
|
+
if len(positional) < n_expected and not has_var_positional:
|
|
88
|
+
found = (
|
|
89
|
+
f"({', '.join(p.name for p in positional)})"
|
|
90
|
+
if positional
|
|
91
|
+
else "()"
|
|
92
|
+
)
|
|
93
|
+
raise TypeError(
|
|
94
|
+
f"{decorator} function {_name_of(fn)!r} must accept "
|
|
95
|
+
f"{n_expected} positional parameters {expected_sig}, but its "
|
|
96
|
+
f"signature is {found}.\n"
|
|
97
|
+
f"\n"
|
|
98
|
+
f"The runtime invokes it as fn{expected_sig}. "
|
|
99
|
+
f"{_extra_hint(decorator)}\n"
|
|
100
|
+
f"\n"
|
|
101
|
+
f"Example:\n"
|
|
102
|
+
f" {decorator}(...)\n"
|
|
103
|
+
f" def {_example_name(fn)}{expected_sig}: ..."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
problems: list[str] = []
|
|
107
|
+
for p in positional[n_expected:]:
|
|
108
|
+
if _extra_is_satisfiable(p, allow_annotated_extras):
|
|
109
|
+
continue
|
|
110
|
+
problems.append(p.name)
|
|
111
|
+
for p in params:
|
|
112
|
+
if p.kind is not inspect.Parameter.KEYWORD_ONLY:
|
|
113
|
+
continue
|
|
114
|
+
if _extra_is_satisfiable(p, allow_annotated_extras):
|
|
115
|
+
continue
|
|
116
|
+
problems.append(p.name)
|
|
117
|
+
|
|
118
|
+
if problems:
|
|
119
|
+
if allow_annotated_extras:
|
|
120
|
+
fix = (
|
|
121
|
+
"give it a default value, or annotate it with your "
|
|
122
|
+
"pack's settings class so the pack loader injects it "
|
|
123
|
+
"(e.g. `*, settings: MyPackSettings`)"
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
fix = "give it a default value or remove it"
|
|
127
|
+
raise TypeError(
|
|
128
|
+
f"{decorator} function {_name_of(fn)!r} has required "
|
|
129
|
+
f"parameter(s) beyond the {expected_sig} contract that the "
|
|
130
|
+
f"runtime will never pass: {', '.join(problems)}.\n"
|
|
131
|
+
f"\n"
|
|
132
|
+
f"The runtime invokes it as fn{expected_sig}, so every "
|
|
133
|
+
f"extra parameter must be optional at call time — {fix}."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def infer_tool_input_schema(fn: Any) -> Any:
|
|
138
|
+
"""Infer a tool's ``input_schema`` from its first parameter's
|
|
139
|
+
type annotation, or return ``None``.
|
|
140
|
+
|
|
141
|
+
v1.3: with ``input_schema=None``, ``@tool`` previously sent the
|
|
142
|
+
model an empty parameters schema (``{"type": "object",
|
|
143
|
+
"properties": {}}``) even when the function body plainly declared
|
|
144
|
+
its shape via ``def fetch(args: FetchArgs, ctx)``. Inference
|
|
145
|
+
closes that gap: when the first positional parameter is annotated
|
|
146
|
+
with a Pydantic ``BaseModel`` subclass, that class becomes the
|
|
147
|
+
tool's ``input_schema`` — the model sees real parameters and the
|
|
148
|
+
runtime validates arguments before invocation, exactly as if
|
|
149
|
+
``input_schema=FetchArgs`` had been passed explicitly.
|
|
150
|
+
|
|
151
|
+
Anything else — no annotation, a non-model annotation
|
|
152
|
+
(``dict``, ``Any``), an uninspectable callable — returns ``None``
|
|
153
|
+
and the tool behaves exactly as before. Explicit ``input_schema=``
|
|
154
|
+
always wins; the decorators only call this when it was omitted.
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
sig = inspect.signature(fn)
|
|
158
|
+
except (TypeError, ValueError):
|
|
159
|
+
return None
|
|
160
|
+
positional = [
|
|
161
|
+
p
|
|
162
|
+
for p in sig.parameters.values()
|
|
163
|
+
if p.kind
|
|
164
|
+
in (
|
|
165
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
166
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
167
|
+
)
|
|
168
|
+
]
|
|
169
|
+
if not positional:
|
|
170
|
+
return None
|
|
171
|
+
annotation = positional[0].annotation
|
|
172
|
+
if annotation is inspect.Parameter.empty:
|
|
173
|
+
return None
|
|
174
|
+
# PEP 563 / `from __future__ import annotations` leaves string
|
|
175
|
+
# annotations; resolve through get_type_hints (module-global
|
|
176
|
+
# visibility), the same tolerance the pack loader's settings
|
|
177
|
+
# injection applies.
|
|
178
|
+
if isinstance(annotation, str):
|
|
179
|
+
import typing
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
hints = typing.get_type_hints(fn)
|
|
183
|
+
except Exception:
|
|
184
|
+
return None
|
|
185
|
+
annotation = hints.get(positional[0].name)
|
|
186
|
+
if annotation is None:
|
|
187
|
+
return None
|
|
188
|
+
try:
|
|
189
|
+
from pydantic import BaseModel
|
|
190
|
+
except ImportError: # pragma: no cover — Pydantic is a hard dep
|
|
191
|
+
return None
|
|
192
|
+
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
193
|
+
return annotation
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _extra_is_satisfiable(
|
|
198
|
+
p: inspect.Parameter, allow_annotated_extras: bool
|
|
199
|
+
) -> bool:
|
|
200
|
+
if p.default is not inspect.Parameter.empty:
|
|
201
|
+
return True
|
|
202
|
+
if allow_annotated_extras and p.annotation is not inspect.Parameter.empty:
|
|
203
|
+
return True
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _name_of(fn: Any) -> str:
|
|
208
|
+
return getattr(fn, "__name__", repr(fn))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _example_name(fn: Any) -> str:
|
|
212
|
+
name = getattr(fn, "__name__", "my_handler")
|
|
213
|
+
return name if name.isidentifier() else "my_handler"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _extra_hint(decorator: str) -> str:
|
|
217
|
+
if decorator == "@tool":
|
|
218
|
+
return (
|
|
219
|
+
"`args` is the validated input model (or a plain dict when "
|
|
220
|
+
"no input_schema is set) and `ctx` is the ToolContext."
|
|
221
|
+
)
|
|
222
|
+
return "See the behavior-handler contract in docs/concepts/behaviors."
|
|
@@ -20,12 +20,15 @@ from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from activegraph.core.event import Event
|
|
23
|
-
from activegraph.core.graph import Graph
|
|
23
|
+
from activegraph.core.graph import Graph, Relation
|
|
24
24
|
from activegraph.frame import Frame
|
|
25
25
|
from activegraph.llm.prompt import AssembledPrompt
|
|
26
|
+
from activegraph.runtime.runtime import Context
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
def _llm_behavior_fn_placeholder(
|
|
29
|
+
def _llm_behavior_fn_placeholder(
|
|
30
|
+
event: "Event", graph: "Graph", ctx: "Context"
|
|
31
|
+
) -> None: # pragma: no cover
|
|
29
32
|
raise RuntimeError(
|
|
30
33
|
"LLMBehavior.fn invoked directly. The runtime owns LLM behavior "
|
|
31
34
|
"invocation via _invoke_llm; calling .run() bypasses prompt "
|
|
@@ -35,6 +38,18 @@ def _llm_behavior_fn_placeholder(event, graph, ctx) -> None: # pragma: no cover
|
|
|
35
38
|
|
|
36
39
|
@dataclass
|
|
37
40
|
class Behavior:
|
|
41
|
+
"""Metadata plus the callable for an event-driven behavior.
|
|
42
|
+
|
|
43
|
+
A Behavior is data, not magic: ``on`` (event types), ``where``
|
|
44
|
+
(payload filter), ``pattern`` (Cypher-subset subscription),
|
|
45
|
+
``activate_after`` (event-count delay), and ``view_spec`` (what
|
|
46
|
+
the runtime builds into ``ctx.view``) describe *when* it fires;
|
|
47
|
+
the 3-arg ``fn`` — invoked as ``fn(event, graph, ctx)`` — is
|
|
48
|
+
*what* runs. Instances come from ``@behavior`` or a pack; the
|
|
49
|
+
runtime introspects the metadata to match events, and nothing
|
|
50
|
+
here executes on its own.
|
|
51
|
+
"""
|
|
52
|
+
|
|
38
53
|
name: str
|
|
39
54
|
fn: Callable[..., None]
|
|
40
55
|
on: list[str] = field(default_factory=list)
|
|
@@ -51,12 +66,22 @@ class Behavior:
|
|
|
51
66
|
# CONTRACT v0.7 #13. Event-count delay. None = fire immediately.
|
|
52
67
|
activate_after: Optional[int] = None
|
|
53
68
|
|
|
54
|
-
def run(self, event, graph, ctx) -> None:
|
|
69
|
+
def run(self, event: "Event", graph: "Graph", ctx: "Context") -> None:
|
|
55
70
|
self.fn(event, graph, ctx)
|
|
56
71
|
|
|
57
72
|
|
|
58
73
|
@dataclass
|
|
59
74
|
class RelationBehavior:
|
|
75
|
+
"""A behavior that fires once per matching relation edge.
|
|
76
|
+
|
|
77
|
+
Same metadata surface as :class:`Behavior` plus
|
|
78
|
+
``relation_type``: when a triggering event touches a relation of
|
|
79
|
+
that type, the 4-arg ``fn`` runs as
|
|
80
|
+
``fn(relation, event, graph, ctx)`` — once per (event, relation)
|
|
81
|
+
pair. Deliberately not a subclass of ``Behavior``; the runtime
|
|
82
|
+
dispatches the two kinds separately.
|
|
83
|
+
"""
|
|
84
|
+
|
|
60
85
|
name: str
|
|
61
86
|
fn: Callable[..., None]
|
|
62
87
|
relation_type: str
|
|
@@ -72,7 +97,9 @@ class RelationBehavior:
|
|
|
72
97
|
pattern_matcher: Any = None
|
|
73
98
|
activate_after: Optional[int] = None
|
|
74
99
|
|
|
75
|
-
def run(
|
|
100
|
+
def run(
|
|
101
|
+
self, relation: "Relation", event: "Event", graph: "Graph", ctx: "Context"
|
|
102
|
+
) -> None:
|
|
76
103
|
self.fn(relation, event, graph, ctx)
|
|
77
104
|
|
|
78
105
|
|
|
@@ -98,7 +125,7 @@ class LLMBehavior(Behavior):
|
|
|
98
125
|
# at registration time. Existing call sites that pass an explicit
|
|
99
126
|
# string keep working unchanged.
|
|
100
127
|
model: Optional[str] = None
|
|
101
|
-
output_schema: Optional[type] = None
|
|
128
|
+
output_schema: Optional[type[Any]] = None
|
|
102
129
|
deterministic: bool = False
|
|
103
130
|
max_tokens: int = 4096
|
|
104
131
|
temperature: float = 0.7
|
|
@@ -106,7 +133,7 @@ class LLMBehavior(Behavior):
|
|
|
106
133
|
timeout_seconds: float = 60.0
|
|
107
134
|
prompt_template: Optional[str] = None
|
|
108
135
|
# v0.7
|
|
109
|
-
tools: list = field(default_factory=list)
|
|
136
|
+
tools: list[Any] = field(default_factory=list) # Tool | str names
|
|
110
137
|
max_tool_turns: int = 6
|
|
111
138
|
|
|
112
139
|
def build_prompt(
|
|
@@ -115,6 +142,7 @@ class LLMBehavior(Behavior):
|
|
|
115
142
|
graph: "Graph",
|
|
116
143
|
*,
|
|
117
144
|
frame: Optional["Frame"] = None,
|
|
145
|
+
structured_output_mode: str = "prompt",
|
|
118
146
|
) -> "AssembledPrompt":
|
|
119
147
|
"""Assemble the prompt that would be sent for this event.
|
|
120
148
|
|
|
@@ -161,4 +189,5 @@ class LLMBehavior(Behavior):
|
|
|
161
189
|
top_p=self.top_p,
|
|
162
190
|
deterministic=self.deterministic,
|
|
163
191
|
prompt_template=self.prompt_template,
|
|
192
|
+
structured_output_mode=structured_output_mode,
|
|
164
193
|
)
|
|
@@ -94,7 +94,15 @@ def clear_registry() -> list[Union[Behavior, RelationBehavior]]:
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def get_registry() -> list[Union[Behavior, RelationBehavior]]:
|
|
97
|
-
"""Snapshot of the global behavior registry (a shallow copy).
|
|
97
|
+
"""Snapshot of the global behavior registry (a shallow copy).
|
|
98
|
+
|
|
99
|
+
Decoration with ``@behavior`` / ``@relation_behavior`` /
|
|
100
|
+
``@llm_behavior`` appends to a module-level list; this returns a
|
|
101
|
+
copy, so callers can filter or iterate without mutating
|
|
102
|
+
registration state. ``Runtime`` snapshots it at construction when
|
|
103
|
+
``behaviors=`` is omitted — later registrations don't leak into
|
|
104
|
+
already-built runtimes.
|
|
105
|
+
"""
|
|
98
106
|
return list(_REGISTRY)
|
|
99
107
|
|
|
100
108
|
|
|
@@ -152,7 +160,7 @@ def behavior(
|
|
|
152
160
|
*,
|
|
153
161
|
pattern: Optional[str] = None,
|
|
154
162
|
activate_after: Any = None,
|
|
155
|
-
) -> Callable[[Callable], Behavior]:
|
|
163
|
+
) -> Callable[[Callable[..., None]], Behavior]:
|
|
156
164
|
"""Decorate a function as an event-driven behavior.
|
|
157
165
|
|
|
158
166
|
v0.7 additions (both keyword-only):
|
|
@@ -175,7 +183,16 @@ def behavior(
|
|
|
175
183
|
if activate_after is not None:
|
|
176
184
|
delay_n = _parse_aa(activate_after)
|
|
177
185
|
|
|
178
|
-
def wrap(fn: Callable) -> Behavior:
|
|
186
|
+
def wrap(fn: Callable[..., None]) -> Behavior:
|
|
187
|
+
# v1.3: arity check at decoration time (see activegraph/_signature.py).
|
|
188
|
+
from activegraph._signature import validate_handler_signature
|
|
189
|
+
|
|
190
|
+
validate_handler_signature(
|
|
191
|
+
fn,
|
|
192
|
+
expected_params=("event", "graph", "ctx"),
|
|
193
|
+
decorator="@behavior",
|
|
194
|
+
allow_annotated_extras=True,
|
|
195
|
+
)
|
|
179
196
|
b = Behavior(
|
|
180
197
|
name=name or fn.__name__,
|
|
181
198
|
fn=fn,
|
|
@@ -215,9 +232,9 @@ def llm_behavior(
|
|
|
215
232
|
priority: int = 0,
|
|
216
233
|
pattern: Optional[str] = None,
|
|
217
234
|
activate_after: Any = None,
|
|
218
|
-
tools: Optional[list] = None,
|
|
235
|
+
tools: Optional[list[Any]] = None,
|
|
219
236
|
max_tool_turns: int = 6,
|
|
220
|
-
) -> Callable[[Callable], LLMBehavior]:
|
|
237
|
+
) -> Callable[[Callable[..., None]], LLMBehavior]:
|
|
221
238
|
"""Decorate a function as an LLM-driven behavior.
|
|
222
239
|
|
|
223
240
|
The decorated function's signature is
|
|
@@ -281,7 +298,16 @@ def llm_behavior(
|
|
|
281
298
|
# call; failing here names the cause at the @llm_behavior line.
|
|
282
299
|
_validate_output_schema(output_schema)
|
|
283
300
|
|
|
284
|
-
def wrap(fn: Callable) -> LLMBehavior:
|
|
301
|
+
def wrap(fn: Callable[..., None]) -> LLMBehavior:
|
|
302
|
+
# v1.3: arity check at decoration time (see activegraph/_signature.py).
|
|
303
|
+
from activegraph._signature import validate_handler_signature
|
|
304
|
+
|
|
305
|
+
validate_handler_signature(
|
|
306
|
+
fn,
|
|
307
|
+
expected_params=("event", "graph", "ctx", "llm_output"),
|
|
308
|
+
decorator="@llm_behavior",
|
|
309
|
+
allow_annotated_extras=True,
|
|
310
|
+
)
|
|
285
311
|
b = LLMBehavior(
|
|
286
312
|
name=name or fn.__name__,
|
|
287
313
|
fn=_llm_behavior_fn_placeholder,
|
|
@@ -331,7 +357,7 @@ def relation_behavior(
|
|
|
331
357
|
*,
|
|
332
358
|
pattern: Optional[str] = None,
|
|
333
359
|
activate_after: Any = None,
|
|
334
|
-
) -> Callable[[Callable], RelationBehavior]:
|
|
360
|
+
) -> Callable[[Callable[..., None]], RelationBehavior]:
|
|
335
361
|
"""Decorate a function as a relation behavior — fires once per matching edge.
|
|
336
362
|
|
|
337
363
|
v0.7: also accepts `pattern=` and `activate_after=` per CONTRACT
|
|
@@ -348,7 +374,16 @@ def relation_behavior(
|
|
|
348
374
|
if activate_after is not None:
|
|
349
375
|
delay_n = _parse_aa(activate_after)
|
|
350
376
|
|
|
351
|
-
def wrap(fn: Callable) -> RelationBehavior:
|
|
377
|
+
def wrap(fn: Callable[..., None]) -> RelationBehavior:
|
|
378
|
+
# v1.3: arity check at decoration time (see activegraph/_signature.py).
|
|
379
|
+
from activegraph._signature import validate_handler_signature
|
|
380
|
+
|
|
381
|
+
validate_handler_signature(
|
|
382
|
+
fn,
|
|
383
|
+
expected_params=("relation", "event", "graph", "ctx"),
|
|
384
|
+
decorator="@relation_behavior",
|
|
385
|
+
allow_annotated_extras=True,
|
|
386
|
+
)
|
|
352
387
|
rb = RelationBehavior(
|
|
353
388
|
name=name or fn.__name__,
|
|
354
389
|
fn=fn,
|