agentic-runner 3.12.2__tar.gz → 3.14.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 (76) hide show
  1. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/PKG-INFO +1 -1
  2. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/__init__.py +1 -1
  3. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/cli.py +66 -1
  4. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/device_login_activities.py +6 -10
  5. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/sealed_box.py +1 -12
  6. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/service.py +41 -38
  7. agentic_runner-3.14.0/src/agentic_runner/skill.py +58 -0
  8. agentic_runner-3.14.0/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +151 -0
  9. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/claude_sign_in.py +145 -252
  10. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/contract_device_login.py +3 -17
  11. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/.gitignore +0 -0
  12. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/LICENSE +0 -0
  13. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/README.md +0 -0
  14. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/pyproject.toml +0 -0
  15. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/activities.py +0 -0
  16. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/attempts.py +0 -0
  17. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/auth_mode.py +0 -0
  18. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/build.py +0 -0
  19. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/callback.py +0 -0
  20. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/child_watcher.py +0 -0
  21. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/cli_floor.py +0 -0
  22. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/config.py +0 -0
  23. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/credentials.py +0 -0
  24. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/egress.py +0 -0
  25. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/harness_self_test.py +0 -0
  26. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/heartbeat_link.py +0 -0
  27. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/hooks.py +0 -0
  28. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/host_store.py +0 -0
  29. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/__init__.py +0 -0
  30. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
  31. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/contracts.py +0 -0
  32. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
  33. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
  34. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/git/workspace.py +0 -0
  35. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
  36. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/auth.py +0 -0
  37. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
  38. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
  39. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
  40. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
  41. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/lifecycle.py +0 -0
  42. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/llm_proxy.py +0 -0
  43. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/mcp.py +0 -0
  44. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/message_store.py +0 -0
  45. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/oauth_connectors.py +0 -0
  46. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/private_state.py +0 -0
  47. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/py.typed +0 -0
  48. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/recipient_key_secret.py +0 -0
  49. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/registration.py +0 -0
  50. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/runtime/__init__.py +0 -0
  51. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
  52. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/__init__.py +0 -0
  53. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/__main__.py +0 -0
  54. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/control_plane.py +0 -0
  55. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/plugin.py +0 -0
  56. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/test_conformance.py +0 -0
  57. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/testing/workflow.py +0 -0
  58. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/tiny_http.py +0 -0
  59. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/triage_activities.py +0 -0
  60. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/usage_windows.py +0 -0
  61. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/user_sources.py +0 -0
  62. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/__init__.py +0 -0
  63. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
  64. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/acp_runtime.py +0 -0
  65. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
  66. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
  67. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
  68. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/command_policy.py +0 -0
  69. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
  70. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/fastapi_client.py +0 -0
  71. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/harness_outcome.py +0 -0
  72. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/harness_usage.py +0 -0
  73. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/mcp_config.py +0 -0
  74. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/settings.py +0 -0
  75. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workers/skills.py +0 -0
  76. {agentic_runner-3.12.2 → agentic_runner-3.14.0}/src/agentic_runner/workstation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentic-runner
3
- Version: 3.12.2
3
+ Version: 3.14.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,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.12.2"
10
+ __version__ = "3.14.0"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -15,6 +15,13 @@ the Organisation's cap.
15
15
  Runner (PRD issue 47, ``workstation.py``): one login agent per Organisation, managed by
16
16
  the user who hosts it, with no ``sudo`` anywhere.
17
17
 
18
+ ``sign-in --contract <id>`` signs one Contract's Claude Code in with Anthropic's own flow,
19
+ in this terminal, as that Contract's uid in its harness root (QTS-1448). No code or token
20
+ passes through the control plane.
21
+
22
+ ``skill install | print`` hands the setup skill to the person's own Claude Code or Codex
23
+ (``skill.py``, console-v2 issue 21).
24
+
18
25
  The remaining subcommands are what a Directive calls back with, over its own attempt
19
26
  socket (``callback.py``): ``annotate``, ``artifact upload``, ``verb`` and ``message send |
20
27
  list`` (the Channel seam, PRD issue 52) and ``ask`` (a Question, issue 60). They read the
