agentic-runner 3.1.0__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/PKG-INFO +1 -1
  2. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/__init__.py +1 -1
  3. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/build.py +1 -1
  4. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/cli.py +2 -1
  5. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/registration.py +21 -0
  6. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workstation.py +42 -6
  7. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/.gitignore +0 -0
  8. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/LICENSE +0 -0
  9. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/README.md +0 -0
  10. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/pyproject.toml +0 -0
  11. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/activities.py +0 -0
  12. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/attempts.py +0 -0
  13. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/callback.py +0 -0
  14. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/child_watcher.py +0 -0
  15. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/config.py +0 -0
  16. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/credentials.py +0 -0
  17. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/device_login_activities.py +0 -0
  18. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/egress.py +0 -0
  19. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/heartbeat_link.py +0 -0
  20. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/hooks.py +0 -0
  21. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/host_store.py +0 -0
  22. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/__init__.py +0 -0
  23. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
  24. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/git/contracts.py +0 -0
  25. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
  26. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/git/fake_workspace.py +0 -0
  27. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/git/workspace.py +0 -0
  28. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
  29. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/github/auth.py +0 -0
  30. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
  31. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
  32. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/lifecycle.py +0 -0
  33. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/llm_proxy.py +0 -0
  34. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/mcp.py +0 -0
  35. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/message_store.py +0 -0
  36. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/private_state.py +0 -0
  37. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/py.typed +0 -0
  38. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/recipient_key_secret.py +0 -0
  39. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/runtime/__init__.py +0 -0
  40. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
  41. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/sealed_box.py +0 -0
  42. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/service.py +0 -0
  43. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/testing/__init__.py +0 -0
  44. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/testing/__main__.py +0 -0
  45. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/testing/control_plane.py +0 -0
  46. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/testing/plugin.py +0 -0
  47. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/testing/test_conformance.py +0 -0
  48. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/testing/workflow.py +0 -0
  49. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/tiny_http.py +0 -0
  50. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/triage_activities.py +0 -0
  51. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/user_sources.py +0 -0
  52. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/__init__.py +0 -0
  53. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
  54. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
  55. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
  56. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
  57. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/command_policy.py +0 -0
  58. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/contract_device_login.py +0 -0
  59. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
  60. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/fastapi_client.py +0 -0
  61. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/harness_usage.py +0 -0
  62. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/mcp_config.py +0 -0
  63. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/settings.py +0 -0
  64. {agentic_runner-3.1.0 → agentic_runner-3.2.0}/src/agentic_runner/workers/skills.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentic-runner
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: The Agentic OS Runner: an activity-only Temporal executor for workspaces, Agent Runtimes and verb seams
5
5
  License-Expression: AGPL-3.0-only
6
6
  License-File: LICENSE
@@ -7,6 +7,6 @@ stay on the platform; this package imports none of them, and a CI partition test
7
7
  (``tests/test_package_partition.py``) fails the build if that changes.
8
8
  """
9
9
 
10
- __version__ = "3.1.0"
10
+ __version__ = "3.2.0"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -23,7 +23,7 @@ def build_id(package: Path = _PACKAGE) -> str:
23
23
 
24
24
  Self-reported build identity (12 B7), not a signature: the consoles say
25
25
  "self-reported" beside it. Only ``.py`` files count, so a wheel, the image and a
26
- Homebrew install of one release give the same value whatever bytecode each compiled.
26
+ ``uv tool install`` of one release give the same value whatever bytecode each compiled.
27
27
  """
28
28
 
29
29
  digest = sha256()
@@ -333,7 +333,8 @@ def _install(arguments: argparse.Namespace, paths: workstation.OrgPaths) -> int:
333
333
  return 2
334
334
  # Pasted, never a flag: a flag is in the process table for every uid on the box.
335
335
  token = os.environ.get(AGENT_TOKEN_ENV, "").strip() or getpass.getpass(
336
- "Agent Token (issued to you by the Organisation's Admin): "
336
+ "Agent Token (yours from /me/runners, or the Organisation Admin's for an "
337
+ "Organisation Runner): "
337
338
  )
