agentic-runner 3.18.0__tar.gz → 3.18.2__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.18.0 → agentic_runner-3.18.2}/PKG-INFO +2 -2
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/pyproject.toml +2 -2
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/cli.py +10 -45
- agentic_runner-3.18.2/src/agentic_runner/launch.py +38 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/service.py +109 -20
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workstation.py +1 -48
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/.gitignore +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/LICENSE +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/README.md +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/activities.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/auth_mode.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/callback.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/device_login_activities.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/contracts.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/workspace.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/sealed_box.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/skill.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/acp_runtime.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/claude_sign_in.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/contract_device_login.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/fastapi_client.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/settings.py +0 -0
- {agentic_runner-3.18.0 → agentic_runner-3.18.2}/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.18.
|
|
3
|
+
Version: 3.18.2
|
|
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.18.
|
|
8
|
+
Requires-Dist: agentic-runner-contracts==3.18.2
|
|
9
9
|
Requires-Dist: cryptography<51,>=50
|
|
10
10
|
Requires-Dist: httpx<1.0,>=0.27
|
|
11
11
|
Requires-Dist: pydantic-settings<3.0,>=2.0
|
|
@@ -10,7 +10,7 @@ dependencies = [
|
|
|
10
10
|
# Exactly this release's contracts: the two ship as one version, and an unpinned
|
|
11
11
|
# requirement let `uv tool upgrade` keep an older contracts wheel the Runner cannot
|
|
12
12
|
# import from (QTS-1473). `scripts/set-version.py` moves it with the version.
|
|
13
|
-
"agentic-runner-contracts==3.18.
|
|
13
|
+
"agentic-runner-contracts==3.18.2",
|
|
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.
|
|
@@ -31,7 +31,7 @@ testing = [
|
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[project.scripts]
|
|
34
|
-
agentic-runner = "agentic_runner.
|
|
34
|
+
agentic-runner = "agentic_runner.launch:main"
|
|
35
35
|
|
|
36
36
|
[project.entry-points.pytest11]
|
|
37
37
|
agentic_runner = "agentic_runner.testing.plugin"
|
|
@@ -13,8 +13,7 @@ the Organisation's cap.
|
|
|
13
13
|
|
|
14
14
|
``install | start | stop | status <org>`` and ``credential set`` are the workstation
|
|
15
15
|
Runner (PRD issue 47, ``workstation.py``): one login agent per Organisation, managed by
|
|
16
|
-
the user who hosts it, with no ``sudo`` anywhere.
|
|
17
|
-
beside them, reusing their control plane and ``PATH`` (ADR-0022 §21).
|
|
16
|
+
the user who hosts it, with no ``sudo`` anywhere.
|
|
18
17
|
|
|
19
18
|
``sign-in --contract <id>`` signs one Contract's Claude Code in with Anthropic's own flow,
|
|
20
19
|
in this terminal, as that Contract's uid in its harness root (QTS-1448). No code or token
|
|
@@ -180,21 +179,6 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
180
179
|
)
|
|
181
180
|
install.add_argument("--no-start", action="store_true", help="write the agent, do not start")
|
|
182
181
|
install.add_argument("--root", default=None, type=Path, help="state root (default per OS)")
|
|
183
|
-
|
|
184
|
-
add = subcommands.add_parser(
|
|
185
|
-
"add",
|
|
186
|
-
help="register another Runner under this install, with its own process, identity "
|
|
187
|
-
"and Recipient Key (one per Contract)",
|
|
188
|
-
)
|
|
189
|
-
add.add_argument("org", metavar="name", help="the new Runner's name on this machine")
|
|
190
|
-
add.add_argument(
|
|
191
|
-
"--beside",
|
|
192
|
-
default=None,
|
|
193
|
-
help="the installed Runner whose control plane and PATH to reuse; needed only when "
|
|
194
|
-
"the installed Runners talk to different control planes",
|
|
195
|
-
)
|
|
196
|
-
add.add_argument("--no-start", action="store_true", help="write the agent, do not start")
|
|
197
|
-
add.add_argument("--root", default=None, type=Path, help="state root (default per OS)")
|
|
198
182
|
for service_verb in ("start", "stop"):
|
|
199
183
|
driven = subcommands.add_parser(
|
|
200
184
|
service_verb, help=f"{service_verb} one Organisation's login agent"
|
|
@@ -328,7 +312,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
328
312
|
if arguments.org:
|
|
329
313
|
return workstation.run_process(_org_paths(arguments), login_agent=arguments.login_agent)
|
|
330
314
|
return asyncio.run(service.run(attestation=_image_attestation()))
|
|
331
|
-
if arguments.command in {"install", "
|
|
315
|
+
if arguments.command in {"install", "start", "stop", "credential"}:
|
|
332
316
|
return _workstation(arguments)
|
|
333
317
|
if arguments.command == "recipient-key":
|
|
334
318
|
api, kube_ns = in_cluster()
|
|
@@ -375,8 +359,6 @@ def _workstation(arguments: argparse.Namespace) -> int:
|
|
|
375
359
|
try:
|
|
376
360
|
if arguments.command == "install":
|
|
377
361
|
return _install(arguments, paths)
|
|
378
|
-
if arguments.command == "add":
|
|
379
|
-
return _add(arguments, paths)
|
|
380
362
|
if arguments.command == "start":
|
|
381
363
|
workstation.start(paths)
|
|
382
364
|
print(f"started {paths.org}")
|
|
@@ -440,7 +422,11 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
|
440
422
|
except ValueError as error:
|
|
441
423
|
print(str(error), file=sys.stderr)
|
|
442
424
|
return 2
|
|
443
|
-
|
|
425
|
+
# Pasted, never a flag: a flag is in the process table for every uid on the box.
|
|
426
|
+
token = registration_token_from_env() or getpass.getpass(
|
|
427
|
+
"Runner Registration Token (yours from /me/runners, or the Organisation Admin's for "
|
|
428
|
+
"an Organisation Runner): "
|
|
429
|
+
)
|
|
444
430
|
settings = workstation.WorkstationSettings(
|
|
445
431
|
org=paths.org,
|
|
446
432
|
control_plane_url=arguments.control_plane,
|
|
@@ -450,19 +436,11 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
|
450
436
|
install_channel=workstation.install_channel(),
|
|
451
437
|
acp_cli_kinds=acp_cli_kinds,
|
|
452
438
|
)
|
|
453
|
-
return _register(paths, settings, token, start=not arguments.no_start)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
def _register(
|
|
457
|
-
paths: workstation.OrgPaths,
|
|
458
|
-
settings: workstation.WorkstationSettings,
|
|
459
|
-
token: str,
|
|
460
|
-
*,
|
|
461
|
-
start: bool,
|
|
462
|
-
) -> int:
|
|
463
439
|
try:
|
|
464
440
|
lines = asyncio.run(
|
|
465
|
-
workstation.install(
|
|
441
|
+
workstation.install(
|
|
442
|
+
paths, settings, registration_token=token, start_service=not arguments.no_start
|
|
443
|
+
)
|
|
466
444
|
)
|
|
467
445
|
except (workstation.CliMissingError, RunnerRegistrationError, RuntimeError) as error:
|
|
468
446
|
print(f"install refused: {error}", file=sys.stderr)
|
|
@@ -472,19 +450,6 @@ def _register(
|
|
|
472
450
|
return 0
|
|
473
451
|
|
|
474
452
|
|
|
475
|
-
def _add(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
|
|
476
|
-
settings = workstation.settings_to_add(paths, beside=arguments.beside)
|
|
477
|
-
return _register(paths, settings, _registration_token(), start=not arguments.no_start)
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
def _registration_token() -> str:
|
|
481
|
-
# Pasted, never a flag: a flag is in the process table for every uid on the box.
|
|
482
|
-
return registration_token_from_env() or getpass.getpass(
|
|
483
|
-
"Runner Registration Token (yours from /me/runners, or the Organisation Admin's for "
|
|
484
|
-
"an Organisation Runner): "
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
|
|
488
453
|
def _acp_cli_kinds(flag: str | None, paths: workstation.OrgPaths) -> str:
|
|
489
454
|
# A reinstall to pick up a new PATH or retry a token must not turn ACP off (QTS-1355).
|
|
490
455
|
if flag is not None:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The ``agentic-runner`` console script: a SIGTERM disposition before the CLI imports.
|
|
2
|
+
|
|
3
|
+
Importing the CLI takes seconds (Temporal, httpx, pydantic). In a container the Runner is
|
|
4
|
+
PID 1, and the kernel drops a signal PID 1 has no handler for, so a SIGTERM in that window
|
|
5
|
+
was lost and ``docker stop`` waited out its grace period to SIGKILL (QTS-1536). Nothing
|
|
6
|
+
has run yet, so it exits 0 -- what launchd's ``KeepAlive: SuccessfulExit: false`` reads
|
|
7
|
+
as a clean stop. ``agentic-runner run`` replaces this with its own handler before its
|
|
8
|
+
first await; every other subcommand gets the default disposition back, so a killed
|
|
9
|
+
callback such as ``ask`` never reads as one that succeeded.
|
|
10
|
+
|
|
11
|
+
Imports nothing but the standard library: anything heavier reopens the window.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import signal
|
|
18
|
+
import sys
|
|
19
|
+
from types import FrameType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> int:
|
|
23
|
+
signal.signal(signal.SIGTERM, _exit_before_start)
|
|
24
|
+
from agentic_runner.cli import main as cli_main
|
|
25
|
+
|
|
26
|
+
if sys.argv[1:2] != ["run"]:
|
|
27
|
+
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
|
28
|
+
return cli_main()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _exit_before_start(signum: int, frame: FrameType | None) -> None:
|
|
32
|
+
# Not SystemExit: raised mid-import, it tears down half-initialised modules and the
|
|
33
|
+
# interpreter prints their errors. Nothing has written state yet to flush or close.
|
|
34
|
+
os._exit(0)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__": # pragma: no cover - `runner_command`'s fallback without the script
|
|
38
|
+
raise SystemExit(main())
|
|
@@ -27,7 +27,7 @@ import signal
|
|
|
27
27
|
import sys
|
|
28
28
|
import tempfile
|
|
29
29
|
import time
|
|
30
|
-
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
30
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Mapping, Sequence
|
|
31
31
|
from dataclasses import dataclass, field
|
|
32
32
|
from datetime import UTC, datetime, timedelta
|
|
33
33
|
from pathlib import Path
|
|
@@ -558,12 +558,14 @@ async def run_worker_until_terminated(
|
|
|
558
558
|
Directive is mid-run. The chart's ``preStop`` hook (issue 02) signals termination as
|
|
559
559
|
early as possible; this handler is what turns that into ``Worker.shutdown()`` -- stop
|
|
560
560
|
polling for new work, wait for the in-flight activity, then let ``run()`` return.
|
|
561
|
+
|
|
562
|
+
A caller that passes ``stop`` owns SIGTERM already (``run`` installs it before it
|
|
563
|
+
starts up, QTS-1536); one set before the Worker polls never starts it.
|
|
561
564
|
"""
|
|
562
565
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
try:
|
|
566
|
+
if stop is not None and stop.is_set():
|
|
567
|
+
return
|
|
568
|
+
async with _sigterm_sets(stop) as stop_event:
|
|
567
569
|
run_task = asyncio.ensure_future(worker.run())
|
|
568
570
|
stop_task = asyncio.ensure_future(stop_event.wait())
|
|
569
571
|
await asyncio.wait({run_task, stop_task}, return_when=asyncio.FIRST_COMPLETED)
|
|
@@ -573,10 +575,51 @@ async def run_worker_until_terminated(
|
|
|
573
575
|
_logger.info("Worker received SIGTERM: draining in-flight activity before exit")
|
|
574
576
|
await worker.shutdown()
|
|
575
577
|
await run_task
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
@contextlib.asynccontextmanager
|
|
581
|
+
async def _sigterm_sets(stop: asyncio.Event | None) -> AsyncIterator[asyncio.Event]:
|
|
582
|
+
"""``stop`` as given, or a new event SIGTERM sets for the length of the block."""
|
|
583
|
+
|
|
584
|
+
if stop is not None:
|
|
585
|
+
yield stop
|
|
586
|
+
return
|
|
587
|
+
loop = asyncio.get_running_loop()
|
|
588
|
+
stop_event = asyncio.Event()
|
|
589
|
+
loop.add_signal_handler(signal.SIGTERM, stop_event.set)
|
|
590
|
+
try:
|
|
591
|
+
yield stop_event
|
|
576
592
|
finally:
|
|
577
593
|
loop.remove_signal_handler(signal.SIGTERM)
|
|
578
594
|
|
|
579
595
|
|
|
596
|
+
class _StoppedBeforeStartError(Exception):
|
|
597
|
+
"""SIGTERM (or ``stop``) arrived before the Worker polled: no Directive is in flight."""
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
async def _before_stop[T](awaitable: Awaitable[T], stop: asyncio.Event) -> T:
|
|
601
|
+
"""``awaitable``'s result, unless ``stop`` is set first; then it is cancelled.
|
|
602
|
+
|
|
603
|
+
Start-up waits on the network -- registration, the first heartbeat, the Temporal
|
|
604
|
+
connection -- for as long as the far end takes to answer. A stop during that wait
|
|
605
|
+
must not wait with it.
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
work = asyncio.ensure_future(awaitable)
|
|
609
|
+
stopped = asyncio.ensure_future(stop.wait())
|
|
610
|
+
try:
|
|
611
|
+
await asyncio.wait({work, stopped}, return_when=asyncio.FIRST_COMPLETED)
|
|
612
|
+
finally:
|
|
613
|
+
stopped.cancel()
|
|
614
|
+
if not work.done():
|
|
615
|
+
work.cancel()
|
|
616
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
617
|
+
await work
|
|
618
|
+
if work.cancelled():
|
|
619
|
+
raise _StoppedBeforeStartError
|
|
620
|
+
return work.result()
|
|
621
|
+
|
|
622
|
+
|
|
580
623
|
# ------------------------------------------------------------------ the stream
|
|
581
624
|
|
|
582
625
|
|
|
@@ -971,6 +1014,40 @@ async def run(
|
|
|
971
1014
|
it says about itself. ``heartbeat_interval`` defaults to the cadence bootstrap gave.
|
|
972
1015
|
"""
|
|
973
1016
|
|
|
1017
|
+
loop = asyncio.get_running_loop()
|
|
1018
|
+
# Before the first await: in a container this process is PID 1, and the kernel drops
|
|
1019
|
+
# a signal PID 1 has no handler for, so a SIGTERM during start-up was lost and
|
|
1020
|
+
# `docker stop` waited out its grace period to SIGKILL (QTS-1536).
|
|
1021
|
+
stop = stop or asyncio.Event()
|
|
1022
|
+
loop.add_signal_handler(signal.SIGTERM, stop.set)
|
|
1023
|
+
try:
|
|
1024
|
+
return await _run(
|
|
1025
|
+
stop=stop,
|
|
1026
|
+
http_client=http_client,
|
|
1027
|
+
connect=connect,
|
|
1028
|
+
worker_factory=worker_factory,
|
|
1029
|
+
can_change_uid=can_change_uid,
|
|
1030
|
+
heartbeat_interval=heartbeat_interval,
|
|
1031
|
+
readiness=readiness,
|
|
1032
|
+
host_store=host_store,
|
|
1033
|
+
attestation=attestation,
|
|
1034
|
+
)
|
|
1035
|
+
finally:
|
|
1036
|
+
loop.remove_signal_handler(signal.SIGTERM)
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
async def _run(
|
|
1040
|
+
*,
|
|
1041
|
+
stop: asyncio.Event,
|
|
1042
|
+
http_client: httpx.AsyncClient | None,
|
|
1043
|
+
connect: Connector,
|
|
1044
|
+
worker_factory: WorkerFactory,
|
|
1045
|
+
can_change_uid: bool | None,
|
|
1046
|
+
heartbeat_interval: float | None,
|
|
1047
|
+
readiness: Readiness | None,
|
|
1048
|
+
host_store: HostCredentialStore | None,
|
|
1049
|
+
attestation: HostAttestation | None,
|
|
1050
|
+
) -> int:
|
|
974
1051
|
install_stop_tolerant_child_watcher(asyncio.get_running_loop())
|
|
975
1052
|
config = load_config()
|
|
976
1053
|
mode = isolation_mode()
|
|
@@ -1094,15 +1171,19 @@ async def _serve(
|
|
|
1094
1171
|
http_client: httpx.AsyncClient | None,
|
|
1095
1172
|
connect: Connector,
|
|
1096
1173
|
worker_factory: WorkerFactory,
|
|
1097
|
-
stop: asyncio.Event
|
|
1174
|
+
stop: asyncio.Event,
|
|
1098
1175
|
heartbeat_interval: float | None,
|
|
1099
1176
|
host_store: HostCredentialStore | None,
|
|
1100
1177
|
attestation: HostAttestation | None,
|
|
1101
1178
|
) -> int:
|
|
1102
1179
|
try:
|
|
1103
|
-
state, outcome = await
|
|
1104
|
-
state_dir=state_dir, can_separate_uids=can_change_uid, client=http_client
|
|
1180
|
+
state, outcome = await _before_stop(
|
|
1181
|
+
register(state_dir=state_dir, can_separate_uids=can_change_uid, client=http_client),
|
|
1182
|
+
stop,
|
|
1105
1183
|
)
|
|
1184
|
+
except _StoppedBeforeStartError:
|
|
1185
|
+
print("stopped before registration completed", flush=True)
|
|
1186
|
+
return 0
|
|
1106
1187
|
except RunnerRegistrationError as error:
|
|
1107
1188
|
print(f"registration refused ({error.reason}): {error}", flush=True)
|
|
1108
1189
|
if error.refuses_registration_token:
|
|
@@ -1148,6 +1229,12 @@ async def _serve(
|
|
|
1148
1229
|
attestation=attestation,
|
|
1149
1230
|
lifecycle=lifecycle,
|
|
1150
1231
|
)
|
|
1232
|
+
except _StoppedBeforeStartError:
|
|
1233
|
+
# No beat with a 10 s timeout on the way out (`_report_stop`): the control plane
|
|
1234
|
+
# may be what start-up was waiting for. The next start carries the stop.
|
|
1235
|
+
lifecycle.add(LifecycleKind.STOP, datetime.now(UTC))
|
|
1236
|
+
print("stopped before the Worker started", flush=True)
|
|
1237
|
+
return 0
|
|
1151
1238
|
finally:
|
|
1152
1239
|
pidfile.unlink(missing_ok=True)
|
|
1153
1240
|
|
|
@@ -1166,7 +1253,7 @@ async def _serve_registered(
|
|
|
1166
1253
|
http_client: httpx.AsyncClient | None,
|
|
1167
1254
|
connect: Connector,
|
|
1168
1255
|
worker_factory: WorkerFactory,
|
|
1169
|
-
stop: asyncio.Event
|
|
1256
|
+
stop: asyncio.Event,
|
|
1170
1257
|
interval: float,
|
|
1171
1258
|
host_store: HostCredentialStore | None,
|
|
1172
1259
|
attestation: HostAttestation | None,
|
|
@@ -1240,21 +1327,24 @@ async def _serve_registered(
|
|
|
1240
1327
|
# none, and the Temporal connection below cannot be opened without it.
|
|
1241
1328
|
while True:
|
|
1242
1329
|
try:
|
|
1243
|
-
await link.exchange()
|
|
1330
|
+
await _before_stop(link.exchange(), stop)
|
|
1244
1331
|
break
|
|
1332
|
+
except _StoppedBeforeStartError:
|
|
1333
|
+
raise
|
|
1245
1334
|
except Exception as error: # noqa: BLE001 - keep trying until the control plane answers
|
|
1246
1335
|
if _revoked(error):
|
|
1247
1336
|
return 1
|
|
1248
1337
|
_logger.warning("heartbeat failed before start: %s", error)
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1338
|
+
await _before_stop(asyncio.sleep(interval), stop)
|
|
1339
|
+
|
|
1340
|
+
client = await _before_stop(
|
|
1341
|
+
connect(
|
|
1342
|
+
temporal_address,
|
|
1343
|
+
namespace=state.temporal_namespace,
|
|
1344
|
+
api_key=stream.runner_token,
|
|
1345
|
+
tls=os.environ.get(TEMPORAL_TLS_ENV, "").strip().lower() in {"1", "true", "yes"},
|
|
1346
|
+
),
|
|
1347
|
+
stop,
|
|
1258
1348
|
)
|
|
1259
1349
|
hooks = build_hook_runner(config)
|
|
1260
1350
|
async with (
|
|
@@ -1289,7 +1379,6 @@ async def _serve_registered(
|
|
|
1289
1379
|
graceful_shutdown_timeout=graceful_shutdown_timeout(settings),
|
|
1290
1380
|
max_concurrent_activities=state.max_concurrent_directives,
|
|
1291
1381
|
)
|
|
1292
|
-
stop = stop or asyncio.Event()
|
|
1293
1382
|
revoked = asyncio.Event()
|
|
1294
1383
|
beat = asyncio.create_task(
|
|
1295
1384
|
_heartbeat_forever(link, stream, client, interval, stop=stop, revoked=revoked)
|
|
@@ -7,8 +7,6 @@ precedent does on a developer machine and nothing a daemon would need:
|
|
|
7
7
|
identity, namespace, Runner Token, hooks, credential store and Workspaces under
|
|
8
8
|
``<root>/<org>/``. A contractor with two clients runs two. There is no supervisor over
|
|
9
9
|
N Organisations -- that is the "one Runner straddling Organisations" 12 A6 rejected.
|
|
10
|
-
``add <name>`` puts another such process beside them, a personal Runner per Contract
|
|
11
|
-
(ADR-0022 §21), named freely rather than after its Organisation.
|
|
12
10
|
* **A login agent, never a system daemon** (23 item 1): a LaunchAgent in
|
|
13
11
|
``~/Library/LaunchAgents`` on macOS, a systemd *user* unit (with linger) on Linux, a
|
|
14
12
|
per-user logon task on Windows. No ``sudo`` anywhere: the login keychain is unreachable
|
|
@@ -83,7 +81,6 @@ __all__ = [
|
|
|
83
81
|
"run_environment",
|
|
84
82
|
"run_process",
|
|
85
83
|
"service_definition",
|
|
86
|
-
"settings_to_add",
|
|
87
84
|
"start",
|
|
88
85
|
"status_lines",
|
|
89
86
|
"stop",
|
|
@@ -390,7 +387,7 @@ def runner_command(org: str, root: Path) -> list[str]:
|
|
|
390
387
|
# The console script as found, not resolved: Homebrew's `bin/agentic-runner` is a
|
|
391
388
|
# symlink into a versioned Cellar path that the next `brew upgrade` deletes.
|
|
392
389
|
script = shutil.which("agentic-runner")
|
|
393
|
-
prefix = [script] if script else [sys.executable, "-m", "agentic_runner.
|
|
390
|
+
prefix = [script] if script else [sys.executable, "-m", "agentic_runner.launch"]
|
|
394
391
|
return [*prefix, "run", "--org", org, "--root", str(root), "--login-agent"]
|
|
395
392
|
|
|
396
393
|
|
|
@@ -676,50 +673,6 @@ async def install(
|
|
|
676
673
|
return lines
|
|
677
674
|
|
|
678
675
|
|
|
679
|
-
def settings_to_add(paths: OrgPaths, *, beside: str | None) -> WorkstationSettings:
|
|
680
|
-
"""``add <name>``: what ``install`` registers another Runner with (ADR-0022 §21).
|
|
681
|
-
|
|
682
|
-
A personal Runner serves one Contract, so a person with three Contracts runs three. Each
|
|
683
|
-
is ``install``'s own process with its own state directory -- identity, Runner Token and
|
|
684
|
-
Recipient Key (§37) -- and only the settings an earlier ``install`` was told are copied,
|
|
685
|
-
so the command the console shows carries no Temporal address.
|
|
686
|
-
"""
|
|
687
|
-
|
|
688
|
-
# Registered state, not workstation.json: ``install`` saves the settings before it
|
|
689
|
-
# registers, so a refused token leaves them behind and the retry must go through.
|
|
690
|
-
if load_state(paths.state_dir) is not None:
|
|
691
|
-
raise ValueError(
|
|
692
|
-
f"{paths.org!r} is already installed under {paths.root}; pick a new name for "
|
|
693
|
-
"the Runner you are adding"
|
|
694
|
-
)
|
|
695
|
-
return _settings_beside(paths.root, beside).model_copy(update={"org": paths.org})
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
def _settings_beside(root: Path, beside: str | None) -> WorkstationSettings:
|
|
699
|
-
if beside is not None:
|
|
700
|
-
return WorkstationSettings.load(OrgPaths(root=root, org=validate_org(beside)))
|
|
701
|
-
installed = [
|
|
702
|
-
WorkstationSettings.load(OrgPaths(root=root, org=file.parent.name))
|
|
703
|
-
for file in sorted(root.glob("*/workstation.json"), key=lambda file: file.stat().st_mtime)
|
|
704
|
-
]
|
|
705
|
-
if not installed:
|
|
706
|
-
raise FileNotFoundError(
|
|
707
|
-
f"no Runner is installed under {root}; run `agentic-runner install` first"
|
|
708
|
-
)
|
|
709
|
-
endpoints = {
|
|
710
|
-
(settings.control_plane_url, settings.temporal_address, settings.temporal_tls)
|
|
711
|
-
for settings in installed
|
|
712
|
-
}
|
|
713
|
-
if len(endpoints) > 1:
|
|
714
|
-
names = ", ".join(sorted(settings.org for settings in installed))
|
|
715
|
-
raise ValueError(
|
|
716
|
-
f"the Runners under {root} ({names}) talk to different control planes; "
|
|
717
|
-
"name the one to add beside with --beside"
|
|
718
|
-
)
|
|
719
|
-
# The newest install's PATH and ACP kinds: the latest the person told this machine.
|
|
720
|
-
return installed[-1]
|
|
721
|
-
|
|
722
|
-
|
|
723
676
|
def start(paths: OrgPaths, **driver: Any) -> None:
|
|
724
677
|
_drive_service(paths, "start", **driver)
|
|
725
678
|
|
|
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.18.0 → agentic_runner-3.18.2}/src/agentic_runner/device_login_activities.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.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/contracts.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/evidence.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/git/workspace.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/github/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/github/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/integrations/github/gh_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/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
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/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.18.0 → agentic_runner-3.18.2}/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.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/_runtime_support.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/claude_runtime.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/claude_sign_in.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/command_policy.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/contract_device_login.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/contract_isolation.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/fastapi_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.18.0 → agentic_runner-3.18.2}/src/agentic_runner/workers/harness_outcome.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|