phronomy 0.25.0 → 0.27.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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/CONTRIBUTING.md +4 -4
  4. data/README.md +14 -7
  5. data/VERIFY.sh +27 -27
  6. data/benchmark/bench_agent_invoke.rb +26 -22
  7. data/benchmark/bench_context_assembler.rb +4 -5
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  10. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  11. data/docs/architecture/agent-context.md +5 -3
  12. data/docs/architecture/agent-transition-ownership.md +89 -0
  13. data/docs/architecture/before-llm-input.md +6 -0
  14. data/docs/architecture/context-management.md +30 -0
  15. data/docs/architecture/context-preparation-steps.md +90 -0
  16. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  17. data/docs/architecture/execution-metadata-and-values.md +80 -0
  18. data/docs/architecture/generator-verifier-ownership.md +111 -0
  19. data/docs/architecture/multi-agent-handoff.md +8 -2
  20. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  21. data/docs/architecture/persistence.md +29 -6
  22. data/docs/architecture/refactoring-closure.md +334 -0
  23. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  24. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  25. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  26. data/docs/architecture/tracing.md +4 -4
  27. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  28. data/docs/architecture.md +209 -0
  29. data/docs/async-composition.md +205 -0
  30. data/docs/decisions/010-cooperative-first-concurrency.md +23 -23
  31. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  32. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  33. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  34. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  35. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  36. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  37. data/docs/decisions/032-storage-backend-composition.md +80 -0
  38. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  39. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  40. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  41. data/docs/decisions/036-context-contract-ownership.md +86 -0
  42. data/docs/decisions/037-common-definition-ownership.md +62 -0
  43. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  44. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  45. data/docs/decisions/040-configuration-default-composition.md +94 -0
  46. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  47. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  48. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  49. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  50. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  51. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  52. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  53. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  54. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  55. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  56. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  57. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  58. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  59. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  60. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  61. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  62. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  63. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  64. data/docs/decisions/README.md +35 -6
  65. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  66. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  67. data/docs/features.md +21 -12
  68. data/docs/getting-started.md +10 -10
  69. data/docs/migrations/0.15.md +5 -0
  70. data/docs/migrations/durable-semantic-coordination-v2.md +7 -1
  71. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  72. data/docs/migrations/neutral-storage-spi.md +43 -0
  73. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  74. data/docs/migrations/shared-state-multi-agent.md +44 -0
  75. data/docs/migrations/storage-backend-composition.md +117 -0
  76. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  77. data/docs/persistence-backends.md +162 -204
  78. data/docs/runtime-and-concurrency.md +205 -60
  79. data/lib/phronomy/agent/api/agent.rb +17 -0
  80. data/lib/phronomy/agent/async_event_api.rb +2 -2
  81. data/lib/phronomy/agent/base.rb +55 -251
  82. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  83. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  84. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  85. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  86. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  87. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  88. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  89. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  90. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  91. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  92. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  93. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  94. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  95. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  96. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  97. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  98. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  99. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +20 -96
  100. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  101. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  102. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +12 -13
  103. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  104. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  105. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  106. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  107. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  108. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  109. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  110. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  111. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  112. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +22 -73
  113. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  114. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  115. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  116. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  117. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  118. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  119. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  120. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  121. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  122. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  123. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  124. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  125. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  126. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  127. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  128. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  129. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  130. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  131. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  132. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  133. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  134. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  135. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  136. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  137. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  138. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  139. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  140. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +36 -41
  141. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  142. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +9 -13
  143. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  144. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  145. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  146. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  147. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  148. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  149. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  150. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  151. data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +2 -2
  152. data/lib/phronomy/common/configuration_error.rb +7 -0
  153. data/lib/phronomy/common/error.rb +5 -0
  154. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  155. data/lib/phronomy/common/values/serializable.rb +32 -0
  156. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  157. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  158. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  159. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  160. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  161. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +15 -8
  162. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  163. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  164. data/lib/phronomy/engine/concurrency/offload_pool.rb +29 -16
  165. data/lib/phronomy/engine/concurrency/operation_binding.rb +43 -0
  166. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +4 -62
  167. data/lib/phronomy/engine/concurrency/result_collector.rb +99 -0
  168. data/lib/phronomy/engine/concurrency/result_composition.rb +145 -0
  169. data/lib/phronomy/engine/concurrency/subscriptions.rb +68 -0
  170. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  171. data/lib/phronomy/engine/event_loop.rb +160 -626
  172. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  173. data/lib/phronomy/engine/execution.rb +229 -0
  174. data/lib/phronomy/engine/execution_cancellation_error.rb +14 -0
  175. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  176. data/lib/phronomy/engine/execution_timeout_error.rb +14 -0
  177. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  178. data/lib/phronomy/engine/fsm_session.rb +38 -31
  179. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +3 -1
  180. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +3 -1
  181. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +3 -1
  182. data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +13 -1
  183. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  184. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  185. data/lib/phronomy/engine/runtime/timer_queue.rb +11 -0
  186. data/lib/phronomy/engine/runtime.rb +80 -84
  187. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  188. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  189. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  190. data/lib/phronomy/engine/{task.rb → task_result.rb} +101 -42
  191. data/lib/phronomy/engine/timeout_error.rb +7 -0
  192. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  193. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  194. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  195. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  196. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  197. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  198. data/lib/phronomy/llm_adapter/base.rb +2 -2
  199. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  200. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  201. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  202. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  203. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  204. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  205. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  206. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  207. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +8 -8
  208. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  209. data/lib/phronomy/multi_agent/orchestrator.rb +36 -68
  210. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  211. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  212. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  213. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  214. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  215. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  216. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  217. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  218. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  219. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  220. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  221. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  222. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  223. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  224. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  225. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  226. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  227. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  228. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  229. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  230. data/lib/phronomy/storage/backend.rb +101 -0
  231. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  232. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  233. data/lib/phronomy/storage/blobs.rb +31 -0
  234. data/lib/phronomy/storage/condition.rb +25 -0
  235. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  236. data/lib/phronomy/storage/conflict_error.rb +9 -0
  237. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  238. data/lib/phronomy/storage/entry.rb +33 -0
  239. data/lib/phronomy/storage/guard_ref.rb +13 -0
  240. data/lib/phronomy/storage/not_found_error.rb +9 -0
  241. data/lib/phronomy/storage/record_codec.rb +177 -0
  242. data/lib/phronomy/storage/records.rb +61 -0
  243. data/lib/phronomy/storage/resource.rb +126 -0
  244. data/lib/phronomy/storage/scope.rb +25 -0
  245. data/lib/phronomy/storage/serialization_error.rb +9 -0
  246. data/lib/phronomy/storage/streams.rb +44 -0
  247. data/lib/phronomy/storage/transaction_error.rb +10 -0
  248. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  249. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  250. data/lib/phronomy/storage/validation.rb +53 -0
  251. data/lib/phronomy/storage/view.rb +131 -0
  252. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +5 -3
  253. data/lib/phronomy/testing/fake_clock.rb +13 -9
  254. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  255. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  256. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  257. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  258. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  259. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  260. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  261. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  262. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  263. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  264. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  265. data/lib/phronomy/tools/agent.rb +6 -6
  266. data/lib/phronomy/vector_store/async_backend.rb +5 -5
  267. data/lib/phronomy/vector_store/embeddings/base.rb +2 -2
  268. data/lib/phronomy/version.rb +1 -1
  269. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  270. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  271. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  272. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  273. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +126 -75
  274. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  275. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  276. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  277. data/lib/phronomy/workflow/phase_machine_builder.rb +8 -8
  278. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  279. data/lib/phronomy.rb +52 -94
  280. data/scripts/api_snapshot.rb +4 -2
  281. data/scripts/storage_spi_snapshot.rb +36 -0
  282. data/sig/phronomy/agent.rbs +4 -5
  283. data/sig/phronomy/execution_receiver.rbs +34 -0
  284. data/sig/phronomy/extensions.rbs +5 -5
  285. data/sig/phronomy/handoff.rbs +4 -2
  286. data/sig/phronomy/multi_agent.rbs +19 -3
  287. data/sig/phronomy/persistence.rbs +7 -91
  288. data/sig/phronomy/runtime.rbs +34 -7
  289. data/sig/phronomy/storage.rbs +174 -0
  290. data/sig/phronomy/tool.rbs +11 -2
  291. data/sig/phronomy/workflow.rbs +1 -1
  292. metadata +236 -99
  293. data/lib/phronomy/agent/execution_coordinator.rb +0 -3138
  294. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  295. data/lib/phronomy/agent/recovery_support.rb +0 -504
  296. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  297. data/lib/phronomy/agent/tool_executor.rb +0 -55
  298. data/lib/phronomy/generator_verifier.rb +0 -369
  299. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  300. data/lib/phronomy/multi_agent/fan_out_invocation.rb +0 -137
  301. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +0 -118
  302. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  303. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  304. data/lib/phronomy/persistence/in_memory.rb +0 -690
  305. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  306. data/lib/phronomy/persistence.rb +0 -276
  307. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  308. data/lib/phronomy/workflow_recovery.rb +0 -123
  309. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  310. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  311. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  312. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  313. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  314. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  315. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  316. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  317. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  318. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  319. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  320. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  321. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  322. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  323. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
