phronomy 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.mutant.yml +9 -9
- data/CHANGELOG.md +92 -0
- data/CONTRIBUTING.md +8 -8
- data/README.md +13 -3
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -47
- data/benchmark/bench_context_assembler.rb +17 -15
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +1 -1
- data/docs/architecture/agent-chat-and-state-ownership.md +153 -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 +13 -0
- data/docs/architecture/backend-groups.md +152 -0
- data/docs/architecture/before-llm-input.md +9 -3
- data/docs/architecture/context-management.md +31 -1
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-abstraction-review.md +88 -0
- data/docs/architecture/execution-boundaries.md +28 -0
- data/docs/architecture/execution-metadata-and-values.md +86 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +11 -4
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +45 -4
- data/docs/architecture/r8-unit1.md +104 -0
- data/docs/architecture/r8-unit10.md +72 -0
- data/docs/architecture/r8-unit11.md +71 -0
- data/docs/architecture/r8-unit12.md +67 -0
- data/docs/architecture/r8-unit13.md +61 -0
- data/docs/architecture/r8-unit14.md +72 -0
- data/docs/architecture/r8-unit15.md +65 -0
- data/docs/architecture/r8-unit16.md +54 -0
- data/docs/architecture/r8-unit17.md +73 -0
- data/docs/architecture/r8-unit18.md +92 -0
- data/docs/architecture/r8-unit19.md +61 -0
- data/docs/architecture/r8-unit2.md +55 -0
- data/docs/architecture/r8-unit20.md +51 -0
- data/docs/architecture/r8-unit21.md +79 -0
- data/docs/architecture/r8-unit22.md +74 -0
- data/docs/architecture/r8-unit23.md +81 -0
- data/docs/architecture/r8-unit3.md +130 -0
- data/docs/architecture/r8-unit4.md +79 -0
- data/docs/architecture/r8-unit5.md +115 -0
- data/docs/architecture/r8-unit6.md +71 -0
- data/docs/architecture/r8-unit7.md +70 -0
- data/docs/architecture/r8-unit8.md +78 -0
- data/docs/architecture/r8-unit9.md +73 -0
- data/docs/architecture/refactoring-closure.md +343 -0
- data/docs/architecture/remaining-refactoring-plan.md +390 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/storage-execution-boundaries.md +54 -0
- data/docs/architecture/tool-schema-recording-gap.md +56 -0
- data/docs/architecture/tracing.md +2 -2
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +222 -0
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
- 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/027-llm-adapter-provider-boundary.md +2 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
- data/docs/decisions/032-storage-backend-composition.md +85 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -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 +145 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
- data/docs/decisions/040-configuration-default-composition.md +105 -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 +104 -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 +153 -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 +83 -0
- data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
- data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
- data/docs/decisions/061-persistence-failure-contracts.md +89 -0
- data/docs/decisions/062-content-store-service.md +52 -0
- data/docs/decisions/063-execution-contracts-and-services.md +88 -0
- data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
- data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
- data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
- data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
- data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
- data/docs/decisions/README.md +52 -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/execution-boundaries-preview.md +45 -0
- data/docs/features.md +16 -5
- data/docs/getting-started.md +11 -8
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/0.22.md +2 -2
- data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
- data/docs/migrations/handoff-runner-multi-agent.md +37 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/persistence-failure-contracts.md +67 -0
- data/docs/migrations/r8-unit10.md +36 -0
- data/docs/migrations/r8-unit11.md +33 -0
- data/docs/migrations/r8-unit12.md +31 -0
- data/docs/migrations/r8-unit13.md +36 -0
- data/docs/migrations/r8-unit14.md +31 -0
- data/docs/migrations/r8-unit15.md +31 -0
- data/docs/migrations/r8-unit16.md +26 -0
- data/docs/migrations/r8-unit17.md +28 -0
- data/docs/migrations/r8-unit18.md +29 -0
- data/docs/migrations/r8-unit19.md +28 -0
- data/docs/migrations/r8-unit20.md +24 -0
- data/docs/migrations/r8-unit21.md +28 -0
- data/docs/migrations/r8-unit22.md +29 -0
- data/docs/migrations/r8-unit23.md +20 -0
- data/docs/migrations/r8-unit4.md +67 -0
- data/docs/migrations/r8-unit5.md +75 -0
- data/docs/migrations/r8-unit6.md +109 -0
- data/docs/migrations/r8-unit7.md +31 -0
- data/docs/migrations/r8-unit8.md +33 -0
- data/docs/migrations/r8-unit9.md +28 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-async-client.md +103 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/migrations/vector-async-clients.md +110 -0
- data/docs/persistence-backends.md +190 -215
- data/docs/runtime-and-concurrency.md +142 -32
- data/lib/phronomy/agent/admission.rb +78 -0
- data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/agent/async_event_api.rb +38 -33
- data/lib/phronomy/agent/base.rb +106 -294
- data/lib/phronomy/agent/before_llm_input.rb +64 -0
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
- data/lib/phronomy/agent/context_importer.rb +2 -2
- data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
- data/lib/phronomy/agent/control_request.rb +21 -0
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
- data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/execution_change.rb +161 -0
- data/lib/phronomy/agent/execution_environment.rb +36 -0
- data/lib/phronomy/agent/execution_extension_state.rb +21 -0
- data/lib/phronomy/agent/execution_observation.rb +35 -0
- data/lib/phronomy/agent/filterable.rb +182 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
- data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
- data/lib/phronomy/agent/handoff_error.rb +7 -0
- 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/knowledge_item.rb +13 -0
- 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 -15
- data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
- data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
- data/lib/phronomy/agent/persistence/codec.rb +338 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
- data/lib/phronomy/agent/persistence/records.rb +33 -0
- data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
- data/lib/phronomy/agent/persistence/watermark.rb +29 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
- data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
- data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
- data/lib/phronomy/agent/reserved_execution.rb +16 -0
- data/lib/phronomy/agent/retention.rb +17 -0
- data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
- data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
- data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
- data/lib/phronomy/agent/saved_context_reader.rb +55 -0
- data/lib/phronomy/agent/settings.rb +47 -0
- data/lib/phronomy/agent/state_writer.rb +166 -0
- data/lib/phronomy/agent/store.rb +253 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
- 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 +30 -0
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
- data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
- data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
- data/lib/phronomy/common/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/runtime_settings.rb +49 -0
- data/lib/phronomy/content_store/storage_schema.rb +12 -0
- data/lib/phronomy/content_store/stored_contents.rb +44 -0
- data/lib/phronomy/context/assembly.rb +202 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
- data/lib/phronomy/context/candidate.rb +51 -0
- data/lib/phronomy/context/constraint.rb +47 -0
- data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
- data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
- data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
- data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
- data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
- data/lib/phronomy/context/default_policy.rb +140 -0
- data/lib/phronomy/context/final_budget_validator.rb +33 -0
- data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
- data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
- data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
- data/lib/phronomy/context/manifest_error.rb +10 -0
- data/lib/phronomy/context/prompt_template.rb +98 -0
- data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
- data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
- data/lib/phronomy/embeddings/async/async_client.rb +44 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
- data/lib/phronomy/embeddings/base.rb +43 -0
- data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
- data/lib/phronomy/embeddings/transport_error.rb +11 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
- data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
- data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
- data/lib/phronomy/engine/event_loop.rb +164 -630
- data/lib/phronomy/engine/execution_binding.rb +23 -0
- data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
- data/lib/phronomy/engine/fsm_protocol.rb +9 -0
- data/lib/phronomy/engine/fsm_session.rb +37 -30
- data/lib/phronomy/engine/metrics.rb +28 -0
- data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
- data/lib/phronomy/engine/runtime.rb +104 -95
- data/lib/phronomy/engine/terminal_decision.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +41 -0
- data/lib/phronomy/execution/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
- data/lib/phronomy/execution/cancellation_error.rb +7 -0
- data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
- data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
- data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
- data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
- data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
- data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
- data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
- data/lib/phronomy/execution/execution_receiver.rb +78 -0
- data/lib/phronomy/execution/execution_scope_state.rb +35 -0
- data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
- data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
- data/lib/phronomy/execution/invocation_controls.rb +73 -0
- data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
- data/lib/phronomy/execution/runnable.rb +24 -0
- data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
- data/lib/phronomy/execution/timeout_error.rb +7 -0
- data/lib/phronomy/execution/wait_policy.rb +24 -0
- data/lib/phronomy/filter/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -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/async/async_client.rb +47 -0
- data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
- data/lib/phronomy/llm_adapter/base.rb +49 -68
- data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
- data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
- data/lib/phronomy/llm_adapter/message.rb +62 -0
- data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
- data/lib/phronomy/llm_adapter/request.rb +79 -0
- data/lib/phronomy/llm_adapter/response.rb +36 -0
- data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
- data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
- data/lib/phronomy/llm_adapter.rb +3 -16
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
- data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
- data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
- data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
- data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
- data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
- data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
- data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
- data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
- data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
- data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
- data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
- data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
- data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/store.rb +156 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/parse_error.rb +7 -0
- data/lib/phronomy/persistence/content_repository.rb +26 -0
- data/lib/phronomy/persistence/errors.rb +16 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
- data/lib/phronomy/persistence/persistence.rb +23 -0
- data/lib/phronomy/persistence/save_outcome.rb +24 -0
- data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
- data/lib/phronomy/persistence/storage_boundary.rb +32 -0
- data/lib/phronomy/persistence/transaction.rb +86 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
- data/lib/phronomy/persistence_composition/stores.rb +34 -0
- data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
- data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
- data/lib/phronomy/runtime_composition/configuration.rb +146 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
- data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
- data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
- data/lib/phronomy/storage/async/async_client.rb +60 -0
- data/lib/phronomy/storage/backend.rb +107 -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/metrics.rb +1 -1
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/authorization.rb +77 -0
- data/lib/phronomy/tool/base.rb +549 -9
- data/lib/phronomy/tool/call_request.rb +40 -0
- data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
- data/lib/phronomy/tool/operation.rb +88 -0
- data/lib/phronomy/tool/schema.rb +214 -0
- data/lib/phronomy/tool/settings.rb +31 -0
- data/lib/phronomy/tool/tool_error.rb +7 -0
- data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
- data/lib/phronomy/tools/agent.rb +22 -90
- data/lib/phronomy/tools/mcp.rb +21 -14
- data/lib/phronomy/tools/vector_search.rb +4 -4
- data/lib/phronomy/tracing/automatic.rb +11 -6
- data/lib/phronomy/tracing/base.rb +1 -1
- data/lib/phronomy/tracing/observation.rb +37 -0
- data/lib/phronomy/tracing/settings.rb +36 -0
- data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
- data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
- data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
- data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
- data/lib/phronomy/vector_store/base.rb +118 -49
- data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
- data/lib/phronomy/vector_store.rb +2 -17
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/completion.rb +11 -0
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
- data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
- data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
- data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy.rb +97 -98
- data/scripts/api_snapshot.rb +36 -16
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/_private/phronomy/agent/settings.rbs +19 -0
- data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
- data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
- data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
- data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
- data/sig/_private/phronomy/tool/settings.rbs +13 -0
- data/sig/_private/phronomy/tracing/settings.rbs +15 -0
- data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
- data/sig/phronomy/agent/admission.rbs +19 -0
- data/sig/phronomy/agent/execution_contract.rbs +132 -0
- data/sig/phronomy/agent/execution_environment.rbs +20 -0
- data/sig/phronomy/agent/store.rbs +73 -0
- data/sig/phronomy/agent.rbs +14 -139
- data/sig/phronomy/common.rbs +7 -0
- data/sig/phronomy/content_store/contracts.rbs +13 -0
- data/sig/phronomy/context.rbs +152 -0
- data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
- data/sig/phronomy/embeddings/base.rbs +17 -0
- data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
- data/sig/phronomy/engine/control_bindings.rbs +11 -0
- data/sig/phronomy/engine/control_protocols.rbs +23 -0
- data/sig/phronomy/engine/execution_binding.rbs +6 -0
- data/sig/phronomy/engine/execution_receiver.rbs +15 -0
- data/sig/phronomy/engine/observability.rbs +13 -0
- data/sig/phronomy/execution/async_operation.rbs +7 -0
- data/sig/phronomy/execution/blocking.rbs +5 -0
- data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
- data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
- data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
- data/sig/phronomy/execution/control_protocols.rbs +18 -0
- data/sig/phronomy/execution/errors.rbs +7 -0
- data/sig/phronomy/execution/execution.rbs +41 -0
- data/sig/phronomy/execution/execution_receiver.rbs +40 -0
- data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
- data/sig/phronomy/execution/invocation_context.rbs +18 -0
- data/sig/phronomy/execution/invocation_controls.rbs +13 -0
- data/sig/phronomy/execution/task_result.rbs +29 -0
- data/sig/phronomy/extensions.rbs +0 -21
- data/sig/phronomy/handoff.rbs +12 -4
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
- data/sig/phronomy/llm_adapter/base.rbs +14 -0
- data/sig/phronomy/llm_adapter/usage.rbs +14 -0
- data/sig/phronomy/llm_adapter/values.rbs +43 -0
- data/sig/phronomy/llm_context_window.rbs +10 -0
- data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
- data/sig/phronomy/multi_agent/store.rbs +54 -0
- data/sig/phronomy/multi_agent.rbs +24 -3
- data/sig/phronomy/persistence/errors.rbs +18 -0
- data/sig/phronomy/persistence/migration.rbs +13 -0
- data/sig/phronomy/persistence/transaction.rbs +19 -0
- data/sig/phronomy/persistence.rbs +3 -185
- data/sig/phronomy/persistence_composition.rbs +19 -0
- data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
- data/sig/phronomy/storage/async/async_client.rbs +8 -0
- data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
- data/sig/phronomy/storage/contracts.rbs +160 -0
- data/sig/phronomy/tool.rbs +58 -5
- data/sig/phronomy/tracing/observation.rbs +7 -0
- data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
- data/sig/phronomy/vector_store/base.rbs +23 -0
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
- data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
- data/sig/phronomy/workflow/store.rbs +8 -0
- data/sig/phronomy/workflow.rbs +1 -1
- data/sig/phronomy.rbs +0 -49
- metadata +462 -130
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
- data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
- data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
- data/lib/phronomy/agent/concerns/filterable.rb +0 -184
- data/lib/phronomy/agent/context/capability/base.rb +0 -507
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
- data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
- data/lib/phronomy/agent/context_policies/default.rb +0 -142
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/handoff_request.rb +0 -45
- data/lib/phronomy/agent/handoff_runner.rb +0 -178
- data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
- data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/selection/candidate.rb +0 -53
- data/lib/phronomy/agent/selection/constraint.rb +0 -49
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
- data/lib/phronomy/configuration.rb +0 -59
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
- data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
- data/lib/phronomy/metrics.rb +0 -26
- 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/recovery.rb +0 -186
- data/lib/phronomy/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/runnable.rb +0 -54
- data/lib/phronomy/token_usage.rb +0 -85
- data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
- data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
- data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
- data/lib/phronomy/workflow_recovery.rb +0 -123
- data/sig/phronomy/llm_adapter.rbs +0 -11
- data/sig/phronomy/runtime.rbs +0 -76
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Tool
|
|
5
|
+
# Tool operation protocol: execution modes, custom async dispatch, and
|
|
6
|
+
# completion-handle validation. Agent approval/progress are separate owners.
|
|
7
|
+
# @api public
|
|
8
|
+
module Operation
|
|
9
|
+
# Starts one Tool operation. The submitter implements Execution submission
|
|
10
|
+
# for the default offloaded path; custom call_async keeps its public protocol
|
|
11
|
+
# and owns its execution mechanism. Neither receives a concrete Runtime.
|
|
12
|
+
# @return [#on_complete] the original completion handle
|
|
13
|
+
# @api public
|
|
14
|
+
def self.call_async(tool:, args:, cancellation_token: nil, config: {},
|
|
15
|
+
submitter: Phronomy::Execution, on_full: :raise)
|
|
16
|
+
unless %i[cooperative offloaded].include?(tool.class.execution_mode)
|
|
17
|
+
raise Phronomy::ConfigurationError,
|
|
18
|
+
"unknown Tool execution_mode: #{tool.class.execution_mode.inspect}"
|
|
19
|
+
end
|
|
20
|
+
operation = if custom_async?(tool.method(:call_async))
|
|
21
|
+
tool.call_async(args, cancellation_token: cancellation_token, config: config)
|
|
22
|
+
else
|
|
23
|
+
ToolExecutor.call_async(tool: tool, args: args,
|
|
24
|
+
cancellation_token: cancellation_token, config: config,
|
|
25
|
+
submitter: submitter, on_full: on_full)
|
|
26
|
+
end
|
|
27
|
+
unless operation.respond_to?(:on_complete)
|
|
28
|
+
raise Phronomy::ToolError,
|
|
29
|
+
"Tool #{tool.class.name}#call_async must return a completion handle"
|
|
30
|
+
end
|
|
31
|
+
operation
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Decorates synchronous Tool results and custom async completions. A custom
|
|
35
|
+
# async operation is mapped through Execution's physical-completion-aware
|
|
36
|
+
# operation contract. The caller owns transformation meaning, ordering and
|
|
37
|
+
# multiplicity: repeated decorations remain distinct stages. Base's internal
|
|
38
|
+
# async-to-sync delegation skips only the synchronous counterpart of these
|
|
39
|
+
# async stages. Explicit application calls retain their own transformations.
|
|
40
|
+
# @return [Class<Phronomy::Tool::Base>] a derived Tool class
|
|
41
|
+
# @api public
|
|
42
|
+
def self.with_result_transform(tool_class, &transform)
|
|
43
|
+
raise ArgumentError, "a result transformation is required" unless transform
|
|
44
|
+
effective_name = tool_class.new.name
|
|
45
|
+
custom_async_call = custom_async?(tool_class.instance_method(:call_async))
|
|
46
|
+
decorated = Class.new(tool_class) do
|
|
47
|
+
tool_name effective_name
|
|
48
|
+
define_method(:call) do |args, **kwargs|
|
|
49
|
+
result = super(args, **kwargs)
|
|
50
|
+
transform.call(result, name, args)
|
|
51
|
+
end
|
|
52
|
+
if custom_async_call
|
|
53
|
+
define_method(:call_async) do |args, **kwargs|
|
|
54
|
+
source = super(args, **kwargs)
|
|
55
|
+
Phronomy::AsyncOperation.map(source, name: "tool-filter-#{name}") do |value|
|
|
56
|
+
transform.call(value, name, args)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
if custom_async_call
|
|
62
|
+
decorated.instance_variable_set(:@phronomy_async_transform_call,
|
|
63
|
+
decorated.instance_method(:call))
|
|
64
|
+
end
|
|
65
|
+
decorated
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Base's async bridge uses this bound callable on the original receiver,
|
|
69
|
+
# including after offload. Skip only framework-generated call wrappers whose
|
|
70
|
+
# async counterpart applies the same stage on completion. Stop at application
|
|
71
|
+
# methods and sync-only stages; do not infer application intent or use ambient
|
|
72
|
+
# flags, mutable receiver state, result identity, or filter deduplication.
|
|
73
|
+
# @api private
|
|
74
|
+
def self.synchronous_delegate(tool)
|
|
75
|
+
callable = tool.method(:call)
|
|
76
|
+
while callable.owner.instance_variable_get(:@phronomy_async_transform_call) == callable.unbind
|
|
77
|
+
callable = callable.super_method
|
|
78
|
+
end
|
|
79
|
+
callable
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.custom_async?(method)
|
|
83
|
+
method.owner != Base
|
|
84
|
+
end
|
|
85
|
+
private_class_method :custom_async?
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json_schemer"
|
|
4
|
+
require "schematist"
|
|
5
|
+
require_relative "../common/values/immutable"
|
|
6
|
+
|
|
7
|
+
module Phronomy
|
|
8
|
+
module Tool
|
|
9
|
+
# One schema defines both the advertised arguments and runtime validation.
|
|
10
|
+
# Schema evaluation is local: remote references and custom vocabularies are
|
|
11
|
+
# not supported. Provider request rendering does not belong to this class.
|
|
12
|
+
# @api public
|
|
13
|
+
class Schema
|
|
14
|
+
KEYWORDS = %w[
|
|
15
|
+
$schema $ref $defs $comment definitions
|
|
16
|
+
type enum const title description default examples deprecated readOnly writeOnly
|
|
17
|
+
properties patternProperties additionalProperties required propertyNames
|
|
18
|
+
minProperties maxProperties dependentRequired dependentSchemas
|
|
19
|
+
items prefixItems contains minContains maxContains
|
|
20
|
+
minItems maxItems uniqueItems minimum maximum exclusiveMinimum exclusiveMaximum multipleOf
|
|
21
|
+
minLength maxLength pattern format allOf anyOf oneOf not if then else
|
|
22
|
+
unevaluatedProperties unevaluatedItems strict
|
|
23
|
+
].freeze
|
|
24
|
+
FORMATS = %w[date-time date time duration email idn-email hostname idn-hostname ipv4 ipv6 uri uri-reference iri iri-reference uuid uri-template json-pointer relative-json-pointer regex].freeze
|
|
25
|
+
SCHEMA_MAPS = %w[properties patternProperties $defs definitions dependentSchemas].freeze
|
|
26
|
+
SCHEMA_LISTS = %w[allOf anyOf oneOf prefixItems].freeze
|
|
27
|
+
SCHEMA_VALUES = %w[additionalProperties propertyNames items contains not if then else unevaluatedProperties unevaluatedItems].freeze
|
|
28
|
+
|
|
29
|
+
# @api public
|
|
30
|
+
def initialize(schema = nil, &block)
|
|
31
|
+
source = if block
|
|
32
|
+
raise ArgumentError, "pass either a schema or a block" unless schema.nil?
|
|
33
|
+
Schematist::Schema.create(&block).new.to_json_schema
|
|
34
|
+
elsif schema.is_a?(Class) && schema.method_defined?(:to_json_schema)
|
|
35
|
+
schema.new.to_json_schema
|
|
36
|
+
elsif schema.respond_to?(:to_json_schema)
|
|
37
|
+
schema.to_json_schema
|
|
38
|
+
else
|
|
39
|
+
schema
|
|
40
|
+
end
|
|
41
|
+
source = source[:schema] || source["schema"] || source if source.is_a?(Hash)
|
|
42
|
+
@json_schema = self.class.json_value(source)
|
|
43
|
+
unless @json_schema.is_a?(Hash) && @json_schema["type"] == "object"
|
|
44
|
+
raise ArgumentError, "Tool schema must declare an object"
|
|
45
|
+
end
|
|
46
|
+
@references = []
|
|
47
|
+
validate_keywords!(@json_schema)
|
|
48
|
+
options = {ref_resolver: ->(uri) { raise ArgumentError, "external Tool schema reference: #{uri}" }}
|
|
49
|
+
unless JSONSchemer.valid_schema?(@json_schema, **options)
|
|
50
|
+
raise ArgumentError, "invalid Tool JSON Schema"
|
|
51
|
+
end
|
|
52
|
+
@json_schema = Phronomy::Values::Immutable.copy(@json_schema)
|
|
53
|
+
@validator = JSONSchemer.schema(@json_schema, **options)
|
|
54
|
+
@references.each { |reference| @validator.ref(reference) }
|
|
55
|
+
@references = nil
|
|
56
|
+
freeze
|
|
57
|
+
rescue JSONSchemer::InvalidRefPointer, JSONSchemer::InvalidRefResolution => error
|
|
58
|
+
raise ArgumentError, "invalid local Tool schema reference: #{error.message}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @api public
|
|
62
|
+
attr_reader :json_schema
|
|
63
|
+
|
|
64
|
+
# Return validated keyword arguments without modifying the caller's values.
|
|
65
|
+
# Coercion is limited to explicit property/item types; combinators and
|
|
66
|
+
# references are validated without guessing a branch or resolving a cast.
|
|
67
|
+
# @api public
|
|
68
|
+
def validate(arguments, coerce: false)
|
|
69
|
+
return [nil, "arguments must be an object (Hash)"] unless arguments.is_a?(Hash)
|
|
70
|
+
data = self.class.json_value(arguments)
|
|
71
|
+
data = self.class.json_value(coerce_value(data, @json_schema)) if coerce
|
|
72
|
+
error = @validator.validate(data).first
|
|
73
|
+
return [nil, error_message(error)] if error
|
|
74
|
+
[restore_keys(arguments, data).transform_keys(&:to_sym), nil]
|
|
75
|
+
rescue ArgumentError, TypeError => error
|
|
76
|
+
[nil, error.message]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# JSON values, with duplicate normalized keys rejected instead of lost.
|
|
80
|
+
# @api private
|
|
81
|
+
def self.json_value(value)
|
|
82
|
+
case value
|
|
83
|
+
when Hash
|
|
84
|
+
value.each_with_object({}) do |(key, child), result|
|
|
85
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
86
|
+
raise ArgumentError, "Tool object keys must be Strings or Symbols"
|
|
87
|
+
end
|
|
88
|
+
name = key.to_s
|
|
89
|
+
raise ArgumentError, "duplicate Tool object key: #{name}" if result.key?(name)
|
|
90
|
+
result[name] = json_value(child)
|
|
91
|
+
end
|
|
92
|
+
when Array then value.map { |child| json_value(child) }
|
|
93
|
+
when String, Integer, TrueClass, FalseClass, NilClass then value
|
|
94
|
+
when Float
|
|
95
|
+
raise ArgumentError, "Tool numbers must be finite" unless value.finite?
|
|
96
|
+
value
|
|
97
|
+
else
|
|
98
|
+
raise ArgumentError, "unsupported Tool value: #{value.class}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def error_message(error)
|
|
105
|
+
path = error.fetch("data_pointer", "").split("/").drop(1)
|
|
106
|
+
field = path.join(".")
|
|
107
|
+
type = error["type"]
|
|
108
|
+
type = "additionalProperties" if error["schema"] == false && error["schema_pointer"].end_with?("/additionalProperties")
|
|
109
|
+
case type
|
|
110
|
+
when "required"
|
|
111
|
+
key = Array(error.dig("details", "missing_keys")).first
|
|
112
|
+
path.empty? ? "required parameter '#{key}' is missing" : "nested required field '#{field}.#{key}' is missing"
|
|
113
|
+
when "additionalProperties"
|
|
114
|
+
(path.size > 1) ? "nested field '#{path[0...-1].join(".")}' contains undeclared key(s): #{path.last.inspect}" : "unknown parameter(s): #{path.last.inspect}"
|
|
115
|
+
when "enum"
|
|
116
|
+
"parameter '#{field}' must be one of: #{Array(error.dig("schema", "enum")).join(", ")} (got: #{error["data"].inspect})"
|
|
117
|
+
when "string", "integer", "number", "boolean", "object", "array", "null"
|
|
118
|
+
prefix = (path.size > 1) ? "nested field '#{field}'" : "parameter '#{field}'"
|
|
119
|
+
"#{prefix} expected type #{type} (got: #{error["data"].inspect})"
|
|
120
|
+
else
|
|
121
|
+
error.fetch("error", "invalid Tool arguments")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def validate_keywords!(schema)
|
|
126
|
+
return if schema == true || schema == false
|
|
127
|
+
raise ArgumentError, "invalid Tool schema node" unless schema.is_a?(Hash)
|
|
128
|
+
unknown = schema.keys - KEYWORDS
|
|
129
|
+
raise ArgumentError, "unsupported Tool schema keywords: #{unknown.join(", ")}" unless unknown.empty?
|
|
130
|
+
if schema.key?("$schema") && schema["$schema"] != "https://json-schema.org/draft/2020-12/schema"
|
|
131
|
+
raise ArgumentError, "Tool schemas require JSON Schema 2020-12"
|
|
132
|
+
end
|
|
133
|
+
if schema.key?("format") && !FORMATS.include?(schema["format"])
|
|
134
|
+
raise ArgumentError, "unsupported Tool schema format: #{schema["format"]}"
|
|
135
|
+
end
|
|
136
|
+
if schema.key?("$ref")
|
|
137
|
+
reference = schema["$ref"]
|
|
138
|
+
if !(reference.is_a?(String) && (reference == "#" || reference.start_with?("#/")))
|
|
139
|
+
raise ArgumentError, "Tool schemas support local JSON Pointer references only"
|
|
140
|
+
end
|
|
141
|
+
@references << reference
|
|
142
|
+
end
|
|
143
|
+
SCHEMA_MAPS.each do |key|
|
|
144
|
+
next unless schema.key?(key)
|
|
145
|
+
raise ArgumentError, "#{key} must be an object" unless schema[key].is_a?(Hash)
|
|
146
|
+
schema[key].each_value { |child| validate_keywords!(child) }
|
|
147
|
+
end
|
|
148
|
+
SCHEMA_LISTS.each do |key|
|
|
149
|
+
next unless schema.key?(key)
|
|
150
|
+
raise ArgumentError, "#{key} must be an array" unless schema[key].is_a?(Array)
|
|
151
|
+
schema[key].each { |child| validate_keywords!(child) }
|
|
152
|
+
end
|
|
153
|
+
SCHEMA_VALUES.each { |key| validate_keywords!(schema[key]) if schema.key?(key) }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def coerce_value(value, schema)
|
|
157
|
+
return value unless schema.is_a?(Hash)
|
|
158
|
+
return value if (schema.keys & %w[$ref anyOf oneOf allOf not if]).any?
|
|
159
|
+
type = schema["type"]
|
|
160
|
+
if type.is_a?(Array)
|
|
161
|
+
alternatives = type - ["null"]
|
|
162
|
+
type = alternatives.first if alternatives.length == 1
|
|
163
|
+
end
|
|
164
|
+
case type
|
|
165
|
+
when "object"
|
|
166
|
+
return value unless value.is_a?(Hash)
|
|
167
|
+
value.to_h { |key, child| [key, coerce_value(child, schema.fetch("properties", {}).fetch(key, {}))] }
|
|
168
|
+
when "array"
|
|
169
|
+
return value unless value.is_a?(Array)
|
|
170
|
+
value.map { |child| coerce_value(child, schema.fetch("items", {})) }
|
|
171
|
+
when "string" then value.nil? ? value : value.to_s
|
|
172
|
+
when "integer"
|
|
173
|
+
return value if value.nil? || value.is_a?(Integer)
|
|
174
|
+
raise ArgumentError, "fractional Tool value cannot be coerced to integer" if value.is_a?(Numeric) && value != value.to_i
|
|
175
|
+
begin
|
|
176
|
+
Integer(value)
|
|
177
|
+
rescue ArgumentError, TypeError
|
|
178
|
+
raise ArgumentError, "parameter cannot be coerced to integer: #{value.inspect}"
|
|
179
|
+
end
|
|
180
|
+
when "number"
|
|
181
|
+
begin
|
|
182
|
+
value.nil? ? value : Float(value)
|
|
183
|
+
rescue ArgumentError, TypeError
|
|
184
|
+
raise ArgumentError, "parameter cannot be coerced to number: #{value.inspect}"
|
|
185
|
+
end
|
|
186
|
+
when "boolean"
|
|
187
|
+
case value
|
|
188
|
+
when true, false, nil then value
|
|
189
|
+
else
|
|
190
|
+
case value.to_s.downcase
|
|
191
|
+
when "true" then true
|
|
192
|
+
when "false" then false
|
|
193
|
+
else raise ArgumentError, "parameter cannot be coerced to boolean: #{value.inspect}"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
else value
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def restore_keys(original, value)
|
|
201
|
+
case value
|
|
202
|
+
when Hash
|
|
203
|
+
value.to_h do |key, child|
|
|
204
|
+
old_key = (original.is_a?(Hash) && original.key?(key.to_sym)) ? key.to_sym : key
|
|
205
|
+
[old_key, restore_keys(original.is_a?(Hash) ? original[old_key] : nil, child)]
|
|
206
|
+
end
|
|
207
|
+
when Array
|
|
208
|
+
value.each_with_index.map { |child, index| restore_keys(original.is_a?(Array) ? original[index] : nil, child) }
|
|
209
|
+
else value
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/configuration_error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module Tool
|
|
7
|
+
# Tool-owned defaults. Composition supplies values; result processing reads
|
|
8
|
+
# them at its existing boundary, after execution or asynchronous completion.
|
|
9
|
+
# @api private
|
|
10
|
+
class Settings
|
|
11
|
+
def self.install_provider(&provider)
|
|
12
|
+
raise ArgumentError, "Tool settings provider requires a block" unless provider
|
|
13
|
+
|
|
14
|
+
@provider = provider
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Resolve the current value container without caching or notifications.
|
|
19
|
+
def self.current
|
|
20
|
+
settings = @provider&.call
|
|
21
|
+
unless settings.is_a?(Settings)
|
|
22
|
+
raise ConfigurationError, "Tool settings provider must return Tool::Settings"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
settings
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_accessor :max_result_size
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Tool
|
|
5
5
|
# Routes Tool work according to the Tool execution contract.
|
|
6
6
|
#
|
|
7
7
|
# Both execution modes return {Phronomy::TaskResult}; only the execution mechanism
|
|
@@ -15,14 +15,18 @@ module Phronomy
|
|
|
15
15
|
# caller-facing completion handle is also a TaskResult. Phronomy does not distinguish
|
|
16
16
|
# whether the reason is blocking I/O, CPU-bound work, or another long
|
|
17
17
|
# synchronous operation.
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
18
20
|
module ToolExecutor
|
|
21
|
+
# @api private
|
|
19
22
|
def self.call_async(
|
|
20
23
|
tool:,
|
|
21
24
|
args:,
|
|
22
25
|
cancellation_token: nil,
|
|
23
26
|
config: {},
|
|
24
|
-
|
|
25
|
-
on_full: :raise
|
|
27
|
+
submitter: Phronomy::Execution,
|
|
28
|
+
on_full: :raise,
|
|
29
|
+
synchronous_call: tool.method(:call)
|
|
26
30
|
)
|
|
27
31
|
mode = tool.class.execution_mode
|
|
28
32
|
|
|
@@ -31,19 +35,18 @@ module Phronomy
|
|
|
31
35
|
task = Phronomy::TaskResult.deferred(name: "tool-#{tool.name}")
|
|
32
36
|
begin
|
|
33
37
|
task.complete(
|
|
34
|
-
|
|
38
|
+
synchronous_call.call(args, cancellation_token: cancellation_token)
|
|
35
39
|
)
|
|
36
40
|
rescue => error
|
|
37
41
|
task.fail(error)
|
|
38
42
|
end
|
|
39
43
|
task
|
|
40
44
|
when :offloaded
|
|
41
|
-
|
|
42
|
-
runtime.offload.submit(
|
|
45
|
+
submitter.submit(
|
|
43
46
|
cancellation_token: cancellation_token,
|
|
44
47
|
on_full: on_full
|
|
45
48
|
) do
|
|
46
|
-
|
|
49
|
+
synchronous_call.call(args, cancellation_token: cancellation_token)
|
|
47
50
|
end
|
|
48
51
|
else
|
|
49
52
|
raise Phronomy::ConfigurationError,
|
data/lib/phronomy/tools/agent.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Phronomy
|
|
|
9
9
|
# returns to EventLoop immediately; the Tool completion handle settles when the
|
|
10
10
|
# child Agent FSMSession finishes. An OffloadPool worker is therefore never
|
|
11
11
|
# consumed merely to wait for another Agent.
|
|
12
|
-
class Agent < Phronomy::
|
|
12
|
+
class Agent < Phronomy::Tool::Base
|
|
13
13
|
execution_mode :cooperative
|
|
14
14
|
description "Wraps a Phronomy::Agent as a Tool"
|
|
15
15
|
param :input, type: :string, desc: "The input to forward to the wrapped Agent"
|
|
@@ -39,11 +39,16 @@ module Phronomy
|
|
|
39
39
|
if cancellation_token
|
|
40
40
|
invoke_options[:config] = {cancellation_token: cancellation_token}
|
|
41
41
|
end
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
# Use the same supplied store factory as the asynchronous path.
|
|
43
|
+
# Preserve the creation arguments previously supplied by run_once.
|
|
44
|
+
persistence = Phronomy::Agent::DefaultPersistence.build
|
|
45
|
+
agent = agent_class.create(
|
|
46
|
+
context: nil,
|
|
47
|
+
knowledge: [],
|
|
48
|
+
persistence: persistence,
|
|
49
|
+
on_event: nil
|
|
46
50
|
)
|
|
51
|
+
result = agent.invoke(input, **invoke_options)
|
|
47
52
|
result[:output].to_s
|
|
48
53
|
end
|
|
49
54
|
|
|
@@ -51,7 +56,7 @@ module Phronomy
|
|
|
51
56
|
# The child Agent owns its own FSMSession/EventLoop lifecycle; this
|
|
52
57
|
# method only returns its completion handle and performs a short map.
|
|
53
58
|
klass.define_method(:execute_async) do |input:, cancellation_token: nil, config: {}|
|
|
54
|
-
persistence = Phronomy::
|
|
59
|
+
persistence = Phronomy::Agent::DefaultPersistence.build
|
|
55
60
|
agent = agent_class.create(persistence: persistence)
|
|
56
61
|
task_config = (config || {}).dup
|
|
57
62
|
if cancellation_token && !task_config[:cancellation_token]
|
|
@@ -83,106 +88,33 @@ module Phronomy
|
|
|
83
88
|
|
|
84
89
|
# Agent-backed Tools have an asynchronous implementation that does not use
|
|
85
90
|
# ToolExecutor/OffloadPool. Validation and Tool error policy still match
|
|
86
|
-
#
|
|
91
|
+
# Tool::Base#call.
|
|
87
92
|
def call_async(
|
|
88
93
|
args,
|
|
89
94
|
cancellation_token: nil,
|
|
90
95
|
config: {}
|
|
91
96
|
)
|
|
92
|
-
cancellation_token
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if schema_error
|
|
96
|
-
return schema_error_task(schema_error)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
source = execute_async(
|
|
100
|
-
**(validated_args || {}),
|
|
101
|
-
cancellation_token: cancellation_token,
|
|
102
|
-
config: config || {}
|
|
103
|
-
)
|
|
104
|
-
unless source.respond_to?(:on_complete)
|
|
105
|
-
raise Phronomy::ToolError,
|
|
106
|
-
"#{self.class.name} asynchronous execution must return a completion handle"
|
|
97
|
+
call_async_operation(args, cancellation_token: cancellation_token,
|
|
98
|
+
operation_name: "agent-tool-#{name}") do |validated_args|
|
|
99
|
+
execute_async(**validated_args, cancellation_token: cancellation_token, config: config || {})
|
|
107
100
|
end
|
|
108
|
-
|
|
109
|
-
result_task = Phronomy::TaskResult.deferred(name: "agent-tool-#{name}")
|
|
110
|
-
source.on_complete do |result, error|
|
|
111
|
-
if error
|
|
112
|
-
settle_async_error(result_task, error)
|
|
113
|
-
next
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
begin
|
|
117
|
-
result_task.complete(send(:truncate_result_if_needed, result))
|
|
118
|
-
rescue => result_error
|
|
119
|
-
settle_async_error(result_task, result_error)
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
result_task
|
|
123
|
-
rescue Phronomy::ToolError, Phronomy::CancellationError => error
|
|
124
|
-
failed_task(error)
|
|
125
|
-
rescue => error
|
|
126
|
-
result_task = Phronomy::TaskResult.deferred(name: "agent-tool-#{name}")
|
|
127
|
-
settle_async_error(result_task, error)
|
|
128
|
-
result_task
|
|
129
101
|
end
|
|
130
102
|
|
|
131
103
|
private
|
|
132
104
|
|
|
133
|
-
# Subclasses created by .from_agent
|
|
105
|
+
# Subclasses created by .from_agent override this method.
|
|
134
106
|
# It deliberately remains private so it is not part of the public Tool API.
|
|
135
107
|
def execute_async(input:, cancellation_token: nil, config: {})
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
task.complete(execute(input: input, cancellation_token: cancellation_token))
|
|
139
|
-
rescue => error
|
|
140
|
-
task.fail(error)
|
|
108
|
+
Phronomy::AsyncOperation.capture(name: "agent-tool-#{name}-fallback") do
|
|
109
|
+
execute(input: input, cancellation_token: cancellation_token)
|
|
141
110
|
end
|
|
142
|
-
task
|
|
143
111
|
end
|
|
144
112
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if
|
|
148
|
-
task.fail(Phronomy::ToolError.new(
|
|
149
|
-
"#{self.class.name} schema error: #{schema_error}"
|
|
150
|
-
))
|
|
151
|
-
else
|
|
152
|
-
task.complete("Schema validation failed: #{schema_error}")
|
|
153
|
-
end
|
|
154
|
-
task
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def failed_task(error)
|
|
158
|
-
Phronomy::TaskResult.deferred(name: "agent-tool-#{name}-failed").tap do |task|
|
|
159
|
-
task.fail(error)
|
|
160
|
-
end
|
|
161
|
-
end
|
|
113
|
+
# Agent recovery is an Agent operation outcome, not a generic Tool error.
|
|
114
|
+
def async_error_value(error)
|
|
115
|
+
raise error if error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
|
|
162
116
|
|
|
163
|
-
|
|
164
|
-
if error.is_a?(Phronomy::ToolError) || error.is_a?(Phronomy::CancellationError) || error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
|
|
165
|
-
task.fail(error)
|
|
166
|
-
return task
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
if self.class.on_error == :suppress
|
|
170
|
-
message = "[Phronomy] Tool #{self.class.name} suppressed error: " \
|
|
171
|
-
"#{error.class}: #{error.message}"
|
|
172
|
-
if Phronomy.configuration.logger
|
|
173
|
-
Phronomy.configuration.logger.warn(message)
|
|
174
|
-
else
|
|
175
|
-
warn message
|
|
176
|
-
end
|
|
177
|
-
task.complete("Tool error suppressed: #{error.message}")
|
|
178
|
-
else
|
|
179
|
-
wrapped = Phronomy::ToolError.new(
|
|
180
|
-
"#{self.class.name} execution failed: #{error.message}"
|
|
181
|
-
)
|
|
182
|
-
wrapped.set_backtrace(error.backtrace)
|
|
183
|
-
task.fail(wrapped)
|
|
184
|
-
end
|
|
185
|
-
task
|
|
117
|
+
super
|
|
186
118
|
end
|
|
187
119
|
end
|
|
188
120
|
end
|
data/lib/phronomy/tools/mcp.rb
CHANGED
|
@@ -7,7 +7,7 @@ require "uri"
|
|
|
7
7
|
|
|
8
8
|
module Phronomy
|
|
9
9
|
module Tools
|
|
10
|
-
# A Phronomy::
|
|
10
|
+
# A Phronomy::Tool::Base subclass that wraps a tool exposed by an external
|
|
11
11
|
# MCP (Model Context Protocol) server.
|
|
12
12
|
#
|
|
13
13
|
# Uses the official MCP Ruby SDK v1.x for transport handling, which provides
|
|
@@ -27,7 +27,7 @@ module Phronomy
|
|
|
27
27
|
# tool_name: "search_web"
|
|
28
28
|
# )
|
|
29
29
|
# agent_class.tools(web_search)
|
|
30
|
-
class Mcp < Phronomy::
|
|
30
|
+
class Mcp < Phronomy::Tool::Base
|
|
31
31
|
SUPPORTED_SCHEMA_DIALECTS = [
|
|
32
32
|
"https://json-schema.org/draft/2020-12/schema",
|
|
33
33
|
"https://json-schema.org/draft/2020-12/schema#"
|
|
@@ -273,8 +273,9 @@ module Phronomy
|
|
|
273
273
|
end
|
|
274
274
|
|
|
275
275
|
def warn_mcp(message)
|
|
276
|
-
|
|
277
|
-
|
|
276
|
+
logger = Phronomy::RuntimeSettings.current.logger
|
|
277
|
+
if logger
|
|
278
|
+
logger.warn(message)
|
|
278
279
|
else
|
|
279
280
|
Kernel.warn(message)
|
|
280
281
|
end
|
|
@@ -344,7 +345,8 @@ module Phronomy
|
|
|
344
345
|
private
|
|
345
346
|
|
|
346
347
|
def perform_mcp_call(cancellation_token:, args:)
|
|
347
|
-
|
|
348
|
+
subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
|
|
349
|
+
mcp_cancellation = build_mcp_cancellation(cancellation_token, subscriptions: subscriptions)
|
|
348
350
|
response = begin
|
|
349
351
|
@mcp_client.call_tool(
|
|
350
352
|
name: self.class.tool_name,
|
|
@@ -376,13 +378,17 @@ module Phronomy
|
|
|
376
378
|
|
|
377
379
|
result = validate_call_tool_response!(response)
|
|
378
380
|
format_tool_result(result)
|
|
381
|
+
ensure
|
|
382
|
+
# A caller-facing timeout can settle before this SDK call returns.
|
|
383
|
+
# Keep its bridge alive until this invocation actually leaves the SDK.
|
|
384
|
+
subscriptions&.close
|
|
379
385
|
end
|
|
380
386
|
|
|
381
|
-
def build_mcp_cancellation(cancellation_token)
|
|
387
|
+
def build_mcp_cancellation(cancellation_token, subscriptions:)
|
|
382
388
|
return nil unless cancellation_token
|
|
383
389
|
|
|
384
390
|
mcp_cancellation = MCP::Cancellation.new
|
|
385
|
-
cancellation_token
|
|
391
|
+
subscriptions.explicit_cancellation(cancellation_token) do
|
|
386
392
|
mcp_cancellation.cancel(reason: "phronomy_cancelled")
|
|
387
393
|
end
|
|
388
394
|
mcp_cancellation
|
|
@@ -478,17 +484,18 @@ module Phronomy
|
|
|
478
484
|
end
|
|
479
485
|
|
|
480
486
|
def schedule_transport_cleanup(transport)
|
|
481
|
-
|
|
482
|
-
:mcp_cleanup,
|
|
487
|
+
Phronomy::Execution.submit(
|
|
488
|
+
pool_name: :mcp_cleanup,
|
|
483
489
|
size: MCP_CLEANUP_POOL_SIZE,
|
|
484
|
-
queue_size: MCP_CLEANUP_QUEUE_SIZE
|
|
485
|
-
|
|
486
|
-
|
|
490
|
+
queue_size: MCP_CLEANUP_QUEUE_SIZE,
|
|
491
|
+
on_full: :raise
|
|
492
|
+
) do
|
|
487
493
|
self.class.send(:close_transport_safely, transport)
|
|
488
494
|
end
|
|
489
|
-
rescue Phronomy::BackpressureError, Phronomy::PoolShutdownError
|
|
495
|
+
rescue Phronomy::BackpressureError, Phronomy::PoolShutdownError, Phronomy::RuntimeShutdownError
|
|
490
496
|
# During shutdown or an exceptional cleanup burst, prefer a bounded
|
|
491
|
-
# synchronous fallback over leaking the child process/socket.
|
|
497
|
+
# synchronous fallback over leaking the child process/socket. Runtime
|
|
498
|
+
# can reject acquisition before the cleanup pool itself is consulted.
|
|
492
499
|
self.class.send(:close_transport_safely, transport)
|
|
493
500
|
end
|
|
494
501
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module Tools
|
|
5
5
|
# A Capability::Base subclass that wraps a {Phronomy::VectorStore::Base} and
|
|
6
|
-
# a {Phronomy::
|
|
6
|
+
# a {Phronomy::Embeddings::Base} adapter so that an agent can
|
|
7
7
|
# perform semantic search as a tool call.
|
|
8
8
|
#
|
|
9
9
|
# Do not instantiate this class directly. Use the factory method
|
|
@@ -11,7 +11,7 @@ module Phronomy
|
|
|
11
11
|
#
|
|
12
12
|
# @example
|
|
13
13
|
# store = Phronomy::VectorStore::InMemory.new
|
|
14
|
-
# emb = Phronomy::
|
|
14
|
+
# emb = Phronomy::Embeddings::RubyLLMEmbeddings.new(model: "...")
|
|
15
15
|
# tool = Phronomy::Tools::VectorSearch.from_store(store, embeddings: emb,
|
|
16
16
|
# k: 3, tool_name: "search_docs",
|
|
17
17
|
# description: "Search the company knowledge base.")
|
|
@@ -19,7 +19,7 @@ module Phronomy
|
|
|
19
19
|
# agent.tools tool
|
|
20
20
|
#
|
|
21
21
|
# @api public
|
|
22
|
-
class VectorSearch < Phronomy::
|
|
22
|
+
class VectorSearch < Phronomy::Tool::Base
|
|
23
23
|
description "Search for relevant documents using semantic similarity."
|
|
24
24
|
param :query, type: :string, desc: "The natural-language search query"
|
|
25
25
|
|
|
@@ -27,7 +27,7 @@ module Phronomy
|
|
|
27
27
|
# Build a VectorSearch tool backed by the given store and embeddings adapter.
|
|
28
28
|
#
|
|
29
29
|
# @param store [Phronomy::VectorStore::Base]
|
|
30
|
-
# @param embeddings [Phronomy::
|
|
30
|
+
# @param embeddings [Phronomy::Embeddings::Base]
|
|
31
31
|
# @param k [Integer] number of results to return (default 5)
|
|
32
32
|
# @param tool_name [String] name exposed to the LLM
|
|
33
33
|
# @param description [String, nil] optional description override
|