ouro-agent 0.2.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 (438) hide show
  1. ouro/__init__.py +5 -0
  2. ouro/application/__init__.py +155 -0
  3. ouro/application/agent_definitions.py +356 -0
  4. ouro/application/agent_execution.py +179 -0
  5. ouro/application/agent_models.py +211 -0
  6. ouro/application/agent_recovery.py +492 -0
  7. ouro/application/agent_tree.py +520 -0
  8. ouro/application/agents.py +2464 -0
  9. ouro/application/compaction.py +1706 -0
  10. ouro/application/configuration.py +1275 -0
  11. ouro/application/configuration_models.py +449 -0
  12. ouro/application/configuration_transfer.py +119 -0
  13. ouro/application/configuration_views.py +261 -0
  14. ouro/application/data_transfer.py +161 -0
  15. ouro/application/dto.py +38 -0
  16. ouro/application/event_models.py +199 -0
  17. ouro/application/events.py +562 -0
  18. ouro/application/file_inputs.py +434 -0
  19. ouro/application/harness.py +47 -0
  20. ouro/application/model_generation.py +127 -0
  21. ouro/application/model_requests.py +303 -0
  22. ouro/application/models.py +329 -0
  23. ouro/application/observers.py +343 -0
  24. ouro/application/plugin_commands.py +329 -0
  25. ouro/application/plugin_models.py +148 -0
  26. ouro/application/plugins.py +1169 -0
  27. ouro/application/prompt_context.py +31 -0
  28. ouro/application/provider_auth.py +368 -0
  29. ouro/application/provider_auth_bindings.py +73 -0
  30. ouro/application/provider_auth_models.py +42 -0
  31. ouro/application/queued_inputs.py +695 -0
  32. ouro/application/recovery.py +241 -0
  33. ouro/application/resource_models.py +46 -0
  34. ouro/application/resources.py +345 -0
  35. ouro/application/run_continuations.py +696 -0
  36. ouro/application/run_drivers.py +422 -0
  37. ouro/application/run_ownership.py +237 -0
  38. ouro/application/run_resources.py +49 -0
  39. ouro/application/run_turns.py +45 -0
  40. ouro/application/runs.py +1291 -0
  41. ouro/application/runtime.py +4528 -0
  42. ouro/application/runtime_activity.py +897 -0
  43. ouro/application/runtime_configuration.py +139 -0
  44. ouro/application/runtime_environment.py +42 -0
  45. ouro/application/session_controls.py +613 -0
  46. ouro/application/session_editing.py +204 -0
  47. ouro/application/session_event_stream.py +98 -0
  48. ouro/application/session_history.py +328 -0
  49. ouro/application/session_models.py +321 -0
  50. ouro/application/session_recovery.py +395 -0
  51. ouro/application/session_transfer.py +364 -0
  52. ouro/application/sessions.py +1088 -0
  53. ouro/application/skill_activations.py +104 -0
  54. ouro/application/supervisor.py +1421 -0
  55. ouro/application/supervisor_models.py +162 -0
  56. ouro/application/task_execution.py +470 -0
  57. ouro/application/task_scheduler.py +118 -0
  58. ouro/application/tasks.py +348 -0
  59. ouro/application/tool_calls.py +1895 -0
  60. ouro/application/usage.py +265 -0
  61. ouro/auth/__init__.py +1 -0
  62. ouro/auth/authorization.py +106 -0
  63. ouro/auth/commit.py +43 -0
  64. ouro/auth/contracts.py +163 -0
  65. ouro/auth/credentials.py +288 -0
  66. ouro/auth/driver_contracts.py +115 -0
  67. ouro/auth/drivers/__init__.py +1 -0
  68. ouro/auth/drivers/openai_callback.py +96 -0
  69. ouro/auth/drivers/openai_codex.py +121 -0
  70. ouro/auth/drivers/openai_errors.py +31 -0
  71. ouro/auth/drivers/openai_tokens.py +213 -0
  72. ouro/auth/file_permissions.py +93 -0
  73. ouro/auth/managed_requests.py +148 -0
  74. ouro/auth/request_auth.py +46 -0
  75. ouro/auth/store.py +121 -0
  76. ouro/automation/__init__.py +1 -0
  77. ouro/automation/file_records.py +66 -0
  78. ouro/automation/local_files.py +383 -0
  79. ouro/automation/plugin_tasks.py +32 -0
  80. ouro/automation/repository.py +37 -0
  81. ouro/automation/schedule.py +130 -0
  82. ouro/automation/sqlite.py +279 -0
  83. ouro/billing/__init__.py +6 -0
  84. ouro/billing/base.py +49 -0
  85. ouro/billing/local.py +353 -0
  86. ouro/billing/queries.py +123 -0
  87. ouro/builtin_plugins/__init__.py +14 -0
  88. ouro/builtin_plugins/agent_context/README.md +101 -0
  89. ouro/builtin_plugins/agent_context/__init__.py +3 -0
  90. ouro/builtin_plugins/agent_context/agent_context_plugin.py +161 -0
  91. ouro/builtin_plugins/agent_context/definition.py +53 -0
  92. ouro/builtin_plugins/agent_context/instructions.py +20 -0
  93. ouro/builtin_plugins/goal/README.md +131 -0
  94. ouro/builtin_plugins/goal/__init__.py +3 -0
  95. ouro/builtin_plugins/goal/commands.py +346 -0
  96. ouro/builtin_plugins/goal/definition.py +151 -0
  97. ouro/builtin_plugins/goal/goal_plugin.py +527 -0
  98. ouro/builtin_plugins/goal/prompts.py +104 -0
  99. ouro/builtin_plugins/goal/state.py +150 -0
  100. ouro/builtin_plugins/plan/README.md +119 -0
  101. ouro/builtin_plugins/plan/__init__.py +3 -0
  102. ouro/builtin_plugins/plan/definition.py +116 -0
  103. ouro/builtin_plugins/plan/plan_plugin.py +558 -0
  104. ouro/builtin_plugins/plan/state.py +62 -0
  105. ouro/cli/__init__.py +1 -0
  106. ouro/cli/agent_commands.py +274 -0
  107. ouro/cli/approval_commands.py +131 -0
  108. ouro/cli/config_commands.py +489 -0
  109. ouro/cli/context_output.py +65 -0
  110. ouro/cli/data_commands.py +20 -0
  111. ouro/cli/errors.py +13 -0
  112. ouro/cli/main.py +854 -0
  113. ouro/cli/mcp_commands.py +190 -0
  114. ouro/cli/parser.py +1931 -0
  115. ouro/cli/plugin_commands.py +375 -0
  116. ouro/cli/prompt_commands.py +86 -0
  117. ouro/cli/provider_auth_commands.py +159 -0
  118. ouro/cli/provider_commands.py +820 -0
  119. ouro/cli/resource_commands.py +194 -0
  120. ouro/cli/run.py +232 -0
  121. ouro/cli/session_commands.py +672 -0
  122. ouro/cli/skills.py +331 -0
  123. ouro/cli/usage_commands.py +293 -0
  124. ouro/config/__init__.py +146 -0
  125. ouro/config/agents.py +183 -0
  126. ouro/config/compaction.py +228 -0
  127. ouro/config/credentials.py +62 -0
  128. ouro/config/endpoints.py +196 -0
  129. ouro/config/file.py +106 -0
  130. ouro/config/permissions.py +148 -0
  131. ouro/config/prompts.py +199 -0
  132. ouro/config/provider_codec.py +230 -0
  133. ouro/config/provider_headers.py +134 -0
  134. ouro/config/provider_models.py +657 -0
  135. ouro/config/provider_runtime.py +166 -0
  136. ouro/config/provider_template_store.py +504 -0
  137. ouro/config/provider_templates.py +1044 -0
  138. ouro/config/providers.py +1324 -0
  139. ouro/config/queued_inputs.py +67 -0
  140. ouro/config/reasoning.py +301 -0
  141. ouro/config/run_budget.py +81 -0
  142. ouro/config/skills.py +233 -0
  143. ouro/config/tool_execution.py +77 -0
  144. ouro/core/__init__.py +54 -0
  145. ouro/core/adapter.py +264 -0
  146. ouro/core/agents.py +462 -0
  147. ouro/core/approvals.py +110 -0
  148. ouro/core/attachment_input.py +60 -0
  149. ouro/core/attachment_projection.py +89 -0
  150. ouro/core/base.py +45 -0
  151. ouro/core/billing.py +209 -0
  152. ouro/core/callbacks.py +113 -0
  153. ouro/core/cancellation.py +52 -0
  154. ouro/core/compaction.py +462 -0
  155. ouro/core/content.py +97 -0
  156. ouro/core/context.py +360 -0
  157. ouro/core/context_providers.py +242 -0
  158. ouro/core/context_snapshots.py +79 -0
  159. ouro/core/continuations.py +258 -0
  160. ouro/core/errors.py +126 -0
  161. ouro/core/events.py +91 -0
  162. ouro/core/extensions.py +5 -0
  163. ouro/core/harness.py +53 -0
  164. ouro/core/harness_calls.py +217 -0
  165. ouro/core/inner_loop.py +49 -0
  166. ouro/core/input_content.py +106 -0
  167. ouro/core/interactions.py +56 -0
  168. ouro/core/message_frames.py +70 -0
  169. ouro/core/message_roles.py +5 -0
  170. ouro/core/message_semantics.py +71 -0
  171. ouro/core/messages.py +339 -0
  172. ouro/core/model_completion.py +50 -0
  173. ouro/core/model_execution.py +226 -0
  174. ouro/core/model_generation.py +55 -0
  175. ouro/core/model_requests.py +46 -0
  176. ouro/core/model_selection.py +3 -0
  177. ouro/core/observers.py +357 -0
  178. ouro/core/persistence.py +40 -0
  179. ouro/core/plugin_contracts.py +19 -0
  180. ouro/core/plugin_tasks.py +38 -0
  181. ouro/core/prompt_policies.py +263 -0
  182. ouro/core/provider.py +255 -0
  183. ouro/core/reasoning.py +141 -0
  184. ouro/core/redaction.py +79 -0
  185. ouro/core/request_context.py +34 -0
  186. ouro/core/resources.py +75 -0
  187. ouro/core/results.py +16 -0
  188. ouro/core/run_budget.py +74 -0
  189. ouro/core/run_drivers.py +140 -0
  190. ouro/core/run_transitions.py +102 -0
  191. ouro/core/session_commits.py +223 -0
  192. ouro/core/session_controls.py +353 -0
  193. ouro/core/session_extensions.py +63 -0
  194. ouro/core/session_titles.py +61 -0
  195. ouro/core/state.py +267 -0
  196. ouro/core/task_ports.py +35 -0
  197. ouro/core/tasks.py +176 -0
  198. ouro/core/tool_batch_transitions.py +146 -0
  199. ouro/core/tool_batches.py +95 -0
  200. ouro/core/tool_policies.py +243 -0
  201. ouro/core/tool_requests.py +61 -0
  202. ouro/core/tool_result_projection.py +97 -0
  203. ouro/core/tools.py +198 -0
  204. ouro/core/turn_policies.py +225 -0
  205. ouro/core/usage_queries.py +87 -0
  206. ouro/data_commands.py +65 -0
  207. ouro/deployment.py +39 -0
  208. ouro/local_application.py +908 -0
  209. ouro/local_io.py +81 -0
  210. ouro/local_locks/__init__.py +341 -0
  211. ouro/local_locks/windows.py +57 -0
  212. ouro/local_processes/__init__.py +96 -0
  213. ouro/local_processes/commands.py +222 -0
  214. ouro/local_processes/windows.py +123 -0
  215. ouro/local_providers.py +48 -0
  216. ouro/mcp/__init__.py +78 -0
  217. ouro/mcp/client.py +48 -0
  218. ouro/mcp/configuration.py +231 -0
  219. ouro/mcp/connection_check.py +62 -0
  220. ouro/mcp/errors.py +33 -0
  221. ouro/mcp/manager.py +612 -0
  222. ouro/mcp/models.py +199 -0
  223. ouro/mcp/runtime.py +97 -0
  224. ouro/mcp/sdk.py +337 -0
  225. ouro/mcp/sse_transport.py +156 -0
  226. ouro/mcp/tool_adapter.py +92 -0
  227. ouro/plugins/__init__.py +22 -0
  228. ouro/plugins/adapters.py +188 -0
  229. ouro/plugins/api.py +1150 -0
  230. ouro/plugins/builtin.py +191 -0
  231. ouro/plugins/candidates.py +50 -0
  232. ouro/plugins/catalog.py +1052 -0
  233. ouro/plugins/compatibility.py +227 -0
  234. ouro/plugins/contracts.py +301 -0
  235. ouro/plugins/contribution_types.py +279 -0
  236. ouro/plugins/contributions.py +1378 -0
  237. ouro/plugins/dependencies.py +256 -0
  238. ouro/plugins/driver_contracts.py +54 -0
  239. ouro/plugins/environment.py +24 -0
  240. ouro/plugins/execution_contracts.py +113 -0
  241. ouro/plugins/generation.py +237 -0
  242. ouro/plugins/generation_resources.py +193 -0
  243. ouro/plugins/harness_calls.py +181 -0
  244. ouro/plugins/implementation_contracts.py +16 -0
  245. ouro/plugins/installer.py +226 -0
  246. ouro/plugins/instructions.py +170 -0
  247. ouro/plugins/invocation.py +66 -0
  248. ouro/plugins/manifest.py +596 -0
  249. ouro/plugins/mcp.py +214 -0
  250. ouro/plugins/models.py +291 -0
  251. ouro/plugins/native_ports.py +109 -0
  252. ouro/plugins/package.py +256 -0
  253. ouro/plugins/paths.py +35 -0
  254. ouro/plugins/processes.py +72 -0
  255. ouro/plugins/providers.py +254 -0
  256. ouro/plugins/readiness.py +87 -0
  257. ouro/plugins/registry.py +301 -0
  258. ouro/plugins/resources.py +125 -0
  259. ouro/plugins/run_drivers.py +208 -0
  260. ouro/plugins/runtime.py +2102 -0
  261. ouro/plugins/scaffold.py +147 -0
  262. ouro/plugins/scheduling.py +155 -0
  263. ouro/plugins/semver.py +134 -0
  264. ouro/plugins/services.py +243 -0
  265. ouro/plugins/session_controllers.py +260 -0
  266. ouro/plugins/sources.py +322 -0
  267. ouro/plugins/store.py +358 -0
  268. ouro/plugins/tasks.py +68 -0
  269. ouro/plugins/tool_content.py +51 -0
  270. ouro/plugins/tool_groups.py +232 -0
  271. ouro/plugins/worker.py +1833 -0
  272. ouro/plugins/worker_drivers.py +95 -0
  273. ouro/plugins/worker_models.py +36 -0
  274. ouro/plugins/worker_processes.py +32 -0
  275. ouro/plugins/worker_providers.py +52 -0
  276. ouro/plugins/worker_scheduling.py +40 -0
  277. ouro/plugins/worker_services.py +142 -0
  278. ouro/plugins/worker_tool_groups.py +130 -0
  279. ouro/prompts/__init__.py +180 -0
  280. ouro/providers/__init__.py +15 -0
  281. ouro/providers/anthropic_messages.py +475 -0
  282. ouro/providers/anthropic_messages_codec.py +231 -0
  283. ouro/providers/authentication.py +177 -0
  284. ouro/providers/connection_check.py +60 -0
  285. ouro/providers/factory.py +299 -0
  286. ouro/providers/faux.py +74 -0
  287. ouro/providers/headers.py +51 -0
  288. ouro/providers/http_transport.py +382 -0
  289. ouro/providers/openai_chat_completions.py +446 -0
  290. ouro/providers/openai_chat_completions_codec.py +346 -0
  291. ouro/providers/openai_responses.py +232 -0
  292. ouro/providers/openai_responses_codec.py +281 -0
  293. ouro/providers/openai_responses_errors.py +32 -0
  294. ouro/providers/openai_responses_items.py +73 -0
  295. ouro/providers/openai_responses_stream.py +316 -0
  296. ouro/providers/reasoning.py +27 -0
  297. ouro/providers/request_context.py +26 -0
  298. ouro/providers/retrying.py +84 -0
  299. ouro/providers/tool_content.py +48 -0
  300. ouro/py.typed +1 -0
  301. ouro/resources/__init__.py +4 -0
  302. ouro/resources/base.py +53 -0
  303. ouro/resources/files.py +86 -0
  304. ouro/resources/local.py +219 -0
  305. ouro/sdk/__init__.py +1 -0
  306. ouro/sdk/plugins/__init__.py +1 -0
  307. ouro/sdk/plugins/content.py +36 -0
  308. ouro/sdk/plugins/environment.py +17 -0
  309. ouro/sdk/plugins/interactions.py +155 -0
  310. ouro/sdk/plugins/models.py +65 -0
  311. ouro/sdk/plugins/processes.py +39 -0
  312. ouro/sdk/plugins/providers.py +183 -0
  313. ouro/sdk/plugins/run_drivers.py +205 -0
  314. ouro/sdk/plugins/scheduled.py +13 -0
  315. ouro/sdk/plugins/scheduling.py +99 -0
  316. ouro/sdk/plugins/services.py +49 -0
  317. ouro/sdk/plugins/state.py +61 -0
  318. ouro/sdk/plugins/v3.py +955 -0
  319. ouro/sdk/plugins/worker.py +229 -0
  320. ouro/sdk/plugins/worker_drivers.py +170 -0
  321. ouro/sdk/plugins/worker_environment.py +15 -0
  322. ouro/sdk/plugins/worker_models.py +54 -0
  323. ouro/sdk/plugins/worker_processes.py +40 -0
  324. ouro/sdk/plugins/worker_providers.py +113 -0
  325. ouro/sdk/plugins/worker_scheduling.py +164 -0
  326. ouro/sdk/plugins/worker_services.py +101 -0
  327. ouro/sdk/plugins/worker_tool_groups.py +96 -0
  328. ouro/sdk/plugins/worker_tools.py +154 -0
  329. ouro/sessions/__init__.py +37 -0
  330. ouro/sessions/base.py +280 -0
  331. ouro/sessions/catalog.py +152 -0
  332. ouro/sessions/file_commits.py +255 -0
  333. ouro/sessions/index.py +154 -0
  334. ouro/sessions/layout.py +15 -0
  335. ouro/sessions/local_files.py +1710 -0
  336. ouro/sessions/local_index.py +291 -0
  337. ouro/sessions/locks.py +29 -0
  338. ouro/sessions/message_index.py +107 -0
  339. ouro/sessions/message_journal.py +35 -0
  340. ouro/sessions/portable.py +175 -0
  341. ouro/sessions/sqlite.py +1589 -0
  342. ouro/sessions/sqlite_commits.py +196 -0
  343. ouro/sessions/transfer.py +149 -0
  344. ouro/skills/__init__.py +48 -0
  345. ouro/skills/context.py +222 -0
  346. ouro/skills/loader.py +188 -0
  347. ouro/skills/manager.py +425 -0
  348. ouro/skills/models.py +60 -0
  349. ouro/skills/parser.py +253 -0
  350. ouro/tools/__init__.py +45 -0
  351. ouro/tools/base.py +266 -0
  352. ouro/tools/batch_executor.py +94 -0
  353. ouro/tools/builtin.py +23 -0
  354. ouro/tools/builtin_agents.py +253 -0
  355. ouro/tools/builtin_ask_user.py +168 -0
  356. ouro/tools/builtin_common.py +45 -0
  357. ouro/tools/builtin_files.py +390 -0
  358. ouro/tools/builtin_glob.py +157 -0
  359. ouro/tools/builtin_grep.py +651 -0
  360. ouro/tools/builtin_ignore.py +94 -0
  361. ouro/tools/builtin_search.py +79 -0
  362. ouro/tools/builtin_shell.py +361 -0
  363. ouro/tools/builtin_todolist.py +178 -0
  364. ouro/tools/builtin_view_image.py +165 -0
  365. ouro/tools/executor.py +501 -0
  366. ouro/tools/registry.py +197 -0
  367. ouro/tools/result_content.py +52 -0
  368. ouro/tools/schema.py +51 -0
  369. ouro/transfer/__init__.py +1 -0
  370. ouro/transfer/archive.py +206 -0
  371. ouro/transfer/skills.py +160 -0
  372. ouro/tui/__init__.py +5 -0
  373. ouro/tui/app.py +5548 -0
  374. ouro/tui/archive_paths.py +120 -0
  375. ouro/tui/attachment_editor.py +215 -0
  376. ouro/tui/attachment_paths.py +47 -0
  377. ouro/tui/attachment_picker.py +120 -0
  378. ouro/tui/clipboard.py +31 -0
  379. ouro/tui/commands.py +299 -0
  380. ouro/tui/configuration_screens.py +1773 -0
  381. ouro/tui/configuration_workflows.py +1658 -0
  382. ouro/tui/controller.py +2632 -0
  383. ouro/tui/conversation_history.py +1887 -0
  384. ouro/tui/data_transfer.py +423 -0
  385. ouro/tui/entry.py +42 -0
  386. ouro/tui/inline_editor.py +170 -0
  387. ouro/tui/input_drafts.py +146 -0
  388. ouro/tui/keyboard.py +29 -0
  389. ouro/tui/markdown.py +85 -0
  390. ouro/tui/menu_labels.py +34 -0
  391. ouro/tui/menu_sorting.py +63 -0
  392. ouro/tui/model_menus.py +101 -0
  393. ouro/tui/plan_confirmation.py +25 -0
  394. ouro/tui/plugin_workflows.py +509 -0
  395. ouro/tui/preferences.py +126 -0
  396. ouro/tui/prompt_screens.py +126 -0
  397. ouro/tui/prompt_workflows.py +147 -0
  398. ouro/tui/provider_auth_screens.py +346 -0
  399. ouro/tui/provider_screens.py +819 -0
  400. ouro/tui/provider_workflows.py +1059 -0
  401. ouro/tui/question_forms.py +163 -0
  402. ouro/tui/screens.py +1821 -0
  403. ouro/tui/selection.py +8 -0
  404. ouro/tui/state.py +538 -0
  405. ouro/tui/subagent_navigation.py +303 -0
  406. ouro/tui/task_screens.py +443 -0
  407. ouro/tui/task_workflows.py +284 -0
  408. ouro/tui/terminal.py +167 -0
  409. ouro/tui/text_inputs.py +80 -0
  410. ouro/tui/text_wrapping.py +103 -0
  411. ouro/tui/theme.py +582 -0
  412. ouro/tui/tool_presentations.py +263 -0
  413. ouro/tui/welcome/__init__.py +50 -0
  414. ouro/tui/welcome/centered.py +84 -0
  415. ouro/tui/welcome/components.py +454 -0
  416. ouro/tui/welcome/dot_matrix/__init__.py +1 -0
  417. ouro/tui/welcome/dot_matrix/field.py +102 -0
  418. ouro/tui/welcome/dot_matrix/raster.py +151 -0
  419. ouro/tui/welcome/dot_matrix/widget.py +200 -0
  420. ouro/tui/welcome/host.py +139 -0
  421. ouro/tui/welcome/model.py +68 -0
  422. ouro/tui/welcome/styles/__init__.py +1 -0
  423. ouro/tui/welcome/styles/basic.py +36 -0
  424. ouro/tui/welcome/styles/clifford.py +95 -0
  425. ouro/tui/welcome/styles/crystal.py +71 -0
  426. ouro/tui/welcome/styles/electron_bloom.py +64 -0
  427. ouro/tui/welcome/styles/event_horizon.py +93 -0
  428. ouro/tui/welcome/styles/interference.py +60 -0
  429. ouro/tui/welcome/styles/julia_reliquary.py +161 -0
  430. ouro/tui/widgets.py +3512 -0
  431. ouro/tui/windows_console.py +60 -0
  432. ouro/tui/windows_driver.py +97 -0
  433. ouro/tui/wrapped_document.py +141 -0
  434. ouro_agent-0.2.0.dist-info/METADATA +198 -0
  435. ouro_agent-0.2.0.dist-info/RECORD +438 -0
  436. ouro_agent-0.2.0.dist-info/WHEEL +4 -0
  437. ouro_agent-0.2.0.dist-info/entry_points.txt +2 -0
  438. ouro_agent-0.2.0.dist-info/licenses/LICENSE +21 -0
