@steerable/runtime-win32-x64 0.9.1 → 0.10.0

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 (68) hide show
  1. package/bin/steerable-egress-proxy.exe +0 -0
  2. package/bin/win-spawn-helper.exe +0 -0
  3. package/package.json +1 -1
  4. package/python-runtime/manifest.json +1 -1
  5. package/python-runtime/python/Lib/site-packages/cffi-2.1.1.dist-info/RECORD +1 -1
  6. package/python-runtime/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
  7. package/python-runtime/python/Lib/site-packages/idna-3.20.dist-info/RECORD +1 -1
  8. package/python-runtime/python/Lib/site-packages/jsonschema-4.26.0.dist-info/RECORD +1 -1
  9. package/python-runtime/python/Lib/site-packages/mcp-1.30.0.dist-info/RECORD +1 -1
  10. package/python-runtime/python/Lib/site-packages/python_dotenv-1.2.4.dist-info/RECORD +1 -1
  11. package/python-runtime/python/Lib/site-packages/pywin32-312.dist-info/RECORD +2 -2
  12. package/python-runtime/python/Lib/site-packages/{steerable_agent_harness-0.9.1.dist-info → steerable_agent_harness-0.10.0.dist-info}/METADATA +1 -1
  13. package/python-runtime/python/Lib/site-packages/{steerable_agent_harness-0.9.1.dist-info → steerable_agent_harness-0.10.0.dist-info}/RECORD +7 -7
  14. package/python-runtime/python/Lib/site-packages/{steerable_agent_protocol-0.9.1.dist-info → steerable_agent_protocol-0.10.0.dist-info}/METADATA +1 -1
  15. package/python-runtime/python/Lib/site-packages/steerable_agent_protocol-0.10.0.dist-info/RECORD +11 -0
  16. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/__init__.py +48 -0
  17. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/budget.py +124 -0
  18. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/child_notice.py +84 -0
  19. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/child_record.py +174 -0
  20. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/engines.py +499 -0
  21. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/history.py +114 -1
  22. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/orchestration.py +496 -33
  23. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/pool.py +662 -16
  24. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/registry.py +345 -0
  25. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/storage/__init__.py +52 -0
  26. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/storage/in_memory.py +74 -0
  27. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/storage/sqlalchemy_store.py +153 -0
  28. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/storage/sqlite_store.py +117 -1
  29. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime/subagent.py +157 -6
  30. package/python-runtime/python/Lib/site-packages/{steerable_agent_runtime-0.9.1.dist-info → steerable_agent_runtime-0.10.0.dist-info}/METADATA +39 -4
  31. package/python-runtime/python/Lib/site-packages/{steerable_agent_runtime-0.9.1.dist-info → steerable_agent_runtime-0.10.0.dist-info}/RECORD +26 -16
  32. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime_native/steerable_agent_runtime_native.pyd +0 -0
  33. package/python-runtime/python/Lib/site-packages/{steerable_agent_runtime_native-1.0.7.dist-info → steerable_agent_runtime_native-1.0.8.dist-info}/METADATA +1 -1
  34. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime_native-1.0.8.dist-info/RECORD +9 -0
  35. package/python-runtime/python/Lib/site-packages/steerable_sidecar/sidecar.py +545 -6
  36. package/python-runtime/python/Lib/site-packages/steerable_sidecar-0.10.0.dist-info/METADATA +97 -0
  37. package/python-runtime/python/Lib/site-packages/{steerable_sidecar-0.9.1.dist-info → steerable_sidecar-0.10.0.dist-info}/RECORD +12 -12
  38. package/python-runtime/python/Lib/site-packages/uvicorn-0.54.0.dist-info/RECORD +1 -1
  39. package/python-runtime/python/Scripts/cffi-gen-src.exe +0 -0
  40. package/python-runtime/python/Scripts/dotenv.exe +0 -0
  41. package/python-runtime/python/Scripts/httpx.exe +0 -0
  42. package/python-runtime/python/Scripts/idna.exe +0 -0
  43. package/python-runtime/python/Scripts/jsonschema.exe +0 -0
  44. package/python-runtime/python/Scripts/mcp.exe +0 -0
  45. package/python-runtime/python/Scripts/pip.exe +0 -0
  46. package/python-runtime/python/Scripts/pip3.12.exe +0 -0
  47. package/python-runtime/python/Scripts/pip3.exe +0 -0
  48. package/python-runtime/python/Scripts/pywin32_postinstall.exe +0 -0
  49. package/python-runtime/python/Scripts/pywin32_testall.exe +0 -0
  50. package/python-runtime/python/Scripts/steerable-sidecar-acp.exe +0 -0
  51. package/python-runtime/python/Scripts/steerable-sidecar-headless.exe +0 -0
  52. package/python-runtime/python/Scripts/steerable-sidecar.exe +0 -0
  53. package/python-runtime/python/Scripts/uvicorn.exe +0 -0
  54. package/runtime.json +94 -70
  55. package/python-runtime/python/Lib/site-packages/steerable_agent_protocol-0.9.1.dist-info/RECORD +0 -11
  56. package/python-runtime/python/Lib/site-packages/steerable_agent_runtime_native-1.0.7.dist-info/RECORD +0 -9
  57. package/python-runtime/python/Lib/site-packages/steerable_sidecar-0.9.1.dist-info/METADATA +0 -58
  58. /package/python-runtime/python/Lib/site-packages/{steerable_agent_harness-0.9.1.dist-info → steerable_agent_harness-0.10.0.dist-info}/INSTALLER +0 -0
  59. /package/python-runtime/python/Lib/site-packages/{steerable_agent_harness-0.9.1.dist-info → steerable_agent_harness-0.10.0.dist-info}/WHEEL +0 -0
  60. /package/python-runtime/python/Lib/site-packages/{steerable_agent_protocol-0.9.1.dist-info → steerable_agent_protocol-0.10.0.dist-info}/INSTALLER +0 -0
  61. /package/python-runtime/python/Lib/site-packages/{steerable_agent_protocol-0.9.1.dist-info → steerable_agent_protocol-0.10.0.dist-info}/WHEEL +0 -0
  62. /package/python-runtime/python/Lib/site-packages/{steerable_agent_runtime-0.9.1.dist-info → steerable_agent_runtime-0.10.0.dist-info}/INSTALLER +0 -0
  63. /package/python-runtime/python/Lib/site-packages/{steerable_agent_runtime-0.9.1.dist-info → steerable_agent_runtime-0.10.0.dist-info}/WHEEL +0 -0
  64. /package/python-runtime/python/Lib/site-packages/{steerable_agent_runtime_native-1.0.7.dist-info → steerable_agent_runtime_native-1.0.8.dist-info}/INSTALLER +0 -0
  65. /package/python-runtime/python/Lib/site-packages/{steerable_agent_runtime_native-1.0.7.dist-info → steerable_agent_runtime_native-1.0.8.dist-info}/WHEEL +0 -0
  66. /package/python-runtime/python/Lib/site-packages/{steerable_sidecar-0.9.1.dist-info → steerable_sidecar-0.10.0.dist-info}/INSTALLER +0 -0
  67. /package/python-runtime/python/Lib/site-packages/{steerable_sidecar-0.9.1.dist-info → steerable_sidecar-0.10.0.dist-info}/WHEEL +0 -0
  68. /package/python-runtime/python/Lib/site-packages/{steerable_sidecar-0.9.1.dist-info → steerable_sidecar-0.10.0.dist-info}/entry_points.txt +0 -0
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/runtime-win32-x64",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Steerable runtime bundle for win32-x64: Python sidecar and egress proxy.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://steerableframework.com/",
@@ -3,6 +3,6 @@
3
3
  "pythonBuildStandaloneRelease": "20260510",