@@ -6,6 +6,9 @@ module Phronomy
6
6
  class Orchestrator < Agent::Base
7
7
  agent_definition id: "orchestrator", version: 1
8
8
 
9
+ ParallelChild = Data.define(:index, :agent, :input, :config)
10
+ private_constant :ParallelChild
11
+
9
12
  # Own one live cancellation token shared with this invocation's static
10
13
  # children, including when Recovery reconstructs the invocation.
11
14
  # @api private
@@ -20,7 +23,7 @@ module Phronomy
20
23
  raise Phronomy::EventLoopReentrancyError, "Orchestrator#resume cannot block EventLoop"
21
24
  end
22
25
  execution = persistence.executions.load(execution_id)
23
- raise Phronomy::Persistence::ConflictError, "Parent execution owner mismatch" unless execution.agent_id == agent_id
26
+ raise Phronomy::Storage::ConflictError, "Parent execution owner mismatch" unless execution.agent_id == agent_id
24
27
  input = persistence.contents.fetch_text(execution.metadata.fetch("current_input_ref"))
25
28
  result = Phronomy::Agent::ExactExecution.start(agent: self, execution_id: execution_id, input: input, config: config).wait_result
26
29
  raise Phronomy::Agent::RecoverySupport.error_from_failure(result[:error]) if result[:error]
