phronomy 0.26.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 (304) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +4 -0
  4. data/VERIFY.sh +27 -27
  5. data/benchmark/bench_agent_invoke.rb +26 -22
  6. data/benchmark/bench_context_assembler.rb +4 -5
  7. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  8. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  9. data/docs/architecture/agent-context.md +4 -2
  10. data/docs/architecture/agent-transition-ownership.md +89 -0
  11. data/docs/architecture/before-llm-input.md +6 -0
  12. data/docs/architecture/context-management.md +30 -0
  13. data/docs/architecture/context-preparation-steps.md +90 -0
  14. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  15. data/docs/architecture/execution-metadata-and-values.md +80 -0
  16. data/docs/architecture/generator-verifier-ownership.md +111 -0
  17. data/docs/architecture/multi-agent-handoff.md +8 -2
  18. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  19. data/docs/architecture/persistence.md +27 -4
  20. data/docs/architecture/refactoring-closure.md +334 -0
  21. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  22. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  23. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  24. data/docs/architecture/tracing.md +1 -1
  25. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  26. data/docs/architecture.md +209 -0
  27. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  28. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  29. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  30. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  31. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  32. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  33. data/docs/decisions/032-storage-backend-composition.md +80 -0
  34. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  35. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  36. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  37. data/docs/decisions/036-context-contract-ownership.md +86 -0
  38. data/docs/decisions/037-common-definition-ownership.md +62 -0
  39. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  40. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  41. data/docs/decisions/040-configuration-default-composition.md +94 -0
  42. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  43. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  44. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  45. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  46. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  47. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  48. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  49. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  50. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  51. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  52. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  53. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  54. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  55. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  56. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  57. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  58. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  59. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  60. data/docs/decisions/README.md +35 -6
  61. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  62. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  63. data/docs/features.md +9 -3
  64. data/docs/getting-started.md +4 -4
  65. data/docs/migrations/0.15.md +5 -0
  66. data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
  67. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  68. data/docs/migrations/neutral-storage-spi.md +43 -0
  69. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  70. data/docs/migrations/shared-state-multi-agent.md +44 -0
  71. data/docs/migrations/storage-backend-composition.md +117 -0
  72. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  73. data/docs/persistence-backends.md +162 -204
  74. data/docs/runtime-and-concurrency.md +117 -32
  75. data/lib/phronomy/agent/api/agent.rb +17 -0
  76. data/lib/phronomy/agent/async_event_api.rb +2 -2
  77. data/lib/phronomy/agent/base.rb +51 -241
  78. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  79. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  80. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  81. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  82. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  83. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  84. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  85. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  86. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  87. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  88. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  89. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  90. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  91. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  92. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  93. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  94. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  95. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +19 -95
  96. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  97. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  98. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +9 -10
  99. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  100. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  101. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  102. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  103. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  104. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  105. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  106. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  107. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  108. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +20 -71
  109. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  110. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  111. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  112. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  113. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  114. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  115. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  116. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  117. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  118. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  119. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  120. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  121. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  122. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  123. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  124. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  125. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  126. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  127. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  128. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  129. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  130. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  131. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  132. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  133. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  134. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  135. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  136. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +32 -37
  137. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  138. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
  139. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  140. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  141. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  142. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  143. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  144. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  145. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  146. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  147. data/lib/phronomy/common/configuration_error.rb +7 -0
  148. data/lib/phronomy/common/error.rb +5 -0
  149. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  150. data/lib/phronomy/common/values/serializable.rb +32 -0
  151. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  152. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  153. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  154. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  155. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  156. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +1 -1
  157. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  158. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  159. data/lib/phronomy/engine/concurrency/offload_pool.rb +4 -0
  160. data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
  161. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  162. data/lib/phronomy/engine/event_loop.rb +158 -624
  163. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  164. data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
  165. data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
  166. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  167. data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
  168. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  169. data/lib/phronomy/engine/fsm_session.rb +35 -28
  170. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
  171. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
  172. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
  173. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  174. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  175. data/lib/phronomy/engine/runtime.rb +80 -84
  176. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  177. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  178. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  179. data/lib/phronomy/engine/task_result.rb +4 -0
  180. data/lib/phronomy/engine/timeout_error.rb +7 -0
  181. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  182. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  183. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  184. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  185. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  186. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  187. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  188. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  189. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  190. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  191. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  192. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  193. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  194. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  195. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +5 -5
  196. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  197. data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
  198. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  199. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  200. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  201. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  202. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  203. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  204. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  205. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  206. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  207. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  208. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  209. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  210. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  211. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  212. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  213. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  214. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  215. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  216. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  217. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  218. data/lib/phronomy/storage/backend.rb +101 -0
  219. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  220. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  221. data/lib/phronomy/storage/blobs.rb +31 -0
  222. data/lib/phronomy/storage/condition.rb +25 -0
  223. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  224. data/lib/phronomy/storage/conflict_error.rb +9 -0
  225. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  226. data/lib/phronomy/storage/entry.rb +33 -0
  227. data/lib/phronomy/storage/guard_ref.rb +13 -0
  228. data/lib/phronomy/storage/not_found_error.rb +9 -0
  229. data/lib/phronomy/storage/record_codec.rb +177 -0
  230. data/lib/phronomy/storage/records.rb +61 -0
  231. data/lib/phronomy/storage/resource.rb +126 -0
  232. data/lib/phronomy/storage/scope.rb +25 -0
  233. data/lib/phronomy/storage/serialization_error.rb +9 -0
  234. data/lib/phronomy/storage/streams.rb +44 -0
  235. data/lib/phronomy/storage/transaction_error.rb +10 -0
  236. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  237. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  238. data/lib/phronomy/storage/validation.rb +53 -0
  239. data/lib/phronomy/storage/view.rb +131 -0
  240. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  241. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  242. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  243. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  244. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  245. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  246. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  247. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  248. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  249. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  250. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  251. data/lib/phronomy/tools/agent.rb +1 -1
  252. data/lib/phronomy/version.rb +1 -1
  253. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
  254. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  255. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  256. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  257. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +122 -71
  258. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  259. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  260. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  261. data/lib/phronomy/workflow/phase_machine_builder.rb +2 -2
  262. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  263. data/lib/phronomy.rb +52 -94
  264. data/scripts/api_snapshot.rb +1 -1
  265. data/scripts/storage_spi_snapshot.rb +36 -0
  266. data/sig/phronomy/agent.rbs +0 -1
  267. data/sig/phronomy/execution_receiver.rbs +34 -0
  268. data/sig/phronomy/handoff.rbs +4 -2
  269. data/sig/phronomy/multi_agent.rbs +18 -0
  270. data/sig/phronomy/persistence.rbs +7 -91
  271. data/sig/phronomy/storage.rbs +174 -0
  272. data/sig/phronomy/tool.rbs +10 -1
  273. metadata +230 -99
  274. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  275. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  276. data/lib/phronomy/agent/recovery_support.rb +0 -504
  277. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  278. data/lib/phronomy/agent/tool_executor.rb +0 -55
  279. data/lib/phronomy/generator_verifier.rb +0 -369
  280. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  281. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  282. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  283. data/lib/phronomy/persistence/in_memory.rb +0 -690
  284. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  285. data/lib/phronomy/persistence.rb +0 -276
  286. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  287. data/lib/phronomy/workflow_recovery.rb +0 -123
  288. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  289. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  290. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  291. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  292. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  293. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  294. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  295. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  296. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  297. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  298. /data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +0 -0
  299. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  300. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  301. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  302. /data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +0 -0
  303. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  304. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