4
4
  "pythonVersion": "3.12.13",
5
5
  "target": "win32-x64",
6
- "totalBytes": 109573820,
6
+ "totalBytes": 109729430,
7
7
  "triple": "x86_64-pc-windows-msvc"
8
8
  }
@@ -1,4 +1,4 @@
1
- ../../Scripts/cffi-gen-src.exe,sha256=HZnGosg9lmx4M79AtRgIlPR7uKRZ6S2HwTDHZmRHtvo,108388
1
+ ../../Scripts/cffi-gen-src.exe,sha256=Zivsl8ko9fk244xL-CEH27FnFkFdvIPOeL-Z0KEgla0,108388
2
2
  _cffi_backend.cp312-win_amd64.pyd,sha256=mvXk9opB8mtyby6UJPnVik9WZhifaki-lfQGslcQKuw,181248
3
3
  cffi-2.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
4
4
  cffi-2.1.1.dist-info/METADATA,sha256=Z7anWV337eIZUBagXShfqn02Ko9XwjiYTtuLtnS1dw0,2594
@@ -1,4 +1,4 @@
1
- ../../Scripts/httpx.exe,sha256=4bx5dS7Px1nCDNyu1Y2LI60pPI9bR-TXRjtQmXRVVGI,108377
1
+ ../../Scripts/httpx.exe,sha256=w2krSROCLDam4SIizZ8h6560s0GWydAOPZQW4BJ-Q3c,108377
2
2
  httpx-0.28.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