@@ -29,7 +32,7 @@ module Phronomy
29
32
 
30
33
  def self.subagent(name, agent_class, on_error: :raise, inherit_knowledge: true)
31
34
  # A subagent Tool is logically asynchronous: ToolInvocation starts the
32
- # child Agent and resumes when its completion Task settles. It must not
35
+ # child Agent and resumes when its completion TaskResult settles. It must not
33
36
  # occupy an OffloadPool worker while waiting for the child.
34
37
  tool_class = Class.new(Phronomy::Tools::Agent) do
35
38
  def self.__framework_owned_operation? = true
@@ -57,7 +60,7 @@ module Phronomy
57
60
  super(args, cancellation_token: cancellation_token, config: config)
58
61
  end
59
62
  rescue => error
60
- Phronomy::Task.deferred(name: "subagent-dispatch-failed").tap { |task| task.fail(error) }
63
+ Phronomy::TaskResult.deferred(name: "subagent-dispatch-failed").tap { |task| task.fail(error) }
61
64
  end
62
65
 
63
66
  define_method(:execute_async) do |input:, cancellation_token: nil, config: {}|
@@ -87,7 +90,7 @@ module Phronomy
87
90
  end
88
91
  end
89
92
  source = agent.invoke_async(input, config: task_config)
90
- result_task = Phronomy::Task.deferred(
93
+ result_task = Phronomy::TaskResult.deferred(
91
94
  name: "subagent-tool-#{name}"
92
95
  )
93
96
  source.on_complete do |result, error|
@@ -99,7 +102,7 @@ module Phronomy
99
102
  end
100
103
  result_task
101
104
  rescue => error
102
- result_task ||= Phronomy::Task.deferred(
105
+ result_task ||= Phronomy::TaskResult.deferred(
103
106
  name: "subagent-tool-#{name}"
104
107
  )
105
108
  (on_error == :raise) ? result_task.fail(error) : result_task.complete(nil)
@@ -160,73 +163,38 @@ module Phronomy
160
163
  inherit_knowledge: true
161
164
  )
162
165
  validate_parallel_options!(tasks, max_concurrency, on_error)
163
- return Phronomy::Task.deferred(name: "fan-out-empty").tap { |task| task.complete([]) } if tasks.empty?
164
-
165
166
  children = build_fan_out_children(
166
167
  tasks,
167
- cancellation_token: cancellation_token,
168
- invocation_context: invocation_context,
169
- inherit_knowledge: inherit_knowledge
170
- )
171
- invocation = FanOutInvocation.new(
172
- children: children,
173
- max_concurrency: max_concurrency || children.length,
174
- on_error: on_error
175
- )
176
- effective_token = cancellation_token || invocation_context&.cancellation_token
177
- FanOutSessionBuilder.start(
178
- invocation: invocation,
179
- timeout: timeout,
180
- cancellation_token: effective_token
181
- )
182
- end
183
-
184
- def fan_out(
185
- agent:,
186
- inputs:,
187
- config: {},
188
- max_concurrency: nil,
189
- on_error: :raise,
190
- timeout: nil,
191
- cancellation_token: nil,
192
- invocation_context: nil,
193
- inherit_knowledge: true
194
- )
195
- dispatch_parallel(
196
- *inputs.map do |input|
197
- {agent: agent, input: input, config: config}
198
- end,
199
- max_concurrency: max_concurrency,
200
- on_error: on_error,
201
- timeout: timeout,
202
- cancellation_token: cancellation_token,
203
- invocation_context: invocation_context,
168
+ cancellation_token: nil,
169
+ invocation_context: nil,
204
170
  inherit_knowledge: inherit_knowledge
205
171
  )
206
- end
172
+ Phronomy::Execution.send(:__run_async, children,
173
+ timeout: timeout, cancellation_token: cancellation_token,
174
+ invocation_context: invocation_context, concurrency_limit: max_concurrency) do |child, execution|
175
+ # Preserve a child-specific context as well as this dispatch's controls.
176
+ # Agent admission adds that context's constraints to the supplied token.
177
+ binding = Phronomy::Concurrency::OperationBinding.new(
178
+ invocation_context: execution.invocation_context,
179
+ cancellation_token: child.config[:cancellation_token]
180
+ )
181
+ child_context = child.config[:invocation_context] ||
182
+ execution.invocation_context.merge(parent_task_id: invocation_context&.task_id)
183
+ begin
184
+ source = child.agent.invoke_async(child.input,
185
+ config: child.config.merge(cancellation_token: binding.token),
186
+ invocation_context: child_context)
187
+ binding.track(source)
188
+ rescue
189
+ binding.close
190
+ raise
191
+ end
192
+ end.map do |outcomes|
193
+ failure = outcomes.find { |outcome| outcome.status != :completed }
194
+ raise failure.error if failure && on_error == :raise
207
195
 
208
- def fan_out_async(
209
- agent:,
210
- inputs:,
211
- config: {},
212
- max_concurrency: nil,
213
- on_error: :raise,
214
- timeout: nil,
215
- cancellation_token: nil,
216
- invocation_context: nil,
217
- inherit_knowledge: true
218
- )
219
- dispatch_parallel_async(
220
- *inputs.map do |input|
221
- {agent: agent, input: input, config: config}
222
- end,
223
- max_concurrency: max_concurrency,
224
- on_error: on_error,
225
- timeout: timeout,
226
- cancellation_token: cancellation_token,
227
- invocation_context: invocation_context,
228
- inherit_knowledge: inherit_knowledge
229
- )
196
+ outcomes.map { |outcome| (outcome.status == :completed) ? outcome.value : nil }
197
+ end
230
198
  end
231
199
 
232
200
  def subagent(
@@ -366,7 +334,7 @@ module Phronomy
366
334
  knowledge_snapshot: knowledge_snapshot
367
335
  )
368
336
 
369
- FanOutInvocation::Child.new(
337
+ ParallelChild.new(
370
338
  index: index,
371
339
  agent: child_agent,
372
340
  input: task.fetch(:input),
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ module Persistence
6
+ # Current-format record schemas and conversion for this domain.
7
+ # @api private
8
+ module Codec
9
+ extend Phronomy::Storage::RecordCodec
10
+
11
+ module_function
12
+
13
+ def encode_team_root(value)
14
+ payload = value.to_h
15
+ Phronomy::MultiAgent::TeamRoot.from_h(payload)
16
+ build_record("phronomy.team_root", "0.1", payload)
17
+ rescue Phronomy::Storage::SerializationError
18
+ raise
19
+ rescue => error
20
+ serialization_error("cannot encode TeamRoot", error)
21
+ end
22
+
23
+ def decode_team_root(record)
24
+ payload = current_payload!(record, record_type: "phronomy.team_root",
25
+ format_version: "0.1", keys: Phronomy::MultiAgent::TeamRoot::ATTRIBUTES, label: "TeamRoot")
26
+ Phronomy::MultiAgent::TeamRoot.from_h(payload)
27
+ rescue Phronomy::Storage::SerializationError
28
+ raise
29
+ rescue => error
30
+ serialization_error("cannot decode TeamRoot", error)
31
+ end
32
+
33
+ def encode_team_execution(value)
34
+ payload = value.to_h
35
+ Phronomy::MultiAgent::TeamExecution.from_h(payload)
36
+ build_record("phronomy.team_execution", "0.1", payload)
37
+ rescue Phronomy::Storage::SerializationError
38
+ raise
39
+ rescue => error
40
+ serialization_error("cannot encode TeamExecution", error)
41
+ end
42
+
43
+ def decode_team_execution(record)
44
+ payload = current_payload!(record, record_type: "phronomy.team_execution",
45
+ format_version: "0.1", keys: Phronomy::MultiAgent::TeamExecution::ATTRIBUTES, label: "TeamExecution")
46
+ Phronomy::MultiAgent::TeamExecution.from_h(payload)
47
+ rescue Phronomy::Storage::SerializationError
48
+ raise
49
+ rescue => error
50
+ serialization_error("cannot decode TeamExecution", error)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ module Persistence
6
+ # Read and assemble domain results; the public entry point owns the execution-thread guard.
7
+ # @api private
8
+ class Queries
9
+ def initialize(repositories)
10
+ @repositories = repositories
11
+ end
12
+
13
+ def team_execution_result(team_execution_id)
14
+ execution = @repositories.team_executions.load(team_execution_id)
15
+ {
16
+ team_execution_id: execution.team_execution_id, team_id: execution.team_id,
17
+ status: execution.status, phase: execution.phase,
18
+ result_ref: execution.result_ref, error_ref: execution.error_ref,
19
+ result: execution.result_ref && @repositories.contents.fetch_json(execution.result_ref),
20
+ error: execution.error_ref && @repositories.contents.fetch_json(execution.error_ref)
21
+ }.then { |value| Phronomy::Values::Immutable.copy(value) }
22
+ end
23
+
24
+ def list_team_executions(team_id, after: nil, limit: 100)
25
+ @repositories.team_executions.list(team_id, after: after, limit: limit)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ module Persistence
6
+ # Owns execution activity, ownership, admission and durable record encoding.
7
+ # @api private
8
+ class TeamExecutionRepository
9
+ def initialize(view) = @view = view
10
+
11
+ def create_active(execution)
12
+ raise Phronomy::Storage::SerializationError, "create_active requires an active TeamExecution" unless execution.active?
13
+ record = Codec.encode_team_execution(execution)
14
+ admission do
15
+ @view.atomic do |bound|
16
+ entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).insert(key: execution.team_execution_id.to_s,
17
+ revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
18
+ decode(entry, execution.team_execution_id, owner: execution.team_id,
19
+ revision: execution.execution_revision, active: true)
20
+ end
21
+ end
22
+ end
23
+
24
+ def load(team_execution_id)
25
+ value = @view.atomic do |bound|
26
+ entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).read(team_execution_id.to_s)
27
+ entry && decode(entry, team_execution_id)
28
+ end
29
+ value || raise(Phronomy::Storage::NotFoundError, "TeamExecution not found: #{team_execution_id}")
30
+ end
31
+
32
+ def save(team_execution_id, expected_revision:, execution:)
33
+ expected = Integer(expected_revision)
34
+ revision = Integer(execution.execution_revision)
35
+ raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
36
+ unless execution.team_execution_id.to_s == team_execution_id.to_s
37
+ raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
38
+ end
39
+ record = Codec.encode_team_execution(execution)
40
+ admission do
41
+ @view.atomic do |bound|
42
+ entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).replace(key: team_execution_id.to_s,
43
+ expected_revision: expected, next_revision: revision, attributes: attributes(execution),
44
+ expected_attributes: execution.active? ? {active: true} : {}, record: record)
45
+ decode(entry, team_execution_id, owner: execution.team_id, revision: revision, active: execution.active?)
46
+ end
47
+ end
48
+ end
49
+
50
+ def list_active(team_id)
51
+ @view.atomic do |bound|
52
+ bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).scan(index: :owner_active,
53
+ equals: {owner: team_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: team_id, active: true) }.freeze
54
+ end
55
+ end
56
+
57
+ def list(team_id, after: nil, limit: 100)
58
+ raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
59
+ @view.atomic do |bound|
60
+ bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: team_id.to_s},
61
+ after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: team_id) }.freeze
62
+ end
63
+ end
64
+
65
+ def delete(team_execution_id)
66
+ @view.records(Phronomy::TeamStorageSchema::EXECUTIONS).delete(key: team_execution_id.to_s)
67
+ end
68
+
69
+ def delete_for_team(team_id)
70
+ @view.records(Phronomy::TeamStorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: team_id.to_s})
71
+ end
72
+
73
+ def assert_idle!(team_id)
74
+ condition = Phronomy::Storage::Condition::NoRows.new(resource: Phronomy::TeamStorageSchema::EXECUTIONS,
75
+ index: :owner_active, equals: {owner: team_id.to_s, active: true})
76
+ @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: Phronomy::TeamStorageSchema::ROOTS, key: team_id.to_s)],
77
+ conditions: [condition])
78
+ rescue Phronomy::Storage::ConditionFailedError => error
79
+ raise unless error.condition.equal?(condition)
80
+ raise Phronomy::AgentBusyError, error.message
81
+ end
82
+
83
+ private
84
+
85
+ def attributes(execution) = {owner: execution.team_id.to_s, active: execution.active?}
86
+
87
+ def admission
88
+ yield
89
+ rescue Phronomy::Storage::UniqueConstraintError => error
90
+ raise unless error.resource == Phronomy::TeamStorageSchema::EXECUTIONS.id && error.constraint == :one_active_owner
91
+ raise Phronomy::AgentBusyError, error.message
92
+ end
93
+
94
+ def decode(entry, team_execution_id, owner: nil, revision: nil, active: nil)
95
+ execution = Codec.decode_team_execution(entry.record)
96
+ valid = entry.key == team_execution_id.to_s && execution.team_execution_id == entry.key &&
97
+ execution.execution_revision == entry.revision && attributes(execution) == entry.attributes &&
98
+ (!owner || execution.team_id == owner.to_s) && (!revision || execution.execution_revision == revision) &&
99
+ (active.nil? || execution.active? == active)
100
+ unless valid
101
+ raise Phronomy::Storage::SerializationError, "backend returned another Team Execution identity/metadata"
102
+ end
103
+ execution
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ module Persistence
6
+ # Owns Team root encoding and validates returned storage metadata.
7
+ # @api private
8
+ class TeamRepository
9
+ def initialize(view) = @view = view
10
+
11
+ def create(root)
12
+ record = Codec.encode_team_root(root)
13
+ @view.atomic do |bound|
14
+ entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).insert(key: root.team_id.to_s,
15
+ revision: Integer(root.team_revision), attributes: {}, record: record)
16
+ decode(entry, root.team_id, revision: root.team_revision)
17
+ end
18
+ end
19
+
20
+ def load(team_id)
21
+ value = @view.atomic do |bound|
22
+ entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).read(team_id.to_s)
23
+ entry && decode(entry, team_id)
24
+ end
25
+ value || raise(Phronomy::Storage::NotFoundError, "Team not found: #{team_id}")
26
+ end
27
+
28
+ def save(team_id, expected_revision:, root:)
29
+ expected = Integer(expected_revision)
30
+ revision = Integer(root.team_revision)
31
+ raise Phronomy::Storage::ConflictError, "root save must advance revision exactly once" unless revision == expected + 1
32
+ unless root.team_id.to_s == team_id.to_s
33
+ raise Phronomy::Storage::SerializationError, "Team root identity mismatch"
34
+ end
35
+ record = Codec.encode_team_root(root)
36
+ @view.atomic do |bound|
37
+ entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).replace(key: team_id.to_s,
38
+ expected_revision: expected, next_revision: revision, attributes: {}, record: record)
39
+ decode(entry, team_id, revision: revision)
40
+ end
41
+ end
42
+
43
+ def delete(team_id)
44
+ @view.records(Phronomy::TeamStorageSchema::ROOTS).delete(key: team_id.to_s)
45
+ end
46
+
47
+ private
48
+
49
+ def decode(entry, team_id, revision: nil)
50
+ root = Codec.decode_team_root(entry.record)
51
+ valid = entry.key == team_id.to_s && root.team_id == entry.key &&
52
+ root.team_revision == entry.revision && (!revision || revision == entry.revision)
53
+ unless valid
54
+ raise Phronomy::Storage::SerializationError, "backend returned another Team identity/revision"
55
+ end
56
+ root
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
5
- # Implements peer coordination through a shared KnowledgeStore.
4
+ module MultiAgent
5
+ # Implements peer coordination through an invocation-local KnowledgeStore.
6
+ # @api public
6
7
  class SharedState