@@ -3,8 +3,8 @@
3
3
  require "time"
4
4
 
5
5
  module Phronomy
6
- module Agent
7
- # Durable Agent-domain responsibility routing within one Persistence instance.
6
+ module MultiAgent
7
+ # Coordinates durable handoffs across Agents using one Persistence instance.
8
8
  # @api public
9
9
  class HandoffRunner
10
10
  MAX_HANDOFFS = 20
@@ -16,7 +16,7 @@ module Phronomy
16
16
  def initialize(main_agent:, handoffs: [])
17
17
  @main_agent, @handoffs = main_agent, Array(handoffs).freeze
18
18
  @persistence = main_agent.persistence
19
- raise ArgumentError, "handoffs must contain Agent::Handoff" unless @handoffs.all? { |h| h.is_a?(Handoff) }
19
+ raise ArgumentError, "handoffs must contain Agent::Handoff" unless @handoffs.all? { |h| h.is_a?(Phronomy::Agent::Handoff) }
20
20
  @agents = ([main_agent] + @handoffs.flat_map { |edge| [edge.source_agent, edge.target_agent] }).uniq.to_h { |a| [a.agent_id, a] }.freeze
21
21
  unless @agents.values.all? { |a| a.persistence.equal?(@persistence) }
22
22
  raise Phronomy::ConfigurationError, "Durable Handoff graph requires one Persistence instance"
@@ -24,8 +24,9 @@ module Phronomy
24
24
  if @handoffs.group_by { |h| [h.source_agent.agent_id, h.target_agent.agent_id] }.any? { |_, edges| edges.size > 1 }
25
25
  raise ArgumentError, "Duplicate Source/Target Handoff edges"
26
26
  end
