phronomy 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +71 -0
- data/CONTRIBUTING.md +4 -4
- data/README.md +14 -7
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -22
- data/benchmark/bench_context_assembler.rb +4 -5
- data/benchmark/bench_regression.rb +1 -1
- data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
- data/docs/architecture/agent-context.md +5 -3
- data/docs/architecture/agent-transition-ownership.md +89 -0
- data/docs/architecture/before-llm-input.md +6 -0
- data/docs/architecture/context-management.md +30 -0
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-metadata-and-values.md +80 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +8 -2
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +29 -6
- data/docs/architecture/refactoring-closure.md +334 -0
- data/docs/architecture/remaining-refactoring-plan.md +374 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/tool-schema-recording-gap.md +50 -0
- data/docs/architecture/tracing.md +4 -4
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +209 -0
- data/docs/async-composition.md +205 -0
- data/docs/decisions/010-cooperative-first-concurrency.md +23 -23
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
- data/docs/decisions/032-storage-backend-composition.md +80 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
- data/docs/decisions/036-context-contract-ownership.md +86 -0
- data/docs/decisions/037-common-definition-ownership.md +62 -0
- data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
- data/docs/decisions/040-configuration-default-composition.md +94 -0
- data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
- data/docs/decisions/042-feature-owned-execution-state.md +106 -0
- data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
- data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
- data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
- data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
- data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
- data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
- data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
- data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
- data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
- data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
- data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
- data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
- data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
- data/docs/decisions/058-neutral-storage-primitives.md +78 -0
- data/docs/decisions/README.md +35 -6
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
- data/docs/features.md +21 -12
- data/docs/getting-started.md +10 -10
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +7 -1
- data/docs/migrations/handoff-runner-multi-agent.md +35 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/persistence-backends.md +162 -204
- data/docs/runtime-and-concurrency.md +205 -60
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/agent/async_event_api.rb +2 -2
- data/lib/phronomy/agent/base.rb +55 -251
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context/capability/base.rb +67 -26
- data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
- data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
- data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
- data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
- data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
- data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
- data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
- data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
- data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
- data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
- data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +20 -96
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +12 -13
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
- data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +22 -73
- data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
- data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
- data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
- data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
- data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
- data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
- data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
- data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
- data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
- data/lib/phronomy/agent/persistence/codec.rb +358 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
- data/lib/phronomy/agent/persistence/queries.rb +61 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
- data/lib/phronomy/agent/persistence/watermark.rb +27 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +36 -41
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +9 -13
- data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
- data/lib/phronomy/agent/selection/candidate.rb +1 -1
- data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
- data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
- data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
- data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
- data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +2 -2
- data/lib/phronomy/common/configuration_error.rb +7 -0
- data/lib/phronomy/common/error.rb +5 -0
- data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
- data/lib/phronomy/common/values/serializable.rb +32 -0
- data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
- data/lib/phronomy/configuration/global_configuration.rb +26 -0
- data/lib/phronomy/content_store/storage_schema.rb +11 -0
- data/lib/phronomy/content_store/stored_contents.rb +43 -0
- data/lib/phronomy/engine/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → engine/blocking.rb} +15 -8
- data/lib/phronomy/engine/cancellation_error.rb +7 -0
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +29 -16
- data/lib/phronomy/engine/concurrency/operation_binding.rb +43 -0
- data/lib/phronomy/engine/concurrency/physical_completion_task.rb +4 -62
- data/lib/phronomy/engine/concurrency/result_collector.rb +99 -0
- data/lib/phronomy/engine/concurrency/result_composition.rb +145 -0
- data/lib/phronomy/engine/concurrency/subscriptions.rb +68 -0
- data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/engine/event_loop.rb +160 -626
- data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/engine/execution.rb +229 -0
- data/lib/phronomy/engine/execution_cancellation_error.rb +14 -0
- data/lib/phronomy/engine/execution_receiver.rb +65 -0
- data/lib/phronomy/engine/execution_timeout_error.rb +14 -0
- data/lib/phronomy/engine/fsm_protocol.rb +14 -0
- data/lib/phronomy/engine/fsm_session.rb +38 -31
- data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +3 -1
- data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +3 -1
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +3 -1
- data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +13 -1
- data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
- data/lib/phronomy/engine/runtime/timer_queue.rb +11 -0
- data/lib/phronomy/engine/runtime.rb +80 -84
- data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/engine/{task.rb → task_result.rb} +101 -42
- data/lib/phronomy/engine/timeout_error.rb +7 -0
- data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
- data/lib/phronomy/generation/generator_verifier.rb +118 -0
- data/lib/phronomy/generation/low_confidence_error.rb +14 -0
- data/lib/phronomy/llm_adapter/base.rb +2 -2
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
- data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
- data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
- data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
- data/lib/phronomy/llm_contract/transport_error.rb +7 -0
- data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +8 -8
- data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +36 -68
- data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
- data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
- data/lib/phronomy/persistence/api/persistence.rb +140 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
- data/lib/phronomy/persistence_composition/repositories.rb +77 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/storage/backend.rb +101 -0
- data/lib/phronomy/storage/backends/in_memory.rb +157 -0
- data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
- data/lib/phronomy/storage/blobs.rb +31 -0
- data/lib/phronomy/storage/condition.rb +25 -0
- data/lib/phronomy/storage/condition_failed_error.rb +16 -0
- data/lib/phronomy/storage/conflict_error.rb +9 -0
- data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
- data/lib/phronomy/storage/entry.rb +33 -0
- data/lib/phronomy/storage/guard_ref.rb +13 -0
- data/lib/phronomy/storage/not_found_error.rb +9 -0
- data/lib/phronomy/storage/record_codec.rb +177 -0
- data/lib/phronomy/storage/records.rb +61 -0
- data/lib/phronomy/storage/resource.rb +126 -0
- data/lib/phronomy/storage/scope.rb +25 -0
- data/lib/phronomy/storage/serialization_error.rb +9 -0
- data/lib/phronomy/storage/streams.rb +44 -0
- data/lib/phronomy/storage/transaction_error.rb +10 -0
- data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
- data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
- data/lib/phronomy/storage/validation.rb +53 -0
- data/lib/phronomy/storage/view.rb +131 -0
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +5 -3
- data/lib/phronomy/testing/fake_clock.rb +13 -9
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/contract/tool_error.rb +7 -0
- data/lib/phronomy/tools/agent.rb +6 -6
- data/lib/phronomy/vector_store/async_backend.rb +5 -5
- data/lib/phronomy/vector_store/embeddings/base.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +126 -75
- data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
- data/lib/phronomy/workflow/phase_machine_builder.rb +8 -8
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
- data/lib/phronomy.rb +52 -94
- data/scripts/api_snapshot.rb +4 -2
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/phronomy/agent.rbs +4 -5
- data/sig/phronomy/execution_receiver.rbs +34 -0
- data/sig/phronomy/extensions.rbs +5 -5
- data/sig/phronomy/handoff.rbs +4 -2
- data/sig/phronomy/multi_agent.rbs +19 -3
- data/sig/phronomy/persistence.rbs +7 -91
- data/sig/phronomy/runtime.rbs +34 -7
- data/sig/phronomy/storage.rbs +174 -0
- data/sig/phronomy/tool.rbs +11 -2
- data/sig/phronomy/workflow.rbs +1 -1
- metadata +236 -99
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3138
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_executor.rb +0 -55
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +0 -137
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +0 -118
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
- data/lib/phronomy/persistence/durable_codec.rb +0 -706
- data/lib/phronomy/persistence/in_memory.rb +0 -690
- data/lib/phronomy/persistence/repository_facades.rb +0 -535
- data/lib/phronomy/persistence.rb +0 -276
- data/lib/phronomy/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/workflow_recovery.rb +0 -123
- /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
- /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
- /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
- /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
|
@@ -21,7 +21,7 @@ module Phronomy
|
|
|
21
21
|
knowledge: Array(knowledge).freeze,
|
|
22
22
|
tools: Array(tools).freeze,
|
|
23
23
|
conversation: Array(conversation).map { |group| Array(group).freeze }.freeze,
|
|
24
|
-
metadata: Immutable.copy(metadata || {})
|
|
24
|
+
metadata: Phronomy::Values::Immutable.copy(metadata || {})
|
|
25
25
|
)
|
|
26
26
|
freeze
|
|
27
27
|
end
|
data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb}
RENAMED
|
@@ -50,8 +50,8 @@ module Phronomy
|
|
|
50
50
|
knowledge: normalized_knowledge,
|
|
51
51
|
tools: normalized_tools,
|
|
52
52
|
conversation: normalized_conversation,
|
|
53
|
-
model_config: Immutable.copy(values[:model_config] || {}),
|
|
54
|
-
metadata: Immutable.copy(values[:metadata] || {})
|
|
53
|
+
model_config: Phronomy::Values::Immutable.copy(values[:model_config] || {}),
|
|
54
|
+
metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})
|
|
55
55
|
))
|
|
56
56
|
freeze
|
|
57
57
|
end
|
|
@@ -145,7 +145,7 @@ module Phronomy
|
|
|
145
145
|
) do
|
|
146
146
|
def initialize(**values)
|
|
147
147
|
provenance = ContextPolicyInput.send(:normalize_provenance, values[:provenance])
|
|
148
|
-
definition = Immutable.copy(values.fetch(:definition))
|
|
148
|
+
definition = Phronomy::Values::Immutable.copy(values.fetch(:definition))
|
|
149
149
|
unless definition.is_a?(Hash) && definition["name"].to_s != ""
|
|
150
150
|
raise ArgumentError, "ContextPolicyInput ToolItem definition requires a non-empty name"
|
|
151
151
|
end
|
|
@@ -156,7 +156,7 @@ module Phronomy
|
|
|
156
156
|
estimated_tokens: ContextPolicyInput.send(:normalize_estimated_tokens, values[:estimated_tokens]),
|
|
157
157
|
required: !!values[:required],
|
|
158
158
|
provenance: provenance,
|
|
159
|
-
metadata: Immutable.copy(values[:metadata] || {})
|
|
159
|
+
metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})
|
|
160
160
|
)
|
|
161
161
|
raise ArgumentError, "ContextPolicyInput ToolItem id must not be empty" if id.empty?
|
|
162
162
|
freeze
|
|
@@ -212,12 +212,12 @@ module Phronomy
|
|
|
212
212
|
id: id,
|
|
213
213
|
kind: kind,
|
|
214
214
|
role: role,
|
|
215
|
-
content: Immutable.copy(values[:content]),
|
|
215
|
+
content: Phronomy::Values::Immutable.copy(values[:content]),
|
|
216
216
|
content_format: content_format,
|
|
217
217
|
estimated_tokens: normalize_estimated_tokens(values[:estimated_tokens]),
|
|
218
218
|
required: !!values[:required],
|
|
219
219
|
provenance: normalize_provenance(values[:provenance]),
|
|
220
|
-
metadata: Immutable.copy(values[:metadata] || {})
|
|
220
|
+
metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})
|
|
221
221
|
}
|
|
222
222
|
end
|
|
223
223
|
|
data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb}
RENAMED
|
@@ -9,7 +9,7 @@ module Phronomy
|
|
|
9
9
|
:config, :call_sequence
|
|
10
10
|
) do
|
|
11
11
|
def initialize(**values)
|
|
12
|
-
super(**values.merge(config: Immutable.copy(values.fetch(:config, {}))))
|
|
12
|
+
super(**values.merge(config: Phronomy::Values::Immutable.copy(values.fetch(:config, {}))))
|
|
13
13
|
freeze
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module Agent
|
|
5
5
|
# ContentStore-backed durable codec boundary for one Provider input manifest.
|
|
6
|
-
# It is intentionally separate from
|
|
6
|
+
# It is intentionally separate from Storage::DurableRecord.
|
|
7
7
|
class LLMInputManifest
|
|
8
8
|
VERSION = "0.1"
|
|
9
9
|
CALL_MODES = %i[ask complete].freeze
|
|
@@ -18,7 +18,7 @@ module Phronomy
|
|
|
18
18
|
if delivery.nil? || !SEGMENT_DELIVERIES.include?(delivery.to_s)
|
|
19
19
|
raise ArgumentError, "unknown Segment delivery: #{delivery.inspect}"
|
|
20
20
|
end
|
|
21
|
-
super(**values.merge(metadata: Immutable.copy(values[:metadata] || {})))
|
|
21
|
+
super(**values.merge(metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})))
|
|
22
22
|
freeze
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -62,7 +62,7 @@ module Phronomy
|
|
|
62
62
|
label: label
|
|
63
63
|
)
|
|
64
64
|
unless SEGMENT_DELIVERIES.include?(delivery)
|
|
65
|
-
raise Phronomy::
|
|
65
|
+
raise Phronomy::Storage::SerializationError,
|
|
66
66
|
"LLMInputManifest segment delivery must be one of #{SEGMENT_DELIVERIES.inspect}, got #{delivery.inspect}"
|
|
67
67
|
end
|
|
68
68
|
tool_call_id = LLMInputManifest.send(
|
|
@@ -87,10 +87,10 @@ module Phronomy
|
|
|
87
87
|
tool_call_id: tool_call_id,
|
|
88
88
|
metadata: metadata
|
|
89
89
|
)
|
|
90
|
-
rescue Phronomy::
|
|
90
|
+
rescue Phronomy::Storage::SerializationError
|
|
91
91
|
raise
|
|
92
92
|
rescue => error
|
|
93
|
-
raise Phronomy::
|
|
93
|
+
raise Phronomy::Storage::SerializationError,
|
|
94
94
|
"invalid LLMInputManifest segment: #{error.class}: #{error.message}"
|
|
95
95
|
end
|
|
96
96
|
|
|
@@ -162,7 +162,7 @@ module Phronomy
|
|
|
162
162
|
label = "LLMInputManifest"
|
|
163
163
|
version = source.fetch("version")
|
|
164
164
|
unless version.is_a?(String) && version == VERSION
|
|
165
|
-
raise Phronomy::
|
|
165
|
+
raise Phronomy::Storage::SerializationError,
|
|
166
166
|
"unsupported LLMInputManifest version: #{version.inspect}; " \
|
|
167
167
|
"current version is #{VERSION.inspect}"
|
|
168
168
|
end
|
|
@@ -229,10 +229,10 @@ module Phronomy
|
|
|
229
229
|
adapter_name: adapter_name,
|
|
230
230
|
adapter_version: adapter_version
|
|
231
231
|
)
|
|
232
|
-
rescue Phronomy::
|
|
232
|
+
rescue Phronomy::Storage::SerializationError
|
|
233
233
|
raise
|
|
234
234
|
rescue => error
|
|
235
|
-
raise Phronomy::
|
|
235
|
+
raise Phronomy::Storage::SerializationError,
|
|
236
236
|
"invalid LLMInputManifest: #{error.class}: #{error.message}"
|
|
237
237
|
end
|
|
238
238
|
|
|
@@ -262,11 +262,11 @@ module Phronomy
|
|
|
262
262
|
|
|
263
263
|
def strict_source!(hash, required:, optional:, label:)
|
|
264
264
|
unless hash.is_a?(Hash)
|
|
265
|
-
raise Phronomy::
|
|
265
|
+
raise Phronomy::Storage::SerializationError,
|
|
266
266
|
"#{label} must be a Hash"
|
|
267
267
|
end
|
|
268
268
|
unless hash.keys.all? { |key| key.is_a?(String) }
|
|
269
|
-
raise Phronomy::
|
|
269
|
+
raise Phronomy::Storage::SerializationError,
|
|
270
270
|
"#{label} keys must all be String"
|
|
271
271
|
end
|
|
272
272
|
|
|
@@ -277,13 +277,13 @@ module Phronomy
|
|
|
277
277
|
details = []
|
|
278
278
|
details << "missing=#{missing.inspect}" unless missing.empty?
|
|
279
279
|
details << "unknown=#{unknown.inspect}" unless unknown.empty?
|
|
280
|
-
raise Phronomy::
|
|
280
|
+
raise Phronomy::Storage::SerializationError,
|
|
281
281
|
"#{label} schema mismatch (#{details.join(", ")})"
|
|
282
282
|
end
|
|
283
283
|
Phronomy::CanonicalJSON.dump(hash)
|
|
284
284
|
hash
|
|
285
285
|
rescue ArgumentError => error
|
|
286
|
-
raise Phronomy::
|
|
286
|
+
raise Phronomy::Storage::SerializationError,
|
|
287
287
|
"#{label} is not canonical JSON compatible: #{error.message}"
|
|
288
288
|
end
|
|
289
289
|
|
|
@@ -291,7 +291,7 @@ module Phronomy
|
|
|
291
291
|
value = hash.fetch(key)
|
|
292
292
|
return value if value.is_a?(Integer)
|
|
293
293
|
|
|
294
|
-
raise Phronomy::
|
|
294
|
+
raise Phronomy::Storage::SerializationError,
|
|
295
295
|
"#{label} #{key} must be an Integer"
|
|
296
296
|
end
|
|
297
297
|
|
|
@@ -299,7 +299,7 @@ module Phronomy
|
|
|
299
299
|
value = hash.fetch(key)
|
|
300
300
|
return value if value.is_a?(Integer) && value.positive?
|
|
301
301
|
|
|
302
|
-
raise Phronomy::
|
|
302
|
+
raise Phronomy::Storage::SerializationError,
|
|
303
303
|
"#{label} #{key} must be a positive Integer"
|
|
304
304
|
end
|
|
305
305
|
|
|
@@ -307,7 +307,7 @@ module Phronomy
|
|
|
307
307
|
value = hash.fetch(key)
|
|
308
308
|
return value if value.is_a?(Integer) && value >= 0
|
|
309
309
|
|
|
310
|
-
raise Phronomy::
|
|
310
|
+
raise Phronomy::Storage::SerializationError,
|
|
311
311
|
"#{label} #{key} must be a non-negative Integer"
|
|
312
312
|
end
|
|
313
313
|
|
|
@@ -315,7 +315,7 @@ module Phronomy
|
|
|
315
315
|
value = hash.fetch(key)
|
|
316
316
|
return value if value.is_a?(String) && !value.empty?
|
|
317
317
|
|
|
318
|
-
raise Phronomy::
|
|
318
|
+
raise Phronomy::Storage::SerializationError,
|
|
319
319
|
"#{label} #{key} must be a non-empty String"
|
|
320
320
|
end
|
|
321
321
|
|
|
@@ -323,7 +323,7 @@ module Phronomy
|
|
|
323
323
|
value = hash[key]
|
|
324
324
|
return value if value.nil? || value.is_a?(String)
|
|
325
325
|
|
|
326
|
-
raise Phronomy::
|
|
326
|
+
raise Phronomy::Storage::SerializationError,
|
|
327
327
|
"#{label} #{key} must be a String or nil"
|
|
328
328
|
end
|
|
329
329
|
|
|
@@ -331,7 +331,7 @@ module Phronomy
|
|
|
331
331
|
value = hash.fetch(key)
|
|
332
332
|
return value if value.is_a?(String) && allowed.include?(value)
|
|
333
333
|
|
|
334
|
-
raise Phronomy::
|
|
334
|
+
raise Phronomy::Storage::SerializationError,
|
|
335
335
|
"#{label} #{key} must be one of #{allowed.inspect}"
|
|
336
336
|
end
|
|
337
337
|
|
|
@@ -339,7 +339,7 @@ module Phronomy
|
|
|
339
339
|
value = hash.fetch(key)
|
|
340
340
|
return value if value.is_a?(Array)
|
|
341
341
|
|
|
342
|
-
raise Phronomy::
|
|
342
|
+
raise Phronomy::Storage::SerializationError,
|
|
343
343
|
"#{label} #{key} must be an Array"
|
|
344
344
|
end
|
|
345
345
|
|
|
@@ -347,7 +347,7 @@ module Phronomy
|
|
|
347
347
|
value = hash.fetch(key)
|
|
348
348
|
return value if value.is_a?(Hash)
|
|
349
349
|
|
|
350
|
-
raise Phronomy::
|
|
350
|
+
raise Phronomy::Storage::SerializationError,
|
|
351
351
|
"#{label} #{key} must be a Hash"
|
|
352
352
|
end
|
|
353
353
|
end
|
|
@@ -7,8 +7,8 @@ module Phronomy
|
|
|
7
7
|
LLMInputPatch = Data.define(:model_config_patch, :segment_candidates) do
|
|
8
8
|
def initialize(model_config_patch: nil, segment_candidates: nil)
|
|
9
9
|
super(
|
|
10
|
-
model_config_patch: model_config_patch && Immutable.copy(model_config_patch),
|
|
11
|
-
segment_candidates: segment_candidates && Immutable.copy(Array(segment_candidates))
|
|
10
|
+
model_config_patch: model_config_patch && Phronomy::Values::Immutable.copy(model_config_patch),
|
|
11
|
+
segment_candidates: segment_candidates && Phronomy::Values::Immutable.copy(Array(segment_candidates))
|
|
12
12
|
)
|
|
13
13
|
freeze
|
|
14
14
|
end
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../engine/concurrency/worker_input_restricted"
|
|
4
|
+
|
|
3
5
|
require "securerandom"
|
|
4
6
|
require "time"
|
|
5
7
|
|
|
6
8
|
module Phronomy
|
|
7
9
|
module Agent
|
|
8
10
|
class AgentExecution
|
|
11
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
12
|
+
|
|
9
13
|
ACTIVE_STATUSES = %i[preparing active suspended].freeze
|
|
10
14
|
TERMINAL_STATUSES = %i[completed handed_off failed cancelled rejected blocked].freeze
|
|
11
15
|
TRANSITIONS = {
|
|
@@ -55,13 +59,13 @@ module Phronomy
|
|
|
55
59
|
ATTRIBUTES.each do |name|
|
|
56
60
|
value = attributes.fetch(name)
|
|
57
61
|
value = value.to_sym if %i[status phase].include?(name)
|
|
58
|
-
instance_variable_set("@#{name}", Immutable.copy(value))
|
|
62
|
+
instance_variable_set("@#{name}", Phronomy::Values::Immutable.copy(value))
|
|
59
63
|
end
|
|
60
64
|
raise ArgumentError, "unknown execution status: #{status.inspect}" unless TRANSITIONS.key?(status)
|
|
61
65
|
raise ArgumentError, "execution_revision must be non-negative" if execution_revision.negative?
|
|
62
|
-
Immutable.validate_canonical_json!(metadata, label: "Execution metadata")
|
|
66
|
+
Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "Execution metadata")
|
|
63
67
|
if approval_request
|
|
64
|
-
Immutable.validate_canonical_json!(approval_request, label: "Approval request")
|
|
68
|
+
Phronomy::Values::Immutable.validate_canonical_json!(approval_request, label: "Approval request")
|
|
65
69
|
end
|
|
66
70
|
freeze
|
|
67
71
|
end
|
|
@@ -86,7 +90,7 @@ module Phronomy
|
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
# Current semantic payload representation. Persistence format identity and
|
|
89
|
-
# compatibility validation are owned by Persistence::
|
|
93
|
+
# compatibility validation are owned by Agent::Persistence::Codec.
|
|
90
94
|
# @api public
|
|
91
95
|
def to_h
|
|
92
96
|
ATTRIBUTES.to_h do |name|
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../engine/concurrency/worker_input_restricted"
|
|
4
|
+
|
|
3
5
|
require "securerandom"
|
|
4
6
|
require "time"
|
|
5
7
|
|
|
6
8
|
module Phronomy
|
|
7
9
|
module Agent
|
|
8
10
|
class AgentInvocation
|
|
11
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
12
|
+
|
|
9
13
|
# execution_id is the logical Agent execution parent. Concrete Runtime
|
|
10
14
|
# routing identity belongs only to the owning FSMSession incarnation.
|
|
11
|
-
TOOL_EVENT_TYPES = %i[
|
|
12
|
-
tool_authorized
|
|
13
|
-
tool_approval_required
|
|
14
|
-
tool_completed
|
|
15
|
-
tool_failed
|
|
16
|
-
tool_rejected
|
|
17
|
-
tool_cancelled
|
|
18
|
-
].freeze
|
|
19
|
-
|
|
20
15
|
LLM_EVENT_TYPES = %i[llm_completed llm_failed].freeze
|
|
21
16
|
CALLBACK_FAILED_EVENTS = %i[application_callback_failed].freeze
|
|
22
17
|
SETUP_FAILED_EVENTS = %i[llm_setup_failed tool_setup_failed].freeze
|
|
@@ -147,11 +142,11 @@ module Phronomy
|
|
|
147
142
|
if durable_id.nil? &&
|
|
148
143
|
Phronomy::Runtime.instance.event_loop.current?
|
|
149
144
|
state =
|
|
150
|
-
Phronomy::Runtime.instance.event_loop.agent_execution_state(
|
|
145
|
+
Phronomy::Agent::ExecutionRegistry.for(Phronomy::Runtime.instance.event_loop).agent_execution_state(
|
|
151
146
|
execution_id
|
|
152
147
|
)
|
|
153
148
|
durable_id = state&.execution&.metadata&.fetch(
|
|
154
|
-
|
|
149
|
+
ExecutionMetadata::PENDING_LLM_ID_KEY,
|
|
155
150
|
nil
|
|
156
151
|
)
|
|
157
152
|
end
|
|
@@ -209,7 +204,7 @@ module Phronomy
|
|
|
209
204
|
return true
|
|
210
205
|
end
|
|
211
206
|
|
|
212
|
-
return false unless
|
|
207
|
+
return false unless InvocationTransitions::TOOL_EVENTS.include?(event.type)
|
|
213
208
|
|
|
214
209
|
invocation = tool_invocation(event.payload&.fetch(:tool_invocation_id, nil))
|
|
215
210
|
return true unless invocation
|
|
@@ -540,7 +535,7 @@ module Phronomy
|
|
|
540
535
|
# listener receives the notification event separately.
|
|
541
536
|
@runtime_events << StreamEvent.new(
|
|
542
537
|
type: event.type,
|
|
543
|
-
payload: Immutable.copy(event.payload)
|
|
538
|
+
payload: Phronomy::Values::Immutable.copy(event.payload)
|
|
544
539
|
)
|
|
545
540
|
listener = @callback_failure ? nil : @event_listener
|
|
546
541
|
return unless listener
|
|
@@ -3,33 +3,6 @@
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module Agent
|
|
5
5
|
class AgentInvocationSessionBuilder
|
|
6
|
-
AUTO_STATE_SET = {
|
|
7
|
-
idle: true,
|
|
8
|
-
filtering_input: true,
|
|
9
|
-
building_context: true,
|
|
10
|
-
starting_tools: true,
|
|
11
|
-
evaluating_tools: true,
|
|
12
|
-
recording_tool_results: true,
|
|
13
|
-
output_filtering: true
|
|
14
|
-
}.freeze
|
|
15
|
-
|
|
16
|
-
DECLARED_STATES = %i[
|
|
17
|
-
idle filtering_input building_context calling_llm starting_tools
|
|
18
|
-
evaluating_tools waiting_for_tools dispatching_tools
|
|
19
|
-
recording_tool_results suspended output_filtering handed_off completed blocked failed
|
|
20
|
-
].freeze
|
|
21
|
-
|
|
22
|
-
WAIT_STATES = %i[suspended].freeze
|
|
23
|
-
|
|
24
|
-
TOOL_EVENTS = %i[
|
|
25
|
-
tool_authorized
|
|
26
|
-
tool_approval_required
|
|
27
|
-
tool_completed
|
|
28
|
-
tool_failed
|
|
29
|
-
tool_rejected
|
|
30
|
-
tool_cancelled
|
|
31
|
-
].freeze
|
|
32
|
-
|
|
33
6
|
def self.build(
|
|
34
7
|
agent:,
|
|
35
8
|
input:,
|
|
@@ -93,13 +66,13 @@ module Phronomy
|
|
|
93
66
|
Phronomy::FSMSession.new(
|
|
94
67
|
context: agent_invocation,
|
|
95
68
|
event_sink: event_sink,
|
|
96
|
-
entry_point:
|
|
69
|
+
entry_point: InvocationTransitions::ENTRY_POINT,
|
|
97
70
|
phase_machine_class: phase_machine,
|
|
98
71
|
entry_actions: {},
|
|
99
|
-
auto_state_set: AUTO_STATE_SET,
|
|
100
|
-
declared_states: DECLARED_STATES,
|
|
101
|
-
wait_state_names: WAIT_STATES,
|
|
102
|
-
external_events:
|
|
72
|
+
auto_state_set: InvocationTransitions::AUTO_STATE_SET,
|
|
73
|
+
declared_states: InvocationTransitions::DECLARED_STATES,
|
|
74
|
+
wait_state_names: InvocationTransitions::WAIT_STATES,
|
|
75
|
+
external_events: InvocationTransitions::EXTERNAL_EVENTS,
|
|
103
76
|
recursion_limit: 12 + (iterations * 8),
|
|
104
77
|
event_loop: runtime.event_loop,
|
|
105
78
|
resume_event: resume_event,
|
|
@@ -108,49 +81,6 @@ module Phronomy
|
|
|
108
81
|
end
|
|
109
82
|
private_class_method :build_session
|
|
110
83
|
|
|
111
|
-
def self.external_events
|
|
112
|
-
tool_transitions = TOOL_EVENTS.to_h do |event_name|
|
|
113
|
-
[event_name, [{from: :waiting_for_tools, to: :evaluating_tools, guard: nil}]]
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
tool_transitions.merge(
|
|
117
|
-
llm_completed: [
|
|
118
|
-
{from: :calling_llm, to: :failed, guard: ->(ctx) { ctx.callback_failed? }},
|
|
119
|
-
{from: :calling_llm, to: :failed, guard: ->(ctx) { ctx.handoff_failed? }},
|
|
120
|
-
{from: :calling_llm, to: :handed_off, guard: ->(ctx) { ctx.handoff_requested? }},
|
|
121
|
-
{from: :calling_llm, to: :starting_tools, guard: ->(ctx) { ctx.tool_call_pending? }},
|
|
122
|
-
{from: :calling_llm, to: :output_filtering, guard: nil}
|
|
123
|
-
],
|
|
124
|
-
llm_failed: [
|
|
125
|
-
{from: :calling_llm, to: :failed, guard: nil}
|
|
126
|
-
],
|
|
127
|
-
llm_setup_failed: [
|
|
128
|
-
{from: :calling_llm, to: :failed, guard: nil}
|
|
129
|
-
],
|
|
130
|
-
tool_setup_failed: [
|
|
131
|
-
{from: :dispatching_tools, to: :failed, guard: nil}
|
|
132
|
-
],
|
|
133
|
-
tool_dispatch_prepared: [
|
|
134
|
-
{from: :dispatching_tools, to: :evaluating_tools, guard: nil}
|
|
135
|
-
],
|
|
136
|
-
resume: [
|
|
137
|
-
{from: :suspended, to: :waiting_for_tools, guard: nil}
|
|
138
|
-
],
|
|
139
|
-
application_callback_failed: [
|
|
140
|
-
{from: :filtering_input, to: :failed, guard: nil},
|
|
141
|
-
{from: :building_context, to: :failed, guard: nil},
|
|
142
|
-
{from: :calling_llm, to: :failed, guard: nil},
|
|
143
|
-
{from: :starting_tools, to: :failed, guard: nil},
|
|
144
|
-
{from: :evaluating_tools, to: :failed, guard: nil},
|
|
145
|
-
{from: :waiting_for_tools, to: :failed, guard: nil},
|
|
146
|
-
{from: :dispatching_tools, to: :failed, guard: nil},
|
|
147
|
-
{from: :recording_tool_results, to: :failed, guard: nil},
|
|
148
|
-
{from: :output_filtering, to: :failed, guard: nil}
|
|
149
|
-
]
|
|
150
|
-
)
|
|
151
|
-
end
|
|
152
|
-
private_class_method :external_events
|
|
153
|
-
|
|
154
84
|
def self.build_entry_actions(agent, runtime, mode:, event_sink:)
|
|
155
85
|
calling_action = if mode.to_sym == :stream
|
|
156
86
|
method(:calling_llm_stream_action).curry.call(agent, runtime, event_sink)
|
|
@@ -159,8 +89,8 @@ module Phronomy
|
|
|
159
89
|
end
|
|
160
90
|
|
|
161
91
|
{
|
|
162
|
-
filtering_input: [method(:
|
|
163
|
-
building_context: [method(:
|
|
92
|
+
filtering_input: [method(:apply_prepared_input_action).curry.call(agent)],
|
|
93
|
+
building_context: [method(:build_runtime_chat_action).curry.call(agent)],
|
|
164
94
|
calling_llm: [calling_action],
|
|
165
95
|
starting_tools: [method(:starting_tools_action).curry.call(runtime, event_sink)],
|
|
166
96
|
dispatching_tools: [method(:dispatching_tools_action).curry.call(runtime, event_sink)],
|
|
@@ -172,13 +102,13 @@ module Phronomy
|
|
|
172
102
|
end
|
|
173
103
|
private_class_method :build_entry_actions
|
|
174
104
|
|
|
175
|
-
def self.
|
|
105
|
+
def self.apply_prepared_input_action(_agent, invocation)
|
|
176
106
|
invocation.input = invocation.config.fetch(:phronomy_filtered_input)
|
|
177
107
|
invocation
|
|
178
108
|
end
|
|
179
|
-
private_class_method :
|
|
109
|
+
private_class_method :apply_prepared_input_action
|
|
180
110
|
|
|
181
|
-
def self.
|
|
111
|
+
def self.build_runtime_chat_action(agent, invocation)
|
|
182
112
|
projection = invocation.config.fetch(:phronomy_runtime_projection)
|
|
183
113
|
invocation.chat = agent.send(:build_chat, model_config: projection.model_config)
|
|
184
114
|
agent.send(
|
|
@@ -189,22 +119,16 @@ module Phronomy
|
|
|
189
119
|
)
|
|
190
120
|
invocation
|
|
191
121
|
end
|
|
192
|
-
private_class_method :
|
|
122
|
+
private_class_method :build_runtime_chat_action
|
|
193
123
|
|
|
194
124
|
def self.install_tool_interceptors(chat, llm_call_id:)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if chat.respond_to?(:on_tool_call_batch)
|
|
201
|
-
chat.on_tool_call_batch do |tool_calls|
|
|
202
|
-
raise build_tool_interception(chat, tool_calls, llm_call_id)
|
|
203
|
-
end
|
|
204
|
-
end
|
|
125
|
+
# RubyLLM 2 records the complete assistant message before this callback,
|
|
126
|
+
# and checks its own approval gate afterwards. Capture here so Phronomy
|
|
127
|
+
# owns authorization and dispatch for the entire Tool batch.
|
|
128
|
+
chat.after_message do |message|
|
|
129
|
+
next unless message.tool_call?
|
|
205
130
|
|
|
206
|
-
|
|
207
|
-
raise build_tool_interception(chat, [tool_call], llm_call_id)
|
|
131
|
+
raise build_tool_interception(chat, [], llm_call_id)
|
|
208
132
|
end
|
|
209
133
|
end
|
|
210
134
|
private_class_method :install_tool_interceptors
|
|
@@ -347,7 +271,7 @@ module Phronomy
|
|
|
347
271
|
chat, message, config: config
|
|
348
272
|
)
|
|
349
273
|
end
|
|
350
|
-
runtime.event_loop.supervise_agent_operation(
|
|
274
|
+
Phronomy::Agent::ExecutionRegistry.for(runtime.event_loop).supervise_agent_operation(
|
|
351
275
|
invocation.execution_id,
|
|
352
276
|
operation
|
|
353
277
|
)
|
|
@@ -456,7 +380,7 @@ module Phronomy
|
|
|
456
380
|
children = invocation.pending_tool_calls.map do |tool_call|
|
|
457
381
|
tool = invocation.chat.tools[tool_call.name.to_sym]
|
|
458
382
|
tool_invocation_id =
|
|
459
|
-
|
|
383
|
+
ToolInvocation.semantic_id(
|
|
460
384
|
execution_id: invocation.execution_id,
|
|
461
385
|
llm_call_id: invocation.tool_batch_llm_call_id,
|
|
462
386
|
tool_call_id: (
|
|
@@ -542,7 +466,7 @@ module Phronomy
|
|
|
542
466
|
end
|
|
543
467
|
|
|
544
468
|
def self.register_child_session(runtime, child, session, parent_event_sink)
|
|
545
|
-
completion = Phronomy::
|
|
469
|
+
completion = Phronomy::TaskResult.deferred(name: "tool-session:#{child.id}")
|
|
546
470
|
completion.on_complete do |_result, error|
|
|
547
471
|
next unless error
|
|
548
472
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../engine/concurrency/worker_input_restricted"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module Agent
|
|
7
|
+
# Persists one approval decision with its captured recovery facts.
|
|
8
|
+
# Admission, live state and session resumption stay with the execution owner.
|
|
9
|
+
# @api private
|
|
10
|
+
class ApprovalResumeCommit
|
|
11
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
12
|
+
|
|
13
|
+
Command = Data.define(
|
|
14
|
+
:execution_id, :expected_execution_revision,
|
|
15
|
+
:root, :execution, :approval_request_id, :approved, :tool_batch_snapshot
|
|
16
|
+
)
|
|
17
|
+
Result = Data.define(:execution, :root)
|
|
18
|
+
|
|
19
|
+
# @api private
|
|
20
|
+
def initialize(agent_id:, persistence:)
|
|
21
|
+
@agent_id = agent_id
|
|
22
|
+
@persistence = persistence
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
def commit(operation)
|
|
27
|
+
request = validated_approval_request(operation)
|
|
28
|
+
execution = stage_recovery_snapshot(operation)
|
|
29
|
+
persist_decision(execution, operation.root, request, operation.approved)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def validated_approval_request(operation)
|
|
35
|
+
request = operation.execution.approval_request || {}
|
|
36
|
+
request_id = request["id"] || request[:id]
|
|
37
|
+
unless request_id.to_s == operation.approval_request_id
|
|
38
|
+
raise ArgumentError,
|
|
39
|
+
"approval request does not match execution #{operation.execution.execution_id}"
|
|
40
|
+
end
|
|
41
|
+
request
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stage_recovery_snapshot(operation)
|
|
45
|
+
return operation.execution unless operation.tool_batch_snapshot
|
|
46
|
+
|
|
47
|
+
ExecutionMetadata.with_values(
|
|
48
|
+
operation.execution,
|
|
49
|
+
ExecutionMetadata::TOOL_BATCH_METADATA_KEY => operation.tool_batch_snapshot
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def persist_decision(execution, root, request, approved)
|
|
54
|
+
updated = next_root = nil
|
|
55
|
+
@persistence.transaction do |tx|
|
|
56
|
+
decision = record_decision(tx, execution, root, request, approved)
|
|
57
|
+
updated = save_resumed_execution(tx, execution, request, approved, decision)
|
|
58
|
+
next_root = save_resumed_root(tx, root)
|
|
59
|
+
end
|
|
60
|
+
Result.new(execution: updated, root: next_root)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def record_decision(tx, execution, root, request, approved)
|
|
64
|
+
decision_ref = tx.contents.put_json(
|
|
65
|
+
"approval_request_id" => (request["id"] || request[:id]).to_s,
|
|
66
|
+
"approved" => approved
|
|
67
|
+
)
|
|
68
|
+
JournalRecord.new(
|
|
69
|
+
agent_id: @agent_id,
|
|
70
|
+
execution_id: execution.execution_id,
|
|
71
|
+
kind: :approval_decided,
|
|
72
|
+
channel: :approval,
|
|
73
|
+
content_ref: decision_ref,
|
|
74
|
+
context_generation: root.transcript_generation,
|
|
75
|
+
context_candidate: false
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def save_resumed_execution(tx, current, request, approved, decision)
|
|
80
|
+
updated = current.with(
|
|
81
|
+
status: :active,
|
|
82
|
+
phase: :resuming,
|
|
83
|
+
working_records: current.working_records + [decision],
|
|
84
|
+
approval_request: request.merge("approved" => approved)
|
|
85
|
+
)
|
|
86
|
+
tx.executions.save(
|
|
87
|
+
current.execution_id,
|
|
88
|
+
expected_revision: current.execution_revision,
|
|
89
|
+
execution: updated
|
|
90
|
+
)
|
|
91
|
+
updated
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def save_resumed_root(tx, current)
|
|
95
|
+
updated = current.with(
|
|
96
|
+
agent_revision: current.agent_revision + 1,
|
|
97
|
+
lifecycle_status: :active
|
|
98
|
+
)
|
|
99
|
+
tx.agents.save(
|
|
100
|
+
current.agent_id,
|
|
101
|
+
expected_revision: current.agent_revision,
|
|
102
|
+
root: updated
|
|
103
|
+
)
|
|
104
|
+
updated
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|