7
8
  # Semantic revision of the framework-owned SharedState instrumentation
8
9
  # applied on top of one specific researcher definition revision.
@@ -13,6 +14,8 @@ module Phronomy
13
14
  INSTRUMENTATION_DEFINITION_VERSION = 1
14
15
  private_constant :INSTRUMENTATION_DEFINITION_VERSION
15
16
 
17
+ # Findings shared by the sequential members of one invocation.
18
+ # @api public
16
19
  class KnowledgeStore
17
20
  def initialize
18
21
  @findings = []
@@ -78,8 +81,15 @@ module Phronomy
78
81
  # @api public
79
82
  def invoke(input, config: {})
80
83
  validate_termination!
81
-
82
84
  store = KnowledgeStore.new
85
+ cycles, terminated_by = coordinate_members(input, store)
86
+
87
+ {output: aggregate_findings(store), cycles: cycles, terminated_by: terminated_by}
88
+ end
89
+
90
+ private
91
+
92
+ def coordinate_members(input, store)
83
93
  max_cycles = self.class._max_cycles
84
94
  deadline = self.class._timeout ? Time.now + self.class._timeout : nil
85
95
  terminated_by = :max_cycles
@@ -87,15 +97,7 @@ module Phronomy
87
97
  cycle_limit = max_cycles || Float::INFINITY
