agentic-runner 3.18.0__tar.gz → 3.18.1__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 (77) hide show
  1. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/PKG-INFO +2 -2
  2. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/pyproject.toml +2 -2
  3. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/__init__.py +1 -1
  4. agentic_runner-3.18.1/src/agentic_runner/launch.py +38 -0
  5. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/service.py +109 -20
  6. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workstation.py +1 -1
  7. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/.gitignore +0 -0
  8. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/LICENSE +0 -0
  9. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/README.md +0 -0
  10. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/activities.py +0 -0
  11. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/attempts.py +0 -0
  12. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/auth_mode.py +0 -0
  13. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/build.py +0 -0
  14. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/callback.py +0 -0
  15. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/child_watcher.py +0 -0
  16. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/cli.py +0 -0
  17. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/cli_floor.py +0 -0
  18. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/config.py +0 -0
  19. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/credentials.py +0 -0
  20. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/device_login_activities.py +0 -0
  21. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/egress.py +0 -0
  22. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/harness_self_test.py +0 -0
  23. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/heartbeat_link.py +0 -0
  24. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/hooks.py +0 -0
  25. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/host_store.py +0 -0
  26. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/__init__.py +0 -0
  27. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/git/__init__.py +0 -0
  28. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/git/contracts.py +0 -0
  29. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/git/evidence.py +0 -0
  30. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
  31. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/git/workspace.py +0 -0
  32. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/github/__init__.py +0 -0
  33. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/github/auth.py +0 -0
  34. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/github/fake_client.py +0 -0
  35. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/github/gh_client.py +0 -0
  36. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
  37. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
  38. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/lifecycle.py +0 -0
  39. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/llm_proxy.py +0 -0
  40. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/mcp.py +0 -0
  41. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/message_store.py +0 -0
  42. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/oauth_connectors.py +0 -0
  43. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/private_state.py +0 -0
  44. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/py.typed +0 -0
  45. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/recipient_key_secret.py +0 -0
  46. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/registration.py +0 -0
  47. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/runtime/__init__.py +0 -0
  48. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/runtime/verifier_command.py +0 -0
  49. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/sealed_box.py +0 -0
  50. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/skill.py +0 -0
  51. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/skills/agentic-runner-setup/SKILL.md +0 -0
  52. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/testing/__init__.py +0 -0
  53. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/testing/__main__.py +0 -0
  54. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/testing/control_plane.py +0 -0
  55. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/testing/plugin.py +0 -0
  56. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/testing/test_conformance.py +0 -0
  57. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/testing/workflow.py +0 -0
  58. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/tiny_http.py +0 -0
  59. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/triage_activities.py +0 -0
  60. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/usage_windows.py +0 -0
  61. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/user_sources.py +0 -0
  62. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/__init__.py +0 -0
  63. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/_runtime_support.py +0 -0
  64. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/acp_runtime.py +0 -0
  65. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/agent_runtime.py +0 -0
  66. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/claude_runtime.py +0 -0
  67. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/claude_sign_in.py +0 -0
  68. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/codex_runtime.py +0 -0
  69. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/command_policy.py +0 -0
  70. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/contract_device_login.py +0 -0
  71. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/contract_isolation.py +0 -0
  72. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/fastapi_client.py +0 -0
  73. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/harness_outcome.py +0 -0
  74. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/harness_usage.py +0 -0
  75. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/mcp_config.py +0 -0
  76. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/src/agentic_runner/workers/settings.py +0 -0
  77. {agentic_runner-3.18.0 → agentic_runner-3.18.1}/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.0
3
+ Version: 3.18.1
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.0
8
+ Requires-Dist: agentic-runner-contracts==3.18.1
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.0",
13
+ "agentic-runner-contracts==3.18.1",
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.cli:main"
34
+ agentic-runner = "agentic_runner.launch:main"
35
35
 
36
36
  [project.entry-points.pytest11]
37
37
  agentic_runner = "agentic_runner.testing.plugin"
@@ -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.18.0"
10
+ __version__ = "3.18.1"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -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
- loop = asyncio.get_running_loop()
564
- stop_event = stop or asyncio.Event()
565
- loop.add_signal_handler(signal.SIGTERM, stop_event.set)
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 | None,
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 register(
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 | None,
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
- if stop is not None and stop.is_set():
1250
- return 1
1251
- await asyncio.sleep(interval)
1252
-
1253
- client = await connect(
1254
- temporal_address,
1255
- namespace=state.temporal_namespace,
1256
- api_key=stream.runner_token,
1257
- tls=os.environ.get(TEMPORAL_TLS_ENV, "").strip().lower() in {"1", "true", "yes"},
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)
@@ -390,7 +390,7 @@ def runner_command(org: str, root: Path) -> list[str]:
390
390
  # The console script as found, not resolved: Homebrew's `bin/agentic-runner` is a
391
391
  # symlink into a versioned Cellar path that the next `brew upgrade` deletes.
392
392
  script = shutil.which("agentic-runner")
393
- prefix = [script] if script else [sys.executable, "-m", "agentic_runner.cli"]
393
+ prefix = [script] if script else [sys.executable, "-m", "agentic_runner.launch"]
394
394
  return [*prefix, "run", "--org", org, "--root", str(root), "--login-agent"]
395
395
 
396
396
 
File without changes