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,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Engine implementation of the Execution mechanism protocol. Installing this
|
|
5
|
+
# binding does not acquire any resources; default Runtime is resolved on use.
|
|
6
|
+
# @api private
|
|
7
|
+
module ExecutionBinding
|
|
8
|
+
def self.submit(pool: nil, runtime: nil, pool_name: nil, size: 10,
|
|
9
|
+
queue_size: 100, **options, &operation)
|
|
10
|
+
unless pool
|
|
11
|
+
runtime ||= Runtime.instance
|
|
12
|
+
pool = pool_name ? runtime.pool(pool_name, size: size, queue_size: queue_size) : runtime.offload
|
|
13
|
+
end
|
|
14
|
+
pool.submit(**options, &operation)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.after(seconds, &callback)
|
|
18
|
+
timer = Runtime.instance.timer_queue
|
|
19
|
+
timer.schedule(seconds: seconds, &callback)
|
|
20
|
+
-> { timer.cancel(callback) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Concrete EventLoop transport and lifecycle registration for execution state.
|
|
5
|
+
# @api private
|
|
6
|
+
class ExecutionReceiverBinding
|
|
7
|
+
def self.receiver(receiver_class, event_loop)
|
|
8
|
+
event_loop.__execution_receiver(key: receiver_class) ||
|
|
9
|
+
event_loop.__register_execution_receiver(key: receiver_class,
|
|
10
|
+
receiver: receiver_class.new(channel: new(event_loop)))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.existing_receiver(receiver_class, runtime)
|
|
14
|
+
runtime.__event_loop_if_initialized&.__execution_receiver(key: receiver_class)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(event_loop)
|
|
18
|
+
@event_loop = event_loop
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def bound_to?(owner) = @event_loop.equal?(owner)
|
|
22
|
+
def current? = @event_loop.current?
|
|
23
|
+
|
|
24
|
+
def synchronize(&block)
|
|
25
|
+
@event_loop.__synchronize_execution_state(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def admit(receiver, &block)
|
|
29
|
+
@event_loop.__admit_execution(receiver, &block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def post(receiver, message, admission:, completion:)
|
|
33
|
+
@event_loop.__post_execution(receiver, message, admission: admission, completion: completion)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def route(receiver, type:, payload:)
|
|
37
|
+
@event_loop.__route_execution_event(receiver) do
|
|
38
|
+
target = yield
|
|
39
|
+
target && Phronomy::Event.new(type: type, target_id: target, payload: payload)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../execution/concurrency/worker_input_restricted"
|
|
4
|
+
|
|
3
5
|
require "securerandom"
|
|
4
6
|
|
|
5
7
|
module Phronomy
|
|
@@ -9,6 +11,8 @@ module Phronomy
|
|
|
9
11
|
# FSMSession owns FSM execution only; it does not own external TaskResult handles,
|
|
10
12
|
# activity tokens, callback correlation, or domain-specific stale-event policy.
|
|
11
13
|
class FSMSession
|
|
14
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
15
|
+
|
|
12
16
|
class IdentityReservation
|
|
13
17
|
attr_reader :fsm_session_id
|
|
14
18
|
|
|
@@ -30,6 +34,8 @@ module Phronomy
|
|
|
30
34
|
private_constant :IdentityReservation
|
|
31
35
|
|
|
32
36
|
class EventSink
|
|
37
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
38
|
+
|
|
33
39
|
attr_reader :fsm_session_id
|
|
34
40
|
|
|
35
41
|
def initialize(event_loop:)
|
|
@@ -60,8 +66,6 @@ module Phronomy
|
|
|
60
66
|
end
|
|
61
67
|
end
|
|
62
68
|
|
|
63
|
-
FINISH = WorkflowRunner::FINISH
|
|
64
|
-
|
|
65
69
|
attr_reader :id, :context, :event_sink
|
|
66
70
|
|
|
67
71
|
# Returns the live current Workflow phase. During an active FSM transition
|
|
@@ -95,7 +99,7 @@ module Phronomy
|
|
|
95
99
|
context_metadata: {},
|
|
96
100
|
event_sink: nil,
|
|
97
101
|
identity_reservation: nil,
|
|
98
|
-
|
|
102
|
+
terminal_policy: nil
|
|
99
103
|
)
|
|
100
104
|
@id = if identity_reservation
|
|
101
105
|
unless identity_reservation.is_a?(IdentityReservation)
|
|
@@ -123,7 +127,7 @@ module Phronomy
|
|
|
123
127
|
@resume_event = resume_event
|
|
124
128
|
@resume_phase = resume_phase
|
|
125
129
|
@stable_observer = stable_observer
|
|
126
|
-
@
|
|
130
|
+
@terminal_policy = terminal_policy
|
|
127
131
|
@terminal_lifecycle_state = :running
|
|
128
132
|
@pending_terminal_type = nil
|
|
129
133
|
@pending_terminal_notify_stable = false
|
|
@@ -160,14 +164,13 @@ module Phronomy
|
|
|
160
164
|
def handle(event)
|
|
161
165
|
return if @done
|
|
162
166
|
|
|
163
|
-
if event
|
|
164
|
-
|
|
167
|
+
if @terminal_policy&.handles?(event)
|
|
168
|
+
handle_terminal_event(event)
|
|
165
169
|
return
|
|
166
170
|
end
|
|
167
171
|
|
|
168
|
-
# Once terminal
|
|
169
|
-
#
|
|
170
|
-
# FSMSession can advance its terminal lifecycle.
|
|
172
|
+
# Once a terminal decision is pending, ordinary events cannot advance
|
|
173
|
+
# the state machine. Only the injected policy can permit terminalization.
|
|
171
174
|
return unless @terminal_lifecycle_state == :running
|
|
172
175
|
|
|
173
176
|
context_disposition = apply_context_event(event)
|
|
@@ -261,7 +264,7 @@ module Phronomy
|
|
|
261
264
|
end
|
|
262
265
|
|
|
263
266
|
def advance_or_halt
|
|
264
|
-
return finish! if @current_state == FINISH
|
|
267
|
+
return finish! if @current_state == FSMProtocol::FINISH
|
|
265
268
|
|
|
266
269
|
# A wait state is the logical end of this Workflow execution segment. Its
|
|
267
270
|
# public stable-state notification is therefore part of terminalization
|
|
@@ -324,7 +327,7 @@ module Phronomy
|
|
|
324
327
|
def finish!(notify_stable: false)
|
|
325
328
|
request_terminal!(
|
|
326
329
|
:finished,
|
|
327
|
-
phase:
|
|
330
|
+
phase: FSMProtocol::FINISH,
|
|
328
331
|
notify_stable: notify_stable
|
|
329
332
|
)
|
|
330
333
|
end
|
|
@@ -339,13 +342,13 @@ module Phronomy
|
|
|
339
342
|
apply_context_metadata!(phase: phase)
|
|
340
343
|
@pending_terminal_type = terminal_type
|
|
341
344
|
@pending_terminal_notify_stable = notify_stable
|
|
342
|
-
unless @
|
|
345
|
+
unless @terminal_policy
|
|
343
346
|
complete_terminal!(terminal_type)
|
|
344
347
|
return
|
|
345
348
|
end
|
|
346
349
|
|
|
347
|
-
@terminal_lifecycle_state = :
|
|
348
|
-
@
|
|
350
|
+
@terminal_lifecycle_state = :awaiting_terminal
|
|
351
|
+
@terminal_policy.start(
|
|
349
352
|
terminal_type: terminal_type,
|
|
350
353
|
context: @ctx,
|
|
351
354
|
event_sink: @event_sink
|
|
@@ -354,31 +357,35 @@ module Phronomy
|
|
|
354
357
|
finish_with_error(error)
|
|
355
358
|
end
|
|
356
359
|
|
|
357
|
-
def
|
|
358
|
-
return unless @terminal_lifecycle_state == :
|
|
360
|
+
def handle_terminal_event(event)
|
|
361
|
+
return unless @terminal_lifecycle_state == :awaiting_terminal
|
|
359
362
|
|
|
360
|
-
|
|
361
|
-
|
|
363
|
+
decision = @terminal_policy.decision_for(event)
|
|
364
|
+
case decision.action
|
|
365
|
+
when :complete
|
|
362
366
|
complete_terminal!(@pending_terminal_type)
|
|
363
|
-
when :
|
|
364
|
-
finish_with_error(
|
|
365
|
-
|
|
366
|
-
)
|
|
367
|
-
when :outcome_unknown
|
|
368
|
-
@done = true
|
|
369
|
-
@terminal_lifecycle_state = :recovery_required
|
|
370
|
-
post_recovery_required_event(result.error)
|
|
367
|
+
when :fail
|
|
368
|
+
finish_with_error(decision.error)
|
|
369
|
+
when :retire
|
|
370
|
+
retire_without_result!(decision.error)
|
|
371
371
|
else
|
|
372
372
|
raise Phronomy::Error,
|
|
373
|
-
"unknown
|
|
373
|
+
"unknown FSM terminal decision: #{decision.action.inspect}"
|
|
374
374
|
end
|
|
375
375
|
end
|
|
376
376
|
|
|
377
|
-
def
|
|
377
|
+
def retire_without_result!(error)
|
|
378
378
|
@done = true
|
|
379
|
+
@terminal_lifecycle_state = :retired
|
|
380
|
+
post_recovery_required_event(error)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def complete_terminal!(terminal_type)
|
|
379
384
|
@terminal_lifecycle_state =
|
|
380
385
|
(terminal_type == :halted) ? :halted : :completed
|
|
386
|
+
# Keep the error path open until the observer returns successfully.
|
|
381
387
|
notify_stable_state! if @pending_terminal_notify_stable
|
|
388
|
+
@done = true
|
|
382
389
|
post_terminal_event(terminal_type, @ctx)
|
|
383
390
|
end
|
|
384
391
|
|
|
@@ -400,7 +407,7 @@ module Phronomy
|
|
|
400
407
|
)
|
|
401
408
|
return if accepted
|
|
402
409
|
|
|
403
|
-
Phronomy.
|
|
410
|
+
Phronomy::RuntimeSettings.current.logger&.warn(
|
|
404
411
|
"[Phronomy::FSMSession] EventLoop rejected recovery-required event for #{@id}"
|
|
405
412
|
)
|
|
406
413
|
end
|
|
@@ -415,7 +422,7 @@ module Phronomy
|
|
|
415
422
|
)
|
|
416
423
|
return if accepted
|
|
417
424
|
|
|
418
|
-
Phronomy.
|
|
425
|
+
Phronomy::RuntimeSettings.current.logger&.warn(
|
|
419
426
|
"[Phronomy::FSMSession] EventLoop rejected terminal event " \
|
|
420
427
|
"#{type.inspect} for #{@id}"
|
|
421
428
|
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Runtime observability snapshot for the two concurrency boundaries:
|
|
5
|
+
# EventLoop and the default OffloadPool. Observation never creates resources;
|
|
6
|
+
# missing resources contribute zero values. Individual counters are sampled
|
|
7
|
+
# independently, not as an atomic view of concurrent execution.
|
|
8
|
+
module Metrics
|
|
9
|
+
def self.snapshot
|
|
10
|
+
runtime = Runtime.__default_if_initialized
|
|
11
|
+
pool = runtime&.__offload_if_initialized
|
|
12
|
+
event_loop = runtime&.__event_loop_if_initialized
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
offload_pool_active: pool&.active_count || 0,
|
|
16
|
+
offload_pool_queue_length: pool&.queue_depth || 0,
|
|
17
|
+
offload_pool_abandoned_active: pool&.abandoned_active_count || 0,
|
|
18
|
+
offload_pool_abandoned_total: pool&.abandoned_count || 0,
|
|
19
|
+
offload_pool_size: pool&.pool_size || 0,
|
|
20
|
+
event_loop_queue_depth: event_loop&.queue_depth || 0,
|
|
21
|
+
event_loop_queue_max_depth: event_loop&.max_queue_depth || 0,
|
|
22
|
+
event_loop_lag_last_ms: ((event_loop&.last_lag_seconds || 0.0) * 1000).round(3),
|
|
23
|
+
event_loop_lag_max_ms: ((event_loop&.max_lag_seconds || 0.0) * 1000).round(3),
|
|
24
|
+
event_loop_lag_average_ms: ((event_loop&.average_lag_seconds || 0.0) * 1000).round(3)
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -68,7 +68,7 @@ module Phronomy
|
|
|
68
68
|
callbacks.each do |callback|
|
|
69
69
|
callback.call
|
|
70
70
|
rescue => error
|
|
71
|
-
Phronomy.
|
|
71
|
+
Phronomy::RuntimeSettings.current.logger&.error do
|
|
72
72
|
"[TimerQueue] callback raised #{error.class}: #{error.message}"
|
|
73
73
|
end
|
|
74
74
|
end
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../execution/concurrency/worker_input_restricted"
|
|
4
|
+
|
|
3
5
|
require_relative "runtime/timer_queue"
|
|
4
6
|
require_relative "runtime/shutdown_result"
|
|
5
7
|
require_relative "runtime/timer_service"
|
|
6
|
-
require_relative "runtime/agent_ownership_registry"
|
|
7
|
-
require_relative "runtime/team_ownership_registry"
|
|
8
8
|
|
|
9
9
|
module Phronomy
|
|
10
10
|
class Runtime
|
|
11
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
12
|
+
|
|
11
13
|
@instance_mutex = Mutex.new
|
|
12
14
|
|
|
13
15
|
class << self
|
|
@@ -15,10 +17,16 @@ module Phronomy
|
|
|
15
17
|
instance_mutex.synchronize { @instance ||= new }
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
# Lookup only: diagnostics must not initialize the default Runtime.
|
|
21
|
+
# @api private
|
|
22
|
+
def __default_if_initialized
|
|
19
23
|
instance_mutex.synchronize { @instance }
|
|
20
24
|
end
|
|
21
25
|
|
|
26
|
+
def default_if_initialized_for_test
|
|
27
|
+
__default_if_initialized
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
def replace_default_for_test(runtime)
|
|
23
31
|
instance_mutex.synchronize do
|
|
24
32
|
previous = @instance
|
|
@@ -31,7 +39,7 @@ module Phronomy
|
|
|
31
39
|
instance_mutex.synchronize { @instance = runtime }
|
|
32
40
|
end
|
|
33
41
|
|
|
34
|
-
def reset_default!(timeout: Phronomy.
|
|
42
|
+
def reset_default!(timeout: Phronomy::RuntimeSettings.current.event_loop_stop_grace_seconds)
|
|
35
43
|
runtime = instance_mutex.synchronize { @instance }
|
|
36
44
|
return ShutdownResult.not_started unless runtime
|
|
37
45
|
|
|
@@ -48,8 +56,7 @@ module Phronomy
|
|
|
48
56
|
end
|
|
49
57
|
|
|
50
58
|
def in_event_loop_context?
|
|
51
|
-
|
|
52
|
-
runtime&.event_loop_current? || false
|
|
59
|
+
Phronomy::WaitPolicy.blocking_forbidden?
|
|
53
60
|
end
|
|
54
61
|
|
|
55
62
|
private
|
|
@@ -64,9 +71,8 @@ module Phronomy
|
|
|
64
71
|
@pool_registry = Phronomy::Concurrency::PoolRegistry.new(
|
|
65
72
|
timer_queue_provider: -> { timer_queue }
|
|
66
73
|
)
|
|
67
|
-
@
|
|
68
|
-
@
|
|
69
|
-
@team_ownership_registry = TeamOwnershipRegistry.new
|
|
74
|
+
@shutdown_participants = {}
|
|
75
|
+
@shutdown_participant_error = nil
|
|
70
76
|
@lifecycle_mutex = Mutex.new
|
|
71
77
|
@shutdown_mutex = Mutex.new
|
|
72
78
|
@state = :running
|
|
@@ -80,8 +86,8 @@ module Phronomy
|
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def offload(
|
|
83
|
-
pool_size: Phronomy.
|
|
84
|
-
queue_size: Phronomy.
|
|
89
|
+
pool_size: Phronomy::RuntimeSettings.current.offload_pool_size,
|
|
90
|
+
queue_size: Phronomy::RuntimeSettings.current.offload_queue_size
|
|
85
91
|
)
|
|
86
92
|
ensure_accepting_work!
|
|
87
93
|
@pool_registry.default_pool(pool_size: pool_size, queue_size: queue_size)
|
|
@@ -92,6 +98,12 @@ module Phronomy
|
|
|
92
98
|
@pool_registry.named_pool(name, size: size, queue_size: queue_size)
|
|
93
99
|
end
|
|
94
100
|
|
|
101
|
+
# Lookup only; retained pools remain observable during and after shutdown.
|
|
102
|
+
# @api private
|
|
103
|
+
def __offload_if_initialized
|
|
104
|
+
@pool_registry.default_pool_if_initialized
|
|
105
|
+
end
|
|
106
|
+
|
|
95
107
|
def timer_queue
|
|
96
108
|
ensure_accepting_work!
|
|
97
109
|
timer = @timer_service.timer_queue
|
|
@@ -103,83 +115,40 @@ module Phronomy
|
|
|
103
115
|
@timer_service.timer_queue
|
|
104
116
|
end
|
|
105
117
|
|
|
106
|
-
#
|
|
107
|
-
# execution state remains inside EventLoop and is never exposed to callers.
|
|
108
|
-
# @api private
|
|
109
|
-
def __agent_execution_owner(execution_id)
|
|
110
|
-
loop_instance = @lifecycle_mutex.synchronize { @event_loop }
|
|
111
|
-
loop_instance&.agent_execution_owner(execution_id)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# @api private
|
|
115
|
-
def __team_owner(id, klass:, create:, persistence:, &block)
|
|
116
|
-
@team_ownership_registry.fetch(id, klass: klass, create: create, persistence: persistence, &block)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# @api private
|
|
120
|
-
def __get_team(id, klass:)
|
|
121
|
-
@team_ownership_registry.get(id.to_s, klass: klass)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# @api private
|
|
125
|
-
def __create_agent(agent_id, expected_class:, &block)
|
|
126
|
-
@agent_ownership_registry.create(agent_id, expected_class: expected_class, &block)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# @api private
|
|
130
|
-
def __load_agent(agent_id, expected_class:, &block)
|
|
131
|
-
@agent_ownership_registry.load(agent_id, expected_class: expected_class, &block)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# @api private
|
|
135
|
-
def __get_agent(agent_id, expected_class:)
|
|
136
|
-
@agent_ownership_registry.get(agent_id, expected_class: expected_class)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# @api private
|
|
140
|
-
def __agent_owned?(agent)
|
|
141
|
-
@agent_ownership_registry.owned?(agent)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# @api private
|
|
145
|
-
def __begin_agent_purge(agent)
|
|
146
|
-
@agent_ownership_registry.begin_purge(agent)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# @api private
|
|
150
|
-
def __complete_agent_purge(agent, token)
|
|
151
|
-
@agent_ownership_registry.complete_purge(agent, token)
|
|
152
|
-
end
|
|
153
|
-
|
|
118
|
+
# Lookup only; inspection must not create an EventLoop during shutdown.
|
|
154
119
|
# @api private
|
|
155
|
-
def
|
|
156
|
-
@
|
|
120
|
+
def __event_loop_if_initialized
|
|
121
|
+
@lifecycle_mutex.synchronize { @event_loop }
|
|
157
122
|
end
|
|
158
123
|
|
|
124
|
+
# Shares one internal participant per key for this Runtime's lifetime.
|
|
125
|
+
# begin_draining must be idempotent, nonblocking and must not call Runtime:
|
|
126
|
+
# admission closes under the same lifecycle lock as registration.
|
|
127
|
+
# wait_until_idle receives an absolute monotonic deadline and returns a Boolean.
|
|
128
|
+
# Optional after_runtime_shutdown runs outside the lifecycle lock only after
|
|
129
|
+
# all waits succeed, EventLoop stops, and pools/timer finish shutdown. It must
|
|
130
|
+
# be idempotent, short, and perform no I/O. Exceptions mark cleanup incomplete.
|
|
131
|
+
# This is a shutdown contract, not an application extension point.
|
|
159
132
|
# @api private
|
|
160
|
-
def
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
# @api private
|
|
165
|
-
def __agent_execution_admitted?(agent_id)
|
|
166
|
-
loop_instance = @lifecycle_mutex.synchronize { @event_loop }
|
|
167
|
-
loop_instance&.agent_execution_admitted?(agent_id) || false
|
|
168
|
-
end
|
|
133
|
+
def __register_shutdown_participant(key:, participant:)
|
|
134
|
+
unless participant.respond_to?(:begin_draining) && participant.respond_to?(:wait_until_idle)
|
|
135
|
+
raise ArgumentError, "shutdown participant must implement begin_draining and wait_until_idle"
|
|
136
|
+
end
|
|
169
137
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
138
|
+
@lifecycle_mutex.synchronize do
|
|
139
|
+
unless @state == :running
|
|
140
|
+
raise Phronomy::RuntimeShutdownError,
|
|
141
|
+
"Runtime is #{@state}; shutdown participants cannot be registered"
|
|
142
|
+
end
|
|
143
|
+
@shutdown_participants[key] ||= participant
|
|
176
144
|
end
|
|
177
|
-
@multi_agent_admissions.admit!(coordinator)
|
|
178
145
|
end
|
|
179
146
|
|
|
147
|
+
# Lookup only: never registers a participant or reopens its admission gate.
|
|
148
|
+
# Retained participants remain available during and after shutdown.
|
|
180
149
|
# @api private
|
|
181
|
-
def
|
|
182
|
-
@
|
|
150
|
+
def __shutdown_participant(key:)
|
|
151
|
+
@lifecycle_mutex.synchronize { @shutdown_participants[key] }
|
|
183
152
|
end
|
|
184
153
|
|
|
185
154
|
def event_loop
|
|
@@ -214,11 +183,12 @@ module Phronomy
|
|
|
214
183
|
|
|
215
184
|
@failure ||= error
|
|
216
185
|
@state = :failed
|
|
186
|
+
begin_draining_participants
|
|
217
187
|
end
|
|
218
188
|
end
|
|
219
189
|
|
|
220
190
|
def shutdown(
|
|
221
|
-
timeout: Phronomy.
|
|
191
|
+
timeout: Phronomy::RuntimeSettings.current.event_loop_stop_grace_seconds,
|
|
222
192
|
cancel_grace: timeout
|
|
223
193
|
)
|
|
224
194
|
if event_loop_current?
|
|
@@ -232,17 +202,14 @@ module Phronomy
|
|
|
232
202
|
return @shutdown_result if @shutdown_result
|
|
233
203
|
|
|
234
204
|
drain_deadline = monotonic_now + timeout
|
|
235
|
-
loop_instance = @lifecycle_mutex.synchronize do
|
|
205
|
+
loop_instance, participants = @lifecycle_mutex.synchronize do
|
|
236
206
|
@state = :draining unless @state == :failed
|
|
237
|
-
|
|
207
|
+
begin_draining_participants
|
|
208
|
+
[@event_loop, @shutdown_participants.values]
|
|
238
209
|
end
|
|
239
210
|
loop_instance&.begin_draining
|
|
240
|
-
@agent_ownership_registry.begin_draining
|
|
241
|
-
@team_ownership_registry.begin_draining
|
|
242
211
|
|
|
243
|
-
|
|
244
|
-
agent_ownership_stable = @agent_ownership_registry.wait_until_stable(drain_deadline)
|
|
245
|
-
team_ownership_stable = @team_ownership_registry.wait_until_stable(drain_deadline)
|
|
212
|
+
participants_idle = wait_for_participants(participants, drain_deadline)
|
|
246
213
|
loop_idle = !loop_instance || loop_instance.wait_until_idle(drain_deadline)
|
|
247
214
|
|
|
248
215
|
@lifecycle_mutex.synchronize do
|
|
@@ -256,12 +223,17 @@ module Phronomy
|
|
|
256
223
|
:not_started
|
|
257
224
|
end
|
|
258
225
|
|
|
259
|
-
subsystem_error = shutdown_pools_and_timer
|
|
260
|
-
|
|
226
|
+
pools_stopped, subsystem_error = shutdown_pools_and_timer(deadline: stop_deadline)
|
|
227
|
+
participant_error = @lifecycle_mutex.synchronize { @shutdown_participant_error }
|
|
228
|
+
cleanup_complete = participants_idle && participant_error.nil? &&
|
|
229
|
+
loop_idle &&
|
|
261
230
|
(!loop_instance || !loop_instance.thread_alive?) &&
|
|
262
231
|
event_loop_status != :cancel_timeout &&
|
|
232
|
+
(!loop_instance || loop_instance.__receiver_cleanup_complete?) &&
|
|
233
|
+
pools_stopped &&
|
|
263
234
|
subsystem_error.nil?
|
|
264
235
|
|
|
236
|
+
cleanup_complete = finalize_participants(participants) if cleanup_complete
|
|
265
237
|
failure = @lifecycle_mutex.synchronize { @failure } || subsystem_error
|
|
266
238
|
runtime_outcome = if failure || event_loop_status == :failed
|
|
267
239
|
:failed
|
|
@@ -269,9 +241,6 @@ module Phronomy
|
|
|
269
241
|
:terminated
|
|
270
242
|
end
|
|
271
243
|
|
|
272
|
-
@agent_ownership_registry.shutdown! if cleanup_complete
|
|
273
|
-
@team_ownership_registry.shutdown! if cleanup_complete
|
|
274
|
-
|
|
275
244
|
result = ShutdownResult.new(
|
|
276
245
|
runtime_outcome: runtime_outcome,
|
|
277
246
|
cleanup_status: cleanup_complete ? :complete : :incomplete,
|
|
@@ -290,6 +259,45 @@ module Phronomy
|
|
|
290
259
|
|
|
291
260
|
private
|
|
292
261
|
|
|
262
|
+
# Called with the lifecycle lock held; participants only close their gates.
|
|
263
|
+
def begin_draining_participants
|
|
264
|
+
@shutdown_participants.each_value do |participant|
|
|
265
|
+
participant.begin_draining
|
|
266
|
+
rescue => error
|
|
267
|
+
record_participant_failure(error)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# All gates are already closed. Wait without holding the lifecycle lock and
|
|
272
|
+
# give every participant the same deadline, even if an earlier wait fails.
|
|
273
|
+
def wait_for_participants(participants, deadline)
|
|
274
|
+
participants.map do |participant|
|
|
275
|
+
participant.wait_until_idle(deadline) == true
|
|
276
|
+
rescue => error
|
|
277
|
+
@lifecycle_mutex.synchronize { record_participant_failure(error) }
|
|
278
|
+
false
|
|
279
|
+
end.all?
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Finalize every participant even if one fails. Earlier releases are not
|
|
283
|
+
# rolled back, and a failure prevents replacement of the default Runtime.
|
|
284
|
+
def finalize_participants(participants)
|
|
285
|
+
participants.map do |participant|
|
|
286
|
+
participant.after_runtime_shutdown if participant.respond_to?(:after_runtime_shutdown)
|
|
287
|
+
true
|
|
288
|
+
rescue => error
|
|
289
|
+
@lifecycle_mutex.synchronize { record_participant_failure(error) }
|
|
290
|
+
false
|
|
291
|
+
end.all?
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Called with the lifecycle lock held. Failed hooks make cleanup uncertain.
|
|
295
|
+
def record_participant_failure(error)
|
|
296
|
+
@shutdown_participant_error ||= error
|
|
297
|
+
@failure ||= error
|
|
298
|
+
@state = :failed
|
|
299
|
+
end
|
|
300
|
+
|
|
293
301
|
def ensure_accepting_work!
|
|
294
302
|
current_state = @lifecycle_mutex.synchronize { @state }
|
|
295
303
|
return if %i[running draining].include?(current_state)
|
|
@@ -298,10 +306,11 @@ module Phronomy
|
|
|
298
306
|
"Runtime is #{current_state}; new work is not accepted"
|
|
299
307
|
end
|
|
300
308
|
|
|
301
|
-
def shutdown_pools_and_timer
|
|
309
|
+
def shutdown_pools_and_timer(deadline:)
|
|
302
310
|
error = nil
|
|
311
|
+
pools_stopped = false
|
|
303
312
|
begin
|
|
304
|
-
@pool_registry.shutdown
|
|
313
|
+
pools_stopped = @pool_registry.shutdown(deadline: deadline)
|
|
305
314
|
rescue => caught
|
|
306
315
|
error ||= caught
|
|
307
316
|
ensure
|
|
@@ -311,7 +320,7 @@ module Phronomy
|
|
|
311
320
|
error ||= caught
|
|
312
321
|
end
|
|
313
322
|
end
|
|
314
|
-
error
|
|
323
|
+
[pools_stopped, error]
|
|
315
324
|
end
|
|
316
325
|
|
|
317
326
|
def validate_timeout!(value, name)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Completion adaptation for framework operations. Owns start-error capture,
|
|
5
|
+
# once-only propagation, error-to-value policy and physical completion tracking.
|
|
6
|
+
# Callers supply domain transformations, never completion setters or callbacks.
|
|
7
|
+
# @api private
|
|
8
|
+
module AsyncOperation
|
|
9
|
+
def self.map(source, name:, on_error: nil, &transform)
|
|
10
|
+
raise ArgumentError, "async mapping requires a block" unless transform
|
|
11
|
+
unless source.respond_to?(:on_complete)
|
|
12
|
+
raise TypeError, "asynchronous operation must return a completion handle"
|
|
13
|
+
end
|
|
14
|
+
Concurrency::ResultComposition.new(source, flatten: false, name: name,
|
|
15
|
+
completion_only: true, recover: on_error, &transform).start
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Capture synchronous preparation/start errors under the same domain policy.
|
|
19
|
+
def self.call(name:, on_error: nil, transform: ->(value) { value })
|
|
20
|
+
source = begin
|
|
21
|
+
yield
|
|
22
|
+
rescue => error
|
|
23
|
+
TaskResult.failed(error, name: name)
|
|
24
|
+
end
|
|
25
|
+
map(source, name: name, on_error: on_error, &transform)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Short synchronous fallback/validation only; never offloads or awaits work.
|
|
29
|
+
def self.capture(name:, on_error: nil)
|
|
30
|
+
value = yield
|
|
31
|
+
TaskResult.completed(value, name: name)
|
|
32
|
+
rescue => error
|
|
33
|
+
begin
|
|
34
|
+
raise error unless on_error
|
|
35
|
+
TaskResult.completed(on_error.call(error), name: name)
|
|
36
|
+
rescue => failure
|
|
37
|
+
TaskResult.failed(failure, name: name)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|