88
98
 
89
99
  (1..cycle_limit).each do |cycle|
90
- self.class._members.each do |member_config|
91
- invoke_researcher(
92
- member_config[:klass],
93
- store,
94
- cycle,
95
- input,
96
- member_config[:instruction]
97
- )
98
- end
100
+ invoke_members(input, store, cycle)
99
101
  completed_cycles = cycle
100
102
 
101
103
  if self.class._terminate_when&.call(store)
@@ -109,17 +111,25 @@ module Phronomy
109
111
  end
110
112
  end
111
113
 
112
- output = if self.class._aggregator
114
+ [completed_cycles, terminated_by]
115
+ end
116
+
117
+ def invoke_members(input, store, cycle)
118
+ self.class._members.each do |member_config|
119
+ invoke_researcher(
120
+ member_config[:klass], store, cycle, input, member_config[:instruction]
121
+ )
122
+ end
123
+ end
124
+
125
+ def aggregate_findings(store)
126
+ if self.class._aggregator
113
127
  self.class._aggregator.call(store)
114
128
  else
115
129
  store.read_all
116
130
  end
117
-
118
- {output: output, cycles: completed_cycles, terminated_by: terminated_by}
119
131
  end
120
132
 
121
- private
122
-
123
133
  def validate_termination!
124
134
  return if self.class._max_cycles || self.class._timeout