@@ -33,9 +40,10 @@ import os
33
40
  import sys
34
41
  from collections.abc import Sequence
35
42
  from pathlib import Path
43
+ from uuid import UUID
36
44
 
37
45
  from agentic_runner import __version__ as runner_version
38
- from agentic_runner import service, workstation
46
+ from agentic_runner import service, skill, workstation
39
47
  from agentic_runner.callback import (
40
48
  CALLBACK_SOCKET_ENV,
41
49
  CALLBACK_TOKEN_ENV,
@@ -56,6 +64,7 @@ from agentic_runner.service import (
56
64
  TAGS_ENV,
57
65
  parse_tags,
58
66
  )
67
+ from agentic_runner.workers.claude_sign_in import OAUTH_TOKEN, SIGN_IN_METHODS
59
68
  from agentic_runner_contracts import __version__ as contracts_version
60
69
  from agentic_runner_contracts.runner_registration import (
61
70
  HostAttestation,
@@ -195,6 +204,21 @@ def main(argv: Sequence[str] | None = None) -> int:
195
204
  status.add_argument("org", nargs="?", help="a workstation Organisation (issue 47)")
196
205
  status.add_argument("--root", default=None, type=Path)
197
206
 
207
+ sign_in = subcommands.add_parser(
208
+ "sign-in",
209
+ help="sign a Contract's Claude Code in with Anthropic's own flow, in this terminal",
210
+ )
211
+ sign_in.add_argument("--contract", required=True, help="the Contract's id")
212
+ sign_in.add_argument(
213
+ "--method",
214
+ default=OAUTH_TOKEN,
215
+ choices=SIGN_IN_METHODS,
216
+ help="oauth_token: a long-lived token (claude setup-token); claude_ai: a Claude.ai "
217
+ "login (claude auth login --claudeai)",
218
+ )
219
+ sign_in.add_argument("--org", default=None, help="a workstation Organisation (issue 47)")
220
+ sign_in.add_argument("--root", default=None, type=Path, help=argparse.SUPPRESS)
221
+
198
222
  annotate = subcommands.add_parser("annotate", help="attach an annotation to this Work Record")
199
223
  annotate.add_argument("--context", default="default")
200
224
  annotate.add_argument(
@@ -250,6 +274,18 @@ def main(argv: Sequence[str] | None = None) -> int:
250
274
  "--base", default="", help="`branch` only: the base branch; default the repository's"
251
275
  )
252
276
 
277
+ skill_parser = subcommands.add_parser(
278
+ "skill", help="the setup skill for your own Claude Code or Codex (console-v2 issue 21)"
279
+ )
280
+ skill_parser.add_argument("action", choices=("install", "print"))
281
+ only = skill_parser.add_mutually_exclusive_group()
282
+ only.add_argument(
283
+ "--claude", dest="targets", action="store_const", const=("claude",), help="Claude only"
284
+ )
285
+ only.add_argument(
286
+ "--codex", dest="targets", action="store_const", const=("codex",), help="Codex only"
287
+ )
288
+
253
289
  subcommands.add_parser("config", help="print the resolved configuration (flag > env > file)")
254
290
 
255
291
  arguments = parser.parse_args(argv)
@@ -288,6 +324,15 @@ def main(argv: Sequence[str] | None = None) -> int:
288
324
  for line in lines:
289
325
  print(line)
290
326
  return 0
327
+ if arguments.command == "sign-in":
328
+ return _sign_in(arguments)
329
+ if arguments.command == "skill":
330
+ if arguments.action == "print":
331
+ sys.stdout.write(skill.text())
332
+ return 0
333
+ for path in skill.install(arguments.targets or skill.TARGETS):
334
+ print(f"installed {path}")
335
+ return 0
291
336
  if arguments.command == "config":
292
337
  config = load_config(config_file=arguments.config, hooks_path=arguments.hooks_path)
293
338
  print(config.model_dump_json(indent=2))
@@ -344,6 +389,26 @@ def _image_attestation() -> HostAttestation:
344
389
  )
345
390
 
346
391
 
392
+ def _sign_in(arguments: argparse.Namespace) -> int:
393
+ try:
394
+ contract_id = str(UUID(arguments.contract))
395
+ except ValueError:
396
+ print(f"--contract expects a Contract id, got {arguments.contract!r}", file=sys.stderr)
397
+ return 2
398
+ if not (os.isatty(0) and os.isatty(1)):
399
+ print(
400
+ "sign-in is interactive: run it in a terminal (`kubectl exec -it`, `docker exec -it`)",
401
+ file=sys.stderr,
402
+ )
403
+ return 2
404
+ if arguments.org:
405
+ paths = _org_paths(arguments)
406
+ os.environ.update(
407
+ workstation.run_environment(paths, workstation.WorkstationSettings.load(paths))
408
+ )
409
+ return asyncio.run(service.sign_in_claude(contract_id, method=arguments.method))
410
+
411
+
347
412
  def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
348
413
  if not arguments.control_plane:
349
414
  print(f"--control-plane (or {CONTROL_PLANE_ENV}) is required", file=sys.stderr)
@@ -14,7 +14,6 @@ from temporalio import activity
14
14
  from temporalio.exceptions import ApplicationError
15
15
 
16
16
  from agentic_runner.auth_mode import SHARED_RUNNER_SIGN_IN_RULE, is_shared_runner
17
- from agentic_runner.workers.claude_sign_in import OAUTH_TOKEN
18
17
  from agentic_runner.workers.contract_device_login import ContractDeviceLogin
19
18
  from agentic_runner.workers.contract_isolation import ContractIsolation
20
19
  from agentic_runner_contracts.activity_io import (
@@ -37,8 +36,9 @@ class ContractDeviceLoginActivities:
37
36
  host_party: str | None = None,
38
37
  ) -> None:
39
38
  self._contract_isolation = contract_isolation
40
- # The party this process was registered as hosted by. A shared Runner signs in only
41
- # a Claude Code long-lived token (local-agents 04, amended by 21).
39
+ # The party this process was registered as hosted by. A shared Runner starts no
40
+ # device login and reports only Claude Code's sign-in (local-agents 04, amended by
41
+ # 21); a person signs Claude Code in on the Runner itself (QTS-1448).
42
42
  self._host_party = host_party
43
43
  # Injectable for tests; None falls through to the real vendor CLIs.
44
44
  self._device_login = device_login or ContractDeviceLogin(contract_isolation)
@@ -53,12 +53,9 @@ class ContractDeviceLoginActivities:
53
53
  async def sign_in_contract_device_login(
54
54
  self, request: ContractDeviceLoginInput
55
55
  ) -> ContractDeviceLoginResult:
56
- long_lived = request.runtime_kind == "claude_code" and (request.method or OAUTH_TOKEN) == (
57
- OAUTH_TOKEN
58
- )
59
- self._refuse_on_shared_runner(allow=long_lived)
56
+ self._refuse_on_shared_runner()
60
57
  prompt = await self._device_login.sign_in(
61
- request.contract_id, runtime_kind=request.runtime_kind, method=request.method
58
+ request.contract_id, runtime_kind=request.runtime_kind
62
59
  )
63
60
  return ContractDeviceLoginResult(
64
61
  contract_id=prompt.contract_id,
@@ -66,7 +63,6 @@ class ContractDeviceLoginActivities:
66
63
  verification_uri=prompt.verification_uri,
67
64
  user_code=prompt.user_code,
68
65
  expires_at=prompt.expires_at.isoformat(),
69
- sign_in_id=prompt.sign_in_id,
70
66
  )
71
67
 
72
68
  @activity.defn(name="check_contract_device_login_status")
@@ -107,7 +103,7 @@ class ContractDeviceLoginActivities:
107
103
  on here, so the failure the platform's sign-in workflow receives names it.
108
104
 
109
105
  Only a Runner known to be the person's own signs in, except that a shared Runner
110
- may make (``allow``) a Claude Code long-lived token (local-agents 21). One whose
106
+ may report (``allow``) a Claude Code long-lived token (local-agents 21). One whose
111
107
  state names no host party (registered before issue 42) is refused either way, as
112
108
  `choose_auth_mode` never runs a subscription on it."""
113
109
 
@@ -63,7 +63,6 @@ from agentic_runner_contracts.sealed_credential import (
63
63
  SEAL_DOMAIN,
64
64
  OpenedCredential,
65
65
  SealedCredential,
66
- SealedSignInCode,
67
66
  delivery_binding,
68
67
  key_fingerprint,
69
68
  )
@@ -554,20 +553,10 @@ class SealedCredentialStream:
554
553
  if not self._awaiting:
555
554
  self._keys.retire_previous()
556
555
 
557
- def open_sign_in_code(self, sealed: SealedSignInCode) -> str | None:
558
- """A relayed sign-in code, or None when it does not open (local-agents 05).
559
-
560
- Opened with the same Recipient Key as a slot, but never held: the caller types it
561
- into the waiting CLI and drops it.
562
- """
563
-
564
- return self._open_with(sealed.recipient_key_id, sealed.binding(), sealed.ciphertext)
565
-
566
556
  def open_oauth_code(self, sealed: SealedOAuthCode) -> str | None:
567
557
  """A relayed OAuth code, or None when it does not open (console-v2 issue 30).
568
558
 
569
- Never held, like a sign-in code: the caller spends it on the token endpoint and
570
- drops it.
559
+ Never held: the caller spends it on the token endpoint and drops it.
571
560
  """
572
561
 
573
562
  return self._open_with(sealed.recipient_key_id, sealed.binding(), sealed.ciphertext)
@@ -24,6 +24,7 @@ import logging
24
24
  import os
25
25
  import shutil
26
26
  import signal
27
+ import sys
27
28
  import tempfile
28
29
  import time
29
30
  from collections.abc import Awaitable, Callable, Mapping, Sequence
@@ -34,6 +35,7 @@ from typing import Any, Protocol
34
35
  from uuid import UUID
35
36
 
36
37
  import httpx
38
+ from pydantic import ValidationError
37
39
  from temporalio import activity
38
40
  from temporalio.client import Client
39
41
  from temporalio.worker import (
@@ -110,7 +112,7 @@ from agentic_runner.user_sources import ProxyTriage, UserSourcePoller
110
112
  from agentic_runner.workers.acp_runtime import ACP_BRIDGES, AcpRuntime, profile_acp_executables
111
113
  from agentic_runner.workers.agent_runtime import AgentRuntime
112
114
  from agentic_runner.workers.claude_runtime import ClaudeRuntime
113
- from agentic_runner.workers.claude_sign_in import ClaudeSignIns
115
+ from agentic_runner.workers.claude_sign_in import ClaudeSignInError, ClaudeSignIns
114
116
  from agentic_runner.workers.codex_runtime import CodexRuntime
115
117
  from agentic_runner.workers.contract_device_login import ContractDeviceLogin
116
118
  from agentic_runner.workers.contract_isolation import UID_MAP_FILENAME, ContractIsolation
@@ -135,11 +137,7 @@ from agentic_runner_contracts.runner_registration import (
135
137
  ResourcePressure,
136
138
  )
137
139
  from agentic_runner_contracts.runtime_context import WorkerRuntimeContextResolver
138
- from agentic_runner_contracts.sealed_credential import (
139
- SignInCodeOutcome,
140
- SignInCodeRelay,
141
- key_fingerprint,
142
- )
140
+ from agentic_runner_contracts.sealed_credential import key_fingerprint
143
141
 
144
142
  __all__ = [
145
143
  "AGENT_TOKEN_ENV",
@@ -166,6 +164,7 @@ __all__ = [
166
164
  "run",
167
165
  "run_runner_lifecycle_hook",
168
166
  "run_worker_until_terminated",
167
+ "sign_in_claude",
169
168
  "with_profile_acp_cli_kinds",
170
169
  ]
171
170
 
@@ -657,13 +656,8 @@ class ControlPlaneStream:
657
656
  usage_windows: UsageWindows | None = None
658
657
  # Local-agents 04b: which delivered references fill the LLM proxy's slot, and where.
659
658
  llm_slots: Mapping[str, LlmProvider] = field(default_factory=lambda: dict(LLM_SLOT_REFERENCES))
660
- # Local-agents 05: the Claude Code sign-ins a relayed code is typed into.
661
- sign_ins: ClaudeSignIns | None = None
662
659
  # Console-v2 issue 30: the OAuth Connectors whose code, tokens and verifier stay here.
663
660
  oauth: OAuthConnectors | None = None
664
- # Reported once, on the beat after the relay: a lost beat loses the outcome, and the
665
- # console's own wait on the sign-in is what tells the person.
666
- _sign_in_outcomes: list[SignInCodeRelay] = field(default_factory=list)
667
661
  _delivered: set[tuple[str, str]] = field(default_factory=set)
668
662
  _offered_to_proxy: dict[tuple[str, str], str] = field(default_factory=dict)
669
663
  _last_seen: tuple[float, datetime] | None = None
@@ -773,17 +767,14 @@ class ControlPlaneStream:
773
767
  clock_skew_seconds=self._clock_skew(now),
774
768
  source_status=self.sources.statuses() if self.sources is not None else [],
775
769
  tool_servers=self.tool_servers.latest() if self.tool_servers is not None else [],
776
- sign_in_codes=self._sign_in_outcomes,
777
770
  oauth_authorizations=self.oauth.take_authorizations() if self.oauth else [],
778
771
  oauth_tokens=self.oauth.uploads() if self.oauth else [],
779
772
  oauth_outcomes=self.oauth.take_outcomes() if self.oauth else [],
780
773
  )
781
- self._sign_in_outcomes = []
782
774
  ack = await self.client.heartbeat(self.state, envelope)
783
775
  if self.sources is not None:
784
776
  self.sources.assign(ack.user_sources)
785
777
  await self._deliver_transcripts(ack)
786
- await self._relay_sign_in_codes(ack)
787
778
  if self.oauth is not None:
788
779
  # Before the sealed values are applied: a Disconnect revokes with the tokens
789
780
  # this Runner holds, and the same ack may already have withdrawn their ciphertext.
@@ -836,28 +827,6 @@ class ControlPlaneStream:
836
827
  "transcript %s could not be delivered: %s", request.request_id, error
837
828
  )
838
829
 
839
- async def _relay_sign_in_codes(self, ack: HeartbeatAck) -> None:
840
- """Type each relayed code into the sign-in it names (local-agents 05).
841
-
842
- The code is opened, written to that sign-in's PTY and dropped here; nothing logs
843
- it and only its outcome travels back.
844
- """
845
-
846
- for sealed in ack.sign_in_codes:
847
- code = self.sealed.open_sign_in_code(sealed)
848
- if code is None:
849
- outcome = SignInCodeOutcome.UNOPENABLE
850
- elif self.sign_ins is None:
851
- outcome = SignInCodeOutcome.UNKNOWN
852
- else:
853
- outcome = await self.sign_ins.relay(
854
- str(sealed.contract_id), sealed.sign_in_id, code
855
- )
856
- del code
857
- self._sign_in_outcomes.append(
858
- SignInCodeRelay(sign_in_id=sealed.sign_in_id, outcome=outcome)
859
- )
860
-
861
830
  def _sync_delivered(self) -> None:
862
831
  """The resolver holds exactly what the last ack let this installation open."""
863
832
 
@@ -1021,6 +990,42 @@ async def run(
1021
990
  await readiness.close()
1022
991
 
1023
992
 
993
+ async def sign_in_claude(contract_id: str, *, method: str) -> int:
994
+ """``agentic-runner sign-in``: Claude Code's own flow for one Contract, in this terminal.
995
+
996
+ Runs with the Runner's environment (``kubectl exec``, or ``--org`` on a workstation),
997
+ so the Contract gets the uid and the harness root its Directives already run under.
998
+ Nothing reaches the control plane: the console only reads the outcome back through
999
+ ``claude auth status`` (QTS-1448).
1000
+ """
1001
+
1002
+ mode = isolation_mode()
1003
+ can = can_separate_uids()
1004
+ try:
1005
+ require_isolation_supported(mode, can_separate_uids=can)
1006
+ except RunnerRegistrationError as error:
1007
+ print(f"refusing to sign in ({error.reason}): {error}", file=sys.stderr)
1008
+ return 1
1009
+ control_plane = os.environ.get(CONTROL_PLANE_ENV, "").strip()
1010
+ if control_plane:
1011
+ os.environ.setdefault("INTERNAL_FASTAPI_BASE_URL", control_plane)
1012
+ try:
1013
+ settings = get_worker_settings()
1014
+ except ValidationError:
1015
+ print(
1016
+ "refusing to sign in: run this with the Runner's environment "
1017
+ "(`--org <org>` on a workstation, `kubectl exec` into the Runner's pod)",
1018
+ file=sys.stderr,
1019
+ )
1020
+ return 2
1021
+ sign_ins = ClaudeSignIns(build_contract_isolation(settings, can_separate_uids=can))
1022
+ try:
1023
+ return await sign_ins.sign_in(contract_id, method=method)
1024
+ except ClaudeSignInError as error:
1025
+ print(f"sign-in failed: {error}", file=sys.stderr)
1026
+ return 1
1027
+
1028
+
1024
1029
  async def _serve(
1025
1030
  *,
1026
1031
  config: RunnerConfig,
@@ -1157,7 +1162,6 @@ async def _serve_registered(
1157
1162
  messages = MessageStore(state_dir / MESSAGES_DIR)
1158
1163
  stream.messages = messages
1159
1164
  sign_ins = ClaudeSignIns(build_contract_isolation(settings, can_separate_uids=can_change_uid))
1160
- stream.sign_ins = sign_ins
1161
1165
  stream.oauth = OAuthConnectors(
1162
1166
  sealed=stream.sealed, provider=HttpOAuthProvider(), credentials=credentials
1163
1167
  )
@@ -1243,7 +1247,6 @@ async def _serve_registered(
1243
1247
  task.cancel()
1244
1248
  with contextlib.suppress(asyncio.CancelledError):
1245
1249
  await task
1246
- await sign_ins.close()
1247
1250
  await run_runner_lifecycle_hook(hooks, HookName.RUNNER_SHUTDOWN)
1248
1251
  await _report_stop(link, lifecycle)
1249
1252
  return 1 if revoked.is_set() else 0
@@ -0,0 +1,58 @@
1
+ """``agentic-runner skill install | print``: the setup skill for the person's own coding CLI.
2
+
3
+ The skill (console-v2 issue 21) is read and run by the person's own Claude Code or Codex,
4
+ so the control plane calls no LLM to onboard anyone (ADR-0010 §4). It ships inside the
5
+ wheel so ``uvx agentic-runner skill install`` needs nothing but PyPI, and the copy a CLI
6
+ reads is always the one that matches the Runner it installs.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from collections.abc import Mapping
13
+ from importlib.resources import files
14
+ from pathlib import Path
15
+ from typing import Literal
16
+
17
+ SKILL_NAME = "agentic-runner-setup"
18
+
19
+ Target = Literal["claude", "codex"]
20
+ TARGETS: tuple[Target, ...] = ("claude", "codex")
21
+
22
+
23
+ def text() -> str:
24
+ return files("agentic_runner").joinpath("skills", SKILL_NAME, "SKILL.md").read_text("utf-8")
25
+
26
+
27
+ def skill_dir(target: Target, *, home: Path, environ: Mapping[str, str]) -> Path:
28
+ """Where each CLI reads a user skill from.
29
+
30
+ Claude Code honours ``CLAUDE_CONFIG_DIR`` for its whole user directory; Codex reads user
31
+ skills from ``~/.agents/skills``, not from ``CODEX_HOME``.
32
+ """
33
+
34
+ if target == "claude":
35
+ root = Path(environ.get("CLAUDE_CONFIG_DIR") or home / ".claude")
36
+ else:
37
+ root = home / ".agents"
38
+ return root / "skills" / SKILL_NAME
39
+
40
+
41
+ def install(
42
+ targets: tuple[Target, ...],
43
+ *,
44
+ home: Path | None = None,
45
+ environ: Mapping[str, str] | None = None,
46
+ ) -> list[Path]:
47
+ written = []
48
+ for target in targets:
49
+ directory = skill_dir(
50
+ target,
51
+ home=home or Path.home(),
52
+ environ=os.environ if environ is None else environ,
53
+ )
54
+ directory.mkdir(parents=True, exist_ok=True)
55
+ path = directory / "SKILL.md"
56
+ path.write_text(text(), encoding="utf-8")
57
+ written.append(path)
58
+ return written
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: agentic-runner-setup
3
+ description: Install and register an Agentic OS Runner on this machine, a Docker host or a Kubernetes cluster, put the Contract's LLM credential in place, and confirm the Runner is online. Use when the person asks to set up, install or connect a Runner, or pastes the prompt from the console's welcome page.
4
+ license: AGPL-3.0-only
5
+ ---
6
+
7
+ # Set up an Agentic OS Runner
8
+
9
+ You help a person who has never run a Runner. A **Runner** is the program that does their
10
+ Agents' work: it runs Codex or Claude Code on a machine they choose and connects out to the
11
+ Agentic OS control plane. It belongs to one **Organisation** (the company the work is for).
12
+
13
+ The install documents in the `qtsone/agentic-runner` repository are the source for every
14
+ command. Do not reconstruct a command from memory: open the section named in each step and
15
+ follow it.
16
+
17
+ ## What the person gives you
18
+
19
+ The prompt they pasted from the console holds these. Ask for any that is missing.
20
+
21
+ - **Organisation**: a short local name for it, lowercase letters, digits and `-` (`acme`).
22
+ - **Control plane URL**: the Runner's `controlPlane.url`, copied from the console's
23
+ Runners page. It is the only control plane address you use, and only as a value you
24
+ pass to the Runner. Never call it or any other control plane address yourself.
25
+ - **Temporal address**: `host:port`, from the same page.
26
+
27
+ The **Agent Token** is not one of them. It is the reusable credential a Runner registers
28
+ with, and the person types it only where the Runner reads it, never into this
29
+ conversation.
30
+
31
+ ## Rules
32
+
33
+ - **Say before you run.** Before each command, show the person the exact command and what
34
+ it does, in one line.
35
+ - **Stop on the first failure.** Show the exact error, link the doc section below that
36
+ covers it, and stop. Do not retry with other flags, do not work around it.
37
+ - **Never ask for the Agent Token or any credential value in this conversation.** Anything
38
+ said here reaches the model vendor and stays in this CLI's history. Every command that
39
+ reads a credential is one the person runs in their own terminal, and the person types the
40
+ value there. If the person pastes a token or a key here anyway, do not use it: tell them
41
+ to revoke it in the console and mint or create a new one.
42
+ - You use only the `agentic-runner` command, the standard tools named in the docs (`uv`,
43
+ `docker`, `kubectl`, `helm`) and read-only checks such as `command -v`.
44
+
45
+ ## Steps
46
+
47
+ ### 1. Look at the machine
48
+
49
+ Run and report, in one short list:
50
+
51
+ ```sh
52
+ uname -sm
53
+ command -v uv python3 docker kubectl helm codex claude
54
+ ```
55
+
56
+ A Runner does its work with **Codex** or **Claude Code** (each is an Agent Runtime). If the
57
+ way the person picks runs on this machine and neither `codex` nor `claude` is found, say
58
+ so and link their install pages
59
+ (https://developers.openai.com/codex/cli, https://code.claude.com/docs/en/setup). You do
60
+ not install them.
61
+
62
+ ### 2. Ask which way
63
+
64
+ Ask the person to pick one, and open the matching document:
65
+
66
+ | Way | When | Document |
67
+ | --- | --- | --- |
68
+ | This machine | A laptop or desktop they use every day. Needs `uv`. | https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md |
69
+ | Docker | A server or always-on machine with Docker. | https://github.com/qtsone/agentic-runner/blob/main/docs/docker.md |
70
+ | Kubernetes | A cluster they run, with `kubectl` and `helm`. | https://github.com/qtsone/agentic-runner/blob/main/docs/helm.md |
71
+
72
+ Read its "Before you start" section with the person, and stop if something on it is
73
+ missing.
74
+
75
+ ### 3. Install and register
76
+
77
+ **This machine.** Follow
78
+ https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md#install. Install the
79
+ Runner, then show the person the command that registers it for the Organisation and ask
80
+ them to run it in their own terminal. The Runner asks them for the Agent Token there,
81
+ without showing it:
82
+
83
+ ```sh
84
+ uv tool install agentic-runner
85
+ agentic-runner install <org> --control-plane <control-plane-url> --temporal-address <temporal-address>
86
+ ```
87
+
88
+ You run the first line. Do not run the second yourself: wait until the person says it
89
+ finished, and ask for its last lines of output.
90
+
91
+ **Docker.** Follow
92
+ https://github.com/qtsone/agentic-runner/blob/main/docs/docker.md#the-env-file and then
93
+ https://github.com/qtsone/agentic-runner/blob/main/docs/docker.md#docker-run. Create
94
+ `runner.env` with the URL and the address filled in, and ask the person to add the Agent
95
+ Token line to it themselves.
96
+
97
+ **Kubernetes.** Follow
98
+ https://github.com/qtsone/agentic-runner/blob/main/docs/helm.md#install. The person creates
99
+ the token Secret themselves (the `read -rs` lines in that section). You run the `helm`
100
+ command.
101
+
102
+ `registration refused (…)` means the token is revoked or expired, the Account is on hold,
103
+ or the Organisation has reached its Runner limit: the person mints a new token in the
104
+ console. Other errors: the "Check it" section of the same document.
105
+
106
+ ### 4. Put the Contract's LLM credential in place
107
+
108
+ A **Contract** is the agreement under which the person works for the Organisation. It says
109
+ whose LLM key or subscription the Agents spend. Ask which of the two the Contract uses.
110
+
111
+ - **An API key (a Credential Reference).** A Credential Reference is the name of a secret
112
+ whose value lives only on the Runner. On this machine, ask the person to run, in their own
113
+ terminal, the line from
114
+ https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md#operate:
115
+
116
+ ```sh
117
+ printf %s "$VALUE" | agentic-runner credential set <org> <reference>
118
+ ```
119
+
120
+ For Docker and Kubernetes the value is delivered from the console, sealed to the Runner:
121
+ https://github.com/qtsone/agentic-runner/blob/main/docs/helm.md#the-recipient-key.
122
+ - **A Codex or Claude Code subscription.** This works only on a Runner the person hosts
123
+ themselves. For Codex, send them to the Contract's page in the console and its
124
+ **Connect** button. For Claude Code, ask the person to run, in their own terminal, the
125
+ sign-in line for their way of installing: `agentic-runner sign-in --org <org> --contract
126
+ <contract-id>` on this machine, or the `docker exec -it` / `kubectl exec -it` line in
127
+ docker.md or helm.md. See
128
+ https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md#sign-in-to-codex-and-claude-code.
129
+ The sign-in is the person's own, with their own Anthropic account: you never run it, and
130
+ you do not run `codex login` or `claude auth login`.
131
+
132
+ ### 5. Wait for the Runner to come online
133
+
134
+ On this machine, run every 10 seconds, for at most 3 minutes:
135
+
136
+ ```sh
137
+ agentic-runner status <org>
138
+ ```
139
+
140
+ The Runner is online when `process` says `running` and `heartbeat` shows a number of
141
+ seconds, not `never`. For Docker and Kubernetes, use the commands in the document's "Check
142
+ it" section. If 3 minutes pass, stop and show the last output and the "Check it" link.
143
+
144
+ ### 6. Report
145
+
146
+ One screen, nothing more:
147
+
148
+ - the way, the Organisation and the Runner's identity line from `status`;
149
+ - whether the LLM credential is in place, or the one thing the person still does in the
150
+ console;
151
+ - the `next` line `status` prints: where in the console to see the Runner and give it work.