3
  httpx-0.28.1.dist-info/METADATA,sha256=_rubD48-gNV8gZnDBPNcQzboWB0dGNeYPJJ2a4J5OyU,7052
4
4
  httpx-0.28.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
- ../../Scripts/idna.exe,sha256=ZHrMWSdoemNPeNS_eJBhAUtQfnv7odHXFqTaTN_UnRQ,108380
1
+ ../../Scripts/idna.exe,sha256=dQJXpAPBYX4AFcNaAmS8VSAnP2mIgB924WkoxxEJx2g,108380
2
2
  idna-3.20.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
3
  idna-3.20.dist-info/METADATA,sha256=29jBTB5MoeDJgkptvHy794iE8461LZEUjNMCX2ceS4U,7207
4
4
  idna-3.20.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
- ../../Scripts/jsonschema.exe,sha256=Aep-OJBaQ2qLq9wHb8HCk8DhpxlO1fV1pywHS3-sR58,108386
1
+ ../../Scripts/jsonschema.exe,sha256=4oCz5sBzF1xpGTjZmHd21IMH4hSNCs25w70qV2G4w5c,108386
2
2
  jsonschema-4.26.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
3
  jsonschema-4.26.0.dist-info/METADATA,sha256=BbGJhmypipbaOTS_a4NofO0_-FMlJzt34WtwX22LJtI,7592
4
4
  jsonschema-4.26.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
- ../../Scripts/mcp.exe,sha256=GzGzIxWShXPjgQdVsi94XtAkZbpDPGl9MF48mZ_Bb5k,108377
1
+ ../../Scripts/mcp.exe,sha256=xGljkG9hOH-0gBy8jSzFrq6vzIWwDcddahCaLEpcB5g,108377
2
2
  mcp-1.30.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
3
  mcp-1.30.0.dist-info/METADATA,sha256=OIDCmJYUmGKXGvV5QuPlwK0qkJaWpmuYEcYvbkfmURE,9289
4
4
  mcp-1.30.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
- ../../Scripts/dotenv.exe,sha256=Wsrre0e6HiwIQB16tIOMHRzvJcJuRMTivln6S8t7w2g,108385
1
+ ../../Scripts/dotenv.exe,sha256=Icvj6FrDFpujkrW7N_n1ga_vW45dYpJ_RdMfWzIs_fo,108385
2
2
  dotenv/__init__.py,sha256=bhY-iK6wwHZamWII6eSCuCAWXUNdn2TQFmEOuXSU7SM,1230
3
3
  dotenv/__main__.py,sha256=N0RhLG7nHIqtlJHwwepIo-zbJPNx9sewCCRGY528h_4,129
4
4
  dotenv/__pycache__/__init__.cpython-312.pyc,,
@@ -1,8 +1,8 @@
1
1
  ../../Scripts/__pycache__/pywin32_postinstall.cpython-312.pyc,,
2
2
  ../../Scripts/__pycache__/pywin32_testall.cpython-312.pyc,,
3
- ../../Scripts/pywin32_postinstall.exe,sha256=_m7g42qnypdrK-g9dM-hfAEtf3CdJsb6RdmZ0TecGaU,108405
3
+ ../../Scripts/pywin32_postinstall.exe,sha256=DXGtSVHBKZkR8yGUbR0l8brfflJp4tABr3oic5np7JU,108405
4
4
  ../../Scripts/pywin32_postinstall.py,sha256=RT3RogIBArMUhRCFaivWkl2NsVZwr9cNNd2hcsXUBU4,25576
5
- ../../Scripts/pywin32_testall.exe,sha256=Kssd92sMZeETJ1isNnelH_lFHjTgytK5-iD4i9iJoi8,108401
5
+ ../../Scripts/pywin32_testall.exe,sha256=cm5gX-G_W9N1YD_wIaSCpl7G2wFteflaJSH4q6tF6uI,108401
6
6
  ../../Scripts/pywin32_testall.py,sha256=FYvjx43thdvbkWaYiLMTX39RxsFL0c-1o-xY2udlBqA,3847
7
7
  PyWin32.chm,sha256=X8MAsKaFhJRAzKBn0QR-GEXRoShgndE6NQg6LqhzTVI,2642799
8
8
  __pycache__/pythoncom.cpython-312.pyc,,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerable-agent-harness
3
- Version: 0.9.1
3
+ Version: 0.10.0
4
4
  Summary: Steerable harness helpers for Python
