agentic-runner 3.26.1__tar.gz → 3.27.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.
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/PKG-INFO +4 -4
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/README.md +2 -2
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/pyproject.toml +1 -1
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/cli.py +52 -34
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/service.py +18 -2
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +10 -7
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workstation.py +65 -59
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/.gitignore +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/LICENSE +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/activities.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/auth_mode.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/callback.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/contracts.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/workspace.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/launch.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/llm_contracts.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/pool_key.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/runner_limit.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/sealed_box.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/sign_in.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/skill.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/acp_runtime.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/claude_sign_in.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/fastapi_client.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/harness_logins.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/settings.py +0 -0
- {agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/skills.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentic-runner
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.27.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==3.
|
|
8
|
+
Requires-Dist: agentic-runner-contracts==3.27.0
|
|
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
|
|
@@ -46,8 +46,8 @@ It ships as `ghcr.io/qtsone/agentic-runner` (`Dockerfile.runner` at the reposito
|
|
|
46
46
|
root: Python, git, `codex`, `claude`, nothing else) and installs with
|
|
47
47
|
`charts/agentic-runner`.
|
|
48
48
|
|
|
49
|
-
On a workstation it is a per-user login agent, one per
|
|
50
|
-
`agentic-runner install | start | stop | status <
|
|
49
|
+
On a workstation it is a per-user login agent, one per Runner:
|
|
50
|
+
`agentic-runner install | start | stop | status <name>` (LaunchAgent, systemd user unit or
|
|
51
51
|
logon task; never `sudo`), with Credential References in the OS credential store.
|
|
52
52
|
|
|
53
53
|
Licensed under AGPL-3.0-only.
|
|
@@ -27,8 +27,8 @@ It ships as `ghcr.io/qtsone/agentic-runner` (`Dockerfile.runner` at the reposito
|
|
|
27
27
|
root: Python, git, `codex`, `claude`, nothing else) and installs with
|
|
28
28
|
`charts/agentic-runner`.
|
|
29
29
|
|
|
30
|
-
On a workstation it is a per-user login agent, one per
|
|
31
|
-
`agentic-runner install | start | stop | status <
|
|
30
|
+
On a workstation it is a per-user login agent, one per Runner:
|
|
31
|
+
`agentic-runner install | start | stop | status <name>` (LaunchAgent, systemd user unit or
|
|
32
32
|
logon task; never `sudo`), with Credential References in the OS credential store.
|
|
33
33
|
|
|
34
34
|
Licensed under AGPL-3.0-only.
|
|
@@ -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.
|
|
13
|
+
"agentic-runner-contracts==3.27.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.
|
|
@@ -11,9 +11,10 @@ namespace and queue this process was assigned. It is idempotent by state file --
|
|
|
11
11
|
restart re-reads what is already there rather than registering a second Runner against
|
|
12
12
|
the Organisation's cap.
|
|
13
13
|
|
|
14
|
-
``install | start | stop | status <
|
|
15
|
-
Runner (PRD issue 47, ``workstation.py``): one login agent per
|
|
16
|
-
|
|
14
|
+
``install | start | stop | status <name>`` and ``credential set`` are the workstation
|
|
15
|
+
Runner (PRD issue 47, ``workstation.py``): one login agent per Runner, managed by the user
|
|
16
|
+
who hosts it, with no ``sudo`` anywhere. ``<name>`` is the Runner's, the one the console
|
|
17
|
+
lists it under (QTS-1773).
|
|
17
18
|
|
|
18
19
|
``sign-in [claude|codex]`` gives a person's own Runner their LLM access: their subscription,
|
|
19
20
|
through the vendor's own sign-in in this terminal, or an API key stored in the Runner's
|
|
@@ -102,6 +103,18 @@ __all__ = [
|
|
|
102
103
|
STATE_DIR_ENV = "AGENTIC_RUNNER_STATE_DIR"
|
|
103
104
|
|
|
104
105
|
|
|
106
|
+
_NAME_HELP = (
|
|
107
|
+
"this Runner's name: the console lists it under this name, and every command on this "
|
|
108
|
+
"machine takes it"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _old_org_flag(parser: argparse.ArgumentParser) -> None:
|
|
113
|
+
# A login agent written before QTS-1773 runs `run --org <name>` and is not rewritten
|
|
114
|
+
# until its next `install`, so the old spelling keeps parsing, unlisted.
|
|
115
|
+
parser.add_argument("--org", dest="name", help=argparse.SUPPRESS)
|
|
116
|
+
|
|
117
|
+
|
|
105
118
|
def _state_dir() -> Path:
|
|
106
119
|
return load_config().state_dir
|
|
107
120
|
|
|
@@ -158,14 +171,15 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
158
171
|
)
|
|
159
172
|
|
|
160
173
|
run = subcommands.add_parser("run", help="register if needed, then heartbeat and poll for work")
|
|
161
|
-
run.add_argument("--
|
|
174
|
+
run.add_argument("--name", default=None, help="run an installed workstation Runner")
|
|
175
|
+
_old_org_flag(run)
|
|
162
176
|
run.add_argument("--root", default=None, type=Path, help=argparse.SUPPRESS)
|
|
163
177
|
run.add_argument("--login-agent", action="store_true", help=argparse.SUPPRESS)
|
|
164
178
|
|
|
165
179
|
install = subcommands.add_parser(
|
|
166
|
-
"install", help="register a workstation Runner
|
|
180
|
+
"install", help="register a workstation Runner and install its login agent (issue 47)"
|
|
167
181
|
)
|
|
168
|
-
install.add_argument("
|
|
182
|
+
install.add_argument("name", help=_NAME_HELP)
|
|
169
183
|
install.add_argument(
|
|
170
184
|
"--control-plane", default=os.environ.get(CONTROL_PLANE_ENV), help="control plane URL"
|
|
171
185
|
)
|
|
@@ -195,16 +209,16 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
195
209
|
install.add_argument("--root", default=None, type=Path, help="state root (default per OS)")
|
|
196
210
|
for service_verb in ("start", "stop"):
|
|
197
211
|
driven = subcommands.add_parser(
|
|
198
|
-
service_verb, help=f"{service_verb} one
|
|
212
|
+
service_verb, help=f"{service_verb} one workstation Runner's login agent"
|
|
199
213
|
)
|
|
200
|
-
driven.add_argument("
|
|
214
|
+
driven.add_argument("name", help=_NAME_HELP)
|
|
201
215
|
driven.add_argument("--root", default=None, type=Path)
|
|
202
216
|
|
|
203
217
|
credential = subcommands.add_parser(
|
|
204
218
|
"credential", help="install a Credential Reference value into this host's store"
|
|
205
219
|
)
|
|
206
220
|
credential.add_argument("action", choices=("set",))
|
|
207
|
-
credential.add_argument("
|
|
221
|
+
credential.add_argument("name", help=_NAME_HELP)
|
|
208
222
|
credential.add_argument("reference")
|
|
209
223
|
credential.add_argument("--root", default=None, type=Path)
|
|
210
224
|
|
|
@@ -219,7 +233,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
219
233
|
status = subcommands.add_parser(
|
|
220
234
|
"status", help="print this installation's versions and Recipient Key fingerprint"
|
|
221
235
|
)
|
|
222
|
-
status.add_argument("
|
|
236
|
+
status.add_argument("name", nargs="?", help="a workstation Runner's name (issue 47)")
|
|
223
237
|
status.add_argument("--root", default=None, type=Path)
|
|
224
238
|
|
|
225
239
|
sign_in = subcommands.add_parser(
|
|
@@ -240,10 +254,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
240
254
|
"claude_ai: a Claude.ai login (claude auth login --claudeai)",
|
|
241
255
|
)
|
|
242
256
|
sign_in.add_argument(
|
|
243
|
-
"--
|
|
257
|
+
"--name",
|
|
244
258
|
default=None,
|
|
245
|
-
help="the workstation
|
|
259
|
+
help="the workstation Runner's name, when this workstation runs more than one",
|
|
246
260
|
)
|
|
261
|
+
_old_org_flag(sign_in)
|
|
247
262
|
sign_in.add_argument("--root", default=None, type=Path, help=argparse.SUPPRESS)
|
|
248
263
|
|
|
249
264
|
annotate = subcommands.add_parser("annotate", help="attach an annotation to this Work Record")
|
|
@@ -332,8 +347,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
332
347
|
return 1
|
|
333
348
|
return 0
|
|
334
349
|
if arguments.command == "run":
|
|
335
|
-
if arguments.
|
|
336
|
-
return workstation.run_process(
|
|
350
|
+
if arguments.name:
|
|
351
|
+
return workstation.run_process(
|
|
352
|
+
_runner_paths(arguments), login_agent=arguments.login_agent
|
|
353
|
+
)
|
|
337
354
|
return asyncio.run(service.run(attestation=_image_attestation(), install=_run_install()))
|
|
338
355
|
if arguments.command in {"install", "start", "stop", "credential"}:
|
|
339
356
|
return _workstation(arguments)
|
|
@@ -347,7 +364,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
347
364
|
)
|
|
348
365
|
return 0
|
|
349
366
|
if arguments.command == "status":
|
|
350
|
-
lines =
|
|
367
|
+
lines = (
|
|
368
|
+
workstation.status_lines(_runner_paths(arguments)) if arguments.name else status_lines()
|
|
369
|
+
)
|
|
351
370
|
for line in lines:
|
|
352
371
|
print(line)
|
|
353
372
|
return 0
|
|
@@ -370,28 +389,28 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
370
389
|
return _callback(arguments)
|
|
371
390
|
|
|
372
391
|
|
|
373
|
-
def
|
|
374
|
-
return workstation.
|
|
392
|
+
def _runner_paths(arguments: argparse.Namespace) -> workstation.RunnerPaths:
|
|
393
|
+
return workstation.RunnerPaths(
|
|
375
394
|
root=arguments.root or workstation.default_root(),
|
|
376
|
-
|
|
395
|
+
name=workstation.validate_name(arguments.name),
|
|
377
396
|
)
|
|
378
397
|
|
|
379
398
|
|
|
380
399
|
def _workstation(arguments: argparse.Namespace) -> int:
|
|
381
|
-
paths =
|
|
400
|
+
paths = _runner_paths(arguments)
|
|
382
401
|
try:
|
|
383
402
|
if arguments.command == "install":
|
|
384
403
|
return _install(arguments, paths)
|
|
385
404
|
if arguments.command == "start":
|
|
386
405
|
workstation.start(paths)
|
|
387
|
-
print(f"started {paths.
|
|
406
|
+
print(f"started {paths.name}")
|
|
388
407
|
return 0
|
|
389
408
|
if arguments.command == "stop":
|
|
390
409
|
workstation.stop(paths)
|
|
391
|
-
print(f"stopped {paths.
|
|
410
|
+
print(f"stopped {paths.name}; every other Runner on this machine keeps polling")
|
|
392
411
|
return 0
|
|
393
412
|
# The value arrives on stdin so it is never in argv or shell history.
|
|
394
|
-
store = open_workstation_store(paths.
|
|
413
|
+
store = open_workstation_store(paths.name, fallback=paths.credentials)
|
|
395
414
|
store.put(arguments.reference, sys.stdin.read().strip())
|
|
396
415
|
print(f"{arguments.reference} stored in the {store.kind.value} store")
|
|
397
416
|
return 0
|
|
@@ -461,26 +480,26 @@ def _sign_in(arguments: argparse.Namespace) -> int:
|
|
|
461
480
|
)
|
|
462
481
|
return 2
|
|
463
482
|
store: KeyStore
|
|
464
|
-
if arguments.
|
|
483
|
+
if arguments.name or not _in_container():
|
|
465
484
|
try:
|
|
466
|
-
paths =
|
|
485
|
+
paths = _sign_in_paths(arguments)
|
|
467
486
|
except ValueError as error:
|
|
468
487
|
print(error, file=sys.stderr)
|
|
469
488
|
return 2
|
|
470
489
|
os.environ.update(
|
|
471
490
|
workstation.run_environment(paths, workstation.WorkstationSettings.load(paths))
|
|
472
491
|
)
|
|
473
|
-
store = open_workstation_store(paths.
|
|
492
|
+
store = open_workstation_store(paths.name, fallback=paths.credentials)
|
|
474
493
|
else:
|
|
475
494
|
store = service.own_store(load_config())
|
|
476
495
|
return asyncio.run(service.sign_in(arguments.runtime, store=store, method=arguments.method))
|
|
477
496
|
|
|
478
497
|
|
|
479
|
-
def
|
|
480
|
-
"""``--
|
|
498
|
+
def _sign_in_paths(arguments: argparse.Namespace) -> workstation.RunnerPaths:
|
|
499
|
+
"""``--name``, or the one Runner this workstation runs -- no id to type."""
|
|
481
500
|
|
|
482
|
-
if arguments.
|
|
483
|
-
return
|
|
501
|
+
if arguments.name:
|
|
502
|
+
return _runner_paths(arguments)
|
|
484
503
|
root = arguments.root or workstation.default_root()
|
|
485
504
|
installed = (
|
|
486
505
|
sorted(path.name for path in root.iterdir() if (path / "workstation.json").is_file())
|
|
@@ -491,12 +510,12 @@ def _sign_in_org(arguments: argparse.Namespace) -> workstation.OrgPaths:
|
|
|
491
510
|
raise ValueError(
|
|
492
511
|
"no workstation Runner is installed here"
|
|
493
512
|
if not installed
|
|
494
|
-
else f"this workstation runs {', '.join(installed)}: name one with --
|
|
513
|
+
else f"this workstation runs {', '.join(installed)}: name one with --name"
|
|
495
514
|
)
|
|
496
|
-
return workstation.
|
|
515
|
+
return workstation.RunnerPaths(root=root, name=installed[0])
|
|
497
516
|
|
|
498
517
|
|
|
499
|
-
def _install(arguments: argparse.Namespace, paths: workstation.
|
|
518
|
+
def _install(arguments: argparse.Namespace, paths: workstation.RunnerPaths) -> int:
|
|
500
519
|
if not arguments.control_plane:
|
|
501
520
|
print(f"--control-plane (or {CONTROL_PLANE_ENV}) is required", file=sys.stderr)
|
|
502
521
|
return 2
|
|
@@ -518,7 +537,6 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
|
518
537
|
else None
|
|
519
538
|
)
|
|
520
539
|
settings = workstation.WorkstationSettings(
|
|
521
|
-
org=paths.org,
|
|
522
540
|
control_plane_url=arguments.control_plane,
|
|
523
541
|
temporal_address=arguments.temporal_address,
|
|
524
542
|
temporal_tls=not arguments.temporal_plaintext,
|
|
@@ -549,7 +567,7 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
|
549
567
|
return 0
|
|
550
568
|
|
|
551
569
|
|
|
552
|
-
def _acp_cli_kinds(flag: str | None, paths: workstation.
|
|
570
|
+
def _acp_cli_kinds(flag: str | None, paths: workstation.RunnerPaths) -> str:
|
|
553
571
|
# A reinstall to pick up a new PATH or retry a token must not turn ACP off (QTS-1355).
|
|
554
572
|
if flag is not None:
|
|
555
573
|
return workstation.parse_acp_cli_kinds(flag)
|
|
@@ -250,7 +250,7 @@ _VERIFIER_ACTIVITY_BUDGET_SECONDS = 20 * 60
|
|
|
250
250
|
# chart's value and this must move together).
|
|
251
251
|
_SHUTDOWN_HEADROOM_SECONDS = 60
|
|
252
252
|
|
|
253
|
-
# What `agentic-runner status <
|
|
253
|
+
# What `agentic-runner status <name>` reads (PRD issue 47): the live pid, and a file the
|
|
254
254
|
# process touches on every acknowledged heartbeat, whose mtime is the heartbeat age.
|
|
255
255
|
PIDFILE_NAME = "runner.pid"
|
|
256
256
|
HEARTBEAT_STAMP_NAME = "last-heartbeat"
|
|
@@ -849,6 +849,9 @@ class ControlPlaneStream:
|
|
|
849
849
|
# control plane on 3.25 named them, and the LLM access report is sent only after.
|
|
850
850
|
_llm_contracts: list[ContractLlmTerms] | None = None
|
|
851
851
|
_pool: PoolTerms | None = None
|
|
852
|
+
# QTS-1773: the control plane's contracts version, from its ack; None until an ack from
|
|
853
|
+
# one on 3.27 or later, the first to send it and to parse `RunnerInstall.name`.
|
|
854
|
+
_control_plane_contracts: str | None = None
|
|
852
855
|
_last_seen: tuple[float, datetime] | None = None
|
|
853
856
|
|
|
854
857
|
def _observe_sleep(self, now: datetime) -> None:
|
|
@@ -976,6 +979,17 @@ class ControlPlaneStream:
|
|
|
976
979
|
for terms in self._llm_contracts
|
|
977
980
|
]
|
|
978
981
|
|
|
982
|
+
def _install_report(self) -> RunnerInstall | None:
|
|
983
|
+
"""Where a command reaches this Runner, sent once an ack named the Contracts -- a
|
|
984
|
+
control plane below 3.25 refuses the field -- and with its name only once an ack
|
|
985
|
+
came from a control plane on 3.27, since one below refuses that key (QTS-1773)."""
|
|
986
|
+
|
|
987
|
+
if self.install is None or self._llm_contracts is None:
|
|
988
|
+
return None
|
|
989
|
+
if self._control_plane_contracts is None:
|
|
990
|
+
return self.install.model_copy(update={"name": None})
|
|
991
|
+
return self.install
|
|
992
|
+
|
|
979
993
|
def _credential_report(self) -> list[CredentialValidity]:
|
|
980
994
|
"""Per Contract the last ack named, whether each reference would resolve (§7).
|
|
981
995
|
|
|
@@ -1037,7 +1051,7 @@ class ControlPlaneStream:
|
|
|
1037
1051
|
oauth_outcomes=self.oauth.take_outcomes() if self.oauth else [],
|
|
1038
1052
|
runner_limit=self.runner_limit,
|
|
1039
1053
|
llm_access=self._llm_access_report(),
|
|
1040
|
-
install=self.
|
|
1054
|
+
install=self._install_report(),
|
|
1041
1055
|
pool_key_fingerprint=self.pool_key_fingerprint if self._pool is not None else None,
|
|
1042
1056
|
)
|
|
1043
1057
|
ack = await self.client.heartbeat(self.state, envelope)
|
|
@@ -1065,6 +1079,8 @@ class ControlPlaneStream:
|
|
|
1065
1079
|
self._manifests = list(ack.credential_manifests)
|
|
1066
1080
|
self._take_llm_contracts(ack.llm_contracts)
|
|
1067
1081
|
self._take_pool(ack.pool)
|
|
1082
|
+
if ack.contracts_version is not None:
|
|
1083
|
+
self._control_plane_contracts = ack.contracts_version
|
|
1068
1084
|
await self._sync_llm_slots()
|
|
1069
1085
|
if ack.runner_token is not None:
|
|
1070
1086
|
self.runner_token = ack.runner_token
|
|
@@ -8,7 +8,8 @@ license: AGPL-3.0-only
|
|
|
8
8
|
|
|
9
9
|
You help a person who has never run a Runner. A **Runner** is the program that does their
|
|
10
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)
|
|
11
|
+
Agentic OS control plane. It belongs to one **Organisation** (the company the work is for),
|
|
12
|
+
and has a **name** the person chose for it in the console.
|
|
12
13
|
|
|
13
14
|
The install documents in the `qtsone/agentic-runner` repository are the source for every
|
|
14
15
|
command. Do not reconstruct a command from memory: open the section named in each step and
|
|
@@ -18,7 +19,9 @@ follow it.
|
|
|
18
19
|
|
|
19
20
|
The prompt they pasted from the console holds these. Ask for any that is missing.
|
|
20
21
|
|
|
21
|
-
- **
|
|
22
|
+
- **Runner name**: the name the person gave the Runner in the console, lowercase letters,
|
|
23
|
+
digits and `-` (`alice-laptop`). A prompt from an older console calls it **Organisation**;
|
|
24
|
+
it is the same value.
|
|
22
25
|
- **Control plane URL**: the Runner's `controlPlane.url`, copied from the console's
|
|
23
26
|
Runners page. It is the only control plane address you use, and only as a value you
|
|
24
27
|
pass to the Runner. Never call it or any other control plane address yourself.
|
|
@@ -75,13 +78,13 @@ missing.
|
|
|
75
78
|
|
|
76
79
|
**This machine.** Follow
|
|
77
80
|
https://github.com/qtsone/agentic-runner/blob/main/docs/workstation.md#install. Install the
|
|
78
|
-
Runner, then show the person the command that registers it
|
|
81
|
+
Runner, then show the person the command that registers it under its name and ask
|
|
79
82
|
them to run it in their own terminal. The Runner asks them for the Runner Registration Token there,
|
|
80
83
|
without showing it:
|
|
81
84
|
|
|
82
85
|
```sh
|
|
83
86
|
uv tool install agentic-runner
|
|
84
|
-
agentic-runner install <
|
|
87
|
+
agentic-runner install <name> --control-plane <control-plane-url>
|
|
85
88
|
```
|
|
86
89
|
|
|
87
90
|
You run the first line. Do not run the second yourself: wait until the person says it
|
|
@@ -113,7 +116,7 @@ agentic-runner sign-in claude
|
|
|
113
116
|
```
|
|
114
117
|
|
|
115
118
|
`codex` in place of `claude` for Codex; with no argument it asks about each. Add
|
|
116
|
-
`--
|
|
119
|
+
`--name <name>` only if this machine runs more than one Runner. On Docker it is
|
|
117
120
|
`docker exec -it <container> agentic-runner sign-in claude`, on Kubernetes
|
|
118
121
|
`kubectl exec -it <pod> -c runner -- agentic-runner sign-in claude`.
|
|
119
122
|
|
|
@@ -130,7 +133,7 @@ it: it uses the Organisation's keys, which its admin puts in the Runner's own st
|
|
|
130
133
|
On this machine, run every 10 seconds, for at most 3 minutes:
|
|
131
134
|
|
|
132
135
|
```sh
|
|
133
|
-
agentic-runner status <
|
|
136
|
+
agentic-runner status <name>
|
|
134
137
|
```
|
|
135
138
|
|
|
136
139
|
The Runner is online when `process` says `running` and `heartbeat` shows a number of
|
|
@@ -141,7 +144,7 @@ it" section. If 3 minutes pass, stop and show the last output and the "Check it"
|
|
|
141
144
|
|
|
142
145
|
One screen, nothing more:
|
|
143
146
|
|
|
144
|
-
- the way, the
|
|
147
|
+
- the way, the Runner's name and its identity line from `status`;
|
|
145
148
|
- whether the LLM credential is in place, or the one thing the person still does in the
|
|
146
149
|
console;
|
|
147
150
|
- the `next` line `status` prints: where in the console to see the Runner and give it work.
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
"""The workstation Runner: one login agent per
|
|
1
|
+
"""The workstation Runner: one login agent per Runner (PRD issue 47, map ticket 23).
|
|
2
2
|
|
|
3
|
-
``agentic-runner install | start | stop | status <
|
|
3
|
+
``agentic-runner install | start | stop | status <name>``. Everything a CI-runner
|
|
4
4
|
precedent does on a developer machine and nothing a daemon would need:
|
|
5
5
|
|
|
6
|
-
* **One process per
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
* **One process per Runner** (23 item 3), each with its own state directory, identity,
|
|
7
|
+
namespace, Runner Token, hooks, credential store and Workspaces under ``<root>/<name>/``.
|
|
8
|
+
A contractor with two clients runs two. There is no supervisor over N Runners -- that
|
|
9
|
+
is the "one Runner straddling Organisations" 12 A6 rejected.
|
|
10
|
+
* **The name is the Runner's** (QTS-1773): the console lists the Runner under the name it
|
|
11
|
+
was installed as, and every local command takes it. It was an Organisation label before,
|
|
12
|
+
so the name a person chose on /me/runners read as an Organisation; an install made then
|
|
13
|
+
keeps its directory, login agent and keychain entries, all keyed by the same string.
|
|
10
14
|
* **A login agent, never a system daemon** (23 item 1): a LaunchAgent in
|
|
11
15
|
``~/Library/LaunchAgents`` on macOS, a systemd *user* unit (with linger) on Linux, a
|
|
12
16
|
per-user logon task on Windows. No ``sudo`` anywhere: the login keychain is unreachable
|
|
@@ -18,8 +22,8 @@ precedent does on a developer machine and nothing a daemon would need:
|
|
|
18
22
|
under a harness root -- the per-Contract ``CODEX_HOME`` / ``CLAUDE_CONFIG_DIR`` belongs
|
|
19
23
|
to the login the funder created there (issues 30, 31).
|
|
20
24
|
* **``isolation: none``** (17 A2): no root and no uid switch, so the control plane routes
|
|
21
|
-
at most one Contract at a time here (issue 42), which one
|
|
22
|
-
|
|
25
|
+
at most one Contract at a time here (issue 42), which one Contract per personal Runner
|
|
26
|
+
already implied.
|
|
23
27
|
"""
|
|
24
28
|
|
|
25
29
|
from __future__ import annotations
|
|
@@ -54,6 +58,7 @@ from agentic_runner.registration import load_state
|
|
|
54
58
|
from agentic_runner.service import HEARTBEAT_STAMP_NAME, PIDFILE_NAME
|
|
55
59
|
from agentic_runner.workers.acp_runtime import ACP_BRIDGES
|
|
56
60
|
from agentic_runner_contracts.runner_registration import (
|
|
61
|
+
RUNNER_NAME_PATTERN,
|
|
57
62
|
CliVersion,
|
|
58
63
|
EgressPosture,
|
|
59
64
|
HostAttestation,
|
|
@@ -71,7 +76,7 @@ __all__ = [
|
|
|
71
76
|
"CLI_NAMES",
|
|
72
77
|
"ROOT_ENV",
|
|
73
78
|
"CliMissingError",
|
|
74
|
-
"
|
|
79
|
+
"RunnerPaths",
|
|
75
80
|
"ServiceDefinition",
|
|
76
81
|
"WorkstationSettings",
|
|
77
82
|
"cli_versions",
|
|
@@ -88,12 +93,11 @@ __all__ = [
|
|
|
88
93
|
"start",
|
|
89
94
|
"status_lines",
|
|
90
95
|
"stop",
|
|
91
|
-
"
|
|
96
|
+
"validate_name",
|
|
92
97
|
]
|
|
93
98
|
|
|
94
99
|
ROOT_ENV: Final = "AGENTIC_RUNNER_WORKSTATION_ROOT"
|
|
95
100
|
|
|
96
|
-
_ORG = re.compile(r"^[a-z0-9][a-z0-9-]{0,31}$")
|
|
97
101
|
_LABEL_PREFIX: Final = "agentic-runner"
|
|
98
102
|
|
|
99
103
|
Run = Callable[..., subprocess.CompletedProcess[str]]
|
|
@@ -103,18 +107,19 @@ class CliMissingError(RuntimeError):
|
|
|
103
107
|
pass
|
|
104
108
|
|
|
105
109
|
|
|
106
|
-
def
|
|
110
|
+
def validate_name(name: str) -> str:
|
|
107
111
|
"""One path segment, one launchd label, one systemd instance name -- all at once."""
|
|
108
112
|
|
|
109
|
-
if not
|
|
113
|
+
if not re.fullmatch(RUNNER_NAME_PATTERN, name):
|
|
110
114
|
raise ValueError(
|
|
111
|
-
f"
|
|
115
|
+
f"Runner name {name!r} must be 1-32 of lowercase letters, digits and '-', "
|
|
116
|
+
"starting with a letter or digit"
|
|
112
117
|
)
|
|
113
|
-
return
|
|
118
|
+
return name
|
|
114
119
|
|
|
115
120
|
|
|
116
121
|
def default_root(*, platform: str = sys.platform, environ: Mapping[str, str] = os.environ) -> Path:
|
|
117
|
-
"""Where every
|
|
122
|
+
"""Where every Runner's directory lives, per the OS's per-user convention."""
|
|
118
123
|
|
|
119
124
|
if environ.get(ROOT_ENV, "").strip():
|
|
120
125
|
return Path(environ[ROOT_ENV])
|
|
@@ -128,19 +133,19 @@ def default_root(*, platform: str = sys.platform, environ: Mapping[str, str] = o
|
|
|
128
133
|
|
|
129
134
|
|
|
130
135
|
@dataclass(frozen=True)
|
|
131
|
-
class
|
|
132
|
-
"""``<root>/<
|
|
136
|
+
class RunnerPaths:
|
|
137
|
+
"""``<root>/<name>/`` and everything the process keeps under it."""
|
|
133
138
|
|
|
134
139
|
root: Path
|
|
135
|
-
|
|
140
|
+
name: str
|
|
136
141
|
|
|
137
142
|
@property
|
|
138
143
|
def state_dir(self) -> Path:
|
|
139
|
-
return self.root / self.
|
|
144
|
+
return self.root / self.name
|
|
140
145
|
|
|
141
146
|
@property
|
|
142
147
|
def hooks(self) -> Path:
|
|
143
|
-
# Issue 45's path, per map ticket 26: `<state_dir>/<
|
|
148
|
+
# Issue 45's path, per map ticket 26: `<state_dir>/<name>/hooks/`.
|
|
144
149
|
return self.state_dir / "hooks"
|
|
145
150
|
|
|
146
151
|
@property
|
|
@@ -189,7 +194,7 @@ class OrgPaths:
|
|
|
189
194
|
tempfile.gettempdir() if sys.platform == "darwin" else ""
|
|
190
195
|
)
|
|
191
196
|
if runtime:
|
|
192
|
-
return Path(runtime) / f"agentic-runner-{self.
|
|
197
|
+
return Path(runtime) / f"agentic-runner-{self.name}"
|
|
193
198
|
return self.state_dir / "sockets"
|
|
194
199
|
|
|
195
200
|
def create(self) -> None:
|
|
@@ -203,10 +208,10 @@ class WorkstationSettings(BaseModel):
|
|
|
203
208
|
"""What ``install`` was told, so ``run`` and ``status`` need no flags."""
|
|
204
209
|
|
|
205
210
|
# `ignore`, not `forbid`: only `save` writes this file, and an install made before
|
|
206
|
-
# local-agents 01 retired the per-process `cli_kind
|
|
211
|
+
# local-agents 01 retired the per-process `cli_kind`, or before QTS-1773 dropped the
|
|
212
|
+
# `org` its directory already names, still has to `run` and `status`.
|
|
207
213
|
model_config = ConfigDict(extra="ignore", frozen=True)
|
|
208
214
|
|
|
209
|
-
org: str
|
|
210
215
|
control_plane_url: str
|
|
211
216
|
# A fallback only: `run` uses the address the registration response carries (ADR-0022
|
|
212
217
|
# §4). An install made before 3.17 saved one and keeps it.
|
|
@@ -222,15 +227,15 @@ class WorkstationSettings(BaseModel):
|
|
|
222
227
|
# which every `install` rewrites (QTS-1355).
|
|
223
228
|
acp_cli_kinds: str = ""
|
|
224
229
|
|
|
225
|
-
def save(self, paths:
|
|
230
|
+
def save(self, paths: RunnerPaths) -> None:
|
|
226
231
|
paths.settings_file.write_text(self.model_dump_json(indent=2), encoding="utf-8")
|
|
227
232
|
|
|
228
233
|
@classmethod
|
|
229
|
-
def load(cls, paths:
|
|
234
|
+
def load(cls, paths: RunnerPaths) -> WorkstationSettings:
|
|
230
235
|
if not paths.settings_file.is_file():
|
|
231
236
|
raise FileNotFoundError(
|
|
232
|
-
f"no Runner is installed for {paths.
|
|
233
|
-
f"run `agentic-runner install {paths.
|
|
237
|
+
f"no Runner is installed for {paths.name!r} under {paths.root}; "
|
|
238
|
+
f"run `agentic-runner install {paths.name}` first"
|
|
234
239
|
)
|
|
235
240
|
return cls.model_validate_json(paths.settings_file.read_text(encoding="utf-8"))
|
|
236
241
|
|
|
@@ -262,8 +267,8 @@ def install_channel(
|
|
|
262
267
|
return InstallChannel.SYSTEMD_USER
|
|
263
268
|
|
|
264
269
|
|
|
265
|
-
def run_environment(paths:
|
|
266
|
-
"""The environment ``agentic-runner run --
|
|
270
|
+
def run_environment(paths: RunnerPaths, settings: WorkstationSettings) -> dict[str, str]:
|
|
271
|
+
"""The environment ``agentic-runner run --name`` gives ``service.run``.
|
|
267
272
|
|
|
268
273
|
Set here rather than in the service definition so one definition format -- a plist,
|
|
269
274
|
a unit, a task -- stays a one-liner, and the three OSes cannot drift apart.
|
|
@@ -285,7 +290,7 @@ def run_environment(paths: OrgPaths, settings: WorkstationSettings) -> dict[str,
|
|
|
285
290
|
"TEMPORAL_ADDRESS": settings.temporal_address,
|
|
286
291
|
"WORKSPACE_ROOT": str(paths.workspaces),
|
|
287
292
|
"WORKER_STATE_DIR": str(paths.contract_state),
|
|
288
|
-
# Ephemeral: one process per
|
|
293
|
+
# Ephemeral: one process per Runner shares the host, so a fixed readiness
|
|
289
294
|
# port inherited from a pod-style environment would collide on the second one.
|
|
290
295
|
service.READINESS_PORT_ENV: "0",
|
|
291
296
|
}
|
|
@@ -396,16 +401,16 @@ class ServiceDefinition:
|
|
|
396
401
|
stop: list[list[str]]
|
|
397
402
|
|
|
398
403
|
|
|
399
|
-
def runner_command(
|
|
404
|
+
def runner_command(name: str, root: Path) -> list[str]:
|
|
400
405
|
# The console script as found, not resolved: Homebrew's `bin/agentic-runner` is a
|
|
401
406
|
# symlink into a versioned Cellar path that the next `brew upgrade` deletes.
|
|
402
407
|
script = shutil.which("agentic-runner")
|
|
403
408
|
prefix = [script] if script else [sys.executable, "-m", "agentic_runner.launch"]
|
|
404
|
-
return [*prefix, "run", "--
|
|
409
|
+
return [*prefix, "run", "--name", name, "--root", str(root), "--login-agent"]
|
|
405
410
|
|
|
406
411
|
|
|
407
412
|
def service_definition(
|
|
408
|
-
paths:
|
|
413
|
+
paths: RunnerPaths,
|
|
409
414
|
settings: WorkstationSettings,
|
|
410
415
|
*,
|
|
411
416
|
platform: str = sys.platform,
|
|
@@ -415,8 +420,8 @@ def service_definition(
|
|
|
415
420
|
command: Sequence[str] | None = None,
|
|
416
421
|
) -> ServiceDefinition:
|
|
417
422
|
home = home or Path.home()
|
|
418
|
-
argv = list(command or runner_command(paths.
|
|
419
|
-
name = f"{_LABEL_PREFIX}.{paths.
|
|
423
|
+
argv = list(command or runner_command(paths.name, paths.root))
|
|
424
|
+
name = f"{_LABEL_PREFIX}.{paths.name}"
|
|
420
425
|
if platform == "darwin":
|
|
421
426
|
return _launch_agent(paths, settings, argv, name=name, home=home, uid=uid)
|
|
422
427
|
if platform == "win32":
|
|
@@ -425,7 +430,7 @@ def service_definition(
|
|
|
425
430
|
|
|
426
431
|
|
|
427
432
|
def _launch_agent(
|
|
428
|
-
paths:
|
|
433
|
+
paths: RunnerPaths,
|
|
429
434
|
settings: WorkstationSettings,
|
|
430
435
|
argv: list[str],
|
|
431
436
|
*,
|
|
@@ -458,19 +463,19 @@ def _launch_agent(
|
|
|
458
463
|
|
|
459
464
|
|
|
460
465
|
def _systemd_user_unit(
|
|
461
|
-
paths:
|
|
466
|
+
paths: RunnerPaths,
|
|
462
467
|
settings: WorkstationSettings,
|
|
463
468
|
argv: list[str],
|
|
464
469
|
*,
|
|
465
470
|
home: Path,
|
|
466
471
|
user: str | None,
|
|
467
472
|
) -> ServiceDefinition:
|
|
468
|
-
unit_name = f"{_LABEL_PREFIX}-{paths.
|
|
473
|
+
unit_name = f"{_LABEL_PREFIX}-{paths.name}.service"
|
|
469
474
|
unit = home / ".config" / "systemd" / "user" / unit_name
|
|
470
475
|
exec_start = " ".join(_systemd_quote(arg) for arg in argv)
|
|
471
476
|
content = (
|
|
472
477
|
"[Unit]\n"
|
|
473
|
-
f"Description=Agentic OS Runner for {paths.
|
|
478
|
+
f"Description=Agentic OS Runner for {paths.name}\n"
|
|
474
479
|
"After=network-online.target\n\n"
|
|
475
480
|
"[Service]\n"
|
|
476
481
|
f"ExecStart={exec_start}\n"
|
|
@@ -504,16 +509,16 @@ def _systemd_quote(value: str) -> str:
|
|
|
504
509
|
|
|
505
510
|
|
|
506
511
|
def _logon_task(
|
|
507
|
-
paths:
|
|
512
|
+
paths: RunnerPaths, argv: list[str], *, name: str, user: str | None
|
|
508
513
|
) -> ServiceDefinition:
|
|
509
514
|
task_xml = paths.state_dir / f"{name}.task.xml"
|
|
510
|
-
task_name = f"\\agentic-runner\\{paths.
|
|
515
|
+
task_name = f"\\agentic-runner\\{paths.name}"
|
|
511
516
|
who = user or os.environ.get("USERNAME", "")
|
|
512
517
|
arguments = subprocess.list2cmdline(argv[1:])
|
|
513
518
|
content = f"""<?xml version="1.0" encoding="UTF-16"?>
|
|
514
519
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
515
520
|
<RegistrationInfo>
|
|
516
|
-
<Description>Agentic OS Runner for {escape(paths.
|
|
521
|
+
<Description>Agentic OS Runner for {escape(paths.name)}</Description>
|
|
517
522
|
</RegistrationInfo>
|
|
518
523
|
<Triggers>
|
|
519
524
|
<LogonTrigger><Enabled>true</Enabled><UserId>{escape(who)}</UserId></LogonTrigger>
|
|
@@ -564,7 +569,7 @@ def drive(commands: list[list[str]], *, run: Run = subprocess.run) -> None:
|
|
|
564
569
|
# ------------------------------------------------------------------ status
|
|
565
570
|
|
|
566
571
|
|
|
567
|
-
def process_id(paths:
|
|
572
|
+
def process_id(paths: RunnerPaths) -> int | None:
|
|
568
573
|
"""The running process's pid, if the pidfile names a live one."""
|
|
569
574
|
|
|
570
575
|
try:
|
|
@@ -580,7 +585,7 @@ def process_id(paths: OrgPaths) -> int | None:
|
|
|
580
585
|
return pid
|
|
581
586
|
|
|
582
587
|
|
|
583
|
-
def heartbeat_age(paths:
|
|
588
|
+
def heartbeat_age(paths: RunnerPaths, *, now: datetime | None = None) -> float | None:
|
|
584
589
|
try:
|
|
585
590
|
stamped = paths.heartbeat_stamp.stat().st_mtime
|
|
586
591
|
except OSError:
|
|
@@ -588,14 +593,14 @@ def heartbeat_age(paths: OrgPaths, *, now: datetime | None = None) -> float | No
|
|
|
588
593
|
return max(0.0, (now or datetime.now(UTC)).timestamp() - stamped)
|
|
589
594
|
|
|
590
595
|
|
|
591
|
-
def status_lines(paths:
|
|
592
|
-
"""``status <
|
|
596
|
+
def status_lines(paths: RunnerPaths, *, now: datetime | None = None) -> list[str]:
|
|
597
|
+
"""``status <name>``: the process, its identity and its heartbeat age."""
|
|
593
598
|
|
|
594
599
|
pid = process_id(paths)
|
|
595
600
|
state = load_state(paths.state_dir)
|
|
596
601
|
age = heartbeat_age(paths, now=now)
|
|
597
602
|
lines = [
|
|
598
|
-
f"
|
|
603
|
+
f"runner name {paths.name} ({paths.state_dir})",
|
|
599
604
|
f"process {'running, pid ' + str(pid) if pid else 'not running'}",
|
|
600
605
|
(
|
|
601
606
|
f"identity {state.runner_id} in {state.temporal_namespace} on {state.task_queue}"
|
|
@@ -634,7 +639,7 @@ def next_step_lines(host_party: str) -> list[str]:
|
|
|
634
639
|
|
|
635
640
|
|
|
636
641
|
async def install(
|
|
637
|
-
paths:
|
|
642
|
+
paths: RunnerPaths,
|
|
638
643
|
settings: WorkstationSettings,
|
|
639
644
|
*,
|
|
640
645
|
registration_token: str,
|
|
@@ -645,7 +650,7 @@ async def install(
|
|
|
645
650
|
run: Run = subprocess.run,
|
|
646
651
|
http_client: httpx.AsyncClient | None = None,
|
|
647
652
|
) -> list[str]:
|
|
648
|
-
"""Register this
|
|
653
|
+
"""Register this Runner and install its login agent.
|
|
649
654
|
|
|
650
655
|
Registration happens here, once, with the Runner Registration Token this user minted
|
|
651
656
|
on /me/runners or the Organisation's Admin issued them (12 A6, QTS-1304) -- pasted at the
|
|
@@ -696,18 +701,18 @@ async def install(
|
|
|
696
701
|
return lines
|
|
697
702
|
|
|
698
703
|
|
|
699
|
-
def start(paths:
|
|
704
|
+
def start(paths: RunnerPaths, **driver: Any) -> None:
|
|
700
705
|
_drive_service(paths, "start", **driver)
|
|
701
706
|
|
|
702
707
|
|
|
703
|
-
def stop(paths:
|
|
704
|
-
"""Stop this
|
|
708
|
+
def stop(paths: RunnerPaths, **driver: Any) -> None:
|
|
709
|
+
"""Stop this Runner; every other Runner on this machine keeps polling."""
|
|
705
710
|
|
|
706
711
|
_drive_service(paths, "stop", **driver)
|
|
707
712
|
|
|
708
713
|
|
|
709
714
|
def _drive_service(
|
|
710
|
-
paths:
|
|
715
|
+
paths: RunnerPaths,
|
|
711
716
|
verb: str,
|
|
712
717
|
*,
|
|
713
718
|
platform: str = sys.platform,
|
|
@@ -719,8 +724,8 @@ def _drive_service(
|
|
|
719
724
|
drive(definition.start if verb == "start" else definition.stop, run=run)
|
|
720
725
|
|
|
721
726
|
|
|
722
|
-
def run_process(paths:
|
|
723
|
-
"""``agentic-runner run --
|
|
727
|
+
def run_process(paths: RunnerPaths, *, login_agent: bool) -> int:
|
|
728
|
+
"""``agentic-runner run --name``: what the login agent executes."""
|
|
724
729
|
|
|
725
730
|
settings = WorkstationSettings.load(paths)
|
|
726
731
|
try:
|
|
@@ -730,13 +735,14 @@ def run_process(paths: OrgPaths, *, login_agent: bool) -> int:
|
|
|
730
735
|
return 1
|
|
731
736
|
for name, value in run_environment(paths, settings).items():
|
|
732
737
|
os.environ[name] = value
|
|
733
|
-
store = open_workstation_store(paths.
|
|
738
|
+
store = open_workstation_store(paths.name, fallback=paths.credentials)
|
|
734
739
|
attestation = collect_attestation(
|
|
735
740
|
channel=settings.install_channel,
|
|
736
741
|
session=SessionKind.LOGIN_AGENT if login_agent else SessionKind.INTERACTIVE,
|
|
737
742
|
store=store.kind,
|
|
738
743
|
path=settings.path,
|
|
739
744
|
)
|
|
740
|
-
# A workstation's hostname can name its person, so it is never reported (rev 3)
|
|
741
|
-
|
|
745
|
+
# A workstation's hostname can name its person, so it is never reported (rev 3); the
|
|
746
|
+
# name it was installed as is the person's choice, and the console lists it (QTS-1773).
|
|
747
|
+
install = RunnerInstall(target=InstallTarget.WORKSTATION, name=paths.name)
|
|
742
748
|
return asyncio.run(service.run(host_store=store, attestation=attestation, install=install))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/contracts.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/evidence.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/git/workspace.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/github/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/github/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/github/gh_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/integrations/oauth/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/runtime/verifier_command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/testing/test_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/_runtime_support.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/claude_runtime.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/claude_sign_in.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/command_policy.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/contract_isolation.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/fastapi_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/harness_logins.py
RENAMED
|
File without changes
|
{agentic_runner-3.26.1 → agentic_runner-3.27.0}/src/agentic_runner/workers/harness_outcome.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|