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,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Implements the Generator-Verifier multi-agent coordination pattern.
|
|
5
|
+
#
|
|
6
|
+
# Agent completion is integrated through application-defined Workflow events;
|
|
7
|
+
# Workflow entry actions do not return or await Agent Tasks.
|
|
8
|
+
class GeneratorVerifier
|
|
9
|
+
DEFAULT_CONFIDENCE_THRESHOLD = 0.7
|
|
10
|
+
DEFAULT_MAX_ITERATIONS = 3
|
|
11
|
+
|
|
12
|
+
Result = Struct.new(
|
|
13
|
+
:output,
|
|
14
|
+
:confidence,
|
|
15
|
+
:citations,
|
|
16
|
+
:iterations,
|
|
17
|
+
:review_notes,
|
|
18
|
+
:trusted
|
|
19
|
+
) do
|
|
20
|
+
alias_method :trusted?, :trusted
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private_constant :PipelineState, :WorkflowBuilder, :AgentResultReceiver, :AgentOperation
|
|
24
|
+
|
|
25
|
+
def initialize(
|
|
26
|
+
draft_agent:,
|
|
27
|
+
review_agent:,
|
|
28
|
+
draft_prompt_builder:,
|
|
29
|
+
review_prompt_builder:,
|
|
30
|
+
draft_result_parser: nil,
|
|
31
|
+
review_result_parser: nil,
|
|
32
|
+
confidence_threshold: DEFAULT_CONFIDENCE_THRESHOLD,
|
|
33
|
+
max_iterations: DEFAULT_MAX_ITERATIONS,
|
|
34
|
+
raise_if_untrusted: false
|
|
35
|
+
)
|
|
36
|
+
@draft_agent_class = draft_agent
|
|
37
|
+
@review_agent_class = review_agent
|
|
38
|
+
@draft_prompt_builder = draft_prompt_builder
|
|
39
|
+
@review_prompt_builder = review_prompt_builder
|
|
40
|
+
@draft_result_parser =
|
|
41
|
+
draft_result_parser || method(:default_parse_draft)
|
|
42
|
+
@review_result_parser =
|
|
43
|
+
review_result_parser || method(:default_parse_review)
|
|
44
|
+
@threshold = confidence_threshold.to_f
|
|
45
|
+
@max_iterations = max_iterations.to_i
|
|
46
|
+
@raise_if_untrusted = raise_if_untrusted
|
|
47
|
+
@compiled_workflow = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def invoke(input, config: {})
|
|
51
|
+
state = compiled_workflow.invoke({input: input}, config: config)
|
|
52
|
+
confidence = combined_confidence(state)
|
|
53
|
+
trusted = confidence >= @threshold
|
|
54
|
+
result = Result.new(
|
|
55
|
+
output: state.output || state.draft.to_s,
|
|
56
|
+
confidence: confidence,
|
|
57
|
+
citations: state.citations,
|
|
58
|
+
iterations: state.iteration,
|
|
59
|
+
review_notes: state.review_notes,
|
|
60
|
+
trusted: trusted
|
|
61
|
+
)
|
|
62
|
+
if @raise_if_untrusted && !trusted
|
|
63
|
+
raise LowConfidenceError.new(result)
|
|
64
|
+
end
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def combined_confidence(state)
|
|
71
|
+
[
|
|
72
|
+
(state.self_score || 0.0).to_f,
|
|
73
|
+
(state.review_score || 0.0).to_f
|
|
74
|
+
].min
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def compiled_workflow
|
|
78
|
+
@compiled_workflow ||= build_workflow
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def build_workflow
|
|
82
|
+
WorkflowBuilder.new(
|
|
83
|
+
draft_agent: AgentOperation.new(@draft_agent_class),
|
|
84
|
+
review_agent: AgentOperation.new(@review_agent_class),
|
|
85
|
+
draft_prompt_builder: @draft_prompt_builder,
|
|
86
|
+
review_prompt_builder: @review_prompt_builder,
|
|
87
|
+
draft_result_parser: @draft_result_parser,
|
|
88
|
+
review_result_parser: @review_result_parser,
|
|
89
|
+
threshold: @threshold,
|
|
90
|
+
max_iterations: @max_iterations
|
|
91
|
+
).build
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def default_parse_draft(text)
|
|
95
|
+
default_parser.parse(text)
|
|
96
|
+
rescue Phronomy::ParseError
|
|
97
|
+
{
|
|
98
|
+
answer: text.to_s,
|
|
99
|
+
confidence: 0.0,
|
|
100
|
+
citations: []
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def default_parse_review(text)
|
|
105
|
+
default_parser.parse(text)
|
|
106
|
+
rescue Phronomy::ParseError
|
|
107
|
+
{
|
|
108
|
+
approved: false,
|
|
109
|
+
score: 0.0,
|
|
110
|
+
feedback: "Review output could not be parsed: #{text}"
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def default_parser
|
|
115
|
+
@default_parser ||= DefaultParser.build
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
class LowConfidenceError < Error
|
|
7
|
+
attr_reader :result
|
|
8
|
+
|
|
9
|
+
def initialize(result)
|
|
10
|
+
@result = result
|
|
11
|
+
super("Answer confidence #{result.confidence} is below the required threshold")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module LLMAdapter
|
|
5
|
+
# Offloads the entire operation, including SDK initialization and decoding.
|
|
6
|
+
# Construction does not start a Runtime.
|
|
7
|
+
# @api private
|
|
8
|
+
class AsyncClient
|
|
9
|
+
# @api private
|
|
10
|
+
def initialize(adapter:, pool: nil, submitter: Phronomy::Execution)
|
|
11
|
+
@adapter = adapter
|
|
12
|
+
@pool = pool
|
|
13
|
+
@submitter = submitter
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @api private
|
|
17
|
+
def complete_async(request, cancellation_token: nil, pool: @pool)
|
|
18
|
+
submit(pool, cancellation_token) do
|
|
19
|
+
@adapter.complete(request, cancellation_token: cancellation_token)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The block is an internal event sink, never an Application callback.
|
|
24
|
+
# @api private
|
|
25
|
+
def stream_async(request, cancellation_token: nil, pool: @pool, &block)
|
|
26
|
+
raise ArgumentError, "stream_async requires a block" unless block
|
|
27
|
+
submit(pool, cancellation_token) do
|
|
28
|
+
@adapter.stream(request, cancellation_token: cancellation_token) do |chunk|
|
|
29
|
+
cancellation_token&.raise_if_cancelled!
|
|
30
|
+
block.call(chunk)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def submit(pool, cancellation_token, &operation)
|
|
38
|
+
if pool
|
|
39
|
+
Phronomy::Execution.submit(pool: pool, cancellation_token: cancellation_token,
|
|
40
|
+
on_full: :raise, &operation)
|
|
41
|
+
else
|
|
42
|
+
@submitter.submit(cancellation_token: cancellation_token, on_full: :raise, &operation)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_llm"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module LLMAdapter
|
|
7
|
+
# SDK objects are operation-local. Only Phronomy request/result values cross
|
|
8
|
+
# the backend boundary; a Tool definition never contains executable work.
|
|
9
|
+
# @api public
|
|
10
|
+
class RubyLLM < Base
|
|
11
|
+
ToolsRequested = Class.new(StandardError) do
|
|
12
|
+
attr_reader :message_value
|
|
13
|
+
def initialize(message_value)
|
|
14
|
+
@message_value = message_value
|
|
15
|
+
super("assistant requested Tools")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
private_constant :ToolsRequested
|
|
19
|
+
|
|
20
|
+
# @api public
|
|
21
|
+
def identity
|
|
22
|
+
super.merge("sdk_version" => ::RubyLLM::VERSION).freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api public
|
|
26
|
+
def input_budget(model_config)
|
|
27
|
+
config = model_config.to_h.transform_keys(&:to_s)
|
|
28
|
+
return nil unless config["model"]
|
|
29
|
+
model = ::RubyLLM.models.find(config["model"], provider: config["provider"])
|
|
30
|
+
limit = model&.context_window
|
|
31
|
+
return nil unless limit.is_a?(Integer) && limit.positive?
|
|
32
|
+
Phronomy::LlmContextWindow::TokenBudget.new(max_input_tokens: limit)
|
|
33
|
+
rescue ::RubyLLM::ModelNotFoundError
|
|
34
|
+
nil
|
|
35
|
+
rescue => error
|
|
36
|
+
raise_provider_error(error)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
protected
|
|
40
|
+
|
|
41
|
+
# @api public
|
|
42
|
+
def perform_complete(request, cancellation_token:)
|
|
43
|
+
execute_request(request, cancellation_token)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @api public
|
|
47
|
+
def perform_stream(request, cancellation_token:, &block)
|
|
48
|
+
execute_request(request, cancellation_token, &block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def execute_request(request, token)
|
|
54
|
+
callback_error = nil
|
|
55
|
+
chat = build_chat(request)
|
|
56
|
+
token&.raise_if_cancelled!
|
|
57
|
+
chat.after_message do |message|
|
|
58
|
+
raise ToolsRequested.new(message) if message.tool_call?
|
|
59
|
+
end
|
|
60
|
+
response = if block_given?
|
|
61
|
+
sink = proc do |chunk|
|
|
62
|
+
token&.raise_if_cancelled!
|
|
63
|
+
value = stream_value(chunk)
|
|
64
|
+
begin
|
|
65
|
+
yield value
|
|
66
|
+
rescue => error
|
|
67
|
+
callback_error = error
|
|
68
|
+
raise
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
request.message ? chat.ask(request.message, &sink) : chat.complete(&sink)
|
|
72
|
+
else
|
|
73
|
+
request.message ? chat.ask(request.message) : chat.complete
|
|
74
|
+
end
|
|
75
|
+
response_value(response)
|
|
76
|
+
rescue ToolsRequested => requested
|
|
77
|
+
response_value(requested.message_value)
|
|
78
|
+
rescue => error
|
|
79
|
+
raise error if error.equal?(callback_error)
|
|
80
|
+
raise_provider_error(error)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_chat(request)
|
|
84
|
+
config = request.model_config
|
|
85
|
+
options = {}
|
|
86
|
+
options[:model] = config["model"] if config["model"]
|
|
87
|
+
if config["provider"]
|
|
88
|
+
options[:provider] = config["provider"].to_sym
|
|
89
|
+
options[:assume_model_exists] = config.fetch("assume_model_exists", true)
|
|
90
|
+
elsif config.key?("assume_model_exists")
|
|
91
|
+
options[:assume_model_exists] = config["assume_model_exists"]
|
|
92
|
+
end
|
|
93
|
+
chat = ::RubyLLM.chat(**options)
|
|
94
|
+
chat.with_temperature(config["temperature"]) if config["temperature"]
|
|
95
|
+
chat.with_max_output_tokens(config["max_output_tokens"]) if config["max_output_tokens"]
|
|
96
|
+
if request.system
|
|
97
|
+
chat.with_instructions(request.system, cache_until_here: config["cache_instructions"])
|
|
98
|
+
end
|
|
99
|
+
request.tools.each { |definition| chat.with_tools(tool_declaration(definition)) }
|
|
100
|
+
request.messages.each { |value| chat.messages << sdk_message(value) }
|
|
101
|
+
chat
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def tool_declaration(definition)
|
|
105
|
+
name = definition.fetch("name")
|
|
106
|
+
klass = Class.new(::RubyLLM::Tool) do
|
|
107
|
+
define_singleton_method(:tool_name) { name }
|
|
108
|
+
define_method(:execute) do |**_arguments|
|
|
109
|
+
raise InvalidResultError, "SDK attempted to execute a declaration-only Tool"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
klass.description(definition.fetch("description"))
|
|
113
|
+
klass.parameters(definition.fetch("parameters_schema"))
|
|
114
|
+
klass.provider_options(definition.fetch("provider_options", {}))
|
|
115
|
+
klass
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def sdk_message(value)
|
|
119
|
+
calls = value.tool_calls.to_h do |call|
|
|
120
|
+
[call.id, ::RubyLLM::ToolCall.new(id: call.id, name: call.name,
|
|
121
|
+
arguments: call.arguments, thought_signature: call.metadata["thought_signature"])]
|
|
122
|
+
end
|
|
123
|
+
content = value.content
|
|
124
|
+
content = JSON.generate(content) if content.is_a?(Hash) || content.is_a?(Array)
|
|
125
|
+
content = "" if value.role == :assistant && content.nil? && !calls.empty?
|
|
126
|
+
::RubyLLM::Message.new(role: value.role, content: content,
|
|
127
|
+
tool_calls: calls.empty? ? nil : calls, tool_call_id: value.tool_call_id,
|
|
128
|
+
model: value.metadata["model_id"])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def response_value(message)
|
|
132
|
+
unless message.respond_to?(:content) && message.respond_to?(:role) && message.role.to_sym == :assistant
|
|
133
|
+
raise InvalidResultError, "provider returned an invalid assistant message"
|
|
134
|
+
end
|
|
135
|
+
raw_calls = message.tool_calls if message.respond_to?(:tool_calls)
|
|
136
|
+
calls = raw_calls.is_a?(Hash) ? raw_calls.values : (raw_calls || [])
|
|
137
|
+
raise InvalidResultError, "provider returned invalid Tool calls" unless calls.is_a?(Array)
|
|
138
|
+
calls = calls.map do |call|
|
|
139
|
+
metadata = {}
|
|
140
|
+
if call.respond_to?(:thought_signature) && call.thought_signature
|
|
141
|
+
metadata["thought_signature"] = call.thought_signature
|
|
142
|
+
end
|
|
143
|
+
Phronomy::Tool::CallRequest.new(id: call.id, name: call.name,
|
|
144
|
+
arguments: call.arguments, metadata: metadata)
|
|
145
|
+
end
|
|
146
|
+
tokens = message.tokens if message.respond_to?(:tokens)
|
|
147
|
+
usage = Phronomy::LLMAdapter::TokenUsage.new(
|
|
148
|
+
input: tokens&.input, output: tokens&.output,
|
|
149
|
+
cached: tokens&.cache_read, cache_creation: tokens&.cache_write
|
|
150
|
+
)
|
|
151
|
+
metadata = {}
|
|
152
|
+
metadata["model_id"] = message.model if message.respond_to?(:model) && message.model
|
|
153
|
+
Response.new(content: message.content, tool_calls: calls, usage: usage, metadata: metadata)
|
|
154
|
+
rescue ArgumentError, TypeError, NoMethodError => error
|
|
155
|
+
raise InvalidResultError, "malformed provider response: #{error.message}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def stream_value(chunk)
|
|
159
|
+
raise InvalidResultError, "provider returned an invalid stream chunk" unless chunk.respond_to?(:content)
|
|
160
|
+
StreamChunk.new(content: chunk.content)
|
|
161
|
+
rescue ArgumentError => error
|
|
162
|
+
raise InvalidResultError, "malformed provider chunk: #{error.message}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Translate SDK errors only. Cancellation and callback failures retain
|
|
166
|
+
# identity; raising here preserves the original provider exception cause.
|
|
167
|
+
def raise_provider_error(error)
|
|
168
|
+
case error
|
|
169
|
+
when ::RubyLLM::RateLimitError
|
|
170
|
+
raise Phronomy::LLMAdapter::RateLimitError, error.message
|
|
171
|
+
when ::RubyLLM::UnauthorizedError, ::RubyLLM::ForbiddenError
|
|
172
|
+
raise Phronomy::LLMAdapter::AuthenticationError, error.message
|
|
173
|
+
when ::RubyLLM::ContextLengthExceededError
|
|
174
|
+
raise Phronomy::LLMAdapter::ContextLengthError, error.message
|
|
175
|
+
when ::RubyLLM::Error
|
|
176
|
+
raise Phronomy::LLMAdapter::TransportError, error.message
|
|
177
|
+
else
|
|
178
|
+
raise error
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -1,91 +1,72 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "request"
|
|
4
|
+
require_relative "response"
|
|
5
|
+
require_relative "stream_chunk"
|
|
6
|
+
require_relative "invalid_result_error"
|
|
7
|
+
|
|
3
8
|
module Phronomy
|
|
4
9
|
module LLMAdapter
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# External adapters implement {#complete} and {#stream}. The adapter or the
|
|
8
|
-
# underlying provider client owns transport timeout, retry, backoff, and
|
|
9
|
-
# rate-limit behavior. Phronomy owns cooperative cancellation and isolates
|
|
10
|
-
# synchronous provider calls in {OffloadPool}.
|
|
11
|
-
#
|
|
12
|
-
# The Agent pipeline calls the framework-owned {#complete_async} and
|
|
13
|
-
# {#stream_async} wrappers. Adapter implementers do not need to depend on
|
|
14
|
-
# EventLoop, FSMSession, OffloadPool, AgentInvocation, or ExecutionCoordinator.
|
|
15
|
-
#
|
|
16
|
-
# The current input to this SPI is the configured/materialized chat runtime
|
|
17
|
-
# object. Formalizing this SPI therefore does not imply a provider-neutral
|
|
18
|
-
# replacement for RubyLLMMaterializer.
|
|
19
|
-
#
|
|
10
|
+
# Owns one-turn operations. Backends implement protected synchronous hooks.
|
|
11
|
+
# SDK construction and translation happen entirely inside those hooks.
|
|
20
12
|
# @api public
|
|
21
13
|
class Base
|
|
22
|
-
# Performs a blocking (non-streaming) LLM completion.
|
|
23
|
-
#
|
|
24
|
-
# Implementors call the configured chat/runtime client and return its
|
|
25
|
-
# response object. Transport/retry policy remains adapter-owned.
|
|
26
|
-
#
|
|
27
|
-
# @param chat [Object] the configured/materialized chat runtime object
|
|
28
|
-
# @param message [String, nil] user message, or nil to continue without adding a new user turn
|
|
29
|
-
# @param config [Hash] invocation config (e.g. +:cancellation_token+)
|
|
30
|
-
# @return [Object] LLM response object
|
|
31
|
-
# @raise [NotImplementedError]
|
|
32
14
|
# @api public
|
|
33
|
-
def
|
|
34
|
-
|
|
15
|
+
def identity
|
|
16
|
+
{"adapter_name" => self.class.name}.freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Local model metadata only. Missing capacity is permitted.
|
|
20
|
+
# @api public
|
|
21
|
+
def input_budget(model_config)
|
|
22
|
+
nil
|
|
35
23
|
end
|
|
36
24
|
|
|
37
|
-
# Performs a blocking streaming LLM completion.
|
|
38
|
-
#
|
|
39
|
-
# @param chat [Object] the configured/materialized chat runtime object
|
|
40
|
-
# @param message [String, nil] user message, or nil to continue without adding a new user turn
|
|
41
|
-
# @param config [Hash] invocation config
|
|
42
|
-
# @yield [chunk] streaming chunk from the LLM
|
|
43
|
-
# @return [Object] LLM response object
|
|
44
|
-
# @raise [NotImplementedError]
|
|
45
25
|
# @api public
|
|
46
|
-
def
|
|
47
|
-
|
|
26
|
+
def complete(request, cancellation_token: nil)
|
|
27
|
+
validate_request!(request, cancellation_token)
|
|
28
|
+
response = perform_complete(request, cancellation_token: cancellation_token)
|
|
29
|
+
cancellation_token&.raise_if_cancelled!
|
|
30
|
+
validate_response!(response)
|
|
48
31
|
end
|
|
49
32
|
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
complete(chat, message, config: config)
|
|
33
|
+
# Notifications run on the caller thread. AsyncClient supplies an internal
|
|
34
|
+
# event sink; application callbacks remain with their owning domain.
|
|
35
|
+
# @api public
|
|
36
|
+
def stream(request, cancellation_token: nil)
|
|
37
|
+
raise ArgumentError, "stream requires a block" unless block_given?
|
|
38
|
+
validate_request!(request, cancellation_token)
|
|
39
|
+
response = perform_stream(request, cancellation_token: cancellation_token) do |chunk|
|
|
40
|
+
cancellation_token&.raise_if_cancelled!
|
|
41
|
+
raise InvalidResultError, "stream must yield StreamChunk values" unless chunk.is_a?(StreamChunk)
|
|
42
|
+
yield chunk
|
|
61
43
|
end
|
|
44
|
+
cancellation_token&.raise_if_cancelled!
|
|
45
|
+
validate_response!(response)
|
|
62
46
|
end
|
|
63
47
|
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
# The block is invoked on an OffloadPool worker thread. Agent code must pass
|
|
67
|
-
# only a lightweight internal sink that posts a value to EventLoop;
|
|
68
|
-
# application callbacks must never be passed directly to this method.
|
|
69
|
-
#
|
|
70
|
-
# @yield [chunk] streaming chunk on the worker thread
|
|
71
|
-
# @return [Phronomy::TaskResult] caller-facing completion handle
|
|
72
|
-
# @api private
|
|
73
|
-
def stream_async(chat, message, config: {}, pool: default_pool, &block)
|
|
74
|
-
raise ArgumentError, "stream_async requires a block" unless block
|
|
48
|
+
protected
|
|
75
49
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
50
|
+
# @api public
|
|
51
|
+
def perform_complete(request, cancellation_token:)
|
|
52
|
+
raise NotImplementedError, "#{self.class}#perform_complete is not implemented"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @api public
|
|
56
|
+
def perform_stream(request, cancellation_token:, &block)
|
|
57
|
+
raise NotImplementedError, "#{self.class}#perform_stream is not implemented"
|
|
83
58
|
end
|
|
84
59
|
|
|
85
60
|
private
|
|
86
61
|
|
|
87
|
-
def
|
|
88
|
-
|
|
62
|
+
def validate_request!(request, token)
|
|
63
|
+
token&.raise_if_cancelled!
|
|
64
|
+
raise ArgumentError, "LLM operation requires a Request" unless request.is_a?(Request)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate_response!(response)
|
|
68
|
+
raise InvalidResultError, "LLM operation must return a Response" unless response.is_a?(Response)
|
|
69
|
+
response
|
|
89
70
|
end
|
|
90
71
|
end
|
|
91
72
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/values/immutable"
|
|
4
|
+
require_relative "../tool/schema"
|
|
5
|
+
require_relative "../tool/call_request"
|
|
6
|
+
|
|
7
|
+
module Phronomy
|
|
8
|
+
module LLMAdapter
|
|
9
|
+
# Provider-independent conversation value, with no executable capabilities.
|
|
10
|
+
# @api public
|
|
11
|
+
class Message
|
|
12
|
+
attr_reader :role, :content, :tool_calls, :tool_call_id, :metadata
|
|
13
|
+
|
|
14
|
+
# @api public
|
|
15
|
+
def initialize(role:, content: nil, tool_calls: [], tool_call_id: nil, metadata: {})
|
|
16
|
+
unless role.respond_to?(:to_sym) && %i[system user assistant tool].include?(role.to_sym)
|
|
17
|
+
raise ArgumentError, "invalid LLM message role"
|
|
18
|
+
end
|
|
19
|
+
unless content.nil? || content.is_a?(String) || content.is_a?(Array) || content.is_a?(Hash)
|
|
20
|
+
raise ArgumentError, "LLM content must be text, structured JSON or nil"
|
|
21
|
+
end
|
|
22
|
+
unless tool_calls.is_a?(Array) && tool_calls.all? { |call| call.is_a?(Phronomy::Tool::CallRequest) }
|
|
23
|
+
raise ArgumentError, "LLM Tool calls must be Tool::CallRequest values"
|
|
24
|
+
end
|
|
25
|
+
ids = tool_calls.map(&:id)
|
|
26
|
+
raise ArgumentError, "duplicate LLM Tool call id" unless ids.uniq == ids
|
|
27
|
+
raise ArgumentError, "only assistant messages can request Tools" if !tool_calls.empty? && role.to_sym != :assistant
|
|
28
|
+
if role.to_sym == :tool && (!tool_call_id.is_a?(String) || tool_call_id.empty?)
|
|
29
|
+
raise ArgumentError, "Tool message requires tool_call_id"
|
|
30
|
+
end
|
|
31
|
+
unless tool_call_id.nil? || tool_call_id.is_a?(String)
|
|
32
|
+
raise ArgumentError, "tool_call_id must be a String"
|
|
33
|
+
end
|
|
34
|
+
raise ArgumentError, "LLM metadata must be a Hash" unless metadata.is_a?(Hash)
|
|
35
|
+
Phronomy::Values::Immutable.validate_canonical_json!(content, label: "LLM content")
|
|
36
|
+
Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "LLM metadata")
|
|
37
|
+
@role = role.to_sym
|
|
38
|
+
@content = Phronomy::Values::Immutable.copy(content)
|
|
39
|
+
@tool_calls = tool_calls.dup.freeze
|
|
40
|
+
@tool_call_id = tool_call_id&.dup&.freeze
|
|
41
|
+
@metadata = Phronomy::Values::Immutable.copy(metadata)
|
|
42
|
+
freeze
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @api public
|
|
46
|
+
def tool_call?
|
|
47
|
+
!tool_calls.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @api public
|
|
51
|
+
def content_present?
|
|
52
|
+
!content.nil? && !content.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @api public
|
|
56
|
+
def to_h
|
|
57
|
+
{"role" => role.to_s, "content" => content, "tool_calls" => tool_calls.map(&:to_h),
|
|
58
|
+
"tool_call_id" => tool_call_id, "metadata" => metadata}.compact
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|