activegraph 1.0.2__tar.gz → 1.0.2.post1__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 (156) hide show
  1. {activegraph-1.0.2 → activegraph-1.0.2.post1}/PKG-INFO +1 -1
  2. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/__init__.py +1 -1
  3. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/behaviors/decorators.py +12 -0
  4. activegraph-1.0.2.post1/activegraph/runtime/_live.py +173 -0
  5. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/runtime.py +24 -90
  6. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/PKG-INFO +1 -1
  7. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/SOURCES.txt +1 -0
  8. {activegraph-1.0.2 → activegraph-1.0.2.post1}/pyproject.toml +1 -1
  9. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_default_model.py +243 -17
  10. {activegraph-1.0.2 → activegraph-1.0.2.post1}/README.md +0 -0
  11. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/__main__.py +0 -0
  12. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/behaviors/__init__.py +0 -0
  13. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/behaviors/base.py +0 -0
  14. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/cli/__init__.py +0 -0
  15. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/cli/main.py +0 -0
  16. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/cli/quickstart.py +0 -0
  17. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/__init__.py +0 -0
  18. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/clock.py +0 -0
  19. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/event.py +0 -0
  20. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/graph.py +0 -0
  21. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/ids.py +0 -0
  22. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/patch.py +0 -0
  23. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/view.py +0 -0
  24. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/errors.py +0 -0
  25. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/frame.py +0 -0
  26. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/__init__.py +0 -0
  27. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/anthropic.py +0 -0
  28. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/cache.py +0 -0
  29. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/errors.py +0 -0
  30. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/openai.py +0 -0
  31. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/parsing.py +0 -0
  32. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/prompt.py +0 -0
  33. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/provider.py +0 -0
  34. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/recorded.py +0 -0
  35. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/types.py +0 -0
  36. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/__init__.py +0 -0
  37. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/logging.py +0 -0
  38. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/metrics.py +0 -0
  39. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/migration.py +0 -0
  40. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/prometheus.py +0 -0
  41. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/status.py +0 -0
  42. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/__init__.py +0 -0
  43. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/__init__.py +0 -0
  44. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/behaviors.py +0 -0
  45. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
  46. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/fixtures/companies.py +0 -0
  47. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/object_types.py +0 -0
  48. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
  49. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
  50. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
  51. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
  52. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/settings.py +0 -0
  53. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/tools.py +0 -0
  54. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/loader.py +0 -0
  55. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/scaffold.py +0 -0
  56. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/policy.py +0 -0
  57. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/__init__.py +0 -0
  58. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/behavior_graph.py +0 -0
  59. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/budget.py +0 -0
  60. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/config_errors.py +0 -0
  61. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/diff.py +0 -0
  62. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/errors.py +0 -0
  63. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/exec_errors.py +0 -0
  64. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/patterns.py +0 -0
  65. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/queue.py +0 -0
  66. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/registration_errors.py +0 -0
  67. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/registry.py +0 -0
  68. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/scheduler.py +0 -0
  69. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/view_builder.py +0 -0
  70. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/__init__.py +0 -0
  71. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/base.py +0 -0
  72. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/conformance.py +0 -0
  73. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/errors.py +0 -0
  74. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/memory.py +0 -0
  75. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/postgres.py +0 -0
  76. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/serde.py +0 -0
  77. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/sqlite.py +0 -0
  78. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/url.py +0 -0
  79. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/__init__.py +0 -0
  80. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/base.py +0 -0
  81. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/cache.py +0 -0
  82. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/context.py +0 -0
  83. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/decorators.py +0 -0
  84. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/errors.py +0 -0
  85. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/graph_query.py +0 -0
  86. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/recorded.py +0 -0
  87. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/web_fetch.py +0 -0
  88. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/trace/__init__.py +0 -0
  89. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/trace/causal.py +0 -0
  90. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/trace/printer.py +0 -0
  91. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/dependency_links.txt +0 -0
  92. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/entry_points.txt +0 -0
  93. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/requires.txt +0 -0
  94. {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/top_level.txt +0 -0
  95. {activegraph-1.0.2 → activegraph-1.0.2.post1}/setup.cfg +0 -0
  96. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_activate_after.py +0 -0
  97. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_causal_cross_tool.py +0 -0
  98. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_cli.py +0 -0
  99. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_clock.py +0 -0
  100. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_diff.py +0 -0
  101. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_diligence_pack.py +0 -0
  102. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_diligence_with_tools.py +0 -0
  103. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_doc_links.py +0 -0
  104. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_doc_site_reachable.py +0 -0
  105. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_errors_format.py +0 -0
  106. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_event.py +0 -0
  107. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_fork.py +0 -0
  108. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_graph.py +0 -0
  109. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_ids.py +0 -0
  110. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_anthropic.py +0 -0
  111. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_behavior.py +0 -0
  112. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_budget.py +0 -0
  113. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_causal.py +0 -0
  114. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_claim_extraction.py +0 -0
  115. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_determinism.py +0 -0
  116. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_failure.py +0 -0
  117. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_openai.py +0 -0
  118. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_prompt.py +0 -0
  119. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_provider_fixtures.py +0 -0
  120. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_replay.py +0 -0
  121. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_tool_loop.py +0 -0
  122. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_trace.py +0 -0
  123. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_trace_snapshot.py +0 -0
  124. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_types.py +0 -0
  125. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_migration.py +0 -0
  126. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_observability_logging.py +0 -0
  127. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_observability_metrics.py +0 -0
  128. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_operate_example.py +0 -0
  129. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pack_scaffold.py +0 -0
  130. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_packs.py +0 -0
  131. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_patch.py +0 -0
  132. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pattern_matcher.py +0 -0
  133. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pattern_parser.py +0 -0
  134. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pattern_subscriptions.py +0 -0
  135. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_persistence.py +0 -0
  136. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_postgres_store.py +0 -0
  137. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_quickstart.py +0 -0
  138. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_quickstart_snapshot.py +0 -0
  139. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_replay.py +0 -0
  140. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_replay_trace_snapshot.py +0 -0
  141. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_requeue_unfired.py +0 -0
  142. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_resume_example.py +0 -0
  143. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_runtime.py +0 -0
  144. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_runtime_status.py +0 -0
  145. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_serde.py +0 -0
  146. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_store_conformance.py +0 -0
  147. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_store_url.py +0 -0
  148. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tool_replay.py +0 -0
  149. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tool_trace_snapshot.py +0 -0
  150. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tools.py +0 -0
  151. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_trace.py +0 -0
  152. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tutorial_snippets.py +0 -0
  153. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_v1_0_1_patches.py +0 -0
  154. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_version_sync.py +0 -0
  155. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_view.py +0 -0
  156. {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_wheel_completeness.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: activegraph
3
- Version: 1.0.2
3
+ Version: 1.0.2.post1
4
4
  Summary: An event-sourced reactive graph runtime for long-running, auditable, agentic systems.
5
5
  Author: Active Graph contributors
6
6
  License: MIT
@@ -221,4 +221,4 @@ __all__ = [
221
221
  "tool",
222
222
  ]
223
223
 
224
- __version__ = "1.0.2"
224
+ __version__ = "1.0.2.post1"
@@ -85,6 +85,12 @@ def register(behavior_obj: Union[Behavior, RelationBehavior]) -> None:
85
85
  f"decorators to construct one."
86
86
  )
87
87
  _REGISTRY.append(behavior_obj)
88
+ # v1.0.2.post1 #1 (b): if any Runtime is already alive, validate the
89
+ # new behavior against each one's configured provider so cross-
90
+ # provider model mismatches fire at the @llm_behavior / register()
91
+ # line rather than at first run_goal.
92
+ from activegraph.runtime._live import validate_behavior_against_live_runtimes
93
+ validate_behavior_against_live_runtimes(behavior_obj)
88
94
 
89
95
 
90
96
  def behavior(
@@ -248,6 +254,12 @@ def llm_behavior(
248
254
  max_tool_turns=max_tool_turns,
249
255
  )
250
256
  _REGISTRY.append(b)
257
+ # v1.0.2.post1 #1 (b): validate against any live Runtime's
258
+ # provider. Mirrors the eager check in register() so decorated
259
+ # behaviors with cross-provider model names fire at the
260
+ # @llm_behavior line instead of at first run_goal.
261
+ from activegraph.runtime._live import validate_behavior_against_live_runtimes
262
+ validate_behavior_against_live_runtimes(b)
251
263
  return b
252
264
 
253
265
  return wrap
@@ -0,0 +1,173 @@
1
+ """Live-Runtime tracking + single-behavior cross-provider validation.
2
+
3
+ CONTRACT v1.0.2 #1 (b). The validation fires at *both* binding
4
+ moments: ``Runtime(graph, llm_provider=...)`` construction (against
5
+ the existing registry) and ``register()`` / ``@llm_behavior``
6
+ decoration (against any live Runtime via the WeakSet below).
7
+
8
+ The WeakSet is module-level so the registration decorators can
9
+ look it up without importing ``Runtime`` at module load. Runtimes
10
+ self-register inside ``__init__`` after wiring; the WeakSet
11
+ auto-cleans GC'd Runtimes — no explicit ``Runtime.close()`` is
12
+ required.
13
+
14
+ The single-behavior validator here is intentionally narrow: it
15
+ only does the cross-provider mismatch check (recognized name
16
+ belonging to a different shipped provider). It does *not* stamp
17
+ provider defaults onto ``model=None`` behaviors — that side
18
+ effect lives in ``_resolve_and_validate_llm_models`` in
19
+ ``runtime.py``, which still runs at first ``_ensure_registry``.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import weakref
25
+ from typing import TYPE_CHECKING, Any
26
+
27
+ from activegraph.behaviors.base import LLMBehavior
28
+
29
+ if TYPE_CHECKING:
30
+ from activegraph.runtime.runtime import Runtime
31
+
32
+
33
+ # WeakSet so abandoned Runtimes don't leak validation calls. The
34
+ # fork/replay paths construct fresh Runtimes that self-register; the
35
+ # parent stays in the set until GC'd.
36
+ _LIVE_RUNTIMES: "weakref.WeakSet[Runtime]" = weakref.WeakSet()
37
+
38
+
39
+ def track_runtime(rt: "Runtime") -> None:
40
+ """Register a live Runtime for cross-provider validation. Called
41
+ by ``Runtime.__init__`` after the provider and graph are wired."""
42
+ _LIVE_RUNTIMES.add(rt)
43
+
44
+
45
+ def live_runtimes() -> list["Runtime"]:
46
+ """Snapshot of currently-alive Runtimes (a list of strong refs that
47
+ the caller releases at end-of-call). Used by ``register()`` and the
48
+ decorators to validate a new behavior against each live provider."""
49
+ return list(_LIVE_RUNTIMES)
50
+
51
+
52
+ def _clear_for_test() -> None:
53
+ """Empty the live-Runtime set. Test-only — used by the conftest
54
+ fixture that isolates the global registry between tests. Production
55
+ code shouldn't call this: the WeakSet cleans itself on GC."""
56
+ _LIVE_RUNTIMES.clear()
57
+
58
+
59
+ def validate_behavior_against_live_runtimes(behavior: Any) -> None:
60
+ """Validate a freshly-registered behavior against every live
61
+ Runtime's provider. Raises :class:`InvalidRuntimeConfiguration` on
62
+ the first cross-provider mismatch.
63
+
64
+ No-op when ``behavior`` isn't an :class:`LLMBehavior`, when no
65
+ Runtime is live, or when the behavior has no explicit model (model
66
+ resolution against the provider's default happens at Runtime
67
+ construction or first-run, not here).
68
+ """
69
+ if not isinstance(behavior, LLMBehavior):
70
+ return
71
+ if behavior.model is None:
72
+ return
73
+ for rt in live_runtimes():
74
+ provider = getattr(rt, "llm_provider", None)
75
+ if provider is None:
76
+ continue
77
+ _validate_one(behavior, provider)
78
+
79
+
80
+ def _validate_one(behavior: LLMBehavior, provider: Any) -> None:
81
+ """Cross-provider mismatch check for a single behavior against a
82
+ single provider. Pure: no side effects, no model-default stamping.
83
+
84
+ Permissive by default per v1.0.2 #1 (b): names no shipped provider
85
+ recognizes pass silently. Only recognized cross-provider mismatches
86
+ raise. The runtime-side ``_resolve_and_validate_llm_models``
87
+ delegates to this for its per-behavior pass so the check lives in
88
+ one place.
89
+ """
90
+ from activegraph.runtime.config_errors import InvalidRuntimeConfiguration
91
+
92
+ model = behavior.model
93
+ if model is None:
94
+ return
95
+ recognizes = getattr(provider, "recognizes_model", None)
96
+ if recognizes is None or recognizes(model):
97
+ return
98
+ claimed_by = _which_shipped_provider_claims(model, exclude=type(provider))
99
+ if claimed_by is None:
100
+ return
101
+
102
+ provider_class = type(provider).__name__
103
+ provider_default = getattr(provider, "default_model", None) or "claude-sonnet-4-5"
104
+ claimed_by_default = getattr(claimed_by, "default_model", "")
105
+ provider_default_hint = (
106
+ f"or remove the model= argument to use {provider_class}'s "
107
+ f"default ({provider_default!r})"
108
+ if provider_default
109
+ else f"or set a {provider_class}-compatible model name"
110
+ )
111
+ raise InvalidRuntimeConfiguration(
112
+ (
113
+ f"@llm_behavior(name={behavior.name!r}, model={model!r}) "
114
+ f"names a {claimed_by.__name__}-family model, but the "
115
+ f"runtime is configured with {provider_class}"
116
+ ),
117
+ what_failed=(
118
+ f"The behavior {behavior.name!r} pinned model={model!r}. "
119
+ f"That name belongs to {claimed_by.__name__}'s model "
120
+ f"family, but this Runtime was constructed with a "
121
+ f"{provider_class} instance. Sending the name to the "
122
+ f"wrong provider produces an HTTP 404 (or equivalent "
123
+ f"'unknown model' response) at first LLM call, with no "
124
+ f"hint that the mismatch is the cause."
125
+ ),
126
+ why=(
127
+ "v1.0.2 #1 validates explicit model names at both binding "
128
+ "moments (Runtime construction and register()/decoration) "
129
+ "against each shipped provider's recognizes_model() "
130
+ "method. The configured provider doesn't claim this name, "
131
+ "but another shipped provider does — that's a "
132
+ "configuration mismatch worth surfacing before the first "
133
+ "network call rather than after."
134
+ ),
135
+ how_to_fix=(
136
+ f"Either swap the provider — Runtime(graph, "
137
+ f"llm_provider={claimed_by.__name__}()) — "
138
+ f"{provider_default_hint}, or pass an explicit name "
139
+ f"from {provider_class}'s model families."
140
+ ),
141
+ context={
142
+ "behavior": behavior.name,
143
+ "model": model,
144
+ "configured_provider": provider_class,
145
+ "claimed_by_provider": claimed_by.__name__,
146
+ "claimed_by_default_model": claimed_by_default,
147
+ },
148
+ )
149
+
150
+
151
+ def _which_shipped_provider_claims(name: str, *, exclude: type) -> Any:
152
+ """Return the first shipped provider class that recognizes `name`,
153
+ excluding `exclude`. Returns None when no other shipped provider
154
+ claims the name (permissive default per v1.0.2 #1 (b))."""
155
+ from activegraph.llm.anthropic import AnthropicProvider
156
+ from activegraph.llm.openai import OpenAIProvider
157
+
158
+ candidates = [AnthropicProvider, OpenAIProvider]
159
+ for cls in candidates:
160
+ if cls is exclude:
161
+ continue
162
+ recognizes = getattr(cls, "recognizes_model", None)
163
+ if recognizes is None:
164
+ continue
165
+ try:
166
+ inst = cls()
167
+ except Exception:
168
+ # Defensive: if a provider's no-arg constructor ever requires
169
+ # real credentials, skip it for the validation lookup.
170
+ continue
171
+ if inst.recognizes_model(name):
172
+ return cls
173
+ return None
@@ -303,6 +303,23 @@ class Runtime:
303
303
  self._pack_behaviors: list = []
304
304
  self._pack_tools: list = []
305
305
 
306
+ # ---- v1.0.2.post1 #1 (b): eager cross-provider validation ----
307
+ # First run the bulk validation pass against whatever's already
308
+ # in the registry; only if it passes do we add this Runtime to
309
+ # the live-set. Failed constructions stay out of the WeakSet so
310
+ # they can't influence subsequent register() / @llm_behavior
311
+ # validation calls. The lazy path inside _ensure_registry()
312
+ # stays as a defensive double-check.
313
+ if self.llm_provider is not None:
314
+ source = (
315
+ self._explicit_behaviors
316
+ if self._explicit_behaviors is not None
317
+ else get_registry()
318
+ )
319
+ _resolve_and_validate_llm_models(source, self.llm_provider)
320
+ from activegraph.runtime._live import track_runtime
321
+ track_runtime(self)
322
+
306
323
  # ---------- public surface ----------
307
324
 
308
325
  @property
@@ -2190,11 +2207,12 @@ def _resolve_and_validate_llm_models(source, provider) -> None:
2190
2207
  raise InvalidRuntimeConfiguration for explicit names that belong to
2191
2208
  a different shipped provider. CONTRACT v1.0.2 #1.
2192
2209
 
2193
- The validation is permissive: a name no shipped provider recognizes
2194
- (custom or fine-tuned model) passes silently — only *recognized*
2195
- cross-provider mismatches fire the diagnostic.
2210
+ The cross-provider mismatch check delegates to
2211
+ :func:`activegraph.runtime._live._validate_one` so the same check
2212
+ fires from both binding moments (Runtime construction here,
2213
+ register()/decoration in ``_live.validate_behavior_against_live_runtimes``).
2196
2214
  """
2197
- from activegraph.runtime.config_errors import InvalidRuntimeConfiguration
2215
+ from activegraph.runtime._live import _validate_one
2198
2216
 
2199
2217
  # Custom providers that pre-date v1.0.2 don't declare `default_model`.
2200
2218
  # Fall back to the v1.0.1 hardcoded default — keeps every pre-v1.0.2
@@ -2202,8 +2220,6 @@ def _resolve_and_validate_llm_models(source, provider) -> None:
2202
2220
  # byte-identically without code changes. The runtime only swaps in a
2203
2221
  # different default when the provider opts in by declaring one.
2204
2222
  provider_default = getattr(provider, "default_model", None) or "claude-sonnet-4-5"
2205
- provider_recognizes = getattr(provider, "recognizes_model", None)
2206
- provider_class = type(provider).__name__
2207
2223
 
2208
2224
  for b in source:
2209
2225
  if not isinstance(b, LLMBehavior):
@@ -2214,90 +2230,8 @@ def _resolve_and_validate_llm_models(source, provider) -> None:
2214
2230
  # declare one).
2215
2231
  b.model = provider_default
2216
2232
  continue
2217
-
2218
- # b.model is an explicit string. Cross-provider validation per
2219
- # v1.0.2 #1 (b): if the configured provider doesn't recognize the
2220
- # name, ask each *other* shipped provider class whether it does.
2221
- # Exactly one other-provider claim ⇒ raise; otherwise pass through.
2222
- if provider_recognizes is None or provider_recognizes(b.model):
2223
- continue
2224
- claimed_by = _which_shipped_provider_claims(b.model, exclude=type(provider))
2225
- if claimed_by is None:
2226
- continue
2227
- # The model name belongs to a different shipped provider's family.
2228
- # This is the v1.0.1-user-test finding the v1.0.2 patch addresses:
2229
- # silently sending an Anthropic-family name to OpenAI 404s with no
2230
- # helpful diagnostic. Fire at registration time, not call time.
2231
- claimed_by_default = getattr(claimed_by, "default_model", "")
2232
- provider_default_hint = (
2233
- f"or remove the model= argument to use {provider_class}'s "
2234
- f"default ({provider_default!r})"
2235
- if provider_default
2236
- else f"or set a {provider_class}-compatible model name"
2237
- )
2238
- raise InvalidRuntimeConfiguration(
2239
- (
2240
- f"@llm_behavior(name={b.name!r}, model={b.model!r}) "
2241
- f"names a {claimed_by.__name__}-family model, but the "
2242
- f"runtime is configured with {provider_class}"
2243
- ),
2244
- what_failed=(
2245
- f"The behavior {b.name!r} pinned model={b.model!r}. That "
2246
- f"name belongs to {claimed_by.__name__}'s model family, "
2247
- f"but this Runtime was constructed with a "
2248
- f"{provider_class} instance. Sending the name to the "
2249
- f"wrong provider produces an HTTP 404 (or equivalent "
2250
- f"'unknown model' response) at first LLM call, with no "
2251
- f"hint that the mismatch is the cause."
2252
- ),
2253
- why=(
2254
- "v1.0.2 #1 validates explicit model names at registration "
2255
- "time against each shipped provider's recognizes_model() "
2256
- "method. The configured provider doesn't claim this "
2257
- "name, but another shipped provider does — that's a "
2258
- "configuration mismatch worth surfacing before the "
2259
- "first network call rather than after."
2260
- ),
2261
- how_to_fix=(
2262
- f"Either swap the provider — Runtime(graph, "
2263
- f"llm_provider={claimed_by.__name__}()) — "
2264
- f"{provider_default_hint}, or pass an explicit name "
2265
- f"from {provider_class}'s model families."
2266
- ),
2267
- context={
2268
- "behavior": b.name,
2269
- "model": b.model,
2270
- "configured_provider": provider_class,
2271
- "claimed_by_provider": claimed_by.__name__,
2272
- "claimed_by_default_model": claimed_by_default,
2273
- },
2274
- )
2275
-
2276
-
2277
- def _which_shipped_provider_claims(name: str, *, exclude: type):
2278
- """Return the first shipped provider class that recognizes `name`,
2279
- excluding `exclude`. Returns None when no other shipped provider
2280
- claims the name (permissive default per v1.0.2 #1 (b))."""
2281
- from activegraph.llm.anthropic import AnthropicProvider
2282
- from activegraph.llm.openai import OpenAIProvider
2283
-
2284
- candidates = [AnthropicProvider, OpenAIProvider]
2285
- for cls in candidates:
2286
- if cls is exclude:
2287
- continue
2288
- recognizes = getattr(cls, "recognizes_model", None)
2289
- if recognizes is None:
2290
- continue
2291
- # Instantiate cheaply: the providers' __init__ doesn't open the SDK.
2292
- try:
2293
- inst = cls()
2294
- except Exception:
2295
- # Defensive: if a provider's no-arg constructor ever requires
2296
- # real credentials, skip it for the validation lookup.
2297
- continue
2298
- if inst.recognizes_model(name):
2299
- return cls
2300
- return None
2233
+ # v1.0.2 #1 (b): cross-provider mismatch check.
2234
+ _validate_one(b, provider)
2301
2235
 
2302
2236
 
2303
2237
  def _budget_reason(name: Optional[str]) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: activegraph
3
- Version: 1.0.2
3
+ Version: 1.0.2.post1
4
4
  Summary: An event-sourced reactive graph runtime for long-running, auditable, agentic systems.
5
5
  Author: Active Graph contributors
6
6
  License: MIT
@@ -55,6 +55,7 @@ activegraph/packs/diligence/prompts/memo_synthesizer.md
55
55
  activegraph/packs/diligence/prompts/question_generator.md
56
56
  activegraph/packs/diligence/prompts/risk_identifier.md
57
57
  activegraph/runtime/__init__.py
58
+ activegraph/runtime/_live.py
58
59
  activegraph/runtime/behavior_graph.py
59
60
  activegraph/runtime/budget.py
60
61
  activegraph/runtime/config_errors.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "activegraph"
7
- version = "1.0.2"
7
+ version = "1.0.2.post1"
8
8
  description = "An event-sourced reactive graph runtime for long-running, auditable, agentic systems."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"