338
339
  settings = workstation.WorkstationSettings(
339
340
  org=paths.org,
@@ -69,6 +69,7 @@ INTAKE_IGNORED_PATH = "/api/runner/v1/runners/intake/ignored"
69
69
  # The refusal a revoked Runner's heartbeat earns (06 §2: revocation *is* the refused
70
70
  # heartbeat). The one reason this process stops on rather than retries.
71
71
  RUNNER_REVOKED_REASON = "runner_revoked"
72
+ CONTROL_PLANE_OLDER_REASON = "control_plane_older"
72
73
 
73
74
  # What the state directory holds after a successful bootstrap. One file, mode 0600: the
74
75
  # private key is in it, and a Runner that loses it re-bootstraps rather than recovering.
@@ -346,9 +347,29 @@ def _refusal(response: httpx.Response) -> tuple[str, str]:
346
347
  detail = None
347
348
  if isinstance(detail, dict) and "reason" in detail:
348
349
  return str(detail["reason"]), str(detail.get("detail", detail["reason"]))
350
+ if unknown := _fields_the_control_plane_does_not_know(response.status_code, detail):
351
+ return CONTROL_PLANE_OLDER_REASON, (
352
+ f"the control plane is older than this Runner (agentic-runner {runner_version}, "
353
+ f"agentic-runner-contracts {contracts_version}): it does not know "
354
+ f"{', '.join(unknown)}. Install the agentic-runner release that matches the "
355
+ "control plane, or ask your Organisation's Admin to upgrade it."
356
+ )
349
357
  return "refused", f"{response.status_code}: {response.text[:200]}"
350
358
 
351
359
 
360
+ def _fields_the_control_plane_does_not_know(status_code: int, detail: Any) -> list[str]:
361
+ # A strict request model (extra="forbid") answering 422 with nothing but
362
+ # `extra_forbidden` errors is version skew, not a bad token: this Runner's contracts
363
+ # added fields the control plane's pinned contracts have not caught up to (RR-13).
364
+ if status_code != 422 or not isinstance(detail, list) or not detail:
365
+ return []
366
+ if not all(
367
+ isinstance(error, dict) and error.get("type") == "extra_forbidden" for error in detail
368
+ ):
369
+ return []
370
+ return [str(error.get("loc", ["?"])[-1]) for error in detail]
371
+
372
+
352
373
  class SignedIntakeStream:
353
374
  """:class:`agentic_runner.user_sources.IntakeStream` over the signed outbound stream."""
354
375
 
@@ -570,7 +570,7 @@ def status_lines(paths: OrgPaths, *, now: datetime | None = None) -> list[str]:
570
570
  pid = process_id(paths)
571
571
  state = load_state(paths.state_dir)
572
572
  age = heartbeat_age(paths, now=now)
573
- return [
573
+ lines = [
574
574
  f"organisation {paths.org} ({paths.state_dir})",
575
575
  f"process {'running, pid ' + str(pid) if pid else 'not running'}",
576
576
  (
@@ -580,6 +580,30 @@ def status_lines(paths: OrgPaths, *, now: datetime | None = None) -> list[str]:
580
580
  ),
581
581
  f"heartbeat {'never' if age is None else f'{age:.0f}s ago'}",
582
582
  ]
583
+ if pid and state is not None:
584
+ lines.extend(next_step_lines(state.host_party))
585
+ return lines
586
+
587
+
588
+ def next_step_lines(host_party: str) -> list[str]:
589
+ """Where to go once the Runner is live (QTS-1314): the dogfood install ended at
590
+ ``started`` and left the person with no idea what the console wanted next.
591
+
592
+ Paths, not URLs: the Runner knows its control plane, not the console, and the two need
593
+ not share a domain (QTS-1300) -- the person minted the token on that console anyway.
594
+ """
595
+
596
+ if host_party == "user":
597
+ return [
598
+ "next open /me/runners in the console to see this Runner, then "
599
+ "/me/work/new and pick a Contract that says it runs on a Runner you host"
600
+ ]
601
+ if host_party:
602
+ return [
603
+ "next this Runner takes the work of Contracts whose Runner is hosted by "
604
+ f"the {host_party}; it shows on the Organisation Console's Runners page"
605
+ ]
606
+ return []
583
607
 
584
608
 
585
609
  # ------------------------------------------------------------------ the verbs
@@ -598,15 +622,17 @@ async def install(
598
622
  ) -> list[str]:
599
623
  """Register this Organisation's process and install its login agent.
600
624
 
601
- Registration happens here, once, with the Agent Token the Organisation's Admin issued
602
- to this user (12 A6) -- pasted at the prompt, never stored: the identity bootstrap
603
- hands back is what every later start uses, and a lost state directory means a
604
- reinstall and a re-delivery of any sealed value (22 A4), by design.
625
+ Registration happens here, once, with the Agent Token this user minted on /me/runners
626
+ or the Organisation's Admin issued them (12 A6, QTS-1304) -- pasted at the prompt,
627
+ never stored: the identity bootstrap hands back is what every later start uses, and a
628
+ lost state directory means a reinstall and a re-delivery of any sealed value (22 A4),
629
+ by design.
605
630
  """
606
631
 
607
632
  require_any_cli(settings.path)
608
633
  paths.create()
609
634
  settings.save(paths)
635
+ already_registered = load_state(paths.state_dir) is not None
610
636
  state, outcome = await service.register(
611
637
  state_dir=paths.state_dir,
612
638
  can_separate_uids=False,
@@ -619,10 +645,20 @@ async def install(
619
645
  definition = service_definition(paths, settings, platform=platform, home=home)
620
646
  definition.path.parent.mkdir(parents=True, exist_ok=True)
621
647
  definition.path.write_bytes(definition.content)
622
- lines = [outcome, f"login agent {definition.path}"]
648
+ lines = [outcome]
649
+ if already_registered:
650
+ # QTS-1314: the dogfood reinstall kept an Organisation-hosted identity while the
651
+ # person believed the user-hosted token they had just pasted had taken effect.
652
+ lines.append(
653
+ f"token not used: {paths.state_dir} already holds Runner "
654
+ f"{state.runner_id}; to register a new one, stop it and move that directory "
655
+ "aside (docs/workstation.md, Switch to a new token)"
656
+ )
657
+ lines.append(f"login agent {definition.path}")
623
658
  if start_service:
624
659
  drive(definition.install + definition.start, run=run)
625
660
  lines.append(f"started {state.runner_id}")
661
+ lines.extend(next_step_lines(state.host_party))
626
662
  return lines
627
663
 
628
664
 
File without changes
File without changes