phronomy 0.26.0 → 0.28.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.
- checksums.yaml +4 -4
- data/.mutant.yml +9 -9
- data/CHANGELOG.md +92 -0
- data/CONTRIBUTING.md +8 -8
- data/README.md +13 -3
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -47
- data/benchmark/bench_context_assembler.rb +17 -15
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +1 -1
- data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
- data/docs/architecture/agent-context.md +4 -2
- data/docs/architecture/agent-transition-ownership.md +13 -0
- data/docs/architecture/backend-groups.md +152 -0
- data/docs/architecture/before-llm-input.md +9 -3
- data/docs/architecture/context-management.md +31 -1
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-abstraction-review.md +88 -0
- data/docs/architecture/execution-boundaries.md +28 -0
- data/docs/architecture/execution-metadata-and-values.md +86 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +11 -4
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +45 -4
- data/docs/architecture/r8-unit1.md +104 -0
- data/docs/architecture/r8-unit10.md +72 -0
- data/docs/architecture/r8-unit11.md +71 -0
- data/docs/architecture/r8-unit12.md +67 -0
- data/docs/architecture/r8-unit13.md +61 -0
- data/docs/architecture/r8-unit14.md +72 -0
- data/docs/architecture/r8-unit15.md +65 -0
- data/docs/architecture/r8-unit16.md +54 -0
- data/docs/architecture/r8-unit17.md +73 -0
- data/docs/architecture/r8-unit18.md +92 -0
- data/docs/architecture/r8-unit19.md +61 -0
- data/docs/architecture/r8-unit2.md +55 -0
- data/docs/architecture/r8-unit20.md +51 -0
- data/docs/architecture/r8-unit21.md +79 -0
- data/docs/architecture/r8-unit22.md +74 -0
- data/docs/architecture/r8-unit23.md +81 -0
- data/docs/architecture/r8-unit3.md +130 -0
- data/docs/architecture/r8-unit4.md +79 -0
- data/docs/architecture/r8-unit5.md +115 -0
- data/docs/architecture/r8-unit6.md +71 -0
- data/docs/architecture/r8-unit7.md +70 -0
- data/docs/architecture/r8-unit8.md +78 -0
- data/docs/architecture/r8-unit9.md +73 -0
- data/docs/architecture/refactoring-closure.md +343 -0
- data/docs/architecture/remaining-refactoring-plan.md +390 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/storage-execution-boundaries.md +54 -0
- data/docs/architecture/tool-schema-recording-gap.md +56 -0
- data/docs/architecture/tracing.md +2 -2
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +222 -0
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
- data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
- data/docs/decisions/032-storage-backend-composition.md +85 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
- data/docs/decisions/036-context-contract-ownership.md +86 -0
- data/docs/decisions/037-common-definition-ownership.md +62 -0
- data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
- data/docs/decisions/040-configuration-default-composition.md +105 -0
- data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
- data/docs/decisions/042-feature-owned-execution-state.md +106 -0
- data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
- data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
- data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
- data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
- data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
- data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
- data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
- data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
- data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
- data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
- data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
- data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
- data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
- data/docs/decisions/058-neutral-storage-primitives.md +83 -0
- data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
- data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
- data/docs/decisions/061-persistence-failure-contracts.md +89 -0
- data/docs/decisions/062-content-store-service.md +52 -0
- data/docs/decisions/063-execution-contracts-and-services.md +88 -0
- data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
- data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
- data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
- data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
- data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
- data/docs/decisions/README.md +52 -6
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
- data/docs/execution-boundaries-preview.md +45 -0
- data/docs/features.md +16 -5
- data/docs/getting-started.md +11 -8
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/0.22.md +2 -2
- data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
- data/docs/migrations/handoff-runner-multi-agent.md +37 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/persistence-failure-contracts.md +67 -0
- data/docs/migrations/r8-unit10.md +36 -0
- data/docs/migrations/r8-unit11.md +33 -0
- data/docs/migrations/r8-unit12.md +31 -0
- data/docs/migrations/r8-unit13.md +36 -0
- data/docs/migrations/r8-unit14.md +31 -0
- data/docs/migrations/r8-unit15.md +31 -0
- data/docs/migrations/r8-unit16.md +26 -0
- data/docs/migrations/r8-unit17.md +28 -0
- data/docs/migrations/r8-unit18.md +29 -0
- data/docs/migrations/r8-unit19.md +28 -0
- data/docs/migrations/r8-unit20.md +24 -0
- data/docs/migrations/r8-unit21.md +28 -0
- data/docs/migrations/r8-unit22.md +29 -0
- data/docs/migrations/r8-unit23.md +20 -0
- data/docs/migrations/r8-unit4.md +67 -0
- data/docs/migrations/r8-unit5.md +75 -0
- data/docs/migrations/r8-unit6.md +109 -0
- data/docs/migrations/r8-unit7.md +31 -0
- data/docs/migrations/r8-unit8.md +33 -0
- data/docs/migrations/r8-unit9.md +28 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-async-client.md +103 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/migrations/vector-async-clients.md +110 -0
- data/docs/persistence-backends.md +190 -215
- data/docs/runtime-and-concurrency.md +142 -32
- data/lib/phronomy/agent/admission.rb +78 -0
- data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/agent/async_event_api.rb +38 -33
- data/lib/phronomy/agent/base.rb +106 -294
- data/lib/phronomy/agent/before_llm_input.rb +64 -0
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
- data/lib/phronomy/agent/context_importer.rb +2 -2
- data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
- data/lib/phronomy/agent/control_request.rb +21 -0
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
- data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/execution_change.rb +161 -0
- data/lib/phronomy/agent/execution_environment.rb +36 -0
- data/lib/phronomy/agent/execution_extension_state.rb +21 -0
- data/lib/phronomy/agent/execution_observation.rb +35 -0
- data/lib/phronomy/agent/filterable.rb +182 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
- data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
- data/lib/phronomy/agent/handoff_error.rb +7 -0
- data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
- data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
- data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
- data/lib/phronomy/agent/knowledge_item.rb +13 -0
- data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
- data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
- data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
- data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
- data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
- data/lib/phronomy/agent/persistence/codec.rb +338 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
- data/lib/phronomy/agent/persistence/records.rb +33 -0
- data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
- data/lib/phronomy/agent/persistence/watermark.rb +29 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
- data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
- data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
- data/lib/phronomy/agent/reserved_execution.rb +16 -0
- data/lib/phronomy/agent/retention.rb +17 -0
- data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
- data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
- data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
- data/lib/phronomy/agent/saved_context_reader.rb +55 -0
- data/lib/phronomy/agent/settings.rb +47 -0
- data/lib/phronomy/agent/state_writer.rb +166 -0
- data/lib/phronomy/agent/store.rb +253 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
- data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
- data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
- data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
- data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
- data/lib/phronomy/common/configuration_error.rb +7 -0
- data/lib/phronomy/common/error.rb +5 -0
- data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
- data/lib/phronomy/common/values/serializable.rb +32 -0
- data/lib/phronomy/configuration/runtime_settings.rb +49 -0
- data/lib/phronomy/content_store/storage_schema.rb +12 -0
- data/lib/phronomy/content_store/stored_contents.rb +44 -0
- data/lib/phronomy/context/assembly.rb +202 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
- data/lib/phronomy/context/candidate.rb +51 -0
- data/lib/phronomy/context/constraint.rb +47 -0
- data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
- data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
- data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
- data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
- data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
- data/lib/phronomy/context/default_policy.rb +140 -0
- data/lib/phronomy/context/final_budget_validator.rb +33 -0
- data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
- data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
- data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
- data/lib/phronomy/context/manifest_error.rb +10 -0
- data/lib/phronomy/context/prompt_template.rb +98 -0
- data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
- data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
- data/lib/phronomy/embeddings/async/async_client.rb +44 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
- data/lib/phronomy/embeddings/base.rb +43 -0
- data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
- data/lib/phronomy/embeddings/transport_error.rb +11 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
- data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
- data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
- data/lib/phronomy/engine/event_loop.rb +164 -630
- data/lib/phronomy/engine/execution_binding.rb +23 -0
- data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
- data/lib/phronomy/engine/fsm_protocol.rb +9 -0
- data/lib/phronomy/engine/fsm_session.rb +37 -30
- data/lib/phronomy/engine/metrics.rb +28 -0
- data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
- data/lib/phronomy/engine/runtime.rb +104 -95
- data/lib/phronomy/engine/terminal_decision.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +41 -0
- data/lib/phronomy/execution/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
- data/lib/phronomy/execution/cancellation_error.rb +7 -0
- data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
- data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
- data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
- data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
- data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
- data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
- data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
- data/lib/phronomy/execution/execution_receiver.rb +78 -0
- data/lib/phronomy/execution/execution_scope_state.rb +35 -0
- data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
- data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
- data/lib/phronomy/execution/invocation_controls.rb +73 -0
- data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
- data/lib/phronomy/execution/runnable.rb +24 -0
- data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
- data/lib/phronomy/execution/timeout_error.rb +7 -0
- data/lib/phronomy/execution/wait_policy.rb +24 -0
- data/lib/phronomy/filter/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
- data/lib/phronomy/generation/generator_verifier.rb +118 -0
- data/lib/phronomy/generation/low_confidence_error.rb +14 -0
- data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
- data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
- data/lib/phronomy/llm_adapter/base.rb +49 -68
- data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
- data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
- data/lib/phronomy/llm_adapter/message.rb +62 -0
- data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
- data/lib/phronomy/llm_adapter/request.rb +79 -0
- data/lib/phronomy/llm_adapter/response.rb +36 -0
- data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
- data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
- data/lib/phronomy/llm_adapter.rb +3 -16
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
- data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
- data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
- data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
- data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
- data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
- data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
- data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
- data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
- data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
- data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
- data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
- data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
- data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/store.rb +156 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/parse_error.rb +7 -0
- data/lib/phronomy/persistence/content_repository.rb +26 -0
- data/lib/phronomy/persistence/errors.rb +16 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
- data/lib/phronomy/persistence/persistence.rb +23 -0
- data/lib/phronomy/persistence/save_outcome.rb +24 -0
- data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
- data/lib/phronomy/persistence/storage_boundary.rb +32 -0
- data/lib/phronomy/persistence/transaction.rb +86 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
- data/lib/phronomy/persistence_composition/stores.rb +34 -0
- data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
- data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
- data/lib/phronomy/runtime_composition/configuration.rb +146 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
- data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
- data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
- data/lib/phronomy/storage/async/async_client.rb +60 -0
- data/lib/phronomy/storage/backend.rb +107 -0
- data/lib/phronomy/storage/backends/in_memory.rb +157 -0
- data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
- data/lib/phronomy/storage/blobs.rb +31 -0
- data/lib/phronomy/storage/condition.rb +25 -0
- data/lib/phronomy/storage/condition_failed_error.rb +16 -0
- data/lib/phronomy/storage/conflict_error.rb +9 -0
- data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
- data/lib/phronomy/storage/entry.rb +33 -0
- data/lib/phronomy/storage/guard_ref.rb +13 -0
- data/lib/phronomy/storage/not_found_error.rb +9 -0
- data/lib/phronomy/storage/record_codec.rb +177 -0
- data/lib/phronomy/storage/records.rb +61 -0
- data/lib/phronomy/storage/resource.rb +126 -0
- data/lib/phronomy/storage/scope.rb +25 -0
- data/lib/phronomy/storage/serialization_error.rb +9 -0
- data/lib/phronomy/storage/streams.rb +44 -0
- data/lib/phronomy/storage/transaction_error.rb +10 -0
- data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
- data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
- data/lib/phronomy/storage/validation.rb +53 -0
- data/lib/phronomy/storage/view.rb +131 -0
- data/lib/phronomy/testing/eval/metrics.rb +1 -1
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/authorization.rb +77 -0
- data/lib/phronomy/tool/base.rb +549 -9
- data/lib/phronomy/tool/call_request.rb +40 -0
- data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
- data/lib/phronomy/tool/operation.rb +88 -0
- data/lib/phronomy/tool/schema.rb +214 -0
- data/lib/phronomy/tool/settings.rb +31 -0
- data/lib/phronomy/tool/tool_error.rb +7 -0
- data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
- data/lib/phronomy/tools/agent.rb +22 -90
- data/lib/phronomy/tools/mcp.rb +21 -14
- data/lib/phronomy/tools/vector_search.rb +4 -4
- data/lib/phronomy/tracing/automatic.rb +11 -6
- data/lib/phronomy/tracing/base.rb +1 -1
- data/lib/phronomy/tracing/observation.rb +37 -0
- data/lib/phronomy/tracing/settings.rb +36 -0
- data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
- data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
- data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
- data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
- data/lib/phronomy/vector_store/base.rb +118 -49
- data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
- data/lib/phronomy/vector_store.rb +2 -17
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/completion.rb +11 -0
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
- data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
- data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
- data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy.rb +97 -98
- data/scripts/api_snapshot.rb +36 -16
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/_private/phronomy/agent/settings.rbs +19 -0
- data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
- data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
- data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
- data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
- data/sig/_private/phronomy/tool/settings.rbs +13 -0
- data/sig/_private/phronomy/tracing/settings.rbs +15 -0
- data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
- data/sig/phronomy/agent/admission.rbs +19 -0
- data/sig/phronomy/agent/execution_contract.rbs +132 -0
- data/sig/phronomy/agent/execution_environment.rbs +20 -0
- data/sig/phronomy/agent/store.rbs +73 -0
- data/sig/phronomy/agent.rbs +14 -139
- data/sig/phronomy/common.rbs +7 -0
- data/sig/phronomy/content_store/contracts.rbs +13 -0
- data/sig/phronomy/context.rbs +152 -0
- data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
- data/sig/phronomy/embeddings/base.rbs +17 -0
- data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
- data/sig/phronomy/engine/control_bindings.rbs +11 -0
- data/sig/phronomy/engine/control_protocols.rbs +23 -0
- data/sig/phronomy/engine/execution_binding.rbs +6 -0
- data/sig/phronomy/engine/execution_receiver.rbs +15 -0
- data/sig/phronomy/engine/observability.rbs +13 -0
- data/sig/phronomy/execution/async_operation.rbs +7 -0
- data/sig/phronomy/execution/blocking.rbs +5 -0
- data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
- data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
- data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
- data/sig/phronomy/execution/control_protocols.rbs +18 -0
- data/sig/phronomy/execution/errors.rbs +7 -0
- data/sig/phronomy/execution/execution.rbs +41 -0
- data/sig/phronomy/execution/execution_receiver.rbs +40 -0
- data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
- data/sig/phronomy/execution/invocation_context.rbs +18 -0
- data/sig/phronomy/execution/invocation_controls.rbs +13 -0
- data/sig/phronomy/execution/task_result.rbs +29 -0
- data/sig/phronomy/extensions.rbs +0 -21
- data/sig/phronomy/handoff.rbs +12 -4
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
- data/sig/phronomy/llm_adapter/base.rbs +14 -0
- data/sig/phronomy/llm_adapter/usage.rbs +14 -0
- data/sig/phronomy/llm_adapter/values.rbs +43 -0
- data/sig/phronomy/llm_context_window.rbs +10 -0
- data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
- data/sig/phronomy/multi_agent/store.rbs +54 -0
- data/sig/phronomy/multi_agent.rbs +24 -3
- data/sig/phronomy/persistence/errors.rbs +18 -0
- data/sig/phronomy/persistence/migration.rbs +13 -0
- data/sig/phronomy/persistence/transaction.rbs +19 -0
- data/sig/phronomy/persistence.rbs +3 -185
- data/sig/phronomy/persistence_composition.rbs +19 -0
- data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
- data/sig/phronomy/storage/async/async_client.rbs +8 -0
- data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
- data/sig/phronomy/storage/contracts.rbs +160 -0
- data/sig/phronomy/tool.rbs +58 -5
- data/sig/phronomy/tracing/observation.rbs +7 -0
- data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
- data/sig/phronomy/vector_store/base.rbs +23 -0
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
- data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
- data/sig/phronomy/workflow/store.rbs +8 -0
- data/sig/phronomy/workflow.rbs +1 -1
- data/sig/phronomy.rbs +0 -49
- metadata +462 -130
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
- data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
- data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
- data/lib/phronomy/agent/concerns/filterable.rb +0 -184
- data/lib/phronomy/agent/context/capability/base.rb +0 -507
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
- data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
- data/lib/phronomy/agent/context_policies/default.rb +0 -142
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/handoff_request.rb +0 -45
- data/lib/phronomy/agent/handoff_runner.rb +0 -178
- data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
- data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/selection/candidate.rb +0 -53
- data/lib/phronomy/agent/selection/constraint.rb +0 -49
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
- data/lib/phronomy/configuration.rb +0 -59
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
- data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
- data/lib/phronomy/metrics.rb +0 -26
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
- data/lib/phronomy/persistence/durable_codec.rb +0 -706
- data/lib/phronomy/persistence/in_memory.rb +0 -690
- data/lib/phronomy/persistence/repository_facades.rb +0 -535
- data/lib/phronomy/persistence.rb +0 -276
- data/lib/phronomy/recovery.rb +0 -186
- data/lib/phronomy/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/runnable.rb +0 -54
- data/lib/phronomy/token_usage.rb +0 -85
- data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
- data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
- data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
- data/lib/phronomy/workflow_recovery.rb +0 -123
- data/sig/phronomy/llm_adapter.rbs +0 -11
- data/sig/phronomy/runtime.rbs +0 -76
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Domain-owned Team metadata, independent of coordination runtime loading.
|
|
5
|
+
# @api private
|
|
6
|
+
module MultiAgent
|
|
7
|
+
module Persistence
|
|
8
|
+
module StorageSchema
|
|
9
|
+
HANDOFF_STATES = Phronomy::Storage::Resource.new(id: "handoff.states", kind: :records,
|
|
10
|
+
attributes: {active_agent_id: :string})
|
|
11
|
+
ROOTS = Phronomy::Storage::Resource.new(id: "team.roots", kind: :records,
|
|
12
|
+
guard: {resource: "team.roots", via: :key})
|
|
13
|
+
EXECUTIONS = Phronomy::Storage::Resource.new(id: "team.executions", kind: :records,
|
|
14
|
+
attributes: {owner: :string, active: :boolean}, immutable_attributes: [:owner],
|
|
15
|
+
indexes: {owner: [:owner], owner_active: [:owner, :active]},
|
|
16
|
+
unique: [{name: :one_active_owner, fields: [:owner], where: {active: true}}],
|
|
17
|
+
guard: {resource: ROOTS.id, via: :owner})
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
13
|
+
raise Phronomy::Storage::SerializationError, "create_active requires an active TeamExecution" unless execution.active?
|
|
14
|
+
record = Codec.encode_team_execution(execution)
|
|
15
|
+
admission do
|
|
16
|
+
@view.atomic do |bound|
|
|
17
|
+
entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).insert(key: execution.team_execution_id.to_s,
|
|
18
|
+
revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
|
|
19
|
+
decode(entry, execution.team_execution_id, owner: execution.team_id,
|
|
20
|
+
revision: execution.execution_revision, active: true)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load(team_execution_id)
|
|
27
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
28
|
+
value = @view.atomic do |bound|
|
|
29
|
+
entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).read(team_execution_id.to_s)
|
|
30
|
+
entry && decode(entry, team_execution_id)
|
|
31
|
+
end
|
|
32
|
+
value || raise(Phronomy::Storage::NotFoundError, "TeamExecution not found: #{team_execution_id}")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def save(team_execution_id, expected_revision:, execution:)
|
|
37
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
38
|
+
expected = Integer(expected_revision)
|
|
39
|
+
revision = Integer(execution.execution_revision)
|
|
40
|
+
raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
|
|
41
|
+
unless execution.team_execution_id.to_s == team_execution_id.to_s
|
|
42
|
+
raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
|
|
43
|
+
end
|
|
44
|
+
record = Codec.encode_team_execution(execution)
|
|
45
|
+
admission do
|
|
46
|
+
@view.atomic do |bound|
|
|
47
|
+
entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).replace(key: team_execution_id.to_s,
|
|
48
|
+
expected_revision: expected, next_revision: revision, attributes: attributes(execution),
|
|
49
|
+
expected_attributes: execution.active? ? {active: true} : {}, record: record)
|
|
50
|
+
decode(entry, team_execution_id, owner: execution.team_id, revision: revision, active: execution.active?)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def list_active(team_id)
|
|
57
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
58
|
+
@view.atomic do |bound|
|
|
59
|
+
bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).scan(index: :owner_active,
|
|
60
|
+
equals: {owner: team_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: team_id, active: true) }.freeze
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def list(team_id, after: nil, limit: 100)
|
|
66
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
67
|
+
raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
|
|
68
|
+
@view.atomic do |bound|
|
|
69
|
+
bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: team_id.to_s},
|
|
70
|
+
after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: team_id) }.freeze
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def delete(team_execution_id)
|
|
76
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
77
|
+
@view.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).delete(key: team_execution_id.to_s)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def delete_for_team(team_id)
|
|
82
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
83
|
+
@view.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: team_id.to_s})
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def assert_idle!(team_id)
|
|
88
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
89
|
+
condition = Phronomy::Storage::Condition::NoRows.new(resource: Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS,
|
|
90
|
+
index: :owner_active, equals: {owner: team_id.to_s, active: true})
|
|
91
|
+
@view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS, key: team_id.to_s)],
|
|
92
|
+
conditions: [condition])
|
|
93
|
+
rescue Phronomy::Storage::ConditionFailedError => error
|
|
94
|
+
raise unless error.condition.equal?(condition)
|
|
95
|
+
raise Phronomy::AgentBusyError, error.message
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def attributes(execution) = {owner: execution.team_id.to_s, active: execution.active?}
|
|
102
|
+
|
|
103
|
+
def admission
|
|
104
|
+
yield
|
|
105
|
+
rescue Phronomy::Storage::UniqueConstraintError => error
|
|
106
|
+
raise unless error.resource == Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS.id && error.constraint == :one_active_owner
|
|
107
|
+
raise Phronomy::AgentBusyError, error.message
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def decode(entry, team_execution_id, owner: nil, revision: nil, active: nil)
|
|
111
|
+
execution = Codec.decode_team_execution(entry.record)
|
|
112
|
+
valid = entry.key == team_execution_id.to_s && execution.team_execution_id == entry.key &&
|
|
113
|
+
execution.execution_revision == entry.revision && attributes(execution) == entry.attributes &&
|
|
114
|
+
(!owner || execution.team_id == owner.to_s) && (!revision || execution.execution_revision == revision) &&
|
|
115
|
+
(active.nil? || execution.active? == active)
|
|
116
|
+
unless valid
|
|
117
|
+
raise Phronomy::Storage::SerializationError, "backend returned another Team Execution identity/metadata"
|
|
118
|
+
end
|
|
119
|
+
execution
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
13
|
+
record = Codec.encode_team_root(root)
|
|
14
|
+
@view.atomic do |bound|
|
|
15
|
+
entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).insert(key: root.team_id.to_s,
|
|
16
|
+
revision: Integer(root.team_revision), attributes: {}, record: record)
|
|
17
|
+
decode(entry, root.team_id, revision: root.team_revision)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load(team_id)
|
|
23
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
24
|
+
value = @view.atomic do |bound|
|
|
25
|
+
entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).read(team_id.to_s)
|
|
26
|
+
entry && decode(entry, team_id)
|
|
27
|
+
end
|
|
28
|
+
value || raise(Phronomy::Storage::NotFoundError, "Team not found: #{team_id}")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def save(team_id, expected_revision:, root:)
|
|
33
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
34
|
+
expected = Integer(expected_revision)
|
|
35
|
+
revision = Integer(root.team_revision)
|
|
36
|
+
raise Phronomy::Storage::ConflictError, "root save must advance revision exactly once" unless revision == expected + 1
|
|
37
|
+
unless root.team_id.to_s == team_id.to_s
|
|
38
|
+
raise Phronomy::Storage::SerializationError, "Team root identity mismatch"
|
|
39
|
+
end
|
|
40
|
+
record = Codec.encode_team_root(root)
|
|
41
|
+
@view.atomic do |bound|
|
|
42
|
+
entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).replace(key: team_id.to_s,
|
|
43
|
+
expected_revision: expected, next_revision: revision, attributes: {}, record: record)
|
|
44
|
+
decode(entry, team_id, revision: revision)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def delete(team_id)
|
|
50
|
+
Phronomy::Persistence::StorageBoundary.call do
|
|
51
|
+
@view.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).delete(key: team_id.to_s)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def decode(entry, team_id, revision: nil)
|
|
58
|
+
root = Codec.decode_team_root(entry.record)
|
|
59
|
+
valid = entry.key == team_id.to_s && root.team_id == entry.key &&
|
|
60
|
+
root.team_revision == entry.revision && (!revision || revision == entry.revision)
|
|
61
|
+
unless valid
|
|
62
|
+
raise Phronomy::Storage::SerializationError, "backend returned another Team identity/revision"
|
|
63
|
+
end
|
|
64
|
+
root
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
# Owns parent reservation checks and joins the Agent's durable admission.
|
|
6
|
+
# Reconstructed from current coordination wiring, never serialized as a Proc.
|
|
7
|
+
# @api public
|
|
8
|
+
class ReservedChildAdmission
|
|
9
|
+
def initialize(persistence:, owner:)
|
|
10
|
+
@persistence = persistence
|
|
11
|
+
@owner = Phronomy::Values::Immutable.copy(owner)
|
|
12
|
+
raise ArgumentError, "unknown reservation kind" unless %w[team subagent handoff].include?(@owner.fetch("kind"))
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Parent lock, current reservation check and child admission share one scope.
|
|
17
|
+
# No task, runtime owner or live Agent state is published before its commit.
|
|
18
|
+
# @api public
|
|
19
|
+
def admit(admission)
|
|
20
|
+
unless admission.correlation == @owner
|
|
21
|
+
raise Phronomy::Persistence::StateConflictError, "Child admission correlation mismatch"
|
|
22
|
+
end
|
|
23
|
+
@persistence.coordinator.atomic do |scope|
|
|
24
|
+
@persistence.participate(scope) do |records|
|
|
25
|
+
case @owner.fetch("kind")
|
|
26
|
+
when "team" then validate_team!(records, admission)
|
|
27
|
+
when "subagent" then validate_subagent!(records, admission, scope)
|
|
28
|
+
when "handoff" then validate_handoff!(records, admission, scope)
|
|
29
|
+
end
|
|
30
|
+
admission.accept_in(scope)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def validate_team!(records, admission)
|
|
39
|
+
records.guard_team!(@owner.fetch("team_id"))
|
|
40
|
+
team = records.team_executions.load(@owner.fetch("team_execution_id"))
|
|
41
|
+
unless team.team_id == @owner.fetch("team_id") && team.active? && !team.metadata["cancel_requested"]
|
|
42
|
+
raise Phronomy::CancellationError, "Team run is no longer dispatchable"
|
|
43
|
+
end
|
|
44
|
+
slot = if @owner.fetch("slot") == "coordinator"
|
|
45
|
+
team.coordinator
|
|
46
|
+
else
|
|
47
|
+
assignment = team.assignments.find { |entry| entry.fetch("task_id") == @owner.fetch("slot") }
|
|
48
|
+
worker = assignment && team.workers.fetch(assignment.fetch("worker"))
|
|
49
|
+
worker&.merge("execution_id" => assignment.fetch("execution_id"))
|
|
50
|
+
end
|
|
51
|
+
unless slot && slot.fetch("execution_id") == admission.execution_id && slot.fetch("agent_id") == admission.agent_id
|
|
52
|
+
raise Phronomy::Persistence::StateConflictError, "Team reserved child identity mismatch"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_subagent!(records, admission, scope)
|
|
57
|
+
@persistence.agent_store.guard_agents(scope, agent_ids: [@owner.fetch("parent_agent_id"), admission.agent_id])
|
|
58
|
+
parent = @persistence.agent_store.observe_execution(agent_id: @owner.fetch("parent_agent_id"),
|
|
59
|
+
execution_id: @owner.fetch("parent_execution_id"), scope: scope)
|
|
60
|
+
unless parent.continuable?
|
|
61
|
+
raise Phronomy::CancellationError, "Parent run is no longer dispatchable"
|
|
62
|
+
end
|
|
63
|
+
extension = @persistence.agent_store.execution_extension(agent_id: parent.agent_id, execution_id: parent.execution_id,
|
|
64
|
+
binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
|
|
65
|
+
snapshot = records.contents.fetch_json(extension.state_ref)
|
|
66
|
+
raise Phronomy::CancellationError, "Parent reservation was cancelled" if snapshot["cancel_requested"]
|
|
67
|
+
slot = snapshot.fetch("children").find { |entry| entry.fetch("slot") == @owner.fetch("slot") }
|
|
68
|
+
unless slot && slot.fetch("agent_id") == admission.agent_id && slot.fetch("execution_id") == admission.execution_id
|
|
69
|
+
raise Phronomy::Persistence::StateConflictError, "Parent reserved child identity mismatch"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_handoff!(records, admission, scope)
|
|
74
|
+
routing = records.handoff_states.load_locked(@owner.fetch("main_agent_id"))
|
|
75
|
+
unless routing && routing.active_agent_id == admission.agent_id
|
|
76
|
+
raise Phronomy::Persistence::StateConflictError, "Handoff responsibility changed before admission"
|
|
77
|
+
end
|
|
78
|
+
if Array(routing.metadata["cancelled_execution_ids"]).include?(admission.execution_id)
|
|
79
|
+
raise Phronomy::CancellationError, "Handoff reservation was cancelled"
|
|
80
|
+
end
|
|
81
|
+
@persistence.agent_store.guard_agents(scope, agent_ids: [@owner.fetch("main_agent_id"), admission.agent_id])
|
|
82
|
+
if routing.phase != "stable" && routing.pending_target_execution_id != admission.execution_id
|
|
83
|
+
raise Phronomy::Persistence::StateConflictError, "Handoff reserved Target identity mismatch"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
# Connects domain registries to the generic Runtime shutdown protocol.
|
|
6
|
+
# Construction does not register participants or start worker resources.
|
|
7
|
+
# @api private
|
|
8
|
+
class EngineEnvironment < ExecutionEnvironment
|
|
9
|
+
def initialize(runtime: Phronomy::Runtime.instance)
|
|
10
|
+
@runtime = runtime
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def admissions
|
|
14
|
+
@runtime.__register_shutdown_participant(
|
|
15
|
+
key: AdmissionRegistry, participant: AdmissionRegistry.new
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ownership
|
|
20
|
+
existing_ownership || @runtime.__register_shutdown_participant(
|
|
21
|
+
key: TeamOwnershipRegistry, participant: TeamOwnershipRegistry.new
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Lookup must not register a participant, including during shutdown.
|
|
26
|
+
def existing_ownership = @runtime.__shutdown_participant(key: TeamOwnershipRegistry)
|
|
27
|
+
|
|
28
|
+
def current? = @runtime.equal?(Phronomy::Runtime.instance)
|
|
29
|
+
|
|
30
|
+
def submit(**options, &operation)
|
|
31
|
+
Phronomy::Execution.submit(runtime: @runtime, **options, &operation)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
5
|
-
# Implements peer coordination through
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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::Tool::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::Tool::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,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
# MultiAgent-owned durable operations and observations. An injected record
|
|
6
|
+
# adapter implements storage; this framework never selects a DB or codec.
|
|
7
|
+
# @api public
|
|
8
|
+
class Store
|
|
9
|
+
# Composition supplies the optional configured store without constructing
|
|
10
|
+
# a fallback. Team and Orchestrator retain their different fallback rules.
|
|
11
|
+
# @api private
|
|
12
|
+
def self.install_configured_provider(&provider)
|
|
13
|
+
raise ArgumentError, "store provider requires a block" unless provider
|
|
14
|
+
@configured_provider = provider
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @api private
|
|
19
|
+
def self.configured = @configured_provider.call
|
|
20
|
+
|
|
21
|
+
attr_reader :coordinator, :agent_store
|
|
22
|
+
|
|
23
|
+
# Record protocol for MultiAgent-owned framework operations only.
|
|
24
|
+
# @api private
|
|
25
|
+
attr_reader :contents, :teams, :team_executions, :handoff_states
|
|
26
|
+
|
|
27
|
+
def initialize(coordinator:, records:, agent_store:)
|
|
28
|
+
@coordinator, @record_adapter = coordinator, records
|
|
29
|
+
|
|
30
|
+
unless agent_store.coordinator.equal?(coordinator)
|
|
31
|
+
raise Phronomy::Persistence::TransactionError, "Team and Agent stores must share one Persistence coordinator"
|
|
32
|
+
end
|
|
33
|
+
@agent_store = agent_store
|
|
34
|
+
@records = coordinator.bind(records)
|
|
35
|
+
@contents = @records.contents
|
|
36
|
+
@teams = @records.teams
|
|
37
|
+
@team_executions = @records.team_executions
|
|
38
|
+
@handoff_states = @records.handoff_states
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# A domain operation owns this short transaction. A coordinating parent
|
|
42
|
+
# can instead call participate with its existing shared scope.
|
|
43
|
+
def transaction
|
|
44
|
+
coordinator.atomic do |scope|
|
|
45
|
+
participate(scope) { |records| yield records, scope }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def participate(scope, &operation)
|
|
50
|
+
scope.participate(persistence: coordinator, adapter: @record_adapter, &operation)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def handoff_leaf(execution_id, main_agent_id:, scope: nil)
|
|
54
|
+
return coordinator.atomic { |current| handoff_leaf(execution_id, main_agent_id: main_agent_id, scope: current) } unless scope
|
|
55
|
+
routing = participate(scope) { |records| records.handoff_states.load(main_agent_id) }
|
|
56
|
+
raise Phronomy::Persistence::NotFoundError, "Handoff history is absent" unless routing
|
|
57
|
+
identity = agent_store.execution_identity(execution_id, scope: scope)
|
|
58
|
+
unless identity.correlation == {"kind" => "handoff", "main_agent_id" => main_agent_id}
|
|
59
|
+
raise Phronomy::Persistence::StateConflictError, "Execution does not belong to this Handoff anchor"
|
|
60
|
+
end
|
|
61
|
+
seen = {}
|
|
62
|
+
loop do
|
|
63
|
+
raise Phronomy::Persistence::SerializationError, "Cyclic Handoff chain" if seen[identity.execution_id]
|
|
64
|
+
seen[identity.execution_id] = true
|
|
65
|
+
observed = agent_store.observe_execution(agent_id: identity.agent_id, execution_id: identity.execution_id, scope: scope)
|
|
66
|
+
return observed unless observed.status == :handed_off
|
|
67
|
+
receipt = observed.transfer_receipt
|
|
68
|
+
unless receipt && receipt.fetch("owner_key") == "handoff:#{main_agent_id}"
|
|
69
|
+
raise Phronomy::Persistence::SerializationError, "Invalid transfer receipt"
|
|
70
|
+
end
|
|
71
|
+
identity = Phronomy::Agent::ReservedExecution.new(agent_id: receipt.fetch("target_agent_id"), execution_id: receipt.fetch("target_execution_id"))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def handoff_result(execution_id, main_agent_id:)
|
|
76
|
+
assert_observation_thread!
|
|
77
|
+
transaction do |records, scope|
|
|
78
|
+
leaf = handoff_leaf(execution_id, main_agent_id: main_agent_id, scope: scope)
|
|
79
|
+
next leaf.to_result unless leaf.absent?
|
|
80
|
+
routing = records.handoff_states.load(main_agent_id)
|
|
81
|
+
if Array(routing.metadata["cancelled_execution_ids"]).include?(leaf.execution_id)
|
|
82
|
+
leaf.to_result.merge(status: :cancelled).freeze
|
|
83
|
+
elsif routing.phase != "stable" && routing.pending_target_execution_id == leaf.execution_id
|
|
84
|
+
leaf.to_result.merge(status: :active, reserved: true).freeze
|
|
85
|
+
else
|
|
86
|
+
raise Phronomy::Persistence::NotFoundError, "Reserved Handoff execution is absent"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def forget_subagents(agent_id:, execution_id:)
|
|
92
|
+
transaction do |records, scope|
|
|
93
|
+
extension = agent_store.execution_extension(agent_id: agent_id, execution_id: execution_id,
|
|
94
|
+
binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
|
|
95
|
+
raise Phronomy::Persistence::NotFoundError, "Subagent history is absent" unless extension&.state_ref
|
|
96
|
+
snapshot = records.contents.fetch_json(extension.state_ref)
|
|
97
|
+
children = snapshot.fetch("children")
|
|
98
|
+
ids = ([agent_id] + children.map { |child| child.fetch("agent_id") }).uniq.sort.select { |id| agent_store.exist?(id) }
|
|
99
|
+
agent_store.guard_agents(scope, agent_ids: ids)
|
|
100
|
+
parent = agent_store.observe_execution(agent_id: agent_id, execution_id: execution_id, scope: scope)
|
|
101
|
+
raise Phronomy::AgentBusyError, "Subagent history retains unfinished parent" unless parent.terminal?
|
|
102
|
+
children.each do |child|
|
|
103
|
+
observed = agent_store.observe_execution(agent_id: child.fetch("agent_id"), execution_id: child.fetch("execution_id"), scope: scope)
|
|
104
|
+
if !observed.terminal? && !(observed.absent? && snapshot["cancel_requested"])
|
|
105
|
+
raise Phronomy::AgentBusyError, "Subagent history retains unfinished child"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
agent_store.remove_execution_extension(agent_id: agent_id, execution_id: execution_id,
|
|
109
|
+
binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
|
|
110
|
+
ids.each { |id| agent_store.release_retention(agent_id: id, owner_key: "subagent:#{execution_id}", scope: scope) }
|
|
111
|
+
end
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def forget_handoff(main_agent_id)
|
|
116
|
+
transaction do |records, scope|
|
|
117
|
+
routing = records.handoff_states.load_locked(main_agent_id)
|
|
118
|
+
raise Phronomy::Persistence::NotFoundError, "Handoff history is absent" unless routing
|
|
119
|
+
ids = (Array(routing.metadata["retained_agent_ids"]) + [main_agent_id, routing.active_agent_id]).uniq.sort
|
|
120
|
+
agent_store.guard_agents(scope, agent_ids: ids)
|
|
121
|
+
unless routing.phase == "stable" && ids.all? { |id| agent_store.active_executions(agent_id: id, scope: scope).empty? }
|
|
122
|
+
raise Phronomy::AgentBusyError, "Handoff history retains unfinished work"
|
|
123
|
+
end
|
|
124
|
+
records.handoff_states.delete(main_agent_id, expected_revision: routing.handoff_revision)
|
|
125
|
+
ids.each { |id| agent_store.release_retention(agent_id: id, owner_key: "handoff:#{main_agent_id}", scope: scope) }
|
|
126
|
+
end
|
|
127
|
+
true
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def result(team_execution_id)
|
|
131
|
+
assert_observation_thread!
|
|
132
|
+
execution = team_executions.load(team_execution_id)
|
|
133
|
+
{
|
|
134
|
+
team_execution_id: execution.team_execution_id, team_id: execution.team_id,
|
|
135
|
+
status: execution.status, phase: execution.phase,
|
|
136
|
+
result_ref: execution.result_ref, error_ref: execution.error_ref,
|
|
137
|
+
result: execution.result_ref && contents.fetch_json(execution.result_ref),
|
|
138
|
+
error: execution.error_ref && contents.fetch_json(execution.error_ref)
|
|
139
|
+
}.then { |value| Phronomy::Values::Immutable.copy(value) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def runs(team_id, after: nil, limit: 100)
|
|
143
|
+
assert_observation_thread!
|
|
144
|
+
team_executions.list(team_id, after: after, limit: limit)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def assert_observation_thread!
|
|
150
|
+
if Phronomy::WaitPolicy.blocking_forbidden?
|
|
151
|
+
raise Phronomy::EventLoopReentrancyError, "Durable observation cannot block EventLoop"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|