125
135
 
@@ -148,28 +158,8 @@ module Phronomy
148
158
 
149
159
  def build_instrumented_researcher(researcher_class, store, cycle)
150
160
  agent_key = researcher_class.name&.to_sym || researcher_class.object_id.to_s.to_sym
151
-
152
- read_tool = Class.new(Phronomy::Agent::Context::Capability::Base) do
153
- tool_name "read_store"
154
- description "Read all current findings from the shared knowledge store. " \
155
- "Call this to see what other researchers have discovered."
156
- execution_mode :cooperative
157
-
158
- define_method(:execute) { store.read_all.to_json }
159
- end
160
-
161
- write_tool = Class.new(Phronomy::Agent::Context::Capability::Base) do
162
- tool_name "write_finding"
163
- description "Record a new finding into the shared knowledge store so " \
164
- "that other researchers can build on your discovery."
165
- execution_mode :cooperative
166
- param :content, type: :string, desc: "The finding to record"
167
-
168
- define_method(:execute) do |content:|
169
- store.write(agent: agent_key, content: content, cycle: cycle)
170
- "Finding recorded."
171
- end
172
- end
161
+ read_tool = build_read_tool(store)
162
+ write_tool = build_write_tool(store, cycle, agent_key)
173
163
 
174
164
  definitions = researcher_class.tools.to_h do |tool_class|
