agentic-runner-contracts 3.4.2__tar.gz → 3.6.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 (30) hide show
  1. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/PKG-INFO +1 -1
  2. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/__init__.py +11 -1
  3. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/activity_io.py +12 -1
  4. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/runner_registration.py +20 -7
  5. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/runtime_context.py +14 -4
  6. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/sealed_credential.py +69 -0
  7. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/.gitignore +0 -0
  8. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/LICENSE +0 -0
  9. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/README.md +0 -0
  10. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/pyproject.toml +0 -0
  11. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/channel_messages.py +0 -0
  12. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/epics.py +0 -0
  13. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/github_port.py +0 -0
  14. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/grants/__init__.py +0 -0
  15. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/grants/catalogue.py +0 -0
  16. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/grants/evaluator.py +0 -0
  17. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/grants/model.py +0 -0
  18. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/grants/seam.py +0 -0
  19. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/grants/snapshot.py +0 -0
  20. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/incidents.py +0 -0
  21. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/llm_usage.py +0 -0
  22. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/public_metadata.py +0 -0
  23. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/py.typed +0 -0
  24. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/questions.py +0 -0
  25. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/redaction.py +0 -0
  26. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/routing.py +0 -0
  27. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/runner_signature.py +0 -0
  28. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/swarm.py +0 -0
  29. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/triage.py +0 -0
  30. {agentic_runner_contracts-3.4.2 → agentic_runner_contracts-3.6.0}/src/agentic_runner_contracts/user_sources.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentic-runner-contracts
3
- Version: 3.4.2
3
+ Version: 3.6.0
4
4
  Summary: Activity I/O contracts and Public Metadata name builders shared by the Agentic OS platform and the installable Runner
5
5
  License-Expression: AGPL-3.0-only
6
6
  License-File: LICENSE
@@ -32,6 +32,16 @@ the package the floor is *about*, and read back through ``agentic-runner --versi
32
32
  # Additive; an older payload parses with no Skills. But the context forbids extra keys,
33
33
  # so a Runner one minor behind refuses a context that carries `skills` -- the platform
34
34
  # must not send the field until its Runners are on 2.7.
35
- __version__ = "3.4.2"
35
+ # 3.5.0 (local-agents 12): `WorkerRuntimeContext.cli_kind` widens from the two known
36
+ # kinds to the registration pattern `^[a-z][a-z0-9_]{0,31}$`. Additive: every payload that
37
+ # parsed still parses; a Runner one minor behind refuses a context naming a new kind, which
38
+ # fails that Directive closed, and routing sends a kind only to a Runner that reports it.
39
+ # 3.6.0 (local-agents 05): `ContractDeviceLoginInput.method`,
40
+ # `ContractDeviceLoginResult.sign_in_id`, `ContractDeviceLoginStatusResult.auth_method` /
41
+ # `subscription_type`, `SealedSignInCode` on `HeartbeatAck.sign_in_codes` and
42
+ # `SignInCodeRelay` on `HeartbeatEnvelope.sign_in_codes`. Additive; the envelope omits the
43
+ # key while it is empty. But the ack forbids extra keys, so the platform must not relay a
44
+ # code to a Runner below 3.6.
45
+ __version__ = "3.6.0"
36
46
 
37
47
  __all__ = ["__version__"]
@@ -1237,6 +1237,10 @@ class ContractDeviceLoginInput:
1237
1237
  # The Runner whose disk holds that harness root, and so whose queue the activity is
1238
1238
  # dispatched on (PRD issue 37). Optional only for a start that predates the split.
1239
1239
  runner_id: str | None = None
1240
+ # Claude Code only (local-agents 05): `oauth_token` (`claude setup-token`, the
1241
+ # long-lived token) or `claude_ai` (`claude auth login --claudeai`). None means
1242
+ # `oauth_token`, and the only one a shared Runner accepts.
1243
+ method: str | None = None
1240
1244
 
1241
1245
 
1242
1246
  @dataclass(frozen=True)
@@ -1254,8 +1258,11 @@ class ContractDeviceLoginResult:
1254
1258
  contract_id: str
1255
1259
  runtime_kind: str
1256
1260
  verification_uri: str
1261
+ # Empty for Claude Code, whose flow has no device code: the browser shows the person a
1262
+ # code to paste back instead, sealed and relayed against `sign_in_id`.
1257
1263
  user_code: str
1258
1264
  expires_at: str
1265
+ sign_in_id: str = ""
1259
1266
 
1260
1267
 
1261
1268
  @dataclass(frozen=True)
@@ -1279,6 +1286,10 @@ class ContractDeviceLoginStatusResult:
1279
1286
  runtime_kind: str
1280
1287
  token_present: bool
1281
1288
  delivered_at: str | None
1289
+ # Claude Code only, from `claude auth status --json` (local-agents 05): which login
1290
+ # the CLI found (`oauth_token`, `claude.ai`) and the plan it reports.
1291
+ auth_method: str | None = None
1292
+ subscription_type: str | None = None
1282
1293
 
