agentic-runner 3.25.0__tar.gz → 3.25.1__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 (79) hide show
  1. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/PKG-INFO +2 -2
  2. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/pyproject.toml +1 -1
  3. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/__init__.py +1 -1
  4. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/activities.py +23 -1
  5. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/auth_mode.py +25 -1
  6. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/service.py +9 -0
  7. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/triage_activities.py +19 -2
  8. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/.gitignore +0 -0
  9. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/LICENSE +0 -0
  10. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/README.md +0 -0
  11. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/attempts.py +0 -0
  12. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/build.py +0 -0
  13. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/callback.py +0 -0
  14. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/child_watcher.py +0 -0
  15. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/cli.py +0 -0
  16. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/cli_floor.py +0 -0
  17. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/config.py +0 -0
  18. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/credentials.py +0 -0
  19. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/egress.py +0 -0
  20. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/harness_self_test.py +0 -0
  21. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/heartbeat_link.py +0 -0
  22. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/hooks.py +0 -0
  23. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/host_store.py +0 -0
  24. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/__init__.py +0 -0
  25. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/git/__init__.py +0 -0
  26. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/git/contracts.py +0 -0
  27. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/git/evidence.py +0 -0
  28. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
  29. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/git/workspace.py +0 -0
  30. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/github/__init__.py +0 -0
  31. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/github/auth.py +0 -0
  32. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/github/fake_client.py +0 -0
  33. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/github/gh_client.py +0 -0
  34. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
  35. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
  36. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/launch.py +0 -0
  37. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/lifecycle.py +0 -0
  38. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/llm_contracts.py +0 -0
  39. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/llm_proxy.py +0 -0
  40. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/mcp.py +0 -0
  41. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/message_store.py +0 -0
  42. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/oauth_connectors.py +0 -0
  43. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/private_state.py +0 -0
  44. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/py.typed +0 -0
  45. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/recipient_key_secret.py +0 -0
  46. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/registration.py +0 -0
  47. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/runner_limit.py +0 -0
  48. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/runtime/__init__.py +0 -0
  49. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/runtime/verifier_command.py +0 -0
  50. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/sealed_box.py +0 -0
  51. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/sign_in.py +0 -0
  52. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/skill.py +0 -0
  53. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +0 -0
  54. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/testing/__init__.py +0 -0
  55. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/testing/__main__.py +0 -0
  56. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/testing/control_plane.py +0 -0
  57. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/testing/plugin.py +0 -0
  58. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/testing/test_conformance.py +0 -0
  59. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/testing/workflow.py +0 -0
  60. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/tiny_http.py +0 -0
  61. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/usage_windows.py +0 -0
  62. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/user_sources.py +0 -0
  63. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/__init__.py +0 -0
  64. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/_runtime_support.py +0 -0
  65. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/acp_runtime.py +0 -0
  66. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/agent_runtime.py +0 -0
  67. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/claude_runtime.py +0 -0
  68. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/claude_sign_in.py +0 -0
  69. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/codex_runtime.py +0 -0
  70. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/command_policy.py +0 -0
  71. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/contract_isolation.py +0 -0
  72. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/fastapi_client.py +0 -0
  73. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/harness_logins.py +0 -0
  74. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/harness_outcome.py +0 -0
  75. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/harness_usage.py +0 -0
  76. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/mcp_config.py +0 -0
  77. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/settings.py +0 -0
  78. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workers/skills.py +0 -0
  79. {agentic_runner-3.25.0 → agentic_runner-3.25.1}/src/agentic_runner/workstation.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentic-runner
3
- Version: 3.25.0
3
+ Version: 3.25.1
4
4
  Summary: The Agentic OS Runner: an activity-only Temporal executor for workspaces, Agent Runtimes and verb seams
5
5
  License-Expression: AGPL-3.0-only
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.12
8
- Requires-Dist: agentic-runner-contracts==3.25.0
8
+ Requires-Dist: agentic-runner-contracts==3.25.1
9
9
  Requires-Dist: cryptography<51,>=50
