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
|
@@ -11,12 +11,27 @@ module Phronomy
|
|
|
11
11
|
# Scheduling and aggregation must be pure, replay-safe result calculations.
|
|
12
12
|
# @api public
|
|
13
13
|
class TeamCoordinator
|
|
14
|
-
WorkerState = Data.define(:index, :agent_id, :
|
|
14
|
+
WorkerState = Data.define(:index, :agent_id, :assignment_count, :status) do
|
|
15
15
|
def available? = status == :available
|
|
16
16
|
end
|
|
17
17
|
private_constant :WorkerState
|
|
18
18
|
|
|
19
|
+
# Raised before writing an operation; update still verifies readback.
|
|
20
|
+
OperationRejected = Class.new(Phronomy::ConfigurationError)
|
|
21
|
+
private_constant :OperationRejected
|
|
22
|
+
|
|
19
23
|
class << self
|
|
24
|
+
# Boot composition supplies a fresh store without selecting it here.
|
|
25
|
+
# @api private
|
|
26
|
+
def install_persistence_factory(factory)
|
|
27
|
+
raise ArgumentError, "factory must respond to call" unless factory.respond_to?(:call)
|
|
28
|
+
raise Phronomy::ConfigurationError, "Team persistence factory already installed" if @persistence_factory
|
|
29
|
+
@persistence_factory = factory
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @api private
|
|
33
|
+
def build_default_persistence = @persistence_factory.call
|
|
34
|
+
|
|
20
35
|
# @api public
|
|
21
36
|
def team_definition(id: nil, version: nil)
|
|
22
37
|
if id || version
|
|
@@ -68,7 +83,9 @@ module Phronomy
|
|
|
68
83
|
end
|
|
69
84
|
|
|
70
85
|
# @api public
|
|
71
|
-
def get(team_id)
|
|
86
|
+
def get(team_id)
|
|
87
|
+
ExecutionEnvironment.current.existing_ownership&.get(team_id.to_s, klass: self)
|
|
88
|
+
end
|
|
72
89
|
|
|
73
90
|
# @api private
|
|
74
91
|
def _coordinator_model = @coordinator_model
|
|
@@ -90,14 +107,14 @@ module Phronomy
|
|
|
90
107
|
private
|
|
91
108
|
|
|
92
109
|
def construct(id, persistence, metadata, listener, create:)
|
|
93
|
-
raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::
|
|
110
|
+
raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
|
|
94
111
|
key = id.to_s
|
|
95
112
|
raise ArgumentError, "team_id must not be empty" if key.empty?
|
|
96
|
-
store = persistence || Phronomy.
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
store = persistence || Phronomy::MultiAgent::Store.configured || TeamCoordinator.build_default_persistence
|
|
114
|
+
environment = ExecutionEnvironment.current
|
|
115
|
+
environment.ownership.fetch(key, klass: self, create: create, persistence: store) do
|
|
99
116
|
instance = allocate
|
|
100
|
-
instance.send(:initialize, key.freeze, store, metadata, listener,
|
|
117
|
+
instance.send(:initialize, key.freeze, store, metadata, listener, environment, create: create)
|
|
101
118
|
instance
|
|
102
119
|
end
|
|
103
120
|
end
|
|
@@ -132,12 +149,12 @@ module Phronomy
|
|
|
132
149
|
# @api public
|
|
133
150
|
def result(team_execution_id)
|
|
134
151
|
execution = read_execution(team_execution_id)
|
|
135
|
-
persistence.
|
|
152
|
+
persistence.result(execution.team_execution_id)
|
|
136
153
|
end
|
|
137
154
|
|
|
138
155
|
# @api public
|
|
139
156
|
def executions(after: nil, limit: 100)
|
|
140
|
-
persistence.
|
|
157
|
+
persistence.runs(team_id, after: after, limit: limit)
|
|
141
158
|
end
|
|
142
159
|
|
|
143
160
|
# Requests cancellation of this run only. Existing Agent cancellation and
|
|
@@ -157,8 +174,9 @@ module Phronomy
|
|
|
157
174
|
|
|
158
175
|
private
|
|
159
176
|
|
|
160
|
-
def initialize(id, store, metadata, listener,
|
|
161
|
-
@team_id, @persistence, @listener, @
|
|
177
|
+
def initialize(id, store, metadata, listener, environment, create:)
|
|
178
|
+
@team_id, @persistence, @listener, @environment = id, store, listener, environment
|
|
179
|
+
@admissions = @environment.admissions
|
|
162
180
|
@tokens_mutex, @tokens = Mutex.new, {}
|
|
163
181
|
@coordinator_classes = {}
|
|
164
182
|
definition = self.class.team_definition
|
|
@@ -181,25 +199,25 @@ module Phronomy
|
|
|
181
199
|
end
|
|
182
200
|
|
|
183
201
|
def assert_caller!
|
|
184
|
-
raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::
|
|
185
|
-
unless @
|
|
202
|
+
raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
|
|
203
|
+
unless @environment.current?
|
|
186
204
|
raise Phronomy::RuntimeShutdownError, "Team #{team_id} belongs to a previous Runtime"
|
|
187
205
|
end
|
|
188
206
|
end
|
|
189
207
|
|
|
190
208
|
def with_admission
|
|
191
209
|
assert_caller!
|
|
192
|
-
@
|
|
210
|
+
@admissions.admit!(self)
|
|
193
211
|
admitted = true
|
|
194
212
|
yield
|
|
195
213
|
ensure
|
|
196
|
-
@
|
|
214
|
+
@admissions.release!(self) if admitted
|
|
197
215
|
end
|
|
198
216
|
|
|
199
217
|
def read_execution(id)
|
|
200
218
|
execution = persistence.team_executions.load(id)
|
|
201
219
|
unless execution.team_id == team_id
|
|
202
|
-
raise Phronomy::Persistence::
|
|
220
|
+
raise Phronomy::Persistence::StateConflictError, "Team execution #{id} belongs to another Team"
|
|
203
221
|
end
|
|
204
222
|
execution
|
|
205
223
|
end
|
|
@@ -218,13 +236,13 @@ module Phronomy
|
|
|
218
236
|
now = Time.now.utc.iso8601(6)
|
|
219
237
|
intended = nil
|
|
220
238
|
begin
|
|
221
|
-
persistence.transaction do |tx|
|
|
239
|
+
persistence.transaction do |tx, scope|
|
|
222
240
|
root = tx.teams.load(team_id)
|
|
223
241
|
raise Phronomy::AgentBusyError, "Team #{team_id} has an active run; use resume" unless tx.team_executions.list_active(team_id).empty?
|
|
224
242
|
intended = TeamExecution.new(team_execution_id: id, team_id: team_id,
|
|
225
243
|
execution_revision: 0, status: "active", phase: "coordinator", input_ref: tx.contents.put_text(input),
|
|
226
244
|
coordinator: {"agent_id" => SecureRandom.uuid, "execution_id" => SecureRandom.uuid, "state" => "reserved"},
|
|
227
|
-
workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid
|
|
245
|
+
workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid} },
|
|
228
246
|
tasks: [], assignments: [], result_ref: nil, error_ref: nil, created_at: now, updated_at: now,
|
|
229
247
|
metadata: {"definition" => definition, "operations" => {}, "cancel_requested" => false,
|
|
230
248
|
"durable_context_ref" => config.key?(:durable_context) ? tx.contents.put_json(config.fetch(:durable_context)) : nil})
|
|
@@ -249,10 +267,10 @@ module Phronomy
|
|
|
249
267
|
attempts = 0
|
|
250
268
|
begin
|
|
251
269
|
intended = nil
|
|
252
|
-
persistence.transaction do |tx|
|
|
270
|
+
persistence.transaction do |tx, scope|
|
|
253
271
|
current = tx.team_executions.load(id)
|
|
254
|
-
raise Phronomy::Persistence::
|
|
255
|
-
intended = yield(current, tx)
|
|
272
|
+
raise Phronomy::Persistence::StateConflictError, "Team execution owner mismatch" unless current.team_id == team_id
|
|
273
|
+
intended = yield(current, tx, scope)
|
|
256
274
|
next if intended.equal?(current)
|
|
257
275
|
tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
|
|
258
276
|
if intended.terminal? && current.active?
|
|
@@ -279,9 +297,9 @@ module Phronomy
|
|
|
279
297
|
end
|
|
280
298
|
token = Phronomy::Concurrency::CancellationToken.new
|
|
281
299
|
@tokens_mutex.synchronize { @tokens[id] = token }
|
|
282
|
-
|
|
283
|
-
callback = proc { @
|
|
284
|
-
|
|
300
|
+
cancellation_subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
|
|
301
|
+
callback = proc { @environment.submit(on_full: :raise) { cancel(id) } }
|
|
302
|
+
cancellation_subscriptions.explicit_cancellation(config[:cancellation_token], &callback)
|
|
285
303
|
token.cancel! if current.metadata["cancel_requested"]
|
|
286
304
|
begin
|
|
287
305
|
loop do
|
|
@@ -320,13 +338,13 @@ module Phronomy
|
|
|
320
338
|
values = assignment_values(current)
|
|
321
339
|
aggregate = self.class._aggregator
|
|
322
340
|
begin
|
|
323
|
-
output = Phronomy::
|
|
341
|
+
output = Phronomy::Values::Serializable.convert(aggregate ? aggregate.call(values) : values, unsupported_message: "Team result is not canonically serializable")
|
|
324
342
|
Phronomy::CanonicalJSON.dump(output)
|
|
325
343
|
rescue => error
|
|
326
|
-
return terminal_value(finish_error(id,
|
|
344
|
+
return terminal_value(finish_error(id, {"class" => error.class.name.to_s, "message" => error.message.to_s}))
|
|
327
345
|
end
|
|
328
346
|
# Aggregate may be replayed only until the canonical outcome commits.
|
|
329
|
-
completed = update(id) do |fresh, tx|
|
|
347
|
+
completed = update(id) do |fresh, tx, scope|
|
|
330
348
|
next fresh if fresh.terminal? || fresh.metadata["cancel_requested"]
|
|
331
349
|
fresh.with(status: "completed", phase: "completed", result_ref: tx.contents.put_json(output))
|
|
332
350
|
end
|
|
@@ -342,7 +360,7 @@ module Phronomy
|
|
|
342
360
|
terminal_value(finish_error(id, {"class" => "Phronomy::CancellationError", "message" => "Team run cancelled"}, status: "cancelled"))
|
|
343
361
|
end
|
|
344
362
|
ensure
|
|
345
|
-
|
|
363
|
+
cancellation_subscriptions.close
|
|
346
364
|
@tokens_mutex.synchronize { @tokens.delete(id) }
|
|
347
365
|
end
|
|
348
366
|
end
|
|
@@ -380,30 +398,31 @@ module Phronomy
|
|
|
380
398
|
id = slot.fetch("agent_id")
|
|
381
399
|
child = klass.get(id)
|
|
382
400
|
unless child
|
|
383
|
-
|
|
384
|
-
persistence.agents.load(id)
|
|
385
|
-
present = true
|
|
386
|
-
rescue Phronomy::Persistence::NotFoundError
|
|
387
|
-
present = false
|
|
388
|
-
end
|
|
401
|
+
present = persistence.agent_store.exist?(id)
|
|
389
402
|
token.raise_if_cancelled! unless present
|
|
390
|
-
child = present ? klass.load(id, persistence: persistence, on_event: @listener) : klass.create(agent_id: id, persistence: persistence, on_event: @listener)
|
|
403
|
+
child = present ? klass.load(id, persistence: persistence.agent_store, on_event: @listener) : klass.create(agent_id: id, persistence: persistence.agent_store, on_event: @listener)
|
|
391
404
|
end
|
|
392
|
-
unless child.persistence.equal?(persistence)
|
|
405
|
+
unless child.persistence.equal?(persistence.agent_store)
|
|
393
406
|
raise Phronomy::ConfigurationError, "Team child #{id} Persistence mismatch"
|
|
394
407
|
end
|
|
395
408
|
child_config = config.merge(cancellation_token: token,
|
|
396
|
-
|
|
397
|
-
|
|
409
|
+
phronomy_reservation: {"kind" => "team", "team_id" => team_id,
|
|
410
|
+
"team_execution_id" => current.team_execution_id, "slot" => purpose})
|
|
411
|
+
child_config = child_config.merge(phronomy_admission: ReservedChildAdmission.new(
|
|
412
|
+
persistence: persistence, owner: child_config.fetch(:phronomy_reservation)
|
|
413
|
+
))
|
|
398
414
|
if (context_ref = current.metadata["durable_context_ref"])
|
|
399
415
|
child_config = child_config.merge(durable_context: persistence.contents.fetch_json(context_ref))
|
|
400
416
|
end
|
|
401
|
-
Phronomy::Agent::
|
|
417
|
+
child.start_reserved_async(reservation: Phronomy::Agent::ReservedExecution.new(agent_id: id, execution_id: slot.fetch("execution_id"), correlation: child_config.fetch(:phronomy_reservation)), input: input, config: child_config).wait_result
|
|
402
418
|
end
|
|
403
419
|
|
|
404
420
|
def reserve_assignment(current, task)
|
|
405
|
-
|
|
406
|
-
|
|
421
|
+
# Team owns this count. Deriving it from retained assignments also
|
|
422
|
+
# makes replay idempotent and avoids consulting the child's journal.
|
|
423
|
+
counts = current.assignments.each_with_object(Hash.new(0)) { |entry, totals| totals[entry.fetch("worker")] += 1 }
|
|
424
|
+
available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), assignment_count: counts[w.fetch("index")], status: :available) }.freeze
|
|
425
|
+
worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:assignment_count)
|
|
407
426
|
raise Phronomy::ConfigurationError, "Scheduler must select an available worker slot" unless available.include?(worker)
|
|
408
427
|
reserved = {"task_id" => task.fetch("id"), "worker" => worker.index,
|
|
409
428
|
"execution_id" => SecureRandom.uuid, "state" => "reserved", "result_ref" => nil, "error_ref" => nil}
|
|
@@ -414,20 +433,15 @@ module Phronomy
|
|
|
414
433
|
end
|
|
415
434
|
|
|
416
435
|
def record_assignment(id, assigned, outcome)
|
|
417
|
-
update(id) do |fresh,
|
|
436
|
+
update(id) do |fresh, _tx|
|
|
418
437
|
entries = fresh.assignments.map do |entry|
|
|
419
438
|
next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
|
|
420
439
|
next entry unless entry.fetch("state") == "reserved"
|
|
421
|
-
raise Phronomy::Persistence::
|
|
440
|
+
raise Phronomy::Persistence::StateConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
|
|
422
441
|
entry.merge("state" => outcome.fetch(:status).to_s,
|
|
423
442
|
"result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
|
|
424
443
|
end
|
|
425
|
-
|
|
426
|
-
next worker unless worker.fetch("index") == assigned.fetch("worker")
|
|
427
|
-
root = tx.agents.load(worker.fetch("agent_id"))
|
|
428
|
-
worker.merge("transcript_size" => root.journal_position)
|
|
429
|
-
end
|
|
430
|
-
fresh.with(assignments: entries, workers: workers)
|
|
444
|
+
fresh.with(assignments: entries)
|
|
431
445
|
end
|
|
432
446
|
end
|
|
433
447
|
|
|
@@ -441,7 +455,7 @@ module Phronomy
|
|
|
441
455
|
end
|
|
442
456
|
|
|
443
457
|
def finish_error(id, error, status: "failed")
|
|
444
|
-
update(id) do |fresh, tx|
|
|
458
|
+
update(id) do |fresh, tx, scope|
|
|
445
459
|
next fresh if fresh.terminal?
|
|
446
460
|
fresh.with(status: status, phase: status, error_ref: tx.contents.put_json(error))
|
|
447
461
|
end
|
|
@@ -449,7 +463,10 @@ module Phronomy
|
|
|
449
463
|
|
|
450
464
|
def terminal_value(execution)
|
|
451
465
|
raise Phronomy::CancellationError, "Team run #{execution.team_execution_id} cancelled" if execution.status == "cancelled"
|
|
452
|
-
|
|
466
|
+
if execution.error_ref
|
|
467
|
+
failure = persistence.contents.fetch_json(execution.error_ref)
|
|
468
|
+
raise Phronomy::Error, "#{failure.fetch("class")}: #{failure.fetch("message")}"
|
|
469
|
+
end
|
|
453
470
|
persistence.contents.fetch_json(execution.result_ref)
|
|
454
471
|
end
|
|
455
472
|
|
|
@@ -474,7 +491,7 @@ module Phronomy
|
|
|
474
491
|
|
|
475
492
|
def build_operation_tool(run_id, operation)
|
|
476
493
|
team = self
|
|
477
|
-
Class.new(Phronomy::
|
|
494
|
+
Class.new(Phronomy::Tool::Base) do
|
|
478
495
|
def self.__framework_owned_operation? = true
|
|
479
496
|
tool_name operation.to_s
|
|
480
497
|
description((operation == :enqueue_task) ? "Add a task to the worker queue." : "Finish task generation.")
|
|
@@ -483,10 +500,10 @@ module Phronomy
|
|
|
483
500
|
param :description, type: :string, desc: "Worker task"
|
|
484
501
|
param :metadata, type: :string, desc: "Optional metadata", required: false
|
|
485
502
|
else
|
|
486
|
-
param :summary, type: :string, desc: "
|
|
503
|
+
param :summary, type: :string, desc: "Task generation summary", required: false
|
|
487
504
|
end
|
|
488
505
|
define_method(:call_async) do |args, cancellation_token: nil, config: {}|
|
|
489
|
-
validated, schema_error =
|
|
506
|
+
validated, schema_error = validate_arguments(args)
|
|
490
507
|
raise Phronomy::ToolError, schema_error if schema_error
|
|
491
508
|
execute_async(**validated, cancellation_token: cancellation_token, config: config)
|
|
492
509
|
rescue => error
|
|
@@ -494,8 +511,10 @@ module Phronomy
|
|
|
494
511
|
end
|
|
495
512
|
define_method(:execute_async) do |config: {}, cancellation_token: nil, **arguments|
|
|
496
513
|
key = config.fetch(:phronomy_tool_invocation_id)
|
|
497
|
-
Phronomy::
|
|
514
|
+
Phronomy::Execution.submit(on_full: :raise) do
|
|
498
515
|
team.send(:apply_operation, run_id, key, operation, arguments)
|
|
516
|
+
rescue OperationRejected => error
|
|
517
|
+
raise Phronomy::ConfigurationError, error.message
|
|
499
518
|
rescue Phronomy::CancellationError
|
|
500
519
|
raise
|
|
501
520
|
rescue => error
|
|
@@ -507,42 +526,35 @@ module Phronomy
|
|
|
507
526
|
end
|
|
508
527
|
|
|
509
528
|
def apply_operation(run_id, key, operation, arguments)
|
|
510
|
-
argument_values = Phronomy::
|
|
511
|
-
current = update(run_id) do |fresh, tx|
|
|
529
|
+
argument_values = Phronomy::Values::Serializable.convert(arguments, unsupported_message: "Team arguments are not canonically serializable")
|
|
530
|
+
current = update(run_id) do |fresh, tx, scope|
|
|
512
531
|
operations = fresh.metadata.fetch("operations")
|
|
513
532
|
if (prior = operations[key])
|
|
514
533
|
unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
|
|
515
|
-
raise Phronomy::Persistence::
|
|
534
|
+
raise Phronomy::Persistence::StateConflictError, "Team operation #{key} identity mismatch"
|
|
516
535
|
end
|
|
517
536
|
next fresh
|
|
518
537
|
end
|
|
519
538
|
raise Phronomy::CancellationError, "Team run cancelled" if fresh.metadata["cancel_requested"]
|
|
520
|
-
raise Phronomy::Persistence::
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
end
|
|
525
|
-
batch = Array(coordinator.metadata[Phronomy::Agent::RecoverySupport::TOOL_BATCH_METADATA_KEY])
|
|
526
|
-
requested = batch.find { |entry| entry.fetch("tool_invocation_id") == key }
|
|
527
|
-
unless requested && requested.fetch("status") == "authorized" && requested.fetch("tool_name") == operation.to_s && requested.fetch("arguments").compact == argument_values
|
|
528
|
-
raise Phronomy::Persistence::ConflictError, "Team operation #{key} is not the authorized call"
|
|
529
|
-
end
|
|
539
|
+
raise Phronomy::Persistence::StateConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
|
|
540
|
+
batch = persistence.agent_store.authorized_operations(scope,
|
|
541
|
+
agent_id: fresh.coordinator.fetch("agent_id"), execution_id: fresh.coordinator.fetch("execution_id"),
|
|
542
|
+
invocation_id: key, name: operation.to_s, arguments: argument_values, names: %w[enqueue_task finalize])
|
|
530
543
|
# All calls passed the Agent authorization barrier. Commit this finite
|
|
531
544
|
# batch in Provider order, so finalize cannot overtake queued tasks.
|
|
532
545
|
tasks = fresh.tasks.dup
|
|
533
546
|
operations = operations.dup
|
|
534
547
|
metadata = fresh.metadata.dup
|
|
535
548
|
batch.each do |entry|
|
|
536
|
-
entry_id = entry.
|
|
537
|
-
next if operations.key?(entry_id)
|
|
538
|
-
name = entry.
|
|
539
|
-
|
|
540
|
-
values = entry.fetch("arguments").compact
|
|
549
|
+
entry_id = entry.invocation_id
|
|
550
|
+
next if operations.key?(entry_id)
|
|
551
|
+
name = entry.name
|
|
552
|
+
values = entry.arguments
|
|
541
553
|
if name == "enqueue_task"
|
|
542
|
-
raise
|
|
554
|
+
raise OperationRejected, "Cannot enqueue after finalize" if metadata["finalized"]
|
|
543
555
|
task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
|
|
544
556
|
tasks << task
|
|
545
|
-
output = "
|
|
557
|
+
output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
|
|
546
558
|
else
|
|
547
559
|
output = "Finalized. #{tasks.size} task(s) enqueued. #{values["summary"]}".strip
|
|
548
560
|
metadata["finalized"] = true
|
|
@@ -14,7 +14,7 @@ module Phronomy
|
|
|
14
14
|
source = values.transform_keys(&:to_s)
|
|
15
15
|
raise ArgumentError, "TeamExecution schema mismatch" unless source.keys.sort == ATTRIBUTES.sort
|
|
16
16
|
canonical = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(source))
|
|
17
|
-
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::
|
|
17
|
+
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Values::Immutable.copy(canonical.fetch(key))) }
|
|
18
18
|
raise ArgumentError, "missing team_execution_id" if team_execution_id.to_s.empty?
|
|
19
19
|
raise ArgumentError, "invalid execution_revision" unless execution_revision.is_a?(Integer) && execution_revision >= 0
|
|
20
20
|
raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
|
|
5
|
-
#
|
|
4
|
+
module MultiAgent
|
|
5
|
+
# Environment-local Team identity and construction exclusion; no execution state.
|
|
6
6
|
# @api private
|
|
7
7
|
class TeamOwnershipRegistry
|
|
8
8
|
def initialize
|
|
@@ -18,7 +18,7 @@ module Phronomy
|
|
|
18
18
|
loop do
|
|
19
19
|
raise Phronomy::RuntimeShutdownError, "Runtime is draining" if @draining
|
|
20
20
|
if (owner = @owners[id])
|
|
21
|
-
raise Phronomy::Persistence::
|
|
21
|
+
raise Phronomy::Persistence::StateConflictError, "Team #{id} already exists" if create
|
|
22
22
|
unless owner.is_a?(klass) && owner.persistence.equal?(persistence)
|
|
23
23
|
raise Phronomy::ConfigurationError, "Team #{id} owner or Persistence mismatch"
|
|
24
24
|
end
|
|
@@ -58,7 +58,7 @@ module Phronomy
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def
|
|
61
|
+
def wait_until_idle(deadline)
|
|
62
62
|
@mutex.synchronize do
|
|
63
63
|
until @constructing.empty?
|
|
64
64
|
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -69,7 +69,7 @@ module Phronomy
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
def
|
|
72
|
+
def after_runtime_shutdown
|
|
73
73
|
@mutex.synchronize { @owners.clear }
|
|
74
74
|
end
|
|
75
75
|
end
|
|
@@ -14,7 +14,7 @@ module Phronomy
|
|
|
14
14
|
source = values.transform_keys(&:to_s)
|
|
15
15
|
raise ArgumentError, "TeamRoot schema mismatch" unless source.keys.sort == ATTRIBUTES.sort
|
|
16
16
|
canonical = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(source))
|
|
17
|
-
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::
|
|
17
|
+
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Values::Immutable.copy(canonical.fetch(key))) }
|
|
18
18
|
raise ArgumentError, "missing team_id" if team_id.to_s.empty?
|
|
19
19
|
raise ArgumentError, "invalid team_revision" unless team_revision.is_a?(Integer) && team_revision >= 0
|
|
20
20
|
raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Domain-facing content access uses the Persistence failure vocabulary.
|
|
6
|
+
# ContentStore implementations retain their independent backend contract.
|
|
7
|
+
# @api private
|
|
8
|
+
class ContentRepository < Phronomy::ContentStore::Base
|
|
9
|
+
def initialize(store)
|
|
10
|
+
@store = store
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def put(bytes, canonicalization_version:)
|
|
14
|
+
StorageBoundary.call { @store.put(bytes, canonicalization_version: canonicalization_version) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fetch(content_id)
|
|
18
|
+
StorageBoundary.call { @store.fetch(content_id) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def exist?(content_id)
|
|
22
|
+
StorageBoundary.call { @store.exist?(content_id) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
class Persistence
|
|
7
|
+
# Failures describe a category, not a universal claim about commit outcome.
|
|
8
|
+
class Error < Phronomy::Error; end
|
|
9
|
+
class ConflictError < Error; end
|
|
10
|
+
class StateConflictError < ConflictError; end
|
|
11
|
+
class NotFoundError < Error; end
|
|
12
|
+
class SerializationError < Error; end
|
|
13
|
+
class UnsupportedBackendError < Error; end
|
|
14
|
+
class TransactionError < Error; end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -32,10 +32,10 @@ module Phronomy
|
|
|
32
32
|
PRE_S3_AGENT_ROOT_KEYS,
|
|
33
33
|
label: "pre-S3 AgentRoot"
|
|
34
34
|
)
|
|
35
|
-
|
|
35
|
+
Phronomy::Agent::Persistence::Codec.encode_agent_root(
|
|
36
36
|
Phronomy::Agent::AgentRoot.from_h(source)
|
|
37
37
|
)
|
|
38
|
-
rescue Phronomy::
|
|
38
|
+
rescue Phronomy::Storage::SerializationError
|
|
39
39
|
raise
|
|
40
40
|
rescue => error
|
|
41
41
|
migration_error("AgentRoot", error)
|
|
@@ -68,8 +68,8 @@ module Phronomy
|
|
|
68
68
|
execution_id: source.fetch("execution_id")
|
|
69
69
|
)
|
|
70
70
|
execution = Phronomy::Agent::AgentExecution.from_h(source)
|
|
71
|
-
|
|
72
|
-
rescue Phronomy::
|
|
71
|
+
Phronomy::Agent::Persistence::Codec.encode_agent_execution(execution)
|
|
72
|
+
rescue Phronomy::Storage::SerializationError
|
|
73
73
|
raise
|
|
74
74
|
rescue => error
|
|
75
75
|
migration_error("AgentExecution", error)
|
|
@@ -77,40 +77,40 @@ module Phronomy
|
|
|
77
77
|
|
|
78
78
|
def journal_record(hash)
|
|
79
79
|
source = normalize_legacy_journal_record(hash, label: "pre-S3 JournalRecord")
|
|
80
|
-
|
|
80
|
+
Phronomy::Agent::Persistence::Codec.encode_journal_record(
|
|
81
81
|
Phronomy::Agent::JournalRecord.from_h(source)
|
|
82
82
|
)
|
|
83
|
-
rescue Phronomy::
|
|
83
|
+
rescue Phronomy::Storage::SerializationError
|
|
84
84
|
raise
|
|
85
85
|
rescue => error
|
|
86
86
|
migration_error("JournalRecord", error)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def workflow_state(workflow_instance_id:, revision:, snapshot:)
|
|
90
|
-
|
|
90
|
+
Phronomy::Workflow::Persistence::Codec.encode_workflow_state(
|
|
91
91
|
workflow_instance_id: workflow_instance_id,
|
|
92
92
|
workflow_revision: Integer(revision),
|
|
93
93
|
snapshot: snapshot
|
|
94
94
|
)
|
|
95
|
-
rescue Phronomy::
|
|
95
|
+
rescue Phronomy::Storage::SerializationError
|
|
96
96
|
raise
|
|
97
97
|
rescue => error
|
|
98
98
|
migration_error("Workflow state", error)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
# LLMInputManifest is a ContentStore codec boundary, not a
|
|
102
|
-
#
|
|
102
|
+
# Storage::DurableRecord. Its pre-CG-07 integer version 1 is migrated
|
|
103
103
|
# explicitly to the pre-1.0 string version "0.1".
|
|
104
104
|
def llm_input_manifest(hash)
|
|
105
105
|
source = stringify_keys(hash)
|
|
106
106
|
old_version = source.fetch("version")
|
|
107
107
|
unless old_version == 1 || old_version == "1"
|
|
108
|
-
raise Phronomy::
|
|
108
|
+
raise Phronomy::Storage::SerializationError,
|
|
109
109
|
"unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
|
|
110
110
|
end
|
|
111
|
-
source["version"] = Phronomy::
|
|
112
|
-
Phronomy::
|
|
113
|
-
rescue Phronomy::
|
|
111
|
+
source["version"] = Phronomy::Context::LLMInputManifest::VERSION
|
|
112
|
+
Phronomy::Context::LLMInputManifest.from_h(source).to_h
|
|
113
|
+
rescue Phronomy::Storage::SerializationError
|
|
114
114
|
raise
|
|
115
115
|
rescue => error
|
|
116
116
|
migration_error("LLMInputManifest", error)
|
|
@@ -141,13 +141,13 @@ module Phronomy
|
|
|
141
141
|
)
|
|
142
142
|
parents = PRE_S3_APPROVAL_PARENT_KEYS.select { |key| source.key?(key) }
|
|
143
143
|
unless parents.length == 1
|
|
144
|
-
raise Phronomy::
|
|
144
|
+
raise Phronomy::Storage::SerializationError,
|
|
145
145
|
"pre-S3 approval_request must contain exactly one parent identity, got #{parents.inspect}"
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
items = source.fetch("items")
|
|
149
149
|
unless items.is_a?(Array) && !items.empty?
|
|
150
|
-
raise Phronomy::
|
|
150
|
+
raise Phronomy::Storage::SerializationError,
|
|
151
151
|
"pre-S3 approval_request items must be a non-empty Array"
|
|
152
152
|
end
|
|
153
153
|
source["items"] = items.map.with_index do |item, index|
|
|
@@ -164,7 +164,7 @@ module Phronomy
|
|
|
164
164
|
source.delete("agent_invocation_id")
|
|
165
165
|
source["execution_id"] = execution_id.to_s
|
|
166
166
|
elsif source.fetch("execution_id").to_s != execution_id.to_s
|
|
167
|
-
raise Phronomy::
|
|
167
|
+
raise Phronomy::Storage::SerializationError,
|
|
168
168
|
"pre-S3 approval_request execution_id does not match AgentExecution"
|
|
169
169
|
end
|
|
170
170
|
source
|
|
@@ -180,7 +180,7 @@ module Phronomy
|
|
|
180
180
|
details = []
|
|
181
181
|
details << "missing=#{missing.inspect}" unless missing.empty?
|
|
182
182
|
details << "unknown=#{unknown.inspect}" unless unknown.empty?
|
|
183
|
-
raise Phronomy::
|
|
183
|
+
raise Phronomy::Storage::SerializationError,
|
|
184
184
|
"#{label} schema mismatch (#{details.join(", ")})"
|
|
185
185
|
end
|
|
186
186
|
private_class_method :validate_allowed_keys!
|
|
@@ -197,17 +197,17 @@ module Phronomy
|
|
|
197
197
|
|
|
198
198
|
def stringify_keys(hash)
|
|
199
199
|
unless hash.is_a?(Hash)
|
|
200
|
-
raise Phronomy::
|
|
200
|
+
raise Phronomy::Storage::SerializationError,
|
|
201
201
|
"migration input must be a Hash"
|
|
202
202
|
end
|
|
203
203
|
hash.each_with_object({}) do |(key, value), result|
|
|
204
204
|
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
205
|
-
raise Phronomy::
|
|
205
|
+
raise Phronomy::Storage::SerializationError,
|
|
206
206
|
"migration key must be String or Symbol, got #{key.class}"
|
|
207
207
|
end
|
|
208
208
|
string_key = key.to_s
|
|
209
209
|
if result.key?(string_key)
|
|
210
|
-
raise Phronomy::
|
|
210
|
+
raise Phronomy::Storage::SerializationError,
|
|
211
211
|
"duplicate migration key after normalization: #{string_key.inspect}"
|
|
212
212
|
end
|
|
213
213
|
result[string_key] = value
|
|
@@ -216,7 +216,7 @@ module Phronomy
|
|
|
216
216
|
private_class_method :stringify_keys
|
|
217
217
|
|
|
218
218
|
def migration_error(label, error)
|
|
219
|
-
raise Phronomy::
|
|
219
|
+
raise Phronomy::Storage::SerializationError,
|
|
220
220
|
"cannot migrate pre-S3 #{label}: #{error.class}: #{error.message}"
|
|
221
221
|
end
|
|
222
222
|
private_class_method :migration_error
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Domain-neutral transaction ownership and participation. Domain operations
|
|
5
|
+
# choose their atomic boundary; composition supplies their storage adapters.
|
|
6
|
+
# @api public
|
|
7
|
+
class Persistence
|
|
8
|
+
attr_reader :backend
|
|
9
|
+
|
|
10
|
+
def initialize(backend:)
|
|
11
|
+
StorageBoundary.call { Storage::Backend.validate_capabilities!(backend) }
|
|
12
|
+
@backend = backend
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Bind an injected adapter for ordinary synchronous reads/standalone writes.
|
|
16
|
+
# Only that adapter receives Storage's raw view. Multi-operation changes use
|
|
17
|
+
# atomic/Transaction#participate so all participants use the exact same view.
|
|
18
|
+
# @api private
|
|
19
|
+
def bind(adapter)
|
|
20
|
+
StorageBoundary.call { adapter.new(backend.view) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|