agent-supervisor 0.2.0__tar.gz → 0.3.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 (108) hide show
  1. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/Cargo.lock +69 -1
  2. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/Cargo.toml +2 -1
  3. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/PKG-INFO +9 -10
  4. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/README.md +8 -9
  5. agent_supervisor-0.3.0/agent_supervisor/__init__.py +165 -0
  6. agent_supervisor-0.3.0/agent_supervisor/_lifecycle.py +248 -0
  7. agent_supervisor-0.3.0/agent_supervisor/_run_context.py +11 -0
  8. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/agent_supervisor/agent_run.py +24 -19
  9. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/agent_supervisor/google_adk.py +5 -4
  10. agent_supervisor-0.3.0/agent_supervisor/langgraph.py +18 -0
  11. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/agent_supervisor/microsoft.py +35 -31
  12. agent_supervisor-0.3.0/agent_supervisor/openai.py +112 -0
  13. agent_supervisor-0.3.0/agent_supervisor/pydantic_ai.py +54 -0
  14. agent_supervisor-0.3.0/dist/agent_supervisor-0.3.0-cp39-abi3-manylinux_2_39_x86_64.whl +0 -0
  15. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/api-boundaries.md +42 -30
  16. agent_supervisor-0.3.0/docs/api-reference.md +229 -0
  17. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/index.md +16 -0
  18. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/openshell.md +1 -1
  19. agent_supervisor-0.3.0/docs/python-api.md +855 -0
  20. agent_supervisor-0.3.0/docs/quickstart.md +309 -0
  21. agent_supervisor-0.3.0/docs/releases/0.3.0.md +78 -0
  22. agent_supervisor-0.3.0/docs/rust-api.md +735 -0
  23. agent_supervisor-0.3.0/docs/supervisor-lifecycle.md +130 -0
  24. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/wasm-backend.md +1 -1
  25. agent_supervisor-0.3.0/examples/README.md +51 -0
  26. agent_supervisor-0.3.0/examples/python/supervisor_background_jobs.py +55 -0
  27. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/mkdocs.yml +12 -10
  28. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/pyproject.toml +1 -1
  29. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/Cargo.lock +99 -2
  30. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/Cargo.toml +1 -1
  31. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/README.md +8 -9
  32. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/src/lib.rs +440 -313
  33. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/src/mcp_stdio.rs +2 -0
  34. agent_supervisor-0.3.0/python/tests/test_api.py +581 -0
  35. agent_supervisor-0.3.0/python/tests/test_framework_adapters.py +386 -0
  36. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/tests/test_microsoft.py +52 -16
  37. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/tests/test_openai.py +53 -23
  38. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/agent_run.rs +181 -64
  39. agent_supervisor-0.3.0/src/event_log.rs +669 -0
  40. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/events.rs +62 -25
  41. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/lib.rs +7 -2
  42. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/process_control.rs +6 -1
  43. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/supervisor.rs +1393 -26
  44. agent_supervisor-0.2.0/agent_supervisor/__init__.py +0 -145
  45. agent_supervisor-0.2.0/agent_supervisor/_lifecycle.py +0 -161
  46. agent_supervisor-0.2.0/agent_supervisor/_run_scope.py +0 -11
  47. agent_supervisor-0.2.0/agent_supervisor/langgraph.py +0 -15
  48. agent_supervisor-0.2.0/agent_supervisor/openai.py +0 -103
  49. agent_supervisor-0.2.0/agent_supervisor/pydantic_ai.py +0 -13
  50. agent_supervisor-0.2.0/dist/agent_supervisor-0.2.0-cp39-abi3-manylinux_2_39_x86_64.whl +0 -0
  51. agent_supervisor-0.2.0/docs/api-reference.md +0 -156
  52. agent_supervisor-0.2.0/docs/python-api.md +0 -508
  53. agent_supervisor-0.2.0/docs/quickstart.md +0 -122
  54. agent_supervisor-0.2.0/docs/rust-api.md +0 -349
  55. agent_supervisor-0.2.0/docs/supervisor-lifecycle.md +0 -20
  56. agent_supervisor-0.2.0/examples/README.md +0 -16
  57. agent_supervisor-0.2.0/python/tests/test_api.py +0 -378
  58. agent_supervisor-0.2.0/python/tests/test_framework_adapters.py +0 -234
  59. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/.github/workflows/ci.yml +0 -0
  60. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/.github/workflows/docs.yml +0 -0
  61. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/.github/workflows/publish-python.yml +0 -0
  62. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/.gitignore +0 -0
  63. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/LICENSE +0 -0
  64. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/agent_supervisor/fastmcp.py +0 -0
  65. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/agent_supervisor/mcp.py +0 -0
  66. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/javascripts/mermaid-init.js +0 -0
  67. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/mcp.md +0 -0
  68. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/network-host-mode.md +0 -0
  69. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/process-groups.md +0 -0
  70. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/releases/0.1.0.md +0 -0
  71. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/releases/0.1.1.md +0 -0
  72. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/releases/0.1.2.md +0 -0
  73. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/releases/0.1.3.md +0 -0
  74. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/releases/0.2.0.md +0 -0
  75. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/security-model.md +0 -0
  76. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/stylesheets/extra.css +0 -0
  77. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/docs/testing.md +0 -0
  78. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/examples/python/official_mcp_server.py +0 -0
  79. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/examples/python/prefect_fastmcp_server.py +0 -0
  80. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/tests/fixtures/official_mcp_server.py +0 -0
  81. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/tests/fixtures/prefect_mcp_server.py +0 -0
  82. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/tests/test_mcp_adapters.py +0 -0
  83. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/python/tests/test_supervision_settings.py +0 -0
  84. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/environment.rs +0 -0
  85. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/error.rs +0 -0
  86. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/filesystem.rs +0 -0
  87. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/group.rs +0 -0
  88. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/mcp/policy.rs +0 -0
  89. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/mcp/relay.rs +0 -0
  90. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/mcp.rs +0 -0
  91. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/network.rs +0 -0
  92. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/pipeline.rs +0 -0
  93. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/policy.rs +0 -0
  94. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/probes.rs +0 -0
  95. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/pty.rs +0 -0
  96. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/runner.rs +0 -0
  97. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/wasm.rs +0 -0
  98. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/src/windows_filesystem.rs +0 -0
  99. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/filesystem_unsupported.rs +0 -0
  100. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/fixtures/mcp_stdio_fixture.rs +0 -0
  101. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/linux_filesystem.rs +0 -0
  102. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/loom_supervisor.rs +0 -0
  103. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/macos_filesystem.rs +0 -0
  104. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/macos_smoke.rs +0 -0
  105. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/mcp_stdio.rs +0 -0
  106. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/resource_limits_properties.rs +0 -0
  107. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/windows_filesystem.rs +0 -0
  108. {agent_supervisor-0.2.0 → agent_supervisor-0.3.0}/tests/windows_smoke.rs +0 -0
