phronomy 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +71 -0
- data/CONTRIBUTING.md +4 -4
- data/README.md +14 -7
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -22
- data/benchmark/bench_context_assembler.rb +4 -5
- data/benchmark/bench_regression.rb +1 -1
- data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
- data/docs/architecture/agent-context.md +5 -3
- data/docs/architecture/agent-transition-ownership.md +89 -0
- data/docs/architecture/before-llm-input.md +6 -0
- data/docs/architecture/context-management.md +30 -0
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-metadata-and-values.md +80 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +8 -2
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +29 -6
- data/docs/architecture/refactoring-closure.md +334 -0
- data/docs/architecture/remaining-refactoring-plan.md +374 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/tool-schema-recording-gap.md +50 -0
- data/docs/architecture/tracing.md +4 -4
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +209 -0
- data/docs/async-composition.md +205 -0
- data/docs/decisions/010-cooperative-first-concurrency.md +23 -23
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- 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/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
- data/docs/decisions/032-storage-backend-composition.md +80 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -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 +131 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
- data/docs/decisions/040-configuration-default-composition.md +94 -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 +100 -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 +151 -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 +78 -0
- data/docs/decisions/README.md +35 -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/features.md +21 -12
- data/docs/getting-started.md +10 -10
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +7 -1
- data/docs/migrations/handoff-runner-multi-agent.md +35 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/persistence-backends.md +162 -204
- data/docs/runtime-and-concurrency.md +205 -60
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/agent/async_event_api.rb +2 -2
- data/lib/phronomy/agent/base.rb +55 -251
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context/capability/base.rb +67 -26
- data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
- data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
- data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
- data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
- data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
- data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
- data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
- data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
- data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
- data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
- data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +20 -96
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +12 -13
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
- data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +22 -73
- data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
- data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
- 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/{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 -10
- data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
- data/lib/phronomy/agent/persistence/codec.rb +358 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
- data/lib/phronomy/agent/persistence/queries.rb +61 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
- data/lib/phronomy/agent/persistence/watermark.rb +27 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +36 -41
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +9 -13
- data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
- data/lib/phronomy/agent/selection/candidate.rb +1 -1
- 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 +90 -0
- data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
- data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
- data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +2 -2
- 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.rb → configuration/configuration.rb} +14 -5
- data/lib/phronomy/configuration/global_configuration.rb +26 -0
- data/lib/phronomy/content_store/storage_schema.rb +11 -0
- data/lib/phronomy/content_store/stored_contents.rb +43 -0
- data/lib/phronomy/engine/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → engine/blocking.rb} +15 -8
- data/lib/phronomy/engine/cancellation_error.rb +7 -0
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +29 -16
- data/lib/phronomy/engine/concurrency/operation_binding.rb +43 -0
- data/lib/phronomy/engine/concurrency/physical_completion_task.rb +4 -62
- data/lib/phronomy/engine/concurrency/result_collector.rb +99 -0
- data/lib/phronomy/engine/concurrency/result_composition.rb +145 -0
- data/lib/phronomy/engine/concurrency/subscriptions.rb +68 -0
- data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/engine/event_loop.rb +160 -626
- data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/engine/execution.rb +229 -0
- data/lib/phronomy/engine/execution_cancellation_error.rb +14 -0
- data/lib/phronomy/engine/execution_receiver.rb +65 -0
- data/lib/phronomy/engine/execution_timeout_error.rb +14 -0
- data/lib/phronomy/engine/fsm_protocol.rb +14 -0
- data/lib/phronomy/engine/fsm_session.rb +38 -31
- data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +3 -1
- data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +3 -1
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +3 -1
- data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +13 -1
- data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
- data/lib/phronomy/engine/runtime/timer_queue.rb +11 -0
- data/lib/phronomy/engine/runtime.rb +80 -84
- data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/engine/{task.rb → task_result.rb} +101 -42
- data/lib/phronomy/engine/timeout_error.rb +7 -0
- data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -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/base.rb +2 -2
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
- data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
- data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
- data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
- data/lib/phronomy/llm_contract/transport_error.rb +7 -0
- data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +8 -8
- data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +36 -68
- data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
- data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
- data/lib/phronomy/persistence/api/persistence.rb +140 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
- data/lib/phronomy/persistence_composition/repositories.rb +77 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/storage/backend.rb +101 -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/scorer/llm_judge.rb +5 -3
- data/lib/phronomy/testing/fake_clock.rb +13 -9
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/contract/tool_error.rb +7 -0
- data/lib/phronomy/tools/agent.rb +6 -6
- data/lib/phronomy/vector_store/async_backend.rb +5 -5
- data/lib/phronomy/vector_store/embeddings/base.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +126 -75
- data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
- data/lib/phronomy/workflow/phase_machine_builder.rb +8 -8
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
- data/lib/phronomy.rb +52 -94
- data/scripts/api_snapshot.rb +4 -2
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/phronomy/agent.rbs +4 -5
- data/sig/phronomy/execution_receiver.rbs +34 -0
- data/sig/phronomy/extensions.rbs +5 -5
- data/sig/phronomy/handoff.rbs +4 -2
- data/sig/phronomy/multi_agent.rbs +19 -3
- data/sig/phronomy/persistence.rbs +7 -91
- data/sig/phronomy/runtime.rbs +34 -7
- data/sig/phronomy/storage.rbs +174 -0
- data/sig/phronomy/tool.rbs +11 -2
- data/sig/phronomy/workflow.rbs +1 -1
- metadata +236 -99
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3138
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_executor.rb +0 -55
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +0 -137
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +0 -118
- 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/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/workflow_recovery.rb +0 -123
- /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
- /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
- /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
- /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "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
|
|
@@ -64,9 +66,8 @@ module Phronomy
|
|
|
64
66
|
@pool_registry = Phronomy::Concurrency::PoolRegistry.new(
|
|
65
67
|
timer_queue_provider: -> { timer_queue }
|
|
66
68
|
)
|
|
67
|
-
@
|
|
68
|
-
@
|
|
69
|
-
@team_ownership_registry = TeamOwnershipRegistry.new
|
|
69
|
+
@shutdown_participants = {}
|
|
70
|
+
@shutdown_participant_error = nil
|
|
70
71
|
@lifecycle_mutex = Mutex.new
|
|
71
72
|
@shutdown_mutex = Mutex.new
|
|
72
73
|
@state = :running
|
|
@@ -103,83 +104,40 @@ module Phronomy
|
|
|
103
104
|
@timer_service.timer_queue
|
|
104
105
|
end
|
|
105
106
|
|
|
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
|
-
|
|
154
|
-
# @api private
|
|
155
|
-
def __abort_agent_purge(agent, token)
|
|
156
|
-
@agent_ownership_registry.abort_purge(agent, token)
|
|
157
|
-
end
|
|
158
|
-
|
|
107
|
+
# Lookup only; inspection must not create an EventLoop during shutdown.
|
|
159
108
|
# @api private
|
|
160
|
-
def
|
|
161
|
-
@
|
|
109
|
+
def __event_loop_if_initialized
|
|
110
|
+
@lifecycle_mutex.synchronize { @event_loop }
|
|
162
111
|
end
|
|
163
112
|
|
|
113
|
+
# Shares one internal participant per key for this Runtime's lifetime.
|
|
114
|
+
# begin_draining must be idempotent, nonblocking and must not call Runtime:
|
|
115
|
+
# admission closes under the same lifecycle lock as registration.
|
|
116
|
+
# wait_until_idle receives an absolute monotonic deadline and returns a Boolean.
|
|
117
|
+
# Optional after_runtime_shutdown runs outside the lifecycle lock only after
|
|
118
|
+
# all waits succeed, EventLoop stops, and pools/timer finish shutdown. It must
|
|
119
|
+
# be idempotent, short, and perform no I/O. Exceptions mark cleanup incomplete.
|
|
120
|
+
# This is a shutdown contract, not an application extension point.
|
|
164
121
|
# @api private
|
|
165
|
-
def
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
122
|
+
def __register_shutdown_participant(key:, participant:)
|
|
123
|
+
unless participant.respond_to?(:begin_draining) && participant.respond_to?(:wait_until_idle)
|
|
124
|
+
raise ArgumentError, "shutdown participant must implement begin_draining and wait_until_idle"
|
|
125
|
+
end
|
|
169
126
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
127
|
+
@lifecycle_mutex.synchronize do
|
|
128
|
+
unless @state == :running
|
|
129
|
+
raise Phronomy::RuntimeShutdownError,
|
|
130
|
+
"Runtime is #{@state}; shutdown participants cannot be registered"
|
|
131
|
+
end
|
|
132
|
+
@shutdown_participants[key] ||= participant
|
|
176
133
|
end
|
|
177
|
-
@multi_agent_admissions.admit!(coordinator)
|
|
178
134
|
end
|
|
179
135
|
|
|
136
|
+
# Lookup only: never registers a participant or reopens its admission gate.
|
|
137
|
+
# Retained participants remain available during and after shutdown.
|
|
180
138
|
# @api private
|
|
181
|
-
def
|
|
182
|
-
@
|
|
139
|
+
def __shutdown_participant(key:)
|
|
140
|
+
@lifecycle_mutex.synchronize { @shutdown_participants[key] }
|
|
183
141
|
end
|
|
184
142
|
|
|
185
143
|
def event_loop
|
|
@@ -214,6 +172,7 @@ module Phronomy
|
|
|
214
172
|
|
|
215
173
|
@failure ||= error
|
|
216
174
|
@state = :failed
|
|
175
|
+
begin_draining_participants
|
|
217
176
|
end
|
|
218
177
|
end
|
|
219
178
|
|
|
@@ -232,17 +191,14 @@ module Phronomy
|
|
|
232
191
|
return @shutdown_result if @shutdown_result
|
|
233
192
|
|
|
234
193
|
drain_deadline = monotonic_now + timeout
|
|
235
|
-
loop_instance = @lifecycle_mutex.synchronize do
|
|
194
|
+
loop_instance, participants = @lifecycle_mutex.synchronize do
|
|
236
195
|
@state = :draining unless @state == :failed
|
|
237
|
-
|
|
196
|
+
begin_draining_participants
|
|
197
|
+
[@event_loop, @shutdown_participants.values]
|
|
238
198
|
end
|
|
239
199
|
loop_instance&.begin_draining
|
|
240
|
-
@agent_ownership_registry.begin_draining
|
|
241
|
-
@team_ownership_registry.begin_draining
|
|
242
200
|
|
|
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)
|
|
201
|
+
participants_idle = wait_for_participants(participants, drain_deadline)
|
|
246
202
|
loop_idle = !loop_instance || loop_instance.wait_until_idle(drain_deadline)
|
|
247
203
|
|
|
248
204
|
@lifecycle_mutex.synchronize do
|
|
@@ -257,11 +213,15 @@ module Phronomy
|
|
|
257
213
|
end
|
|
258
214
|
|
|
259
215
|
subsystem_error = shutdown_pools_and_timer
|
|
260
|
-
|
|
216
|
+
participant_error = @lifecycle_mutex.synchronize { @shutdown_participant_error }
|
|
217
|
+
cleanup_complete = participants_idle && participant_error.nil? &&
|
|
218
|
+
loop_idle &&
|
|
261
219
|
(!loop_instance || !loop_instance.thread_alive?) &&
|
|
262
220
|
event_loop_status != :cancel_timeout &&
|
|
221
|
+
(!loop_instance || loop_instance.__receiver_cleanup_complete?) &&
|
|
263
222
|
subsystem_error.nil?
|
|
264
223
|
|
|
224
|
+
cleanup_complete = finalize_participants(participants) if cleanup_complete
|
|
265
225
|
failure = @lifecycle_mutex.synchronize { @failure } || subsystem_error
|
|
266
226
|
runtime_outcome = if failure || event_loop_status == :failed
|
|
267
227
|
:failed
|
|
@@ -269,9 +229,6 @@ module Phronomy
|
|
|
269
229
|
:terminated
|
|
270
230
|
end
|
|
271
231
|
|
|
272
|
-
@agent_ownership_registry.shutdown! if cleanup_complete
|
|
273
|
-
@team_ownership_registry.shutdown! if cleanup_complete
|
|
274
|
-
|
|
275
232
|
result = ShutdownResult.new(
|
|
276
233
|
runtime_outcome: runtime_outcome,
|
|
277
234
|
cleanup_status: cleanup_complete ? :complete : :incomplete,
|
|
@@ -290,6 +247,45 @@ module Phronomy
|
|
|
290
247
|
|
|
291
248
|
private
|
|
292
249
|
|
|
250
|
+
# Called with the lifecycle lock held; participants only close their gates.
|
|
251
|
+
def begin_draining_participants
|
|
252
|
+
@shutdown_participants.each_value do |participant|
|
|
253
|
+
participant.begin_draining
|
|
254
|
+
rescue => error
|
|
255
|
+
record_participant_failure(error)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# All gates are already closed. Wait without holding the lifecycle lock and
|
|
260
|
+
# give every participant the same deadline, even if an earlier wait fails.
|
|
261
|
+
def wait_for_participants(participants, deadline)
|
|
262
|
+
participants.map do |participant|
|
|
263
|
+
participant.wait_until_idle(deadline) == true
|
|
264
|
+
rescue => error
|
|
265
|
+
@lifecycle_mutex.synchronize { record_participant_failure(error) }
|
|
266
|
+
false
|
|
267
|
+
end.all?
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Finalize every participant even if one fails. Earlier releases are not
|
|
271
|
+
# rolled back, and a failure prevents replacement of the default Runtime.
|
|
272
|
+
def finalize_participants(participants)
|
|
273
|
+
participants.map do |participant|
|
|
274
|
+
participant.after_runtime_shutdown if participant.respond_to?(:after_runtime_shutdown)
|
|
275
|
+
true
|
|
276
|
+
rescue => error
|
|
277
|
+
@lifecycle_mutex.synchronize { record_participant_failure(error) }
|
|
278
|
+
false
|
|
279
|
+
end.all?
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Called with the lifecycle lock held. Failed hooks make cleanup uncertain.
|
|
283
|
+
def record_participant_failure(error)
|
|
284
|
+
@shutdown_participant_error ||= error
|
|
285
|
+
@failure ||= error
|
|
286
|
+
@state = :failed
|
|
287
|
+
end
|
|
288
|
+
|
|
293
289
|
def ensure_accepting_work!
|
|
294
290
|
current_state = @lifecycle_mutex.synchronize { @state }
|
|
295
291
|
return if %i[running draining].include?(current_state)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "event_loop_reentrancy_error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
# Backward-compatible error class name for callers that still rescue the old
|
|
7
|
+
# scheduler-oriented exception. New code should use EventLoopReentrancyError.
|
|
8
|
+
class SchedulerReentrancyError < EventLoopReentrancyError; end
|
|
9
|
+
end
|
|
@@ -1,22 +1,60 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "concurrency/worker_input_restricted"
|
|
4
|
+
|
|
3
5
|
module Phronomy
|
|
4
6
|
# A thread-free asynchronous completion handle.
|
|
5
7
|
#
|
|
6
|
-
#
|
|
7
|
-
# OffloadPool.
|
|
8
|
+
# TaskResult does not execute work. Execution belongs to EventLoop/FSMSession or
|
|
9
|
+
# OffloadPool. TaskResult represents completion, failure, cancellation, callbacks,
|
|
8
10
|
# and a blocking wait for callers outside EventLoop.
|
|
9
11
|
#
|
|
10
|
-
# Framework components own
|
|
11
|
-
#
|
|
12
|
+
# Framework components own TaskResult settlement. Application code should observe a
|
|
13
|
+
# TaskResult through {#wait_result}, {#on_complete}, {#map}, and state readers rather
|
|
12
14
|
# than calling {#complete}, {#fail}, or {#cancel!}. Operation-wide cancellation
|
|
13
15
|
# is supplied through the CancellationToken accepted by the API that created
|
|
14
|
-
# the
|
|
15
|
-
class
|
|
16
|
+
# the TaskResult.
|
|
17
|
+
class TaskResult
|
|
18
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
19
|
+
|
|
16
20
|
STATES = %i[pending completed failed cancelled].freeze
|
|
17
21
|
TERMINAL_STATES = %i[completed failed cancelled].freeze
|
|
18
22
|
private_constant :TERMINAL_STATES
|
|
19
23
|
|
|
24
|
+
# A shallow immutable input-position record. Application values are retained
|
|
25
|
+
# by reference; they are neither copied nor frozen by this record.
|
|
26
|
+
# @api public
|
|
27
|
+
class Outcome < Struct.new(:index, :status, :value, :error)
|
|
28
|
+
def initialize(**fields)
|
|
29
|
+
super
|
|
30
|
+
freeze
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Observe already-created results without taking ownership of their work.
|
|
35
|
+
# Invalid inputs are rejected before any completion subscription is added.
|
|
36
|
+
# @param results [Array<TaskResult>]
|
|
37
|
+
# @return [TaskResult<Array<Outcome>>]
|
|
38
|
+
# @raise [TypeError] for a non-Array list or non-TaskResult element
|
|
39
|
+
# @api public
|
|
40
|
+
def self.all_settled(results)
|
|
41
|
+
raise TypeError, "results must be an Array" unless results.is_a?(Array)
|
|
42
|
+
|
|
43
|
+
inputs = results.dup
|
|
44
|
+
inputs.each_with_index do |result, index|
|
|
45
|
+
unless result.is_a?(Phronomy::TaskResult)
|
|
46
|
+
raise TypeError, "results[#{index}] must be a Phronomy::TaskResult"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
combined = Phronomy::TaskResult.deferred(name: "all-settled")
|
|
50
|
+
collector = Concurrency::ResultCollector.new(inputs.length) do |_, outcomes|
|
|
51
|
+
combined.complete(outcomes)
|
|
52
|
+
end
|
|
53
|
+
inputs.each_with_index { |result, index| collector.watch(index, result) }
|
|
54
|
+
collector.finish(:completed)
|
|
55
|
+
combined
|
|
56
|
+
end
|
|
57
|
+
|
|
20
58
|
# Creates an unsettled completion handle for framework-owned execution.
|
|
21
59
|
# @api private
|
|
22
60
|
def self.deferred(name: nil, parent: nil)
|
|
@@ -24,26 +62,26 @@ module Phronomy
|
|
|
24
62
|
end
|
|
25
63
|
|
|
26
64
|
# Public factories for already-settled values. No execution is started.
|
|
27
|
-
# Always create a base
|
|
65
|
+
# Always create a base TaskResult: there is no physical worker to supervise.
|
|
28
66
|
# @param value [Object] already available result
|
|
29
67
|
# @param name [String, nil] optional diagnostic name
|
|
30
|
-
# @return [Phronomy::
|
|
68
|
+
# @return [Phronomy::TaskResult] a completed base TaskResult
|
|
31
69
|
# @api public
|
|
32
70
|
def self.completed(value = nil, name: nil)
|
|
33
|
-
Phronomy::
|
|
71
|
+
Phronomy::TaskResult.deferred(name: name).tap { |task| task.complete(value) }
|
|
34
72
|
end
|
|
35
73
|
|
|
36
74
|
# Represents an already known failure without raising the stored error.
|
|
37
75
|
# Observation through wait_result raises it; on_complete receives it.
|
|
38
76
|
# @param error [Exception] original failure object
|
|
39
77
|
# @param name [String, nil] optional diagnostic name
|
|
40
|
-
# @return [Phronomy::
|
|
78
|
+
# @return [Phronomy::TaskResult] a failed base TaskResult
|
|
41
79
|
# @raise [ArgumentError] if error is not an Exception
|
|
42
80
|
# @api public
|
|
43
81
|
def self.failed(error, name: nil)
|
|
44
82
|
raise ArgumentError, "error must be an Exception" unless error.is_a?(Exception)
|
|
45
83
|
|
|
46
|
-
Phronomy::
|
|
84
|
+
Phronomy::TaskResult.deferred(name: name).tap { |task| task.fail(error) }
|
|
47
85
|
end
|
|
48
86
|
|
|
49
87
|
attr_reader :name, :parent
|
|
@@ -68,13 +106,13 @@ module Phronomy
|
|
|
68
106
|
@mutex.synchronize { @status }
|
|
69
107
|
end
|
|
70
108
|
|
|
71
|
-
# @return [Boolean] whether the
|
|
109
|
+
# @return [Boolean] whether the TaskResult has reached a terminal state
|
|
72
110
|
# @api public
|
|
73
111
|
def done?
|
|
74
112
|
@mutex.synchronize { TERMINAL_STATES.include?(@status) }
|
|
75
113
|
end
|
|
76
114
|
|
|
77
|
-
# @return [Boolean] whether the
|
|
115
|
+
# @return [Boolean] whether the TaskResult has not yet reached a terminal state
|
|
78
116
|
# @api public
|
|
79
117
|
def alive?
|
|
80
118
|
!done?
|
|
@@ -82,19 +120,19 @@ module Phronomy
|
|
|
82
120
|
|
|
83
121
|
# Blocks the calling thread until settlement.
|
|
84
122
|
#
|
|
85
|
-
# EventLoop is never allowed to wait for a
|
|
123
|
+
# EventLoop is never allowed to wait for a TaskResult; framework continuation must
|
|
86
124
|
# proceed through explicit events. The optional timeout is waiter-local: it
|
|
87
|
-
# does not settle or cancel the
|
|
125
|
+
# does not settle or cancel the TaskResult.
|
|
88
126
|
#
|
|
89
127
|
# @param timeout [Numeric, nil] maximum seconds this caller will block
|
|
90
128
|
# @return [Object] the completed value
|
|
91
129
|
# @raise [Phronomy::TimeoutError] when the waiter-local timeout expires
|
|
92
|
-
# @raise [Exception] the error that settled the
|
|
130
|
+
# @raise [Exception] the error that settled the TaskResult
|
|
93
131
|
# @api public
|
|
94
132
|
def wait_result(timeout: nil)
|
|
95
133
|
if Phronomy::Runtime.in_event_loop_context? && !done?
|
|
96
134
|
raise Phronomy::EventLoopReentrancyError,
|
|
97
|
-
"
|
|
135
|
+
"TaskResult#wait_result cannot block the EventLoop thread; continue via an event"
|
|
98
136
|
end
|
|
99
137
|
|
|
100
138
|
deadline = timeout && monotonic_now + timeout.to_f
|
|
@@ -104,7 +142,7 @@ module Phronomy
|
|
|
104
142
|
remaining = deadline - monotonic_now
|
|
105
143
|
if remaining <= 0
|
|
106
144
|
raise Phronomy::TimeoutError,
|
|
107
|
-
"timed out waiting for
|
|
145
|
+
"timed out waiting for TaskResult #{@name || "(unnamed)"}"
|
|
108
146
|
end
|
|
109
147
|
@cond.wait(@mutex, remaining)
|
|
110
148
|
else
|
|
@@ -118,13 +156,13 @@ module Phronomy
|
|
|
118
156
|
value
|
|
119
157
|
end
|
|
120
158
|
|
|
121
|
-
# Compatibility wait that does not re-raise the
|
|
159
|
+
# Compatibility wait that does not re-raise the TaskResult error.
|
|
122
160
|
# Returns self when settled, nil on timeout.
|
|
123
161
|
# @api private
|
|
124
162
|
def join(limit = nil)
|
|
125
163
|
if Phronomy::Runtime.in_event_loop_context? && !done?
|
|
126
164
|
raise Phronomy::EventLoopReentrancyError,
|
|
127
|
-
"
|
|
165
|
+
"TaskResult#join cannot block the EventLoop thread; continue via an event"
|
|
128
166
|
end
|
|
129
167
|
|
|
130
168
|
deadline = limit && monotonic_now + limit.to_f
|
|
@@ -148,7 +186,7 @@ module Phronomy
|
|
|
148
186
|
# registers after settlement, an OffloadPool worker, or a framework control
|
|
149
187
|
# thread. Callbacks must therefore be thread-safe and should complete quickly.
|
|
150
188
|
# A callback failure is logged and does not suppress delivery to other
|
|
151
|
-
# completion callbacks or change the
|
|
189
|
+
# completion callbacks or change the TaskResult's already-settled result.
|
|
152
190
|
#
|
|
153
191
|
# @yield [value, error]
|
|
154
192
|
# @return [self]
|
|
@@ -168,53 +206,74 @@ module Phronomy
|
|
|
168
206
|
self
|
|
169
207
|
end
|
|
170
208
|
|
|
171
|
-
# Settles this
|
|
209
|
+
# Settles this TaskResult successfully. Framework-owned settlement API.
|
|
172
210
|
# @api private
|
|
173
211
|
def complete(value = nil)
|
|
174
212
|
settle!(:completed, value: value)
|
|
175
213
|
end
|
|
176
214
|
|
|
177
|
-
# Settles this
|
|
215
|
+
# Settles this TaskResult with a failure. Framework-owned settlement API.
|
|
178
216
|
# @api private
|
|
179
217
|
def fail(error)
|
|
180
218
|
raise ArgumentError, "error is required" unless error
|
|
181
219
|
settle!(:failed, error: error)
|
|
182
220
|
end
|
|
183
221
|
|
|
184
|
-
# Settles this
|
|
222
|
+
# Settles this TaskResult as cancelled. Framework-owned settlement API.
|
|
185
223
|
#
|
|
186
224
|
# This method does not propagate backwards into a CancellationToken that may
|
|
187
|
-
# have been used to create the
|
|
225
|
+
# have been used to create the TaskResult. Tokens can be shared across operations;
|
|
188
226
|
# operation-wide cancellation is owned by the creating API.
|
|
189
227
|
# @api private
|
|
190
|
-
def cancel!(error = Phronomy::CancellationError.new("
|
|
228
|
+
def cancel!(error = Phronomy::CancellationError.new("TaskResult cancelled"))
|
|
191
229
|
changed = settle!(:cancelled, error: error)
|
|
192
230
|
if changed
|
|
193
231
|
children = @mutex.synchronize { @children.dup }
|
|
194
|
-
children.each
|
|
232
|
+
children.each { |child| child.cancel!(error) }
|
|
195
233
|
end
|
|
196
234
|
self
|
|
197
235
|
end
|
|
198
236
|
|
|
199
|
-
# Creates a derived
|
|
237
|
+
# Creates a derived TaskResult by transforming this TaskResult's successful result.
|
|
200
238
|
# @api public
|
|
201
239
|
def map(&block)
|
|
202
240
|
raise ArgumentError, "map requires a block" unless block
|
|
203
241
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if error
|
|
207
|
-
mapped.fail(error)
|
|
208
|
-
next
|
|
209
|
-
end
|
|
242
|
+
Concurrency::ResultComposition.new(self, flatten: false, &block).start
|
|
243
|
+
end
|
|
210
244
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
# Transform a successful value into the result of another asynchronous step.
|
|
246
|
+
# The block must return a TaskResult. No implicit wrapping or nested value
|
|
247
|
+
# flattening is performed by map.
|
|
248
|
+
# @return [TaskResult]
|
|
249
|
+
# @api public
|
|
250
|
+
def flat_map(&block)
|
|
251
|
+
raise ArgumentError, "flat_map requires a block" unless block
|
|
252
|
+
|
|
253
|
+
Concurrency::ResultComposition.new(self, flatten: true, &block).start
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Atomic state observation and removable framework subscriptions.
|
|
257
|
+
# @api private
|
|
258
|
+
def __snapshot
|
|
259
|
+
@mutex.synchronize { [@status, @value, @error] }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# @api private
|
|
263
|
+
def __unsubscribe(callback)
|
|
264
|
+
@mutex.synchronize { @on_complete_callbacks.delete(callback) }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Bound at admission, before application code can attach continuations.
|
|
268
|
+
# @api private
|
|
269
|
+
def __bind_execution(execution)
|
|
270
|
+
@execution_scope = execution
|
|
271
|
+
self
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# @api private
|
|
275
|
+
def __execution_scope
|
|
276
|
+
@execution_scope
|
|
218
277
|
end
|
|
219
278
|
|
|
220
279
|
protected
|
|
@@ -250,7 +309,7 @@ module Phronomy
|
|
|
250
309
|
callback.call(value, error)
|
|
251
310
|
rescue => callback_error
|
|
252
311
|
Phronomy.configuration.logger&.error do
|
|
253
|
-
"[
|
|
312
|
+
"[TaskResult] on_complete callback raised #{callback_error.class}: #{callback_error.message}"
|
|
254
313
|
end
|
|
255
314
|
end
|
|
256
315
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class GeneratorVerifier
|
|
5
|
+
# Converts Agent events to correlated Workflow events. PipelineState owns
|
|
6
|
+
# stale-result rejection and all Workflow state mutation.
|
|
7
|
+
class AgentResultReceiver
|
|
8
|
+
def initialize(draft_result_parser:, review_result_parser:)
|
|
9
|
+
@draft_result_parser = draft_result_parser
|
|
10
|
+
@review_result_parser = review_result_parser
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def draft_listener(workflow:, workflow_instance_id:, request_id:)
|
|
14
|
+
agent_listener(:draft, workflow, workflow_instance_id, request_id) do |output|
|
|
15
|
+
draft_payload(output)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def review_listener(workflow:, workflow_instance_id:, request_id:)
|
|
20
|
+
agent_listener(:review, workflow, workflow_instance_id, request_id) do |output|
|
|
21
|
+
review_payload(output)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def draft_payload(output)
|
|
28
|
+
parsed = @draft_result_parser.call(output)
|
|
29
|
+
{
|
|
30
|
+
draft: parsed[:answer].to_s,
|
|
31
|
+
self_score: clamp(parsed[:confidence]),
|
|
32
|
+
citations: normalize_citations(parsed[:citations])
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def review_payload(output)
|
|
37
|
+
parsed = @review_result_parser.call(output)
|
|
38
|
+
{
|
|
39
|
+
review_score: clamp(parsed[:score]),
|
|
40
|
+
approved: parsed[:approved] == true,
|
|
41
|
+
feedback: parsed[:feedback].to_s
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def agent_listener(phase, workflow, workflow_instance_id, request_id, &completion)
|
|
46
|
+
->(agent_event) {
|
|
47
|
+
case agent_event.type
|
|
48
|
+
when :done
|
|
49
|
+
begin
|
|
50
|
+
payload = completion.call(agent_event.payload[:output])
|
|
51
|
+
workflow.signal(
|
|
52
|
+
workflow_instance_id: workflow_instance_id,
|
|
53
|
+
event: :"#{phase}_completed",
|
|
54
|
+
payload: {request_id: request_id, **payload}
|
|
55
|
+
)
|
|
56
|
+
rescue => error
|
|
57
|
+
signal_failure(workflow, workflow_instance_id, request_id, phase, error)
|
|
58
|
+
end
|
|
59
|
+
when :error, :timeout, :cancelled
|
|
60
|
+
error = agent_event.payload[:error] ||
|
|
61
|
+
Phronomy::Error.new("#{phase.to_s.capitalize} Agent ended with #{agent_event.type}")
|
|
62
|
+
signal_failure(workflow, workflow_instance_id, request_id, phase, error)
|
|
63
|
+
when :approval_required
|
|
64
|
+
error = Phronomy::Error.new("GeneratorVerifier #{phase} Agent suspended for approval")
|
|
65
|
+
signal_failure(workflow, workflow_instance_id, request_id, phase, error)
|
|
66
|
+
end
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def signal_failure(workflow, workflow_instance_id, request_id, phase, error)
|
|
71
|
+
workflow.signal(
|
|
72
|
+
workflow_instance_id: workflow_instance_id,
|
|
73
|
+
event: :"#{phase}_failed",
|
|
74
|
+
payload: {request_id: request_id, error: error}
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def clamp(value)
|
|
79
|
+
value.to_f.clamp(0.0, 1.0)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def normalize_citations(raw)
|
|
83
|
+
Array(raw).filter_map do |citation|
|
|
84
|
+
citation.is_a?(Hash) ? citation.transform_keys(&:to_sym) : nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|