agentic-runner 4.0.0__tar.gz → 4.1.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 (83) hide show
  1. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/PKG-INFO +2 -2
  2. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/pyproject.toml +1 -1
  3. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/__init__.py +1 -1
  4. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/activities.py +61 -5
  5. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/credentials.py +7 -0
  6. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/pool_key.py +40 -2
  7. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/fastapi_client.py +13 -0
  8. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/.gitignore +0 -0
  9. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/LICENSE +0 -0
  10. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/README.md +0 -0
  11. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/attempts.py +0 -0
  12. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/auth_mode.py +0 -0
  13. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/build.py +0 -0
  14. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/callback.py +0 -0
  15. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/child_watcher.py +0 -0
  16. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/cli.py +0 -0
  17. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/cli_floor.py +0 -0
  18. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/config.py +0 -0
  19. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/connections.py +0 -0
  20. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/egress.py +0 -0
  21. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/guardrails.py +0 -0
  22. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/harness_self_test.py +0 -0
  23. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/heartbeat_link.py +0 -0
  24. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/hooks.py +0 -0
  25. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/host_store.py +0 -0
  26. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/__init__.py +0 -0
  27. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
  28. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/git/contracts.py +0 -0
  29. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
  30. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
  31. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/git/workspace.py +0 -0
  32. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
  33. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/github/auth.py +0 -0
  34. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
  35. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
  36. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
  37. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
  38. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/launch.py +0 -0
  39. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/lifecycle.py +0 -0
  40. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/listener_firing.py +0 -0
  41. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/llm_contracts.py +0 -0
  42. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/llm_proxy.py +0 -0
  43. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/mcp.py +0 -0
  44. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/message_store.py +0 -0
  45. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/oauth_connectors.py +0 -0
  46. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/organisation_name.py +0 -0
  47. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/private_state.py +0 -0
  48. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/py.typed +0 -0
  49. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/recipient_key_secret.py +0 -0
  50. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/registration.py +0 -0
  51. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/runtime/__init__.py +0 -0
  52. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
  53. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/sealed_box.py +0 -0
  54. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/service.py +0 -0
  55. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/sign_in.py +0 -0
  56. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/skill.py +0 -0
  57. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +0 -0
  58. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/testing/__init__.py +0 -0
  59. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/testing/__main__.py +0 -0
  60. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/testing/control_plane.py +0 -0
  61. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/testing/plugin.py +0 -0
  62. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/testing/test_conformance.py +0 -0
  63. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/testing/workflow.py +0 -0
  64. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/tiny_http.py +0 -0
  65. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/triage_activities.py +0 -0
  66. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/usage_windows.py +0 -0
  67. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/user_sources.py +0 -0
  68. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/__init__.py +0 -0
  69. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
  70. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/acp_runtime.py +0 -0
  71. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
  72. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
  73. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/claude_sign_in.py +0 -0
  74. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
  75. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/command_policy.py +0 -0
  76. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
  77. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/harness_logins.py +0 -0
  78. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/harness_outcome.py +0 -0
  79. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/harness_usage.py +0 -0
  80. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/mcp_config.py +0 -0
  81. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/settings.py +0 -0
  82. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workers/skills.py +0 -0
  83. {agentic_runner-4.0.0 → agentic_runner-4.1.0}/src/agentic_runner/workstation.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentic-runner
3
- Version: 4.0.0
3
+ Version: 4.1.0
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==4.0.0
8
+ Requires-Dist: agentic-runner-contracts==4.1.0
9
9
  Requires-Dist: cryptography<51,>=50
10
10
  Requires-Dist: google-re2<2.0,>=1.1
11
11
  Requires-Dist: httpx<1.0,>=0.27
@@ -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==4.0.0",
13
+ "agentic-runner-contracts==4.1.0",
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__ = "4.0.0"
10
+ __version__ = "4.1.0"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -310,6 +310,7 @@ from agentic_runner_contracts.runtime_context import (
310
310
  WorkerRuntimeContext,
311
311
  WorkerRuntimeContextResolver,
312
312
  )
