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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# ADR-051: Execution Outcome Worker Ownership
|
|
2
|
+
|
|
3
|
+
**Status**: Accepted on the architecture refactoring branch
|
|
4
|
+
**Date**: 2026-09-22
|
|
5
|
+
**Refines**: [024-event-loop-single-writer-agent-runtime](024-event-loop-single-writer-agent-runtime.md),
|
|
6
|
+
[030-agent-handoff-domain-and-durable-responsibility](030-agent-handoff-domain-and-durable-responsibility.md),
|
|
7
|
+
[047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md)
|
|
8
|
+
and [050-approval-resume-snapshot-and-commit-ownership](050-approval-resume-snapshot-and-commit-ownership.md).
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
ExecutionCoordinator owned both EventLoop execution control and Offload result
|
|
13
|
+
persistence. HandoffExecutionCoordinator overrode outcome selection and depended
|
|
14
|
+
on its parent's transaction helpers. Extracting only one side would preserve
|
|
15
|
+
that hidden dependency or accidentally change Handoff precedence. Moving the
|
|
16
|
+
long bodies unchanged would also leave persistence policy mixed with individual
|
|
17
|
+
record fields.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
### Select and persist outcomes in operation workers
|
|
22
|
+
|
|
23
|
+
`Agent::ExecutionOutcomeCommitter#commit_outcome` selects completion, failure,
|
|
24
|
+
approval suspension or owned-child waiting from a captured command. It handles
|
|
25
|
+
persistence exceptions through the existing operation-specific readback rules.
|
|
26
|
+
`Agent::HandoffOutcomeCommitter` specializes that selection and atomic Source
|
|
27
|
+
transfer, sharing ordinary completion, failure and suspension persistence.
|
|
28
|
+
Handoff selection retains its separate callback/error/suspension/transfer order;
|
|
29
|
+
it does not acquire the ordinary worker's child-coordination wait branch.
|
|
30
|
+
|
|
31
|
+
HandoffExecutionCoordinator retains its class identity and inheritance, but now
|
|
32
|
+
only selects the Handoff worker. This selection is internal, not a public SPI.
|
|
33
|
+
Coordinator captures input, submits the worker, validates the returned revision
|
|
34
|
+
and session, applies live state and delivers results. Worker code never calls
|
|
35
|
+
back into Coordinator. Command state and intermediate results remain local to
|
|
36
|
+
one operation; workers hold only Agent service context and Persistence.
|
|
37
|
+
|
|
38
|
+
Agent context is still required by the existing coordination hook, error
|
|
39
|
+
translation, saved Manifest reader and transcript materializer. This is not a
|
|
40
|
+
claim that all Agent references or dependency cycles have been removed.
|
|
41
|
+
`WorkerInputRestricted` marks both worker types; it is not a recursive validator
|
|
42
|
+
of arbitrary application-owned values.
|
|
43
|
+
|
|
44
|
+
### Keep transactions visible in the purpose-level methods
|
|
45
|
+
|
|
46
|
+
`commit_completed` encodes completion records, appends Journal, saves Execution
|
|
47
|
+
and coordination, advances Root and materializes the caller-facing transcript
|
|
48
|
+
inside one transaction, then constructs the result. Record fields and repository
|
|
49
|
+
arguments belong to its helpers. Transcript materialization stays inside the
|
|
50
|
+
transaction so failure rolls back the transition.
|
|
51
|
+
|
|
52
|
+
`commit_failed_outcome` persists audit-only records and the translated failure.
|
|
53
|
+
The Context revision stays unchanged because none of those records is a Context
|
|
54
|
+
candidate. Completion and Handoff advance Context only if the appended records
|
|
55
|
+
contain a candidate. Suspension retains its records in Execution working state,
|
|
56
|
+
advances Root to suspended and does not append Journal or advance Context.
|
|
57
|
+
|
|
58
|
+
`commit_handed_off` validates Source routing and cancellation, persists projected
|
|
59
|
+
Context, transfers routing to a deterministic Target execution ID, and saves
|
|
60
|
+
Source Journal, Execution and Root in one transaction. Target definition lookup
|
|
61
|
+
uses the transaction view; the existing saved Manifest reader keeps its service
|
|
62
|
+
context. Target completion/failure stabilizes routing inside its own existing
|
|
63
|
+
terminal transaction. No Provider/Tool dispatch or application delivery moves
|
|
64
|
+
into these workers.
|
|
65
|
+
|
|
66
|
+
### Preserve the distinct readback contracts
|
|
67
|
+
|
|
68
|
+
| Operation | Existing confirmation rule after an exception | Consequence |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Ordinary/Handoff terminal | Same Agent, terminal Execution, expected revision + 1; then load saved Root, Journal and result | Reuse the persisted outcome; do not write a second terminal transition |
|
|
71
|
+
| Child-coordination wait | Complete saved Execution payload equals the intended waiting record | Return that nonterminal wait; active status alone does not establish a match |
|
|
72
|
+
| Approval suspension | It is nonterminal, so terminal readback does not confirm it | Propagate uncertain completion to the owner |
|
|
73
|
+
|
|
74
|
+
Terminal confirmation is deliberately not changed to DispatchPreparation's
|
|
75
|
+
full intended-payload comparison. Missing, active, mismatched or unreadable
|
|
76
|
+
results do not authorize another write. Readback errors retain their existing
|
|
77
|
+
propagation behavior. Recovered success retains the existing smaller result
|
|
78
|
+
shape; recovered failures use `RecoverySupport.error_from_failure`, including
|
|
79
|
+
its generic Phronomy::Error representation. This extraction does not normalize
|
|
80
|
+
those results or introduce a stronger commit-certainty protocol.
|
|
81
|
+
|
|
82
|
+
### Preserve EventLoop result authority and delivery
|
|
83
|
+
|
|
84
|
+
The owner still waits for physical work quiescence before terminal persistence.
|
|
85
|
+
A stale result changes no live state, admission or Task. Completed/failed/Handoff
|
|
86
|
+
results release ownership and deliver their existing events before settling
|
|
87
|
+
waiters. Suspension retains admission and leaves the original Task pending;
|
|
88
|
+
exact observers receive the existing approval-required failure. Child waiting
|
|
89
|
+
releases ownership and fails waiters with recovery required.
|
|
90
|
+
|
|
91
|
+
A worker error for ordinary execution retains recovery_required admission and
|
|
92
|
+
pending waiters. The existing coordination-metadata error path releases the
|
|
93
|
+
owner and fails waiters. Those branches are not merged into unconditional cleanup.
|
|
94
|
+
Live Handoff request/Manifest delivery handles remain outside the worker command.
|
|
95
|
+
|
|
96
|
+
### Internal types
|
|
97
|
+
|
|
98
|
+
ExecutionOutcomeCommitter owns `Command`, `Outcome`, `TerminalView` and
|
|
99
|
+
`HandoffTerminalView`. Coordinator retains its previous constant paths as aliases
|
|
100
|
+
to the same classes. All four member lists stay unchanged; their canonical Ruby
|
|
101
|
+
names change. TerminalDelivery and TerminalCommitReady remain owner-local.
|
|
102
|
+
These types are internal and are not serialized by class name. Public API, saved
|
|
103
|
+
schemas, Agent/Handoff Coordinator identity and caller selection remain unchanged.
|
|
104
|
+
|
|
105
|
+
## Guarantees and verification
|
|
106
|
+
|
|
107
|
+
Using [ADR-018](018-durability-guarantees-and-failure-model.md):
|
|
108
|
+
|
|
109
|
+
| Subject / property | Provider | Failure / boundary | Result |
|
|
110
|
+
| --- | --- | --- | --- |
|
|
111
|
+
| Terminal or Handoff transition atomicity (G5), stale write rejection (G8) | Existing transaction and repository CAS | F0/F2; no new framework X0 | CONDITIONAL on conforming Persistence backend |
|
|
112
|
+
| No second terminal write after uncertain response | Operation-specific worker readback and owner recovery path | F1; no new worker X0 | YES; no stronger outcome-certainty guarantee |
|
|
113
|
+
| Current execution alone may apply worker results | Existing EventLoop owner/revision/session guards | F2/F3 and delayed completion | YES within the same Runtime ownership contract |
|
|
114
|
+
| Logical continuation from saved Source/Target state (G4) | Existing Handoff recovery and exact Target identity | F4; later execution can cross X0 | CONDITIONAL on existing recovery contracts |
|
|
115
|
+
| Cross-process exclusion (G7), external duplicate prevention (G9), exactly once (G10) | No new protocol added | F1/F2/F4 across X0 | NO new guarantee |
|
|
116
|
+
|
|
117
|
+
Behavior tests exercise completion/rejection, failure categories, suspension,
|
|
118
|
+
transaction rollback, Root CAS, response loss and readback mismatches/failures,
|
|
119
|
+
child waiting, atomic Source transfer, routing conflict/cancellation and Target
|
|
120
|
+
completion/failure. Existing suites cover physical quiescence, stale results,
|
|
121
|
+
callback policy, approval recovery and durable Handoff recovery. Unit fault
|
|
122
|
+
injections do not represent real process loss or live external service failures.
|
|
123
|
+
Architecture guards now inspect the actual workers and disallow repository
|
|
124
|
+
reloads in Coordinator without the old terminal-helper exceptions.
|
|
125
|
+
|
|
126
|
+
## Owner-control review (2026-09-22)
|
|
127
|
+
|
|
128
|
+
The fifth stage keeps one execution owner and makes its remaining result paths
|
|
129
|
+
read as validation, state application and continuation/delivery. Preparation
|
|
130
|
+
recovery separates failed-outcome settlement from session restart. Approval
|
|
131
|
+
resume separates committed-state installation and tracing from FSM entry.
|
|
132
|
+
Terminal results retain visible outcome selection while private methods handle
|
|
133
|
+
the distinct waiting, suspension, completion, Handoff and failure deliveries.
|
|
134
|
+
Only the identical execution/admission release is shared across terminal paths.
|
|
135
|
+
Initial preparation snapshot construction is named explicitly; start/resume
|
|
136
|
+
admission flags, submission flags and their rescue decisions remain together.
|
|
137
|
+
|
|
138
|
+
No transaction, authority condition, failure policy, public type or class owner
|
|
139
|
+
changes. Direct Ready delivery tests run on the real EventLoop/ExecutionRegistry
|
|
140
|
+
and check stale-result isolation, state-before-notification, notification-before-
|
|
141
|
+
settlement, fallback waiters, suspension, uncertainty and recovery/resume failure
|
|
142
|
+
boundaries. These tests also pass against the preceding implementation, documenting
|
|
143
|
+
preserved behavior rather than a new contract. F0/F1/F3 are injected locally;
|
|
144
|
+
no new X0 operation or stronger F4 guarantee is claimed.
|
|
145
|
+
|
|
146
|
+
## Remaining work
|
|
147
|
+
|
|
148
|
+
The execution-owner decomposition and final readability review are implemented.
|
|
149
|
+
Tool restoration, SharedState, Storage domain responsibilities and Workflow
|
|
150
|
+
terminal ownership remain separate work items. Further owner splitting needs a
|
|
151
|
+
concrete responsibility or failure-boundary reason; file length alone is not one.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ADR-052: Tool Invocation Restoration Ownership
|
|
2
|
+
|
|
3
|
+
**Status**: Accepted on the architecture refactoring branch
|
|
4
|
+
**Date**: 2026-09-22
|
|
5
|
+
**Refines**: [046-agent-responsibility-layout-and-shared-records](046-agent-responsibility-layout-and-shared-records.md)
|
|
6
|
+
and [047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md).
|
|
7
|
+
|
|
8
|
+
## Problem
|
|
9
|
+
|
|
10
|
+
InvocationRestorer decoded saved Tool batches and directly assigned
|
|
11
|
+
ToolInvocation's status, result, authorization decision and approval evidence.
|
|
12
|
+
Moving restoration into its own module in ADR-046 left that knowledge of another
|
|
13
|
+
object's representation intact. ADR-047 explicitly retained it as separate work.
|
|
14
|
+
A change to ToolInvocation's state representation therefore required Recovery to
|
|
15
|
+
track its instance variables as well as the saved format.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Keep the existing classes and introduce the internal
|
|
20
|
+
`ToolInvocation#restore_state!(status:, result: nil, approval_item: nil)` operation.
|
|
21
|
+
It is Ruby-public for the Recovery caller and classified `@api private`; it is
|
|
22
|
+
not an Application extension SPI. Its input is materialized semantic state,
|
|
23
|
+
not an Execution record or a persistence repository.
|
|
24
|
+
|
|
25
|
+
InvocationRestorer retains saved snapshot key normalization, Tool Call and Tool
|
|
26
|
+
lookup, construction of ordinary or missing-Tool invocations, and matching the
|
|
27
|
+
approval item by invocation ID. It converts the saved status to a Symbol and
|
|
28
|
+
passes the saved result and matched approval item to the newly constructed
|
|
29
|
+
ToolInvocation. No ToolInvocation instance variable is assigned by Recovery.
|
|
30
|
+
The separate AgentInvocation batch-ID assignment is outside this change.
|
|
31
|
+
|
|
32
|
+
ToolInvocation owns the supported-state dispatch and approval evidence copy:
|
|
33
|
+
|
|
34
|
+
| Saved state | Existing behavior retained |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| awaiting_approval | Validate arguments unless already terminal, restore require_approval and the waiting state |
|
|
37
|
+
| authorized | Validate arguments unless already terminal, restore allow and the authorized state |
|
|
38
|
+
| completed | Restore the saved result, including nil or false, and completed state |
|
|
39
|
+
| rejected | Restore rejection and its decision |
|
|
40
|
+
| failed | Restore the existing generic Tool preflight failure |
|
|
41
|
+
| cancelled | Restore cancellation |
|
|
42
|
+
| Other state | Raise ExecutionRehydrationRequiredError before applying approval evidence |
|
|
43
|
+
|
|
44
|
+
The entry method reads as saved-state application followed by saved approval
|
|
45
|
+
evidence application. Private methods contain each operation's implementation.
|
|
46
|
+
Evidence copying preserves the existing immutable facts and reason semantics,
|
|
47
|
+
including an explicit nil facts value. A missing approval item leaves constructor
|
|
48
|
+
defaults intact. Saved display evidence is not a new policy evaluation input.
|
|
49
|
+
|
|
50
|
+
The caller supplies a newly constructed invocation before session installation.
|
|
51
|
+
This is not a general rollback or arbitrary live-state replacement interface.
|
|
52
|
+
The existing EventLoop ownership and continuation validation remain unchanged.
|
|
53
|
+
The old internal `InvocationRestorer.restore_tool_snapshot!` entry is removed;
|
|
54
|
+
no forwarding compatibility wrapper is introduced for this private helper.
|
|
55
|
+
|
|
56
|
+
## Compatibility and limits
|
|
57
|
+
|
|
58
|
+
No public API, RBS contract, saved schema, approval decision, external replay
|
|
59
|
+
eligibility, missing-Tool rule, or callback sequence changes. The existing
|
|
60
|
+
validation behavior is retained, including the handling of current Tool
|
|
61
|
+
validation errors and missing definitions; this refactor does not add a new
|
|
62
|
+
schema-migration or saved-state validation policy. It does not restore arbitrary
|
|
63
|
+
Application Tool instance variables.
|
|
64
|
+
|
|
65
|
+
Restoration itself does not run authorization policy, execute a Tool, acquire a
|
|
66
|
+
Runtime, perform persistence I/O or notify listeners. This does not suppress the
|
|
67
|
+
existing recovery installation notification of an outstanding approval request.
|
|
68
|
+
Load-time notification and later approval/rejection dispatch keep their existing
|
|
69
|
+
owners. External outcome-unknown resolution and framework operation recovery
|
|
70
|
+
remain with RecoveryCoordinator and the execution owner.
|
|
71
|
+
|
|
72
|
+
Under [018-durability-guarantees-and-failure-model](018-durability-guarantees-and-failure-model.md),
|
|
73
|
+
this change concerns operation-local reconstruction from already materialized
|
|
74
|
+
facts. Unsupported input is an F0 rejection without X0 dispatch by this operation.
|
|
75
|
+
Same logical execution resumption after F4 remains CONDITIONAL on the existing
|
|
76
|
+
confirmed saved state and operation-specific recovery contract. F1 outcome
|
|
77
|
+
resolution is unchanged. No new external-effect exactly-once guarantee follows.
|
|
78
|
+
|
|
79
|
+
## Verification
|
|
80
|
+
|
|
81
|
+
Replace tests that mock another object's instance-variable writes with real
|
|
82
|
+
ToolInvocation behavior tests. Run identical assertions against the old owner
|
|
83
|
+
boundary and the new operation. Cover all six states, unsupported states, saved
|
|
84
|
+
results, missing definitions, immutable approval evidence, and dispatch gating
|
|
85
|
+
before and after approval. Existing restart tests exercise approval and rejection
|
|
86
|
+
through actual recovery installation and session continuation. Run the ordinary
|
|
87
|
+
and integration suites, API snapshot, annotations, RBS, examples and SQLite
|
|
88
|
+
persistence checks. No test count implies a stronger F4 or X0 guarantee.
|
|
89
|
+
|
|
90
|
+
SharedState coordination ownership, Storage domain responsibilities and Workflow
|
|
91
|
+
terminal persistence remain separate work. This decision completes only the Tool
|
|
92
|
+
restoration ownership item carried forward by ADR-047.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ADR-053: SharedState Coordination Ownership
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted on the architecture refactoring branch, 2026-09-22.
|
|
6
|
+
This resolves the deferred SharedState ownership decision in
|
|
7
|
+
[046-agent-responsibility-layout-and-shared-records](046-agent-responsibility-layout-and-shared-records.md).
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
SharedState creates an invocation-local findings store, equips several Agent
|
|
12
|
+
definitions with read/write Tools, invokes members in declaration order, repeats
|
|
13
|
+
cycles until a configured limit, and aggregates the findings. It is not an
|
|
14
|
+
individual Agent's state container or execution coordinator. These responsibilities
|
|
15
|
+
belong to MultiAgent, alongside the other multi-Agent coordination patterns.
|
|
16
|
+
|
|
17
|
+
Its implementation also mixes cycle orchestration with individual member calls,
|
|
18
|
+
and researcher instrumentation with the bodies of injected Tool definitions.
|
|
19
|
+
Moving a file alone would leave those abstraction levels mixed.
|
|
20
|
+
|
|
21
|
+
## Decision
|
|
22
|
+
|
|
23
|
+
1. Move the implementation to `multi_agent/shared_state.rb`, exposing the
|
|
24
|
+
Experimental `Phronomy::MultiAgent::SharedState` and its nested `KnowledgeStore`.
|
|
25
|
+
Remove `Phronomy::Agent::SharedState` without an alias on this branch, following
|
|
26
|
+
the coordination ownership precedent in
|
|
27
|
+
[034-handoff-runner-coordination-ownership](034-handoff-runner-coordination-ownership.md).
|
|
28
|
+
Update examples, RBS and the [migration guide](../migrations/shared-state-multi-agent.md)
|
|
29
|
+
together. Ordinary consumers continue to `require "phronomy"`.
|
|
30
|
+
2. Express invocation as termination validation, store creation, member
|
|
31
|
+
coordination and result aggregation. Express each cycle as ordered member
|
|
32
|
+
invocation followed by stopping decisions. Build the two injected Tool
|
|
33
|
+
definitions in named private methods. Keep these responsibilities in the same
|
|
34
|
+
class; no new execution manager or general coordination abstraction is needed.
|
|
35
|
+
3. Preserve the DSL, method parameters, prompt text, Tool descriptions/schema,
|
|
36
|
+
cooperative execution mode, original Tool aliases, result shape and exceptions.
|
|
37
|
+
Members remain sequential; findings from an earlier member are visible to the
|
|
38
|
+
next member in the same cycle. `terminate_when` still takes precedence over
|
|
39
|
+
timeout after each complete cycle, and timeout does not interrupt a member.
|
|
40
|
+
4. Preserve the generated Agent definition ID prefix
|
|
41
|
+
`Phronomy::Agent::SharedState::Instrumented/` and instrumentation version 1.
|
|
42
|
+
This string is semantic identity, not a Ruby constant lookup or old-name alias.
|
|
43
|
+
Namespace placement alone must not change the wrapped definition revision.
|
|
44
|
+
|
|
45
|
+
## Consequences and limits
|
|
46
|
+
|
|
47
|
+
This is a breaking rename of an Experimental public API, including the nested
|
|
48
|
+
store class name. The Stable/Beta API snapshot does not enumerate SharedState;
|
|
49
|
+
an unchanged snapshot does not establish complete public API compatibility.
|
|
50
|
+
|
|
51
|
+
The store is still newly created for each invocation and is not durable shared
|
|
52
|
+
Agent state. The coordinator adds no resume, cancellation, asynchronous execution,
|
|
53
|
+
transaction, F4 or X0 guarantee. `invoke` still accepts `config:` without
|
|
54
|
+
forwarding it to the member calls. Revising that behavior is a separate change.
|
|
55
|
+
|
|
56
|
+
No persistence schema or saved-record migration is introduced. Individual Agent
|
|
57
|
+
persistence keeps its existing rules; this move does not make the in-memory
|
|
58
|
+
coordination lifetime resumable. The method extraction slightly increases source
|
|
59
|
+
length and does not eliminate unrelated dependency cycles.
|
|
60
|
+
|
|
61
|
+
Storage domain responsibilities and Workflow terminal ownership remain separate
|
|
62
|
+
work items. This decision does not reopen the completed ExecutionCoordinator split.
|
|
63
|
+
|
|
64
|
+
## Verification obligations
|
|
65
|
+
|
|
66
|
+
Verify new namespace loading, absence of the old alias, repeated eager-load
|
|
67
|
+
identity and no Runtime startup during loading. Exercise existing coordination,
|
|
68
|
+
stopping, aggregation, Tool alias and definition identity tests. Check the full
|
|
69
|
+
and integration suites, offline examples, RBS, annotations, style and built gem
|
|
70
|
+
contents, including removal of the old implementation path. Record executed
|
|
71
|
+
results and untested environments separately in the delivery review.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ADR-054: One Workflow Terminal Save Implementation
|
|
2
|
+
|
|
3
|
+
## Status and scope
|
|
4
|
+
|
|
5
|
+
Accepted on the architecture refactoring branch, 2026-09-22.
|
|
6
|
+
Amends only the Workflow recovery prepend requirement in
|
|
7
|
+
[038-responsibility-based-source-layout](038-responsibility-based-source-layout.md).
|
|
8
|
+
The durable barrier in
|
|
9
|
+
[026-workflow-runtime-admission-and-durable-terminal-barrier](026-workflow-runtime-admission-and-durable-terminal-barrier.md)
|
|
10
|
+
and the receiver ownership in
|
|
11
|
+
[042-feature-owned-execution-state](042-feature-owned-execution-state.md)
|
|
12
|
+
remain unchanged.
|
|
13
|
+
|
|
14
|
+
## Problem
|
|
15
|
+
|
|
16
|
+
WorkflowRunner defines terminal save submission, but WorkflowRecovery defines
|
|
17
|
+
the same method and is prepended during application loading. It never calls
|
|
18
|
+
`super`. The active implementation reconciles uncertain save outcomes against
|
|
19
|
+
durable pre/post state; the shadowed implementation does not. Reading or editing
|
|
20
|
+
the Runner alone can therefore target code that is not actually used.
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
Move the currently active submission and F1 reconciliation into WorkflowRunner.
|
|
25
|
+
Delete the shadowed implementation, the private WorkflowRecovery module and its
|
|
26
|
+
explicit installation. Do not introduce a new class or compatibility alias for
|
|
27
|
+
the removed private override.
|
|
28
|
+
|
|
29
|
+
Express terminal save submission as admission marking, immutable command
|
|
30
|
+
construction, Offload submission and session-local result delivery. Named private
|
|
31
|
+
methods own saving/reconciliation and delivery separately. The existing Command
|
|
32
|
+
and Result Data classes retain their identity and members.
|
|
33
|
+
|
|
34
|
+
The worker saves once. Portable ConflictError, NotFoundError, SerializationError
|
|
35
|
+
and UnsupportedBackendError remain known failures without readback. Other save
|
|
36
|
+
errors use the existing authoritative snapshot comparison: exact post-state is
|
|
37
|
+
success, pre-state preserves the original failure, and conflict or failed
|
|
38
|
+
readback remains outcome-unknown. No automatic save retry is introduced.
|
|
39
|
+
|
|
40
|
+
The existing session sink receives `workflow_terminal_persistence_result`.
|
|
41
|
+
Offload completion errors still become outcome-unknown, and rejected sink
|
|
42
|
+
delivery retains its existing warning. Runner still marks admission and captures
|
|
43
|
+
the snapshot on EventLoop; workers only use that captured command for persistence.
|
|
44
|
+
|
|
45
|
+
## Compatibility and guarantees
|
|
46
|
+
|
|
47
|
+
This is an internal ownership/readability change. Public Workflow/Persistence
|
|
48
|
+
APIs, Backend SPI, snapshots, revisions, error messages and the session event
|
|
49
|
+
protocol are unchanged. Removing the prepend can make WorkflowRunner load lazily
|
|
50
|
+
under ordinary `require "phronomy"`; first access and eager loading must retain
|
|
51
|
+
the same recovery behavior without starting Runtime.
|
|
52
|
+
|
|
53
|
+
F1 save certainty remains CONDITIONAL on authoritative readback matching the
|
|
54
|
+
existing expected pre-state or intended post-state rules. It is not established
|
|
55
|
+
merely by receiving an exception. F0 portable failures follow the existing known
|
|
56
|
+
failure path. Runtime release, success notification and Task settlement still
|
|
57
|
+
wait for EventLoop-owned session acceptance; uncertain outcomes keep the existing
|
|
58
|
+
recovery-required behavior. F4 readability depends on the backend retaining
|
|
59
|
+
confirmed data. X0 external effects are outside this save, and this change adds
|
|
60
|
+
no execution replay, distributed ownership or exactly-once guarantee.
|
|
61
|
+
|
|
62
|
+
FSMSession still owns interpretation of the Workflow terminal persistence
|
|
63
|
+
event. Moving that policy out of Engine is a separate next step; this decision
|
|
64
|
+
does not claim that the entire Workflow terminal ownership issue is resolved.
|
|
65
|
+
Storage's fixed repository slots and Agent watermark also remain separate work.
|
|
66
|
+
|
|
67
|
+
## Verification
|
|
68
|
+
|
|
69
|
+
Characterize the effective submission path before and after the change, including
|
|
70
|
+
immutable snapshots, known errors, F1 post/pre/conflicting/unreadable outcomes,
|
|
71
|
+
single-save behavior and failed Offload completion delivery. Preserve existing
|
|
72
|
+
real-runtime tests for delayed save, stream barriers, admission release and
|
|
73
|
+
uncertain outcomes. Verify ordinary/eager loading without the override, full and
|
|
74
|
+
integration suites, API/RBS/annotations/style, offline examples and built gem
|
|
75
|
+
contents. Keep results from tests actually run distinct from unavailable live
|
|
76
|
+
Provider or database-server evidence.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# ADR-055: Settle Terminal Observer Failures
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted on the architecture refactoring branch, 2026-09-23.
|
|
6
|
+
Clarifies terminal observer failure ordering in
|
|
7
|
+
[026-workflow-runtime-admission-and-durable-terminal-barrier](026-workflow-runtime-admission-and-durable-terminal-barrier.md).
|
|
8
|
+
It does not move Workflow terminal policy out of FSMSession; that remains W2b.
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
FSMSession marked itself done before delivering a pending stable-state observer
|
|
13
|
+
notification. If that observer raised at a wait state or a declared terminal
|
|
14
|
+
state, the existing error handler saw done and returned without an error event.
|
|
15
|
+
The synchronous stream caller remained blocked and Workflow admission stayed
|
|
16
|
+
owned. Durable executions could already have saved their terminal snapshot.
|
|
17
|
+
|
|
18
|
+
The existing observer-exception test covered an automatic transition's ordinary
|
|
19
|
+
stable notification, before terminalization. It did not cover the deferred
|
|
20
|
+
terminal notification. Six real-runtime observations on both pre-Refactor-31 and
|
|
21
|
+
applied Refactor-31 source reproduce four affected cases and two unaffected
|
|
22
|
+
automatic-transition controls. This is not a regression introduced by Refactor 31.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
|
|
26
|
+
Keep the existing terminal lifecycle selection, invoke the pending stable
|
|
27
|
+
observer, and set done only after the observer returns successfully. If it
|
|
28
|
+
raises, the existing start/handle/request error boundary emits the ordinary
|
|
29
|
+
error event with the original exception. EventLoop retires the session and the
|
|
30
|
+
existing Runner completion path releases admission before failing the caller.
|
|
31
|
+
|
|
32
|
+
Preserve successful notification-before-terminal-event ordering. All transitions
|
|
33
|
+
remain on EventLoop. Do not introduce a new class, callback retry, extra save,
|
|
34
|
+
Task settlement path, or direct admission mutation from the observer.
|
|
35
|
+
|
|
36
|
+
## Durable meaning and limits
|
|
37
|
+
|
|
38
|
+
A durable terminal observer runs only after the session accepts a known-success
|
|
39
|
+
save result. Observer failure therefore means notification failed; it does not
|
|
40
|
+
mean the save failed, and must not erase, roll back, or repeat the saved record.
|
|
41
|
+
The caller receives the original observer exception. Applications must not
|
|
42
|
+
assume that every raised stream exception proves non-commit, nor replay external
|
|
43
|
+
effects automatically. This adds no exactly-once or crash-atomic notification
|
|
44
|
+
guarantee; existing F1/F4/X0 limits remain.
|
|
45
|
+
|
|
46
|
+
Known save failure and unresolved save uncertainty are unchanged. In particular,
|
|
47
|
+
the outcome-unknown path still retires the session without falsely settling the
|
|
48
|
+
Workflow Task or releasing its recovery-required admission.
|
|
49
|
+
|
|
50
|
+
## Verification
|
|
51
|
+
|
|
52
|
+
Add public stream regressions for wait/declared-terminal boundaries, each with
|
|
53
|
+
and without persistence. Require the same exception object, one notification
|
|
54
|
+
on EventLoop, caller completion, session retirement and admission release.
|
|
55
|
+
For durable cases, also require the existing snapshot and revision 1 to remain.
|
|
56
|
+
Keep test cleanup bounded so these tests can demonstrate failure on old source.
|
|
57
|
+
|
|
58
|
+
Run the shared FSMSession/Agent/Tool tests, Workflow admission and F1 tests, full
|
|
59
|
+
and integration suites, API/RBS/style, examples and real SQLite. Keep W2b policy
|
|
60
|
+
extraction in a later package after application verification of this fix.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# ADR-056: Workflow-Owned Terminal Policy
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted on the architecture refactoring branch, 2026-09-23.
|
|
6
|
+
Amends the implementation ownership in
|
|
7
|
+
[ADR-026](026-workflow-runtime-admission-and-durable-terminal-barrier.md).
|
|
8
|
+
Preserves the ordering and failure semantics clarified by
|
|
9
|
+
[ADR-055](055-terminal-observer-failure-settlement.md).
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
FSMSession is used by WorkflowRunner, AgentInvocationSessionBuilder and
|
|
14
|
+
ToolInvocationSessionBuilder. It belongs to Engine, but it directly recognized
|
|
15
|
+
Workflow persistence events and interpreted success, known failure and unknown
|
|
16
|
+
save outcomes. Moving the entire session would couple Agent and Tool to Workflow.
|
|
17
|
+
Runner already owns the one terminal save and F1 reconciliation (ADR-054), and
|
|
18
|
+
WorkflowExecutionRegistry already owns admission and owner tokens (ADR-042).
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
WorkflowRunner injects a WorkflowTerminalPolicy only for durable execution.
|
|
23
|
+
Ephemeral Workflow, Agent and Tool sessions keep their immediate terminal path.
|
|
24
|
+
The policy holds the persistence callback, not live session state or admission.
|
|
25
|
+
|
|
26
|
+
The private session protocol is:
|
|
27
|
+
|
|
28
|
+
- `start(terminal_type:, context:, event_sink:)` begins the barrier on EventLoop.
|
|
29
|
+
Its return value does not authorize completion. Results use the bound sink.
|
|
30
|
+
- `handles?(event)` recognizes a policy event, including an early event that
|
|
31
|
+
must be discarded by the session before any terminal request.
|
|
32
|
+
- `decision_for(event)` interprets an accepted event and returns the immutable
|
|
33
|
+
`FSMProtocol::TerminalDecision(action:, error:)` value.
|
|
34
|
+
|
|
35
|
+
WorkflowTerminalPolicy maps success to `complete`, known failure to `fail` with
|
|
36
|
+
its original error or the existing fallback error, and outcome unknown to
|
|
37
|
+
`retire` with its diagnostic error. Invalid Workflow outcomes remain errors.
|
|
38
|
+
FSMSession knows these generic actions, not the Workflow event or outcome values.
|
|
39
|
+
An unsupported action fails through the existing session error path.
|
|
40
|
+
|
|
41
|
+
FSMSession alone owns the pending terminal type, stable-notification flag and
|
|
42
|
+
acceptance state. It enters `awaiting_terminal` before starting the policy,
|
|
43
|
+
ignores ordinary events during that wait, ignores early policy events, and
|
|
44
|
+
accepts at most one terminal decision. The policy must not maintain a second
|
|
45
|
+
pending lifecycle or mutate the session from a worker.
|
|
46
|
+
|
|
47
|
+
`complete` delivers any deferred stable notification before marking done and
|
|
48
|
+
posting the terminal event. `fail` uses the existing failure path. `retire`
|
|
49
|
+
marks the session retired and uses EventLoop's existing recovery-required
|
|
50
|
+
management route without settling a result. Runner persistence/F1 code,
|
|
51
|
+
Registry ownership and EventLoop retirement/shutdown code remain unchanged.
|
|
52
|
+
|
|
53
|
+
## Uncertainty and shutdown
|
|
54
|
+
|
|
55
|
+
Retirement for an unknown save result removes the concrete routing session and
|
|
56
|
+
retains recovery-required admission. It does not complete or fail the caller.
|
|
57
|
+
The old sink cannot target a subsequent incarnation. Normal Runtime shutdown
|
|
58
|
+
clears Registry ownership and terminates the dispatcher, but does not synthesize
|
|
59
|
+
a Workflow result: the unresolved caller remains pending. Unexpected dispatcher
|
|
60
|
+
failure has its separate existing waiter cleanup. These are preserved behaviors,
|
|
61
|
+
not a new guarantee of automatic recovery or a stronger F1/F4/X0 contract.
|
|
62
|
+
|
|
63
|
+
## Compatibility and non-goals
|
|
64
|
+
|
|
65
|
+
`terminal_barrier:` is replaced by private `terminal_policy:` with no alias.
|
|
66
|
+
The internal lifecycle names change to `awaiting_terminal` and `retired`.
|
|
67
|
+
Neither constructor injection nor these states are a new public plugin API.
|
|
68
|
+
Without an injected policy, Engine no longer reserves a Workflow-only event
|
|
69
|
+
name. Workflow's producer emits that event only for its durable execution path.
|
|
70
|
+
Session identity, EventSink correlation, public Workflow/Agent/Tool APIs,
|
|
71
|
+
Workflow result types, durable records and Storage SPI are unchanged.
|
|
72
|
+
|
|
73
|
+
Do not move snapshot persistence into the policy, settle caller Tasks there,
|
|
74
|
+
rename outcome strings while leaving interpretation in Engine, introduce a
|
|
75
|
+
subclass/prepend override, or duplicate admission. A small new policy and value
|
|
76
|
+
type clarify ownership; reducing the total line count is not the objective.
|
|
77
|
+
|
|
78
|
+
## Verification
|
|
79
|
+
|
|
80
|
+
The generic session contract uses a non-Workflow event and policy, exercising
|
|
81
|
+
early/ordinary/duplicate events, completion, failure, retirement, submission
|
|
82
|
+
error, malformed decisions and the immediate default path. Workflow tests cover
|
|
83
|
+
invalid outcomes and the existing missing-error fallback through Runner wiring.
|
|
84
|
+
Real Runtime tests cover F1 pre-state, conflicting/unreadable readback, retained
|
|
85
|
+
admission and normal shutdown, stale sinks across incarnations, and submission
|
|
86
|
+
rejection. Existing delayed-save, halted-stream, observer-failure, Agent/Tool and
|
|
87
|
+
F1 tests remain acceptance gates; rejected result delivery must not retry save.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# ADR-057: Storage update constraints and explicit transaction boundaries
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted for implementation in Refactor 34 (Storage S2a).
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
The raw fixed-repository/error portions are amended by [ADR-058](058-neutral-storage-primitives.md). Domain ownership and applicable transaction/uncertainty decisions remain in force.
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
The existing eight-repository SPI had three concrete behavioral differences.
|
|
13
|
+
InMemory allowed an inactive Agent execution to become active while another
|
|
14
|
+
execution for that Agent was active. SQL rejected that update. SQL Journal
|
|
15
|
+
append serialized each record immediately before inserting it; catching an
|
|
16
|
+
invalid later record inside the outer transaction could commit earlier rows
|
|
17
|
+
without advancing the head. Explicit nested InMemory transactions restored an
|
|
18
|
+
inner snapshot, whereas ActiveRecord's default nesting joined the outer scope.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
Keep the existing SPI and data formats while closing these differences.
|
|
23
|
+
|
|
24
|
+
1. InMemory checks active-owner uniqueness on Agent execution updates under the
|
|
25
|
+
same Monitor as revision validation and writes. Exclude the updated identity.
|
|
26
|
+
Report `Storage::ActiveExecutionConflictError` and leave record and revision
|
|
27
|
+
unchanged on rejection. Team terminal execution reactivation remains forbidden.
|
|
28
|
+
2. Both SQL Journal adapters serialize the complete batch and normalize the
|
|
29
|
+
expected position before any writes. Preserve ID checks, lock order, CAS,
|
|
30
|
+
return values and transaction-bound connection access.
|
|
31
|
+
3. Explicit `Backend#transaction` / `Persistence#transaction` nesting on the same
|
|
32
|
+
backend and synchronous execution context uses savepoint semantics. Roll back
|
|
33
|
+
a failed inner scope and re-raise the same exception. The outer scope may catch
|
|
34
|
+
it and continue. Inner success is not an independent commit: outer failure
|
|
35
|
+
rolls back both. SQL uses `requires_new: true` on the same checked-out
|
|
36
|
+
connection. InMemory retains its reentrant Monitor and snapshots.
|
|
37
|
+
|
|
38
|
+
The SQL wrappers also re-raise `ActiveRecord::Rollback` after ActiveRecord rolls
|
|
39
|
+
back and consumes it. The Storage API does not use an exception as a successful
|
|
40
|
+
return value. Repository operations on a bound view still join that scope; this
|
|
41
|
+
change does not introduce per-operation savepoints or separate connections.
|
|
42
|
+
|
|
43
|
+
## Compatibility and migration
|
|
44
|
+
|
|
45
|
+
This intentionally changes SQL behavior. If an application catches an inner
|
|
46
|
+
failure, writes from that failed inner block will no longer remain in the outer
|
|
47
|
+
transaction. Catch outside the explicit inner block and re-read any conditions
|
|
48
|
+
needed to continue. Catching a database failure inside the same transaction view
|
|
49
|
+
and continuing is not a portable recovery contract; propagate it or isolate the
|
|
50
|
+
operation in an explicit inner transaction before executing it.
|
|
51
|
+
|
|
52
|
+
`ActiveRecord::Rollback` now propagates from the Storage/Persistence boundary.
|
|
53
|
+
Callers relying on ActiveRecord's silent rollback must catch it outside that
|
|
54
|
+
boundary. Backend authors should run the public Persistence contract suite;
|
|
55
|
+
method signatures and required capability keys are unchanged.
|
|
56
|
+
|
|
57
|
+
No schema, record type/version, payload, content identity or public facade
|
|
58
|
+
changes are required. Unknown commit outcomes remain backend/database failures;
|
|
59
|
+
this decision does not add exactly-once behavior or retry external effects.
|
|
60
|
+
|
|
61
|
+
## Verification and remaining work
|
|
62
|
+
|
|
63
|
+
Shared tests cover rejected update immutability, nonconflicting active updates,
|
|
64
|
+
invalid later Journal records, retrying the same record IDs, inner-only rollback,
|
|
65
|
+
exception identity, continued outer writes, normal results and outer rollback.
|
|
66
|
+
Both raw Backend and domain Persistence transaction entry points are exercised.
|
|
67
|
+
SQL-specific tests cover `ActiveRecord::Rollback` propagation.
|
|
68
|
+
|
|
69
|
+
The new neutral Records/Streams/Blobs SPI, failed-view lifecycle and explicit
|
|
70
|
+
non-local block-exit handling (`return` / `break` / `throw`) belong to S2b.
|
|
71
|
+
Do not use non-local exits as portable commit controls. PostgreSQL live-server
|
|
72
|
+
conformance and concurrency tests remain a required integration gate; source
|
|
73
|
+
parity or tests with a non-PostgreSQL connection do not satisfy that gate.
|