5
5
  License: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -1,10 +1,10 @@
1
- steerable_agent_harness-0.9.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
- steerable_agent_harness-0.9.1.dist-info/METADATA,sha256=caazIRSA4mHjBhJy_x0Ps3uRerbIN63MDtrl74Pr2A0,384
3
- steerable_agent_harness-0.9.1.dist-info/RECORD,,
4
- steerable_agent_harness-0.9.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- steerable_agent_harness-0.9.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
6
- steerable_agent_harness-0.9.1.dist-info/direct_url.json,sha256=wXlgb6OuAyzAA_zryk3_o4zE6mDdMNpNe2nDrgdm_xA,289
7
- steerable_agent_harness-0.9.1.dist-info/top_level.txt,sha256=L99GRcOHaeL_gakhZZnmpEbK5yV4fOeIxXzWKW_2lDA,24
1
+ steerable_agent_harness-0.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ steerable_agent_harness-0.10.0.dist-info/METADATA,sha256=hezhQUaNKGROfqHgUMQfwBLzvMkqiqhACLvEBXS-liw,385
3
+ steerable_agent_harness-0.10.0.dist-info/RECORD,,
4
+ steerable_agent_harness-0.10.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ steerable_agent_harness-0.10.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
6
+ steerable_agent_harness-0.10.0.dist-info/direct_url.json,sha256=UXvoylUbK4Lkfuzi3TRjKqrGCVAmNd-ebjMbkos5cAk,290
7
+ steerable_agent_harness-0.10.0.dist-info/top_level.txt,sha256=L99GRcOHaeL_gakhZZnmpEbK5yV4fOeIxXzWKW_2lDA,24
8
8
  steerable_agent_harness/__init__.py,sha256=hZK9IhKIFS66jIYH9LKMNuGmdJkLEeE5LsPeRPcLl6E,1253
9
9
  steerable_agent_harness/__pycache__/__init__.cpython-312.pyc,,
10
10
  steerable_agent_harness/__pycache__/budget.cpython-312.pyc,,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerable-agent-protocol
3
- Version: 0.9.1
3
+ Version: 0.10.0
4
4
  Summary: Steerable protocol schemas for Python
5
5
  License: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -0,0 +1,11 @@
1
+ steerable_agent_protocol-0.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ steerable_agent_protocol-0.10.0.dist-info/METADATA,sha256=IgT9xICSjhelATS_pyfkbK_Ou1UlVBlhn2kPJbItGYg,328
3
+ steerable_agent_protocol-0.10.0.dist-info/RECORD,,
4
+ steerable_agent_protocol-0.10.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ steerable_agent_protocol-0.10.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
6
+ steerable_agent_protocol-0.10.0.dist-info/direct_url.json,sha256=e_xB1q2-B5msly9-DBUbHPG6yqDTLnFuKp6JrevO-rA,291
7
+ steerable_agent_protocol-0.10.0.dist-info/top_level.txt,sha256=K8P6-3hLZwuhUEWM2CS2wjFpBzJ83fyzZbdS2Txh4VU,25
8
+ steerable_agent_protocol/__init__.py,sha256=adci5VbbNw9seedRNj8OsibxVH3B1M531_adq3fIU1c,25
9
+ steerable_agent_protocol/__pycache__/__init__.cpython-312.pyc,,
10
+ steerable_agent_protocol/__pycache__/generated.cpython-312.pyc,,
11
+ steerable_agent_protocol/generated.py,sha256=jeynKncOP0HWEpXcFs7A7y3z4zloR0njCar2gMcBfbo,6005
@@ -83,6 +83,7 @@ from .branch import (
83
83
  lineage,
84
84
  resolve_fork_seq,
85
85
  )