313
+ from agentic_runner_contracts.secret import SecretRelease, SecretReleaseRequest
313
314
  from agentic_runner_contracts.swarm import ROLE_LEAD
314
315
 
315
316
  _GIT_EVIDENCE_LIMIT_BYTES = 32_768
@@ -566,6 +567,11 @@ class RunnerRalphFastApiClient(Protocol):
566
567
  ) -> dict[str, Any]:
567
568
  """The sealed shares of the pool's LLM Key a Directive was routed to (rev 3)."""
568
569
 
570
+ async def release_secrets(
571
+ self, work_record_id: str, payload: Mapping[str, Any]
572
+ ) -> dict[str, Any]:
573
+ """The sealed shares of the Secrets a Directive needs (Agent Guardrails rev 7)."""
574
+
569
575
  async def report_harness_usage(self, payload: Mapping[str, Any]) -> list[dict[str, Any]]:
570
576
  """One Usage Record per model off a harness's own JSON (PRD issue 31, 17 A9)."""
571
577
 
@@ -2759,7 +2765,7 @@ class RunnerRalphActivities:
2759
2765
  )
2760
2766
 
2761
2767
  await self._refuse_skill_digest_mismatch(work_record_id, state)
2762
- credentials = await self._resolve_credentials(work_record_id, state)
2768
+ credentials = await self._resolve_credentials(work_record_id, state, attempt_directive_id)
2763
2769
  if credentials is not None:
2764
2770
  # Names only, at the moment they resolved (22 A1's Evidence): a support read
2765
2771
  # of "which credentials did this Directive have" must not be answerable by
@@ -3190,23 +3196,29 @@ class RunnerRalphActivities:
3190
3196
  self,
3191
3197
  work_record_id: str,
3192
3198
  state: _RuntimeContextState,
3199
+ attempt_directive_id: str,
3193
3200
  ) -> DirectiveCredentials | None:
3194
3201
  """Resolve this Contract's Credential References, or fail the Directive closed.
3195
3202
 
3196
3203
  22 A1: the Runner resolves a reference at Directive time from its host store and
3197
- hands the value only to verb seams and Runner-hosted MCP servers. An unresolvable
3198
- one is not degraded into "run without it" -- the Directive would then do the work
3199
- wrong and the Evidence would not say why -- so it raises, naming the reference,
3200
- after appending the Evidence that names it.
3204
+ hands the value only to verb seams and Runner-hosted MCP servers. A reference the
3205
+ host does not hold is asked of the control plane as a Secret (Agent Guardrails rev
3206
+ 7, S4). An unresolvable one is not degraded into "run without it" -- the Directive
3207
+ would then do the work wrong and the Evidence would not say why -- so it raises,
3208
+ naming the reference, after appending the Evidence that names it.
3201
3209
  """
3202
3210
 
3203
3211
  if not state.credential_references:
3204
3212
  return None
3205
3213
  resolver = self._credentials or CredentialResolver(store=EmptyCredentialStore())
3206
3214
  try:
3215
+ released = await self._release_secrets(
3216
+ work_record_id, state, resolver, attempt_directive_id
3217
+ )
3207
3218
  return resolver.resolve(
3208
3219
  contract_id=state.contract_id,
3209
3220
  manifest=state.credential_references,
3221
+ released=released,
3210
3222
  )
3211
3223
  except UnresolvableCredentialReferenceError as error:
3212
3224
  await self._fastapi_client.append_evidence(
@@ -3216,6 +3228,50 @@ class RunnerRalphActivities:
3216
3228
  )
3217
3229
  raise
3218
3230
 
