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.
Files changed (174) hide show
  1. {activegraph-1.0.5.post2/activegraph.egg-info → activegraph-1.2.0}/PKG-INFO +43 -14
  2. {activegraph-1.0.5.post2 → activegraph-1.2.0}/README.md +26 -13
  3. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/__init__.py +9 -1
  4. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/cli/main.py +281 -13
  5. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/cli/quickstart.py +4 -87
  6. activegraph-1.2.0/activegraph/cli/renderers.py +97 -0
  7. activegraph-1.2.0/activegraph/core/__init__.py +8 -0
  8. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/graph.py +108 -96
  9. activegraph-1.2.0/activegraph/core/graph_store.py +356 -0
  10. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/view.py +11 -0
  11. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/cache.py +9 -5
  12. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/errors.py +14 -14
  13. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/openai.py +92 -27
  14. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/__init__.py +5 -2
  15. activegraph-1.2.0/activegraph/observability/otel.py +105 -0
  16. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/loader.py +84 -0
  17. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/budget.py +13 -0
  18. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/patterns.py +39 -67
  19. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/registry.py +3 -1
  20. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/runtime.py +342 -64
  21. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/view_builder.py +9 -4
  22. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/__init__.py +5 -0
  23. activegraph-1.2.0/activegraph/store/falkordb.py +647 -0
  24. activegraph-1.2.0/activegraph/store/graph_conformance.py +491 -0
  25. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/memory.py +10 -0
  26. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/decorators.py +16 -0
  27. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/trace/printer.py +16 -0
  28. {activegraph-1.0.5.post2 → activegraph-1.2.0/activegraph.egg-info}/PKG-INFO +43 -14
  29. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/SOURCES.txt +10 -0
  30. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/requires.txt +15 -0
  31. {activegraph-1.0.5.post2 → activegraph-1.2.0}/pyproject.toml +23 -1
  32. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_cli.py +174 -2
  33. activegraph-1.2.0/tests/test_cli_docs_flags.py +78 -0
  34. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_diff.py +15 -2
  35. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_doc_links.py +6 -4
  36. activegraph-1.2.0/tests/test_doc_python_snippets.py +85 -0
  37. activegraph-1.2.0/tests/test_falkordb_store.py +446 -0
  38. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_fork.py +20 -0
  39. activegraph-1.2.0/tests/test_graph_store.py +123 -0
  40. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_anthropic.py +6 -0
  41. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_failure.py +159 -0
  42. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_openai.py +235 -26
  43. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_replay.py +81 -1
  44. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llms_txt.py +49 -1
  45. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_observability_metrics.py +63 -0
  46. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_persistence.py +23 -0
  47. activegraph-1.2.0/tests/test_reason_codes_docs.py +32 -0
  48. activegraph-1.2.0/tests/test_version_sync.py +84 -0
  49. activegraph-1.0.5.post2/activegraph/core/__init__.py +0 -1
  50. activegraph-1.0.5.post2/tests/test_version_sync.py +0 -30
  51. {activegraph-1.0.5.post2 → activegraph-1.2.0}/LICENSE +0 -0
  52. {activegraph-1.0.5.post2 → activegraph-1.2.0}/NOTICE +0 -0
  53. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/__main__.py +0 -0
  54. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/behaviors/__init__.py +0 -0
  55. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/behaviors/base.py +0 -0
  56. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/behaviors/decorators.py +0 -0
  57. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/cli/__init__.py +0 -0
  58. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/clock.py +0 -0
  59. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/event.py +0 -0
  60. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/ids.py +0 -0
  61. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/core/patch.py +0 -0
  62. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/errors.py +0 -0
  63. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/frame.py +0 -0
  64. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/__init__.py +0 -0
  65. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/anthropic.py +0 -0
  66. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/parsing.py +0 -0
  67. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/prompt.py +0 -0
  68. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/provider.py +0 -0
  69. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/recorded.py +0 -0
  70. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/llm/types.py +0 -0
  71. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/logging.py +0 -0
  72. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/metrics.py +0 -0
  73. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/migration.py +0 -0
  74. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/prometheus.py +0 -0
  75. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/observability/status.py +0 -0
  76. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/__init__.py +0 -0
  77. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/__init__.py +0 -0
  78. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/behaviors.py +0 -0
  79. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
  80. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/fixtures/companies.py +0 -0
  81. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/object_types.py +0 -0
  82. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
  83. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
  84. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
  85. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
  86. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/settings.py +0 -0
  87. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/diligence/tools.py +0 -0
  88. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/packs/scaffold.py +0 -0
  89. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/policy.py +0 -0
  90. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/__init__.py +0 -0
  91. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/_live.py +0 -0
  92. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/behavior_graph.py +0 -0
  93. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/config_errors.py +0 -0
  94. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/diff.py +0 -0
  95. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/errors.py +0 -0
  96. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/exec_errors.py +0 -0
  97. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/queue.py +0 -0
  98. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/registration_errors.py +0 -0
  99. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/runtime/scheduler.py +0 -0
  100. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/base.py +0 -0
  101. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/conformance.py +0 -0
  102. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/errors.py +0 -0
  103. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/postgres.py +0 -0
  104. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/serde.py +0 -0
  105. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/sqlite.py +0 -0
  106. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/store/url.py +0 -0
  107. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/__init__.py +0 -0
  108. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/base.py +0 -0
  109. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/cache.py +0 -0
  110. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/context.py +0 -0
  111. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/errors.py +0 -0
  112. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/graph_query.py +0 -0
  113. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/recorded.py +0 -0
  114. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/tools/web_fetch.py +0 -0
  115. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/trace/__init__.py +0 -0
  116. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph/trace/causal.py +0 -0
  117. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/dependency_links.txt +0 -0
  118. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/entry_points.txt +0 -0
  119. {activegraph-1.0.5.post2 → activegraph-1.2.0}/activegraph.egg-info/top_level.txt +0 -0
  120. {activegraph-1.0.5.post2 → activegraph-1.2.0}/setup.cfg +0 -0
  121. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_activate_after.py +0 -0
  122. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_causal_cross_tool.py +0 -0
  123. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_clock.py +0 -0
  124. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_diligence_pack.py +0 -0
  125. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_diligence_with_tools.py +0 -0
  126. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_doc_site_reachable.py +0 -0
  127. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_errors_format.py +0 -0
  128. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_event.py +0 -0
  129. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_graph.py +0 -0
  130. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_ids.py +0 -0
  131. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_license.py +0 -0
  132. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_behavior.py +0 -0
  133. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_budget.py +0 -0
  134. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_causal.py +0 -0
  135. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_claim_extraction.py +0 -0
  136. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_default_model.py +0 -0
  137. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_determinism.py +0 -0
  138. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_prompt.py +0 -0
  139. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_provider_fixtures.py +0 -0
  140. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_tool_loop.py +0 -0
  141. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_trace.py +0 -0
  142. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_trace_snapshot.py +0 -0
  143. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_llm_types.py +0 -0
  144. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_migration.py +0 -0
  145. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_observability_logging.py +0 -0
  146. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_operate_example.py +0 -0
  147. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pack_scaffold.py +0 -0
  148. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_packs.py +0 -0
  149. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_patch.py +0 -0
  150. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pattern_matcher.py +0 -0
  151. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pattern_parser.py +0 -0
  152. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_pattern_subscriptions.py +0 -0
  153. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_postgres_store.py +0 -0
  154. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_quickstart.py +0 -0
  155. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_quickstart_snapshot.py +0 -0
  156. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_replay.py +0 -0
  157. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_replay_trace_snapshot.py +0 -0
  158. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_requeue_unfired.py +0 -0
  159. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_resume_example.py +0 -0
  160. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_runtime.py +0 -0
  161. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_runtime_status.py +0 -0
  162. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_serde.py +0 -0
  163. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_store_conformance.py +0 -0
  164. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_store_url.py +0 -0
  165. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tool_replay.py +0 -0
  166. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tool_trace_snapshot.py +0 -0
  167. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tools.py +0 -0
  168. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_trace.py +0 -0
  169. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_tutorial_snippets.py +0 -0
  170. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_v1_0_1_patches.py +0 -0
  171. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_v1_0_3_behavior_failed_ux.py +0 -0
  172. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_v1_0_3_tool_multiturn.py +0 -0
  173. {activegraph-1.0.5.post2 → activegraph-1.2.0}/tests/test_view.py +0 -0
  174. {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.5.post2
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 and the v1.0.1 limitations
106
- (OpenAI tool use is a v1.1 candidate).
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 default store is SQLite,
298
- optionally Postgres. For a high-throughput graph backend, plug one
299
- in behind the `EventStore` protocol.
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-05). The first-time-user gate per
306
- [CONTRACT v1.0 #C4](CONTRACT.md#v10-c4-v10-ships-as-v10-rc1-first-time-user-gate-is-owned-externally)
307
- ran through three rcs; v1.0 final ships rc3 plus a tutorial-step-7
308
- output fix and a README "Concepts at a glance" index. See
309
- [CHANGELOG.md](CHANGELOG.md) for the full v0 → v1.0 history and
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 planned for v1.1 are tracked in
335
- [CONTRACT.md § v1.1](CONTRACT.md).
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 and the v1.0.1 limitations
51
- (OpenAI tool use is a v1.1 candidate).
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 default store is SQLite,
243
- optionally Postgres. For a high-throughput graph backend, plug one
244
- in behind the `EventStore` protocol.
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-05). The first-time-user gate per
251
- [CONTRACT v1.0 #C4](CONTRACT.md#v10-c4-v10-ships-as-v10-rc1-first-time-user-gate-is-owned-externally)
252
- ran through three rcs; v1.0 final ships rc3 plus a tutorial-step-7
253
- output fix and a README "Concepts at a glance" index. See
254
- [CHANGELOG.md](CHANGELOG.md) for the full v0 → v1.0 history and
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 planned for v1.1 are tracked in
280
- [CONTRACT.md § v1.1](CONTRACT.md).
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.5.post2"
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 ``--event``, ``--behaviors``, or ``--pack-version``, prints only
249
- that focused section instead of the full status. The three are
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, and --pack-version are mutually exclusive.",
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