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,117 @@
|
|
|
1
|
+
# Storage Backend composition migration
|
|
2
|
+
|
|
3
|
+
Historical migration step. Its old raw SPI surface is superseded by the
|
|
4
|
+
[neutral Storage SPI 2 migration](neutral-storage-spi.md); use that guide for the
|
|
5
|
+
current backend contract.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
This change targets the `refactor/architecture` branch after `1b481f6`, with
|
|
9
|
+
shared value operations already owned by `Values::Immutable`.
|
|
10
|
+
It replaces the Beta Backend SPI; the gem version and persisted record formats
|
|
11
|
+
are not changed by this branch-only refactoring package.
|
|
12
|
+
|
|
13
|
+
## Application construction
|
|
14
|
+
|
|
15
|
+
An isolated in-memory domain is now constructed with:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require "phronomy"
|
|
19
|
+
|
|
20
|
+
persistence = Phronomy::Persistence.in_memory
|
|
21
|
+
Phronomy.configure { |config| config.persistence = persistence }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
An explicitly selected raw backend is composed once:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require "phronomy"
|
|
28
|
+
|
|
29
|
+
backend = Phronomy::Storage::Backends::InMemory.new
|
|
30
|
+
persistence = Phronomy::Persistence.new(backend: backend)
|
|
31
|
+
Phronomy.configure { |config| config.persistence = persistence }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use the same `persistence` instance wherever one storage/ownership domain is
|
|
35
|
+
required. Constructing several facades over one physical backend does not create
|
|
36
|
+
independent data, and their object identities are distinct for callers that use
|
|
37
|
+
Persistence identity to determine a domain. `persistence.backend` exposes the
|
|
38
|
+
selected raw backend for backend-specific administration such as accessing its
|
|
39
|
+
connection pool. Ordinary domain reads and writes still use `persistence`.
|
|
40
|
+
|
|
41
|
+
## Replaced API
|
|
42
|
+
|
|
43
|
+
| Previous API | Replacement |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `Persistence::InMemory.new` | `Persistence.in_memory` |
|
|
46
|
+
| Backend subclass of `Persistence` | Backend subclass of `Storage::Backend`, or the same raw protocol |
|
|
47
|
+
| `Persistence.new` with eight raw repository keyword arguments | `Persistence.new(backend: raw_backend)` |
|
|
48
|
+
| Backend `super` with eight repositories | The same arguments passed to `Storage::Backend#initialize` |
|
|
49
|
+
| `Persistence#build_transaction_view` | A raw `Storage::Repositories` view yielded by the backend |
|
|
50
|
+
| `Persistence::DurableRecord` | `Storage::DurableRecord` |
|
|
51
|
+
| `Persistence::ConflictError` / `NotFoundError` / `SerializationError` / `UnsupportedBackendError` | The same names under `Storage` |
|
|
52
|
+
| `Persistence::REQUIRED_CAPABILITIES` | `Storage::Backend::REQUIRED_CAPABILITIES` |
|
|
53
|
+
|
|
54
|
+
The previous constants/helper/constructor are removed. Rescue the new storage
|
|
55
|
+
error constants. The public `Phronomy::AgentBusyError` admission exception remains.
|
|
56
|
+
This is a source/API migration, not a persisted payload migration.
|
|
57
|
+
|
|
58
|
+
## SQL implementation changes
|
|
59
|
+
|
|
60
|
+
A raw SQL backend implements `capabilities`, `transaction`, and
|
|
61
|
+
`assert_agent_watermark!`, and supplies all eight raw repositories to the common
|
|
62
|
+
Backend constructor. It exchanges `Storage::DurableRecord` and explicit ID,
|
|
63
|
+
revision, position, and admission metadata. It does not decode domain types.
|
|
64
|
+
|
|
65
|
+
In its transaction implementation:
|
|
66
|
+
|
|
67
|
+
1. Check out a connection and open the database transaction as before.
|
|
68
|
+
2. Bind contents, agents, journals, executions, workflow_states, handoff_states,
|
|
69
|
+
teams, team_executions, and the watermark object to that same connection.
|
|
70
|
+
3. Construct `Storage::Repositories` with those eight keyword arguments and
|
|
71
|
+
`watermark:`; yield this raw view inside the database transaction block.
|
|
72
|
+
4. Return the block result; propagate failures through the existing transaction
|
|
73
|
+
rollback path. Keep the existing handling of indeterminate commit outcomes.
|
|
74
|
+
|
|
75
|
+
`Persistence#transaction` constructs the corresponding domain facades inside
|
|
76
|
+
that block. SQL code no longer calls `build_transaction_view` or private
|
|
77
|
+
`PersistenceComposition::Repositories` or domain repository wrappers. Application/bootstrap code creates the raw SQL backend,
|
|
78
|
+
then passes it to `Persistence.new(backend:)`.
|
|
79
|
+
|
|
80
|
+
The SQLite and PostgreSQL implementations in `phronomy-examples` at `231d253`
|
|
81
|
+
use the previous SPI and require this migration. Do not use their unchanged
|
|
82
|
+
sources with the new core. Keep this branch's API change separate from a release
|
|
83
|
+
until the applications/backends to be shipped with it have also migrated.
|
|
84
|
+
|
|
85
|
+
## Validation
|
|
86
|
+
|
|
87
|
+
Use the existing explicitly loaded shared contract suite with a composed
|
|
88
|
+
`persistence` value. All eight repositories must still commit or roll back
|
|
89
|
+
together. Validate root and transaction repository binding, CAS/admission and
|
|
90
|
+
watermark checks, record round trips, and codec failure rollback. Perform actual
|
|
91
|
+
SQL tests with the backend's supported database and driver versions; an
|
|
92
|
+
in-memory success does not prove SQL transaction/concurrency behavior.
|
|
93
|
+
|
|
94
|
+
## Execution constraint errors (ADR-043)
|
|
95
|
+
|
|
96
|
+
Raw `Storage::Backend` execution repositories must report
|
|
97
|
+
`Storage::ActiveExecutionConflictError`, a `Storage::ConflictError` subtype, when
|
|
98
|
+
an existing nonterminal execution prevents another admission or an idle-only
|
|
99
|
+
operation for that owner. Update the same constraint branches in `save` if the
|
|
100
|
+
backend already checks them. Do not convert unrelated identity/revision failures.
|
|
101
|
+
|
|
102
|
+
The Agent and Team repository facades translate this subtype into the existing
|
|
103
|
+
`Phronomy::AgentBusyError` while preserving its message and cause. A transaction
|
|
104
|
+
still sees the exception before commit. Public Persistence/Agent/Team callers
|
|
105
|
+
keep their previous busy exception; raw backend callers must update their rescue.
|
|
106
|
+
|
|
107
|
+
Apply the new core before the migrated SQLite/PostgreSQL sources. Both examples
|
|
108
|
+
require the new storage constant; for coordinated source development set
|
|
109
|
+
`PHRONOMY_PATH` to the updated core checkout before resolving/running their
|
|
110
|
+
bundles. An unchanged legacy backend remains usable through the domain facade
|
|
111
|
+
because its old `AgentBusyError` is passed through, but it does not satisfy the
|
|
112
|
+
new raw conformance checks. No fallback constant aliases are supplied.
|
|
113
|
+
|
|
114
|
+
Run `a Persistence backend` from `phronomy/testing/persistence_contract`; it now
|
|
115
|
+
includes `storage execution constraint notifications` for both raw execution
|
|
116
|
+
repositories. Keep running the domain repository suites and backend concurrency
|
|
117
|
+
and rollback tests. No schema or serialized data migration is required.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Storage transaction boundaries (Refactor 34)
|
|
2
|
+
|
|
3
|
+
Historical migration step. Its old raw SPI surface is superseded by the
|
|
4
|
+
[neutral Storage SPI 2 migration](neutral-storage-spi.md); use that guide for the
|
|
5
|
+
current backend contract.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Update core and the SQLite/PostgreSQL reference adapters together. No data
|
|
9
|
+
migration is required. The public method signatures, eight repository accessors,
|
|
10
|
+
required capability keys and stored record formats are unchanged.
|
|
11
|
+
|
|
12
|
+
## Explicit nested calls
|
|
13
|
+
|
|
14
|
+
Nested `Backend#transaction` and `Persistence#transaction` calls on the same
|
|
15
|
+
backend and synchronous execution context now have savepoint semantics.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
persistence.transaction do |outer|
|
|
19
|
+
outer.contents.put_text("keep if outer succeeds")
|
|
20
|
+
begin
|
|
21
|
+
persistence.transaction do |inner|
|
|
22
|
+
inner.contents.put_text("discard on inner failure")
|
|
23
|
+
raise "inner failure"
|
|
24
|
+
end
|
|
25
|
+
rescue RuntimeError
|
|
26
|
+
# Inner changes have already been rolled back. Re-read conditions needed
|
|
27
|
+
# for subsequent work through the valid outer scope.
|
|
28
|
+
outer.contents.put_text("outer may continue")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Earlier SQL reference versions joined the outer scope. Catching the inner error
|
|
34
|
+
could preserve inner writes. Such writes are now rolled back. A successful inner
|
|
35
|
+
call does not commit independently; failure of the outer call rolls back both.
|
|
36
|
+
The same exception object is propagated after rollback, including
|
|
37
|
+
`ActiveRecord::Rollback`, which ActiveRecord itself normally consumes. Catch
|
|
38
|
+
that exception outside the Storage/Persistence block if continued execution is
|
|
39
|
+
intended. No separate database connection is introduced.
|
|
40
|
+
|
|
41
|
+
## Invalid Journal batches and database failures
|
|
42
|
+
|
|
43
|
+
The SQL references now validate and serialize every record and normalize the
|
|
44
|
+
expected position before the first write. An invalid second record cannot leave
|
|
45
|
+
the first inserted with an unchanged Journal head. Record IDs from a rejected
|
|
46
|
+
input batch can be retried with valid data.
|
|
47
|
+
|
|
48
|
+
This is an input-validation guarantee. A database failure after writing must
|
|
49
|
+
escape the current transaction scope. To continue outer work, establish an
|
|
50
|
+
explicit inner scope before the operation, and catch outside that inner scope.
|
|
51
|
+
Do not catch and ignore database errors inside the failed scope. Unknown commit
|
|
52
|
+
outcomes still require existing reconciliation rules, not blind retries.
|
|
53
|
+
|
|
54
|
+
## Active Agent execution updates
|
|
55
|
+
|
|
56
|
+
InMemory now enforces the same active-owner exclusion during update as SQL.
|
|
57
|
+
Trying to reactivate an Agent execution while a different execution for that
|
|
58
|
+
Agent is active raises `Storage::ActiveExecutionConflictError` at the raw boundary
|
|
59
|
+
and does not advance the revision or replace the record. The domain-facing
|
|
60
|
+
repository retains its existing error translation. Team terminal execution
|
|
61
|
+
reactivation remains forbidden.
|
|
62
|
+
|
|
63
|
+
## Backend authors and remaining migration
|
|
64
|
+
|
|
65
|
+
Run `phronomy/testing/persistence_contract`, including `a Persistence backend`,
|
|
66
|
+
against each real backend. Explicit nested calls must preserve block results,
|
|
67
|
+
exception identity, inner rollback and outer atomicity across repositories.
|
|
68
|
+
SQL integrations additionally exercise physical constraints, concurrent writers,
|
|
69
|
+
connection failure and durable reload; mocked connection responses do not cover
|
|
70
|
+
those guarantees.
|
|
71
|
+
|
|
72
|
+
Records/Streams/Blobs, failed-view enforcement and explicit non-local block-exit
|
|
73
|
+
handling are later S2b work. Do not use `return` / `break` / `throw` as portable
|
|
74
|
+
commit controls in the current SPI. See [ADR-057](../decisions/057-storage-transaction-boundaries.md).
|
|
@@ -1,179 +1,150 @@
|
|
|
1
|
-
# Persistence
|
|
1
|
+
# Persistence and neutral Storage SPI 2
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[ADR-058](decisions/058-neutral-storage-primitives.md) defines the current
|
|
4
|
+
extension contract. This is an intentional breaking replacement of the old
|
|
5
|
+
fixed-repository Backend SPI. Application code keeps `Persistence.new(backend:)`,
|
|
6
|
+
`Persistence.in_memory`, its eight domain repositories and result queries.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
backend should not depend on Phronomy private APIs or Runtime internals.
|
|
8
|
+
## Ownership and composition
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Storage owns `Resource`, `Backend`, `View`, `Records`, `Streams`, `Blobs`, immutable
|
|
11
|
+
`Entry` values, guards, conditions and neutral exceptions. It has no Agent/Team
|
|
12
|
+
record types, active-execution policy, content digest algorithm or watermark API.
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
Agent, Team, Workflow and ContentStore declare their own resource schemas.
|
|
15
|
+
`PersistenceComposition::StorageSchema` gathers those declarations and
|
|
16
|
+
`PersistenceComposition::Repositories` assembles domain wrappers over a View.
|
|
17
|
+
The Team and Workflow schema files live beside their features in nested loader
|
|
18
|
+
roots; loading metadata does not load their runtime implementations.
|
|
19
|
+
`ContentStore::StoredContents` owns SHA-256 identity and digest verification.
|
|
20
|
+
The Agent-owned `Watermark` composes guarded revision and stream-head conditions.
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Runtime / EventLoop / ExecutionCoordinator
|
|
20
|
-
↓
|
|
21
|
-
Phronomy::Persistence synchronous Backend SPI
|
|
22
|
-
↓
|
|
23
|
-
Database / durable storage
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Persistence does not own live Agent identity, top-level Runtime admission, or
|
|
27
|
-
live execution state. In particular, a backend must not persist or reconstruct
|
|
28
|
-
the following as part of this SPI:
|
|
29
|
-
|
|
30
|
-
- Runtime Agent ownership-registry entries;
|
|
31
|
-
- EventLoop Agent top-level admission entries;
|
|
32
|
-
- EventLoop Agent execution-directory entries;
|
|
33
|
-
- `AgentInvocation`;
|
|
34
|
-
- `FSMSession`;
|
|
35
|
-
- `Task` or callbacks;
|
|
36
|
-
- EventLoop queue contents;
|
|
37
|
-
- Runtime Workflow admission entries;
|
|
38
|
-
- in-flight provider operations.
|
|
39
|
-
|
|
40
|
-
Persistence operations are synchronous. Framework-owned blocking Persistence I/O
|
|
41
|
-
is submitted to the Runtime OffloadPool by Phronomy; a backend must not post
|
|
42
|
-
EventLoop events or introduce `load_async` / `save_async` variants into this
|
|
43
|
-
contract.
|
|
44
|
-
|
|
45
|
-
## Required root surface
|
|
22
|
+
InMemory receives `resources:` and supplies one Monitor/snapshot transaction
|
|
23
|
+
across the catalog. SQL reference composition supplies the same catalog and a
|
|
24
|
+
separate physical table/column mapping to the neutral driver in examples
|
|
25
|
+
`shared/storage`. Table names, columns, indexes, DurableRecord envelopes, payloads,
|
|
26
|
+
format versions and content identities remain unchanged.
|
|
46
27
|
|
|
47
|
-
|
|
28
|
+
The [S3 closure review](architecture/refactoring-closure.md) records why these
|
|
29
|
+
names and placements remain and separates applied evidence from the candidate.
|
|
48
30
|
|
|
49
|
-
|
|
50
|
-
contents
|
|
51
|
-
agents
|
|
52
|
-
journals
|
|
53
|
-
executions
|
|
54
|
-
workflow_states
|
|
55
|
-
handoff_states
|
|
56
|
-
teams
|
|
57
|
-
team_executions
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
and two root operations:
|
|
31
|
+
## Backend and View
|
|
61
32
|
|
|
62
33
|
```ruby
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
The object yielded by `transaction` is a transaction-scoped Persistence view. It
|
|
72
|
-
must respond to all eight repository accessors and
|
|
73
|
-
`assert_agent_watermark!`. It may be the Persistence instance itself, but SQL
|
|
74
|
-
backends may instead yield an object bound to a checked-out connection or
|
|
75
|
-
transaction session.
|
|
76
|
-
|
|
77
|
-
## Required capabilities
|
|
78
|
-
|
|
79
|
-
Every backend must advertise:
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
{
|
|
83
|
-
atomic_all: true,
|
|
84
|
-
atomic_admission: true,
|
|
85
|
-
optimistic_revision: true
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
`Phronomy::Persistence::REQUIRED_CAPABILITIES` is the executable definition of
|
|
90
|
-
this requirement.
|
|
91
|
-
|
|
92
|
-
### `atomic_all`
|
|
93
|
-
|
|
94
|
-
All durable repositories must be able to participate in one atomic transaction
|
|
95
|
-
domain. A transaction may change `contents`, `agents`, `journals`, `executions`,
|
|
96
|
-
`workflow_states`, `handoff_states`, `teams`, and `team_executions`, and then either commit all changes or roll them all back.
|
|
97
|
-
|
|
98
|
-
This requirement deliberately does not claim exactly-once semantics after an
|
|
99
|
-
indeterminate database/network failure. If the underlying database cannot tell
|
|
100
|
-
the caller whether a commit happened, the backend should surface the storage
|
|
101
|
-
failure rather than pretending the outcome is known.
|
|
102
|
-
|
|
103
|
-
### `atomic_admission`
|
|
104
|
-
|
|
105
|
-
This capability is a **durable Agent execution integrity defense**. It is not the
|
|
106
|
-
primary same-process Agent ownership/admission mechanism and it is not Workflow
|
|
107
|
-
distributed locking.
|
|
108
|
-
|
|
109
|
-
For one Agent, `executions.create_active` must atomically guarantee both:
|
|
110
|
-
|
|
111
|
-
```text
|
|
112
|
-
execution_id is unique
|
|
113
|
-
AND
|
|
114
|
-
no active/suspended execution already exists for agent_id
|
|
34
|
+
backend = Phronomy::Storage::Backends::InMemory.new(resources: resources)
|
|
35
|
+
backend.view.records(resource)
|
|
36
|
+
backend.view.streams(resource)
|
|
37
|
+
backend.view.blobs(resource)
|
|
38
|
+
backend.transaction { |view| ... }
|
|
39
|
+
view.check!(guards: guards, conditions: conditions)
|
|
115
40
|
```
|
|
116
41
|
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
42
|
+
The backend declares `spi_version: 2` and true values for `atomic_resources`,
|
|
43
|
+
`record_cas`, `stream_cas`, `conditional_unique`, `guarded_checks` and
|
|
44
|
+
`nested_savepoints`. Persistence validates these capabilities and the required
|
|
45
|
+
resource declarations before exposing repositories. Old duck-typed backends are
|
|
46
|
+
rejected with `UnsupportedBackendError`; there is no eight-slot compatibility
|
|
47
|
+
adapter. Public Persistence capabilities retain `atomic_all`, `atomic_admission`
|
|
48
|
+
and `optimistic_revision`, derived by composition from these primitives.
|
|
49
|
+
|
|
50
|
+
A root handle routes to the current transaction on the same backend/thread.
|
|
51
|
+
A bound view and all its handles expire on commit or rollback and reject use from
|
|
52
|
+
another thread. Explicit nested transactions use savepoints on the same SQL
|
|
53
|
+
connection, or nested InMemory snapshots. Inner success depends on outer commit;
|
|
54
|
+
an inner exception is re-raised after rollback and can be caught by the outer
|
|
55
|
+
scope. `ActiveRecord::Rollback` also propagates.
|
|
56
|
+
|
|
57
|
+
A failure during physical work marks the scope failed. Catching it inside that
|
|
58
|
+
same scope does not permit further operations or successful commit. Establish an
|
|
59
|
+
explicit inner transaction before a recoverable operation and catch outside it.
|
|
60
|
+
Input validation occurs before physical work. A successful optional read returning
|
|
61
|
+
nil is not a failed physical operation; domain required-load errors may be raised
|
|
62
|
+
after that read. Domain decode/returned-metadata failures stay inside the atomic
|
|
63
|
+
boundary so a corrupt response after a write causes rollback.
|
|
64
|
+
|
|
65
|
+
Transaction blocks must finish normally. `return`, `break` and `throw` escaping
|
|
66
|
+
them cause rollback and `TransactionError`. These exits are not commit controls.
|
|
67
|
+
Commit/rollback transport failures remain database failures; this contract does
|
|
68
|
+
not promise exactly-once external effects or infer commit certainty.
|
|
69
|
+
|
|
70
|
+
## Resource declarations
|
|
71
|
+
|
|
72
|
+
A `Resource` is an immutable value with `id`, `kind`, `attributes`,
|
|
73
|
+
`immutable_attributes`, `indexes`, `unique` and optional `guard`. Attributes use
|
|
74
|
+
`:string`, `:integer`, `:boolean` and their explicit `:nullable_*` forms. Keys and
|
|
75
|
+
text attributes use valid UTF-8 without NUL; keys are nonempty. Binary data belongs
|
|
76
|
+
to Blobs. No Proc, SQL expression or payload predicate is accepted.
|
|
77
|
+
|
|
78
|
+
Named equality indexes specify exact fields. Records may declare conditional
|
|
79
|
+
unique constraints with a symbol name, fields and equality `where` values. Null
|
|
80
|
+
unique-key fields are distinct, matching the default SQL unique-index semantics.
|
|
81
|
+
Streams have no indexed attributes; Blobs have attributes but no indexes/guards.
|
|
82
|
+
A record guard uses its key or an immutable non-null string attribute. A stream
|
|
83
|
+
guard uses its stream ID. Its anchor must be a registered Records resource.
|
|
84
|
+
Required anchors must exist; missing parents raise `NotFoundError` in all drivers.
|
|
85
|
+
|
|
86
|
+
## Records
|
|
87
|
+
|
|
88
|
+
| Operation | Contract |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `insert(key:, revision:, attributes:, record:)` | Insert only when absent; return an independent immutable `Entry::Record`. Primary duplicates and named unique failures differ. |
|
|
91
|
+
| `read(key)` / `fetch(key)` | Optional nil / required `NotFoundError`. |
|
|
92
|
+
| `replace(key:, expected_revision:, next_revision:, attributes:, record:, expected_attributes: {})` | Check existence, revision, expected attributes, immutable fields and uniqueness atomically; next revision must equal expected + 1. Replace all attributes. |
|
|
93
|
+
| `delete(key:, expected_revision: Records::UNCHECKED)` | Unchecked deletion is idempotent. A supplied revision requires existence and equality. Return nil. |
|
|
94
|
+
| `scan(index:, equals:, after: nil, limit: nil)` | Exact named-index fields, UTF-8 byte order, exclusive key cursor, positive limit or nil for all. |
|
|
95
|
+
| `delete_matching(index:, equals:)` | Delete equality matches atomically; a parent-guarded resource must constrain the guard attribute. Return nil. |
|
|
96
|
+
|
|
97
|
+
An Entry carries key, revision, attributes and an opaque `DurableRecord`.
|
|
98
|
+
The driver does not reconstruct metadata from payload. Domain wrappers verify
|
|
99
|
+
that returned identity, revision and attributes agree with decoded domain data.
|
|
100
|
+
Workflow/Handoff choose initial revision 1 and route nil expected revision to
|
|
101
|
+
insert; nil is never an unchecked update. Team terminal-to-active rejection is a
|
|
102
|
+
domain `expected_attributes: {active: true}` precondition when saving active state.
|
|
103
|
+
|
|
104
|
+
## Streams and Blobs
|
|
105
|
+
|
|
106
|
+
Streams expose `append(stream:, expected_head:, entries:)`,
|
|
107
|
+
`read(stream:, after: 0, limit: nil)`, `head(stream:)`, and `delete(stream:)`.
|
|
108
|
+
Append takes `Entry::Append(id:, record:)`, validates the entire batch before any
|
|
109
|
+
write, enforces unique entry IDs within a stream, assigns contiguous positions
|
|
110
|
+
and updates the head atomically. Empty append still checks the expected head.
|
|
111
|
+
Reads return immutable `Entry::Stream(position:, id:, record:)` values in position
|
|
112
|
+
order. Delete removes head and entries together. The domain Journal wrapper
|
|
113
|
+
preserves its existing `limit: 0` empty-result behavior without a raw zero-limit
|
|
114
|
+
operation.
|
|
115
|
+
|
|
116
|
+
Blobs expose `put_if_absent(key:, bytes:, attributes:)`, `fetch(key)` and
|
|
117
|
+
`exist?(key)`. Same bytes retain the first attributes; different bytes for an
|
|
118
|
+
existing key raise `BlobConflictError`. Entry bytes are independent immutable
|
|
119
|
+
binary strings. Blob keys are arbitrary storage keys; ContentStore adds the
|
|
120
|
+
`sha256:<digest>` contract and maps integrity failures to its own `IntegrityError`.
|
|
121
|
+
|
|
122
|
+
## Guards, conditions and errors
|
|
123
|
+
|
|
124
|
+
`GuardRef(resource:, key:)` names a stable existing parent record. `View#check!`
|
|
125
|
+
acquires guards in resource/key byte order before evaluating the closed condition
|
|
126
|
+
set: `RevisionIs`, `StreamHeadIs`, `NoRows`. A condition must include the guard
|
|
127
|
+
required by its resource and scope. PostgreSQL locks parents before child heads
|
|
128
|
+
or records; SQLite relies on the transaction/CAS/unique constraints and must not
|
|
129
|
+
claim a SELECT alone reserves a writer. Transactions spanning multiple owners
|
|
130
|
+
must establish a consistent owner lock order; database deadlocks remain database
|
|
131
|
+
errors, never optimistic conflicts.
|
|
132
|
+
|
|
133
|
+
`UniqueConstraintError < ConflictError` carries the resource ID and constraint
|
|
134
|
+
name. Agent/Team translate only their exact `one_active_owner` constraint to
|
|
135
|
+
`AgentBusyError`. Their idle checks translate their own `NoRows` condition failure.
|
|
136
|
+
`ConditionFailedError` carries the failed condition. Duplicate identities,
|
|
137
|
+
stale revisions and ordinary constraint conflicts remain `ConflictError`.
|
|
138
|
+
`NotFoundError`, `SerializationError`, `UnsupportedBackendError` retain their
|
|
139
|
+
meanings; `TransactionError` identifies invalid scope use. The removed
|
|
140
|
+
`ActiveExecutionConflictError` and `Storage::Repositories` have no aliases.
|
|
141
|
+
|
|
142
|
+
The following sections describe the retained **domain Persistence** surface.
|
|
171
143
|
|
|
172
144
|
## Contents repository
|
|
173
145
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
content-ID calculation.
|
|
146
|
+
`ContentStore::StoredContents < ContentStore::Base` supplies this domain surface
|
|
147
|
+
over neutral Blobs, with text/JSON helpers and canonical content-ID calculation.
|
|
177
148
|
|
|
178
149
|
Required primitive surface:
|
|
179
150
|
|
|
@@ -188,7 +159,7 @@ Required semantics:
|
|
|
188
159
|
- content is immutable and content-addressed;
|
|
189
160
|
- writing identical bytes is idempotent and returns the same content ID;
|
|
190
161
|
- `fetch` returns a binary `String` isolated from caller mutation;
|
|
191
|
-
- a missing content ID raises `
|
|
162
|
+
- a missing content ID raises `Storage::NotFoundError`;
|
|
192
163
|
- one content ID must never resolve to different bytes; a digest-integrity
|
|
193
164
|
violation raises `ContentStore::IntegrityError`.
|
|
194
165
|
|
|
@@ -374,10 +345,9 @@ honestly rather than converting them into `ConflictError`.
|
|
|
374
345
|
|
|
375
346
|
### Workflow value serialization
|
|
376
347
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
`Proc`, IO objects, sockets, or runtime callbacks.
|
|
348
|
+
The Workflow domain codec accepts canonical JSON-compatible snapshot values in
|
|
349
|
+
all drivers, including InMemory. Proc, IO, sockets and runtime callbacks are not
|
|
350
|
+
durable snapshot values.
|
|
381
351
|
|
|
382
352
|
A JSON/JSONB backend should document its supported value domain. A recommended
|
|
383
353
|
domain is:
|
|
@@ -391,7 +361,7 @@ Array of supported values
|
|
|
391
361
|
Hash with String/Symbol keys and supported values
|
|
392
362
|
```
|
|
393
363
|
|
|
394
|
-
If a value cannot be represented, raise `
|
|
364
|
+
If a value cannot be represented, raise `Storage::SerializationError` rather
|
|
395
365
|
than silently converting it into a lossy form. JSON backends may return String
|
|
396
366
|
keys after decoding; `WorkflowRunner` deliberately accepts String and Symbol keys
|
|
397
367
|
and normalizes them when comparing durable snapshots.
|
|
@@ -401,8 +371,8 @@ particular database backend accept arbitrary Workflow values.
|
|
|
401
371
|
|
|
402
372
|
## Durable Agent watermark
|
|
403
373
|
|
|
404
|
-
`assert_agent_watermark!` is a
|
|
405
|
-
|
|
374
|
+
`assert_agent_watermark!` is a domain Persistence operation. Agent-owned
|
|
375
|
+
`Watermark` composes a parent guard, root revision and Journal head conditions.
|
|
406
376
|
|
|
407
377
|
Phronomy uses it at durable barriers because a hydrated live Agent owns the
|
|
408
378
|
current logical state and Phronomy deliberately does not reload mutable Agent
|
|
@@ -482,9 +452,9 @@ This is a targeted migration rule for the removed generic identity field. It doe
|
|
|
482
452
|
not establish a general unknown-field or long-term codec/schema-versioning
|
|
483
453
|
policy.
|
|
484
454
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
455
|
+
Domain codecs own canonical Hash representation. The raw driver stores the
|
|
456
|
+
DurableRecord envelope and separately supplied metadata without interpreting
|
|
457
|
+
domain payload fields.
|
|
488
458
|
|
|
489
459
|
## Conformance tests
|
|
490
460
|
|
|
@@ -508,6 +478,11 @@ an Agent repository
|
|
|
508
478
|
a Journal repository
|
|
509
479
|
an Execution repository
|
|
510
480
|
a workflow state repository
|
|
481
|
+
a Handoff state repository
|
|
482
|
+
a Team repository
|
|
483
|
+
a Team execution repository
|
|
484
|
+
neutral storage primitives
|
|
485
|
+
storage transaction boundaries
|
|
511
486
|
a Persistence backend
|
|
512
487
|
```
|
|
513
488
|
|
|
@@ -518,7 +493,7 @@ require "phronomy"
|
|
|
518
493
|
require "phronomy/testing/persistence_contract"
|
|
519
494
|
|
|
520
495
|
RSpec.describe MyPersistenceBackend do
|
|
521
|
-
let(:persistence) { described_class.new(...) }
|
|
496
|
+
let(:persistence) { Phronomy::Persistence.new(backend: described_class.new(...)) }
|
|
522
497
|
|
|
523
498
|
it_behaves_like "a persistence content store"
|
|
524
499
|
it_behaves_like "an Agent repository"
|
|
@@ -532,7 +507,7 @@ RSpec.describe MyPersistenceBackend do
|
|
|
532
507
|
end
|
|
533
508
|
```
|
|
534
509
|
|
|
535
|
-
`Persistence
|
|
510
|
+
`Persistence.in_memory` is run through the same shipped contract source in
|
|
536
511
|
Phronomy CI. The files under `spec/support/shared_examples/` are compatibility
|
|
537
512
|
require wrappers only; the authoritative shared-example implementations live
|
|
538
513
|
under `lib/phronomy/testing/persistence_contract/` so the core suite and external
|
|
@@ -560,34 +535,17 @@ may use combinations of:
|
|
|
560
535
|
Backend-specific database exceptions should be translated to the Phronomy error
|
|
561
536
|
contract where their meaning is known.
|
|
562
537
|
|
|
563
|
-
##
|
|
538
|
+
## Coordination domain repositories
|
|
564
539
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
`
|
|
568
|
-
|
|
569
|
-
|
|
540
|
+
Handoff starts at revision 1 with expected revision nil; later saves advance one
|
|
541
|
+
revision. Team roots and executions begin at revision 0. Team admission retains
|
|
542
|
+
`AgentBusyError`; stale CAS and duplicate identities use `ConflictError`.
|
|
543
|
+
Missing Team/execution loads raise `NotFoundError`; absent Handoff state returns
|
|
544
|
+
nil. The unchanged record types are `phronomy.handoff_state`, `phronomy.team_root`
|
|
545
|
+
and `phronomy.team_execution`, all version `0.1`.
|
|
570
546
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
| `executions` extension | `list(agent_id, after: nil, limit: 100)` for retained active and terminal records |
|
|
577
|
-
|
|
578
|
-
Handoff starts at revision 1 with expected revision nil; later saves advance
|
|
579
|
-
exactly one revision. Team roots and executions begin at revision 0. Team
|
|
580
|
-
admission has the existing `AgentBusyError` contract for an already active owner;
|
|
581
|
-
stale CAS/duplicate identity uses `ConflictError`. Missing Team/execution loads
|
|
582
|
-
raise `NotFoundError`; absent Handoff state returns nil. Unavailable reads and
|
|
583
|
-
codec errors must propagate separately.
|
|
584
|
-
|
|
585
|
-
Lists sort IDs lexically, use an exclusive ID cursor, return at most the positive
|
|
586
|
-
integer limit, include only the requested owner, and return immutable copies.
|
|
587
|
-
Backends define retention; enumeration does not implement input correlation or
|
|
588
|
-
request deduplication. Terminal-to-active rewrites are invalid.
|
|
589
|
-
|
|
590
|
-
For F1, atomic commit does not imply a known response. Phronomy reads back the
|
|
591
|
-
same intended ID/fact before advancing. A backend must provide authoritative
|
|
592
|
-
reads/CAS; failure of readback is returned without new semantic work. No callback
|
|
593
|
-
ACK/index is part of the SPI. Existing cross-process exclusion limitations remain.
|
|
547
|
+
Backend authors should also read the [SPI 2 migration guide](migrations/neutral-storage-spi.md).
|
|
548
|
+
The Stable/Beta product API snapshot and the explicit Storage SPI 2 signature
|
|
549
|
+
snapshot are separate gates. Live PostgreSQL locking and failure tests must run
|
|
550
|
+
against the candidate core and examples revisions; earlier SPI results do not
|
|
551
|
+
satisfy this gate.
|