1283
1294
 
1284
1295
  class ContractDeviceLoginActivities(Protocol):
@@ -1298,7 +1309,7 @@ class ContractDeviceLoginStatusActivities(Protocol):
1298
1309
  async def check_contract_device_login_status(
1299
1310
  self, request: ContractDeviceLoginStatusInput
1300
1311
  ) -> ContractDeviceLoginStatusResult:
1301
- """Stat this Contract's harness token file; never open it."""
1312
+ """Whether this Contract's harness is signed in; never opens its login."""
1302
1313
 
1303
1314
 
1304
1315
  @dataclass(frozen=True)
@@ -43,7 +43,12 @@ from pydantic import (
43
43
 
44
44
  from agentic_runner_contracts.channel_messages import TranscriptRequest
45
45
  from agentic_runner_contracts.llm_usage import UsageRecord
46
- from agentic_runner_contracts.sealed_credential import OpenedCredential, SealedCredential
46
+ from agentic_runner_contracts.sealed_credential import (
47
+ OpenedCredential,
48
+ SealedCredential,
49
+ SealedSignInCode,
50
+ SignInCodeRelay,
51
+ )
47
52
  from agentic_runner_contracts.user_sources import SourceStatus, UserSourceAssignment
48
53
 
49
54
  # 23 item 4 (Buildkite's own numbers): heartbeat every 30 s, stale at 3 min. Stale is a
@@ -544,15 +549,19 @@ class HeartbeatEnvelope(BaseModel):
544
549
  # Console-v2 issue 29: each Runner-hosted Tool Server's latest start, per slug. A
545
550
  # server not started since the process began is absent.
546
551
  tool_servers: list[ToolServerHealth] = Field(default_factory=list, max_length=64)
552
+ # Local-agents 05: what became of each sign-in code the last ack relayed.
553
+ sign_in_codes: list[SignInCodeRelay] = Field(default_factory=list, max_length=16)
547
554
 
548
555
  @model_serializer(mode="wrap")
549
- def _omit_empty_tool_servers(self, handler: SerializerFunctionWrapHandler) -> Any:
550
- # A control plane on contracts 2.5 forbids extra keys, so a Runner that started no
551
- # Tool Server sends the body that control plane already parses. One that did needs
552
- # the control plane on 2.6 first.
556
+ def _omit_empty_optional_lists(self, handler: SerializerFunctionWrapHandler) -> Any:
557
+ # An older control plane forbids extra keys, so a Runner with nothing to say in
558
+ # these sends the body that control plane already parses. One that has something
559
+ # needs the control plane on the contracts minor that added the field first.
553
560
  body = handler(self)
554
- if isinstance(body, dict) and not body.get("tool_servers"):
555
- body.pop("tool_servers", None)
561
+ if isinstance(body, dict):
562
+ for name in ("tool_servers", "sign_in_codes"):
563
+ if not body.get(name):
564
+ body.pop(name, None)
556
565
  return body
557
566
 
558
567
 
@@ -600,6 +609,10 @@ class HeartbeatAck(BaseModel):
600
609
  # the control plane hands a request over on exactly one beat -- and answered on the
601
610
  # signed stream (`TRANSCRIPT_DELIVERIES_PATH`), never on the ack itself.
602
611
  transcript_requests: list[TranscriptRequest] = Field(default_factory=list, max_length=16)
612
+ # Local-agents 05: one-time browser sign-in codes, sealed to this installation's
613
+ # Recipient Key. Relayed once each, like `transcript_requests`; the outcome comes back
614
+ # on the next envelope's `sign_in_codes`.
615
+ sign_in_codes: list[SealedSignInCode] = Field(default_factory=list, max_length=16)
603
616
 
604
617
 
605
618
  class DirectiveTokenRequest(BaseModel):
@@ -9,9 +9,16 @@ the same reason: both sides resolve this context, through clients of their own.
9
9
  from __future__ import annotations
10
10
 
11
11
  import re
12
- from typing import Any, Literal, Protocol
13
-
14
- from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
12
+ from typing import Annotated, Any, Literal, Protocol
13
+
14
+ from pydantic import (
15
+ BaseModel,
16
+ ConfigDict,
17
+ Field,
18
+ StringConstraints,
19
+ field_validator,
20
+ model_validator,
21
+ )
15
22
 
16
23
  _FORBIDDEN_SECRET_VALUE_FIELDS = frozenset(
17
24
  {
@@ -98,7 +105,10 @@ class WorkerRuntimeContext(BaseModel):
98
105
 
99
106
  work_record_id: str
100
107
  profile_slug: str
101
- cli_kind: Literal["codex_cli", "claude_code"]
108
+ # Any harness a Profile names, the pattern the Runner's registration already reports
109
+ # (local-agents 12 item 6). Which runtime serves it is the Runner's choice; a kind the
110
+ # Runner does not serve fails closed there.
111
+ cli_kind: Annotated[str, StringConstraints(pattern=r"^[a-z][a-z0-9_]{0,31}$")]
102
112
  repo: str
103
113
  base_branch: str
104
114
  reviewer: str | None = None
@@ -33,6 +33,7 @@ import base64
33
33
  import binascii
34
34
  import hashlib
35
35
  from datetime import timedelta
36
+ from enum import StrEnum
36
37
  from typing import Annotated, Final
37
38
  from uuid import UUID
38
39
 
@@ -43,11 +44,16 @@ __all__ = [
43
44
  "HONEST_STATEMENT",
44
45
  "RENEWAL_INTERVAL",
45
46
  "SEAL_DOMAIN",
47
+ "SIGN_IN_CODE_DOMAIN",
46
48
  "OpenedCredential",
47
49
  "SealedCredential",
50
+ "SealedSignInCode",
51
+ "SignInCodeOutcome",
52
+ "SignInCodeRelay",
48
53
  "SlotDelivery",
49
54
  "delivery_binding",
50
55
  "key_fingerprint",
56
+ "sign_in_code_binding",
51
57
  ]
52
58
 
53
59
  # Domain separation. Version it rather than the wire: a second construction would be a
@@ -55,6 +61,11 @@ __all__ = [
55
61
  # something it misread.
56
62
  SEAL_DOMAIN: Final[str] = "agentic-os/sealed-credential/v1"
57
63
 
64
+ # A one-time sign-in code is sealed to the same Recipient Key but is not a credential slot
65
+ # (local-agents 05): its own prefix means a code can never open as a slot value, nor a slot
66
+ # value as a code.
67
+ SIGN_IN_CODE_DOMAIN: Final[str] = "agentic-os/sign-in-code/v1"
68
+
58
69
  # 22 A7: the Runner regenerates its keypair on this cadence, re-seals every slot to the
59
70
  # new key and destroys the old private half only once every slot reports the new key id.
60
71
  RENEWAL_INTERVAL: Final[timedelta] = timedelta(days=30)
@@ -91,6 +102,21 @@ def delivery_binding(*, contract_id: UUID | str, slot: str, recipient_key_id: st
91
102
  return "|".join((SEAL_DOMAIN, str(contract_id), slot, recipient_key_id)).encode("utf-8")
92
103
 
93
104
 
105
+ def sign_in_code_binding(
106
+ *, contract_id: UUID | str, sign_in_id: str, recipient_key_id: str
107
+ ) -> bytes:
108
+ """The AEAD associated data one sealed sign-in code is bound to (local-agents 05).
109
+
110
+ Bound to the sign-in the Runner started, so a code lifted from one sign-in and
111
+ replayed against another -- or another Contract's -- does not open. The browser builds
112
+ the same bytes in TypeScript; see :func:`delivery_binding` for why this lives here.
113
+ """
114
+
115
+ return "|".join((SIGN_IN_CODE_DOMAIN, str(contract_id), sign_in_id, recipient_key_id)).encode(
116
+ "utf-8"
117
+ )
118
+
119
+
94
120
  def key_fingerprint(public_key: str) -> str:
95
121
  """The Recipient Key's fingerprint, for out-of-band comparison (22 A4).
96
122
 
@@ -169,3 +195,46 @@ class SlotDelivery(BaseModel):
169
195
  delivered: int = Field(ge=0)
170
196
  stale: bool = False
171
197
  host_party: str = ""
198
+
199
+
200
+ SignInId = Annotated[str, StringConstraints(pattern=r"^si_[0-9a-f]{32}$")]
201
+
202
+
203
+ class SealedSignInCode(BaseModel):
204
+ """A one-time browser sign-in code, sealed to the Runner that started the sign-in.
205
+
206
+ Carried on the heartbeat ack once and never in a Temporal payload (local-agents 05):
207
+ the platform relays ciphertext it cannot open and deletes it on delivery or expiry.
208
+ """
209
+
210
+ model_config = ConfigDict(extra="forbid")
211
+
212
+ contract_id: UUID
213
+ sign_in_id: SignInId
214
+ recipient_key_id: KeyId
215
+ ciphertext: Annotated[str, StringConstraints(min_length=1, max_length=2048)]
216
+
217
+ def binding(self) -> bytes:
218
+ return sign_in_code_binding(
219
+ contract_id=self.contract_id,
220
+ sign_in_id=self.sign_in_id,
221
+ recipient_key_id=self.recipient_key_id,
222
+ )
223
+
224
+
225
+ class SignInCodeOutcome(StrEnum):
226
+ """What the Runner did with one relayed code -- the reason the console shows."""
227
+
228
+ WRITTEN = "written"
229
+ UNKNOWN = "unknown"
230
+ EXPIRED = "expired"
231
+ UNOPENABLE = "unopenable"
232
+
233
+
234
+ class SignInCodeRelay(BaseModel):
235
+ """One relayed code's outcome, reported on the next heartbeat. Never the code."""
236
+
237
+ model_config = ConfigDict(extra="forbid")
238
+
239
+ sign_in_id: SignInId
240
+ outcome: SignInCodeOutcome