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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Phronomy uses an **EventLoop / FSMSession first** architecture for framework
|
|
4
4
|
lifecycle coordination. `FSMSession` represents explicit lifecycle state and
|
|
5
|
-
events. `Phronomy::
|
|
5
|
+
events. `Phronomy::TaskResult` is the common caller-facing completion handle, not an
|
|
6
6
|
execution backend. Synchronous work that must stay off EventLoop is isolated in
|
|
7
7
|
the bounded `OffloadPool`.
|
|
8
8
|
|
|
@@ -23,37 +23,38 @@ are defined by
|
|
|
23
23
|
|
|
24
24
|
## Runtime model
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
│ │ └─ agent_id -> one nonterminal logical Execution admission
|
|
38
|
-
│ └─ Agent execution directory
|
|
39
|
-
│ └─ execution_id -> immutable live-state record
|
|
40
|
-
├─ OffloadPool (bounded operating-system Threads)
|
|
41
|
-
│ ├─ private Operation records
|
|
42
|
-
│ ├─ blocking input/output (I/O)
|
|
43
|
-
│ ├─ central-processing-unit (CPU)-bound synchronous work
|
|
44
|
-
│ └─ operation-specific durable Agent/Workflow work
|
|
45
|
-
├─ named OffloadPools
|
|
46
|
-
└─ EventLoop-driven timers
|
|
47
|
-
|
|
48
|
-
EventLoop / FSMSession ─┐
|
|
49
|
-
├─> Task = completion handle
|
|
50
|
-
OffloadPool ────────────┘
|
|
51
|
-
```
|
|
26
|
+
| Runtime-retained component | Implementation owner and responsibility |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Shutdown participants | Feature-owned registries: Agent identity, Team identity, and MultiAgent synchronous-call admission |
|
|
29
|
+
| EventLoop | Engine dispatcher thread, FSMSessions and registered feature receivers |
|
|
30
|
+
| Execution receivers | Agent/Workflow own execution admission, state and idle policies; mutations run on EventLoop |
|
|
31
|
+
| OffloadPool and named pools | Engine bounded workers and private physical-operation records |
|
|
32
|
+
| Timers | Engine EventLoop-driven scheduling |
|
|
33
|
+
|
|
34
|
+
Runtime retains participants through a generic lifecycle protocol; it does not
|
|
35
|
+
construct Agent or Team identity registries. Each feature registers its own
|
|
36
|
+
instance. See [ADR-041](decisions/041-feature-owned-identity-registries.md).
|
|
52
37
|
|
|
53
38
|
The framework does not allocate one operating-system Thread per logical
|
|
54
39
|
Agent/Workflow/Tool lifecycle. Logical waits remain explicit states plus later
|
|
55
40
|
EventLoop events.
|
|
56
41
|
|
|
42
|
+
## Feature execution receivers
|
|
43
|
+
|
|
44
|
+
[ADR-042](decisions/042-feature-owned-execution-state.md) separates implementation
|
|
45
|
+
ownership from the single-writer execution context. Agent and Workflow create
|
|
46
|
+
`Agent::ExecutionRegistry` and `WorkflowExecutionRegistry` respectively; Engine
|
|
47
|
+
retains registered receivers without selecting concrete feature types.
|
|
48
|
+
|
|
49
|
+
Receiver registration and new request admission close before idle checks.
|
|
50
|
+
Already queued requests, pending durable operations and supervised physical
|
|
51
|
+
work are counted until their feature continuation settles. Worker callbacks
|
|
52
|
+
only enqueue results. Workflow instance routing and generic FSM enqueue share
|
|
53
|
+
one lock. After a clean join, receiver cleanup invalidates retained state;
|
|
54
|
+
a failing receiver makes Runtime cleanup incomplete. Dispatcher failure instead
|
|
55
|
+
notifies receivers on the failing loop thread with the original error.
|
|
56
|
+
|
|
57
|
+
|
|
57
58
|
## Live state and durable state
|
|
58
59
|
|
|
59
60
|
A live Agent or Workflow owns its current logical state. `Persistence` is the
|
|
@@ -61,7 +62,7 @@ last committed durable representation and recovery source; it is not reloaded at
|
|
|
61
62
|
every semantic boundary.
|
|
62
63
|
|
|
63
64
|
For active Agents, **EventLoop is the single writer of Phronomy-managed live
|
|
64
|
-
execution state**.
|
|
65
|
+
execution state**. `Agent::ExecutionRegistry` owns the process-local directory keyed by
|
|
65
66
|
canonical `execution_id`. Each directory value is immutable and is replaced on
|
|
66
67
|
EventLoop when the current AgentExecution, RuntimeProjection, AgentInvocation, or
|
|
67
68
|
owning FSMSession changes. The former mutex-protected
|
|
@@ -75,7 +76,7 @@ handling; workers do not receive AgentInvocation as a mutable state authority.
|
|
|
75
76
|
Mutable Agent/Execution/Journal state is not automatically reloaded before every
|
|
76
77
|
LLM or Tool step. Durable writes use optimistic revision/position guardrails; an
|
|
77
78
|
external writer that advances the durable base causes
|
|
78
|
-
`
|
|
79
|
+
`Storage::ConflictError` rather than automatic reload or merge.
|
|
79
80
|
|
|
80
81
|
For Workflows, the current `WorkflowContext` and FSMSession own the active
|
|
81
82
|
logical state. A durable Workflow hydrates once at invocation/resume and saves at
|
|
@@ -88,8 +89,9 @@ content reference is value materialization rather than mutable state refresh.
|
|
|
88
89
|
|
|
89
90
|
`agent_id` identifies one logical Agent, not a reusable lookup key for independent
|
|
90
91
|
mutable objects. One Runtime therefore publishes at most one mutable live Agent
|
|
91
|
-
instance for a given `agent_id`. The
|
|
92
|
-
cache, and reserves the identity before
|
|
92
|
+
instance for a given `agent_id`. The Agent-owned registry, strongly retained by
|
|
93
|
+
Runtime, is an authority, not a cache, and reserves the identity before
|
|
94
|
+
create/load materialization.
|
|
93
95
|
|
|
94
96
|
The application-facing identity operations are distinct:
|
|
95
97
|
|
|
@@ -100,7 +102,7 @@ new / create
|
|
|
100
102
|
load(agent_id, persistence:)
|
|
101
103
|
live -> exact same Ruby object, with no Persistence reload
|
|
102
104
|
durable-only -> hydrate and publish once
|
|
103
|
-
missing ->
|
|
105
|
+
missing -> Storage::NotFoundError
|
|
104
106
|
|
|
105
107
|
get(agent_id)
|
|
106
108
|
live Runtime lookup only; missing -> nil
|
|
@@ -114,7 +116,8 @@ invalidates the old object, deletes durable state, releases the process-local
|
|
|
114
116
|
identity, and allows a later new Agent to reuse the textual ID.
|
|
115
117
|
|
|
116
118
|
Live Agent ownership and top-level Execution admission are separate lifetimes.
|
|
117
|
-
For one live Agent,
|
|
119
|
+
For one live Agent, `Agent::ExecutionRegistry` admits at most one nonterminal
|
|
120
|
+
top-level Execution on EventLoop.
|
|
118
121
|
Admission is acquired **before** the initial Offload/Persistence operation:
|
|
119
122
|
|
|
120
123
|
```text
|
|
@@ -195,6 +198,15 @@ Phronomy-managed live domain objects are rejected from that value data. A comple
|
|
|
195
198
|
value-type/serialization contract for arbitrary Application-owned opaque objects is
|
|
196
199
|
deferred; such objects remain Application-owned and must be worker-safe.
|
|
197
200
|
|
|
201
|
+
The existing restricted types declare the internal methodless
|
|
202
|
+
`Concurrency::WorkerInputRestricted` marker at their own definitions.
|
|
203
|
+
ToolInvocation checks that execution-boundary contract instead of enumerating
|
|
204
|
+
Agent/Workflow/Runtime classes. Frozen marked values and subclasses remain
|
|
205
|
+
restricted; Hash keys and values, nested Arrays and behavior handles are all
|
|
206
|
+
checked. The marker does not inspect opaque application fields or closure
|
|
207
|
+
captures and is not a general restriction on every OffloadPool command.
|
|
208
|
+
See [ADR-045](decisions/045-worker-input-restriction-ownership.md).
|
|
209
|
+
|
|
198
210
|
Worker authorization/execution outcomes return as values carrying
|
|
199
211
|
`tool_invocation_id`; the Tool FSMSession consumes a mismatched semantic result
|
|
200
212
|
without advancing its current state.
|
|
@@ -254,7 +266,7 @@ lifecycle. Logical halt/completion first enters a private
|
|
|
254
266
|
`persisting_terminal` lifecycle condition; the FSMSession remains nonterminal
|
|
255
267
|
while WorkflowRunner saves the terminal snapshot through OffloadPool. Only a
|
|
256
268
|
known-successful save result returned to that same FSMSession permits
|
|
257
|
-
`HALTED`/`COMPLETED`, admission release, and caller
|
|
269
|
+
`HALTED`/`COMPLETED`, admission release, and caller TaskResult settlement.
|
|
258
270
|
|
|
259
271
|
```text
|
|
260
272
|
RUNNING
|
|
@@ -279,13 +291,39 @@ remain durable conflict defense rather than distributed ownership.
|
|
|
279
291
|
|
|
280
292
|
## Tool execution modes
|
|
281
293
|
|
|
294
|
+
The default dispatch helper is the private
|
|
295
|
+
`Phronomy::Agent::Context::Capability::ToolExecutor`, colocated with Capability
|
|
296
|
+
Base. Agent ToolInvocation supplies Runtime and admission policy for the
|
|
297
|
+
standard path, and owns authorization and logical result handling. Custom
|
|
298
|
+
`call_async` implementations keep the public Tool protocol. This ownership is
|
|
299
|
+
defined by [ADR-035](decisions/035-tool-executor-capability-ownership.md); the
|
|
300
|
+
public `Phronomy::Tool::Base` facade and its Class identity remain unchanged.
|
|
301
|
+
|
|
302
|
+
Agent uses ordinary `RubyLLM::Chat` for both complete and streaming Provider
|
|
303
|
+
calls. The `before_tool_call` callback takes the complete assistant Tool-call
|
|
304
|
+
batch before RubyLLM executes the first Tool body. `AgentInvocation` then owns
|
|
305
|
+
authorization, dispatch, approval suspension and result collection. Offloaded
|
|
306
|
+
Tools can overlap within Runtime capacity; short cooperative Tools run according
|
|
307
|
+
to their existing execution contract.
|
|
308
|
+
|
|
309
|
+
On successful batch completion, Agent records one Tool result per call ID in
|
|
310
|
+
request order and includes the whole exchange in the next Provider request.
|
|
311
|
+
Worker completion order does not split the conversation or trigger a partial
|
|
312
|
+
Provider continuation. Failure, cancellation and approval keep their existing
|
|
313
|
+
Agent lifecycle semantics.
|
|
314
|
+
|
|
315
|
+
There is no separate Chat execution path or `parallel_tool_execution` switch.
|
|
316
|
+
The removed internal `MultiAgent::ParallelToolChat` class is not replaced by an
|
|
317
|
+
alias. See the [Chat migration guide](migrations/parallel-tool-chat-removal.md)
|
|
318
|
+
for application configuration and existing stored model-config records.
|
|
319
|
+
|
|
282
320
|
Phronomy exposes two execution modes for capabilities:
|
|
283
321
|
|
|
284
322
|
- `:cooperative` — short EventLoop-safe work, or specialized asynchronous work
|
|
285
|
-
that starts another Phronomy lifecycle and returns a
|
|
323
|
+
that starts another Phronomy lifecycle and returns a TaskResult immediately;
|
|
286
324
|
- `:offloaded` — synchronous work that must not run to completion on EventLoop.
|
|
287
325
|
|
|
288
|
-
Both paths return `Phronomy::
|
|
326
|
+
Both paths return `Phronomy::TaskResult`. The execution mechanism differs; the
|
|
289
327
|
completion abstraction does not.
|
|
290
328
|
|
|
291
329
|
Phronomy does not classify application work into framework-level I/O/CPU/process
|
|
@@ -310,7 +348,7 @@ Correct shape:
|
|
|
310
348
|
parent FSMSession
|
|
311
349
|
→ start child lifecycle
|
|
312
350
|
→ return immediately
|
|
313
|
-
→ child
|
|
351
|
+
→ child TaskResult settles
|
|
314
352
|
→ post parent EventLoop event
|
|
315
353
|
```
|
|
316
354
|
|
|
@@ -326,7 +364,7 @@ Agent preparation/commit and Workflow hydrate/save operations are submitted to
|
|
|
326
364
|
|
|
327
365
|
A durable barrier may pause one logical lifecycle without blocking EventLoop.
|
|
328
366
|
Persistence does not implement async repository variants and must not depend on
|
|
329
|
-
EventLoop, FSMSession,
|
|
367
|
+
EventLoop, FSMSession, TaskResult settlement internals, or private OffloadPool operation
|
|
330
368
|
records.
|
|
331
369
|
|
|
332
370
|
## Sync versus async application APIs
|
|
@@ -336,7 +374,7 @@ records.
|
|
|
336
374
|
| Top-level application code | `agent.invoke(...)` when blocking the caller is acceptable |
|
|
337
375
|
| Top-level explicit async | `task = agent.invoke_async(...)`; optionally `task.wait_result` outside EventLoop |
|
|
338
376
|
| Workflow entry/transition action | Start async work and continue through `Workflow#signal` |
|
|
339
|
-
| EventLoop callback | Never block waiting for a
|
|
377
|
+
| EventLoop callback | Never block waiting for a TaskResult that requires EventLoop progress |
|
|
340
378
|
| Top-level streaming | `agent.stream(...)` |
|
|
341
379
|
| Non-blocking streaming | `agent.stream_async(...)` |
|
|
342
380
|
| Approval from EventLoop callback | Resolve with `live_for_execution`, call `agent.approve_async(...)`, and return immediately |
|
|
@@ -345,37 +383,88 @@ Blocking synchronous APIs reject EventLoop re-entry with
|
|
|
345
383
|
`Phronomy::EventLoopReentrancyError` when waiting would stall the same EventLoop
|
|
346
384
|
needed for progress.
|
|
347
385
|
|
|
348
|
-
##
|
|
386
|
+
## TaskResult
|
|
349
387
|
|
|
350
|
-
`Phronomy::
|
|
388
|
+
`Phronomy::TaskResult` is thread-free. It represents one terminal result:
|
|
351
389
|
|
|
352
390
|
- completed value;
|
|
353
391
|
- failure;
|
|
354
392
|
- cancellation.
|
|
355
393
|
|
|
356
|
-
|
|
394
|
+
TaskResult is the common completion abstraction for logical EventLoop/FSMSession
|
|
357
395
|
lifecycles and OffloadPool-backed synchronous work.
|
|
358
396
|
|
|
359
|
-
`
|
|
360
|
-
timeout is waiter-local: it does not settle/cancel the
|
|
397
|
+
`TaskResult#wait_result(timeout:)` is a bridge for external synchronous callers. Its
|
|
398
|
+
timeout is waiter-local: it does not settle/cancel the TaskResult or alter OffloadPool
|
|
361
399
|
abandonment state.
|
|
362
400
|
|
|
363
|
-
`
|
|
401
|
+
`TaskResult#on_complete` registers an independent notification callback. Callback
|
|
364
402
|
execution thread is not guaranteed. A callback may be delivered by an OffloadPool
|
|
365
403
|
worker, a timer/cancellation caller, an EventLoop-related control path, or the
|
|
366
404
|
thread that registers after settlement. Callbacks must therefore be thread-safe
|
|
367
405
|
and should complete quickly. Framework lifecycle code normally converts worker
|
|
368
406
|
completion into an explicit EventLoop event before applying live state.
|
|
369
407
|
|
|
370
|
-
`
|
|
371
|
-
the mapped
|
|
408
|
+
`TaskResult#map` is application-level composition. A transformation exception settles
|
|
409
|
+
the mapped TaskResult as failed.
|
|
372
410
|
|
|
373
|
-
|
|
374
|
-
|
|
411
|
+
`flat_map` connects a returned TaskResult's completion, while `all_settled`
|
|
412
|
+
observes an input-order list of results. `Execution.run_async` starts and joins
|
|
413
|
+
JOBs under whole-execution controls. See [Result composition and Execution](async-composition.md)
|
|
414
|
+
for the complete contracts, ownership boundaries and development-release migration.
|
|
415
|
+
|
|
416
|
+
Framework components own TaskResult settlement. Application code should not use
|
|
417
|
+
`TaskResult#complete`, `TaskResult#fail`, or `TaskResult#cancel!` as operation-control APIs. Request
|
|
375
418
|
operation-wide cancellation through the `CancellationToken` accepted by the API
|
|
376
|
-
that created the
|
|
419
|
+
that created the TaskResult. TaskResult settlement never propagates backwards to cancel a
|
|
377
420
|
shared CancellationToken.
|
|
378
421
|
|
|
422
|
+
### Settled TaskResult factories
|
|
423
|
+
|
|
424
|
+
`TaskResult.completed(value)` and `TaskResult.failed(error)` create results that are already
|
|
425
|
+
settled. They are useful when application code has a result or an error in hand
|
|
426
|
+
and needs to present it through the same `TaskResult` contract as a lifecycle-backed
|
|
427
|
+
completion:
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
# Already-computed value — no execution started.
|
|
431
|
+
task = Phronomy::TaskResult.completed("cached result")
|
|
432
|
+
|
|
433
|
+
# Known failure — no execution started.
|
|
434
|
+
task = Phronomy::TaskResult.failed(StandardError.new("precondition not met"))
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Settled results satisfy `TaskResult#wait_result`, `TaskResult#on_complete`, and `TaskResult#map` with
|
|
438
|
+
the same contract as a normally-completed TaskResult.
|
|
439
|
+
|
|
440
|
+
## Blocking
|
|
441
|
+
|
|
442
|
+
`Phronomy::Blocking.call_async(&block)` submits a synchronous block to the
|
|
443
|
+
existing default Runtime OffloadPool and returns a `TaskResult`. It is the public
|
|
444
|
+
application entry point for one-off synchronous work that should not block the
|
|
445
|
+
caller:
|
|
446
|
+
|
|
447
|
+
```ruby
|
|
448
|
+
task = Phronomy::Blocking.call_async { expensive_io_call }
|
|
449
|
+
result = task.wait_result
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
The optional `invocation_context:` connects this operation to an Execution
|
|
453
|
+
scope or an existing invocation context. Context controls and the optional
|
|
454
|
+
individual `cancellation_token:` both apply, without reverse cancellation of
|
|
455
|
+
shared controls. The application chooses when synchronous work needs OffloadPool.
|
|
456
|
+
|
|
457
|
+
The method uses the existing bounded OffloadPool. It does not create a new thread
|
|
458
|
+
or scheduler. Capacity and backpressure semantics are inherited from the pool
|
|
459
|
+
configuration (`offload_pool_size` / `offload_queue_size`).
|
|
460
|
+
|
|
461
|
+
`Blocking.call_async` always uses non-waiting admission (`on_full: :raise`) and can
|
|
462
|
+
be called from within the EventLoop thread. Admission `StandardError` failures (for
|
|
463
|
+
example `BackpressureError` when the queue is full) are caught and returned as a
|
|
464
|
+
failed `TaskResult`. Accepted work retains the pool's original `TaskResult` unchanged. Do not
|
|
465
|
+
call `TaskResult#wait_result` on the returned `TaskResult` from within the EventLoop — waiting
|
|
466
|
+
for a `TaskResult` that requires EventLoop progress will deadlock.
|
|
467
|
+
|
|
379
468
|
## OffloadPool
|
|
380
469
|
|
|
381
470
|
`OffloadPool` is a bounded worker pool for synchronous work that must not execute
|
|
@@ -391,7 +480,7 @@ Its guarantees include:
|
|
|
391
480
|
- runtime metrics;
|
|
392
481
|
- shutdown/drain behavior.
|
|
393
482
|
|
|
394
|
-
`OffloadPool#submit` returns a `Phronomy::
|
|
483
|
+
`OffloadPool#submit` returns a `Phronomy::TaskResult`. OffloadPool does not expose its
|
|
395
484
|
execution record as a caller-facing future/promise. Its private `Operation` owns:
|
|
396
485
|
|
|
397
486
|
- the submitted block;
|
|
@@ -403,27 +492,36 @@ execution record as a caller-facing future/promise. Its private `Operation` owns
|
|
|
403
492
|
- metrics state needed by the pool.
|
|
404
493
|
|
|
405
494
|
This separation keeps execution details private while allowing every asynchronous
|
|
406
|
-
Phronomy API to expose the same
|
|
495
|
+
Phronomy API to expose the same TaskResult completion contract.
|
|
407
496
|
|
|
408
497
|
### EventLoop queue admission
|
|
409
498
|
|
|
410
499
|
Framework-owned EventLoop-origin submissions must not wait for a free worker
|
|
411
500
|
queue slot. They use non-blocking admission (`on_full: :raise`) and route
|
|
412
|
-
`BackpressureError` through the ordinary FSM/
|
|
501
|
+
`BackpressureError` through the ordinary FSM/TaskResult completion path.
|
|
502
|
+
|
|
503
|
+
Any internal `OffloadPool#submit` call that requests waiting admission
|
|
504
|
+
(`on_full: :wait` or `:timeout`) is **rejected before the Operation is created**
|
|
505
|
+
when the caller is on the EventLoop thread. The rejection raises
|
|
506
|
+
`Phronomy::EventLoopReentrancyError` directly; it does not create an Operation,
|
|
507
|
+
timer, cancellation subscription, or TaskResult. The public `Phronomy::Blocking.call_async`
|
|
508
|
+
always uses `on_full: :raise` and therefore does not trigger this guard; it converts
|
|
509
|
+
admission `StandardError` (such as `BackpressureError`) into a failed `TaskResult` at the
|
|
510
|
+
public-API layer.
|
|
413
511
|
|
|
414
512
|
External management threads may choose a blocking admission policy when blocking
|
|
415
513
|
the caller is acceptable.
|
|
416
514
|
|
|
417
515
|
## Submit timeout and cancellation
|
|
418
516
|
|
|
419
|
-
Submit-time timeout and submit cancellation settle the caller-facing
|
|
517
|
+
Submit-time timeout and submit cancellation settle the caller-facing TaskResult. They
|
|
420
518
|
do **not** asynchronously interrupt an already-running synchronous worker.
|
|
421
519
|
|
|
422
520
|
### Before worker start
|
|
423
521
|
|
|
424
522
|
If timeout/cancellation wins before execution starts:
|
|
425
523
|
|
|
426
|
-
- the
|
|
524
|
+
- the TaskResult settles (`TimeoutError` failure or cancellation);
|
|
427
525
|
- the submitted block does not run;
|
|
428
526
|
- the private Operation is not counted as abandoned.
|
|
429
527
|
|
|
@@ -431,7 +529,7 @@ If timeout/cancellation wins before execution starts:
|
|
|
431
529
|
|
|
432
530
|
If timeout/cancellation wins after execution starts:
|
|
433
531
|
|
|
434
|
-
- the
|
|
532
|
+
- the TaskResult settles immediately;
|
|
435
533
|
- the private Operation is marked abandoned;
|
|
436
534
|
- the worker continues until its synchronous call returns;
|
|
437
535
|
- the eventual worker result is discarded.
|
|
@@ -447,7 +545,7 @@ requiring callback delivery promote the deadline to explicit `cancel!` through
|
|
|
447
545
|
the Runtime timer queue. OffloadPool does this for its submit cancellation token.
|
|
448
546
|
|
|
449
547
|
A CancellationToken may be shared by multiple operations. For that reason,
|
|
450
|
-
settling or cancelling one
|
|
548
|
+
settling or cancelling one TaskResult does not cancel the token in the reverse
|
|
451
549
|
direction.
|
|
452
550
|
|
|
453
551
|
## Native async boundary
|
|
@@ -455,7 +553,7 @@ direction.
|
|
|
455
553
|
A genuine native-async driver that does not create a Phronomy-owned OS Thread and
|
|
456
554
|
does not block EventLoop need not consume an OffloadPool worker. If Phronomy
|
|
457
555
|
formally exposes such an extension point, it must adapt completion to
|
|
458
|
-
`Phronomy::
|
|
556
|
+
`Phronomy::TaskResult` rather than exposing a provider-specific future or a private
|
|
459
557
|
Runtime type.
|
|
460
558
|
|
|
461
559
|
The current Persistence, VectorStore, Embeddings, and LLM call-extension
|
|
@@ -473,8 +571,8 @@ Two metrics answer different operational questions:
|
|
|
473
571
|
- `offload_pool_abandoned_active` — current number of abandoned operations whose
|
|
474
572
|
synchronous workers still occupy pool capacity.
|
|
475
573
|
|
|
476
|
-
The abandonment state belongs to the private OffloadPool Operation, not to
|
|
477
|
-
|
|
574
|
+
The abandonment state belongs to the private OffloadPool Operation, not to TaskResult.
|
|
575
|
+
TaskResult reports only caller-facing settlement.
|
|
478
576
|
|
|
479
577
|
## EventLoop metrics
|
|
480
578
|
|
|
@@ -487,6 +585,53 @@ Use these to distinguish worker saturation from EventLoop backlog/latency.
|
|
|
487
585
|
Runtime-owned EventLoop, then closes pools and timers according to the Runtime
|
|
488
586
|
shutdown contract.
|
|
489
587
|
|
|
588
|
+
Internal subsystems can register one shutdown participant per Runtime-local key
|
|
589
|
+
through `Runtime#__register_shutdown_participant`. This is an internal lifecycle
|
|
590
|
+
contract, not a public extension API or an execution registry. Runtime does not
|
|
591
|
+
construct participants or interpret their admission rules.
|
|
592
|
+
|
|
593
|
+
Each participant implements two required operations:
|
|
594
|
+
|
|
595
|
+
- `begin_draining` closes admission under the participant's admission lock. It
|
|
596
|
+
must be idempotent, short and nonblocking, and must not call back into Runtime.
|
|
597
|
+
Runtime invokes it under its lifecycle lock when shutdown starts or EventLoop
|
|
598
|
+
fails, so no participant can be registered after closure begins.
|
|
599
|
+
- `wait_until_idle(deadline)` returns whether admitted calls have finished before
|
|
600
|
+
the shared absolute monotonic deadline. Runtime closes every participant before
|
|
601
|
+
waiting, and waits without holding its lifecycle lock. A timeout or failed hook
|
|
602
|
+
makes cleanup incomplete; other participants, pools and timers still receive
|
|
603
|
+
their shutdown calls. Incomplete cleanup prevents default Runtime replacement.
|
|
604
|
+
|
|
605
|
+
Participants may also implement `after_runtime_shutdown`. Runtime calls it
|
|
606
|
+
outside its lifecycle lock after every wait succeeds, EventLoop has stopped,
|
|
607
|
+
and pools/timers have shut down successfully. The hook must be idempotent,
|
|
608
|
+
short, and perform no I/O; its return value is ignored. A hook exception marks
|
|
609
|
+
cleanup incomplete, retains the first failure, and does not skip later hooks.
|
|
610
|
+
Completed releases are not rolled back; repeated shutdown returns its cached
|
|
611
|
+
result without retrying hooks. A prior closure/wait failure, timeout, or worker
|
|
612
|
+
shutdown failure skips finalization entirely.
|
|
613
|
+
|
|
614
|
+
`Runtime#__shutdown_participant(key:)` only retrieves an existing participant,
|
|
615
|
+
including during/after shutdown. Registration still requires a running Runtime,
|
|
616
|
+
even for an existing key. Agent/Team `get` uses lookup without registration;
|
|
617
|
+
feature gates reject new ownership changes through retained registries after
|
|
618
|
+
closure. Already admitted transitions may finish. Identity registries wait only
|
|
619
|
+
for active construction/purge, then detach Agent references or clear Team
|
|
620
|
+
owners at finalization; live idle objects do not themselves prolong shutdown.
|
|
621
|
+
|
|
622
|
+
`MultiAgent::AdmissionRegistry.for(runtime)` constructs and registers the shared
|
|
623
|
+
coordination admission registry. MultiAgent::HandoffRunner and TeamCoordinator retain that
|
|
624
|
+
registry, admit each synchronous call directly and release it in `ensure` after
|
|
625
|
+
successful admission. The registry closes admission atomically with respect to
|
|
626
|
+
`admit!`; a call admitted before closure is included in the wait, and a call after
|
|
627
|
+
closure raises `RuntimeShutdownError`, including after EventLoop failure. The
|
|
628
|
+
existing `HandoffError` for duplicate admission remains unchanged.
|
|
629
|
+
|
|
630
|
+
This tracks the current synchronous Handoff/Team call, not the lifetime or
|
|
631
|
+
completion of a durable AgentExecution or TeamExecution. Multi-Agent policy stays
|
|
632
|
+
in `multi_agent`; Runtime depends only on the shutdown operations. Application
|
|
633
|
+
authors do not register these participants themselves.
|
|
634
|
+
|
|
490
635
|
Workflow durable admission participates in EventLoop idleness: a Workflow whose
|
|
491
636
|
FSMSession has ended but whose durable save is still in flight remains owned until
|
|
492
637
|
that save completes and owner-aware admission is released.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Agent
|
|
5
|
+
StreamEvent = Data.define(:type, :payload)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require_relative "../async_event_api"
|
|
10
|
+
|
|
11
|
+
unless Phronomy::Agent::Base < Phronomy::Agent::AsyncEventApi
|
|
12
|
+
Phronomy::Agent::Base.prepend(Phronomy::Agent::AsyncEventApi)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Durable Agent Recovery and Agent-incarnation event binding.
|
|
16
|
+
require_relative "../recovery/recovery_support"
|
|
17
|
+
require_relative "../recovery/recovery_coordinator"
|
|
@@ -96,7 +96,7 @@ module Phronomy
|
|
|
96
96
|
|
|
97
97
|
def approve_async(execution_id, approval_request_id:, approved: true, config: {})
|
|
98
98
|
_reject_removed_generic_identity_keys!(config)
|
|
99
|
-
owner = Phronomy::Runtime.instance
|
|
99
|
+
owner = Phronomy::Agent::ExecutionRegistry.existing_for(Phronomy::Runtime.instance)&.agent_execution_owner(execution_id)
|
|
100
100
|
coordinator = if owner&.agent&.equal?(self)
|
|
101
101
|
owner.coordinator
|
|
102
102
|
else
|
|
@@ -190,7 +190,7 @@ module Phronomy
|
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
bytes = Phronomy::CanonicalJSON.dump(value)
|
|
193
|
-
snapshot = Phronomy::
|
|
193
|
+
snapshot = Phronomy::Values::Immutable.copy(
|
|
194
194
|
Phronomy::CanonicalJSON.load(bytes)
|
|
195
195
|
)
|
|
196
196
|
config.merge(durable_context: snapshot)
|