27
- @bindings = @handoffs.group_by { |h| h.source_agent.agent_id }.transform_values { |edges| edges.map { |h| HandoffCapabilityFactory.build(h) }.freeze }.freeze
27
+ @bindings = @handoffs.group_by { |h| h.source_agent.agent_id }.transform_values { |edges| edges.map { |h| Phronomy::Agent::HandoffCapabilityFactory.build(h) }.freeze }.freeze
28
28
  @runtime = Phronomy::Runtime.instance
29
+ @admissions = Phronomy::MultiAgent::AdmissionRegistry.for(@runtime)
29
30
  end
30
31
 
31
32
  # @api public
@@ -41,7 +42,7 @@ module Phronomy
41
42
  raise Phronomy::RuntimeShutdownError, "HandoffRunner belongs to a previous Runtime"
42
43
  end
43
44
  config = config.merge(cancellation_token: config[:cancellation_token] || Phronomy::Concurrency::CancellationToken.new)
44
- @runtime.__admit_multi_agent(main_agent)
45
+ @admissions.admit!(main_agent)
45
46
  admitted = true
46
47
  state = load_state
47
48
  count = 0
@@ -49,7 +50,7 @@ module Phronomy
49
50
  active = @agents.fetch(state.active_agent_id) do
50
51
  raise Phronomy::ExecutionRehydrationRequiredError, "Handoff graph lacks active Agent #{state.active_agent_id}"
51
52
  end
52
- context = state.active_handoff_context_ref && HandoffContext.from_h(@persistence.contents.fetch_json(state.active_handoff_context_ref))
53
+ context = state.active_handoff_context_ref && Phronomy::Agent::HandoffContext.from_h(@persistence.contents.fetch_json(state.active_handoff_context_ref))
53
54
  wiring = config.merge(phronomy_handoff_bindings: @bindings.fetch(active.agent_id, []),
54
55
  phronomy_handoff_context: context,
55
56
  phronomy_coordination: {"kind" => "handoff", "main_agent_id" => main_agent.agent_id, "handoff_revision" => state.handoff_revision}).freeze
@@ -61,11 +62,11 @@ module Phronomy
61
62
  else
62
63
  exact = unfinished.fetch(0)
63
64
  unless unfinished.size == 1 && exact.metadata.dig("coordination", "main_agent_id") == main_agent.agent_id
64
- raise Phronomy::Persistence::ConflictError, "Active Agent execution belongs to another coordination turn"
65
+ raise Phronomy::Storage::ConflictError, "Active Agent execution belongs to another coordination turn"
65
66
  end
66
67
  wiring[:cancellation_token].cancel! if Array(state.metadata["cancelled_execution_ids"]).include?(exact.execution_id)
67
68
  stored_input = @persistence.contents.fetch_text(exact.metadata.fetch("current_input_ref"))
68
- ExactExecution.start(agent: active, execution_id: exact.execution_id, input: stored_input, config: wiring).wait_result
69
+ Phronomy::Agent::ExactExecution.start(agent: active, execution_id: exact.execution_id, input: stored_input, config: wiring).wait_result
69
70
  end
70
71
  else
71
72
  source = @persistence.executions.load(state.pending_source_execution_id)
@@ -77,7 +78,7 @@ module Phronomy
77
78
  raise Phronomy::ConfigurationError, "Handoff Target definition mismatch"
78
79
  end
79
80
  wiring[:cancellation_token].cancel! if Array(state.metadata["cancelled_execution_ids"]).include?(state.pending_target_execution_id)
