phronomy 0.26.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 +40 -0
- data/README.md +4 -0
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -22
- data/benchmark/bench_context_assembler.rb +4 -5
- 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 +4 -2
- 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 +27 -4
- 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 +1 -1
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +209 -0
- 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 +9 -3
- data/docs/getting-started.md +4 -4
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
- 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 +117 -32
- 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 +51 -241
- 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} +19 -95
- 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} +9 -10
- 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} +20 -71
- 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 +32 -37
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
- 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/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} +1 -1
- 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 +4 -0
- data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
- data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/engine/event_loop.rb +158 -624
- data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
- data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
- data/lib/phronomy/engine/execution_receiver.rb +65 -0
- data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
- data/lib/phronomy/engine/fsm_protocol.rb +14 -0
- data/lib/phronomy/engine/fsm_session.rb +35 -28
- data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
- 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.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_result.rb +4 -0
- 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_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 +5 -5
- data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
- 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/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 +1 -1
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
- 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} +122 -71
- 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 +2 -2
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
- data/lib/phronomy.rb +52 -94
- data/scripts/api_snapshot.rb +1 -1
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/phronomy/agent.rbs +0 -1
- data/sig/phronomy/execution_receiver.rbs +34 -0
- data/sig/phronomy/handoff.rbs +4 -2
- data/sig/phronomy/multi_agent.rbs +18 -0
- data/sig/phronomy/persistence.rbs +7 -91
- data/sig/phronomy/storage.rbs +174 -0
- data/sig/phronomy/tool.rbs +10 -1
- metadata +230 -99
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
- 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/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/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.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/{invocation_context.rb → engine/invocation_context.rb} +0 -0
- /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
- /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
|
@@ -68,7 +68,9 @@ module Phronomy
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# @api public
|
|
71
|
-
def get(team_id)
|
|
71
|
+
def get(team_id)
|
|
72
|
+
TeamOwnershipRegistry.existing_for(Phronomy::Runtime.instance)&.get(team_id.to_s, klass: self)
|
|
73
|
+
end
|
|
72
74
|
|
|
73
75
|
# @api private
|
|
74
76
|
def _coordinator_model = @coordinator_model
|
|
@@ -93,9 +95,9 @@ module Phronomy
|
|
|
93
95
|
raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::Runtime.in_event_loop_context?
|
|
94
96
|
key = id.to_s
|
|
95
97
|
raise ArgumentError, "team_id must not be empty" if key.empty?
|
|
96
|
-
store = persistence || Phronomy.configuration.persistence || Phronomy::Persistence
|
|
98
|
+
store = persistence || Phronomy.configuration.persistence || Phronomy::Persistence.in_memory
|
|
97
99
|
runtime = Phronomy::Runtime.instance
|
|
98
|
-
runtime.
|
|
100
|
+
TeamOwnershipRegistry.for(runtime).fetch(key, klass: self, create: create, persistence: store) do
|
|
99
101
|
instance = allocate
|
|
100
102
|
instance.send(:initialize, key.freeze, store, metadata, listener, runtime, create: create)
|
|
101
103
|
instance
|
|
@@ -159,6 +161,7 @@ module Phronomy
|
|
|
159
161
|
|
|
160
162
|
def initialize(id, store, metadata, listener, runtime, create:)
|
|
161
163
|
@team_id, @persistence, @listener, @runtime = id, store, listener, runtime
|
|
164
|
+
@admissions = AdmissionRegistry.for(@runtime)
|
|
162
165
|
@tokens_mutex, @tokens = Mutex.new, {}
|
|
163
166
|
@coordinator_classes = {}
|
|
164
167
|
definition = self.class.team_definition
|
|
@@ -189,17 +192,17 @@ module Phronomy
|
|
|
189
192
|
|
|
190
193
|
def with_admission
|
|
191
194
|
assert_caller!
|
|
192
|
-
@
|
|
195
|
+
@admissions.admit!(self)
|
|
193
196
|
admitted = true
|
|
194
197
|
yield
|
|
195
198
|
ensure
|
|
196
|
-
@
|
|
199
|
+
@admissions.release!(self) if admitted
|
|
197
200
|
end
|
|
198
201
|
|
|
199
202
|
def read_execution(id)
|
|
200
203
|
execution = persistence.team_executions.load(id)
|
|
201
204
|
unless execution.team_id == team_id
|
|
202
|
-
raise Phronomy::
|
|
205
|
+
raise Phronomy::Storage::ConflictError, "Team execution #{id} belongs to another Team"
|
|
203
206
|
end
|
|
204
207
|
execution
|
|
205
208
|
end
|
|
@@ -235,7 +238,7 @@ module Phronomy
|
|
|
235
238
|
raise error unless intended
|
|
236
239
|
begin
|
|
237
240
|
confirmed = read_execution(id)
|
|
238
|
-
rescue Phronomy::
|
|
241
|
+
rescue Phronomy::Storage::NotFoundError
|
|
239
242
|
raise error
|
|
240
243
|
end
|
|
241
244
|
raise error unless confirmed.to_h == intended.to_h
|
|
@@ -251,7 +254,7 @@ module Phronomy
|
|
|
251
254
|
intended = nil
|
|
252
255
|
persistence.transaction do |tx|
|
|
253
256
|
current = tx.team_executions.load(id)
|
|
254
|
-
raise Phronomy::
|
|
257
|
+
raise Phronomy::Storage::ConflictError, "Team execution owner mismatch" unless current.team_id == team_id
|
|
255
258
|
intended = yield(current, tx)
|
|
256
259
|
next if intended.equal?(current)
|
|
257
260
|
tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
|
|
@@ -264,7 +267,7 @@ module Phronomy
|
|
|
264
267
|
rescue => error
|
|
265
268
|
confirmed = read_execution(id)
|
|
266
269
|
return confirmed if intended && confirmed.to_h == intended.to_h
|
|
267
|
-
if error.is_a?(Phronomy::
|
|
270
|
+
if error.is_a?(Phronomy::Storage::ConflictError) && (attempts += 1) < 8
|
|
268
271
|
retry
|
|
269
272
|
end
|
|
270
273
|
raise error
|
|
@@ -383,7 +386,7 @@ module Phronomy
|
|
|
383
386
|
begin
|
|
384
387
|
persistence.agents.load(id)
|
|
385
388
|
present = true
|
|
386
|
-
rescue Phronomy::
|
|
389
|
+
rescue Phronomy::Storage::NotFoundError
|
|
387
390
|
present = false
|
|
388
391
|
end
|
|
389
392
|
token.raise_if_cancelled! unless present
|
|
@@ -418,7 +421,7 @@ module Phronomy
|
|
|
418
421
|
entries = fresh.assignments.map do |entry|
|
|
419
422
|
next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
|
|
420
423
|
next entry unless entry.fetch("state") == "reserved"
|
|
421
|
-
raise Phronomy::
|
|
424
|
+
raise Phronomy::Storage::ConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
|
|
422
425
|
entry.merge("state" => outcome.fetch(:status).to_s,
|
|
423
426
|
"result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
|
|
424
427
|
end
|
|
@@ -483,7 +486,7 @@ module Phronomy
|
|
|
483
486
|
param :description, type: :string, desc: "Worker task"
|
|
484
487
|
param :metadata, type: :string, desc: "Optional metadata", required: false
|
|
485
488
|
else
|
|
486
|
-
param :summary, type: :string, desc: "
|
|
489
|
+
param :summary, type: :string, desc: "Task generation summary", required: false
|
|
487
490
|
end
|
|
488
491
|
define_method(:call_async) do |args, cancellation_token: nil, config: {}|
|
|
489
492
|
validated, schema_error = send(:validate_and_coerce, args)
|
|
@@ -512,20 +515,20 @@ module Phronomy
|
|
|
512
515
|
operations = fresh.metadata.fetch("operations")
|
|
513
516
|
if (prior = operations[key])
|
|
514
517
|
unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
|
|
515
|
-
raise Phronomy::
|
|
518
|
+
raise Phronomy::Storage::ConflictError, "Team operation #{key} identity mismatch"
|
|
516
519
|
end
|
|
517
520
|
next fresh
|
|
518
521
|
end
|
|
519
522
|
raise Phronomy::CancellationError, "Team run cancelled" if fresh.metadata["cancel_requested"]
|
|
520
|
-
raise Phronomy::
|
|
523
|
+
raise Phronomy::Storage::ConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
|
|
521
524
|
coordinator = tx.executions.load(fresh.coordinator.fetch("execution_id"))
|
|
522
525
|
unless coordinator.agent_id == fresh.coordinator.fetch("agent_id")
|
|
523
|
-
raise Phronomy::
|
|
526
|
+
raise Phronomy::Storage::ConflictError, "Team coordinator owner mismatch"
|
|
524
527
|
end
|
|
525
|
-
batch = Array(coordinator.metadata[Phronomy::Agent::
|
|
528
|
+
batch = Array(coordinator.metadata[Phronomy::Agent::ExecutionMetadata::TOOL_BATCH_METADATA_KEY])
|
|
526
529
|
requested = batch.find { |entry| entry.fetch("tool_invocation_id") == key }
|
|
527
530
|
unless requested && requested.fetch("status") == "authorized" && requested.fetch("tool_name") == operation.to_s && requested.fetch("arguments").compact == argument_values
|
|
528
|
-
raise Phronomy::
|
|
531
|
+
raise Phronomy::Storage::ConflictError, "Team operation #{key} is not the authorized call"
|
|
529
532
|
end
|
|
530
533
|
# All calls passed the Agent authorization barrier. Commit this finite
|
|
531
534
|
# batch in Provider order, so finalize cannot overtake queued tasks.
|
|
@@ -542,7 +545,7 @@ module Phronomy
|
|
|
542
545
|
raise Phronomy::ConfigurationError, "Cannot enqueue after finalize" if metadata["finalized"]
|
|
543
546
|
task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
|
|
544
547
|
tasks << task
|
|
545
|
-
output = "
|
|
548
|
+
output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
|
|
546
549
|
else
|
|
547
550
|
output = "Finalized. #{tasks.size} task(s) enqueued. #{values["summary"]}".strip
|
|
548
551
|
metadata["finalized"] = true
|
|
@@ -14,7 +14,7 @@ module Phronomy
|
|
|
14
14
|
source = values.transform_keys(&:to_s)
|
|
15
15
|
raise ArgumentError, "TeamExecution schema mismatch" unless source.keys.sort == ATTRIBUTES.sort
|
|
16
16
|
canonical = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(source))
|
|
17
|
-
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::
|
|
17
|
+
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Values::Immutable.copy(canonical.fetch(key))) }
|
|
18
18
|
raise ArgumentError, "missing team_execution_id" if team_execution_id.to_s.empty?
|
|
19
19
|
raise ArgumentError, "invalid execution_revision" unless execution_revision.is_a?(Integer) && execution_revision >= 0
|
|
20
20
|
raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
|
|
4
|
+
module MultiAgent
|
|
5
5
|
# Runtime-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
|
+
|
|
8
18
|
def initialize
|
|
9
19
|
@mutex = Mutex.new
|
|
10
20
|
@condition = ConditionVariable.new
|
|
@@ -18,7 +28,7 @@ module Phronomy
|
|
|
18
28
|
loop do
|
|
19
29
|
raise Phronomy::RuntimeShutdownError, "Runtime is draining" if @draining
|
|
20
30
|
if (owner = @owners[id])
|
|
21
|
-
raise Phronomy::
|
|
31
|
+
raise Phronomy::Storage::ConflictError, "Team #{id} already exists" if create
|
|
22
32
|
unless owner.is_a?(klass) && owner.persistence.equal?(persistence)
|
|
23
33
|
raise Phronomy::ConfigurationError, "Team #{id} owner or Persistence mismatch"
|
|
24
34
|
end
|
|
@@ -58,7 +68,7 @@ module Phronomy
|
|
|
58
68
|
end
|
|
59
69
|
end
|
|
60
70
|
|
|
61
|
-
def
|
|
71
|
+
def wait_until_idle(deadline)
|
|
62
72
|
@mutex.synchronize do
|
|
63
73
|
until @constructing.empty?
|
|
64
74
|
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -69,7 +79,7 @@ module Phronomy
|
|
|
69
79
|
end
|
|
70
80
|
end
|
|
71
81
|
|
|
72
|
-
def
|
|
82
|
+
def after_runtime_shutdown
|
|
73
83
|
@mutex.synchronize { @owners.clear }
|
|
74
84
|
end
|
|
75
85
|
end
|
|
@@ -14,7 +14,7 @@ module Phronomy
|
|
|
14
14
|
source = values.transform_keys(&:to_s)
|
|
15
15
|
raise ArgumentError, "TeamRoot schema mismatch" unless source.keys.sort == ATTRIBUTES.sort
|
|
16
16
|
canonical = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(source))
|
|
17
|
-
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::
|
|
17
|
+
ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Values::Immutable.copy(canonical.fetch(key))) }
|
|
18
18
|
raise ArgumentError, "missing team_id" if team_id.to_s.empty?
|
|
19
19
|
raise ArgumentError, "invalid team_revision" unless team_revision.is_a?(Integer) && team_revision >= 0
|
|
20
20
|
raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Content-addressed immutable content repository. ContentStore has its own
|
|
6
|
+
# codec/canonicalization boundary and is not wrapped in DurableRecord.
|
|
7
|
+
#
|
|
8
|
+
# @return [Object]
|
|
9
|
+
# @api public
|
|
10
|
+
def contents = @repositories.contents
|
|
11
|
+
|
|
12
|
+
# Runtime/domain-facing AgentRoot repository. The injected backend is
|
|
13
|
+
# record-oriented; this facade owns encode/decode.
|
|
14
|
+
#
|
|
15
|
+
# @return [Object]
|
|
16
|
+
# @api public
|
|
17
|
+
def agents = @repositories.agents
|
|
18
|
+
|
|
19
|
+
# Runtime/domain-facing append-only Agent Journal repository.
|
|
20
|
+
#
|
|
21
|
+
# @return [Object]
|
|
22
|
+
# @api public
|
|
23
|
+
def journals = @repositories.journals
|
|
24
|
+
|
|
25
|
+
# Runtime/domain-facing AgentExecution repository.
|
|
26
|
+
#
|
|
27
|
+
# @return [Object]
|
|
28
|
+
# @api public
|
|
29
|
+
def executions = @repositories.executions
|
|
30
|
+
|
|
31
|
+
# Runtime/domain-facing durable Workflow snapshot repository.
|
|
32
|
+
#
|
|
33
|
+
# @return [Object]
|
|
34
|
+
# @api public
|
|
35
|
+
def workflow_states = @repositories.workflow_states
|
|
36
|
+
|
|
37
|
+
# Purpose-specific durable Handoff and Team repositories in atomic_all.
|
|
38
|
+
# @api public
|
|
39
|
+
def handoff_states = @repositories.handoff_states
|
|
40
|
+
|
|
41
|
+
# @api public
|
|
42
|
+
def teams = @repositories.teams
|
|
43
|
+
|
|
44
|
+
# @api public
|
|
45
|
+
def team_executions = @repositories.team_executions
|
|
46
|
+
|
|
47
|
+
# Read-only exact Agent execution result; never creates a live Agent owner.
|
|
48
|
+
# Result is nil until a result_ref exists; errors remain canonical data.
|
|
49
|
+
# @api public
|
|
50
|
+
def execution_result(execution_id)
|
|
51
|
+
assert_observation_thread!
|
|
52
|
+
Phronomy::Agent::Persistence::Queries.new(self).execution_result(execution_id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Follows one exact Handoff turn without constructing a live Agent or graph.
|
|
56
|
+
# A committed absent Target reservation remains active; failed reads raise.
|
|
57
|
+
# @api public
|
|
58
|
+
def handoff_result(execution_id, main_agent_id: nil)
|
|
59
|
+
assert_observation_thread!
|
|
60
|
+
Phronomy::Agent::Persistence::Queries.new(self).handoff_result(execution_id, main_agent_id: main_agent_id)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Lists retained active and terminal executions in lexical ID order.
|
|
64
|
+
# after is an exclusive ID cursor; discovery does not imply request dedup.
|
|
65
|
+
# @api public
|
|
66
|
+
def list_executions(agent_id, after: nil, limit: 100)
|
|
67
|
+
assert_observation_thread!
|
|
68
|
+
Phronomy::Agent::Persistence::Queries.new(self).list_executions(agent_id, after: after, limit: limit)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Read-only exact Team result, independent of current Team class/Proc wiring.
|
|
72
|
+
# @api public
|
|
73
|
+
def team_execution_result(team_execution_id)
|
|
74
|
+
assert_observation_thread!
|
|
75
|
+
Phronomy::MultiAgent::Persistence::Queries.new(self).team_execution_result(team_execution_id)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Lists retained Team runs; no continuation or callback delivery occurs.
|
|
79
|
+
# @api public
|
|
80
|
+
def list_team_executions(team_id, after: nil, limit: 100)
|
|
81
|
+
assert_observation_thread!
|
|
82
|
+
Phronomy::MultiAgent::Persistence::Queries.new(self).list_team_executions(team_id, after: after, limit: limit)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The selected raw storage backend, for backend-specific administration.
|
|
86
|
+
# Read and write domain records through the repository accessors above.
|
|
87
|
+
# @api public
|
|
88
|
+
attr_reader :backend
|
|
89
|
+
|
|
90
|
+
# Assembles domain repositories over one synchronous storage backend.
|
|
91
|
+
# @api public
|
|
92
|
+
def initialize(backend:)
|
|
93
|
+
Phronomy::Storage::Backend.validate_capabilities!(backend)
|
|
94
|
+
PersistenceComposition::StorageSchema.validate!(backend.view)
|
|
95
|
+
@backend = backend
|
|
96
|
+
@repositories = PersistenceComposition::Repositories.new(backend.view)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Constructs an isolated in-memory storage domain with standard codecs.
|
|
100
|
+
# @api public
|
|
101
|
+
def self.in_memory
|
|
102
|
+
new(backend: Phronomy::Storage::Backends::InMemory.new(resources: PersistenceComposition::StorageSchema.resources))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @api public
|
|
106
|
+
def capabilities = {atomic_all: true, atomic_admission: true, optimistic_revision: true}.freeze
|
|
107
|
+
|
|
108
|
+
# Executes a single backend transaction and exposes domain repositories
|
|
109
|
+
# bound to its raw view. Codec and caller failures remain inside the backend
|
|
110
|
+
# block so the backend can roll them back. The block result is returned.
|
|
111
|
+
# Explicit nested calls on this service use savepoint semantics: a failed
|
|
112
|
+
# inner scope rolls back and re-raises; a successful inner scope is committed
|
|
113
|
+
# only with its outer scope. See Storage::Backend#transaction.
|
|
114
|
+
# Storage failures whose commit outcome is fundamentally
|
|
115
|
+
# unknown remain backend/database failures; Phronomy does not claim
|
|
116
|
+
# exactly-once semantics for such failures.
|
|
117
|
+
# @api public
|
|
118
|
+
def transaction
|
|
119
|
+
backend.transaction do |raw_view|
|
|
120
|
+
repositories = PersistenceComposition::Repositories.new(raw_view)
|
|
121
|
+
yield repositories
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @api public
|
|
126
|
+
def assert_agent_watermark!(agent_id:, agent_revision:, journal_position:)
|
|
127
|
+
@repositories.assert_agent_watermark!(
|
|
128
|
+
agent_id: agent_id, agent_revision: agent_revision, journal_position: journal_position
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def assert_observation_thread!
|
|
135
|
+
if Phronomy::Runtime.in_event_loop_context?
|
|
136
|
+
raise Phronomy::EventLoopReentrancyError, "Persistence observation cannot block EventLoop"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -32,10 +32,10 @@ module Phronomy
|
|
|
32
32
|
PRE_S3_AGENT_ROOT_KEYS,
|
|
33
33
|
label: "pre-S3 AgentRoot"
|
|
34
34
|
)
|
|
35
|
-
|
|
35
|
+
Phronomy::Agent::Persistence::Codec.encode_agent_root(
|
|
36
36
|
Phronomy::Agent::AgentRoot.from_h(source)
|
|
37
37
|
)
|
|
38
|
-
rescue Phronomy::
|
|
38
|
+
rescue Phronomy::Storage::SerializationError
|
|
39
39
|
raise
|
|
40
40
|
rescue => error
|
|
41
41
|
migration_error("AgentRoot", error)
|
|
@@ -68,8 +68,8 @@ module Phronomy
|
|
|
68
68
|
execution_id: source.fetch("execution_id")
|
|
69
69
|
)
|
|
70
70
|
execution = Phronomy::Agent::AgentExecution.from_h(source)
|
|
71
|
-
|
|
72
|
-
rescue Phronomy::
|
|
71
|
+
Phronomy::Agent::Persistence::Codec.encode_agent_execution(execution)
|
|
72
|
+
rescue Phronomy::Storage::SerializationError
|
|
73
73
|
raise
|
|
74
74
|
rescue => error
|
|
75
75
|
migration_error("AgentExecution", error)
|
|
@@ -77,40 +77,40 @@ module Phronomy
|
|
|
77
77
|
|
|
78
78
|
def journal_record(hash)
|
|
79
79
|
source = normalize_legacy_journal_record(hash, label: "pre-S3 JournalRecord")
|
|
80
|
-
|
|
80
|
+
Phronomy::Agent::Persistence::Codec.encode_journal_record(
|
|
81
81
|
Phronomy::Agent::JournalRecord.from_h(source)
|
|
82
82
|
)
|
|
83
|
-
rescue Phronomy::
|
|
83
|
+
rescue Phronomy::Storage::SerializationError
|
|
84
84
|
raise
|
|
85
85
|
rescue => error
|
|
86
86
|
migration_error("JournalRecord", error)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def workflow_state(workflow_instance_id:, revision:, snapshot:)
|
|
90
|
-
|
|
90
|
+
Phronomy::Workflow::Persistence::Codec.encode_workflow_state(
|
|
91
91
|
workflow_instance_id: workflow_instance_id,
|
|
92
92
|
workflow_revision: Integer(revision),
|
|
93
93
|
snapshot: snapshot
|
|
94
94
|
)
|
|
95
|
-
rescue Phronomy::
|
|
95
|
+
rescue Phronomy::Storage::SerializationError
|
|
96
96
|
raise
|
|
97
97
|
rescue => error
|
|
98
98
|
migration_error("Workflow state", error)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
# LLMInputManifest is a ContentStore codec boundary, not a
|
|
102
|
-
#
|
|
102
|
+
# Storage::DurableRecord. Its pre-CG-07 integer version 1 is migrated
|
|
103
103
|
# explicitly to the pre-1.0 string version "0.1".
|
|
104
104
|
def llm_input_manifest(hash)
|
|
105
105
|
source = stringify_keys(hash)
|
|
106
106
|
old_version = source.fetch("version")
|
|
107
107
|
unless old_version == 1 || old_version == "1"
|
|
108
|
-
raise Phronomy::
|
|
108
|
+
raise Phronomy::Storage::SerializationError,
|
|
109
109
|
"unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
|
|
110
110
|
end
|
|
111
111
|
source["version"] = Phronomy::Agent::LLMInputManifest::VERSION
|
|
112
112
|
Phronomy::Agent::LLMInputManifest.from_h(source).to_h
|
|
113
|
-
rescue Phronomy::
|
|
113
|
+
rescue Phronomy::Storage::SerializationError
|
|
114
114
|
raise
|
|
115
115
|
rescue => error
|
|
116
116
|
migration_error("LLMInputManifest", error)
|
|
@@ -141,13 +141,13 @@ module Phronomy
|
|
|
141
141
|
)
|
|
142
142
|
parents = PRE_S3_APPROVAL_PARENT_KEYS.select { |key| source.key?(key) }
|
|
143
143
|
unless parents.length == 1
|
|
144
|
-
raise Phronomy::
|
|
144
|
+
raise Phronomy::Storage::SerializationError,
|
|
145
145
|
"pre-S3 approval_request must contain exactly one parent identity, got #{parents.inspect}"
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
items = source.fetch("items")
|
|
149
149
|
unless items.is_a?(Array) && !items.empty?
|
|
150
|
-
raise Phronomy::
|
|
150
|
+
raise Phronomy::Storage::SerializationError,
|
|
151
151
|
"pre-S3 approval_request items must be a non-empty Array"
|
|
152
152
|
end
|
|
153
153
|
source["items"] = items.map.with_index do |item, index|
|
|
@@ -164,7 +164,7 @@ module Phronomy
|
|
|
164
164
|
source.delete("agent_invocation_id")
|
|
165
165
|
source["execution_id"] = execution_id.to_s
|
|
166
166
|
elsif source.fetch("execution_id").to_s != execution_id.to_s
|
|
167
|
-
raise Phronomy::
|
|
167
|
+
raise Phronomy::Storage::SerializationError,
|
|
168
168
|
"pre-S3 approval_request execution_id does not match AgentExecution"
|
|
169
169
|
end
|
|
170
170
|
source
|
|
@@ -180,7 +180,7 @@ module Phronomy
|
|
|
180
180
|
details = []
|
|
181
181
|
details << "missing=#{missing.inspect}" unless missing.empty?
|
|
182
182
|
details << "unknown=#{unknown.inspect}" unless unknown.empty?
|
|
183
|
-
raise Phronomy::
|
|
183
|
+
raise Phronomy::Storage::SerializationError,
|
|
184
184
|
"#{label} schema mismatch (#{details.join(", ")})"
|
|
185
185
|
end
|
|
186
186
|
private_class_method :validate_allowed_keys!
|
|
@@ -197,17 +197,17 @@ module Phronomy
|
|
|
197
197
|
|
|
198
198
|
def stringify_keys(hash)
|
|
199
199
|
unless hash.is_a?(Hash)
|
|
200
|
-
raise Phronomy::
|
|
200
|
+
raise Phronomy::Storage::SerializationError,
|
|
201
201
|
"migration input must be a Hash"
|
|
202
202
|
end
|
|
203
203
|
hash.each_with_object({}) do |(key, value), result|
|
|
204
204
|
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
205
|
-
raise Phronomy::
|
|
205
|
+
raise Phronomy::Storage::SerializationError,
|
|
206
206
|
"migration key must be String or Symbol, got #{key.class}"
|
|
207
207
|
end
|
|
208
208
|
string_key = key.to_s
|
|
209
209
|
if result.key?(string_key)
|
|
210
|
-
raise Phronomy::
|
|
210
|
+
raise Phronomy::Storage::SerializationError,
|
|
211
211
|
"duplicate migration key after normalization: #{string_key.inspect}"
|
|
212
212
|
end
|
|
213
213
|
result[string_key] = value
|
|
@@ -216,7 +216,7 @@ module Phronomy
|
|
|
216
216
|
private_class_method :stringify_keys
|
|
217
217
|
|
|
218
218
|
def migration_error(label, error)
|
|
219
|
-
raise Phronomy::
|
|
219
|
+
raise Phronomy::Storage::SerializationError,
|
|
220
220
|
"cannot migrate pre-S3 #{label}: #{error.class}: #{error.message}"
|
|
221
221
|
end
|
|
222
222
|
private_class_method :migration_error
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module PersistenceComposition
|
|
5
|
+
# Assemble domain repositories over one raw backend or transaction view.
|
|
6
|
+
# @api private
|
|
7
|
+
class Repositories
|
|
8
|
+
attr_reader :contents
|
|
9
|
+
|
|
10
|
+
def initialize(view)
|
|
11
|
+
@view = view
|
|
12
|
+
@contents = Phronomy::ContentStore::StoredContents.new(view)
|
|
13
|
+
@domain_repositories = {}
|
|
14
|
+
@repository_lock = Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def agents
|
|
18
|
+
domain_repository(:agents) do
|
|
19
|
+
Phronomy::Agent::Persistence::AgentRepository.new(@view)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def journals
|
|
24
|
+
domain_repository(:journals) do
|
|
25
|
+
Phronomy::Agent::Persistence::JournalRepository.new(@view)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def executions
|
|
30
|
+
domain_repository(:executions) do
|
|
31
|
+
Phronomy::Agent::Persistence::ExecutionRepository.new(@view)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def workflow_states
|
|
36
|
+
domain_repository(:workflow_states) do
|
|
37
|
+
Phronomy::Workflow::Persistence::StateRepository.new(@view)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handoff_states
|
|
42
|
+
domain_repository(:handoff_states) do
|
|
43
|
+
Phronomy::Agent::Persistence::HandoffStateRepository.new(@view)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def teams
|
|
48
|
+
domain_repository(:teams) do
|
|
49
|
+
Phronomy::MultiAgent::Persistence::TeamRepository.new(@view)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def team_executions
|
|
54
|
+
domain_repository(:team_executions) do
|
|
55
|
+
Phronomy::MultiAgent::Persistence::TeamExecutionRepository.new(@view)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def assert_agent_watermark!(agent_id:, agent_revision:, journal_position:)
|
|
60
|
+
Phronomy::Agent::Persistence::Watermark.new(@view).check!(
|
|
61
|
+
agent_id: agent_id.to_s,
|
|
62
|
+
agent_revision: Integer(agent_revision),
|
|
63
|
+
journal_position: Integer(journal_position)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Cache one wrapper per view without loading unused domain implementations.
|
|
70
|
+
def domain_repository(name)
|
|
71
|
+
@repository_lock.synchronize do
|
|
72
|
+
@domain_repositories.fetch(name) { @domain_repositories[name] = yield }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module PersistenceComposition
|
|
5
|
+
# One composition catalog, supplied to a neutral storage driver.
|
|
6
|
+
# @api private
|
|
7
|
+
module StorageSchema
|
|
8
|
+
def self.validate!(view)
|
|
9
|
+
resources.each { |resource| view.public_send(resource.kind, resource) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.resources
|
|
13
|
+
[Agent::Persistence::StorageSchema::ROOTS,
|
|
14
|
+
Agent::Persistence::StorageSchema::EXECUTIONS,
|
|
15
|
+
Agent::Persistence::StorageSchema::JOURNAL,
|
|
16
|
+
Agent::Persistence::StorageSchema::HANDOFF_STATES,
|
|
17
|
+
TeamStorageSchema::ROOTS,
|
|
18
|
+
TeamStorageSchema::EXECUTIONS,
|
|
19
|
+
WorkflowStorageSchema::STATES,
|
|
20
|
+
ContentStore::StorageSchema::CONTENTS].freeze
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -36,7 +36,7 @@ module Phronomy
|
|
|
36
36
|
reason: reason.to_sym,
|
|
37
37
|
subject: subject && Phronomy::Recovery.normalize_subject(subject),
|
|
38
38
|
allowed_outcomes: Array(allowed_outcomes).map(&:to_sym).freeze,
|
|
39
|
-
facts: Phronomy::
|
|
39
|
+
facts: Phronomy::Values::Immutable.copy(facts || {})
|
|
40
40
|
)
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Select the Agent default at the application composition boundary. Binding
|
|
4
|
+
# does not create Persistence, global configuration, or a Runtime.
|
|
5
|
+
Phronomy::Agent::DefaultPersistence.install_factory(
|
|
6
|
+
-> { Phronomy::Persistence.in_memory }
|
|
7
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Select concrete defaults at the application composition boundary. Each factory
|
|
4
|
+
# runs during Configuration.new, not during binding; no Runtime is started here.
|
|
5
|
+
# Explicitly loaded after Zeitwerk setup and before global configuration access.
|
|
6
|
+
Phronomy::Configuration.install_default_factories(
|
|
7
|
+
tracer: -> { Phronomy::Tracing::NullTracer.new },
|
|
8
|
+
llm_adapter: -> { Phronomy::LLMAdapter::RubyLLM.new }
|
|
9
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Application lifecycle coordination across Runtime and global configuration.
|
|
4
|
+
# Explicitly loaded by the application entry point; not an Engine primitive.
|
|
5
|
+
module Phronomy
|
|
6
|
+
class << self
|
|
7
|
+
def reset_runtime!(timeout: configuration.event_loop_stop_grace_seconds)
|
|
8
|
+
previous_grace = @configuration&.event_loop_stop_grace_seconds
|
|
9
|
+
result = Runtime.reset_default!(timeout: timeout)
|
|
10
|
+
|
|
11
|
+
new_configuration = Configuration.new
|
|
12
|
+
if previous_grace
|
|
13
|
+
new_configuration.event_loop_stop_grace_seconds = previous_grace
|
|
14
|
+
end
|
|
15
|
+
@configuration = new_configuration
|
|
16
|
+
result
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|