175
165
  [tool_class, researcher_class.tool_aliases[tool_class]]
@@ -197,12 +187,39 @@ module Phronomy
197
187
  end
198
188
  end
199
189
 
190
+ def build_read_tool(store)
191
+ Class.new(Phronomy::Agent::Context::Capability::Base) do
192
+ tool_name "read_store"
193
+ description "Read all current findings from the shared knowledge store. " \
194
+ "Call this to see what other researchers have discovered."
195
+ execution_mode :cooperative
196
+
197
+ define_method(:execute) { store.read_all.to_json }
198
+ end
199
+ end
200
+
201
+ def build_write_tool(store, cycle, agent_key)
202
+ Class.new(Phronomy::Agent::Context::Capability::Base) do
203
+ tool_name "write_finding"
204
+ description "Record a new finding into the shared knowledge store so " \
205
+ "that other researchers can build on your discovery."
206
+ execution_mode :cooperative
207
+ param :content, type: :string, desc: "The finding to record"
208
+
209
+ define_method(:execute) do |content:|
210
+ store.write(agent: agent_key, content: content, cycle: cycle)
211
+ "Finding recorded."
212
+ end
213
+ end
214
+ end
215
+
200
216
  def instrumented_definition_for(researcher_class)
201
217
  parent_def = researcher_class.agent_definition
202
218
  parent_id = parent_def.fetch(:id)
203
219
  parent_version = parent_def.fetch(:version)
204
220
 
205
221
  {
222
+ # Keep the existing semantic ID across the Ruby namespace migration.
206
223
  id: "Phronomy::Agent::SharedState::Instrumented/#{parent_id}@#{parent_version}".freeze,
207
224
  version: INSTRUMENTATION_DEFINITION_VERSION
208
225
  }.freeze
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Domain-owned Team metadata, independent of coordination runtime loading.
5
+ # @api private
6
+ module TeamStorageSchema
7
+ ROOTS = Phronomy::Storage::Resource.new(id: "team.roots", kind: :records,
8
+ guard: {resource: "team.roots", via: :key})
9
+ EXECUTIONS = Phronomy::Storage::Resource.new(id: "team.executions", kind: :records,
10
+ attributes: {owner: :string, active: :boolean}, immutable_attributes: [:owner],
11
+ indexes: {owner: [:owner], owner_active: [:owner, :active]},
12
+ unique: [{name: :one_active_owner, fields: [:owner], where: {active: true}}],
13
+ guard: {resource: ROOTS.id, via: :owner})
14
+ end
15
+ end