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
data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb
RENAMED
|
@@ -10,7 +10,7 @@ module Phronomy
|
|
|
10
10
|
|
|
11
11
|
def begin_resolve_on_event_loop(request)
|
|
12
12
|
event_loop = @runtime.event_loop
|
|
13
|
-
state = event_loop.agent_execution_state(
|
|
13
|
+
state = Phronomy::Agent::ExecutionRegistry.for(event_loop).agent_execution_state(
|
|
14
14
|
request.execution_id
|
|
15
15
|
)
|
|
16
16
|
unless state && state.agent.equal?(agent)
|
|
@@ -26,7 +26,7 @@ module Phronomy
|
|
|
26
26
|
unless current.execution_revision ==
|
|
27
27
|
request.expected_execution_revision
|
|
28
28
|
request.completion.fail(
|
|
29
|
-
Phronomy::
|
|
29
|
+
Phronomy::Storage::ConflictError.new(
|
|
30
30
|
"Recovery resolution revision conflict: expected " \
|
|
31
31
|
"#{request.expected_execution_revision}, actual " \
|
|
32
32
|
"#{current.execution_revision}"
|
|
@@ -69,7 +69,7 @@ module Phronomy
|
|
|
69
69
|
result: request.result,
|
|
70
70
|
failure: request.failure
|
|
71
71
|
)
|
|
72
|
-
event_loop.mark_agent_execution_admission(
|
|
72
|
+
Phronomy::Agent::ExecutionRegistry.for(event_loop).mark_agent_execution_admission(
|
|
73
73
|
agent.agent_id,
|
|
74
74
|
execution_id: current.execution_id,
|
|
75
75
|
state: :recovery_required
|
|
@@ -116,9 +116,9 @@ module Phronomy
|
|
|
116
116
|
llm_call_id =
|
|
117
117
|
operation.subject.fetch(:llm_call_id).to_s
|
|
118
118
|
unless current.metadata[
|
|
119
|
-
|
|
119
|
+
ExecutionMetadata::PENDING_LLM_ID_KEY
|
|
120
120
|
].to_s == llm_call_id
|
|
121
|
-
raise Phronomy::
|
|
121
|
+
raise Phronomy::Storage::ConflictError,
|
|
122
122
|
"LLM Recovery subject is no longer pending"
|
|
123
123
|
end
|
|
124
124
|
|
|
@@ -136,27 +136,27 @@ module Phronomy
|
|
|
136
136
|
"automatic semantic redispatch is unavailable without a replay contract"
|
|
137
137
|
}.freeze
|
|
138
138
|
recovery = {
|
|
139
|
-
"version" =>
|
|
139
|
+
"version" => ExecutionMetadata::CONTRACT_VERSION,
|
|
140
140
|
"resolution_outcome" => "not_performed",
|
|
141
141
|
"failure" => failure
|
|
142
142
|
}.freeze
|
|
143
143
|
updated = current.with(
|
|
144
144
|
phase: :recovery_resolved_failed,
|
|
145
145
|
metadata: current.metadata.merge(
|
|
146
|
-
|
|
146
|
+
ExecutionMetadata::RECOVERY_METADATA_KEY => recovery
|
|
147
147
|
)
|
|
148
148
|
)
|
|
149
149
|
intended = ResolutionResult.new(execution: updated)
|
|
150
150
|
save_resolution_result(current, intended)
|
|
151
151
|
when :failed
|
|
152
152
|
recovery = {
|
|
153
|
-
"version" =>
|
|
153
|
+
"version" => ExecutionMetadata::CONTRACT_VERSION,
|
|
154
154
|
"failure" => operation.failure
|
|
155
155
|
}
|
|
156
156
|
updated = current.with(
|
|
157
157
|
phase: :recovery_resolved_failed,
|
|
158
158
|
metadata: current.metadata.merge(
|
|
159
|
-
|
|
159
|
+
ExecutionMetadata::RECOVERY_METADATA_KEY =>
|
|
160
160
|
recovery
|
|
161
161
|
)
|
|
162
162
|
)
|
|
@@ -170,7 +170,6 @@ module Phronomy
|
|
|
170
170
|
)
|
|
171
171
|
updated = nil
|
|
172
172
|
with_resolution_f1_capture do |tx|
|
|
173
|
-
main = agent.send(:execution_coordinator_for, agent.__coordination_config)
|
|
174
173
|
snapshot = {
|
|
175
174
|
llm_results: [{
|
|
176
175
|
llm_call_id: llm_call_id,
|
|
@@ -178,22 +177,22 @@ module Phronomy
|
|
|
178
177
|
error: nil,
|
|
179
178
|
streaming: (
|
|
180
179
|
current.metadata[
|
|
181
|
-
|
|
180
|
+
ExecutionMetadata::INVOCATION_MODE_KEY
|
|
182
181
|
].to_s == "stream"
|
|
183
182
|
),
|
|
184
183
|
manifest_ref: current.metadata.fetch(
|
|
185
184
|
"manifest_ref"
|
|
186
185
|
),
|
|
187
186
|
started_at: current.metadata[
|
|
188
|
-
|
|
187
|
+
ExecutionMetadata::PENDING_LLM_STARTED_AT_KEY
|
|
189
188
|
] || current.updated_at
|
|
190
189
|
}.freeze].freeze,
|
|
191
190
|
runtime_events: [].freeze,
|
|
192
191
|
active_call: nil
|
|
193
192
|
}.freeze
|
|
194
|
-
records, calls =
|
|
195
|
-
:encode_runtime_records,
|
|
193
|
+
records, calls = RuntimeRecordEncoder.encode(
|
|
196
194
|
current,
|
|
195
|
+
agent_id: agent.agent_id,
|
|
197
196
|
tx: tx,
|
|
198
197
|
snapshot: snapshot,
|
|
199
198
|
context_candidate: true,
|
|
@@ -210,15 +209,15 @@ module Phronomy
|
|
|
210
209
|
:recovery_provider_completed : :recovery_tools
|
|
211
210
|
metadata = current.metadata.dup
|
|
212
211
|
metadata.delete(
|
|
213
|
-
|
|
212
|
+
ExecutionMetadata::PENDING_LLM_ID_KEY
|
|
214
213
|
)
|
|
215
214
|
metadata.delete(
|
|
216
|
-
|
|
215
|
+
ExecutionMetadata::PENDING_LLM_STARTED_AT_KEY
|
|
217
216
|
)
|
|
218
217
|
metadata.delete("framework_calls_pending")
|
|
219
218
|
metadata["framework_calls_pending"] = true unless framework_subjects.empty?
|
|
220
|
-
if subjects.any? && Array(metadata[
|
|
221
|
-
metadata[
|
|
219
|
+
if subjects.any? && Array(metadata[ExecutionMetadata::TOOL_BATCH_METADATA_KEY]).empty?
|
|
220
|
+
metadata[ExecutionMetadata::TOOL_BATCH_METADATA_KEY] = subjects.map do |entry|
|
|
222
221
|
{
|
|
223
222
|
"tool_invocation_id" => entry.fetch("tool_invocation_id"),
|
|
224
223
|
"llm_call_id" => entry.fetch("llm_call_id"),
|
|
@@ -231,11 +230,11 @@ module Phronomy
|
|
|
231
230
|
end
|
|
232
231
|
if external_subjects.empty?
|
|
233
232
|
metadata.delete(
|
|
234
|
-
|
|
233
|
+
ExecutionMetadata::RECOVERY_METADATA_KEY
|
|
235
234
|
)
|
|
236
235
|
else
|
|
237
236
|
metadata[
|
|
238
|
-
|
|
237
|
+
ExecutionMetadata::RECOVERY_METADATA_KEY
|
|
239
238
|
] = RecoverySupport.build_recovery_hash(
|
|
240
239
|
external_subjects
|
|
241
240
|
)
|
|
@@ -268,7 +267,7 @@ module Phronomy
|
|
|
268
267
|
recovery = RecoverySupport.build_recovery_hash(subjects)
|
|
269
268
|
end
|
|
270
269
|
unless recovery
|
|
271
|
-
raise Phronomy::
|
|
270
|
+
raise Phronomy::Storage::ConflictError,
|
|
272
271
|
"Tool Recovery state is missing"
|
|
273
272
|
end
|
|
274
273
|
|
|
@@ -281,7 +280,7 @@ module Phronomy
|
|
|
281
280
|
hash.fetch("state", "unresolved") == "unresolved"
|
|
282
281
|
end
|
|
283
282
|
unless subject_entry
|
|
284
|
-
raise Phronomy::
|
|
283
|
+
raise Phronomy::Storage::ConflictError,
|
|
285
284
|
"Tool Recovery subject is no longer unresolved"
|
|
286
285
|
end
|
|
287
286
|
subject_entry =
|
|
@@ -309,7 +308,7 @@ module Phronomy
|
|
|
309
308
|
updated = current.with(
|
|
310
309
|
phase: :recovery_resolved_failed,
|
|
311
310
|
metadata: current.metadata.merge(
|
|
312
|
-
|
|
311
|
+
ExecutionMetadata::RECOVERY_METADATA_KEY =>
|
|
313
312
|
resolved_recovery
|
|
314
313
|
)
|
|
315
314
|
)
|
|
@@ -329,7 +328,7 @@ module Phronomy
|
|
|
329
328
|
updated = current.with(
|
|
330
329
|
phase: :recovery_resolved_failed,
|
|
331
330
|
metadata: current.metadata.merge(
|
|
332
|
-
|
|
331
|
+
ExecutionMetadata::RECOVERY_METADATA_KEY =>
|
|
333
332
|
recovery_with_failure
|
|
334
333
|
)
|
|
335
334
|
)
|
|
@@ -338,9 +337,7 @@ module Phronomy
|
|
|
338
337
|
when :succeeded
|
|
339
338
|
updated = nil
|
|
340
339
|
with_resolution_f1_capture do |tx|
|
|
341
|
-
|
|
342
|
-
result_ref = main.send(
|
|
343
|
-
:put_runtime_content,
|
|
340
|
+
result_ref = RuntimeRecordEncoder.put_runtime_content(
|
|
344
341
|
tx,
|
|
345
342
|
operation.result
|
|
346
343
|
)
|
|
@@ -363,9 +360,9 @@ module Phronomy
|
|
|
363
360
|
subject_entry.fetch("llm_call_id").to_s
|
|
364
361
|
}.freeze
|
|
365
362
|
)
|
|
366
|
-
records, _calls =
|
|
367
|
-
:encode_runtime_records,
|
|
363
|
+
records, _calls = RuntimeRecordEncoder.encode(
|
|
368
364
|
current,
|
|
365
|
+
agent_id: agent.agent_id,
|
|
369
366
|
tx: tx,
|
|
370
367
|
snapshot: {
|
|
371
368
|
llm_results: [].freeze,
|
|
@@ -388,7 +385,7 @@ module Phronomy
|
|
|
388
385
|
RecoverySupport.unresolved_subjects(
|
|
389
386
|
next_recovery
|
|
390
387
|
)
|
|
391
|
-
tool_batch = Array(current.metadata[
|
|
388
|
+
tool_batch = Array(current.metadata[ExecutionMetadata::TOOL_BATCH_METADATA_KEY]).map do |entry|
|
|
392
389
|
if entry.fetch("tool_invocation_id") == subject_entry.fetch("tool_invocation_id")
|
|
393
390
|
entry.merge("status" => "completed", "result" => operation.result)
|
|
394
391
|
else
|
|
@@ -402,8 +399,8 @@ module Phronomy
|
|
|
402
399
|
working_records:
|
|
403
400
|
current.working_records + records,
|
|
404
401
|
metadata: current.metadata.merge(
|
|
405
|
-
|
|
406
|
-
|
|
402
|
+
ExecutionMetadata::RECOVERY_METADATA_KEY => next_recovery,
|
|
403
|
+
ExecutionMetadata::TOOL_BATCH_METADATA_KEY => tool_batch
|
|
407
404
|
)
|
|
408
405
|
)
|
|
409
406
|
tx.executions.save(
|
|
@@ -418,10 +415,10 @@ module Phronomy
|
|
|
418
415
|
# simplecov:enable
|
|
419
416
|
|
|
420
417
|
def known_non_f1_error?(error)
|
|
421
|
-
error.is_a?(Phronomy::
|
|
422
|
-
error.is_a?(Phronomy::
|
|
423
|
-
error.is_a?(Phronomy::
|
|
424
|
-
error.is_a?(Phronomy::
|
|
418
|
+
error.is_a?(Phronomy::Storage::ConflictError) ||
|
|
419
|
+
error.is_a?(Phronomy::Storage::NotFoundError) ||
|
|
420
|
+
error.is_a?(Phronomy::Storage::SerializationError) ||
|
|
421
|
+
error.is_a?(Phronomy::Storage::UnsupportedBackendError) ||
|
|
425
422
|
error.is_a?(ArgumentError) ||
|
|
426
423
|
error.is_a?(Phronomy::ConfigurationError)
|
|
427
424
|
end
|
|
@@ -477,27 +474,27 @@ module Phronomy
|
|
|
477
474
|
if current.to_h == operation.execution.to_h
|
|
478
475
|
raise(error.is_a?(ResolutionOutcomeUnknownError) ? error.original_error : error)
|
|
479
476
|
end
|
|
480
|
-
raise Phronomy::
|
|
477
|
+
raise Phronomy::Storage::ConflictError,
|
|
481
478
|
"Recovery resolution durable outcome conflicts with both expected pre-state and intended post-state"
|
|
482
479
|
end
|
|
483
480
|
|
|
484
481
|
def apply_resolve_on_event_loop(ready)
|
|
485
482
|
request = ready.request
|
|
486
483
|
event_loop = @runtime.event_loop
|
|
487
|
-
state = event_loop.agent_execution_state(request.execution_id)
|
|
484
|
+
state = Phronomy::Agent::ExecutionRegistry.for(event_loop).agent_execution_state(request.execution_id)
|
|
488
485
|
unless state && state.agent.equal?(agent)
|
|
489
486
|
raise Phronomy::ExecutionRehydrationRequiredError,
|
|
490
487
|
"recovered execution disappeared before resolution apply"
|
|
491
488
|
end
|
|
492
489
|
unless state.execution.equal?(ready.operation.execution) && state.fsm_session_id.nil?
|
|
493
|
-
raise Phronomy::
|
|
490
|
+
raise Phronomy::Storage::ConflictError,
|
|
494
491
|
"Recovery execution changed before resolution apply"
|
|
495
492
|
end
|
|
496
493
|
raise ready.error if ready.error
|
|
497
494
|
|
|
498
495
|
result = ready.result
|
|
499
496
|
if result.execution
|
|
500
|
-
event_loop.replace_agent_execution(request.execution_id, execution: result.execution)
|
|
497
|
+
Phronomy::Agent::ExecutionRegistry.for(event_loop).replace_agent_execution(request.execution_id, execution: result.execution)
|
|
501
498
|
end
|
|
502
499
|
raise result.error if result.error
|
|
503
500
|
|
|
@@ -57,14 +57,14 @@ module Phronomy
|
|
|
57
57
|
Array(tx.executions.list_active(agent.agent_id))
|
|
58
58
|
end
|
|
59
59
|
if active.length > 1
|
|
60
|
-
raise Phronomy::
|
|
60
|
+
raise Phronomy::Storage::ConflictError,
|
|
61
61
|
"multiple active AgentExecutions exist for #{agent.agent_id}"
|
|
62
62
|
end
|
|
63
63
|
return agent if active.empty?
|
|
64
64
|
|
|
65
65
|
execution = active.first
|
|
66
66
|
unless execution.agent_id.to_s == agent.agent_id.to_s
|
|
67
|
-
raise Phronomy::
|
|
67
|
+
raise Phronomy::Storage::ConflictError,
|
|
68
68
|
"active AgentExecution belongs to another Agent: #{execution.agent_id}"
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -187,14 +187,10 @@ module Phronomy
|
|
|
187
187
|
private
|
|
188
188
|
|
|
189
189
|
def post_control(command)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
Phronomy::EventLoop::SYSTEM_CHANNEL_ID,
|
|
195
|
-
payload: {command: command}.freeze
|
|
196
|
-
)
|
|
197
|
-
)
|
|
190
|
+
admission = command.is_a?(InstallCommand) || command.is_a?(ResolveCommand)
|
|
191
|
+
completion = admission ? command.completion : command.request.completion
|
|
192
|
+
ExecutionRegistry.for(@runtime.event_loop).post(command,
|
|
193
|
+
admission: admission, completion: completion)
|
|
198
194
|
rescue Phronomy::RuntimeShutdownError
|
|
199
195
|
false
|
|
200
196
|
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Agent
|
|
5
|
+
module RecoverySupport
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def canonical_copy(value)
|
|
9
|
+
Phronomy::Values::Serializable.convert(value,
|
|
10
|
+
unsupported_message: "Recovery value is not canonically serializable")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def tool_calls_from_outcome(outcome)
|
|
14
|
+
Array(outcome.tool_calls)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def normalize_provider_outcome(value)
|
|
18
|
+
return value if value.is_a?(Phronomy::Agent::ProviderCallOutcome)
|
|
19
|
+
|
|
20
|
+
if value.is_a?(Hash)
|
|
21
|
+
return Phronomy::Agent::ProviderCallOutcome.from_h(value)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
captured = Phronomy::Agent::ProviderCallOutcome.capture(value)
|
|
25
|
+
return captured if captured
|
|
26
|
+
|
|
27
|
+
raise ArgumentError,
|
|
28
|
+
"LLM Recovery :succeeded requires a Provider result or ProviderCallOutcome-compatible Hash"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resolution_failure(error)
|
|
32
|
+
{
|
|
33
|
+
"class" => error.class.name.to_s,
|
|
34
|
+
"message" => error.message.to_s
|
|
35
|
+
}.freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def error_from_failure(failure)
|
|
39
|
+
hash = failure.to_h { |key, value| [key.to_s, value] }
|
|
40
|
+
Phronomy::Error.new(
|
|
41
|
+
"#{hash.fetch("class", "Error")}: #{hash.fetch("message", "Recovery-resolved failure")}"
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def event_payload(execution, descriptor)
|
|
46
|
+
{
|
|
47
|
+
execution_id: execution.execution_id,
|
|
48
|
+
execution_revision: execution.execution_revision,
|
|
49
|
+
reason: descriptor.fetch(:reason),
|
|
50
|
+
subject: Phronomy::Recovery.normalize_subject(
|
|
51
|
+
descriptor.fetch(:subject)
|
|
52
|
+
),
|
|
53
|
+
allowed_outcomes: Array(
|
|
54
|
+
descriptor.fetch(:allowed_outcomes)
|
|
55
|
+
).map(&:to_sym).freeze,
|
|
56
|
+
facts: Phronomy::Values::Immutable.copy(
|
|
57
|
+
descriptor.fetch(:facts, {})
|
|
58
|
+
)
|
|
59
|
+
}.freeze
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def pending_llm_descriptor(execution)
|
|
63
|
+
llm_call_id = execution.metadata[ExecutionMetadata::PENDING_LLM_ID_KEY]
|
|
64
|
+
return unless llm_call_id
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
reason: :outcome_unknown,
|
|
68
|
+
subject: {
|
|
69
|
+
type: :llm_call,
|
|
70
|
+
llm_call_id: llm_call_id
|
|
71
|
+
},
|
|
72
|
+
allowed_outcomes: Phronomy::Recovery::OUTCOMES,
|
|
73
|
+
facts: {
|
|
74
|
+
manifest_ref: execution.metadata["manifest_ref"],
|
|
75
|
+
call_sequence: execution.llm_calls.length + 1
|
|
76
|
+
}.compact.freeze
|
|
77
|
+
}.freeze
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def recovery_hash(execution)
|
|
81
|
+
value = execution.metadata[ExecutionMetadata::RECOVERY_METADATA_KEY]
|
|
82
|
+
value.is_a?(Hash) ? value : nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def current_tool_descriptor(execution)
|
|
86
|
+
recovery = recovery_hash(execution)
|
|
87
|
+
return unless recovery
|
|
88
|
+
|
|
89
|
+
subjects = Array(recovery["subjects"] || recovery[:subjects])
|
|
90
|
+
current = subjects.find do |entry|
|
|
91
|
+
hash = entry.to_h { |key, value| [key.to_s, value] }
|
|
92
|
+
hash.fetch("state", "unresolved") == "unresolved"
|
|
93
|
+
end
|
|
94
|
+
return unless current
|
|
95
|
+
|
|
96
|
+
hash = current.to_h { |key, value| [key.to_s, value] }
|
|
97
|
+
{
|
|
98
|
+
reason: (recovery["reason"] || recovery[:reason] || "outcome_unknown").to_sym,
|
|
99
|
+
subject: {
|
|
100
|
+
type: :tool_invocation,
|
|
101
|
+
tool_invocation_id: hash.fetch("tool_invocation_id")
|
|
102
|
+
},
|
|
103
|
+
allowed_outcomes: Array(
|
|
104
|
+
recovery["allowed_outcomes"] ||
|
|
105
|
+
recovery[:allowed_outcomes] ||
|
|
106
|
+
Phronomy::Recovery::OUTCOMES
|
|
107
|
+
).map(&:to_sym),
|
|
108
|
+
facts: {
|
|
109
|
+
tool_call_id: hash["tool_call_id"],
|
|
110
|
+
tool_name: hash["tool_name"],
|
|
111
|
+
arguments: hash["arguments"],
|
|
112
|
+
llm_call_id: hash["llm_call_id"]
|
|
113
|
+
}.compact
|
|
114
|
+
}.freeze
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def pending_tool_subjects(execution)
|
|
118
|
+
Array(execution.metadata[ExecutionMetadata::TOOL_BATCH_METADATA_KEY]).filter_map do |entry|
|
|
119
|
+
hash = entry.to_h { |key, value| [key.to_s, value] }
|
|
120
|
+
next unless %w[authorized awaiting_approval].include?(
|
|
121
|
+
hash.fetch("status")
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
"tool_invocation_id" => hash.fetch("tool_invocation_id"),
|
|
126
|
+
"llm_call_id" => hash["llm_call_id"],
|
|
127
|
+
"tool_call_id" => hash.fetch("tool_call_id"),
|
|
128
|
+
"tool_name" => hash.fetch("tool_name"),
|
|
129
|
+
"arguments" => canonical_copy(
|
|
130
|
+
hash["arguments"] || hash["raw_arguments"] || {}
|
|
131
|
+
),
|
|
132
|
+
"state" => "unresolved"
|
|
133
|
+
}.compact.freeze
|
|
134
|
+
end.freeze
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def pending_tool_descriptor(execution)
|
|
138
|
+
subjects = pending_tool_subjects(execution)
|
|
139
|
+
return if subjects.empty?
|
|
140
|
+
|
|
141
|
+
first = subjects.first
|
|
142
|
+
{
|
|
143
|
+
reason: :outcome_unknown,
|
|
144
|
+
subject: {
|
|
145
|
+
type: :tool_invocation,
|
|
146
|
+
tool_invocation_id: first.fetch("tool_invocation_id")
|
|
147
|
+
},
|
|
148
|
+
allowed_outcomes: Phronomy::Recovery::OUTCOMES,
|
|
149
|
+
facts: {
|
|
150
|
+
tool_call_id: first["tool_call_id"],
|
|
151
|
+
tool_name: first["tool_name"],
|
|
152
|
+
arguments: first["arguments"],
|
|
153
|
+
llm_call_id: first["llm_call_id"]
|
|
154
|
+
}.compact
|
|
155
|
+
}.freeze
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def recovery_descriptor(execution)
|
|
159
|
+
case execution.phase.to_sym
|
|
160
|
+
when :calling_llm
|
|
161
|
+
pending_llm_descriptor(execution)
|
|
162
|
+
when :resuming
|
|
163
|
+
approved = execution.approval_request &&
|
|
164
|
+
(
|
|
165
|
+
execution.approval_request["approved"] ||
|
|
166
|
+
execution.approval_request[:approved]
|
|
167
|
+
)
|
|
168
|
+
approved ? pending_tool_descriptor(execution) : nil
|
|
169
|
+
when :dispatching_tools
|
|
170
|
+
pending_tool_descriptor(execution)
|
|
171
|
+
when :recovery_tools
|
|
172
|
+
current_tool_descriptor(execution)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def build_tool_subjects(execution, llm_call_id, outcome)
|
|
177
|
+
tool_calls_from_outcome(outcome).map do |call|
|
|
178
|
+
call_hash = canonical_copy(call)
|
|
179
|
+
tool_call_id = call_hash.fetch("id").to_s
|
|
180
|
+
tool_name = call_hash.fetch("name").to_s
|
|
181
|
+
{
|
|
182
|
+
"tool_invocation_id" => ToolInvocation.semantic_id(
|
|
183
|
+
execution_id: execution.execution_id,
|
|
184
|
+
llm_call_id: llm_call_id,
|
|
185
|
+
tool_call_id: tool_call_id,
|
|
186
|
+
tool_name: tool_name
|
|
187
|
+
),
|
|
188
|
+
"llm_call_id" => llm_call_id.to_s,
|
|
189
|
+
"tool_call_id" => tool_call_id,
|
|
190
|
+
"tool_name" => tool_name,
|
|
191
|
+
"arguments" => canonical_copy(call_hash.fetch("arguments", {})),
|
|
192
|
+
"state" => "unresolved"
|
|
193
|
+
}.freeze
|
|
194
|
+
end.freeze
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def build_recovery_hash(subjects, reason: :outcome_unknown, allowed_outcomes: Phronomy::Recovery::OUTCOMES)
|
|
198
|
+
{
|
|
199
|
+
"version" => ExecutionMetadata::CONTRACT_VERSION,
|
|
200
|
+
"reason" => reason.to_s,
|
|
201
|
+
"allowed_outcomes" => Array(allowed_outcomes).map(&:to_s),
|
|
202
|
+
"subjects" => Array(subjects).map { |entry| canonical_copy(entry) }
|
|
203
|
+
}.freeze
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def update_recovery_subject(recovery, tool_invocation_id:, state:, outcome:, result_ref: nil)
|
|
207
|
+
copy = canonical_copy(recovery)
|
|
208
|
+
copy["subjects"] = Array(copy.fetch("subjects")).map do |entry|
|
|
209
|
+
next entry unless entry.fetch("tool_invocation_id").to_s == tool_invocation_id.to_s
|
|
210
|
+
|
|
211
|
+
entry.merge(
|
|
212
|
+
"state" => state.to_s,
|
|
213
|
+
"outcome" => outcome.to_s,
|
|
214
|
+
"result_ref" => result_ref
|
|
215
|
+
).compact
|
|
216
|
+
end
|
|
217
|
+
copy.freeze
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def unresolved_subjects(recovery)
|
|
221
|
+
Array(recovery.fetch("subjects")).select do |entry|
|
|
222
|
+
entry.fetch("state", "unresolved") == "unresolved"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -41,7 +41,7 @@ module Phronomy
|
|
|
41
41
|
sequence: values[:sequence] && Integer(values[:sequence]),
|
|
42
42
|
constraint: effective_constraint,
|
|
43
43
|
priority: Integer(values[:priority] || 0),
|
|
44
|
-
metadata: Immutable.copy(values[:metadata] || {})
|
|
44
|
+
metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
super(**normalized)
|
|
@@ -102,18 +102,7 @@ module Phronomy
|
|
|
102
102
|
private
|
|
103
103
|
|
|
104
104
|
def immutable_copy(value)
|
|
105
|
-
|
|
106
|
-
when Hash
|
|
107
|
-
value.each_with_object({}) do |(key, item), result|
|
|
108
|
-
result[immutable_copy(key)] = immutable_copy(item)
|
|
109
|
-
end.freeze
|
|
110
|
-
when Array
|
|
111
|
-
value.map { |item| immutable_copy(item) }.freeze
|
|
112
|
-
when String
|
|
113
|
-
value.dup.freeze
|
|
114
|
-
else
|
|
115
|
-
value
|
|
116
|
-
end
|
|
105
|
+
Phronomy::Values::Immutable.copy(value)
|
|
117
106
|
end
|
|
118
107
|
end
|
|
119
108
|
end
|
data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb}
RENAMED
|
@@ -75,16 +75,7 @@ module Phronomy
|
|
|
75
75
|
private
|
|
76
76
|
|
|
77
77
|
def immutable_copy(value)
|
|
78
|
-
|
|
79
|
-
when Hash
|
|
80
|
-
value.each_with_object({}) { |(k, v), h| h[immutable_copy(k)] = immutable_copy(v) }.freeze
|
|
81
|
-
when Array
|
|
82
|
-
value.map { |v| immutable_copy(v) }.freeze
|
|
83
|
-
when String
|
|
84
|
-
value.dup.freeze
|
|
85
|
-
else
|
|
86
|
-
value
|
|
87
|
-
end
|
|
78
|
+
Phronomy::Values::Immutable.copy(value)
|
|
88
79
|
end
|
|
89
80
|
end
|
|
90
81
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Agent
|
|
5
|
+
# Builds invocation-independent Tool decorators from explicit Agent settings.
|
|
6
|
+
# Authorization, execution and Orchestrator context remain with their owners.
|
|
7
|
+
# @api private
|
|
8
|
+
class ToolBinding
|
|
9
|
+
def initialize(tool_class, alias_name:)
|
|
10
|
+
@tool_class = if alias_name
|
|
11
|
+
Class.new(tool_class) do
|
|
12
|
+
tool_name alias_name
|
|
13
|
+
end
|
|
14
|
+
else
|
|
15
|
+
tool_class
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def prepare(result_filters:)
|
|
20
|
+
return @tool_class if result_filters.empty?
|
|
21
|
+
|
|
22
|
+
effective_name = @tool_class.new.name
|
|
23
|
+
custom_async_call =
|
|
24
|
+
@tool_class.instance_method(:call_async).owner !=
|
|
25
|
+
Phronomy::Agent::Context::Capability::Base
|
|
26
|
+
filter_async_result = method(:filter_async_result)
|
|
27
|
+
|
|
28
|
+
Class.new(@tool_class) do
|
|
29
|
+
tool_name effective_name
|
|
30
|
+
define_method(:call) do |args, **kwargs|
|
|
31
|
+
result = super(args, **kwargs)
|
|
32
|
+
result_filters.inject(result) { |val, filter|
|
|
33
|
+
filter.call(val, tool_name: name, args: args)
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if custom_async_call
|
|
38
|
+
define_method(:call_async) do |args, **kwargs|
|
|
39
|
+
source = super(args, **kwargs)
|
|
40
|
+
filter_async_result.call(source, name) do |value|
|
|
41
|
+
result_filters.inject(value) { |val, filter|
|
|
42
|
+
filter.call(val, tool_name: name, args: args)
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def filter_async_result(source, tool_name, &filter_result)
|
|
53
|
+
filtered = Phronomy::Concurrency::PhysicalCompletionTask.deferred(
|
|
54
|
+
name: "tool-filter-#{tool_name}"
|
|
55
|
+
)
|
|
56
|
+
source_has_physical_signal = source.respond_to?(:on_physical_complete)
|
|
57
|
+
source.on_physical_complete { filtered.mark_physical_complete! } if
|
|
58
|
+
source_has_physical_signal
|
|
59
|
+
source.on_complete do |value, error|
|
|
60
|
+
if error
|
|
61
|
+
propagate_failure(filtered, source, error, source_has_physical_signal)
|
|
62
|
+
else
|
|
63
|
+
complete_filtered_result(filtered, source_has_physical_signal) do
|
|
64
|
+
filter_result.call(value)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
filtered
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def propagate_failure(filtered, source, error, source_has_physical_signal)
|
|
72
|
+
filtered.mark_physical_complete! unless source_has_physical_signal
|
|
73
|
+
if source.respond_to?(:status) && source.status == :cancelled
|
|
74
|
+
filtered.cancel!(error)
|
|
75
|
+
else
|
|
76
|
+
filtered.fail(error)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def complete_filtered_result(filtered, source_has_physical_signal)
|
|
81
|
+
result = yield
|
|
82
|
+
filtered.mark_physical_complete! unless source_has_physical_signal
|
|
83
|
+
filtered.complete(result)
|
|
84
|
+
rescue => filter_error
|
|
85
|
+
filtered.mark_physical_complete! unless source_has_physical_signal
|
|
86
|
+
filtered.fail(filter_error)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb}
RENAMED
|
@@ -4,8 +4,8 @@ module Phronomy
|
|
|
4
4
|
module Agent
|
|
5
5
|
# Normal control-transfer signal raised before RubyLLM executes a Tool Call.
|
|
6
6
|
#
|
|
7
|
-
# RubyLLM
|
|
8
|
-
# been added to Chat#messages before
|
|
7
|
+
# RubyLLM 2.0 guarantees that the complete assistant message has already
|
|
8
|
+
# been added to Chat#messages before after_message runs. Keeping that
|
|
9
9
|
# message here allows Phronomy to persist the complete Provider outcome even
|
|
10
10
|
# though the RubyLLM call itself unwinds through this exception.
|
|
11
11
|
# @api private
|