3231
+ async def _release_secrets(
3232
+ self,
3233
+ work_record_id: str,
3234
+ state: _RuntimeContextState,
3235
+ resolver: CredentialResolver,
3236
+ attempt_directive_id: str,
3237
+ ) -> dict[str, str]:
3238
+ """The Secrets this Directive needs and the host does not hold, opened in memory.
3239
+
3240
+ Asked only by a Runner holding the Organisation Key, the one holder key a Secret is
3241
+ sealed to in this release; any other leaves the names to fail as not installed. The
3242
+ values live in the Directive's credentials and nowhere else, and go with it.
3243
+ """
3244
+
3245
+ missing = [
3246
+ reference
3247
+ for reference in state.credential_references
3248
+ if not resolver.usable(str(state.contract_id), reference)
3249
+ ]
3250
+ fingerprint = self._llm_keys.fingerprint if self._llm_keys is not None else None
3251
+ if not missing or self._llm_keys is None or fingerprint is None:
3252
+ return {}
3253
+ try:
3254
+ payload = await self._fastapi_client.release_secrets(
3255
+ work_record_id,
3256
+ SecretReleaseRequest(
3257
+ directive_id=attempt_directive_id,
3258
+ names=missing,
3259
+ holder_fingerprints=[fingerprint],
3260
+ ).model_dump(mode="json"),
3261
+ )
3262
+ except WorkerFastApiClientRejectionError as error:
3263
+ raise UnresolvableCredentialReferenceError(
3264
+ missing[0],
3265
+ contract_id=state.contract_id,
3266
+ reason=f"secret_release_refused:{error.status_code}",
3267
+ ) from error
3268
+ try:
3269
+ return self._llm_keys.open_secrets(SecretRelease.model_validate(payload), names=missing)
3270
+ except (ValidationError, LlmKeyUnopenableError) as error:
3271
+ raise UnresolvableCredentialReferenceError(
3272
+ missing[0], contract_id=state.contract_id, reason="secret_unopenable"
3273
+ ) from error
3274
+
3219
3275
  async def _directive_auth_mode(
3220
3276
  self, work_record_id: str, state: _RuntimeContextState, access: LlmAccess | None = None
3221
3277
  ) -> AuthMode:
@@ -260,9 +260,14 @@ class CredentialResolver:
260
260
  *,
261
261
  contract_id: str | None,
262
262
  manifest: Iterable[str],
263
+ released: Mapping[str, str] | None = None,
263
264
  ) -> DirectiveCredentials:
264
265
  """Every reference the Contract declares, or the first failure, named.
265
266
 
267
+ ``released`` holds the Secrets the control plane released for this Directive
268
+ (Spec: Agent Guardrails rev 7, S4). They come last: a value this Runner's host
269
+ installed under the same name still counts, and wins (A2).
270
+
266
271
  Fails on the *first* unresolvable reference rather than collecting them: a
267
272
  Directive that cannot have one of its credentials is not going to run, and a
268
273
  partial resolution left lying around is a value held for no reason.
@@ -271,6 +276,8 @@ class CredentialResolver:
271
276
  values: dict[str, str] = {}
272
277
  for reference in manifest:
273
278
  value = self._value(contract_id, reference)
279
+ if value is None and released is not None:
280
+ value = released.get(reference)
274
281
  if value is None:
275
282
  raise UnresolvableCredentialReferenceError(
276
283
  reference, contract_id=contract_id, reason="not_installed"
@@ -1,4 +1,5 @@
1
- """The pool's Pool Key on this Runner, and the LLM Keys it opens (Spec: Runners rev 3).
1
+ """The pool's Pool Key on this Runner, and the LLM Keys and Secrets it opens (Spec: Runners
2
+ rev 3; Spec: Agent Guardrails rev 7, S3-S4, where it is called the Organisation Key).
2
3
 
3
4
  An Organisation's Admin creates the Pool Key in the console, which shows its private half
4
5
  once, and gives it to every Runner of the pool at install: one chart Secret for all
@@ -15,7 +16,7 @@ from __future__ import annotations
15
16
  import base64
16
17
  import binascii
17
18
  import os
18
- from collections.abc import Mapping, MutableMapping
19
+ from collections.abc import Mapping, MutableMapping, Sequence
19
20
  from dataclasses import dataclass, field
20
21
  from pathlib import Path
21
22
  from typing import Final
@@ -28,6 +29,7 @@ from agentic_runner.sealed_box import RecipientKeyStore, SealedCredentialError,
28
29
  from agentic_runner_contracts.llm_access import PROVIDER_KEY_NAMES
29
30
  from agentic_runner_contracts.llm_key import LlmKeyRelease, join_llm_key
30
31
  from agentic_runner_contracts.sealed_credential import key_fingerprint
32
+ from agentic_runner_contracts.secret import HolderKind, SecretRelease, join_secret
31
33
 
32
34
  __all__ = [
33
35
  "POOL_KEY_ENV",
@@ -166,6 +168,42 @@ class LlmKeyOpener:
166
168
  reference=key_name, key_id=release.llm_key_id, value=value
167
169
  )
168
170
 
171
+ def open_secrets(self, release: SecretRelease, *, names: Sequence[str]) -> dict[str, str]:
172
+ """Every asked-for Secret's value, by name, held by the caller for one Directive.
173
+
174
+ The release must carry exactly ``names``, each sealed to this Runner's Pool Key:
175
+ a Secret the Directive did not ask for is not one it may hold. One error for every
176
+ way it fails, for :meth:`open`'s reason.
177
+ """
178
+
179
+ unopenable = "the released Secrets do not open with this Runner's keys"
180
+ if self._pool_key is None or sorted(secret.name for secret in release.secrets) != sorted(
181
+ names
182
+ ):
183
+ raise LlmKeyUnopenableError(unopenable)
184
+ values: dict[str, str] = {}
185
+ try:
186
+ for secret in release.secrets:
187
+ if (
188
+ secret.holder_kind != HolderKind.ORGANISATION_KEY
189
+ or secret.holder_fingerprint != self._pool_key.fingerprint
190
+ ):
191
+ raise SealedCredentialError("sealed to another holder key")
192
+ holder_share = open_sealed(
193
+ private_key=self._pool_key.private_key,
194
+ binding=release.holder_share_binding(secret),
195
+ ciphertext=secret.holder_share,
196
+ )
197
+ platform_share = open_sealed(
198
+ private_key=self._recipient_private_key(release.recipient_key_id),
199
+ binding=release.platform_share_binding(secret),
200
+ ciphertext=secret.platform_share,
201
+ )
202
+ values[secret.name] = join_secret(holder_share, platform_share)
203
+ except (SealedCredentialError, ValueError) as error:
204
+ raise LlmKeyUnopenableError(unopenable) from error
205
+ return values
206
+
169
207
  def _recipient_private_key(self, recipient_key_id: str) -> str:
170
208
  # The previous key too: the control plane may have sealed its share just before a
171
209
  # renewal.
@@ -190,6 +190,19 @@ class RunnerFastApiClient:
190
190
  payload,
191
191
  )
192
192
 
193
+ async def release_secrets(
194
+ self, work_record_id: str, payload: Mapping[str, Any]
195
+ ) -> dict[str, Any]:
196
+ """The sealed shares of the Secrets a Directive needs and this Runner's host does
197
+ not hold (Spec: Agent Guardrails rev 7, S4). ``payload`` is
198
+ ``SecretReleaseRequest``'s shape; the Directive token says which Runner and
199
+ Directive ask."""
200
+
201
+ return await self._post(
202
+ f"{self._prefix}/work-records/{_path_segment(work_record_id)}/secrets/release",
203
+ payload,
204
+ )
205
+
193
206
  async def append_evidence(
194
207
  self,
195
208
  work_record_id: str,
File without changes
File without changes