10
10
  Requires-Dist: httpx<1.0,>=0.27
11
11
  Requires-Dist: pydantic-settings<3.0,>=2.0
@@ -10,7 +10,7 @@ dependencies = [
10
10
  # Exactly this release's contracts: the two ship as one version, and an unpinned
11
11
  # requirement let `uv tool upgrade` keep an older contracts wheel the Runner cannot
12
12
  # import from (QTS-1473). `scripts/set-version.py` moves it with the version.
13
- "agentic-runner-contracts==3.25.0",
13
+ "agentic-runner-contracts==3.25.1",
14
14
  # Imported directly by `registration.py` (the Ed25519 identity that signs every
15
15
  # heartbeat); already present transitively through pyjwt[crypto], declared because
16
16
  # we import it.
@@ -7,6 +7,6 @@ stay on the platform; this package imports none of them, and a CI partition test
7
7
  (``tests/test_package_partition.py``) fails the build if that changes.
8
8
  """
9
9
 
10
- __version__ = "3.25.0"
10
+ __version__ = "3.25.1"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -292,7 +292,11 @@ from agentic_runner_contracts.routing import (
292
292
  assert_routed,
293
293
  selector_satisfied,
294
294
  )
295
- from agentic_runner_contracts.runner_registration import SubscriptionState
295
+ from agentic_runner_contracts.runner_registration import (
296
+ ContractLlmTerms,
297
+ SubscriptionRight,
298
+ SubscriptionState,
299
+ )
296
300
  from agentic_runner_contracts.runtime_context import (
297
301
  EGRESS_ALLOW_LIST_KEY,
298
302
  McpServerSpec,
@@ -1720,6 +1724,7 @@ class RunnerRalphActivities:
1720
1724
  directives_in_flight: DirectivesInFlight | None = None,
1721
1725
  usage_windows: UsageWindows | None = None,
1722
1726
  runner_limit: AppliedRunnerLimit | None = None,
1727
+ llm_contracts: Callable[[], Sequence[ContractLlmTerms] | None] | None = None,
1723
1728
  ) -> None:
1724
1729
  self._fastapi_client = fastapi_client
1725
1730
  # What this Runner's host denies every Agent on it, checked at start (ADR-0022
@@ -1794,6 +1799,9 @@ class RunnerRalphActivities:
1794
1799
  # The retry fence (runner-repo 05). None fences nothing: a process that never
1795
1800
  # registered has no state directory to keep the records in.
1796
1801
  self._attempt_records = attempt_records
1802
+ # The Contracts the last heartbeat ack named, read at each Directive (QTS-1751).
1803
+ # None, or a callable answering None, before any ack from a 3.25 control plane.
1804
+ self._llm_contracts = llm_contracts
1797
1805
 
1798
1806
  def activity_callables(self) -> list[Callable[..., Any]]:
1799
1807
  """Return decorated activity callables for Temporal worker registration."""
@@ -3148,6 +3156,7 @@ class RunnerRalphActivities:
3148
3156
  host_party=host_party,
3149
3157
  host_key_held=self._host_key_held(state),
3150
3158
  subscription=self._subscription_state(state),
3159
+ subscription_right=self._subscription_right(state),
3151
3160
  runtime_modes=getattr(runtime, "auth_modes", frozenset({AuthMode.API_KEY})),
3152
3161
  )
3153
3162
  return choose_auth_mode(
@@ -3219,6 +3228,19 @@ class RunnerRalphActivities:
3219
3228
  and self._llm_proxy.slots.holds_host(provider.value)
3220
3229
  )
3221
3230
 
3231
+ def _subscription_right(self, state: _RuntimeContextState) -> SubscriptionRight | None:
3232
+ """What the last ack said of this Contract's own subscription; ``no_right`` for a
3233
+ Contract it did not name, since nothing then allowed one here."""
3234
+
3235
+ terms = self._llm_contracts() if self._llm_contracts is not None else None
3236
+ if terms is None:
3237
+ return None
3238
+ contract_id = _optional_uuid(state.contract_id)
3239
+ return next(
3240
+ (term.subscription for term in terms if term.contract_id == contract_id),
3241
+ SubscriptionRight.NO_RIGHT,
3242
+ )
3243
+
3222
3244
  def _subscription_state(self, state: _RuntimeContextState) -> SubscriptionState:
3223
3245
  if self._contract_isolation is None or not state.contract_id:
3224
3246
  return SubscriptionState.NONE
@@ -30,7 +30,7 @@ from typing import Final
30
30
 
31
31
  from agentic_runner.workers.agent_runtime import AuthMode
32
32
  from agentic_runner_contracts.llm_access import LlmAccess, LlmAccessKind
33
- from agentic_runner_contracts.runner_registration import SubscriptionState
33
+ from agentic_runner_contracts.runner_registration import SubscriptionRight, SubscriptionState
34
34
 
35
35
  __all__ = [
36
36
  "NO_RUNTIME_SUBSCRIPTION_RULE",
@@ -38,7 +38,9 @@ __all__ = [
38
38
  "LLM_ACCESS_UNKNOWN_RULE",
39
39
  "LLM_KEY_UNSUPPORTED_RULE",
40
40
  "SETUP_TOKEN_REFUSED_RULE",
41
+ "SUBSCRIPTION_NOT_ACKNOWLEDGED_RULE",
41
42
  "SUBSCRIPTION_NOT_SIGNED_IN_RULE",
43
+ "SUBSCRIPTION_NO_RIGHT_RULE",
42
44
  "SUBSCRIPTION_OFF_PERSONAL_RUNNER_RULE",
43
45
  "SHARED_HOST_PARTIES",
44
46
  "SIGN_IN_NEEDS_CONTRACT_UID_RULE",
@@ -64,6 +66,8 @@ SETUP_TOKEN_REFUSED_RULE: Final[str] = "setup_token_refused"
64
66
  HOST_KEY_NOT_HELD_RULE: Final[str] = "host_key_not_held"
65
67
  SUBSCRIPTION_NOT_SIGNED_IN_RULE: Final[str] = "subscription_not_signed_in"
66
68
  SUBSCRIPTION_OFF_PERSONAL_RUNNER_RULE: Final[str] = "subscription_off_personal_runner"
69
+ SUBSCRIPTION_NO_RIGHT_RULE: Final[str] = "subscription_no_right"
70
+ SUBSCRIPTION_NOT_ACKNOWLEDGED_RULE: Final[str] = "subscription_not_acknowledged"
67
71
  LLM_KEY_UNSUPPORTED_RULE: Final[str] = "llm_key_unsupported"
68
72
  LLM_ACCESS_UNKNOWN_RULE: Final[str] = "llm_access_unknown"
69
73
  # `claude setup-token` mints an OAuth bearer under this prefix; an Anthropic API key is
@@ -148,6 +152,7 @@ def require_named_access(
148
152
  host_party: str | None,
149
153
  host_key_held: bool,
150
154
  subscription: SubscriptionState,
155
+ subscription_right: SubscriptionRight | None,
151
156
  runtime_modes: Collection[AuthMode],
152
157
  ) -> AuthMode:
153
158
  """The mode the named access runs in, or a refusal naming what is missing.
@@ -156,6 +161,12 @@ def require_named_access(
156
161
  proxy; ``subscription`` the Contract's harness root for the Directive's kind. A
157
162
  subscription runs only on its person's own Runner (rev 3: never on a pool Runner),
158
163
  and only while it is signed in, unexpired and not at its usage limit.
164
+
165
+ ``subscription_right`` is what the last heartbeat ack said of the Contract's own
166
+ subscription (``llm_contracts``), so a misrouted Directive is refused here as well as
167
+ at the platform's routing (QTS-1349 controls 4 and 7, QTS-1751). ``None`` while no ack
168
+ has named the Contracts -- a control plane below 3.25 -- which leaves routing the only
169
+ check, as before.
159
170
  """
160
171
 
161
172
  if access.kind == LlmAccessKind.HOST_KEY:
@@ -173,6 +184,19 @@ def require_named_access(
173
184
  f"this Runner is hosted by {host_party!r}; a subscription runs only on its "
174
185
  "person's own Runner",
175
186
  )
187
+ if subscription_right == SubscriptionRight.NO_RIGHT:
188
+ raise AuthModeRefusedError(
189
+ SUBSCRIPTION_NO_RIGHT_RULE,
190
+ "this Directive was routed to the person's subscription, and the control plane "
191
+ "last told this Runner that the Contract does not allow one",
192
+ )
193
+ if subscription_right == SubscriptionRight.NOT_ACKNOWLEDGED:
194
+ raise AuthModeRefusedError(
195
+ SUBSCRIPTION_NOT_ACKNOWLEDGED_RULE,
196
+ "this Directive was routed to the person's subscription, and the control plane "
197
+ "last told this Runner that the person has not acknowledged the subscription "
198
+ "terms",
199
+ )
176
200
  if AuthMode.SUBSCRIPTION not in runtime_modes:
177
201
  raise AuthModeRefusedError(
178
202
  NO_RUNTIME_SUBSCRIPTION_RULE,
@@ -1092,6 +1092,10 @@ class ControlPlaneStream:
1092
1092
  self.credentials.drop(contract_id, slot)
1093
1093
  self._delivered = set(plaintext)
1094
1094
 
1095
+ @property
1096
+ def llm_contracts(self) -> list[ContractLlmTerms] | None:
1097
+ return None if self._llm_contracts is None else list(self._llm_contracts)
1098
+
1095
1099
  def _take_llm_contracts(self, terms: list[ContractLlmTerms] | None) -> None:
1096
1100
  """Keep the ack's Contract terms, and the copy ``sign-in`` reads when they change.
1097
1101
  An ack without them (a control plane below 3.25) leaves what is held."""
@@ -1626,6 +1630,7 @@ async def _serve_registered(
1626
1630
  tool_servers=tool_servers,
1627
1631
  usage_windows=usage_windows,
1628
1632
  runner_limit=runner_limit,
1633
+ llm_contracts=lambda: stream.llm_contracts,
1629
1634
  # The wake signal rides the connection this process polls with: one
1630
1635
  # namespace, the Organisation's own (PRD issue 52, map ticket 15 §2).
1631
1636
  signaller=TemporalWorkflowSignaller(client),
@@ -1823,6 +1828,7 @@ def _activities(
1823
1828
  tool_servers: ToolServerHealthLog | None = None,
1824
1829
  usage_windows: UsageWindows | None = None,
1825
1830
  runner_limit: AppliedRunnerLimit | None = None,
1831
+ llm_contracts: Callable[[], list[ContractLlmTerms] | None] | None = None,
1826
1832
  ) -> list[Callable[..., Any]]:
1827
1833
  github_client = GitHubAppClient(
1828
1834
  app_id=os.getenv("GITHUB_APP_ID"),
@@ -1872,6 +1878,7 @@ def _activities(
1872
1878
  directives_in_flight=directives_in_flight,
1873
1879
  usage_windows=usage_windows,
1874
1880
  runner_limit=runner_limit,
1881
+ llm_contracts=llm_contracts,
1875
1882
  )
1876
1883
  return [
1877
1884
  *ralph.activity_callables(),
@@ -1879,5 +1886,7 @@ def _activities(
1879
1886
  proxy=proxy,
1880
1887
  model=settings.TRIAGE_MODEL,
1881
1888
  timeout_seconds=settings.TRIAGE_DIRECTIVE_TIMEOUT_SECONDS,
1889
+ host_party=state.host_party,
1890
+ llm_contracts=llm_contracts,
1882
1891
  ).activity_callables(),
1883
1892
  ]
@@ -9,7 +9,7 @@ the same turn in-process (``user_sources.ProxyTriage``).
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from collections.abc import Callable
12
+ from collections.abc import Callable, Sequence
13
13
  from uuid import UUID
14
14
 
15
15
  import httpx
@@ -17,6 +17,7 @@ from temporalio import activity
17
17
 
18
18
  from agentic_runner.llm_proxy import LlmProxy
19
19
  from agentic_runner_contracts.activity_io import TriageTurnInput, TriageTurnOutput
20
+ from agentic_runner_contracts.runner_registration import ContractLlmTerms
20
21
 
21
22
 
22
23
  async def run_triage_turn(
@@ -29,13 +30,15 @@ async def run_triage_turn(
29
30
  contract_id: UUID | None,
30
31
  agent_id: UUID | None,
31
32
  reserve_max_tokens: int | None = None,
33
+ delivered_only: bool = False,
32
34
  client: httpx.AsyncClient | None = None,
33
35
  ) -> str | None:
34
36
  """One completion through this Runner's proxy, metered like any Directive.
35
37
 
36
38
  ``None`` on anything that keeps the answer from being trusted -- a hang, a ceiling or
37
39
  Reserve refusal, a missing slot, a malformed body: the caller's fallback is the
38
- keyword classifier, never an Incident (PRD issue 29).
40
+ keyword classifier, never an Incident (PRD issue 29). ``delivered_only`` keeps the
41
+ turn off this Runner's store key (``LlmProxy.attempt``).
39
42
  """
40
43
 
41
44
  http = client or httpx.AsyncClient()
@@ -45,6 +48,7 @@ async def run_triage_turn(
45
48
  contract_id=contract_id,
46
49
  agent_id=agent_id,
47
50
  reserve_max_tokens=reserve_max_tokens,
51
+ delivered_only=delivered_only,
48
52
  ) as handle:
49
53
  response = await http.post(
50
54
  f"{handle.base_url}/chat/completions",
@@ -80,11 +84,15 @@ class RunnerTriageActivities:
80
84
  proxy: LlmProxy,
81
85
  model: str,
82
86
  timeout_seconds: float,
87
+ host_party: str | None = None,
88
+ llm_contracts: Callable[[], Sequence[ContractLlmTerms] | None] | None = None,
83
89
  client: httpx.AsyncClient | None = None,
84
90
  ) -> None:
85
91
  self._proxy = proxy
86
92
  self._model = model
87
93
  self._timeout_seconds = timeout_seconds
94
+ self._host_party = host_party
95
+ self._llm_contracts = llm_contracts
88
96
  self._client = client
89
97
 
90
98
  def activity_callables(self) -> list[Callable[..., object]]:
@@ -101,10 +109,19 @@ class RunnerTriageActivities:
101
109
  contract_id=_optional_uuid(request.intake_lead_contract_id),
102
110
  agent_id=_optional_uuid(request.intake_lead_agent_id),
103
111
  reserve_max_tokens=request.reserve_max_tokens,
112
+ delivered_only=self._pool_runner_before_named_access(),
104
113
  client=self._client,
105
114
  )
106
115
  return TriageTurnOutput(raw_text=raw_text)
107
116
 
117
+ def _pool_runner_before_named_access(self) -> bool:
118
+ """A pool Runner's store key stays out of the turn until an ack names the
119
+ Contracts: a control plane below 3.25 caps none of that spend, the rule a
120
+ Directive that names no access follows (QTS-1736, QTS-1751)."""
121
+
122
+ named = self._llm_contracts() if self._llm_contracts is not None else None
123
+ return self._host_party != "user" and named is None
124
+
108
125
 
109
126
  def _optional_uuid(value: str) -> UUID | None:
110
127
  try:
File without changes