rio 0.4.0__py3-none-any.whl

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 (140) hide show
  1. rio-0.4.0.dist-info/METADATA +90 -0
  2. rio-0.4.0.dist-info/RECORD +140 -0
  3. rio-0.4.0.dist-info/WHEEL +4 -0
  4. rio-0.4.0.dist-info/entry_points.txt +2 -0
  5. rio-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. rio-0.4.0.dist-info/licenses/NOTICE +37 -0
  7. rio_agent/__init__.py +47 -0
  8. rio_agent/errors.py +29 -0
  9. rio_agent/events.py +95 -0
  10. rio_agent/harness.py +125 -0
  11. rio_agent/loop.py +199 -0
  12. rio_agent/observation.py +24 -0
  13. rio_agent/prompt.py +111 -0
  14. rio_agent/py.typed +0 -0
  15. rio_agent/skill.py +27 -0
  16. rio_agent/state.py +50 -0
  17. rio_ai/__init__.py +81 -0
  18. rio_ai/_provider_events.py +93 -0
  19. rio_ai/anthropic.py +771 -0
  20. rio_ai/content.py +45 -0
  21. rio_ai/env.py +158 -0
  22. rio_ai/events.py +37 -0
  23. rio_ai/fake.py +41 -0
  24. rio_ai/google.py +522 -0
  25. rio_ai/http.py +81 -0
  26. rio_ai/http_errors.py +65 -0
  27. rio_ai/messages.py +286 -0
  28. rio_ai/mistral.py +525 -0
  29. rio_ai/model_limits.py +48 -0
  30. rio_ai/openai_cache.py +20 -0
  31. rio_ai/openai_codex.py +1080 -0
  32. rio_ai/openai_compatible.py +1387 -0
  33. rio_ai/provider.py +37 -0
  34. rio_ai/provider_events.py +107 -0
  35. rio_ai/py.typed +0 -0
  36. rio_ai/retry.py +62 -0
  37. rio_ai/stream.py +212 -0
  38. rio_ai/tool_call_ids.py +24 -0
  39. rio_ai/tools.py +118 -0
  40. rio_ai/types.py +8 -0
  41. rio_coding/__init__.py +306 -0
  42. rio_coding/__main__.py +5 -0
  43. rio_coding/auth_commands.py +72 -0
  44. rio_coding/branch_summary.py +79 -0
  45. rio_coding/built_in_extensions.py +114 -0
  46. rio_coding/catalog_loader.py +763 -0
  47. rio_coding/cli.py +667 -0
  48. rio_coding/coding_skill.py +129 -0
  49. rio_coding/commands.py +956 -0
  50. rio_coding/context.py +95 -0
  51. rio_coding/credentials.py +260 -0
  52. rio_coding/data/catalog.toml +6882 -0
  53. rio_coding/data/docs/README.md +14 -0
  54. rio_coding/data/docs/architecture.md +105 -0
  55. rio_coding/data/docs/cli.md +42 -0
  56. rio_coding/data/docs/extensions.md +38 -0
  57. rio_coding/data/docs/local-inference.md +9 -0
  58. rio_coding/data/docs/models.md +23 -0
  59. rio_coding/data/docs/security.md +13 -0
  60. rio_coding/data/docs/skills.md +32 -0
  61. rio_coding/data/examples/offline_session.py +47 -0
  62. rio_coding/data/models-dev-catalog.json +15937 -0
  63. rio_coding/data/release-notes/releases.json +16 -0
  64. rio_coding/diagnostics.py +183 -0
  65. rio_coding/events.py +121 -0
  66. rio_coding/extension_installer.py +200 -0
  67. rio_coding/extensions/__init__.py +235 -0
  68. rio_coding/extensions/api.py +1173 -0
  69. rio_coding/extensions/builtins/__init__.py +1 -0
  70. rio_coding/extensions/builtins/llama_cpp/__init__.py +87 -0
  71. rio_coding/extensions/builtins/llama_cpp/huggingface.py +217 -0
  72. rio_coding/extensions/builtins/llama_cpp/router.py +315 -0
  73. rio_coding/extensions/builtins/llama_cpp/service.py +1763 -0
  74. rio_coding/extensions/builtins/llama_cpp/state.py +411 -0
  75. rio_coding/extensions/loader.py +405 -0
  76. rio_coding/extensions/provider_registry.py +650 -0
  77. rio_coding/extensions/providers.py +527 -0
  78. rio_coding/extensions/runtime.py +1282 -0
  79. rio_coding/extensions/startup.py +65 -0
  80. rio_coding/frontend_session.py +216 -0
  81. rio_coding/image_processing.py +253 -0
  82. rio_coding/local_backends.py +1299 -0
  83. rio_coding/models_dev.py +335 -0
  84. rio_coding/models_dev_store.py +207 -0
  85. rio_coding/oauth.py +528 -0
  86. rio_coding/oauth_anthropic.py +288 -0
  87. rio_coding/oauth_device.py +109 -0
  88. rio_coding/oauth_github_copilot.py +285 -0
  89. rio_coding/oauth_registry.py +55 -0
  90. rio_coding/oauth_types.py +127 -0
  91. rio_coding/paths.py +150 -0
  92. rio_coding/project_trust.py +703 -0
  93. rio_coding/prompt_templates.py +285 -0
  94. rio_coding/provider_catalog.py +119 -0
  95. rio_coding/provider_config.py +2617 -0
  96. rio_coding/provider_runtime.py +534 -0
  97. rio_coding/py.typed +0 -0
  98. rio_coding/reload.py +31 -0
  99. rio_coding/rendering/__init__.py +28 -0
  100. rio_coding/rendering/base.py +45 -0
  101. rio_coding/rendering/json.py +72 -0
  102. rio_coding/rendering/plain.py +103 -0
  103. rio_coding/rendering/steps.py +116 -0
  104. rio_coding/resources.py +376 -0
  105. rio_coding/rpc.py +596 -0
  106. rio_coding/self_docs.py +23 -0
  107. rio_coding/session.py +795 -0
  108. rio_coding/session_export.py +634 -0
  109. rio_coding/session_manager.py +383 -0
  110. rio_coding/session_preparation.py +85 -0
  111. rio_coding/session_runner.py +486 -0
  112. rio_coding/session_stats.py +99 -0
  113. rio_coding/session_store/__init__.py +51 -0
  114. rio_coding/session_store/entries.py +186 -0
  115. rio_coding/session_store/jsonl.py +40 -0
  116. rio_coding/session_store/storage.py +203 -0
  117. rio_coding/session_store/tree.py +90 -0
  118. rio_coding/session_usage.py +207 -0
  119. rio_coding/shell_config.py +74 -0
  120. rio_coding/skills.py +254 -0
  121. rio_coding/step_footprint.py +148 -0
  122. rio_coding/system_prompt.py +308 -0
  123. rio_coding/thinking.py +93 -0
  124. rio_coding/tools.py +1376 -0
  125. rio_coding/update_check.py +379 -0
  126. rio_coding/updater.py +387 -0
  127. rio_coding/version.py +16 -0
  128. rio_tui/__init__.py +6 -0
  129. rio_tui/__main__.py +3 -0
  130. rio_tui/app.py +219 -0
  131. rio_tui/app.tcss +74 -0
  132. rio_tui/bridge.py +219 -0
  133. rio_tui/dialogs.py +267 -0
  134. rio_tui/session.py +510 -0
  135. rio_tui/settings.py +48 -0
  136. rio_tui/widgets/__init__.py +1 -0
  137. rio_tui/widgets/conversation.py +134 -0
  138. rio_tui/widgets/prompt.py +192 -0
  139. rio_tui/widgets/sidebar.py +55 -0
  140. rio_tui/widgets/terminal.py +191 -0
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.5
2
+ Name: rio
3
+ Version: 0.4.0
4
+ Summary: rio_ai: a ported multi-provider LLM streaming SDK. rio_agent: a SKILL.state long-horizon agent runtime (arXiv:2608.26263). rio_coding: a SKILL.state coding agent.
5
+ Project-URL: Repository, https://github.com/soasme/rio
6
+ Author-email: Ju Lin <soasme@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ License-File: NOTICE
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: anyio>=4.0
12
+ Requires-Dist: httpx[socks]>=0.27
13
+ Requires-Dist: packaging>=24.0
14
+ Requires-Dist: pillow>=11.0
15
+ Requires-Dist: pydantic>=2.11
16
+ Requires-Dist: pygments>=2.18
17
+ Requires-Dist: pyte>=0.8.2
18
+ Requires-Dist: rich>=13.0
19
+ Requires-Dist: textual-diff-view>=0.1.5
20
+ Requires-Dist: textual>=8.2.8
21
+ Requires-Dist: typer>=0.12
22
+ Description-Content-Type: text/markdown
23
+
24
+ # rio
25
+
26
+ Four packages:
27
+
28
+ - **`rio_ai`** -- a multi-provider LLM streaming SDK (Anthropic, Google
29
+ Gemini, Mistral, OpenAI Codex, OpenAI-compatible), ported from
30
+ [huggingface/tau](https://github.com/huggingface/tau)'s `tau_ai` module.
31
+ The message/tool/type vocabulary `tau_ai` depends on (`tau_agent.messages`,
32
+ `tau_agent.tools`, `tau_agent.types`, `tau_agent.provider`,
33
+ `tau_agent.provider_events` upstream) is folded in here too, so `rio_ai`
34
+ is self-contained. See [`NOTICE`](NOTICE) for the upstream MIT license.
35
+ - **`rio_agent`** -- a from-scratch runtime implementing
36
+ [*SKILL.state: Scalable Long-Horizon Agent Skills*](https://arxiv.org/abs/2608.26263)
37
+ (Badhe, Tiwari & Chung; EMNLP), built on `rio_ai`.
38
+ - **`rio_coding`** -- the coding backend (CLI, tools, skills, extensions), built on `rio_agent`.
39
+ - **`rio_tui`** -- the terminal application, wired to `rio_coding`. It owns
40
+ session tabs, the prompt editor, shell, file navigation, diffs, and settings.
41
+
42
+ ## Coding agent
43
+
44
+ ```bash
45
+ uv sync
46
+ uv run rio setup --provider local --base-url http://localhost:8080/v1 --model my-model
47
+ uv run rio # interactive terminal interface
48
+ uv run rio -p "Inspect this project and explain its entry point"
49
+ uv run rio -p --mode json "Add a regression test for the parser"
50
+ uv run rio --mode rpc # JSONL commands on stdin
51
+ uv run rio sessions
52
+ uv run rio --session SESSION_ID
53
+ uv run rio export SESSION_ID --format html
54
+ ```
55
+
56
+ Choose an existing provider with `--provider NAME --model MODEL`; `rio providers`
57
+ lists the configured catalog. Credentials may come from the provider's environment
58
+ variable or Rio's credential store (`rio login PROVIDER`; use `--method api-key`
59
+ for an API key). A successful login remembers the provider for future launches.
60
+ API keys and OAuth tokens are stored in `~/.rio/credentials.json` with owner-only
61
+ permissions; the file is unencrypted. OAuth tokens refresh automatically.
62
+ Use `rio logout PROVIDER` to remove saved credentials. Configuration and journals
63
+ live under `~/.rio`.
64
+ Use `--approve` to allow ambient project instructions and extensions for a run;
65
+ project trust controls resource loading, not what shell commands can access.
66
+
67
+ The coding tools are `read`, `write`, `edit`, `bash`, and `respond`. Each step
68
+ updates structured state and executes one action. Sessions store snapshots for
69
+ resume and checkpoint restoration; they never replay a transcript into the model.
70
+ HTML exports show steps, final state, and estimated token footprints.
71
+
72
+ See the [installed documentation](src/rio_coding/data/docs/README.md) for
73
+ providers, skills, extensions, RPC, and the state-based runtime. The
74
+ [offline example](src/rio_coding/data/examples/offline_session.py) runs a complete
75
+ session with a fake provider and requires no credentials.
76
+
77
+ ## Development
78
+
79
+ ```bash
80
+ uv sync
81
+ uv run pytest
82
+ uv run ruff check .
83
+ ```
84
+
85
+ Tests use scripted providers (`rio_ai.FakeProvider`) and mocked HTTP; no live
86
+ provider credentials are required. Headless Textual tests exercise terminal
87
+ interaction, and tool integration tests work in temporary directories. See in
88
+ particular `tests/test_rio_agent_loop.py::test_prompt_footprint_is_bounded_across_steps`,
89
+ which asserts that per-step prompt size stays constant across many steps
90
+ rather than growing -- a direct runtime check of the paper's core claim.
@@ -0,0 +1,140 @@
1
+ rio_agent/__init__.py,sha256=DC9rZDoQ4qI1gUamg1RbB9dZHtfSsiajm4F-6mxv18Y,1629
2
+ rio_agent/errors.py,sha256=Zep_eWy0u4dTAY2vy534Uzl51pSsvFfwLvRWz2ioLJ8,939
3
+ rio_agent/events.py,sha256=fYez8Yqf5Q_xQ1M7GR0WZxVfeNdRgj3NgyRCadf6aWo,2017
4
+ rio_agent/harness.py,sha256=J9XVfofIIH5hvXHCLVkW0uyzTUuTKTraBLhn4gHpgmo,4105
5
+ rio_agent/loop.py,sha256=ypgEm49I-5j15NjgSfbBy6T31AB9zL859_wnTftBQmI,8489
6
+ rio_agent/observation.py,sha256=XDWkY80XYvAaOtBkmTaaGlNUsh_2G1-ioR2WVL9ZnaY,887
7
+ rio_agent/prompt.py,sha256=93v8gJyLMj7dYkoFnReFnAFhQW0h8DKZG678jfyS5XE,4288
8
+ rio_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ rio_agent/skill.py,sha256=Ia_5duVHTSDAVIQO53oQsFKtr34g49-IUISSjonaRsQ,889
10
+ rio_agent/state.py,sha256=DKTMV_Pffa7Jdh_MKA6BOyYlRASnxvmRr-weiuAJLQk,1873
11
+ rio_ai/__init__.py,sha256=9KCNoV61_DmwbDkL7gftewJKEGST82iqjGGLH04Kr4I,2468
12
+ rio_ai/_provider_events.py,sha256=qx9pdFfL6u6WXR5_xYccSX_C5SkZVTcUeA6ysqMxJP8,2321
13
+ rio_ai/anthropic.py,sha256=5A7gSNLImEjWTKAsx8zMXIEDILH6sA0PWiLRvG7x_Bo,32553
14
+ rio_ai/content.py,sha256=HIZ_NT61L7mX9nYY7sLuO5nA7fjUGFiXdAbH1mnc-zc,1683
15
+ rio_ai/env.py,sha256=sSgw6mEuiEefWovVg7022jj8rAuUSgMXMXP8vDIpsv8,6218
16
+ rio_ai/events.py,sha256=kKHwffbfjb4yXIPE1iGvcMnTKN1yPQ1zPKGLKI180zw,823
17
+ rio_ai/fake.py,sha256=x0mU14Q0q9pXaVvnYmsDSwmcjOioy1DAF9GQpBHHRK4,1417
18
+ rio_ai/google.py,sha256=e9huzlaXV1INGErp2nPcdnXHiXKP-5UCUqyphPBoXAI,20609
19
+ rio_ai/http.py,sha256=9BvofnxhZqnp3a8lxKblRY6nfrvhz_4PAtDkATYCkOc,2387
20
+ rio_ai/http_errors.py,sha256=kuSGGwkSCi9XLUFHAuR2xjOAXJPJbPmbeYanIiJ7I8Y,2020
21
+ rio_ai/messages.py,sha256=1jCeqn8yovwMfVopZaJQUtKSuzvWIVEPpdaQaW_VDTo,8419
22
+ rio_ai/mistral.py,sha256=1emOQywqR_CrL-oKXIrN1CWRgo4XNLKHx6_r75QS-Gw,19487
23
+ rio_ai/model_limits.py,sha256=zxC4CbA6lrPhvmbkx_fp8gmJCSOUEzDLvizY-dNZtVk,2077
24
+ rio_ai/openai_cache.py,sha256=jpyLf2jJndg3OVNdgJX0dTuOQU4zmqIlv9jdXQBynzE,628
25
+ rio_ai/openai_codex.py,sha256=RwUFVzA4_FverridAD6kYb6mnpAtsfRg2EbU_PbsErM,40286
26
+ rio_ai/openai_compatible.py,sha256=tJAhUdiuCXOEVL1PM2udYVExGLsYfkaSM6ksAXVHeHM,52499
27
+ rio_ai/provider.py,sha256=pbyT8k6EYy5m6K7OmIBInqnv1XYfF6wU4aKYxwx_YLM,1062
28
+ rio_ai/provider_events.py,sha256=1DlMfBAp5xIWCVnBEABQ4rXQj2d3YQOrBhs0nW188RQ,2438
29
+ rio_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ rio_ai/retry.py,sha256=b2hYU0k4bh73ihsUH-RK1gW_ZauSPH-FrsrTH3jz-ac,1885
31
+ rio_ai/stream.py,sha256=KT5yVDfxZjlWfP2l4Cq7n3UH2jtqnWSOIGbZjv2pu-0,8454
32
+ rio_ai/tool_call_ids.py,sha256=7-faY63oTCtTmdUBEQ9z3o9m0BREsIphCsEnCBWa0NI,928
33
+ rio_ai/tools.py,sha256=GAZ6Em0ptvFyTmHDnYgF-GIUudqtPiqEgxIIgkF89pY,3549
34
+ rio_ai/types.py,sha256=UOFf9T_uMjFMRvFsYS8RJvKPjIFMwHP2WeJFGtYsEZQ,333
35
+ rio_coding/__init__.py,sha256=mkf3UE_wR3xk0Z-mbJCwJYDUItJIEXI23mV6JJuoBkA,8100
36
+ rio_coding/__main__.py,sha256=DPbgfyZSWPIiTwG070jOOV_np2jNi4XEsU3_a8JKgyQ,84
37
+ rio_coding/auth_commands.py,sha256=He4znCCEZmVM4bce43OU0T3t335C2GOEoLy6_Fy2_5k,2680
38
+ rio_coding/branch_summary.py,sha256=hyPBzP08fnuNiD0mfMddJVeu7RkSk_fBK9G0nE0ITp4,3103
39
+ rio_coding/built_in_extensions.py,sha256=5u8v81a0YjdZveUORIcD-uzuPkIV6F1OdCmqI5vXyCo,4075
40
+ rio_coding/catalog_loader.py,sha256=38qVMwtuFryGX7Ja45s42dEdA5vrStksW9WMZqF58s0,29530
41
+ rio_coding/cli.py,sha256=MRY537lBB-OmjTMtTvspAmAkZr8iFjk2MQ6LtS1IjT4,25325
42
+ rio_coding/coding_skill.py,sha256=Xkqn8pCGnUH42kYoaZeBQh9-yO9GRVWh0A3d4gQPifk,4309
43
+ rio_coding/commands.py,sha256=kTSBK0fQfZ_BwyDQDorfyYaNf1zXiThkQw1wagygI3Q,33331
44
+ rio_coding/context.py,sha256=aRb8NrQxSEXBmtC92u3_Y5Zq9nPSnIgL37THHSAZIgE,3267
45
+ rio_coding/credentials.py,sha256=5uGiUN--TB5Iw25E0jbzLSz7Tt_oUJcRrUuPq_WbcXc,9616
46
+ rio_coding/diagnostics.py,sha256=-q9tIW1eWr905V6Gd9FVsByotFc0-6mrxTuYYsyajwk,6239
47
+ rio_coding/events.py,sha256=scdrsJWHnSv_FbzjtD5FIvZYdNJS_eVeyzBSzbSy-G0,3465
48
+ rio_coding/extension_installer.py,sha256=cDiqICrlBSFldqBL1LcoXR1uod-wyb6j01HQ40tPgc4,7438
49
+ rio_coding/frontend_session.py,sha256=EwYsfJ-hs_MGAj1UF-DmmFzjtqdlkP_RmtMm1RmPpPU,9398
50
+ rio_coding/image_processing.py,sha256=3172j-fRSjvZfUVNa_Q6ZmoJKRERdZS8FjeRFnwhnyg,9260
51
+ rio_coding/local_backends.py,sha256=12ENUomVFRz6XjKVJVchqjiF84T3YgZdYBFBPzHQcJY,48797
52
+ rio_coding/models_dev.py,sha256=gL-rI0h1OL7q2ZSFXdPKpIhWsWeqktn397UMWeZbotA,13111
53
+ rio_coding/models_dev_store.py,sha256=DAejqmZRIb6xrsMr36a7G0qMrHuG1DXvDOChdxWaSzI,7187
54
+ rio_coding/oauth.py,sha256=bwfElAQWKsW31PsNjnowkVigPomSgXG0V6Qqh-OKzkI,17462
55
+ rio_coding/oauth_anthropic.py,sha256=YDLsyih0I0U41-o5XhiFKTkbhyoFtinDL0L0ygajJ2Y,10160
56
+ rio_coding/oauth_device.py,sha256=D9P5FPDGak8KCF7cbDp20xWdr8FydAb_38a5hVqKY9c,3502
57
+ rio_coding/oauth_github_copilot.py,sha256=M_vMnKHG-CK7qiGaYDbJZhaRZTVtx8U0sqAAgeB_CGw,10757
58
+ rio_coding/oauth_registry.py,sha256=6zuyX6fZWEJ6FE5y_dYbGuiDcWYFzRYBVD2yifIcFl8,1998
59
+ rio_coding/oauth_types.py,sha256=O1ZuCslKoEJvUi9WJlzZbax7iQ32FJo7wFT7Cz-nZMk,3556
60
+ rio_coding/paths.py,sha256=tP--CPMsBGsuHHDxwe1cmS89dO6FM_mX6pQKPSMWm_Q,5284
61
+ rio_coding/project_trust.py,sha256=cceOvdafJ35w8Bxx9Qat5nZPLWo2DcnwWsy_odChTZY,27518
62
+ rio_coding/prompt_templates.py,sha256=5Wog4enRWnjdG2PxXH8Yfl4hKW-40wBdrTFBRuxOAc8,10227
63
+ rio_coding/provider_catalog.py,sha256=RLTXavhKv5Mi-8AAM5oUYhEuIMbgH1fJVD6F5UAvLIk,4030
64
+ rio_coding/provider_config.py,sha256=aw9uM5Qiz3-MxvUnyTqPASy1_wm4bYa2ZBT4lqytpvc,104036
65
+ rio_coding/provider_runtime.py,sha256=SNt9oCeCv6xCPD24wErnNdEoHrcsRhHwlI2gFk0OaVs,22248
66
+ rio_coding/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
+ rio_coding/reload.py,sha256=5MJ033lyL-jqHBAfEo1iR2c6y6qHrPJ2d07_tvEtJLM,789
68
+ rio_coding/resources.py,sha256=l6aREEeAi6Umd5cY_7pz4zjwrOOT5J5UKZWzAWo7Mlg,12297
69
+ rio_coding/rpc.py,sha256=h4rmBFkafQ6W5Yy9mHJyyByE-YQ1IYT-ZR4__cBJBn0,22723
70
+ rio_coding/self_docs.py,sha256=jWy3exeyLABuCtobEf-nqsg5z_UnQrSpXfQk38xGWjs,613
71
+ rio_coding/session.py,sha256=shgawEyN58mr175B1K1Io2vN3fHVfBUYb_0V-RTPiIM,29819
72
+ rio_coding/session_export.py,sha256=-NM3ZnbPpy9yobFnlVMLBV6vWpzQyqrBLgjR4QpumYo,21888
73
+ rio_coding/session_manager.py,sha256=PDTEYFHKaYAiTsrfhC_WdDX0gCKWVVVUf-bcKrlq-K4,14320
74
+ rio_coding/session_preparation.py,sha256=MXYLfNgfUgyloGCwgOT9Q5_CWea1qQ36wxJExOjTgdk,2774
75
+ rio_coding/session_runner.py,sha256=8THfX1xPzEKK0TJJ14lJq4kdRsluV_uiAvaF7fgqCoo,19211
76
+ rio_coding/session_stats.py,sha256=RdfyxWp2bYmXwH9ROibMwRwJXh8lBpBdmeFr3r1dQTQ,3818
77
+ rio_coding/session_usage.py,sha256=nKS-OqL5Ex30QcXST1W0r6vtrOb5RtfF1OqTA01Sxfg,7166
78
+ rio_coding/shell_config.py,sha256=pu32bvupMZzLxd5OaQlg50ePzjML1vfJWCPanJpevUM,2843
79
+ rio_coding/skills.py,sha256=yeWNZhfZVvFch5d4sEs0ZCNkomsCOr6UkrF2Fkno6mo,8959
80
+ rio_coding/step_footprint.py,sha256=FO35WoocS0nDF5QbWb9m6hJpdgYvBQISiUoOV_k6MC8,5534
81
+ rio_coding/system_prompt.py,sha256=bii7CgiDEmnhvnZseO2TFLPvSO90ux25j-k1zfKS_kw,13357
82
+ rio_coding/thinking.py,sha256=aknZSnW7JthyGMP8i2GPy0bNbD799w6FyvCUaVoak8w,3044
83
+ rio_coding/tools.py,sha256=EapKdE-tGdj8GDxJpAbVo7v7WB1G5dtx45cRIcj8HZg,51952
84
+ rio_coding/update_check.py,sha256=CIrkv_p1GH5XxISHlZdTlkpKB8V_eKrw-hYyEVsu5pc,13075
85
+ rio_coding/updater.py,sha256=Ud0rwdAhmu20ReQYekA-RVOn84bFgeO7Jy4o5xfpFeg,13491
86
+ rio_coding/version.py,sha256=_jG2JtOXwfNuug0a0wfc4zKUvquFYUnDFH6FqijYfO4,409
87
+ rio_coding/data/catalog.toml,sha256=WdFGv6xpyPkV0bqlre0CPK2aDf7fL3__YF6DXm4vgIo,233789
88
+ rio_coding/data/models-dev-catalog.json,sha256=TMjIGXnQqIzO4W-SJZ19DMWmhwttCKOZG9gTxOeJA4Y,403853
89
+ rio_coding/data/docs/README.md,sha256=UgaiPH8_leXVd-4CNe1cZycQTIe5qsiwDzLxbwSryns,781
90
+ rio_coding/data/docs/architecture.md,sha256=yDrfpfTA1iw-DCbCd7rVenaY7qKCITEt9Rx4Tyh_vw8,5843
91
+ rio_coding/data/docs/cli.md,sha256=9m5ZbnDLTJUvZah8lxxAY-sylb46oj2sNe9Fs5_r1yg,2637
92
+ rio_coding/data/docs/extensions.md,sha256=td-Dpd-SvdePi3vN0YRvAtx4op6rl2M9TQS81QUUhjk,2436
93
+ rio_coding/data/docs/local-inference.md,sha256=4vGaelExDf3e2GgA7ddNZ2xV99ClCm4LYEB0OKsrg7s,970
94
+ rio_coding/data/docs/models.md,sha256=NBuNVt1STerRmcIx-5PLpXleSmCSWoRBwSeRaJZRNN4,2187
95
+ rio_coding/data/docs/security.md,sha256=nBiIyaDphF4bEUxn08mAWlkcNWSRLMJI2K5ItCeP1bw,1452
96
+ rio_coding/data/docs/skills.md,sha256=lgBUjiJ7w8J7iogVmIRaAGa-zCKYNuNZWTUM4dBRDhQ,2142
97
+ rio_coding/data/examples/offline_session.py,sha256=wlhHKNueupqg8rh0MwqS6891IqsEVMbmzqJmm1jJpwk,1478
98
+ rio_coding/data/release-notes/releases.json,sha256=EpVsjjsE8s9rJaAdznyx38p3pAOHMRj9eBcAkk2lLXY,1086
99
+ rio_coding/extensions/__init__.py,sha256=Wksp3cLcH3uao4sxK8t7IOqrNfFt-sl8aG5-mgWnwjM,5756
100
+ rio_coding/extensions/api.py,sha256=SAWymZX8EPRCT3hkV-sKycqLK8QGDkH4E6uC8yW98jc,42069
101
+ rio_coding/extensions/loader.py,sha256=0FNF6Umx_UjICuxflW7BitUTtjvyhqZTNJZGnMXPQIA,14221
102
+ rio_coding/extensions/provider_registry.py,sha256=yW0hnvp8u39EIBU2zy7Zo2P3-4DkHdXZ64hM2uq5HMk,25950
103
+ rio_coding/extensions/providers.py,sha256=WKyuiMgtLSDzF_a3xMxpKvZRVZ8OaWxZAusdA6hE2d0,21826
104
+ rio_coding/extensions/runtime.py,sha256=gNY_-HCY4wWN8m3irMc2diCI95Uj-9rOf-sY6QceN2E,51672
105
+ rio_coding/extensions/startup.py,sha256=hWlRdqfobmNjDQL72owXCgP_-LFuGIUoCnszoN5CLYs,2438
106
+ rio_coding/extensions/builtins/__init__.py,sha256=8RwaQsjE6IUr8qeUxamm_oYp-xeV-Tddp4_Ncn4BSBk,41
107
+ rio_coding/extensions/builtins/llama_cpp/__init__.py,sha256=_RdD-RmlLjjwP5So428wdg3LbI4H-BMuSF9w5CfDiI4,2555
108
+ rio_coding/extensions/builtins/llama_cpp/huggingface.py,sha256=oem3mZgAzgPWvlrLxtXsI64vLWlgfyGMohxtRe3ADZQ,7511
109
+ rio_coding/extensions/builtins/llama_cpp/router.py,sha256=QgEsdFKYc82Nf9LaZmtWxnE35E-gmdeCMUws5WuVjTA,10639
110
+ rio_coding/extensions/builtins/llama_cpp/service.py,sha256=8BPgxNcixo189EqWsvxdFc1mM9WGuesRF5H5vAFN8TU,71014
111
+ rio_coding/extensions/builtins/llama_cpp/state.py,sha256=w4wklCBOB4ujGWhflmy6E0dhM8wbWPpgx07LfnK85QY,17333
112
+ rio_coding/rendering/__init__.py,sha256=oibCD3w0YTu3--f2r2AwiKJ8Cc1WysgemHQHI1rIUJc,877
113
+ rio_coding/rendering/base.py,sha256=g7Ag16orXE4OPAYCs8LR3vfvdFwhVImI_QMAEU0LTqs,1802
114
+ rio_coding/rendering/json.py,sha256=ImYpMA8y9nJjkIAPpI1ZoQRo05e6iSFX3_gynJiyGlw,2654
115
+ rio_coding/rendering/plain.py,sha256=_FSOaAbtJpP2fhHje2K4lcIIIzZE_uMQN4qnLRFs5LA,3909
116
+ rio_coding/rendering/steps.py,sha256=lV4gBepQ2r42O3EXh3RhZLgg0-BOA7xZySN8o6BTYaE,4539
117
+ rio_coding/session_store/__init__.py,sha256=wvn1HrS29bvJUsl_yRqyod4COzfgyP4sGJQOHlgN0Z4,1308
118
+ rio_coding/session_store/entries.py,sha256=2xqd5VdCmqGWMCgoEhX_l3PoySyF7xrUFWKjjltNhSE,5564
119
+ rio_coding/session_store/jsonl.py,sha256=PXilexk8J9d4Da92yECZTesHG51t-HMd43iqcrgewAo,1431
120
+ rio_coding/session_store/storage.py,sha256=w2zBo7DBt2v8CF_11S00296imoSYODWBeVP7QX8lZ2k,7344
121
+ rio_coding/session_store/tree.py,sha256=nbZ4nNVbehV52VVT0HKovc3mxTm3FsEEXLDK4_RasJg,3265
122
+ rio_tui/__init__.py,sha256=GURtvbciFTnpf1TBIalK8hT7j3hvKP-V4-2okKYylIQ,191
123
+ rio_tui/__main__.py,sha256=Z-FokKnWybtUCIyaU_-1RyMwMN2E1jZqcON1RtBmVC8,38
124
+ rio_tui/app.py,sha256=SqK9NCINBFGEY8y_HTVid705GAIX1v_JwN3EBwmwZfc,8360
125
+ rio_tui/app.tcss,sha256=ZYamNhJ2yii75DItVUYbiLJnG8RbyStNAtvvVKeV8Uw,4085
126
+ rio_tui/bridge.py,sha256=s0m6miRd_zSY5nxVzr3d_97qun2b2TPcsWBAuTCLYu0,7653
127
+ rio_tui/dialogs.py,sha256=a57UVyWOhqykBnO-J4i30NOO8yzvo5uFEMMLVSeGpAU,9298
128
+ rio_tui/session.py,sha256=TQMNDyQnCz1UDizCHTqCusohaUVT76-aGQiF7NJcxoM,21705
129
+ rio_tui/settings.py,sha256=CXmn7SN0ZmtQumz74EiLoGx2D1VI7HfU9yTq1G9RhyI,1533
130
+ rio_tui/widgets/__init__.py,sha256=PsSHMD7Gm75fDIplUpoPWa0WVacHQIgzgtFoX0LajZE,40
131
+ rio_tui/widgets/conversation.py,sha256=ZwvsLJs9E51Rho503xOLzJzw1bpRbVjOGTMuzW-B5lQ,4549
132
+ rio_tui/widgets/prompt.py,sha256=nVss9GFaEypfOIt4gfVHZojU32on316lFpr9ngGJ5Q8,6566
133
+ rio_tui/widgets/sidebar.py,sha256=NKL5pGMt6QHrDjv0BXVyhr9eBsMA7DM9X2R0kpcPb1Y,1943
134
+ rio_tui/widgets/terminal.py,sha256=Ip0uKtdsWrLQphncVJigsFVeZeGQYTno6rM-5VIXKoU,6447
135
+ rio-0.4.0.dist-info/METADATA,sha256=SY1fIe9X7VEMc2Z8Bt6u4L8EQsR_iI-DNLZ733ANtZo,4108
136
+ rio-0.4.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
137
+ rio-0.4.0.dist-info/entry_points.txt,sha256=oGugo6Dvgb9fDiTC3zoUNQWZ_GRnkcUoCl51W00FoAU,43
138
+ rio-0.4.0.dist-info/licenses/LICENSE,sha256=-PLaKGZFrtCpEyu6v0yJCWrR95hROnRQ3eEh0Fq2_Wg,1063
139
+ rio-0.4.0.dist-info/licenses/NOTICE,sha256=lQVrQEsQH8CumOXq48YYUSXAMAMOz0CdqKjxT7rmYk4,1856
140
+ rio-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ rio = rio_coding.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ju Lin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ rio_ai (src/rio_ai/) is a port of huggingface/tau's `tau_ai` module, plus the
2
+ `tau_agent` message/tool/type vocabulary that `tau_ai` depends on
3
+ (`messages.py`, `tools.py`, `types.py`, `provider.py`, `provider_events.py`).
4
+
5
+ rio_coding (src/rio_coding/) is a port of huggingface/tau's `tau_coding`
6
+ module, retargeted from `tau_agent`'s append-only conversational harness onto
7
+ `rio_agent`'s SKILL.state runtime. Modules whose purpose was serving a growing
8
+ transcript -- context compaction, transcript rendering and export, and the
9
+ message-history session log -- were redesigned around execution state rather
10
+ than carried over. `rio_coding/session_store/` derives from `tau_agent.session`.
11
+
12
+ Upstream project: https://github.com/huggingface/tau
13
+ Upstream license: MIT, reproduced below.
14
+
15
+ ---
16
+
17
+ MIT License
18
+
19
+ Copyright (c) 2026 Alejandro AO
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a copy
22
+ of this software and associated documentation files (the "Software"), to deal
23
+ in the Software without restriction, including without limitation the rights
24
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
+ copies of the Software, and to permit persons to whom the Software is
26
+ furnished to do so, subject to the following conditions:
27
+
28
+ The above copyright notice and this permission notice shall be included in all
29
+ copies or substantial portions of the Software.
30
+
31
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
+ SOFTWARE.
rio_agent/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ """rio_agent: a SKILL.state long-horizon agent runtime.
2
+
3
+ Fixed skill instructions, a structured mutable execution state, and the
4
+ latest observation -- an action's result, a user message, or both -- are the
5
+ only inputs to each step. The runtime discards the reasoning behind each
6
+ step once it commits a valid state update, so per-step prompt size stays
7
+ fixed instead of growing with the number of steps already taken.
8
+
9
+ Exposes a loop interface shaped like `rio_ai`'s ported tau_agent-style
10
+ `AgentHarness`/`run_agent_loop` (a stateful harness plus a bare async-
11
+ generator loop function, both emitting a typed event stream), but backed
12
+ internally by execution state rather than an append-only transcript.
13
+ """
14
+
15
+ # ruff: noqa: F401 - this module intentionally defines the public facade
16
+
17
+ from rio_agent.errors import (
18
+ ActionNotFoundError,
19
+ ProviderResponseError,
20
+ RetriesExhaustedError,
21
+ StateValidationError,
22
+ )
23
+ from rio_agent.events import (
24
+ ActionEndEvent,
25
+ ActionStartEvent,
26
+ ReasoningDiscardedEvent,
27
+ RunEndEvent,
28
+ RunStartEvent,
29
+ SkillEvent,
30
+ StateUpdateEvent,
31
+ StepEndEvent,
32
+ StepStartEvent,
33
+ ValidationErrorEvent,
34
+ )
35
+ from rio_agent.harness import (
36
+ EventListener,
37
+ Harness,
38
+ HarnessCancellationToken,
39
+ HarnessConfig,
40
+ )
41
+ from rio_agent.loop import run_skill_loop
42
+ from rio_agent.observation import HarnessObservation
43
+ from rio_agent.prompt import STEP_TOOL_NAME, build_step_messages, skill_step_tool
44
+ from rio_agent.skill import HarnessSpec
45
+ from rio_agent.state import apply_state_delta, validate_state_delta
46
+
47
+ __all__ = [name for name in globals() if not name.startswith("_")]
rio_agent/errors.py ADDED
@@ -0,0 +1,29 @@
1
+ """Errors raised while validating and committing SKILL.state step output."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class StateValidationError(Exception):
7
+ """A proposed state_delta failed deterministic runtime validation.
8
+
9
+ Validation happens in the runtime, not the model, so a malformed patch
10
+ never corrupts persistent state -- it triggers a rollback-retry cycle
11
+ instead (see `RetriesExhaustedError`).
12
+ """
13
+
14
+
15
+ class ActionNotFoundError(Exception):
16
+ """The model proposed an action that isn't declared on the skill."""
17
+
18
+
19
+ class RetriesExhaustedError(Exception):
20
+ """The rollback-retry cycle exceeded `max_retries` without a valid step."""
21
+
22
+
23
+ class ProviderResponseError(Exception):
24
+ """The provider failed to produce an assistant message.
25
+
26
+ Distinct from a malformed step: the model never got to propose one, so
27
+ there is nothing to roll back and nothing a retry of the same prompt would
28
+ correct.
29
+ """
rio_agent/events.py ADDED
@@ -0,0 +1,95 @@
1
+ """Events emitted by `rio_agent.loop.run_skill_loop`.
2
+
3
+ Shaped like `rio_ai`'s ported tau_agent-style agent events (a flat stream of
4
+ small, typed records a UI or logger can subscribe to) but describing SKILL.
5
+ state's step lifecycle instead of an append-only conversation turn.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+
12
+ from rio_agent.observation import HarnessObservation
13
+ from rio_ai.tools import AgentToolResult
14
+ from rio_ai.types import JSONObject
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class RunStartEvent:
19
+ skill: str
20
+
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class StepStartEvent:
24
+ step: int
25
+ state: JSONObject
26
+ observation: HarnessObservation
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class ReasoningDiscardedEvent:
31
+ """The model's reasoning for this step. Surfaced once for observability, then never sent back
32
+ to the model.
33
+ """
34
+
35
+ step: int
36
+ reasoning: str
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class ValidationErrorEvent:
41
+ """A proposed state update or action failed runtime validation; a rollback-retry follows."""
42
+
43
+ step: int
44
+ attempt: int
45
+ error: str
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class StateUpdateEvent:
50
+ """The proposed state update was validated and committed to form the new state."""
51
+
52
+ step: int
53
+ delta: JSONObject
54
+ state: JSONObject
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class ActionStartEvent:
59
+ step: int
60
+ name: str
61
+ arguments: JSONObject
62
+
63
+
64
+ @dataclass(frozen=True, slots=True)
65
+ class ActionEndEvent:
66
+ step: int
67
+ name: str
68
+ result: AgentToolResult
69
+ is_error: bool
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class StepEndEvent:
74
+ step: int
75
+ state: JSONObject
76
+ terminated: bool
77
+
78
+
79
+ @dataclass(frozen=True, slots=True)
80
+ class RunEndEvent:
81
+ steps: int
82
+ state: JSONObject
83
+
84
+
85
+ type SkillEvent = (
86
+ RunStartEvent
87
+ | StepStartEvent
88
+ | ReasoningDiscardedEvent
89
+ | ValidationErrorEvent
90
+ | StateUpdateEvent
91
+ | ActionStartEvent
92
+ | ActionEndEvent
93
+ | StepEndEvent
94
+ | RunEndEvent
95
+ )
rio_agent/harness.py ADDED
@@ -0,0 +1,125 @@
1
+ """Harness: a reusable stateful runtime around `run_skill_loop`.
2
+
3
+ Deliberately mirrors the public shape of `rio_ai`'s ported tau_agent-style
4
+ `AgentHarness` -- construct with a config, `subscribe()` an event listener,
5
+ call `run()`/`prompt()` to drive it, `cancel()` to stop it -- so callers
6
+ already familiar with that loop interface can pick this one up directly.
7
+ The difference is entirely internal: instead of holding a growing message
8
+ transcript, this harness holds only the current execution state and
9
+ replays `run_skill_loop`'s fixed-size per-step prompt.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import AsyncIterator, Awaitable, Callable
15
+ from contextlib import suppress
16
+ from dataclasses import dataclass
17
+ from inspect import isawaitable
18
+
19
+ from rio_agent.events import RunEndEvent, SkillEvent, StateUpdateEvent
20
+ from rio_agent.loop import run_skill_loop
21
+ from rio_agent.observation import HarnessObservation
22
+ from rio_agent.skill import HarnessSpec
23
+ from rio_ai.provider import ModelProvider
24
+ from rio_ai.types import JSONObject, JSONValue
25
+
26
+ EventListener = Callable[[SkillEvent], Awaitable[None] | None]
27
+
28
+
29
+ @dataclass(slots=True)
30
+ class HarnessConfig:
31
+ provider: ModelProvider
32
+ model: str
33
+ skill: HarnessSpec
34
+ max_steps: int | None = None
35
+ max_retries: int = 2
36
+
37
+
38
+ class HarnessCancellationToken:
39
+ def __init__(self) -> None:
40
+ self._cancelled = False
41
+
42
+ def cancel(self) -> None:
43
+ self._cancelled = True
44
+
45
+ def is_cancelled(self) -> bool:
46
+ return self._cancelled
47
+
48
+
49
+ class Harness:
50
+ """Reusable stateful long-horizon agent runtime built on SKILL.state."""
51
+
52
+ def __init__(
53
+ self,
54
+ config: HarnessConfig,
55
+ *,
56
+ state: JSONObject | None = None,
57
+ ) -> None:
58
+ self._config = config
59
+ self._state: JSONObject = dict(state if state is not None else config.skill.initial_state)
60
+ self._listeners: list[EventListener] = []
61
+ self._current_signal: HarnessCancellationToken | None = None
62
+ self._running = False
63
+
64
+ @property
65
+ def state(self) -> dict[str, JSONValue]:
66
+ return dict(self._state)
67
+
68
+ @property
69
+ def config(self) -> HarnessConfig:
70
+ return self._config
71
+
72
+ @property
73
+ def is_running(self) -> bool:
74
+ return self._running
75
+
76
+ def subscribe(self, listener: EventListener) -> Callable[[], None]:
77
+ self._listeners.append(listener)
78
+
79
+ def unsubscribe() -> None:
80
+ with suppress(ValueError):
81
+ self._listeners.remove(listener)
82
+
83
+ return unsubscribe
84
+
85
+ def cancel(self) -> None:
86
+ if self._current_signal is not None:
87
+ self._current_signal.cancel()
88
+
89
+ def run(self, observation: HarnessObservation) -> AsyncIterator[SkillEvent]:
90
+ self._ensure_not_running()
91
+ self._running = True
92
+ return self._run(observation)
93
+
94
+ async def _run(self, observation: HarnessObservation) -> AsyncIterator[SkillEvent]:
95
+ signal = HarnessCancellationToken()
96
+ self._current_signal = signal
97
+ try:
98
+ async for event in run_skill_loop(
99
+ provider=self._config.provider,
100
+ model=self._config.model,
101
+ skill=self._config.skill,
102
+ observation=observation,
103
+ state=self._state,
104
+ max_steps=self._config.max_steps,
105
+ max_retries=self._config.max_retries,
106
+ signal=signal,
107
+ ):
108
+ if isinstance(event, (StateUpdateEvent, RunEndEvent)):
109
+ self._state = dict(event.state)
110
+ await self._notify(event)
111
+ yield event
112
+ finally:
113
+ if self._current_signal is signal:
114
+ self._current_signal = None
115
+ self._running = False
116
+
117
+ async def _notify(self, event: SkillEvent) -> None:
118
+ for listener in list(self._listeners):
119
+ result = listener(event)
120
+ if isawaitable(result):
121
+ await result
122
+
123
+ def _ensure_not_running(self) -> None:
124
+ if self._running:
125
+ raise RuntimeError("Harness is already running")