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
data/docs/architecture.md
CHANGED
|
@@ -41,6 +41,215 @@ explicit compatibility decision.
|
|
|
41
41
|
dependency minimization must not weaken accepted durability,
|
|
42
42
|
ownership, security-boundary, or extension contracts.
|
|
43
43
|
|
|
44
|
+
## Responsibility groups and dependency graphs
|
|
45
|
+
|
|
46
|
+
Dependency views describe a directed graph of responsibility groups, not a
|
|
47
|
+
tree or a single pipeline. Several distinct groups may share one horizontal
|
|
48
|
+
band. A band's height expresses a proposed abstraction level; it does not imply
|
|
49
|
+
that its groups are independent or that every valid dependency points down.
|
|
50
|
+
|
|
51
|
+
Assess each dependency by the referenced contract and its owner. Shared value
|
|
52
|
+
types, extension contracts, and collaboration within a component can justify
|
|
53
|
+
dependencies between groups. A directory containing both shared definitions and
|
|
54
|
+
concrete orchestration must be examined by responsibility before assigning it a
|
|
55
|
+
single position. Cycles and upward arrows are review evidence, not defect counts.
|
|
56
|
+
|
|
57
|
+
The FSM terminal marker illustrates this distinction. `FSMProtocol::FINISH`
|
|
58
|
+
owns the internal `:__end__` marker used by the session and Workflow compilers.
|
|
59
|
+
The public Workflow DSL continues to use `:__finish__`. The execution session
|
|
60
|
+
and phase compiler do not depend on `WorkflowRunner` for this shared vocabulary;
|
|
61
|
+
the Workflow builder still legitimately creates its runner.
|
|
62
|
+
|
|
63
|
+
Context Policy and hook contracts provide another example. The shared values,
|
|
64
|
+
Manifest representation, and Plan validation live in `agent/context_contract/`,
|
|
65
|
+
separately from concrete policies and Agent execution. Zeitwerk collapses that
|
|
66
|
+
directory to preserve the existing `Phronomy::Agent` constants. A Policy's
|
|
67
|
+
dependency on those contracts does not make it depend on Agent execution. See
|
|
68
|
+
[ADR-036](decisions/036-context-contract-ownership.md).
|
|
69
|
+
|
|
70
|
+
### Common definitions
|
|
71
|
+
|
|
72
|
+
`common/` owns general definitions shared across the framework that do not
|
|
73
|
+
belong to a particular feature. They must not depend on concrete Agent,
|
|
74
|
+
Workflow, Runtime, or other feature implementations. Being used in several
|
|
75
|
+
places, or inheriting a common base class, does not by itself make a definition
|
|
76
|
+
common; feature-owned contracts remain with their owners.
|
|
77
|
+
|
|
78
|
+
This group contains `Phronomy::Error`, `Phronomy::ConfigurationError`,
|
|
79
|
+
`Phronomy::CanonicalJSON`, and `Phronomy::Values::Immutable`. Zeitwerk collapses
|
|
80
|
+
`common/`, preserving these canonical names without introducing a
|
|
81
|
+
`Phronomy::Common` namespace. Other exceptions belong to their feature
|
|
82
|
+
contracts. `configuration/` owns settings and scalar defaults. The concrete
|
|
83
|
+
adapter and tracer defaults are selected in `runtime_composition/`, which binds
|
|
84
|
+
fresh-instance factories consumed by `Configuration.new`. This keeps concrete
|
|
85
|
+
feature selection outside settings while preserving application behavior.
|
|
86
|
+
Application Runtime reset and configuration replacement are also coordinated in
|
|
87
|
+
`runtime_composition/`, separately from configuration access and Engine mechanics.
|
|
88
|
+
See [ADR-039](decisions/039-runtime-configuration-lifecycle-ownership.md) and
|
|
89
|
+
[ADR-040](decisions/040-configuration-default-composition.md).
|
|
90
|
+
See [ADR-037](decisions/037-common-definition-ownership.md) and
|
|
91
|
+
[ADR-038](decisions/038-responsibility-based-source-layout.md).
|
|
92
|
+
|
|
93
|
+
### Source placement and loading
|
|
94
|
+
|
|
95
|
+
`lib/phronomy.rb` is the application loading entry point. Internal production
|
|
96
|
+
files must not require it. Feature implementations and contracts live in their
|
|
97
|
+
responsibility directories; the direct root contains only `version.rb` and the
|
|
98
|
+
small namespace/loading files enumerated in ADR-038.
|
|
99
|
+
The separately documented external backend-test entry
|
|
100
|
+
`phronomy/testing/persistence_contract` retains its existing opt-in loading
|
|
101
|
+
contract and is excluded from production automatic loading.
|
|
102
|
+
|
|
103
|
+
Engine owns Event, Execution composition and its outcome exceptions, and the
|
|
104
|
+
synchronous FSM callback exceptions. Recovery owns shared rehydration
|
|
105
|
+
requirements. Workflow implementation lives under `workflow/execution/`;
|
|
106
|
+
Agent namespace/event loading lives under `agent/api/`, separately from the shared
|
|
107
|
+
Agent lifecycle exceptions in `agent/lifecycle_contract/`. LLM values and
|
|
108
|
+
call-boundary exceptions live under `llm_contract/`.
|
|
109
|
+
|
|
110
|
+
Agent consumes its private fresh-Persistence factory only when neither an
|
|
111
|
+
explicit instance nor a configured instance is available. Concrete selection
|
|
112
|
+
and binding live in `runtime_composition/agent_defaults.rb`. The one-shot
|
|
113
|
+
`Agent.run_once` method is defined in `agent/composition/run_once.rb`, because it
|
|
114
|
+
explicitly composes Agent and fresh ephemeral Persistence on every call.
|
|
115
|
+
The application entry loads both composition files; Agent execution and
|
|
116
|
+
namespace loading do not delegate upward to them. See
|
|
117
|
+
[ADR-044](decisions/044-agent-default-and-one-shot-composition.md).
|
|
118
|
+
|
|
119
|
+
Types excluded from authorization worker inputs declare the internal,
|
|
120
|
+
methodless `Concurrency::WorkerInputRestricted` contract at their own
|
|
121
|
+
definitions. ToolInvocation checks that execution-boundary contract rather
|
|
122
|
+
than concrete Workflow types. The original restriction set and opaque
|
|
123
|
+
application-value behavior are preserved; see
|
|
124
|
+
[ADR-045](decisions/045-worker-input-restriction-ownership.md).
|
|
125
|
+
|
|
126
|
+
Agent implementation files are grouped into lifecycle, execution, Tool execution,
|
|
127
|
+
context assembly, journal, Handoff and recovery directories. These directories
|
|
128
|
+
are collapsed, so existing Agent constant names remain unchanged. Journal encoding,
|
|
129
|
+
saved context reads and live invocation restoration have separate internal owners;
|
|
130
|
+
transaction and EventLoop state decisions remain with their callers. See
|
|
131
|
+
[ADR-046](decisions/046-agent-responsibility-layout-and-shared-records.md).
|
|
132
|
+
|
|
133
|
+
Recovery hands semantic continuation commands to the execution owner through
|
|
134
|
+
its EventLoop delivery boundary. The owner checks current identity, revision
|
|
135
|
+
and session state before applying them. `Agent::ExecutionSessionRunner` shares
|
|
136
|
+
ordinary and recovered Agent/Tool session registration and reports completion
|
|
137
|
+
back to the same coordinator; operation workers now own terminal persistence
|
|
138
|
+
under its EventLoop result authority (ADR-051 below).
|
|
139
|
+
See [ADR-047](decisions/047-recovered-execution-continuation-contract.md).
|
|
140
|
+
|
|
141
|
+
InvocationRestorer decodes saved Tool snapshots, resolves the Tool Call and Tool,
|
|
142
|
+
and matches approval evidence. The newly constructed ToolInvocation applies its
|
|
143
|
+
own saved state through the internal `restore_state!` operation; Recovery no
|
|
144
|
+
longer writes its instance variables. State application neither evaluates approval
|
|
145
|
+
nor dispatches Tool execution. Existing recovery installation notifications and
|
|
146
|
+
continuation rules are unchanged. See
|
|
147
|
+
[ADR-052](decisions/052-tool-invocation-restoration-ownership.md).
|
|
148
|
+
|
|
149
|
+
`Agent::DispatchPreparation` owns Provider/Tool dispatch prerequisites and their
|
|
150
|
+
operation-specific readback. ExecutionCoordinator captures and submits inputs,
|
|
151
|
+
then validates/applies results on EventLoop before dispatch. The worker's Provider
|
|
152
|
+
entry separates record encoding, application ContextPolicy, prerequisite commit
|
|
153
|
+
and post-commit materialization without changing transaction/rescue boundaries.
|
|
154
|
+
Its input/result types are worker-owned; existing Coordinator constant paths are
|
|
155
|
+
internal aliases, with changed canonical Ruby names. See
|
|
156
|
+
[ADR-048](decisions/048-dispatch-preparation-worker-ownership.md).
|
|
157
|
+
|
|
158
|
+
`Agent::InitialPreparation` owns initial durable admission, Context preparation,
|
|
159
|
+
preparation failure persistence and replay from saved preparing inputs. Ordinary
|
|
160
|
+
start and recovery share the admitted-preparation steps; Runtime admission,
|
|
161
|
+
result validation, live-state apply and session delivery remain on EventLoop.
|
|
162
|
+
The known failure base advances only after a successful commit response.
|
|
163
|
+
`Agent::ExecutionFailure` shares the existing pure failure classification with
|
|
164
|
+
terminal persistence; it does not own transactions or delivery. See
|
|
165
|
+
[ADR-049](decisions/049-initial-preparation-worker-ownership.md).
|
|
166
|
+
|
|
167
|
+
`Agent::ApprovalResumeCommit` persists approval decisions with operation-owned
|
|
168
|
+
Tool recovery snapshots. Coordinator validates the suspended owner and approval
|
|
169
|
+
request before copying canonical snapshot values into the immutable Command;
|
|
170
|
+
there is no shared snapshot lookup. The worker validates the target, stages
|
|
171
|
+
recovery facts and commits decision/Execution/Root together. EventLoop retains
|
|
172
|
+
admission, stale-result checks, live-state application and session resumption.
|
|
173
|
+
An uncertain commit still requires recovery; it is not retried or treated as a
|
|
174
|
+
confirmed resume. Internal Coordinator type aliases remain, with changed
|
|
175
|
+
canonical names and an added Command snapshot field. See
|
|
176
|
+
[ADR-050](decisions/050-approval-resume-snapshot-and-commit-ownership.md).
|
|
177
|
+
|
|
178
|
+
`Agent::ExecutionOutcomeCommitter` owns ordinary completion, failure, suspension
|
|
179
|
+
and child waiting; `Agent::HandoffOutcomeCommitter` adds atomic Source transfer.
|
|
180
|
+
The Handoff Coordinator now only selects its worker. Transaction boundaries,
|
|
181
|
+
operation-specific readback and Handoff selection precedence remain unchanged.
|
|
182
|
+
Coordinator retains quiescence, submission, stale-result validation, live-state
|
|
183
|
+
application, admission and Task/listener delivery. Command/view/outcome types are
|
|
184
|
+
worker-owned with internal Coordinator aliases and changed canonical Ruby names.
|
|
185
|
+
See [ADR-051](decisions/051-execution-outcome-worker-ownership.md).
|
|
186
|
+
|
|
187
|
+
The remaining execution owner expresses result handling as validation, committed
|
|
188
|
+
state installation, and continuation or delivery. Private methods keep these
|
|
189
|
+
steps in Coordinator; they introduce neither another owner nor shared per-operation
|
|
190
|
+
fields. The operation-specific authority checks and outer rescue boundaries stay
|
|
191
|
+
at the result entry points. Start/resume admission and submission flags stay in
|
|
192
|
+
the same methods as their cleanup decisions.
|
|
193
|
+
|
|
194
|
+
| Owner entry | Purpose-level steps |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| Initial preparation recovery result | Validate preparing owner; restart the prepared session or settle its saved failure; complete the load observer |
|
|
197
|
+
| Approval resume result | Validate suspended revision; install committed state and waiter; observe the task; resume the FSM |
|
|
198
|
+
| Terminal result | Validate revision/session; handle commit uncertainty; apply state and acknowledge the snapshot; deliver the selected outcome |
|
|
199
|
+
|
|
200
|
+
Terminal delivery releases ownership before notifying completed/failed/Handoff
|
|
201
|
+
observers, then settles Tasks. Suspension keeps ordinary Tasks pending. Ordinary
|
|
202
|
+
commit uncertainty keeps recovery admission and pending waiters; coordination
|
|
203
|
+
errors retain their separate release-and-fail behavior. Session registration
|
|
204
|
+
failure during preparation recovery still terminalizes without a live session;
|
|
205
|
+
trace/resume failure still uses the newly installed execution revision.
|
|
206
|
+
This is an internal readability refinement of ADR-024/047/051, not a change to
|
|
207
|
+
persistence, recovery guarantees or public interfaces.
|
|
208
|
+
|
|
209
|
+
Selected nested Zeitwerk roots retain existing top-level Phronomy constants
|
|
210
|
+
without changing the enclosing feature's existing nested constants. For
|
|
211
|
+
example, `Phronomy::WorkflowContext` and `Phronomy::WorkflowRunner` coexist with
|
|
212
|
+
`Phronomy::Workflow::Persistence`. Workflow remains a class and its source
|
|
213
|
+
file lives beside its implementation. These moves do not introduce aliases or
|
|
214
|
+
a new public API for requiring arbitrary internal paths.
|
|
215
|
+
|
|
216
|
+
WorkflowRunner directly owns terminal save submission and F1 snapshot readback.
|
|
217
|
+
The former WorkflowRecovery prepend and shadowed save implementation are removed;
|
|
218
|
+
the active path is independent of an installation hook. Private methods separate
|
|
219
|
+
submission, save/reconciliation and result delivery. FSMSession still interprets
|
|
220
|
+
the terminal persistence event; that ownership is a separate next step. See
|
|
221
|
+
[ADR-054](decisions/054-workflow-terminal-save-single-owner.md) and the
|
|
222
|
+
[remaining refactoring plan](architecture/remaining-refactoring-plan.md).
|
|
223
|
+
The Agent entry explicitly preserves Agent lifecycle extension installation.
|
|
224
|
+
Configuration accessors now live beside Configuration, rather than inside the
|
|
225
|
+
loader. Configuration constructs fresh components through internally bound
|
|
226
|
+
factories; composition selects their concrete types. Static source-reference
|
|
227
|
+
graphs do not follow these injected calls. LLMAdapter's async bridge still uses
|
|
228
|
+
Runtime, and other dependency cycles remain.
|
|
229
|
+
|
|
230
|
+
Agent and Team implement their identity registries in `agent/` and
|
|
231
|
+
`multi_agent/`. Runtime strongly retains one of each when registered, using only
|
|
232
|
+
its generic shutdown participant contract. Feature code reserves identities,
|
|
233
|
+
handles feature exceptions, and detaches owners after completed cleanup.
|
|
234
|
+
See [ADR-041](decisions/041-feature-owned-identity-registries.md).
|
|
235
|
+
|
|
236
|
+
Agent and Workflow also own their distinct execution registries. EventLoop
|
|
237
|
+
retains them through Engine's internal `ExecutionReceiver` contract, dispatches
|
|
238
|
+
queued messages and combines generic session/delivery counts with their idle
|
|
239
|
+
predicates. Normal mutation remains on the EventLoop thread; only final
|
|
240
|
+
invalidation after join runs on the management thread. EventLoop has no Agent
|
|
241
|
+
or Workflow dispatch branch. See
|
|
242
|
+
[ADR-042](decisions/042-feature-owned-execution-state.md).
|
|
243
|
+
|
|
244
|
+
`MultiAgent::SharedState` owns sequential peer coordination through a findings
|
|
245
|
+
store created for each invocation. It equips member Agents with shared-store
|
|
246
|
+
Tools, runs complete cycles, checks stopping conditions and aggregates findings.
|
|
247
|
+
It is not an individual Agent's durable state or execution coordinator. Its
|
|
248
|
+
Experimental public namespace moves from Agent to MultiAgent without an alias;
|
|
249
|
+
generated semantic definition IDs retain their previous values. See
|
|
250
|
+
[ADR-053](decisions/053-shared-state-coordination-ownership.md) and the
|
|
251
|
+
[migration guide](migrations/shared-state-multi-agent.md).
|
|
252
|
+
|
|
44
253
|
## Current architecture
|
|
45
254
|
|
|
46
255
|
| Area | Current document |
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Result composition and Execution
|
|
2
|
+
|
|
3
|
+
`Phronomy::TaskResult` represents a pending or settled result. It does not start
|
|
4
|
+
a thread. `Phronomy::Execution` starts the input JOBs and waits for the final
|
|
5
|
+
TaskResult returned by each JOB. The application defines those completion
|
|
6
|
+
conditions and what to do with the collected values.
|
|
7
|
+
|
|
8
|
+
## Invocation, individual results and fan-in
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
require "phronomy"
|
|
12
|
+
|
|
13
|
+
def collect_answers_async(agents, question:, invocation_context: nil)
|
|
14
|
+
Phronomy::Execution.run_async(
|
|
15
|
+
agents, timeout: 30, invocation_context: invocation_context
|
|
16
|
+
) do |agent, execution|
|
|
17
|
+
agent.invoke_async(question, invocation_context: execution.invocation_context)
|
|
18
|
+
.map { |response| response.fetch(:output).strip }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Each outcome identifies the original input position, including failed JOBs.
|
|
23
|
+
outcomes = collect_answers_async(agents, question: "Explain the tradeoff").wait_result
|
|
24
|
+
outcomes.each do |outcome|
|
|
25
|
+
puts [outcome.index, outcome.status, outcome.value, outcome.error&.message].inspect
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`agents` contains separately generated or loaded Agent instances. Use each
|
|
30
|
+
Agent's materialization listener for lifecycle/progress events. Result handling
|
|
31
|
+
uses the TaskResult returned by the invocation.
|
|
32
|
+
|
|
33
|
+
The input must be an Array. Its positions are shallowly copied before any JOB
|
|
34
|
+
starts. Start blocks run in input order without waiting for previous returned
|
|
35
|
+
results. They can run on the calling thread. Execution adds no per-JOB thread
|
|
36
|
+
or worker: keep start and transformation blocks short and use
|
|
37
|
+
`Blocking.call_async` for application work that can block. A start block itself
|
|
38
|
+
can delay the return of `run_async` if the application performs synchronous work.
|
|
39
|
+
|
|
40
|
+
Zero, one and many inputs all produce an Array of Outcome records. Empty input
|
|
41
|
+
still validates parameters and rejects an already-expired deadline or cancelled
|
|
42
|
+
token before completing. `Execution.run` starts the same execution once and
|
|
43
|
+
waits for its Outcome Array. The synchronous entrance is rejected on EventLoop
|
|
44
|
+
even for empty or already-completed inputs.
|
|
45
|
+
|
|
46
|
+
## map and flat_map
|
|
47
|
+
|
|
48
|
+
| Operation | Success callback | Derived result |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `result.map { ... }` | Returns any value | That value, even when the value itself is a TaskResult |
|
|
51
|
+
| `result.flat_map { ... }` | Returns a TaskResult | Waits for and adopts that inner result's terminal value/state |
|
|
52
|
+
| `result.on_complete { \|value, error\| ... }` | Independent completion notification | Returns the original result; the callback does not transform it |
|
|
53
|
+
|
|
54
|
+
A callback can run immediately during registration or on the completing thread.
|
|
55
|
+
There is no callback thread guarantee. `map`/`flat_map` block StandardError
|
|
56
|
+
failures become failures of the derived result, preserving the original error.
|
|
57
|
+
A wrong `flat_map` return becomes a TypeError failure. Missing blocks raise
|
|
58
|
+
ArgumentError at registration. A source or inner **cancelled state** propagates
|
|
59
|
+
as cancelled with the original error. A failed CancellationError, or one raised
|
|
60
|
+
by an application transformation, stays failed.
|
|
61
|
+
|
|
62
|
+
For example, extend each JOB through a second Agent before returning its result:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
evaluations = Phronomy::Execution.run_async(pairs, timeout: 30) do |pair, execution|
|
|
66
|
+
context = execution.invocation_context
|
|
67
|
+
pair.fetch(:author).invoke_async(question, invocation_context: context)
|
|
68
|
+
.flat_map do |answer|
|
|
69
|
+
pair.fetch(:reviewer).invoke_async(answer.fetch(:output), invocation_context: context)
|
|
70
|
+
.map { |review| review.fetch(:output) }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If a JOB raises StandardError or returns something other than a TaskResult,
|
|
76
|
+
that JOB becomes failed. Other JOBs continue. Once all final results settle,
|
|
77
|
+
the whole execution succeeds with the Outcome Array even if it contains
|
|
78
|
+
individual failures or cancellations.
|
|
79
|
+
|
|
80
|
+
## Outcome records and whole-execution deadlines
|
|
81
|
+
|
|
82
|
+
`TaskResult::Outcome` has read-only `index`, `status`, `value` and `error` fields.
|
|
83
|
+
The returned Array and its records are frozen. Values and original exceptions
|
|
84
|
+
are retained by reference, without deep copying or freezing application objects.
|
|
85
|
+
|
|
86
|
+
| Whole execution | Whole TaskResult | Available records |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| All JOBs settled | completed | Successful value is the Outcome Array |
|
|
89
|
+
| `timeout:` won | failed, ExecutionTimeoutError | `error.outcomes` |
|
|
90
|
+
| Explicit or inherited token/deadline cancellation won | cancelled, ExecutionCancellationError | `error.outcomes` |
|
|
91
|
+
|
|
92
|
+
Record statuses are `:completed`, `:failed`, `:cancelled`, and, in an interrupted
|
|
93
|
+
snapshot, `:unfinished`. Unstarted JOBs also have unfinished records. This is
|
|
94
|
+
not another terminal state of TaskResult. A JOB's record refers to its final
|
|
95
|
+
returned result, so completed transformed values are retained; an unfinished
|
|
96
|
+
inner `flat_map` result makes that JOB unfinished.
|
|
97
|
+
|
|
98
|
+
The execution serializes record updates and terminal claims. The first terminal
|
|
99
|
+
claim wins. At timeout/cancellation it fixes the records before requesting child
|
|
100
|
+
cancellation; later notifications cannot rewrite those records. It does not
|
|
101
|
+
retroactively decide using physical worker completion timestamps.
|
|
102
|
+
|
|
103
|
+
`timeout:` covers fan-out start through the final JOB results reaching fan-in.
|
|
104
|
+
`nil` adds no deadline. Zero or negative values fail before starting JOBs. It
|
|
105
|
+
accepts finite real Numeric values usable as elapsed seconds; other types raise
|
|
106
|
+
TypeError, and non-finite/complex values raise ArgumentError. These checks occur
|
|
107
|
+
before JOB starts or cancellation subscriptions. The existing Blocking numeric
|
|
108
|
+
conversion rules are unchanged.
|
|
109
|
+
|
|
110
|
+
`cancellation_token:` accepts nil or a CancellationToken (including subclasses).
|
|
111
|
+
`invocation_context:` accepts nil or an existing InvocationContext (including
|
|
112
|
+
subclasses). Other types raise TypeError at the Execution entrance.
|
|
113
|
+
|
|
114
|
+
`TaskResult#wait_result(timeout:)` sets only that caller's wait limit. It does not
|
|
115
|
+
cancel or alter the underlying result. Pending waits are forbidden on EventLoop.
|
|
116
|
+
An individual Blocking timeout keeps its TimeoutError failure; an individual
|
|
117
|
+
deadline token keeps the cancellation state. These are distinct from the whole
|
|
118
|
+
execution's errors and snapshot.
|
|
119
|
+
|
|
120
|
+
## Context, cancellation and ownership
|
|
121
|
+
|
|
122
|
+
Pass `execution.invocation_context` explicitly to an Agent or to
|
|
123
|
+
`Blocking.call_async(invocation_context: ..., cancellation_token: ...)` for work
|
|
124
|
+
belonging to that execution. The result is bound before it returns to the app;
|
|
125
|
+
its `map`/`flat_map` continuations inherit the scope. A continuation checks the
|
|
126
|
+
scope before running. Explicitly passing the context again when starting an
|
|
127
|
+
inner operation also covers cancellation after the continuation has begun.
|
|
128
|
+
|
|
129
|
+
An optional existing context contributes its user, policy, budget and tracing
|
|
130
|
+
information by reference. Execution derives a new context without modifying
|
|
131
|
+
the supplied one. Existing context cancellation/deadline and the explicit
|
|
132
|
+
Execution token/timeout all remain effective. Controls connect one way into a
|
|
133
|
+
private token. Cancelling one execution or an individual operation does not
|
|
134
|
+
cancel a parent's or another caller's shared token. Agent and Blocking admission
|
|
135
|
+
combine the individual token with the context's controls.
|
|
136
|
+
|
|
137
|
+
Scope cancellation stops unstarted scoped operations/transformations and requests
|
|
138
|
+
cooperative cancellation of running operations. It does not use Thread#raise,
|
|
139
|
+
roll back effects, or guarantee physical worker termination. Offload work and
|
|
140
|
+
owned composition steps retain their physical-completion tracking after logical
|
|
141
|
+
cancellation. `on_complete` notifications still run, and suppressed framework
|
|
142
|
+
continuations settle as cancelled rather than remaining pending.
|
|
143
|
+
|
|
144
|
+
Execution waits only for registered JOBs' returned final results. If a JOB
|
|
145
|
+
starts X and Y but returns Y, the app is responsible for X. Normal completion
|
|
146
|
+
does not discover, join, or sweep-cancel X. If the app explicitly supplied this
|
|
147
|
+
scope to X, its API's cancellation and closed-scope start restrictions still
|
|
148
|
+
apply. A normally closed context cannot be reused to start another operation.
|
|
149
|
+
|
|
150
|
+
## Observing work started elsewhere
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# Source started under its original owner and controls.
|
|
154
|
+
shared_result = existing_agent.invoke_async(question)
|
|
155
|
+
|
|
156
|
+
run = Phronomy::Execution.run_async([shared_result], timeout: 5) do |source, execution|
|
|
157
|
+
execution.observe(source).map { |response| response.fetch(:output).upcase }
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`observe` returns a distinct scoped result. It does not restart, rebind or cancel
|
|
162
|
+
the source, its existing continuations, or its physical work. A JOB may also
|
|
163
|
+
return an external result directly if it only needs to wait for it.
|
|
164
|
+
|
|
165
|
+
`TaskResult.all_settled(results)` is the public wait-only API. It accepts an Array
|
|
166
|
+
of TaskResult instances/subclasses, snapshots the positions, and preserves order
|
|
167
|
+
and duplicate positions. Invalid elements cause an immediate TypeError before
|
|
168
|
+
any source is subscribed. It accepts an empty list and has no timeout/cancel
|
|
169
|
+
parameters. It does not own or infer scopes from its sources. If subsequent
|
|
170
|
+
transformations should be scoped, explicitly use
|
|
171
|
+
`execution.observe(TaskResult.all_settled(results)).map { ... }`.
|
|
172
|
+
|
|
173
|
+
## Whole-result processing and existing layers
|
|
174
|
+
|
|
175
|
+
The whole TaskResult is the scope exit. An outer `map`/`flat_map` does not inherit
|
|
176
|
+
or extend the completed inner scope, whether registered before or after fan-in.
|
|
177
|
+
For example, vote counting belongs in an outer `map`. A slow save belongs in an
|
|
178
|
+
outer `flat_map` returning `Blocking.call_async(timeout: 5) { ... }`. That new
|
|
179
|
+
operation's five seconds start when it is invoked, independently of the first
|
|
180
|
+
execution's deadline. There is no `compose` argument or separate fan-out entrance.
|
|
181
|
+
|
|
182
|
+
Execution owns common runtime coordination. Orchestrator remains above the Agent
|
|
183
|
+
layer: `dispatch_parallel[_async]` retains Agent construction, knowledge
|
|
184
|
+
inheritance, bounded active children and `on_error` policy, and delegates its
|
|
185
|
+
runtime coordination to Execution. AgentExecution remains the durable Agent
|
|
186
|
+
record; ExecutionCoordinator keeps admission, persistence, approval, recovery
|
|
187
|
+
and terminal barriers while reusing the common context/control binding.
|
|
188
|
+
|
|
189
|
+
## Development-release migration
|
|
190
|
+
|
|
191
|
+
- Replace `Phronomy::Task` with `Phronomy::TaskResult`. The old constant and file
|
|
192
|
+
are removed; there is no compatibility alias.
|
|
193
|
+
- Replace `orchestrator.fan_out(agent: klass, inputs: inputs, ...)` with
|
|
194
|
+
`orchestrator.dispatch_parallel(*inputs.map { |input| {agent: klass, input: input} }, ...)`
|
|
195
|
+
when keeping Orchestrator's Agent construction/knowledge/concurrency policy.
|
|
196
|
+
Use Execution directly for application-defined JOB result composition.
|
|
197
|
+
- Replace the asynchronous equivalent with `dispatch_parallel_async` or
|
|
198
|
+
`Execution.run_async` as appropriate. `fan_out` and `fan_out_async` are removed.
|
|
199
|
+
- Keep per-incarnation listeners at Agent creation/load. This change introduces
|
|
200
|
+
no invocation listener blocks, Proc persistence, durable result transforms,
|
|
201
|
+
callback acknowledgements or new scheduler.
|
|
202
|
+
|
|
203
|
+
See examples `32_async_composition` for complete basic and asynchronously
|
|
204
|
+
evaluated majority-vote applications, and `23_bounded_parallel` for bounded
|
|
205
|
+
Agent dispatch. Examples must depend on a core commit containing these APIs.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
Accepted — revised for a single
|
|
5
|
+
Accepted — revised for a single TaskResult completion contract and the OffloadPool
|
|
6
6
|
execution boundary.
|
|
7
7
|
|
|
8
8
|
## Context
|
|
@@ -19,7 +19,7 @@ executes is separate from the object used by callers to observe completion.
|
|
|
19
19
|
## Decision
|
|
20
20
|
|
|
21
21
|
Framework lifecycle coordination uses one Runtime-owned EventLoop and explicit
|
|
22
|
-
FSMSession state/events. **`Phronomy::
|
|
22
|
+
FSMSession state/events. **`Phronomy::TaskResult` is the single caller-facing completion
|
|
23
23
|
handle**, not an execution backend.
|
|
24
24
|
|
|
25
25
|
There are two framework execution mechanisms:
|
|
@@ -28,22 +28,22 @@ There are two framework execution mechanisms:
|
|
|
28
28
|
- **OffloadPool** for synchronous work that must execute on bounded worker OS
|
|
29
29
|
Threads away from EventLoop.
|
|
30
30
|
|
|
31
|
-
Both mechanisms surface asynchronous completion as `Phronomy::
|
|
31
|
+
Both mechanisms surface asynchronous completion as `Phronomy::TaskResult`.
|
|
32
32
|
|
|
33
33
|
Phronomy defines two Tool execution modes:
|
|
34
34
|
|
|
35
35
|
- `:cooperative` — short, EventLoop-safe work, or a specialized asynchronous
|
|
36
36
|
implementation that starts another Phronomy lifecycle and immediately returns
|
|
37
|
-
a
|
|
37
|
+
a TaskResult;
|
|
38
38
|
- `:offloaded` — synchronous work that must not run on EventLoop. It executes in
|
|
39
|
-
the bounded OffloadPool and returns a
|
|
39
|
+
the bounded OffloadPool and returns a TaskResult.
|
|
40
40
|
|
|
41
41
|
Workload classification such as I/O-bound versus CPU-bound is application-owned.
|
|
42
42
|
Phronomy does not provide separate `:blocking_io`, `:cpu_bound`, or
|
|
43
43
|
`:external_process` Tool execution modes.
|
|
44
44
|
|
|
45
45
|
Logical waits are never offloaded merely to obtain concurrency. Waiting for an
|
|
46
|
-
Agent, Workflow, ToolInvocation, approval, timer, or another
|
|
46
|
+
Agent, Workflow, ToolInvocation, approval, timer, or another TaskResult is represented
|
|
47
47
|
as FSMSession state plus a later EventLoop event.
|
|
48
48
|
|
|
49
49
|
## Runtime model
|
|
@@ -65,7 +65,7 @@ Runtime
|
|
|
65
65
|
└─ EventLoop-driven timers
|
|
66
66
|
|
|
67
67
|
EventLoop / FSMSession ─┐
|
|
68
|
-
├─>
|
|
68
|
+
├─> TaskResult = completion handle
|
|
69
69
|
OffloadPool ────────────┘
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -91,11 +91,11 @@ resource domains.
|
|
|
91
91
|
An EventLoop action must not block while waiting for a free OffloadPool queue
|
|
92
92
|
slot. Framework-owned EventLoop-origin submissions therefore use non-blocking
|
|
93
93
|
admission (`on_full: :raise`) and propagate `BackpressureError` through the normal
|
|
94
|
-
FSM/
|
|
94
|
+
FSM/TaskResult completion path.
|
|
95
95
|
|
|
96
96
|
## Timeout and cancellation
|
|
97
97
|
|
|
98
|
-
An OffloadPool submit-time timeout settles the caller-facing
|
|
98
|
+
An OffloadPool submit-time timeout settles the caller-facing TaskResult with
|
|
99
99
|
`TimeoutError`. It does not asynchronously interrupt a running worker Thread. If
|
|
100
100
|
execution has already started, the private Operation becomes abandoned, the
|
|
101
101
|
worker may continue until the submitted synchronous call returns, and that
|
|
@@ -104,8 +104,8 @@ eventual worker result is discarded.
|
|
|
104
104
|
The cancellation token passed to `OffloadPool#submit` follows the same model:
|
|
105
105
|
|
|
106
106
|
- cancellation before worker execution prevents the submitted block from
|
|
107
|
-
starting and settles the
|
|
108
|
-
- cancellation after worker execution starts settles the
|
|
107
|
+
starting and settles the TaskResult as cancelled;
|
|
108
|
+
- cancellation after worker execution starts settles the TaskResult immediately, marks
|
|
109
109
|
the private Operation abandoned, and allows the worker to continue;
|
|
110
110
|
- cancellation does not use `Thread#raise`;
|
|
111
111
|
- application code may observe the same CancellationToken and terminate its own
|
|
@@ -114,18 +114,18 @@ The cancellation token passed to `OffloadPool#submit` follows the same model:
|
|
|
114
114
|
A submit token with a monotonic deadline is connected to the Runtime timer queue,
|
|
115
115
|
so deadline expiry becomes explicit cancellation without a polling Thread.
|
|
116
116
|
|
|
117
|
-
`
|
|
117
|
+
`TaskResult#wait_result(timeout:)` is a synchronous bridge for non-EventLoop callers.
|
|
118
118
|
Its timeout is waiter-local: it raises `TimeoutError` only to that caller and does
|
|
119
|
-
not settle the
|
|
119
|
+
not settle the TaskResult, cancel the submitted operation, or mark an OffloadPool
|
|
120
120
|
Operation abandoned.
|
|
121
121
|
|
|
122
|
-
Framework components own
|
|
122
|
+
Framework components own TaskResult settlement. Application code should request
|
|
123
123
|
operation-wide cancellation through the CancellationToken accepted by the API
|
|
124
|
-
that created the
|
|
125
|
-
|
|
124
|
+
that created the TaskResult rather than calling TaskResult settlement methods directly. A
|
|
125
|
+
TaskResult cancellation must not implicitly cancel a shared CancellationToken in the
|
|
126
126
|
reverse direction.
|
|
127
127
|
|
|
128
|
-
Independent `
|
|
128
|
+
Independent `TaskResult#on_complete` notification callbacks are fault-isolated. Their
|
|
129
129
|
execution thread is not guaranteed, so callbacks must be thread-safe and should
|
|
130
130
|
return quickly. Framework lifecycle code normally converts completion into an
|
|
131
131
|
explicit EventLoop event.
|
|
@@ -136,7 +136,7 @@ explicit EventLoop event.
|
|
|
136
136
|
caller-facing timeout or cancellation won after worker execution had already
|
|
137
137
|
started. `offload_pool_abandoned_active` is current-state: it counts only those
|
|
138
138
|
abandoned operations whose synchronous workers are still occupying OffloadPool
|
|
139
|
-
capacity.
|
|
139
|
+
capacity. TaskResult does not expose abandonment as caller-facing completion state;
|
|
140
140
|
that distinction remains private OffloadPool execution/observability state.
|
|
141
141
|
|
|
142
142
|
## CPU-bound work
|
|
@@ -154,7 +154,7 @@ does not reintroduce a Tool-level workload class.
|
|
|
154
154
|
A component that truly uses a native asynchronous driver and does not create a
|
|
155
155
|
Phronomy-owned OS Thread does not need an OffloadPool worker. If such an
|
|
156
156
|
extension point is formally introduced, it must still adapt completion into a
|
|
157
|
-
`Phronomy::
|
|
157
|
+
`Phronomy::TaskResult`; it must not expose provider-specific futures or private Runtime
|
|
158
158
|
operation records as Phronomy's completion contract.
|
|
159
159
|
|
|
160
160
|
The current VectorStore and Embeddings extension contracts are synchronous; their
|
|
@@ -168,14 +168,14 @@ OffloadPool worker
|
|
|
168
168
|
→ wait_result
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
and equivalently for Workflow/ToolInvocation/
|
|
171
|
+
and equivalently for Workflow/ToolInvocation/TaskResult lifecycles.
|
|
172
172
|
|
|
173
173
|
The correct model is:
|
|
174
174
|
|
|
175
175
|
```text
|
|
176
176
|
parent FSMSession
|
|
177
177
|
→ start child lifecycle
|
|
178
|
-
→ return
|
|
178
|
+
→ return TaskResult immediately
|
|
179
179
|
→ child settles
|
|
180
180
|
→ post parent EventLoop event
|
|
181
181
|
```
|
|
@@ -183,8 +183,8 @@ parent FSMSession
|
|
|
183
183
|
## Consequences
|
|
184
184
|
|
|
185
185
|
- There is one explicit framework continuation model: FSMSession + EventLoop.
|
|
186
|
-
- There is one caller-facing completion abstraction:
|
|
187
|
-
-
|
|
186
|
+
- There is one caller-facing completion abstraction: TaskResult.
|
|
187
|
+
- TaskResult stays thread-free and represents settlement only.
|
|
188
188
|
- OffloadPool owns bounded OS-thread execution and its private Operation state.
|
|
189
189
|
- Tool execution classification remains `:cooperative` / `:offloaded`.
|
|
190
190
|
- CPU/I/O classification and resource sizing are application responsibilities.
|
|
@@ -278,3 +278,14 @@ ambiguous.
|
|
|
278
278
|
|
|
279
279
|
Rejected. Cleanup from an invocation that failed to acquire admission could then
|
|
280
280
|
release the real owner's reservation. Release is owner-aware by construction.
|
|
281
|
+
|
|
282
|
+
## Storage composition refinement (2026-09-17)
|
|
283
|
+
|
|
284
|
+
[ADR-032](032-storage-backend-composition.md) refines the public storage boundary:
|
|
285
|
+
`Persistence` is the domain-facing composed service and `Storage::Backend` is
|
|
286
|
+
the raw storage extension contract. Backend inheritance from `Persistence`, the
|
|
287
|
+
old raw-repository constructor, and `build_transaction_view` are replaced.
|
|
288
|
+
Storage record/error constants move to `Storage`. The single atomic transaction
|
|
289
|
+
domain, durable-state/live-owner split, and failure guarantees in this decision
|
|
290
|
+
remain in force. Earlier API spellings in this decision are historical for this
|
|
291
|
+
amended scope; use the current Backend contract and migration guide.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# ADR-024: EventLoop Single-Writer Agent Runtime State
|
|
2
2
|
|
|
3
|
-
**Status**:
|
|
3
|
+
**Status**: Amended
|
|
4
|
+
**Implementation ownership amendment**: [042-feature-owned-execution-state](042-feature-owned-execution-state.md) moves feature execution state and decisions to Agent/Workflow receivers; the EventLoop thread remains the single writer. Its registration, delivery and drain rules refine the original descriptions below.
|
|
5
|
+
|
|
6
|
+
**Worker input classification amendment**: [045-worker-input-restriction-ownership](045-worker-input-restriction-ownership.md) replaces the concrete rejection list with an execution-boundary marker declared by each owning type. The existing value/behavior restrictions and application-owned opaque-value contract remain unchanged.
|
|
7
|
+
|
|
4
8
|
**Date**: 2026-08-24
|
|
5
9
|
**Partially supersedes**: [ADR-014](014-unified-persistence-durable-state.md) for live Agent Runtime ownership and `AgentExecutionActivation`
|
|
6
10
|
**Complements**: [ADR-010](010-cooperative-first-concurrency.md), [ADR-012](012-canonical-execution-log-and-context-policy.md), [ADR-018](018-durability-guarantees-and-failure-model.md), [ADR-022](022-agent-execution-parent-identity-and-runtime-routing-boundary.md), [ADR-023](023-fsm-session-incarnation-identity-and-routing.md)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# ADR-025: Process-Local Agent Ownership and Runtime Admission
|
|
2
2
|
|
|
3
|
-
**Status**:
|
|
3
|
+
**Status**: Amended
|
|
4
|
+
**Implementation ownership amendment**: [042-feature-owned-execution-state](042-feature-owned-execution-state.md) moves feature execution state and decisions to Agent/Workflow receivers; the EventLoop thread remains the single writer. Its registration, delivery and drain rules refine the original descriptions below.
|
|
5
|
+
|
|
4
6
|
**Date**: 2026-08-24
|
|
7
|
+
**Amended by**: [041-feature-owned-identity-registries](041-feature-owned-identity-registries.md) for registry implementation ownership and its generic Runtime shutdown contract
|
|
5
8
|
**Partially supersedes**: [ADR-014](014-unified-persistence-durable-state.md) for same-process Agent live-instance ownership and top-level execution admission
|
|
6
9
|
**Complements**: [ADR-018](018-durability-guarantees-and-failure-model.md), [ADR-022](022-agent-execution-parent-identity-and-runtime-routing-boundary.md), [ADR-023](023-fsm-session-incarnation-identity-and-routing.md), [ADR-024](024-event-loop-single-writer-agent-runtime.md)
|
|
7
10
|
|
|
@@ -43,8 +46,8 @@ live ownership service.
|
|
|
43
46
|
|
|
44
47
|
### One `agent_id` has one mutable live Agent owner per Runtime
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
`agent_id
|
|
49
|
+
Agent owns the purpose-specific registry implementation, keyed only by
|
|
50
|
+
`agent_id`; Runtime strongly retains one registered instance for its lifetime:
|
|
48
51
|
|
|
49
52
|
```text
|
|
50
53
|
agent_id
|
|
@@ -63,6 +66,13 @@ The registry is separate from EventLoop's `execution_id -> AgentExecutionState`
|
|
|
63
66
|
directory. Agent lifetime and Execution lifetime are different semantic
|
|
64
67
|
lifetimes and must not be represented by one registry.
|
|
65
68
|
|
|
69
|
+
The original implementation placed this registry under Runtime. ADR-041 moves
|
|
70
|
+
it to `Agent::OwnershipRegistry` and removes the feature-specific Runtime
|
|
71
|
+
forwarding methods. Runtime uses only the generic shutdown participant contract
|
|
72
|
+
to close, wait for, and finally detach registered owners. The invariant and
|
|
73
|
+
Runtime-scoped lifetime are unchanged; EventLoop admission is not moved by that
|
|
74
|
+
amendment.
|
|
75
|
+
|
|
66
76
|
### Public Agent construction/resolution semantics
|
|
67
77
|
|
|
68
78
|
The public operations have distinct meanings:
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# ADR-026: Workflow Runtime Admission and Durable Terminal Barrier
|
|
2
2
|
|
|
3
|
-
**Status**:
|
|
3
|
+
**Status**: Amended
|
|
4
|
+
**Implementation ownership amendment**: [042-feature-owned-execution-state](042-feature-owned-execution-state.md) moves feature execution state and decisions to Agent/Workflow receivers; the EventLoop thread remains the single writer. Its registration, delivery and drain rules refine the original descriptions below.
|
|
5
|
+
**Observer failure clarification**: [055-terminal-observer-failure-settlement](055-terminal-observer-failure-settlement.md) keeps terminal observer errors on the ordinary failure path without undoing an already confirmed save.
|
|
6
|
+
**Terminal policy ownership amendment**: [056-workflow-terminal-policy-ownership](056-workflow-terminal-policy-ownership.md) moves Workflow event/outcome interpretation to WorkflowTerminalPolicy while preserving the shared session, durable barrier and uncertainty lifecycle.
|
|
7
|
+
|
|
4
8
|
**Date**: 2026-08-24
|
|
5
9
|
**Partially supersedes**: [ADR-014](014-unified-persistence-durable-state.md) for same-process Workflow admission ownership and terminal durable-barrier ordering
|
|
6
10
|
**Refines**: [ADR-023](023-fsm-session-incarnation-identity-and-routing.md) by removing the transitional Workflow FSMSession-identity reservation bridge
|