80
- ExactExecution.start(agent: active, execution_id: state.pending_target_execution_id,
81
+ Phronomy::Agent::ExactExecution.start(agent: active, execution_id: state.pending_target_execution_id,
81
82
  input: context.responsibility, config: wiring).wait_result
82
83
  end
83
84
  execution = @persistence.executions.load(result.fetch(:execution_id))
@@ -90,7 +91,7 @@ module Phronomy
90
91
  if execution.active?
91
92
  raise Phronomy::ExecutionRehydrationRequiredError, "Handoff execution requires approval or recovery"
92
93
  end
93
- raise RecoverySupport.error_from_failure(result[:error]) if result[:error]
94
+ raise Phronomy::Agent::RecoverySupport.error_from_failure(result[:error]) if result[:error]
94
95
  return result.reject { |key, _| key.to_s.start_with?("_phronomy_") || key == :handoff_request }.merge(agent: active)
95
96
  end
96
97
  rescue Phronomy::CancellationError => error
@@ -101,7 +102,7 @@ module Phronomy
101
102
  operation_error = error
102
103
  raise
103
104
  ensure
104
- @runtime.__release_multi_agent(main_agent) if admitted
105
+ @admissions.release!(main_agent) if admitted
105
106
  Phronomy::Tracing::Automatic.finish(trace_handle, output: result && result[:output], error: operation_error)
106
107
  end
107
108
 
@@ -117,25 +118,25 @@ module Phronomy
117
118
  @persistence.transaction do |tx|
118
119
  first = tx.executions.load(execution_id)
119
120
  unless first.metadata.dig("coordination", "main_agent_id") == main_agent.agent_id
120
- raise Phronomy::Persistence::ConflictError, "Execution does not belong to this Handoff anchor"
121
+ raise Phronomy::Storage::ConflictError, "Execution does not belong to this Handoff anchor"
121
122
  end
122
123
  leaf = first
123
124
  leaf_id = first.execution_id
124
125
  seen = {}
125
126
  while leaf&.status == :handed_off
126
- raise Phronomy::Persistence::SerializationError, "Cyclic Handoff chain" if seen[leaf_id]
127
+ raise Phronomy::Storage::SerializationError, "Cyclic Handoff chain" if seen[leaf_id]
127
128
  seen[leaf_id] = true
128
129
  leaf_id = leaf.metadata.fetch("handoff_target_execution_id")
129
130
  begin
130
131
  leaf = tx.executions.load(leaf_id)
131
- rescue Phronomy::Persistence::NotFoundError
132
+ rescue Phronomy::Storage::NotFoundError
132
133
  leaf = nil
133
134
  end
134
135
  end
135
136
  next if leaf&.terminal?
136
137
  routing = tx.handoff_states.load(main_agent.agent_id)
137
138
  unless routing && (leaf ? routing.active_agent_id == leaf.agent_id : routing.pending_target_execution_id == leaf_id)
138
- raise Phronomy::Persistence::ConflictError, "Handoff routing no longer owns the requested turn"
139
+ raise Phronomy::Storage::ConflictError, "Handoff routing no longer owns the requested turn"
139
140
  end
140
141
  ids = (Array(routing.metadata["cancelled_execution_ids"]) + [leaf_id]).uniq
141
142
  intended = routing.with(phase: leaf ? routing.phase : "stable",
@@ -147,7 +148,7 @@ module Phronomy
147
148
  raise error unless intended && confirmed && Array(confirmed.metadata["cancelled_execution_ids"]).include?(leaf_id)
148
149
  end
149
150
  return @persistence.execution_result(leaf_id) if leaf&.terminal?
150
- ExecutionCancellation.signal(leaf_id, leaf.agent_id) if leaf&.active?
151
+ Phronomy::Agent::ExecutionCancellation.signal(leaf_id, leaf.agent_id) if leaf&.active?
151
152
  {execution_id: leaf_id, cancellation_requested: !leaf&.terminal?}.freeze
152
153
  end
153
154
 
@@ -163,7 +164,7 @@ module Phronomy
163
164
  state = @persistence.handoff_states.load(main_agent.agent_id)
164
165
  return state if state
165
166
  now = Time.now.utc.iso8601(6)
166
- initial = HandoffState.new(main_agent_id: main_agent.agent_id, handoff_revision: 1,
167
+ initial = Phronomy::Agent::HandoffState.new(main_agent_id: main_agent.agent_id, handoff_revision: 1,
167
168
  active_agent_id: main_agent.agent_id, active_handoff_context_ref: nil,
168
169
  phase: "stable", pending_source_execution_id: nil, pending_target_execution_id: nil,
169
170
  created_at: now, updated_at: now, metadata: {})
@@ -23,7 +23,7 @@ module Phronomy
23
23
  raise Phronomy::EventLoopReentrancyError, "Orchestrator#resume cannot block EventLoop"
24
24
  end
25
25
  execution = persistence.executions.load(execution_id)
26
- 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
27
27
  input = persistence.contents.fetch_text(execution.metadata.fetch("current_input_ref"))
28
28
  result = Phronomy::Agent::ExactExecution.start(agent: self, execution_id: execution_id, input: input, config: config).wait_result
29
29
  raise Phronomy::Agent::RecoverySupport.error_from_failure(result[:error]) if result[:error]
@@ -174,7 +174,7 @@ module Phronomy
174
174
  invocation_context: invocation_context, concurrency_limit: max_concurrency) do |child, execution|
175
175
  # Preserve a child-specific context as well as this dispatch's controls.
176
176
  # Agent admission adds that context's constraints to the supplied token.
177
- binding = Phronomy::Execution.__operation_binding(
177
+ binding = Phronomy::Concurrency::OperationBinding.new(
178
178
  invocation_context: execution.invocation_context,
179
179
  cancellation_token: child.config[:cancellation_token]
180
180
  )
@@ -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