86
+ from .budget import SessionBudgetExceeded, TreeBudget, TreeBudgetReport
86
87
  from .cache_control import (
87
88
  CacheControlProvider,
88
89
  CacheDriftMonitor,
@@ -91,6 +92,22 @@ from .cache_control import (
91
92
  system_blocks_with_cache,
92
93
  )
93
94
  from .calibration import CalibratingProvider, ModelCalibration, UsageCalibration
95
+ from .child_record import (
96
+ INTERRUPTED_CHILD_STATUS,
97
+ RESUMABLE_CHILD_STATUSES,
98
+ TERMINAL_CHILD_STATUSES,
99
+ ChildRecord,
100
+ )
101
+ from .child_notice import CHILD_NOTICE_KIND, ChildNoticeHooks, render_child_notice
102
+ from .engines import (
103
+ AcpEngineSpec,
104
+ AcpOutcome,
105
+ AcpPermissionPolicy,
106
+ AcpToolBridge,
107
+ AcpUnavailable,
108
+ PermissionDiagnostic,
109
+ run_acp_child,
110
+ )
94
111
  from .compaction_policy import (
95
112
  CompactionPolicy,
96
113
  is_large_window,
@@ -121,6 +138,7 @@ from .history import (
121
138
  RecordEntry,
122
139
  RecordFormatError,
123
140
  SystemPromptFragment,
141
+ completed_turn_prefix,
124
142
  entry_from_dict,
125
143
  render_fragment_capped,
126
144
  entry_to_dict,
@@ -209,6 +227,13 @@ from .pricing import (
209
227
  register_model_price,
210
228
  )
211
229
  from .pseudo import extract_inline_tool_calls
230
+ from .registry import (
231
+ ChildRegistry,
232
+ ChildStatusView,
233
+ SpawnedChild,
234
+ default_record_id,
235
+ registry_status,
236
+ )
212
237
  from .recording import (
213
238
  DEFAULT_MAX_ITEM_TOKENS,
214
239
  InMemoryRequestSink,
@@ -292,6 +317,13 @@ from .world_state import (
292
317
  )
293
318
 
294
319
  __all__ = [
320
+ "AcpEngineSpec",
321
+ "AcpOutcome",
322
+ "AcpPermissionPolicy",
323
+ "AcpToolBridge",
324
+ "AcpUnavailable",
325
+ "PermissionDiagnostic",
326
+ "run_acp_child",
295
327
  "DEFAULT_MAX_ITEM_TOKENS",
296
328
  "DEFAULT_MAX_SERVER_TOOLS",
297
329
  "DEFAULT_SHELL_TOOLS",
@@ -338,6 +370,21 @@ __all__ = [
338
370
  "CacheControlProvider",
339
371
  "CacheDriftMonitor",
340
372
  "CacheRetention",
373
+ "ChildRecord",
374
+ "ChildRegistry",
375
+ "ChildNoticeHooks",
376
+ "CHILD_NOTICE_KIND",
377
+ "SessionBudgetExceeded",
378
+ "TreeBudget",
379
+ "TreeBudgetReport",
380
+ "render_child_notice",
381
+ "ChildStatusView",
382
+ "SpawnedChild",
383
+ "default_record_id",
384
+ "registry_status",
385
+ "INTERRUPTED_CHILD_STATUS",
386
+ "RESUMABLE_CHILD_STATUSES",
387
+ "TERMINAL_CHILD_STATUSES",
341
388
  "CalibratingProvider",
342
389
  "ChainHooks",
343
390
  "ChildOutcome",
@@ -354,6 +401,7 @@ __all__ = [
354
401
  "ContentPart",
355
402
  "ContextFragment",
356
403
  "ContextManager",
404
+ "completed_turn_prefix",
357
405
  "CoreLoop",
358
406
  "DecisionNote",
359
407
  "ExecutionBudget",
@@ -0,0 +1,124 @@
1
+ """One token budget for a whole delegating turn tree.
2
+
3
+ ``steerable_agent_harness.budget`` budgets **one loop run**: it charges that
4
+ run's own requests and reports exhaustion to that run. That is the wrong scope
5
+ once a turn can delegate: a parent plus three children would each get the full
6
+ allowance, so a turn's real spend could be four times what the host asked for
7
+ and no single loop would ever notice.
8
+
9
+ This module is the other scope — one counter shared by every agent in the tree.
10
+ Each request, whoever made it, charges the same state, and the first one to
11
+ cross the limit fails there. The alternative (handing each child a slice of
12
+ the remainder) was considered and rejected: a serial child given 1/(n+1) of
13
+ the budget hits its own wall while the tree still has room, which turns a
14
+ generous budget into a tight one.
15
+
16
+ Attribution is kept alongside the total because "the turn spent 40k" is not
17
+ actionable when a host needs to know which child spent it.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import threading
23
+ from dataclasses import dataclass, field
24
+
25
+ from .llm import LLMUsage
26
+
27
+ __all__ = ["SessionBudgetExceeded", "TreeBudget", "TreeBudgetReport"]
28
+
29
+
30
+ class SessionBudgetExceeded(Exception):
31
+ """Raised when a request would take the tree past its token limit.
32
+
33
+ Named for the session, not for a child: the limit belongs to the whole
34
+ turn tree, and the agent that happens to cross it is not the one at fault.
35
+ """
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class TreeBudgetReport:
40
+ """The tree's spend at one point in time.
41
+
42
+ ``by_agent`` keys are the caller's own agent labels (``"parent"``,
43
+ ``"0.1"`` …) — the budget does not know about lineage and must not guess
44
+ it, so it stores whatever key the caller passes.
45
+ """
46
+
47
+ limit: int
48
+ spent: int
49
+ by_agent: dict[str, int]
50
+
51
+ @property
52
+ def remaining(self) -> int:
53
+ return max(self.limit - self.spent, 0)
54
+
55
+ @property
56
+ def exhausted(self) -> bool:
57
+ return self.spent >= self.limit
58
+
59
+
60
+ @dataclass
61
+ class TreeBudget:
62
+ """A token limit shared by every agent in one turn tree.
63
+
64
+ Thread-safe: the loop hands usage in from both the parent's event loop and
65
+ the worker thread that drives the native engine, so plain attribute
66
+ updates would race.
67
+ """
68
+
69
+ limit: int
70
+ _spent: int = field(default=0, init=False)
71
+ _by_agent: dict[str, int] = field(default_factory=dict, init=False)
72
+ _lock: threading.Lock = field(default_factory=threading.Lock, init=False)
73
+
74
+ def charge(self, usage: LLMUsage | None, *, agent: str) -> int:
75
+ """Charge one request's usage; returns the tree's spend afterwards.
76
+
77
+ ``usage=None`` charges nothing but still records the agent, so a host
78
+ can tell "this child ran and reported nothing" apart from "this child
79
+ never ran" — the distinction matters when the bill does not add up.
80
+
81
+ Raises:
82
+ SessionBudgetExceeded: when the charge takes the tree past
83
+ ``limit``. The spend is recorded **before** raising, so the
84
+ report a caller shows still includes the request that broke it.
85
+ """
86
+ if usage is None:
87
+ with self._lock:
88
+ self._by_agent.setdefault(agent, 0)
89
+ return self._spent
90
+ # Total, not prompt+completion: providers disagree about whether the
91
+ # total includes cache reads, and ``usage.total_tokens`` is the one
92
+ # number every provider reports consistently.
93
+ billed = max(usage.total_tokens, 0)
94
+ with self._lock:
95
+ self._spent += billed
96
+ self._by_agent[agent] = self._by_agent.get(agent, 0) + billed
97
+ spent = self._spent
98
+ if spent > self.limit:
99
+ raise SessionBudgetExceeded(
100
+ f"turn tree spent {spent} tokens against a {self.limit}-token "
101
+ f"budget (agent {agent!r} crossed it)"
102
+ )
103
+ return spent
104
+
105
+ @property
106
+ def remaining(self) -> int:
107
+ """Tokens left, floored at zero."""
108
+ with self._lock:
109
+ return max(self.limit - self._spent, 0)
110
+
111
+ @property
112
+ def exhausted(self) -> bool:
113
+ """True once the tree has spent its whole limit."""
114
+ with self._lock:
115
+ return self._spent >= self.limit
116
+
117
+ def report(self) -> TreeBudgetReport:
118
+ """A snapshot, safe to hand to a host mid-turn."""
119
+ with self._lock:
120
+ return TreeBudgetReport(
121
+ limit=self.limit,
122
+ spent=self._spent,
123
+ by_agent=dict(self._by_agent),
124
+ )
@@ -0,0 +1,84 @@
1
+ """Parent-side delivery of child-agent terminal notices.
2
+
3
+ A parent that spawned children has two ways to learn one finished: poll
4
+ ``agent_wait``, or be told. Polling costs a whole model round per check and,
5
+ worse, the model has to *remember* to look. This hook closes that gap: when a
6
+ child of this pool settles, the notice is appended to the parent's transcript
7
+ at its next round boundary.
8
+
9
+ Design notes that matter for correctness:
10
+
11
+ - The notice is a **system-role** message with its own record kind
12
+ (``subagent.notice``). It is not a user message: nothing about it came from
13
+ the user, and a model that treats it as an instruction from the user would
14
+ be misreading the conversation.
15
+ - The notice carries **no child text**. The answer still comes from
16
+ ``agent_wait``; putting the answer here would duplicate the child's output
17
+ into the parent's context on every finish, and the parent may never read it.
18
+ - Delivery is idempotent per child: each notice is drained exactly once, so a
19
+ settled child cannot re-notify on every later step.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import TYPE_CHECKING
25
+
26
+ from .hooks import NoopHooks, PreStepAction, TranscriptAppend
27
+ from .llm import LLMMessage
28
+
29
+ if TYPE_CHECKING: # pragma: no cover - typing only
30
+ from .loop import LoopContext
31
+ from .pool import AgentPool
32
+
33
+ __all__ = ["CHILD_NOTICE_KIND", "ChildNoticeHooks"]
34
+
35
+ #: Record kind for the injected notice. ``<feature>.<name>`` like the other
36
+ #: injected fragments (``skills.catalog``, ``world_state.patch``), so a replay
37
+ #: can tell it apart from anything the user or the model said.
38
+ CHILD_NOTICE_KIND = "subagent.notice"
39
+
40
+
41
+ class ChildNoticeHooks(NoopHooks):
42
+ """``pre_step``: append notices for children that settled since last step.
43
+
44
+ One instance per parent loop. Wrap it in a ``ChainHooks`` with whatever
45
+ else the host installs — this hook only ever appends; it never rewrites,
46
+ never rejects, and never changes tool choice.
47
+ """
48
+
49
+ def __init__(self, pool: AgentPool) -> None:
50
+ self._pool = pool
51
+
52
+ async def pre_step(
53
+ self, transcript: list[LLMMessage], ctx: "LoopContext"
54
+ ) -> PreStepAction:
55
+ notices = self._pool.drain_notices()
56
+ if not notices:
57
+ return PreStepAction(kind="proceed")
58
+ appends = [
59
+ TranscriptAppend(
60
+ message=LLMMessage.text_of("system", render_child_notice(*notice)),
61
+ kind=CHILD_NOTICE_KIND,
62
+ )
63
+ for notice in notices
64
+ ]
65
+ return PreStepAction(
66
+ kind="proceed",
67
+ appends=appends,
68
+ append_action="child_notice",
69
+ )
70
+
71
+
72
+ def render_child_notice(child_id: str, status: str, answer: str) -> str:
73
+ """The notice text.
74
+
75
+ A system message, so it reads as harness bookkeeping rather than as either
76
+ party speaking. The one line of guidance matters: without it a model that
77
+ sees "child 0.2 completed" tends to narrate the completion instead of
78
+ collecting the answer.
79
+ """
80
+ settled = "finished" if status == "completed" else f"ended with status {status}"
81
+ return (
82
+ f"[sub-agent notice] Sub-agent {child_id} {settled}. "
83
+ "Its answer is not included here; call agent_wait to read it."
84
+ )
@@ -0,0 +1,174 @@
1
+ """The child-agent registry record: one shape, shared by every store.
2
+
3
+ A record answers only "whose child is this, how deep, what built it, and
4
+ where it ended". Task text and answers are deliberately absent — they live
5
+ in the child's own ``history`` record, so each fact has one authoritative
6
+ copy.
7
+
8
+ This module is a leaf: no storage and no agent imports. Both the registry
9
+ (``registry.py``) and the adapters (``storage/sqlite_store.py``) read these
10
+ definitions, which is what keeps their column mapping from drifting.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, replace
16
+ from typing import Any
17
+
18
+ #: Statuses that will never change again. ``running`` is absent on purpose:
19
+ #: it is derived from a live in-process handle, never written, so a killed
20
+ #: process cannot leave a permanently "running" row behind.
21
+ TERMINAL_CHILD_STATUSES: frozenset[str] = frozenset(
22
+ {
23
+ "completed",
24
+ "error",
25
+ "cancelled",
26
+ "budget_exhausted",
27
+ "closed",
28
+ }
29
+ )
30
+
31
+ #: Derived, never written: the child has no terminal row and no live handle.
32
+ #: The parent turn ended or the process died mid-run; ``agent_send`` resumes it.
33
+ INTERRUPTED_CHILD_STATUS = "interrupted"
34
+
35
+ #: Statuses that accept a further message. ``closed`` is excluded: it is the
36
+ #: caller's explicit "never again" and must stay that way.
37
+ RESUMABLE_CHILD_STATUSES: frozenset[str] = frozenset(
38
+ {
39
+ "completed",
40
+ "interrupted",
41
+ "cancelled",
42
+ "budget_exhausted",
43
+ }
44
+ )
45
+
46
+
47
+ @dataclass(frozen=True, slots=True)
48
+ class ChildRecord:
49
+ """One registry row.
50
+
51
+ ``record_id`` is the child's own durable record (``<parent>:child:<lineage
52
+ id>``); ``child_id`` is the lineage id inside the parent's pool. ``depth``
53
+ and ``profile`` are duplicated from creation time on purpose: resume must
54
+ rebuild the child's tool domain and nesting from what it was created with,
55
+ never from current settings — otherwise a depth-1 child that was never
56
+ allowed to spawn would gain orchestration tools by being resumed.
57
+ """
58
+
59
+ record_id: str
60
+ parent_record_id: str
61
+ child_id: str
62
+ depth: int
63
+ profile: str | None
64
+ engine: str
65
+ status: str
66
+ resumable: bool
67
+ prompt_tokens: int | None
68
+ completion_tokens: int | None
69
+ usage_known: bool
70
+ created_at: str
71
+ updated_at: str
72
+
73
+ @property
74
+ def terminal(self) -> bool:
75
+ """True when this status will never change again."""
76
+ return self.status in TERMINAL_CHILD_STATUSES
77
+
78
+ def with_terminal_status(
79
+ self,
80
+ *,
81
+ status: str,
82
+ prompt_tokens: int | None,
83
+ completion_tokens: int | None,
84
+ usage_known: bool,
85
+ resumable: bool,
86
+ updated_at: str,
87
+ ) -> ChildRecord:
88
+ """This record after a run settled."""
89
+ return replace(
90
+ self,
91
+ status=status,
92
+ prompt_tokens=prompt_tokens,
93
+ completion_tokens=completion_tokens,
94
+ usage_known=usage_known,
95
+ resumable=resumable,
96
+ updated_at=updated_at,
97
+ )
98
+
99
+ def to_wire(self) -> dict[str, Any]:
100
+ """Camel-case payload shape for ``agent_list`` and the ``children`` CLI.
101
+
102
+ The row is already model-facing (it says nothing about storage), so
103
+ this is the only translation step between disk and the protocol.
104
+ """
105
+ return {
106
+ "recordId": self.record_id,
107
+ "parentRecordId": self.parent_record_id,
108
+ "childId": self.child_id,
109
+ "depth": self.depth,
110
+ "profile": self.profile,
111
+ "engine": self.engine,
112
+ "status": self.status,
113
+ "resumable": self.resumable,
114
+ "promptTokens": self.prompt_tokens,
115
+ "completionTokens": self.completion_tokens,
116
+ "usageKnown": self.usage_known,
117
+ "createdAt": self.created_at,
118
+ "updatedAt": self.updated_at,
119
+ }
120
+
121
+
122
+ #: Record attribute → SQL column, in insert order. Adapters use this to bind
123
+ #: one statement; ``child_row.to_wire`` uses the attribute names for the wire.
124
+ CHILD_COLUMNS: tuple[tuple[str, str], ...] = (
125
+ ("record_id", "record_id"),
126
+ ("parent_record_id", "parent_record_id"),
127
+ ("child_id", "child_id"),
128
+ ("depth", "depth"),
129
+ ("profile", "profile"),
130
+ ("engine", "engine"),
131
+ ("status", "status"),
132
+ ("resumable", "resumable"),
133
+ ("prompt_tokens", "prompt_tokens"),
134
+ ("completion_tokens", "completion_tokens"),
135
+ ("usage_known", "usage_known"),
136
+ ("created_at", "created_at"),
137
+ ("updated_at", "updated_at"),
138
+ )
139
+
140
+ _INT_FIELDS = frozenset({"depth"})
141
+ _BOOL_FIELDS = frozenset({"resumable", "usage_known"})
142
+
143
+
144
+ def record_to_values(record: ChildRecord) -> tuple[Any, ...]:
145
+ """Positional values for the insert whose columns are ``CHILD_COLUMNS``.
146
+
147
+ SQLite has no boolean type, so flags bind as 0/1 here rather than at each
148
+ call site.
149
+ """
150
+ values: list[Any] = []
151
+ for attribute, _column in CHILD_COLUMNS:
152
+ value = getattr(record, attribute)
153
+ if attribute in _BOOL_FIELDS:
154
+ values.append(int(bool(value)))
155
+ else:
156
+ values.append(value)
157
+ return tuple(values)
158
+
159
+
160
+ def record_from_columns(columns: dict[str, Any]) -> ChildRecord:
161
+ """Rebuild a record from a store's column mapping.
162
+
163
+ Flags arrive as 0/1 from SQLite and as ``bool`` from SQLAlchemy; both are
164
+ normalised here so callers never branch on the storage engine.
165
+ """
166
+ values: dict[str, Any] = {}
167
+ for attribute, column in CHILD_COLUMNS:
168
+ value = columns[column]
169
+ if attribute in _INT_FIELDS:
170
+ value = int(value)
171
+ elif attribute in _BOOL_FIELDS:
172
+ value = bool(value)
173
+ values[attribute] = value
174
+ return ChildRecord(**values)