phronomy 0.27.0 → 0.29.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/.mutant.yml +9 -9
- data/CHANGELOG.md +52 -0
- data/CONTRIBUTING.md +8 -8
- data/README.md +28 -7
- data/benchmark/bench_agent_invoke.rb +25 -50
- data/benchmark/bench_context_assembler.rb +14 -11
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +1 -1
- data/docs/application-recipes.md +229 -0
- data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
- data/docs/architecture/agent-transition-ownership.md +13 -89
- data/docs/architecture/backend-groups.md +152 -0
- data/docs/architecture/before-llm-input.md +3 -3
- data/docs/architecture/context-management.md +1 -1
- data/docs/architecture/dx05-agent-scope.md +119 -0
- data/docs/architecture/dx05-workflow-task-state.md +134 -0
- data/docs/architecture/error-api-review.md +245 -0
- data/docs/architecture/execution-abstraction-review.md +88 -0
- data/docs/architecture/execution-boundaries.md +28 -0
- data/docs/architecture/execution-metadata-and-values.md +6 -0
- data/docs/architecture/multi-agent-handoff.md +6 -5
- data/docs/architecture/persistence.md +27 -9
- data/docs/architecture/r8-unit1.md +104 -0
- data/docs/architecture/r8-unit10.md +72 -0
- data/docs/architecture/r8-unit11.md +71 -0
- data/docs/architecture/r8-unit12.md +67 -0
- data/docs/architecture/r8-unit13.md +61 -0
- data/docs/architecture/r8-unit14.md +72 -0
- data/docs/architecture/r8-unit15.md +65 -0
- data/docs/architecture/r8-unit16.md +54 -0
- data/docs/architecture/r8-unit17.md +73 -0
- data/docs/architecture/r8-unit18.md +92 -0
- data/docs/architecture/r8-unit19.md +61 -0
- data/docs/architecture/r8-unit2.md +55 -0
- data/docs/architecture/r8-unit20.md +51 -0
- data/docs/architecture/r8-unit21.md +79 -0
- data/docs/architecture/r8-unit22.md +74 -0
- data/docs/architecture/r8-unit23.md +81 -0
- data/docs/architecture/r8-unit3.md +130 -0
- data/docs/architecture/r8-unit4.md +79 -0
- data/docs/architecture/r8-unit5.md +115 -0
- data/docs/architecture/r8-unit6.md +71 -0
- data/docs/architecture/r8-unit7.md +70 -0
- data/docs/architecture/r8-unit8.md +78 -0
- data/docs/architecture/r8-unit9.md +73 -0
- data/docs/architecture/refactoring-closure.md +10 -1
- data/docs/architecture/remaining-refactoring-plan.md +16 -0
- data/docs/architecture/storage-execution-boundaries.md +54 -0
- data/docs/architecture/tool-schema-recording-gap.md +6 -0
- data/docs/architecture/tracing.md +1 -1
- data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
- data/docs/architecture.md +24 -11
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
- data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
- data/docs/decisions/032-storage-backend-composition.md +5 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
- data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
- data/docs/decisions/040-configuration-default-composition.md +12 -1
- data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
- data/docs/decisions/058-neutral-storage-primitives.md +5 -0
- data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
- data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
- data/docs/decisions/061-persistence-failure-contracts.md +89 -0
- data/docs/decisions/062-content-store-service.md +52 -0
- data/docs/decisions/063-execution-contracts-and-services.md +88 -0
- data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
- data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
- data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
- data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
- data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
- data/docs/decisions/README.md +18 -1
- data/docs/execution-boundaries-preview.md +45 -0
- data/docs/failure-reason-codes.md +45 -0
- data/docs/features.md +12 -4
- data/docs/getting-started.md +52 -48
- data/docs/migrations/0.22.md +2 -2
- data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
- data/docs/migrations/handoff-runner-multi-agent.md +2 -0
- data/docs/migrations/persistence-failure-contracts.md +67 -0
- data/docs/migrations/r8-unit10.md +36 -0
- data/docs/migrations/r8-unit11.md +33 -0
- data/docs/migrations/r8-unit12.md +31 -0
- data/docs/migrations/r8-unit13.md +36 -0
- data/docs/migrations/r8-unit14.md +31 -0
- data/docs/migrations/r8-unit15.md +31 -0
- data/docs/migrations/r8-unit16.md +26 -0
- data/docs/migrations/r8-unit17.md +28 -0
- data/docs/migrations/r8-unit18.md +29 -0
- data/docs/migrations/r8-unit19.md +28 -0
- data/docs/migrations/r8-unit20.md +24 -0
- data/docs/migrations/r8-unit21.md +28 -0
- data/docs/migrations/r8-unit22.md +29 -0
- data/docs/migrations/r8-unit23.md +20 -0
- data/docs/migrations/r8-unit4.md +67 -0
- data/docs/migrations/r8-unit5.md +75 -0
- data/docs/migrations/r8-unit6.md +109 -0
- data/docs/migrations/r8-unit7.md +31 -0
- data/docs/migrations/r8-unit8.md +33 -0
- data/docs/migrations/r8-unit9.md +28 -0
- data/docs/migrations/storage-async-client.md +103 -0
- data/docs/migrations/vector-async-clients.md +110 -0
- data/docs/ordered-event-delivery.md +54 -0
- data/docs/persistence-backends.md +38 -21
- data/docs/runtime-and-concurrency.md +26 -1
- data/docs/workflow-completion.md +92 -0
- data/gemfiles/http_minimum.gemfile +5 -0
- data/lib/phronomy/agent/admission.rb +78 -0
- data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
- data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
- data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
- data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
- data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
- data/lib/phronomy/agent/api/agent.rb +13 -0
- data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
- data/lib/phronomy/agent/async_event_api.rb +132 -103
- data/lib/phronomy/agent/base.rb +146 -113
- data/lib/phronomy/agent/before_llm_input.rb +70 -0
- data/lib/phronomy/agent/composition/run_once.rb +1 -1
- data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
- data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
- data/lib/phronomy/agent/control_request.rb +21 -0
- data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
- data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
- data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
- data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
- data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
- data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
- data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
- data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
- data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
- data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
- data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
- data/lib/phronomy/agent/execution_change.rb +161 -0
- data/lib/phronomy/agent/execution_environment.rb +36 -0
- data/lib/phronomy/agent/execution_extension_state.rb +21 -0
- data/lib/phronomy/agent/execution_observation.rb +35 -0
- data/lib/phronomy/agent/filterable.rb +188 -0
- data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
- data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
- data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
- data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
- data/lib/phronomy/agent/knowledge_item.rb +13 -0
- data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
- data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
- data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
- data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
- data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
- data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
- data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
- data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
- data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
- data/lib/phronomy/agent/persistence/codec.rb +1 -21
- data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
- data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
- data/lib/phronomy/agent/persistence/records.rb +33 -0
- data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
- data/lib/phronomy/agent/persistence/watermark.rb +13 -11
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
- data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
- data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
- data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
- data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
- data/lib/phronomy/agent/reserved_execution.rb +16 -0
- data/lib/phronomy/agent/retention.rb +17 -0
- data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
- data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
- data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
- data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
- data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
- data/lib/phronomy/agent/settings.rb +47 -0
- data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
- data/lib/phronomy/agent/store.rb +253 -0
- data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
- data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
- data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
- data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
- data/lib/phronomy/common/error.rb +65 -1
- data/lib/phronomy/configuration/runtime_settings.rb +49 -0
- data/lib/phronomy/content_store/base.rb +7 -2
- data/lib/phronomy/content_store/storage_schema.rb +1 -0
- data/lib/phronomy/content_store/stored_contents.rb +2 -1
- data/lib/phronomy/context/assembly.rb +195 -0
- data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
- data/lib/phronomy/context/candidate.rb +51 -0
- data/lib/phronomy/context/constraint.rb +47 -0
- data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
- data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
- data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
- data/lib/phronomy/context/default_policy.rb +140 -0
- data/lib/phronomy/context/final_budget_validator.rb +33 -0
- data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
- data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
- data/lib/phronomy/context/manifest_error.rb +10 -0
- data/lib/phronomy/context/prompt_template.rb +98 -0
- data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
- data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
- data/lib/phronomy/embeddings/async/async_client.rb +44 -0
- data/lib/phronomy/embeddings/authentication_error.rb +11 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
- data/lib/phronomy/embeddings/base.rb +43 -0
- data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
- data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
- data/lib/phronomy/embeddings/transport_error.rb +11 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
- data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
- data/lib/phronomy/engine/diagnostics.rb +1 -1
- data/lib/phronomy/engine/event_loop.rb +7 -7
- data/lib/phronomy/engine/execution_binding.rb +23 -0
- data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
- data/lib/phronomy/engine/fsm_protocol.rb +1 -6
- data/lib/phronomy/engine/fsm_session.rb +3 -3
- data/lib/phronomy/engine/metrics.rb +16 -14
- data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
- data/lib/phronomy/engine/runtime.rb +25 -12
- data/lib/phronomy/engine/terminal_decision.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +41 -0
- data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
- data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
- data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
- data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
- data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
- data/lib/phronomy/{engine → execution}/execution.rb +61 -27
- data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
- data/lib/phronomy/execution/execution_receiver.rb +78 -0
- data/lib/phronomy/execution/execution_scope_state.rb +35 -0
- data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
- data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
- data/lib/phronomy/execution/invocation_controls.rb +73 -0
- data/lib/phronomy/execution/runnable.rb +24 -0
- data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
- data/lib/phronomy/execution/wait_policy.rb +24 -0
- data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
- data/lib/phronomy/generation/generator_verifier.rb +7 -7
- data/lib/phronomy/generation/low_confidence_error.rb +2 -2
- data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
- data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
- data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
- data/lib/phronomy/llm_adapter/base.rb +49 -68
- data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
- data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
- data/lib/phronomy/llm_adapter/message.rb +62 -0
- data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
- data/lib/phronomy/llm_adapter/request.rb +79 -0
- data/lib/phronomy/llm_adapter/response.rb +36 -0
- data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
- data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
- data/lib/phronomy/llm_adapter.rb +3 -16
- data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
- data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
- data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
- data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
- data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
- data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
- data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
- data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
- data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +60 -44
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
- data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
- data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
- data/lib/phronomy/multi_agent/shared_state.rb +2 -2
- data/lib/phronomy/multi_agent/store.rb +156 -0
- data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
- data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
- data/lib/phronomy/output_parser/parse_error.rb +7 -0
- data/lib/phronomy/persistence/content_repository.rb +26 -0
- data/lib/phronomy/persistence/errors.rb +16 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
- data/lib/phronomy/persistence/persistence.rb +23 -0
- data/lib/phronomy/persistence/save_outcome.rb +24 -0
- data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
- data/lib/phronomy/persistence/storage_boundary.rb +32 -0
- data/lib/phronomy/persistence/transaction.rb +86 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
- data/lib/phronomy/persistence_composition/stores.rb +34 -0
- data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
- data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
- data/lib/phronomy/runtime_composition/configuration.rb +146 -0
- data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
- data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
- data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
- data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
- data/lib/phronomy/storage/async/async_client.rb +60 -0
- data/lib/phronomy/storage/backend.rb +8 -2
- data/lib/phronomy/storage/condition_failed_error.rb +2 -2
- data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
- data/lib/phronomy/testing/eval/metrics.rb +1 -1
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
- data/lib/phronomy/tool/authorization.rb +77 -0
- data/lib/phronomy/tool/base.rb +551 -9
- data/lib/phronomy/tool/call_request.rb +40 -0
- data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
- data/lib/phronomy/tool/operation.rb +88 -0
- data/lib/phronomy/tool/schema.rb +214 -0
- data/lib/phronomy/tool/settings.rb +31 -0
- data/lib/phronomy/tool/tool_error.rb +7 -0
- data/lib/phronomy/tool/tool_executor.rb +58 -0
- data/lib/phronomy/tools/agent.rb +22 -90
- data/lib/phronomy/tools/mcp.rb +21 -14
- data/lib/phronomy/tools/vector_search.rb +4 -4
- data/lib/phronomy/tracing/automatic.rb +11 -6
- data/lib/phronomy/tracing/base.rb +36 -7
- data/lib/phronomy/tracing/observation.rb +37 -0
- data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
- data/lib/phronomy/tracing/settings.rb +36 -0
- data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
- data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
- data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
- data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
- data/lib/phronomy/vector_store/base.rb +118 -49
- data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
- data/lib/phronomy/vector_store.rb +2 -17
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/completion.rb +11 -0
- data/lib/phronomy/workflow/execution/workflow.rb +37 -9
- data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
- data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
- data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
- data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
- data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
- data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
- data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
- data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
- data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
- data/lib/phronomy.rb +63 -16
- data/scripts/api_snapshot.rb +47 -15
- data/scripts/check_readme_ruby.rb +24 -29
- data/scripts/check_readme_runnable.rb +92 -81
- data/sig/_private/phronomy/agent/settings.rbs +19 -0
- data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
- data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
- data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
- data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
- data/sig/_private/phronomy/tool/settings.rbs +13 -0
- data/sig/_private/phronomy/tracing/settings.rbs +15 -0
- data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
- data/sig/phronomy/agent/admission.rbs +19 -0
- data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
- data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
- data/sig/phronomy/agent/execution_contract.rbs +132 -0
- data/sig/phronomy/agent/execution_environment.rbs +20 -0
- data/sig/phronomy/agent/store.rbs +73 -0
- data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
- data/sig/phronomy/agent.rbs +29 -135
- data/sig/phronomy/common.rbs +7 -0
- data/sig/phronomy/content_store/contracts.rbs +13 -0
- data/sig/phronomy/context.rbs +152 -0
- data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
- data/sig/phronomy/embeddings/base.rbs +23 -0
- data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
- data/sig/phronomy/engine/control_bindings.rbs +11 -0
- data/sig/phronomy/engine/control_protocols.rbs +23 -0
- data/sig/phronomy/engine/execution_binding.rbs +6 -0
- data/sig/phronomy/engine/execution_receiver.rbs +15 -0
- data/sig/phronomy/engine/observability.rbs +13 -0
- data/sig/phronomy/execution/async_operation.rbs +7 -0
- data/sig/phronomy/execution/blocking.rbs +5 -0
- data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
- data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
- data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
- data/sig/phronomy/execution/control_protocols.rbs +18 -0
- data/sig/phronomy/execution/errors.rbs +7 -0
- data/sig/phronomy/execution/execution.rbs +41 -0
- data/sig/phronomy/execution/execution_receiver.rbs +40 -0
- data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
- data/sig/phronomy/execution/invocation_context.rbs +18 -0
- data/sig/phronomy/execution/invocation_controls.rbs +13 -0
- data/sig/phronomy/execution/task_result.rbs +29 -0
- data/sig/phronomy/extensions.rbs +1 -21
- data/sig/phronomy/filter/filter_block_error.rbs +6 -0
- data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
- data/sig/phronomy/handoff.rbs +10 -4
- data/sig/phronomy/integrations.rbs +14 -0
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
- data/sig/phronomy/llm_adapter/base.rbs +14 -0
- data/sig/phronomy/llm_adapter/usage.rbs +14 -0
- data/sig/phronomy/llm_adapter/values.rbs +43 -0
- data/sig/phronomy/llm_context_window.rbs +10 -0
- data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
- data/sig/phronomy/multi_agent/store.rbs +54 -0
- data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
- data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
- data/sig/phronomy/multi_agent.rbs +6 -3
- data/sig/phronomy/persistence/errors.rbs +18 -0
- data/sig/phronomy/persistence/migration.rbs +13 -0
- data/sig/phronomy/persistence/transaction.rbs +19 -0
- data/sig/phronomy/persistence.rbs +1 -99
- data/sig/phronomy/persistence_composition.rbs +19 -0
- data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
- data/sig/phronomy/storage/async/async_client.rbs +8 -0
- data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
- data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +4 -18
- data/sig/phronomy/tool.rbs +48 -4
- data/sig/phronomy/tracing/observation.rbs +7 -0
- data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
- data/sig/phronomy/vector_store/base.rbs +23 -0
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +15 -0
- data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
- data/sig/phronomy/workflow/store.rbs +8 -0
- data/sig/phronomy/workflow.rbs +5 -1
- data/sig/phronomy.rbs +4 -49
- metadata +365 -117
- data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
- data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
- data/lib/phronomy/agent/concerns/filterable.rb +0 -184
- data/lib/phronomy/agent/context/capability/base.rb +0 -548
- data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
- data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
- data/lib/phronomy/agent/context_policies/default.rb +0 -142
- data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
- data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
- data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
- data/lib/phronomy/agent/persistence/queries.rb +0 -61
- data/lib/phronomy/agent/selection/candidate.rb +0 -53
- data/lib/phronomy/agent/selection/constraint.rb +0 -49
- data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
- data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
- data/lib/phronomy/configuration/configuration.rb +0 -68
- data/lib/phronomy/configuration/global_configuration.rb +0 -26
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
- data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
- data/lib/phronomy/engine/execution_receiver.rb +0 -65
- data/lib/phronomy/engine/runnable.rb +0 -54
- data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
- data/lib/phronomy/llm_contract/token_usage.rb +0 -85
- data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
- data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
- data/lib/phronomy/persistence/api/persistence.rb +0 -140
- data/lib/phronomy/persistence_composition/repositories.rb +0 -77
- data/lib/phronomy/recovery/recovery.rb +0 -186
- data/lib/phronomy/tool/contract/tool_error.rb +0 -7
- data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
- data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
- data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
- data/sig/phronomy/execution_receiver.rbs +0 -34
- data/sig/phronomy/llm_adapter.rbs +0 -11
- data/sig/phronomy/runtime.rbs +0 -76
- /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
- /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
- /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
|
@@ -11,12 +11,27 @@ module Phronomy
|
|
|
11
11
|
# Scheduling and aggregation must be pure, replay-safe result calculations.
|
|
12
12
|
# @api public
|
|
13
13
|
class TeamCoordinator
|
|
14
|
-
WorkerState = Data.define(:index, :agent_id, :
|
|
14
|
+
WorkerState = Data.define(:index, :agent_id, :assignment_count, :status) do
|
|
15
15
|
def available? = status == :available
|
|
16
16
|
end
|
|
17
17
|
private_constant :WorkerState
|
|
18
18
|
|
|
19
|
+
# Raised before writing an operation; update still verifies readback.
|
|
20
|
+
OperationRejected = Class.new(Phronomy::ConfigurationError)
|
|
21
|
+
private_constant :OperationRejected
|
|
22
|
+
|
|
19
23
|
class << self
|
|
24
|
+
# Boot composition supplies a fresh store without selecting it here.
|
|
25
|
+
# @api private
|
|
26
|
+
def install_persistence_factory(factory)
|
|
27
|
+
raise ArgumentError, "factory must respond to call" unless factory.respond_to?(:call)
|
|
28
|
+
raise Phronomy::ConfigurationError, "Team persistence factory already installed" if @persistence_factory
|
|
29
|
+
@persistence_factory = factory
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @api private
|
|
33
|
+
def build_default_persistence = @persistence_factory.call
|
|
34
|
+
|
|
20
35
|
# @api public
|
|
21
36
|
def team_definition(id: nil, version: nil)
|
|
22
37
|
if id || version
|
|
@@ -69,7 +84,7 @@ module Phronomy
|
|
|
69
84
|
|
|
70
85
|
# @api public
|
|
71
86
|
def get(team_id)
|
|
72
|
-
|
|
87
|
+
ExecutionEnvironment.current.existing_ownership&.get(team_id.to_s, klass: self)
|
|
73
88
|
end
|
|
74
89
|
|
|
75
90
|
# @api private
|
|
@@ -92,14 +107,14 @@ module Phronomy
|
|
|
92
107
|
private
|
|
93
108
|
|
|
94
109
|
def construct(id, persistence, metadata, listener, create:)
|
|
95
|
-
raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::
|
|
110
|
+
raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
|
|
96
111
|
key = id.to_s
|
|
97
112
|
raise ArgumentError, "team_id must not be empty" if key.empty?
|
|
98
|
-
store = persistence || Phronomy.
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
store = persistence || Phronomy::MultiAgent::Store.configured || TeamCoordinator.build_default_persistence
|
|
114
|
+
environment = ExecutionEnvironment.current
|
|
115
|
+
environment.ownership.fetch(key, klass: self, create: create, persistence: store) do
|
|
101
116
|
instance = allocate
|
|
102
|
-
instance.send(:initialize, key.freeze, store, metadata, listener,
|
|
117
|
+
instance.send(:initialize, key.freeze, store, metadata, listener, environment, create: create)
|
|
103
118
|
instance
|
|
104
119
|
end
|
|
105
120
|
end
|
|
@@ -134,12 +149,12 @@ module Phronomy
|
|
|
134
149
|
# @api public
|
|
135
150
|
def result(team_execution_id)
|
|
136
151
|
execution = read_execution(team_execution_id)
|
|
137
|
-
persistence.
|
|
152
|
+
persistence.result(execution.team_execution_id)
|
|
138
153
|
end
|
|
139
154
|
|
|
140
155
|
# @api public
|
|
141
156
|
def executions(after: nil, limit: 100)
|
|
142
|
-
persistence.
|
|
157
|
+
persistence.runs(team_id, after: after, limit: limit)
|
|
143
158
|
end
|
|
144
159
|
|
|
145
160
|
# Requests cancellation of this run only. Existing Agent cancellation and
|
|
@@ -159,9 +174,9 @@ module Phronomy
|
|
|
159
174
|
|
|
160
175
|
private
|
|
161
176
|
|
|
162
|
-
def initialize(id, store, metadata, listener,
|
|
163
|
-
@team_id, @persistence, @listener, @
|
|
164
|
-
@admissions =
|
|
177
|
+
def initialize(id, store, metadata, listener, environment, create:)
|
|
178
|
+
@team_id, @persistence, @listener, @environment = id, store, listener, environment
|
|
179
|
+
@admissions = @environment.admissions
|
|
165
180
|
@tokens_mutex, @tokens = Mutex.new, {}
|
|
166
181
|
@coordinator_classes = {}
|
|
167
182
|
definition = self.class.team_definition
|
|
@@ -173,19 +188,30 @@ module Phronomy
|
|
|
173
188
|
begin
|
|
174
189
|
store.transaction { |tx| tx.teams.create(intended) }
|
|
175
190
|
rescue => error
|
|
176
|
-
|
|
191
|
+
begin
|
|
192
|
+
confirmed = store.teams.load(id)
|
|
193
|
+
rescue
|
|
194
|
+
# A failed readback cannot turn an unknown create outcome into
|
|
195
|
+
# a claim that the Team is absent or safe to create again.
|
|
196
|
+
raise error, cause: error.cause
|
|
197
|
+
end
|
|
177
198
|
raise error unless confirmed.to_h == intended.to_h
|
|
178
199
|
end
|
|
179
200
|
end
|
|
180
|
-
|
|
201
|
+
begin
|
|
202
|
+
root = store.teams.load(id)
|
|
203
|
+
rescue Phronomy::Persistence::NotFoundError => error
|
|
204
|
+
raise if create || error.is_a?(TeamNotFoundError)
|
|
205
|
+
raise TeamNotFoundError.new(team_id: id), cause: error
|
|
206
|
+
end
|
|
181
207
|
unless root.team_definition_id == definition.fetch(:id) && root.team_definition_version == definition.fetch(:version)
|
|
182
208
|
raise Phronomy::ConfigurationError, "Team #{id} definition mismatch"
|
|
183
209
|
end
|
|
184
210
|
end
|
|
185
211
|
|
|
186
212
|
def assert_caller!
|
|
187
|
-
raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::
|
|
188
|
-
unless @
|
|
213
|
+
raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
|
|
214
|
+
unless @environment.current?
|
|
189
215
|
raise Phronomy::RuntimeShutdownError, "Team #{team_id} belongs to a previous Runtime"
|
|
190
216
|
end
|
|
191
217
|
end
|
|
@@ -202,7 +228,7 @@ module Phronomy
|
|
|
202
228
|
def read_execution(id)
|
|
203
229
|
execution = persistence.team_executions.load(id)
|
|
204
230
|
unless execution.team_id == team_id
|
|
205
|
-
raise Phronomy::
|
|
231
|
+
raise Phronomy::Persistence::StateConflictError, "Team execution #{id} belongs to another Team"
|
|
206
232
|
end
|
|
207
233
|
execution
|
|
208
234
|
end
|
|
@@ -221,13 +247,13 @@ module Phronomy
|
|
|
221
247
|
now = Time.now.utc.iso8601(6)
|
|
222
248
|
intended = nil
|
|
223
249
|
begin
|
|
224
|
-
persistence.transaction do |tx|
|
|
250
|
+
persistence.transaction do |tx, scope|
|
|
225
251
|
root = tx.teams.load(team_id)
|
|
226
|
-
raise
|
|
252
|
+
raise TeamBusyError.new("Team #{team_id} has an active run; use resume", team_id: team_id) unless tx.team_executions.list_active(team_id).empty?
|
|
227
253
|
intended = TeamExecution.new(team_execution_id: id, team_id: team_id,
|
|
228
254
|
execution_revision: 0, status: "active", phase: "coordinator", input_ref: tx.contents.put_text(input),
|
|
229
255
|
coordinator: {"agent_id" => SecureRandom.uuid, "execution_id" => SecureRandom.uuid, "state" => "reserved"},
|
|
230
|
-
workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid
|
|
256
|
+
workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid} },
|
|
231
257
|
tasks: [], assignments: [], result_ref: nil, error_ref: nil, created_at: now, updated_at: now,
|
|
232
258
|
metadata: {"definition" => definition, "operations" => {}, "cancel_requested" => false,
|
|
233
259
|
"durable_context_ref" => config.key?(:durable_context) ? tx.contents.put_json(config.fetch(:durable_context)) : nil})
|
|
@@ -238,7 +264,7 @@ module Phronomy
|
|
|
238
264
|
raise error unless intended
|
|
239
265
|
begin
|
|
240
266
|
confirmed = read_execution(id)
|
|
241
|
-
rescue Phronomy::
|
|
267
|
+
rescue Phronomy::Persistence::NotFoundError
|
|
242
268
|
raise error
|
|
243
269
|
end
|
|
244
270
|
raise error unless confirmed.to_h == intended.to_h
|
|
@@ -252,10 +278,10 @@ module Phronomy
|
|
|
252
278
|
attempts = 0
|
|
253
279
|
begin
|
|
254
280
|
intended = nil
|
|
255
|
-
persistence.transaction do |tx|
|
|
281
|
+
persistence.transaction do |tx, scope|
|
|
256
282
|
current = tx.team_executions.load(id)
|
|
257
|
-
raise Phronomy::
|
|
258
|
-
intended = yield(current, tx)
|
|
283
|
+
raise Phronomy::Persistence::StateConflictError, "Team execution owner mismatch" unless current.team_id == team_id
|
|
284
|
+
intended = yield(current, tx, scope)
|
|
259
285
|
next if intended.equal?(current)
|
|
260
286
|
tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
|
|
261
287
|
if intended.terminal? && current.active?
|
|
@@ -267,7 +293,7 @@ module Phronomy
|
|
|
267
293
|
rescue => error
|
|
268
294
|
confirmed = read_execution(id)
|
|
269
295
|
return confirmed if intended && confirmed.to_h == intended.to_h
|
|
270
|
-
if error.is_a?(Phronomy::
|
|
296
|
+
if error.is_a?(Phronomy::Persistence::ConflictError) && (attempts += 1) < 8
|
|
271
297
|
retry
|
|
272
298
|
end
|
|
273
299
|
raise error
|
|
@@ -282,9 +308,9 @@ module Phronomy
|
|
|
282
308
|
end
|
|
283
309
|
token = Phronomy::Concurrency::CancellationToken.new
|
|
284
310
|
@tokens_mutex.synchronize { @tokens[id] = token }
|
|
285
|
-
|
|
286
|
-
callback = proc { @
|
|
287
|
-
|
|
311
|
+
cancellation_subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
|
|
312
|
+
callback = proc { @environment.submit(on_full: :raise) { cancel(id) } }
|
|
313
|
+
cancellation_subscriptions.explicit_cancellation(config[:cancellation_token], &callback)
|
|
288
314
|
token.cancel! if current.metadata["cancel_requested"]
|
|
289
315
|
begin
|
|
290
316
|
loop do
|
|
@@ -323,13 +349,13 @@ module Phronomy
|
|
|
323
349
|
values = assignment_values(current)
|
|
324
350
|
aggregate = self.class._aggregator
|
|
325
351
|
begin
|
|
326
|
-
output = Phronomy::
|
|
352
|
+
output = Phronomy::Values::Serializable.convert(aggregate ? aggregate.call(values) : values, unsupported_message: "Team result is not canonically serializable")
|
|
327
353
|
Phronomy::CanonicalJSON.dump(output)
|
|
328
354
|
rescue => error
|
|
329
|
-
return terminal_value(finish_error(id, Phronomy::
|
|
355
|
+
return terminal_value(finish_error(id, Phronomy::Error.diagnostic(error)))
|
|
330
356
|
end
|
|
331
357
|
# Aggregate may be replayed only until the canonical outcome commits.
|
|
332
|
-
completed = update(id) do |fresh, tx|
|
|
358
|
+
completed = update(id) do |fresh, tx, scope|
|
|
333
359
|
next fresh if fresh.terminal? || fresh.metadata["cancel_requested"]
|
|
334
360
|
fresh.with(status: "completed", phase: "completed", result_ref: tx.contents.put_json(output))
|
|
335
361
|
end
|
|
@@ -345,7 +371,7 @@ module Phronomy
|
|
|
345
371
|
terminal_value(finish_error(id, {"class" => "Phronomy::CancellationError", "message" => "Team run cancelled"}, status: "cancelled"))
|
|
346
372
|
end
|
|
347
373
|
ensure
|
|
348
|
-
|
|
374
|
+
cancellation_subscriptions.close
|
|
349
375
|
@tokens_mutex.synchronize { @tokens.delete(id) }
|
|
350
376
|
end
|
|
351
377
|
end
|
|
@@ -383,30 +409,31 @@ module Phronomy
|
|
|
383
409
|
id = slot.fetch("agent_id")
|
|
384
410
|
child = klass.get(id)
|
|
385
411
|
unless child
|
|
386
|
-
|
|
387
|
-
persistence.agents.load(id)
|
|
388
|
-
present = true
|
|
389
|
-
rescue Phronomy::Storage::NotFoundError
|
|
390
|
-
present = false
|
|
391
|
-
end
|
|
412
|
+
present = persistence.agent_store.exist?(id)
|
|
392
413
|
token.raise_if_cancelled! unless present
|
|
393
|
-
child = present ? klass.load(id, persistence: persistence, on_event: @listener) : klass.create(agent_id: id, persistence: persistence, on_event: @listener)
|
|
414
|
+
child = present ? klass.load(id, persistence: persistence.agent_store, on_event: @listener) : klass.create(agent_id: id, persistence: persistence.agent_store, on_event: @listener)
|
|
394
415
|
end
|
|
395
|
-
unless child.persistence.equal?(persistence)
|
|
416
|
+
unless child.persistence.equal?(persistence.agent_store)
|
|
396
417
|
raise Phronomy::ConfigurationError, "Team child #{id} Persistence mismatch"
|
|
397
418
|
end
|
|
398
419
|
child_config = config.merge(cancellation_token: token,
|
|
399
|
-
|
|
400
|
-
|
|
420
|
+
phronomy_reservation: {"kind" => "team", "team_id" => team_id,
|
|
421
|
+
"team_execution_id" => current.team_execution_id, "slot" => purpose})
|
|
422
|
+
child_config = child_config.merge(phronomy_admission: ReservedChildAdmission.new(
|
|
423
|
+
persistence: persistence, owner: child_config.fetch(:phronomy_reservation)
|
|
424
|
+
))
|
|
401
425
|
if (context_ref = current.metadata["durable_context_ref"])
|
|
402
426
|
child_config = child_config.merge(durable_context: persistence.contents.fetch_json(context_ref))
|
|
403
427
|
end
|
|
404
|
-
Phronomy::Agent::
|
|
428
|
+
child.start_reserved_async(reservation: Phronomy::Agent::ReservedExecution.new(agent_id: id, execution_id: slot.fetch("execution_id"), correlation: child_config.fetch(:phronomy_reservation)), input: input, config: child_config).wait_result
|
|
405
429
|
end
|
|
406
430
|
|
|
407
431
|
def reserve_assignment(current, task)
|
|
408
|
-
|
|
409
|
-
|
|
432
|
+
# Team owns this count. Deriving it from retained assignments also
|
|
433
|
+
# makes replay idempotent and avoids consulting the child's journal.
|
|
434
|
+
counts = current.assignments.each_with_object(Hash.new(0)) { |entry, totals| totals[entry.fetch("worker")] += 1 }
|
|
435
|
+
available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), assignment_count: counts[w.fetch("index")], status: :available) }.freeze
|
|
436
|
+
worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:assignment_count)
|
|
410
437
|
raise Phronomy::ConfigurationError, "Scheduler must select an available worker slot" unless available.include?(worker)
|
|
411
438
|
reserved = {"task_id" => task.fetch("id"), "worker" => worker.index,
|
|
412
439
|
"execution_id" => SecureRandom.uuid, "state" => "reserved", "result_ref" => nil, "error_ref" => nil}
|
|
@@ -417,20 +444,15 @@ module Phronomy
|
|
|
417
444
|
end
|
|
418
445
|
|
|
419
446
|
def record_assignment(id, assigned, outcome)
|
|
420
|
-
update(id) do |fresh,
|
|
447
|
+
update(id) do |fresh, _tx|
|
|
421
448
|
entries = fresh.assignments.map do |entry|
|
|
422
449
|
next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
|
|
423
450
|
next entry unless entry.fetch("state") == "reserved"
|
|
424
|
-
raise Phronomy::
|
|
451
|
+
raise Phronomy::Persistence::StateConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
|
|
425
452
|
entry.merge("state" => outcome.fetch(:status).to_s,
|
|
426
453
|
"result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
|
|
427
454
|
end
|
|
428
|
-
|
|
429
|
-
next worker unless worker.fetch("index") == assigned.fetch("worker")
|
|
430
|
-
root = tx.agents.load(worker.fetch("agent_id"))
|
|
431
|
-
worker.merge("transcript_size" => root.journal_position)
|
|
432
|
-
end
|
|
433
|
-
fresh.with(assignments: entries, workers: workers)
|
|
455
|
+
fresh.with(assignments: entries)
|
|
434
456
|
end
|
|
435
457
|
end
|
|
436
458
|
|
|
@@ -444,7 +466,7 @@ module Phronomy
|
|
|
444
466
|
end
|
|
445
467
|
|
|
446
468
|
def finish_error(id, error, status: "failed")
|
|
447
|
-
update(id) do |fresh, tx|
|
|
469
|
+
update(id) do |fresh, tx, scope|
|
|
448
470
|
next fresh if fresh.terminal?
|
|
449
471
|
fresh.with(status: status, phase: status, error_ref: tx.contents.put_json(error))
|
|
450
472
|
end
|
|
@@ -452,7 +474,10 @@ module Phronomy
|
|
|
452
474
|
|
|
453
475
|
def terminal_value(execution)
|
|
454
476
|
raise Phronomy::CancellationError, "Team run #{execution.team_execution_id} cancelled" if execution.status == "cancelled"
|
|
455
|
-
|
|
477
|
+
if execution.error_ref
|
|
478
|
+
failure = persistence.contents.fetch_json(execution.error_ref)
|
|
479
|
+
raise Phronomy::Error.from_diagnostic(failure)
|
|
480
|
+
end
|
|
456
481
|
persistence.contents.fetch_json(execution.result_ref)
|
|
457
482
|
end
|
|
458
483
|
|
|
@@ -477,7 +502,7 @@ module Phronomy
|
|
|
477
502
|
|
|
478
503
|
def build_operation_tool(run_id, operation)
|
|
479
504
|
team = self
|
|
480
|
-
Class.new(Phronomy::
|
|
505
|
+
Class.new(Phronomy::Tool::Base) do
|
|
481
506
|
def self.__framework_owned_operation? = true
|
|
482
507
|
tool_name operation.to_s
|
|
483
508
|
description((operation == :enqueue_task) ? "Add a task to the worker queue." : "Finish task generation.")
|
|
@@ -489,7 +514,7 @@ module Phronomy
|
|
|
489
514
|
param :summary, type: :string, desc: "Task generation summary", required: false
|
|
490
515
|
end
|
|
491
516
|
define_method(:call_async) do |args, cancellation_token: nil, config: {}|
|
|
492
|
-
validated, schema_error =
|
|
517
|
+
validated, schema_error = validate_arguments(args)
|
|
493
518
|
raise Phronomy::ToolError, schema_error if schema_error
|
|
494
519
|
execute_async(**validated, cancellation_token: cancellation_token, config: config)
|
|
495
520
|
rescue => error
|
|
@@ -497,8 +522,10 @@ module Phronomy
|
|
|
497
522
|
end
|
|
498
523
|
define_method(:execute_async) do |config: {}, cancellation_token: nil, **arguments|
|
|
499
524
|
key = config.fetch(:phronomy_tool_invocation_id)
|
|
500
|
-
Phronomy::
|
|
525
|
+
Phronomy::Execution.submit(on_full: :raise) do
|
|
501
526
|
team.send(:apply_operation, run_id, key, operation, arguments)
|
|
527
|
+
rescue OperationRejected => error
|
|
528
|
+
raise Phronomy::ConfigurationError.new(error.message, code: error.code)
|
|
502
529
|
rescue Phronomy::CancellationError
|
|
503
530
|
raise
|
|
504
531
|
rescue => error
|
|
@@ -510,39 +537,34 @@ module Phronomy
|
|
|
510
537
|
end
|
|
511
538
|
|
|
512
539
|
def apply_operation(run_id, key, operation, arguments)
|
|
513
|
-
argument_values = Phronomy::
|
|
514
|
-
current = update(run_id) do |fresh, tx|
|
|
540
|
+
argument_values = Phronomy::Values::Serializable.convert(arguments, unsupported_message: "Team arguments are not canonically serializable")
|
|
541
|
+
current = update(run_id) do |fresh, tx, scope|
|
|
515
542
|
operations = fresh.metadata.fetch("operations")
|
|
516
543
|
if (prior = operations[key])
|
|
517
544
|
unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
|
|
518
|
-
raise Phronomy::
|
|
545
|
+
raise Phronomy::Persistence::StateConflictError, "Team operation #{key} identity mismatch"
|
|
519
546
|
end
|
|
520
547
|
next fresh
|
|
521
548
|
end
|
|
522
549
|
raise Phronomy::CancellationError, "Team run cancelled" if fresh.metadata["cancel_requested"]
|
|
523
|
-
raise Phronomy::
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
end
|
|
528
|
-
batch = Array(coordinator.metadata[Phronomy::Agent::ExecutionMetadata::TOOL_BATCH_METADATA_KEY])
|
|
529
|
-
requested = batch.find { |entry| entry.fetch("tool_invocation_id") == key }
|
|
530
|
-
unless requested && requested.fetch("status") == "authorized" && requested.fetch("tool_name") == operation.to_s && requested.fetch("arguments").compact == argument_values
|
|
531
|
-
raise Phronomy::Storage::ConflictError, "Team operation #{key} is not the authorized call"
|
|
532
|
-
end
|
|
550
|
+
raise Phronomy::Persistence::StateConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
|
|
551
|
+
batch = persistence.agent_store.authorized_operations(scope,
|
|
552
|
+
agent_id: fresh.coordinator.fetch("agent_id"), execution_id: fresh.coordinator.fetch("execution_id"),
|
|
553
|
+
invocation_id: key, name: operation.to_s, arguments: argument_values, names: %w[enqueue_task finalize])
|
|
533
554
|
# All calls passed the Agent authorization barrier. Commit this finite
|
|
534
555
|
# batch in Provider order, so finalize cannot overtake queued tasks.
|
|
535
556
|
tasks = fresh.tasks.dup
|
|
536
557
|
operations = operations.dup
|
|
537
558
|
metadata = fresh.metadata.dup
|
|
538
559
|
batch.each do |entry|
|
|
539
|
-
entry_id = entry.
|
|
540
|
-
next if operations.key?(entry_id)
|
|
541
|
-
name = entry.
|
|
542
|
-
|
|
543
|
-
values = entry.fetch("arguments").compact
|
|
560
|
+
entry_id = entry.invocation_id
|
|
561
|
+
next if operations.key?(entry_id)
|
|
562
|
+
name = entry.name
|
|
563
|
+
values = entry.arguments
|
|
544
564
|
if name == "enqueue_task"
|
|
545
|
-
|
|
565
|
+
if metadata["finalized"]
|
|
566
|
+
raise OperationRejected.new("Cannot enqueue after finalize", code: "team.enqueue_after_finalize")
|
|
567
|
+
end
|
|
546
568
|
task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
|
|
547
569
|
tasks << task
|
|
548
570
|
output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../persistence/errors"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module MultiAgent
|
|
7
|
+
# Describes the Team itself, not a child Agent.
|
|
8
|
+
# @api public
|
|
9
|
+
class TeamNotFoundError < Phronomy::Persistence::NotFoundError
|
|
10
|
+
attr_reader :team_id
|
|
11
|
+
|
|
12
|
+
def initialize(message = nil, team_id:)
|
|
13
|
+
@team_id = team_id.to_s.dup.freeze
|
|
14
|
+
super(message || "Team #{@team_id}: not_found", code: "team.not_found")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -2,19 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module MultiAgent
|
|
5
|
-
#
|
|
5
|
+
# Environment-local Team identity and construction exclusion; no execution state.
|
|
6
6
|
# @api private
|
|
7
7
|
class TeamOwnershipRegistry
|
|
8
|
-
def self.for(runtime)
|
|
9
|
-
existing_for(runtime) || runtime.__register_shutdown_participant(
|
|
10
|
-
key: self, participant: new
|
|
11
|
-
)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.existing_for(runtime)
|
|
15
|
-
runtime.__shutdown_participant(key: self)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
8
|
def initialize
|
|
19
9
|
@mutex = Mutex.new
|
|
20
10
|
@condition = ConditionVariable.new
|
|
@@ -28,7 +18,7 @@ module Phronomy
|
|
|
28
18
|
loop do
|
|
29
19
|
raise Phronomy::RuntimeShutdownError, "Runtime is draining" if @draining
|
|
30
20
|
if (owner = @owners[id])
|
|
31
|
-
raise Phronomy::
|
|
21
|
+
raise Phronomy::Persistence::StateConflictError, "Team #{id} already exists" if create
|
|
32
22
|
unless owner.is_a?(klass) && owner.persistence.equal?(persistence)
|
|
33
23
|
raise Phronomy::ConfigurationError, "Team #{id} owner or Persistence mismatch"
|
|
34
24
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Domain-facing content access uses the Persistence failure vocabulary.
|
|
6
|
+
# ContentStore implementations retain their independent backend contract.
|
|
7
|
+
# @api private
|
|
8
|
+
class ContentRepository < Phronomy::ContentStore::Base
|
|
9
|
+
def initialize(store)
|
|
10
|
+
@store = store
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def put(bytes, canonicalization_version:)
|
|
14
|
+
StorageBoundary.call { @store.put(bytes, canonicalization_version: canonicalization_version) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fetch(content_id)
|
|
18
|
+
StorageBoundary.call { @store.fetch(content_id) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def exist?(content_id)
|
|
22
|
+
StorageBoundary.call { @store.exist?(content_id) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
class Persistence
|
|
7
|
+
# Failures describe a category, not a universal claim about commit outcome.
|
|
8
|
+
class Error < Phronomy::Error; end
|
|
9
|
+
class ConflictError < Error; end
|
|
10
|
+
class StateConflictError < ConflictError; end
|
|
11
|
+
class NotFoundError < Error; end
|
|
12
|
+
class SerializationError < Error; end
|
|
13
|
+
class UnsupportedBackendError < Error; end
|
|
14
|
+
class TransactionError < Error; end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -108,8 +108,8 @@ module Phronomy
|
|
|
108
108
|
raise Phronomy::Storage::SerializationError,
|
|
109
109
|
"unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
|
|
110
110
|
end
|
|
111
|
-
source["version"] = Phronomy::
|
|
112
|
-
Phronomy::
|
|
111
|
+
source["version"] = Phronomy::Context::LLMInputManifest::VERSION
|
|
112
|
+
Phronomy::Context::LLMInputManifest.from_h(source).to_h
|
|
113
113
|
rescue Phronomy::Storage::SerializationError
|
|
114
114
|
raise
|
|
115
115
|
rescue => error
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Domain-neutral transaction ownership and participation. Domain operations
|
|
5
|
+
# choose their atomic boundary; composition supplies their storage adapters.
|
|
6
|
+
# @api public
|
|
7
|
+
class Persistence
|
|
8
|
+
attr_reader :backend
|
|
9
|
+
|
|
10
|
+
def initialize(backend:)
|
|
11
|
+
StorageBoundary.call { Storage::Backend.validate_capabilities!(backend) }
|
|
12
|
+
@backend = backend
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Bind an injected adapter for ordinary synchronous reads/standalone writes.
|
|
16
|
+
# Only that adapter receives Storage's raw view. Multi-operation changes use
|
|
17
|
+
# atomic/Transaction#participate so all participants use the exact same view.
|
|
18
|
+
# @api private
|
|
19
|
+
def bind(adapter)
|
|
20
|
+
StorageBoundary.call { adapter.new(backend.view) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Classifies evidence supplied by the owning domains. No retry, callback,
|
|
6
|
+
# ownership release or external operation is performed here.
|
|
7
|
+
# @api public
|
|
8
|
+
SaveOutcome = Data.define(:disposition, :original_error, :read_error) do
|
|
9
|
+
def self.compare(before:, after:, original_error: nil)
|
|
10
|
+
current = yield
|
|
11
|
+
disposition = if current == after
|
|
12
|
+
:committed
|
|
13
|
+
elsif current == before
|
|
14
|
+
:not_committed
|
|
15
|
+
else
|
|
16
|
+
:unknown
|
|
17
|
+
end
|
|
18
|
+
new(disposition: disposition, original_error: original_error, read_error: nil)
|
|
19
|
+
rescue => error
|
|
20
|
+
new(disposition: :unknown, original_error: original_error, read_error: error)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Compares revision and snapshot evidence supplied by the owning domain.
|
|
6
|
+
# Does not load, save, retry, resolve external operations or decide resumption.
|
|
7
|
+
# @api private
|
|
8
|
+
module SnapshotComparison
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Compare authoritative current state with a known pre/post revision pair.
|
|
12
|
+
# This is deliberately small: callers still validate entity-specific content.
|
|
13
|
+
def compare_revisions(current_revision:, expected_pre_revision:, intended_post_revision:)
|
|
14
|
+
current = current_revision
|
|
15
|
+
return :post_state if current == intended_post_revision
|
|
16
|
+
return :pre_state if current == expected_pre_revision
|
|
17
|
+
|
|
18
|
+
:conflict
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Evidence comparison for revisioned snapshot repositories.
|
|
22
|
+
# Some repositories return a newly allocated revision only after save, so
|
|
23
|
+
# callers may not know the intended post revision in advance. In that case
|
|
24
|
+
# the intended snapshot plus a revision different from the expected pre
|
|
25
|
+
# revision identifies the post-state.
|
|
26
|
+
def compare_revisioned_snapshot(
|
|
27
|
+
record:,
|
|
28
|
+
expected_pre_revision:,
|
|
29
|
+
intended_snapshot:,
|
|
30
|
+
intended_post_revision: nil
|
|
31
|
+
)
|
|
32
|
+
return :pre_state if record.nil? && expected_pre_revision.nil?
|
|
33
|
+
return :conflict if record.nil?
|
|
34
|
+
|
|
35
|
+
revision = fetch_value(record, :revision)
|
|
36
|
+
snapshot = fetch_value(record, :snapshot)
|
|
37
|
+
normalized_snapshot = normalize_value(snapshot)
|
|
38
|
+
normalized_intended = normalize_value(intended_snapshot)
|
|
39
|
+
|
|
40
|
+
if intended_post_revision
|
|
41
|
+
if revision == intended_post_revision &&
|
|
42
|
+
normalized_snapshot == normalized_intended
|
|
43
|
+
return :post_state
|
|
44
|
+
end
|
|
45
|
+
elsif normalized_snapshot == normalized_intended &&
|
|
46
|
+
revision != expected_pre_revision
|
|
47
|
+
return :post_state
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return :pre_state if revision == expected_pre_revision
|
|
51
|
+
|
|
52
|
+
:conflict
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def normalize_value(value)
|
|
56
|
+
case value
|
|
57
|
+
when Hash
|
|
58
|
+
value.each_with_object({}) do |(key, child), result|
|
|
59
|
+
result[key.to_s] = normalize_value(child)
|
|
60
|
+
end
|
|
61
|
+
when Array
|
|
62
|
+
value.map { |child| normalize_value(child) }
|
|
63
|
+
when Symbol
|
|
64
|
+
value.to_s
|
|
65
|
+
else
|
|
66
|
+
value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def fetch_value(record, key)
|
|
71
|
+
return nil unless record
|
|
72
|
+
return record.public_send(key) if record.respond_to?(key)
|
|
73
|
+
return record[key] if record.respond_to?(:key?) && record.key?(key)
|
|
74
|
+
string_key = key.to_s
|
|
75
|
+
return record[string_key] if record.respond_to?(:key?) && record.key?(string_key)
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|