ouro/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """OURO Agent runtime package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.2.0"
@@ -0,0 +1,155 @@
1
+ """Application services that orchestrate OURO core runtime boundaries."""
2
+
3
+ from ouro.application.agent_definitions import AgentDefinitionService
4
+ from ouro.application.agent_models import (
5
+ AgentDefinitionCatalogView,
6
+ AgentDefinitionDetail,
7
+ AgentDefinitionView,
8
+ AgentHandleView,
9
+ AgentInteractionSummary,
10
+ AgentInteractionView,
11
+ AgentReadView,
12
+ AgentTreeEdge,
13
+ AgentTreeNode,
14
+ AgentTreeNotification,
15
+ AgentTreeSnapshot,
16
+ AgentWaitResultView,
17
+ SpawnAgentRequest,
18
+ )
19
+ from ouro.application.agent_tree import AgentTreeSubscription
20
+ from ouro.application.agents import AgentService
21
+ from ouro.application.configuration import ConfigurationFacade
22
+ from ouro.application.configuration_models import (
23
+ ConfigurationOverview,
24
+ MCPConfigCatalogView,
25
+ MCPConnectionCheckView,
26
+ MCPServerConfigView,
27
+ )
28
+ from ouro.application.dto import ApplicationStartupReport
29
+ from ouro.application.event_models import RuntimeEventCategory, RuntimeEventView
30
+ from ouro.application.events import RuntimeEventRecorder, RuntimeEventSinkRegistration
31
+ from ouro.application.models import ModelReasoningState
32
+ from ouro.application.queued_inputs import QueuedUserInputView
33
+ from ouro.application.resources import ResourceService
34
+ from ouro.application.runtime import RuntimeService
35
+ from ouro.application.runtime_configuration import (
36
+ RuntimeConfigurationResolver,
37
+ RuntimeConfigurationSnapshot,
38
+ RuntimeProviderOptions,
39
+ )
40
+ from ouro.application.session_models import (
41
+ ReplayFrame,
42
+ SessionBranchResult,
43
+ SessionCatalogEntry,
44
+ SessionCatalogQuery,
45
+ SessionCatalogResult,
46
+ SessionContextSnapshot,
47
+ SessionContextStatus,
48
+ SessionDetail,
49
+ SessionEventPage,
50
+ SessionExport,
51
+ SessionMessagePage,
52
+ SessionMutationResult,
53
+ SessionObservationSnapshot,
54
+ SessionReplay,
55
+ SessionSummary,
56
+ SessionTrashResult,
57
+ SessionView,
58
+ )
59
+ from ouro.application.sessions import SessionService, SessionTitleGenerator
60
+ from ouro.application.supervisor import RuntimeSubscription, RuntimeSupervisor
61
+ from ouro.application.supervisor_models import (
62
+ RUNTIME_TOOL_ARGUMENT_TAIL_MAX_CHARS,
63
+ HostInputReceipt,
64
+ RunHandle,
65
+ RuntimeActivity,
66
+ RuntimeActivitySnapshot,
67
+ RuntimeNotification,
68
+ RuntimeStatusSnapshot,
69
+ RuntimeToolCallActivity,
70
+ ShutdownPolicy,
71
+ SupervisorStartResult,
72
+ )
73
+ from ouro.application.usage import UsageService
74
+ from ouro.core.results import RunResult
75
+ from ouro.core.usage_queries import (
76
+ UsageAuditLog,
77
+ UsageEntryList,
78
+ UsageMutationResult,
79
+ UsageSummary,
80
+ UsageTotals,
81
+ )
82
+
83
+ __all__ = [
84
+ "AgentDefinitionCatalogView",
85
+ "AgentDefinitionDetail",
86
+ "AgentDefinitionService",
87
+ "AgentDefinitionView",
88
+ "AgentHandleView",
89
+ "AgentInteractionSummary",
90
+ "AgentInteractionView",
91
+ "AgentReadView",
92
+ "AgentService",
93
+ "AgentTreeEdge",
94
+ "AgentTreeNotification",
95
+ "AgentTreeNode",
96
+ "AgentTreeSnapshot",
97
+ "AgentTreeSubscription",
98
+ "AgentWaitResultView",
99
+ "ApplicationStartupReport",
100
+ "ConfigurationFacade",
101
+ "ConfigurationOverview",
102
+ "HostInputReceipt",
103
+ "MCPConfigCatalogView",
104
+ "MCPConnectionCheckView",
105
+ "MCPServerConfigView",
106
+ "ModelReasoningState",
107
+ "QueuedUserInputView",
108
+ "RUNTIME_TOOL_ARGUMENT_TAIL_MAX_CHARS",
109
+ "RunResult",
110
+ "RuntimeEventRecorder",
111
+ "RuntimeEventSinkRegistration",
112
+ "RuntimeEventCategory",
113
+ "RuntimeEventView",
114
+ "RuntimeService",
115
+ "RuntimeConfigurationResolver",
116
+ "RuntimeConfigurationSnapshot",
117
+ "RuntimeProviderOptions",
118
+ "RuntimeSupervisor",
119
+ "RuntimeSubscription",
120
+ "ResourceService",
121
+ "RunHandle",
122
+ "RuntimeNotification",
123
+ "RuntimeActivity",
124
+ "RuntimeActivitySnapshot",
125
+ "RuntimeStatusSnapshot",
126
+ "RuntimeToolCallActivity",
127
+ "ShutdownPolicy",
128
+ "SupervisorStartResult",
129
+ "UsageAuditLog",
130
+ "UsageEntryList",
131
+ "UsageMutationResult",
132
+ "UsageService",
133
+ "UsageSummary",
134
+ "UsageTotals",
135
+ "ReplayFrame",
136
+ "SessionBranchResult",
137
+ "SessionCatalogEntry",
138
+ "SessionService",
139
+ "SessionTitleGenerator",
140
+ "SessionCatalogQuery",
141
+ "SessionCatalogResult",
142
+ "SessionContextSnapshot",
143
+ "SessionContextStatus",
144
+ "SessionDetail",
145
+ "SessionEventPage",
146
+ "SessionExport",
147
+ "SessionMessagePage",
148
+ "SessionMutationResult",
149
+ "SessionObservationSnapshot",
150
+ "SessionReplay",
151
+ "SessionSummary",
152
+ "SessionTrashResult",
153
+ "SessionView",
154
+ "SpawnAgentRequest",
155
+ ]
@@ -0,0 +1,356 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+
5
+ from ouro.application.agent_models import (
6
+ AgentDefinitionCatalogView,
7
+ AgentDefinitionDetail,
8
+ AgentDefinitionView,
9
+ AgentToolOptionView,
10
+ )
11
+ from ouro.config.agents import AgentDefinitionsConfigManager
12
+ from ouro.config.providers import ProviderConfigManager
13
+ from ouro.core.agents import (
14
+ AGENT_CONTROL_TOOL_NAMES,
15
+ AgentCatalogEntry,
16
+ AgentCatalogSnapshot,
17
+ AgentDefinition,
18
+ EffectiveToolRef,
19
+ ToolSelection,
20
+ )
21
+ from ouro.core.approvals import (
22
+ PermissionMode,
23
+ SessionApprovalMode,
24
+ configured_tool_permission,
25
+ resolve_tool_permission,
26
+ )
27
+ from ouro.core.errors import ConfigError
28
+ from ouro.core.tools import tool_definition_contract_hash
29
+ from ouro.tools import ToolDefinition, ToolRegistry
30
+
31
+ GENERAL_AGENT_DEFINITION = AgentDefinition(
32
+ agent_id="general",
33
+ name="General",
34
+ description="A general-purpose subagent for a bounded delegated task.",
35
+ system_prompt=(
36
+ "Work independently on the delegated task. Use only the available tools and return "
37
+ "a concise result with evidence, verification, and unresolved questions."
38
+ ),
39
+ tool_selection=ToolSelection(mode="all"),
40
+ can_spawn=False,
41
+ )
42
+
43
+
44
+ class AgentDefinitionService:
45
+ """Resolve definitions and current tool capabilities for one decision."""
46
+
47
+ def __init__(self, config: AgentDefinitionsConfigManager) -> None:
48
+ self.config = config
49
+ self.providers = ProviderConfigManager(config.file.root)
50
+
51
+ def default_model_route(self) -> str:
52
+ return self.providers.require_default_route()
53
+
54
+ def list(self, *, tools: ToolRegistry) -> AgentDefinitionCatalogView:
55
+ config = self.config.snapshot()
56
+ entries = self._entries(tools)
57
+ return AgentDefinitionCatalogView(
58
+ revision=config.revision,
59
+ definitions=tuple(
60
+ self._view(
61
+ entry,
62
+ source=(
63
+ "builtin"
64
+ if entry.definition.agent_id == GENERAL_AGENT_DEFINITION.agent_id
65
+ else "user"
66
+ ),
67
+ )
68
+ for entry in entries
69
+ ),
70
+ )
71
+
72
+ def get(self, agent_id: str, *, tools: ToolRegistry) -> AgentDefinitionDetail:
73
+ entry = next(
74
+ (item for item in self._entries(tools) if item.definition.agent_id == agent_id),
75
+ None,
76
+ )
77
+ if entry is None:
78
+ raise ConfigError(f"agent definition not found: {agent_id}")
79
+ source = "builtin" if agent_id == GENERAL_AGENT_DEFINITION.agent_id else "user"
80
+ view = self._view(entry, source=source)
81
+ return AgentDefinitionDetail(
82
+ **view.model_dump(), system_prompt=entry.definition.system_prompt
83
+ )
84
+
85
+ def create(
86
+ self,
87
+ *,
88
+ agent_id: str,
89
+ name: str,
90
+ description: str,
91
+ system_prompt: str,
92
+ tool_selection: ToolSelection,
93
+ model: str | None = None,
94
+ tools: ToolRegistry,
95
+ expected_revision: int,
96
+ ) -> AgentDefinitionCatalogView:
97
+ self._require_known_selected_tools(tool_selection, tools)
98
+ definition = AgentDefinition(
99
+ agent_id=agent_id,
100
+ name=name,
101
+ description=description,
102
+ system_prompt=system_prompt,
103
+ tool_selection=tool_selection,
104
+ model=model,
105
+ )
106
+ self.config.create(definition, expected_revision=expected_revision)
107
+ return self.list(tools=tools)
108
+
109
+ def update(
110
+ self,
111
+ *,
112
+ agent_id: str,
113
+ name: str,
114
+ description: str,
115
+ system_prompt: str,
116
+ tool_selection: ToolSelection,
117
+ model: str | None,
118
+ tools: ToolRegistry,
119
+ expected_revision: int,
120
+ ) -> AgentDefinitionCatalogView:
121
+ if agent_id == GENERAL_AGENT_DEFINITION.agent_id:
122
+ raise ConfigError(f"builtin agent definition is immutable: {agent_id}")
123
+ self._require_known_selected_tools(tool_selection, tools)
124
+ current = next(
125
+ (item for item in self.config.snapshot().definitions if item.agent_id == agent_id),
126
+ None,
127
+ )
128
+ if current is None:
129
+ raise ConfigError(f"agent definition not found: {agent_id}")
130
+ updated = AgentDefinition(
131
+ agent_id=agent_id,
132
+ name=name,
133
+ description=description,
134
+ system_prompt=system_prompt,
135
+ tool_selection=tool_selection,
136
+ model=model,
137
+ can_spawn=current.can_spawn,
138
+ default_budget=current.default_budget,
139
+ )
140
+ self.config.update(updated, expected_revision=expected_revision)
141
+ return self.list(tools=tools)
142
+
143
+ def delete(
144
+ self,
145
+ agent_id: str,
146
+ *,
147
+ expected_revision: int,
148
+ tools: ToolRegistry | None = None,
149
+ ) -> AgentDefinitionCatalogView:
150
+ if agent_id == GENERAL_AGENT_DEFINITION.agent_id:
151
+ raise ConfigError(f"builtin agent definition is immutable: {agent_id}")
152
+ self.config.delete(agent_id, expected_revision=expected_revision)
153
+ return self.list(tools=tools or ToolRegistry())
154
+
155
+ def copy_builtin(
156
+ self,
157
+ agent_id: str,
158
+ *,
159
+ new_agent_id: str,
160
+ name: str,
161
+ tools: ToolRegistry,
162
+ expected_revision: int,
163
+ ) -> AgentDefinitionCatalogView:
164
+ if agent_id != GENERAL_AGENT_DEFINITION.agent_id:
165
+ raise ConfigError(f"builtin agent definition not found: {agent_id}")
166
+ copied = AgentDefinition(
167
+ **{
168
+ **GENERAL_AGENT_DEFINITION.model_dump(exclude={"contract_hash"}),
169
+ "agent_id": new_agent_id,
170
+ "name": name,
171
+ }
172
+ )
173
+ self.config.create(copied, expected_revision=expected_revision)
174
+ return self.list(tools=tools)
175
+
176
+ def tool_options(self, tools: ToolRegistry) -> tuple[AgentToolOptionView, ...]:
177
+ return tuple(
178
+ AgentToolOptionView(
179
+ name=definition.name,
180
+ description=definition.description,
181
+ effect=definition.effect,
182
+ owner_id=self._owner_id(tools, definition.name),
183
+ execution_contract_hash=self.tool_reference(
184
+ tools, definition
185
+ ).execution_contract_hash,
186
+ )
187
+ for definition in self._candidate_definitions(tools)
188
+ )
189
+
190
+ def catalog_snapshot(
191
+ self,
192
+ tools: ToolRegistry,
193
+ *,
194
+ permission_overrides: Mapping[str, PermissionMode] | None = None,
195
+ plugin_permission_overrides: Mapping[str, PermissionMode] | None = None,
196
+ approval_mode: SessionApprovalMode = "inherit",
197
+ ) -> AgentCatalogSnapshot:
198
+ config = self.config.snapshot()
199
+ return AgentCatalogSnapshot(
200
+ revision=config.revision,
201
+ entries=self._entries(
202
+ tools,
203
+ permission_overrides=permission_overrides,
204
+ plugin_permission_overrides=plugin_permission_overrides,
205
+ approval_mode=approval_mode,
206
+ ),
207
+ )
208
+
209
+ def _entries(
210
+ self,
211
+ tools: ToolRegistry,
212
+ *,
213
+ permission_overrides: Mapping[str, PermissionMode] | None = None,
214
+ plugin_permission_overrides: Mapping[str, PermissionMode] | None = None,
215
+ approval_mode: SessionApprovalMode = "inherit",
216
+ ) -> tuple[AgentCatalogEntry, ...]:
217
+ config = self.config.snapshot()
218
+ overrides = permission_overrides or {}
219
+ plugin_overrides = plugin_permission_overrides or {}
220
+ definitions = (GENERAL_AGENT_DEFINITION, *config.definitions)
221
+ candidates = {
222
+ definition.name: definition for definition in self._candidate_definitions(tools)
223
+ }
224
+ entries: list[AgentCatalogEntry] = []
225
+ for definition in definitions:
226
+ selected_names = (
227
+ tuple(candidates)
228
+ if definition.tool_selection.mode == "all"
229
+ else definition.tool_selection.names
230
+ )
231
+ missing = tuple(name for name in selected_names if name not in candidates)
232
+ effective: list[EffectiveToolRef] = []
233
+ for name in selected_names:
234
+ if name not in candidates:
235
+ continue
236
+ tool = candidates[name]
237
+ raw_plugin_id = tool.metadata.get("plugin_id")
238
+ plugin_id = (
239
+ raw_plugin_id if isinstance(raw_plugin_id, str) and raw_plugin_id else None
240
+ )
241
+ permission = resolve_tool_permission(
242
+ tool.permission_mode,
243
+ approval_mode,
244
+ permission_control=tool.permission_control,
245
+ override=configured_tool_permission(
246
+ name,
247
+ plugin_id,
248
+ tool_overrides=overrides,
249
+ plugin_overrides=plugin_overrides,
250
+ ),
251
+ )
252
+ if permission == "deny":
253
+ continue
254
+ effective.append(
255
+ self.tool_reference(
256
+ tools,
257
+ tool,
258
+ minimum_permission_mode=permission,
259
+ )
260
+ )
261
+ entries.append(
262
+ AgentCatalogEntry(
263
+ definition=definition,
264
+ effective_tools=tuple(effective),
265
+ missing_tool_names=missing,
266
+ )
267
+ )
268
+ return tuple(entries)
269
+
270
+ @staticmethod
271
+ def _candidate_definitions(tools: ToolRegistry) -> tuple[ToolDefinition, ...]:
272
+ return tuple(
273
+ definition
274
+ for definition in tools.definitions()
275
+ if definition.name not in AGENT_CONTROL_TOOL_NAMES
276
+ )
277
+
278
+ @classmethod
279
+ def tool_reference(
280
+ cls,
281
+ tools: ToolRegistry,
282
+ definition: ToolDefinition,
283
+ *,
284
+ minimum_permission_mode: PermissionMode = "auto",
285
+ ) -> EffectiveToolRef:
286
+ metadata = definition.metadata
287
+ plugin_id = metadata.get("plugin_id")
288
+ contribution_id = metadata.get("contribution_id")
289
+ canonical_id = (
290
+ f"{plugin_id}/tools/{contribution_id}"
291
+ if isinstance(plugin_id, str) and isinstance(contribution_id, str)
292
+ else f"ouro.host/tools/{definition.name}"
293
+ )
294
+ execution_hash = metadata.get("execution_contract_hash")
295
+ raw_plugin_permissions = metadata.get("plugin_permissions", ())
296
+ plugin_permissions = (
297
+ frozenset(item for item in raw_plugin_permissions if isinstance(item, str))
298
+ if isinstance(raw_plugin_permissions, (list, tuple))
299
+ else frozenset()
300
+ )
301
+ is_shell = isinstance(metadata.get("shell_kind"), str)
302
+ is_mcp = definition.source == "mcp"
303
+ return EffectiveToolRef(
304
+ public_name=definition.name,
305
+ canonical_tool_id=canonical_id,
306
+ owner_id=cls._owner_id(tools, definition.name),
307
+ execution_contract_hash=(
308
+ execution_hash
309
+ if isinstance(execution_hash, str) and execution_hash
310
+ else tool_definition_contract_hash(definition)
311
+ ),
312
+ source=definition.source,
313
+ effect=definition.effect,
314
+ minimum_permission_mode=minimum_permission_mode,
315
+ requires_network=(is_shell or is_mcp or "network.outbound" in plugin_permissions),
316
+ requires_mcp=is_mcp,
317
+ requires_external_paths=(is_shell or is_mcp or "process.spawn" in plugin_permissions),
318
+ )
319
+
320
+ @staticmethod
321
+ def _owner_id(tools: ToolRegistry, name: str) -> str:
322
+ return tools.owner_of(name) or "ouro.host"
323
+
324
+ @staticmethod
325
+ def _require_known_selected_tools(
326
+ selection: ToolSelection,
327
+ tools: ToolRegistry,
328
+ ) -> None:
329
+ if selection.mode != "selected":
330
+ return
331
+ known = set(tools.names()) - AGENT_CONTROL_TOOL_NAMES
332
+ missing = sorted(set(selection.names) - known)
333
+ if missing:
334
+ raise ConfigError("unknown selected tools: " + ", ".join(missing))
335
+
336
+ @staticmethod
337
+ def _view(
338
+ entry: AgentCatalogEntry,
339
+ *,
340
+ source: str,
341
+ ) -> AgentDefinitionView:
342
+ return AgentDefinitionView(
343
+ agent_id=entry.definition.agent_id,
344
+ name=entry.definition.name,
345
+ description=entry.definition.description,
346
+ tool_selection=entry.definition.tool_selection,
347
+ model=entry.definition.model,
348
+ source="builtin" if source == "builtin" else "user",
349
+ editable=source == "user",
350
+ compatibility="available" if entry.available else "unavailable",
351
+ missing_tool_names=entry.missing_tool_names,
352
+ contract_hash=entry.definition.contract_hash,
353
+ )
354
+
355
+
356
+ __all__ = ["AgentDefinitionService", "GENERAL_AGENT_DEFINITION"]
@@ -0,0 +1,179 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from collections.abc import Awaitable, Callable
6
+ from typing import Protocol
7
+
8
+ from ouro.application.events import ApplicationEventPublisher
9
+ from ouro.application.supervisor_models import RunHandle, RuntimeStatusSnapshot
10
+ from ouro.core.agents import DelegationRecord
11
+ from ouro.core.callbacks import run_blocking
12
+ from ouro.core.errors import DelegationRevisionConflictError, PersistenceError, SessionBusyError
13
+ from ouro.core.redaction import redact_text
14
+ from ouro.core.state import RunRecord
15
+ from ouro.sessions import SessionStore
16
+
17
+ _LOGGER = logging.getLogger(__name__)
18
+
19
+
20
+ class RunObserver(Protocol):
21
+ async def wait_run(self, session_id: str, run_id: str) -> RunRecord: ...
22
+
23
+ async def wait(self, handle: RunHandle) -> RuntimeStatusSnapshot: ...
24
+
25
+ def release_handle(self, handle: RunHandle) -> None: ...
26
+
27
+
28
+ class AgentExecutionTasks:
29
+ """Own result settlement tasks by durable child Run identity.
30
+
31
+ Retries are bounded and scoped to observed delegations. Pending delivery
32
+ remains durable after exhaustion and can be retried by targeted recovery.
33
+ No Session enumeration, per-token checkpoint reads, or extra polling loop.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ store: SessionStore,
39
+ observer: RunObserver,
40
+ events: ApplicationEventPublisher,
41
+ settle: Callable[[DelegationRecord], Awaitable[object]],
42
+ ) -> None:
43
+ self.store = store
44
+ self.observer = observer
45
+ self.events = events
46
+ self.settle = settle
47
+ self._tasks: dict[tuple[str, str], asyncio.Task[None]] = {}
48
+ self._receipts: set[asyncio.Task[None]] = set()
49
+ self._closed = False
50
+
51
+ def watch(self, relation: DelegationRecord) -> None:
52
+ if (
53
+ self._closed
54
+ or relation.child_session_id is None
55
+ or relation.current_child_run_id is None
56
+ or (relation.result is not None and relation.result.delivery.status != "pending")
57
+ ):
58
+ return
59
+ key = relation.delegation_id, relation.current_child_run_id
60
+ current = self._tasks.get(key)
61
+ if current is not None and not current.done():
62
+ return
63
+ task = asyncio.create_task(self._observe(relation), name=f"ouro-agent-result-{key[1]}")
64
+ self._tasks[key] = task
65
+ task.add_done_callback(lambda done: self._finished(key, done))
66
+
67
+ def own_receipt(self, handle: RunHandle) -> None:
68
+ """Release private process receipts; result waits use durable identity."""
69
+ if self._closed:
70
+ return
71
+
72
+ async def release() -> None:
73
+ await self.observer.wait(handle)
74
+ self.observer.release_handle(handle)
75
+
76
+ task = asyncio.create_task(release(), name=f"ouro-agent-receipt-{handle.handle_id}")
77
+ self._receipts.add(task)
78
+ task.add_done_callback(self._receipt_finished)
79
+
80
+ def _receipt_finished(self, task: asyncio.Task[None]) -> None:
81
+ self._receipts.discard(task)
82
+ if not task.cancelled() and task.exception() is not None:
83
+ _LOGGER.error("Agent receipt cleanup failed: %s", type(task.exception()).__name__)
84
+
85
+ async def close(self) -> None:
86
+ self._closed = True
87
+ tasks = (*self._tasks.values(), *self._receipts)
88
+ for task in tasks:
89
+ task.cancel()
90
+ await asyncio.gather(*tasks, return_exceptions=True)
91
+ self._tasks.clear()
92
+ self._receipts.clear()
93
+
94
+ def _finished(self, key: tuple[str, str], task: asyncio.Task[None]) -> None:
95
+ if self._tasks.get(key) is task:
96
+ self._tasks.pop(key)
97
+ if not task.cancelled() and task.exception() is not None:
98
+ # Reporting itself can fail if storage is unavailable. Keep this
99
+ # visible without logging prompts, tool payloads, or raw exceptions.
100
+ _LOGGER.error(
101
+ "Agent settlement reporting failed: %s (%s)", key, type(task.exception()).__name__
102
+ )
103
+
104
+ async def _observe(self, relation: DelegationRecord) -> None:
105
+ async def settle() -> object:
106
+ assert (
107
+ relation.child_session_id is not None and relation.current_child_run_id is not None
108
+ )
109
+ await self.observer.wait_run(relation.child_session_id, relation.current_child_run_id)
110
+ return await self.settle(relation)
111
+
112
+ await self.retry(relation, settle)
113
+
114
+ async def retry(
115
+ self, relation: DelegationRecord, operation: Callable[[], Awaitable[object]]
116
+ ) -> None:
117
+ for attempt, delay in enumerate((0, 0.1, 0.5, 2.0)):
118
+ if delay:
119
+ await asyncio.sleep(delay)
120
+ try:
121
+ await operation()
122
+ return
123
+ except Exception as exc:
124
+ retryable = isinstance(exc, (OSError, PersistenceError, SessionBusyError))
125
+ error = f"{type(exc).__name__}: {redact_text(str(exc))}"[:4_000]
126
+ try:
127
+ await run_blocking(self._retain_error, relation, error)
128
+ except OSError, PersistenceError:
129
+ # The failed dependency may be the same store used for
130
+ # diagnostics. Still spend only the bounded retry budget.
131
+ if retryable and attempt < 3:
132
+ continue
133
+ raise
134
+ if retryable and attempt < 3:
135
+ continue
136
+ await self.events.publish(
137
+ session_id=relation.root_session_id,
138
+ run_id=relation.parent_run_id,
139
+ event_type="diagnostic",
140
+ payload={
141
+ "code": "agent_result_delivery_incomplete",
142
+ "severity": "error",
143
+ "component": "application.agents",
144
+ "delegation_id": relation.delegation_id,
145
+ "child_run_id": relation.current_child_run_id,
146
+ "error": error,
147
+ "recoverable": True,
148
+ "suggested_action": "Reconcile this root Session to retry settlement.",
149
+ },
150
+ )
151
+ return
152
+
153
+ def _retain_error(self, relation: DelegationRecord, error: str) -> None:
154
+ for _ in range(3):
155
+ current = self.store.load_delegation(relation.root_session_id, relation.delegation_id)
156
+ result = current.result
157
+ if (
158
+ result is None
159
+ or result.child_run_id != relation.current_child_run_id
160
+ or result.delivery.status != "pending"
161
+ ):
162
+ return
163
+ try:
164
+ self.store.save_delegation(
165
+ current.model_copy(
166
+ update={
167
+ "result": result.model_copy(
168
+ update={
169
+ "delivery": result.delivery.model_copy(update={"error": error})
170
+ }
171
+ )
172
+ }
173
+ ),
174
+ expected_revision=current.revision,
175
+ )
176
+ return
177
+ except DelegationRevisionConflictError:
178
+ continue
179
+ raise DelegationRevisionConflictError("Could not retain pending result delivery error")