@@ -4,7 +4,7 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "agent-supervisor"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  dependencies = [
9
9
  "futures",
10
10
  "landlock",
@@ -19,6 +19,7 @@ dependencies = [
19
19
  "thiserror 1.0.69",
20
20
  "tokio",
21
21
  "tokio-util",
22
+ "uuid",
22
23
  "wasmi",
23
24
  "wat",
24
25
  "windows-sys",
@@ -340,6 +341,17 @@ version = "1.0.18"
340
341
  source = "registry+https://github.com/rust-lang/crates.io-index"
341
342
  checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
342
343
 
344
+ [[package]]
345
+ name = "js-sys"
346
+ version = "0.3.107"
347
+ source = "registry+https://github.com/rust-lang/crates.io-index"
348
+ checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a"
349
+ dependencies = [
350
+ "cfg-if",
351
+ "futures-util",
352
+ "wasm-bindgen",
353
+ ]
354
+
343
355
  [[package]]
344
356
  name = "landlock"
345
357
  version = "0.4.7"
@@ -984,6 +996,18 @@ version = "0.2.3"
984
996
  source = "registry+https://github.com/rust-lang/crates.io-index"
985
997
  checksum = "ca718471876992059f251e33aafc334346ada08042b7661dd1fe313a9bced557"
986
998
 
999
+ [[package]]
1000
+ name = "uuid"
1001
+ version = "1.29.0"
1002
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1003
+ checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
1004
+ dependencies = [
1005
+ "getrandom 0.4.3",
1006
+ "js-sys",
1007
+ "serde_core",
1008
+ "wasm-bindgen",
1009
+ ]
1010
+
987
1011
  [[package]]
988
1012
  name = "valuable"
989
1013
  version = "0.1.1"
@@ -1014,6 +1038,50 @@ dependencies = [
1014
1038
  "wit-bindgen",
1015
1039
  ]
1016
1040
 
1041
+ [[package]]
1042
+ name = "wasm-bindgen"
1043
+ version = "0.2.130"
1044
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1045
+ checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c"
1046
+ dependencies = [
1047
+ "cfg-if",
1048
+ "rustversion",
1049
+ "wasm-bindgen-macro",
1050
+ "wasm-bindgen-shared",
1051
+ ]
1052
+
1053
+ [[package]]
1054
+ name = "wasm-bindgen-macro"
1055
+ version = "0.2.130"
1056
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1057
+ checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9"
1058
+ dependencies = [
1059
+ "quote",
1060
+ "wasm-bindgen-macro-support",
1061
+ ]
1062
+
1063
+ [[package]]
1064
+ name = "wasm-bindgen-macro-support"
1065
+ version = "0.2.130"
1066
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1067
+ checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a"
1068
+ dependencies = [
1069
+ "bumpalo",
1070
+ "proc-macro2",
1071
+ "quote",
1072
+ "syn 3.0.5",
1073
+ "wasm-bindgen-shared",
1074
+ ]
1075
+
1076
+ [[package]]
1077
+ name = "wasm-bindgen-shared"
1078
+ version = "0.2.130"
1079
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1080
+ checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e"
1081
+ dependencies = [
1082
+ "unicode-ident",
1083
+ ]
1084
+
1017
1085
  [[package]]
1018
1086
  name = "wasm-encoder"
1019
1087
  version = "0.262.0"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "agent-supervisor"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
  license = "Apache-2.0"
6
6
  description = "Policy-driven process isolation and supervision for RooAGI runtimes"
@@ -19,6 +19,7 @@ tokio-util = "0.7"
19
19
  portable-pty = "0.9"
20
20
  wasmi = { version = "2", optional = true }
21
21
  serde_json = "1"
22
+ uuid = { version = "1", features = ["v4", "serde"] }
22
23
 
23
24
  [features]
24
25
  default = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-supervisor
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: Apache Software License
6
6
  Classifier: Operating System :: MacOS
@@ -31,11 +31,11 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
31
31
 
32
32
  Python bindings for the `agent-supervisor` native process supervisor.
33
33
 
34
- Version 0.2.0 source provides bounded one-shot execution and output-event
35
- streaming through the native Linux, macOS, and Windows backends. The latest
36
- published PyPI version is 0.1.2. Its wheels target Apple Silicon macOS,
37
- x86-64 Windows, and x86-64 Linux with glibc 2.39 or newer. The Rust crate's
38
- optional WASM runner is not exposed by the Python binding.
34
+ Version 0.3.0 source provides bounded one-shot execution, output-event
35
+ streaming, manager-owned background jobs, supervisor-scoped process IDs, and
36
+ agent-run lifecycle management through the native Linux, macOS, and Windows
37
+ backends. The Rust crate's optional WASM runner is not exposed by the Python
38
+ binding.
39
39
 
40
40
  Install the published package with `python -m pip install agent-supervisor`.
41
41
  To use the current repository source, build it from this checkout:
@@ -55,9 +55,8 @@ The API and supported platform behavior are documented in
55
55
  optional stdio adapters for the official MCP Python SDK and Prefect FastMCP; see
56
56
  [`../docs/mcp.md`](../docs/mcp.md) for complete server examples and installation steps.
57
57
 
58
- The 0.2.0 repository source includes optional run adapters for OpenAI Agents
59
- SDK, Microsoft Agent Framework, LangGraph, PydanticAI, and Google ADK. The
60
- published PyPI package is 0.1.2 and does not include these adapters yet. From a
58
+ The 0.3.0 source includes optional run adapters for OpenAI Agents SDK,
59
+ Microsoft Agent Framework, LangGraph, PydanticAI, and Google ADK. From a
61
60
  checkout, install an adapter with its matching extra, for example:
62
61
 
63
62
  ```bash
@@ -66,7 +65,7 @@ python -m pip install -e '.[langgraph]'
66
65
  ```
67
66
 
68
67
  Use `openai`, `microsoft`, `pydantic-ai`, or `google-adk` for the other
69
- adapters. They share the Rust run scope,
68
+ adapters. They share the Rust agent run,
70
69
  apply the same default deadline and cleanup behavior, and clean up local tool
71
70
  processes started through Agent Supervisor. Agent code remains in your app
72
71
  process. See the
@@ -20,7 +20,7 @@ application remains responsible for identity, authentication, authorization,
20
20
  and orchestration. Enforcement depends on the operating system and selected
21
21
  policy; inspect the execution result and platform capabilities.
22
22
 
23
- The current version in this checkout is **0.2.0**.
23
+ The current version in this checkout is **0.3.0**.
24
24
 
25
25
  ## Who it is for
26
26
 
@@ -178,7 +178,7 @@ Add the crate:
178
178
 
179
179
  ```toml
180
180
  [dependencies]
181
- agent-supervisor = "0.2.0"
181
+ agent-supervisor = "0.3.0"
182
182
  ```
183
183
 
184
184
 
@@ -217,16 +217,14 @@ and [using agent SDKs](docs/api-boundaries.md) for the integration example.
217
217
 
218
218
  ## Python bindings
219
219
 
220
- The Python package is published on PyPI as `agent-supervisor` 0.1.2. Prebuilt
221
- wheels are available for Apple Silicon macOS, x86-64 Windows, and x86-64 Linux
222
- with glibc 2.39 or newer:
220
+ Install the Python package from PyPI:
223
221
 
224
222
  ```bash
225
- python -m pip install agent-supervisor==0.1.2
223
+ python -m pip install agent-supervisor
226
224
  ```
227
225
 
228
- The current repository source is 0.2.0 and can be built with maturin; it has
229
- not yet been published to PyPI.
226
+ To use the current 0.3.0 source from this checkout, build it with maturin; the
227
+ build steps are in [`python/README.md`](python/README.md).
230
228
 
231
229
  See the [Python API guide](docs/python-api.md) for capture, streaming,
232
230
  cancellation, and platform behavior.
@@ -282,7 +280,8 @@ behavior, and release notes:
282
280
 
283
281
  ## Release
284
282
 
285
- See the [0.2.0 release notes](docs/releases/0.2.0.md) for agent-run scopes and
283
+ See the [0.3.0 release notes](docs/releases/0.3.0.md) for manager-owned jobs,
284
+ supervisor-scoped process IDs, and store/resume behavior; the [0.2.0 release notes](docs/releases/0.2.0.md) cover agent-run scopes and
286
285
  MCP integrations, the [0.1.3 notes](docs/releases/0.1.3.md) for the optional WASM
287
286
  runner, the [0.1.2 notes](docs/releases/0.1.2.md) for Python bindings, and the
288
287
  [0.1.1 notes](docs/releases/0.1.1.md) for the separate OpenShell developer
@@ -0,0 +1,165 @@
1
+ """Python bindings for agent-supervisor."""
2
+
3
+ import asyncio
4
+
5
+ from ._native import (
6
+ CancellationToken,
7
+ Command,
8
+ OutputEvent,
9
+ Policy,
10
+ Process,
11
+ RunResult,
12
+ SupervisorProcessId,
13
+ SupervisorError,
14
+ SupervisorEvent,
15
+ SupervisorHandle,
16
+ platform_capabilities,
17
+ run as _run_native,
18
+ )
19
+ from .agent_run import AgentRun, AgentRunPolicy
20
+
21
+
22
+ class Supervisor:
23
+ """Own agent runs and local child processes for the application.
24
+
25
+ Use ``start_agent_run`` to create a deadline and cleanup boundary for one
26
+ framework invocation. The supervisor also owns background jobs that can
27
+ continue between agent runs.
28
+ """
29
+
30
+ def __init__(self):
31
+ self._native = SupervisorHandle()
32
+
33
+ def start_agent_run(self, policy=None):
34
+ """Create one agent run owned by this supervisor."""
35
+ if policy is None:
36
+ policy = AgentRunPolicy()
37
+ if not isinstance(policy, AgentRunPolicy):
38
+ raise TypeError("policy must be an AgentRunPolicy")
39
+ native = self._native.start_agent_run(policy.timeout, policy.shutdown_grace)
40
+ return AgentRun(native, self, policy)
41
+
42
+ def active_agent_runs(self):
43
+ """Return the number of agent runs currently owned by this supervisor."""
44
+ return self._native.active_agent_runs()
45
+
46
+ def active_processes(self):
47
+ """Return the number of child processes registered with this supervisor."""
48
+ return self._native.active_processes()
49
+
50
+ async def start(self, command, *, policy=None):
51
+ """Start a persistent child and return its interactive Process handle."""
52
+ run = _current_run_context()
53
+ if run is not None:
54
+ return await run.start(command, policy=policy)
55
+ return await self._native.start(command, policy=policy)
56
+
57
+ def stream(self, *, run=None, process=None):
58
+ """Watch this supervisor's work as one ordered stream of events.
59
+
60
+ ``async for event in supervisor.stream(...)``. This only observes:
61
+ launch work with :func:`run`, :meth:`start`, :meth:`start_background`,
62
+ or a framework adapter. Stopping the watcher stops nothing, and a slow
63
+ watcher never slows a process.
64
+
65
+ - ``stream()`` watches everything, until the supervisor is dropped.
66
+ - ``stream(run=run)`` watches one agent run, including its framework
67
+ events, and ends when the run ends.
68
+ - ``stream(process=process_id)`` watches one process or background job
69
+ and ends when it exits.
70
+
71
+ A watcher starts with the oldest retained event, so opening one just
72
+ after starting work does not miss it.
73
+ """
74
+ native_run = run._native if run is not None else None
75
+ return self._native.stream(run=native_run, process=process)
76
+
77
+ async def start_background(self, command, *, policy=None):
78
+ """Start a manager-owned job and return its stable SupervisorProcessId.
79
+
80
+ Persistent jobs are stopped explicitly with ``shutdown_process`` or
81
+ ``shutdown_all``; ``Command.timeout`` does not end a background job.
82
+ The command's stdout and stderr byte limits still apply.
83
+ """
84
+ return await self._native.start_background(command, policy=policy)
85
+
86
+ def process_status(self, process_id):
87
+ """Return ``running``, ``completed``, ``failed``, or ``None`` if unknown."""
88
+ return self._native.process_status(process_id)
89
+
90
+ def process_result(self, process_id):
91
+ """Return a completed RunResult snapshot, or ``None`` while active/unknown."""
92
+ return self._native.process_result(process_id)
93
+
94
+ async def wait_process(self, process_id):
95
+ """Wait for a background job; return ``None`` if its ID is unknown."""
96
+ return await self._native.wait_process(process_id)
97
+
98
+ def take_process_result(self, process_id):
99
+ """Remove and return a completed result, or ``None`` if not completed."""
100
+ return self._native.take_process_result(process_id)
101
+
102
+ def forget_process(self, process_id):
103
+ """Forget a completed/failed result. Active jobs cannot be forgotten."""
104
+ return self._native.forget_process(process_id)
105
+
106
+ async def write_process_stdin(self, process_id, data):
107
+ """Write bytes to a managed job's stdin."""
108
+ return await self._native.write_process_stdin(process_id, data)
109
+
110
+ async def close_process_stdin(self, process_id):
111
+ """Close a managed job's stdin and deliver EOF."""
112
+ return await self._native.close_process_stdin(process_id)
113
+
114
+ async def shutdown_process(self, process_id, *, grace=5.0):
115
+ """Stop one process tree; return the stop request outcome string."""
116
+ return await self._native.shutdown_process(process_id, grace=grace)
117
+
118
+ async def shutdown_all(self, *, grace=5.0):
119
+ """Stop all jobs and return ``[(process_id, RunResult), ...]`` results."""
120
+ return await self._native.shutdown_all(grace=grace)
121
+
122
+
123
+ def _current_run_context():
124
+ from ._run_context import current_run
125
+ return current_run()
126
+
127
+
128
+ async def run(command, *, policy=None, input=None, cancellation=None):
129
+ """Run one command, collecting bounded stdout and stderr bytes."""
130
+ token = cancellation if cancellation is not None else CancellationToken()
131
+ run = _current_run_context()
132
+ operation = asyncio.ensure_future(
133
+ run.run_command(command, policy=policy, input=input) if run is not None
134
+ else _run_native(command, policy=policy, input=input, cancellation=token)
135
+ )
136
+ try:
137
+ return await asyncio.shield(operation)
138
+ except asyncio.CancelledError:
139
+ if run is not None:
140
+ await run.cancel()
141
+ else:
142
+ token.cancel()
143
+ try:
144
+ await asyncio.shield(operation)
145
+ except SupervisorError:
146
+ pass
147
+ raise
148
+
149
+
150
+ __all__ = [
151
+ "CancellationToken",
152
+ "AgentRunPolicy",
153
+ "AgentRun",
154
+ "Command",
155
+ "OutputEvent",
156
+ "Policy",
157
+ "Process",
158
+ "RunResult",
159
+ "SupervisorProcessId",
160
+ "SupervisorError",
161
+ "SupervisorEvent",
162
+ "Supervisor",
163
+ "platform_capabilities",
164
+ "run",
165
+ ]
@@ -0,0 +1,248 @@
1
+ """Shared lifecycle helpers for optional agent framework adapters."""
2
+
3
+ import asyncio
4
+ import inspect
5
+ import math
6
+
7
+ from .agent_run import AgentRunPolicy
8
+ from ._run_context import active_run
9
+
10
+
11
+ def supervision_settings(supervision):
12
+ if supervision is None:
13
+ return 300.0, 5.0
14
+ if not isinstance(supervision, dict):
15
+ raise TypeError("supervision must be a dict or None")
16
+ unknown = set(supervision) - {"timeout", "shutdown_grace"}
17
+ if unknown:
18
+ raise TypeError(f"unexpected supervision option: {sorted(unknown)[0]}")
19
+ timeout = supervision.get("timeout", 300.0)
20
+ grace = supervision.get("shutdown_grace", 5.0)
21
+ if (
22
+ not isinstance(timeout, (int, float))
23
+ or not math.isfinite(timeout)
24
+ or timeout <= 0
25
+ ):
26
+ raise ValueError("timeout must be a finite positive number of seconds")
27
+ if not isinstance(grace, (int, float)) or not 0 <= grace <= 60:
28
+ raise ValueError("shutdown_grace must be between 0 and 60")
29
+ return float(timeout), float(grace)
30
+
31
+
32
+ def new_run(supervision, supervisor=None):
33
+ timeout, grace = supervision_settings(supervision)
34
+ if supervisor is None:
35
+ from . import Supervisor
36
+
37
+ supervisor = Supervisor()
38
+ return supervisor.start_agent_run(AgentRunPolicy(timeout, grace))
39
+
40
+
41
+ async def invoke_run(operation, supervision=None, supervisor=None):
42
+ """Call a framework operation within a run owned by ``supervisor``."""
43
+ run = new_run(supervision, supervisor)
44
+ try:
45
+ return await run.run(operation())
46
+ finally:
47
+ await asyncio.shield(run.cancel())
48
+
49
+
50
+ _DONE = object()
51
+
52
+
53
+ class AgentEventPump:
54
+ """Read a framework's stream in the background.
55
+
56
+ Each event is published to watchers of the run and queued for the caller,
57
+ so watchers see agent events whether or not the caller reads the stream.
58
+ The queue is unbounded because dropping events would change what the caller
59
+ receives; the run's deadline limits how long it can grow.
60
+
61
+ ``on_finish(exc, completed)`` runs once when reading stops, in this task.
62
+ """
63
+
64
+ def __init__(self, iterator, run, deadline, on_finish):
65
+ self._iterator = iterator
66
+ self._run = run
67
+ self._deadline = deadline
68
+ self._on_finish = on_finish
69
+ self._queue = asyncio.Queue()
70
+ self._stop_exc = None
71
+ self._task = asyncio.ensure_future(self._read())
72
+
73
+ async def _read(self):
74
+ token = active_run.set(self._run)
75
+ loop = asyncio.get_running_loop()
76
+ failure = None
77
+ completed = False
78
+ try:
79
+ while True:
80
+ remaining = self._deadline - loop.time()
81
+ if remaining <= 0:
82
+ raise asyncio.TimeoutError("agent run exceeded its deadline")
83
+ try:
84
+ event = await asyncio.wait_for(self._iterator.__anext__(), remaining)
85
+ except StopAsyncIteration:
86
+ completed = True
87
+ break
88
+ self._run.publish_agent_event(event)
89
+ self._queue.put_nowait(event)
90
+ except asyncio.CancelledError:
91
+ failure = self._stop_exc
92
+ except BaseException as exc:
93
+ failure = exc
94
+ finally:
95
+ active_run.reset(token)
96
+ try:
97
+ await self._on_finish(failure, completed)
98
+ except BaseException as exc:
99
+ failure = failure or exc
100
+ self._queue.put_nowait(_Failure(failure) if failure is not None else _DONE)
101
+
102
+ async def get(self):
103
+ """Return the next event, or raise when the stream has ended."""
104
+ item = await self._queue.get()
105
+ if item is _DONE or isinstance(item, _Failure):
106
+ # Keep the end marker so later reads end the same way.
107
+ self._queue.put_nowait(item)
108
+ if item is _DONE:
109
+ raise StopAsyncIteration
110
+ raise item.exc
111
+ return item
112
+
113
+ async def wait(self):
114
+ """Wait until the framework's stream has been fully read."""
115
+ await asyncio.gather(self._task, return_exceptions=True)
116
+
117
+ async def stop(self, exc=None):
118
+ """Stop reading early. Cleanup runs in the reading task."""
119
+ if not self._task.done():
120
+ self._stop_exc = exc
121
+ self._task.cancel()
122
+ await self.wait()
123
+
124
+
125
+ class _Failure:
126
+ def __init__(self, exc):
127
+ self.exc = exc
128
+
129
+
130
+ def run_iterator(iterator, supervision=None, on_close=None, supervisor=None):
131
+ """Keep a native agent run alive while a framework's stream is read."""
132
+ timeout, grace = supervision_settings(supervision)
133
+ if supervisor is None:
134
+ from . import Supervisor
135
+
136
+ supervisor = Supervisor()
137
+ run = supervisor.start_agent_run(AgentRunPolicy(timeout, grace))
138
+ return AgentRunAsyncIterator(iterator, run, timeout, on_close)
139
+
140
+
141
+ def run_context_manager(open_context, supervision=None, supervisor=None):
142
+ """Keep a run active across ``open_context(run)``'s context manager."""
143
+ timeout, grace = supervision_settings(supervision)
144
+ run = new_run({"timeout": timeout, "shutdown_grace": grace}, supervisor)
145
+ return AgentRunAsyncContextManager(open_context(run), run, timeout)
146
+
147
+
148
+ class AgentRunAsyncIterator:
149
+ def __init__(self, iterator, run, timeout, on_close=None):
150
+ self._iterator = iterator.__aiter__()
151
+ self._run = run
152
+ self.agent_run = run
153
+ self._on_close = on_close
154
+ self._closed = False
155
+ deadline = asyncio.get_running_loop().time() + timeout
156
+ self._pump = AgentEventPump(self._iterator, run, deadline, self._finish)
157
+
158
+ def __aiter__(self):
159
+ return self
160
+
161
+ async def __anext__(self):
162
+ try:
163
+ return await self._pump.get()
164
+ except StopAsyncIteration:
165
+ raise
166
+ except BaseException as exc:
167
+ await self.aclose(exc)
168
+ raise
169
+
170
+ async def aclose(self, exc=None):
171
+ await self._pump.stop(exc)
172
+
173
+ async def _finish(self, exc, completed):
174
+ if self._closed:
175
+ return
176
+ self._closed = True
177
+ token = active_run.set(self._run)
178
+ try:
179
+ if self._on_close is not None:
180
+ result = self._on_close(exc, completed)
181
+ if inspect.isawaitable(result):
182
+ await result
183
+ close = getattr(self._iterator, "aclose", None)
184
+ if close is not None:
185
+ await close()
186
+ finally:
187
+ await asyncio.shield(self._run.cancel())
188
+ active_run.reset(token)
189
+
190
+ async def __aenter__(self):
191
+ return self
192
+
193
+ async def __aexit__(self, exc_type, exc, traceback):
194
+ await self.aclose(exc)
195
+
196
+
197
+ class AgentRunAsyncContextManager:
198
+ """Keep the run active across a native async context manager's body."""
199
+
200
+ def __init__(self, context_manager, run, timeout):
201
+ self._context_manager = context_manager
202
+ self._run = run
203
+ self.agent_run = run
204
+ self._timeout = timeout
205
+ self._token = None
206
+ self._timeout_context = None
207
+ self._entered = False
208
+ self._task = None
209
+ self._timer = None
210
+ self._expired = False
211
+
212
+ async def __aenter__(self):
213
+ self._token = active_run.set(self._run)
214
+ self._task = asyncio.current_task()
215
+ self._timer = asyncio.get_running_loop().call_later(
216
+ self._timeout, self._expire
217
+ )
218
+ try:
219
+ result = await self._context_manager.__aenter__()
220
+ self._entered = True
221
+ return result
222
+ except BaseException as exc:
223
+ await self._finish(type(exc), exc, exc.__traceback__)
224
+ raise
225
+
226
+ async def __aexit__(self, exc_type, exc, traceback):
227
+ return await self._finish(exc_type, exc, traceback)
228
+
229
+ async def _finish(self, exc_type, exc, traceback):
230
+ suppress = False
231
+ try:
232
+ if self._entered:
233
+ suppress = await self._context_manager.__aexit__(exc_type, exc, traceback)
234
+ finally:
235
+ if self._timer is not None:
236
+ self._timer.cancel()
237
+ if self._token is not None:
238
+ active_run.reset(self._token)
239
+ self._token = None
240
+ await asyncio.shield(self._run.cancel())
241
+ if self._expired and exc_type is asyncio.CancelledError:
242
+ raise asyncio.TimeoutError("agent run exceeded its deadline") from None
243
+ return suppress
244
+
245
+ def _expire(self):
246
+ self._expired = True
247
+ if self._task is not None:
248
+ self._task.cancel()
@@ -0,0 +1,11 @@
1
+ """Context shared by framework adapters and supervised local tools."""
2
+
3
+ from contextvars import ContextVar
4
+
5
+
6
+ active_run = ContextVar("agent_supervisor_run_context", default=None)
7
+
8
+
9
+ def current_run():
10
+ """Return the agent run active in this async context, if any."""
11
+ return active_run.get()