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,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "message"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module LLMAdapter
|
|
7
|
+
# Immutable input for one model turn, including inert Tool definitions.
|
|
8
|
+
# @api public
|
|
9
|
+
class Request
|
|
10
|
+
attr_reader :model_config, :system, :messages, :tools, :message
|
|
11
|
+
|
|
12
|
+
# @api public
|
|
13
|
+
def initialize(model_config: {}, system: nil, messages: [], tools: [], message: nil)
|
|
14
|
+
if !(model_config.is_a?(Hash) && (system.nil? || system.is_a?(String)) && (message.nil? || message.is_a?(String)))
|
|
15
|
+
raise ArgumentError, "invalid LLM request configuration or text"
|
|
16
|
+
end
|
|
17
|
+
unless messages.is_a?(Array) && messages.all? { |item| item.is_a?(Message) }
|
|
18
|
+
raise ArgumentError, "LLM request messages must be Message values"
|
|
19
|
+
end
|
|
20
|
+
unless tools.is_a?(Array) && tools.all? { |tool| tool.is_a?(Hash) }
|
|
21
|
+
raise ArgumentError, "LLM request tools must be definition Hashes"
|
|
22
|
+
end
|
|
23
|
+
normalized_tools = tools.map do |definition|
|
|
24
|
+
definition = normalize_keys(definition)
|
|
25
|
+
name = definition["name"]
|
|
26
|
+
unless name.is_a?(String) && !name.empty? && definition["description"].is_a?(String)
|
|
27
|
+
raise ArgumentError, "Tool definition requires name and description"
|
|
28
|
+
end
|
|
29
|
+
if definition.key?("provider_options") && !definition["provider_options"].is_a?(Hash)
|
|
30
|
+
raise ArgumentError, "Tool provider_options must be a Hash"
|
|
31
|
+
end
|
|
32
|
+
Phronomy::Tool::Schema.new(definition.fetch("parameters_schema"))
|
|
33
|
+
Phronomy::Values::Immutable.validate_canonical_json!(definition, label: "Tool definition")
|
|
34
|
+
definition
|
|
35
|
+
end
|
|
36
|
+
names = normalized_tools.map { |tool| tool.fetch("name") }
|
|
37
|
+
raise ArgumentError, "duplicate Tool definition name" unless names.uniq == names
|
|
38
|
+
config = normalize_keys(model_config)
|
|
39
|
+
Phronomy::Values::Immutable.validate_canonical_json!(config, label: "LLM model configuration")
|
|
40
|
+
max = config["max_output_tokens"]
|
|
41
|
+
if !max.nil? && (!max.is_a?(Integer) || max <= 0)
|
|
42
|
+
raise ArgumentError, "max_output_tokens must be a positive Integer"
|
|
43
|
+
end
|
|
44
|
+
%w[model provider].each do |key|
|
|
45
|
+
value = config[key]
|
|
46
|
+
if !(value.nil? || (value.is_a?(String) && !value.empty?))
|
|
47
|
+
raise ArgumentError, "#{key} must be a non-empty String or nil"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
%w[assume_model_exists cache_instructions].each do |key|
|
|
51
|
+
raise ArgumentError, "#{key} must be boolean or nil" unless [true, false, nil].include?(config[key])
|
|
52
|
+
end
|
|
53
|
+
temperature = config["temperature"]
|
|
54
|
+
if !(temperature.nil? || (temperature.is_a?(Numeric) && temperature.finite?))
|
|
55
|
+
raise ArgumentError, "temperature must be a finite number or nil"
|
|
56
|
+
end
|
|
57
|
+
@model_config = Phronomy::Values::Immutable.copy(config)
|
|
58
|
+
@system = system&.dup&.freeze
|
|
59
|
+
@message = message&.dup&.freeze
|
|
60
|
+
@messages = messages.dup.freeze
|
|
61
|
+
@tools = Phronomy::Values::Immutable.copy(normalized_tools)
|
|
62
|
+
freeze
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def normalize_keys(value)
|
|
68
|
+
value.each_with_object({}) do |(key, child), result|
|
|
69
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
70
|
+
raise ArgumentError, "LLM configuration/definition keys must be Strings or Symbols"
|
|
71
|
+
end
|
|
72
|
+
name = key.to_s
|
|
73
|
+
raise ArgumentError, "duplicate LLM configuration/definition key: #{name}" if result.key?(name)
|
|
74
|
+
result[name] = child
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "message"
|
|
4
|
+
require_relative "token_usage"
|
|
5
|
+
|
|
6
|
+
module Phronomy
|
|
7
|
+
module LLMAdapter
|
|
8
|
+
# Completed assistant turn. Tool requests do not authorize execution.
|
|
9
|
+
# @api public
|
|
10
|
+
class Response < Message
|
|
11
|
+
attr_reader :usage
|
|
12
|
+
|
|
13
|
+
# @api public
|
|
14
|
+
def initialize(content: nil, tool_calls: [], usage: Phronomy::LLMAdapter::TokenUsage.new, metadata: {}, role: :assistant)
|
|
15
|
+
raise ArgumentError, "LLM response must be an assistant message" unless role == :assistant || role == "assistant"
|
|
16
|
+
raise ArgumentError, "LLM usage must be LLMAdapter::TokenUsage" unless usage.is_a?(Phronomy::LLMAdapter::TokenUsage)
|
|
17
|
+
@usage = usage
|
|
18
|
+
super(role: :assistant, content: content, tool_calls: tool_calls, metadata: metadata)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @api public
|
|
22
|
+
def self.from_h(value)
|
|
23
|
+
source = value.transform_keys(&:to_s)
|
|
24
|
+
new(role: source.fetch("role", "assistant"), content: source["content"],
|
|
25
|
+
tool_calls: source.fetch("tool_calls", []).map { |call| Phronomy::Tool::CallRequest.from_h(call) },
|
|
26
|
+
usage: Phronomy::LLMAdapter::TokenUsage.new(**source.fetch("usage", {}).transform_keys(&:to_sym)),
|
|
27
|
+
metadata: source.fetch("metadata", {}))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @api public
|
|
31
|
+
def to_h
|
|
32
|
+
super.merge("usage" => usage.to_h.transform_keys(&:to_s))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module LLMAdapter
|
|
5
|
+
# Intermediate text notification; the final Response is authoritative.
|
|
6
|
+
# @api public
|
|
7
|
+
class StreamChunk
|
|
8
|
+
attr_reader :content
|
|
9
|
+
|
|
10
|
+
# @api public
|
|
11
|
+
def initialize(content:)
|
|
12
|
+
raise ArgumentError, "stream content must be a String or nil" unless content.nil? || content.is_a?(String)
|
|
13
|
+
@content = content&.dup&.freeze
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module LLMAdapter
|
|
5
|
+
# Immutable usage counts. An omitted count remains nil; zero is a reported
|
|
6
|
+
# measurement. SDK field translation belongs to the backend.
|
|
7
|
+
# @api public
|
|
8
|
+
class TokenUsage
|
|
9
|
+
attr_reader :input, :output, :cached, :cache_creation
|
|
10
|
+
|
|
11
|
+
def initialize(input: nil, output: nil, cached: nil, cache_creation: nil)
|
|
12
|
+
[input, output, cached, cache_creation].each do |value|
|
|
13
|
+
if !(value.nil? || (value.is_a?(Integer) && value >= 0))
|
|
14
|
+
raise ArgumentError, "token usage must be non-negative Integers or nil"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
@input = input
|
|
18
|
+
@output = output
|
|
19
|
+
@cached = cached
|
|
20
|
+
@cache_creation = cache_creation
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns a zero-valued TokenUsage suitable as an accumulator seed.
|
|
25
|
+
def self.zero
|
|
26
|
+
new(input: 0, output: 0, cached: 0, cache_creation: 0)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Adds two TokenUsage instances. nil fields are treated as 0 so that
|
|
30
|
+
# partially-reported usage accumulates correctly.
|
|
31
|
+
# When other is nil, returns self unchanged.
|
|
32
|
+
def +(other)
|
|
33
|
+
return self if other.nil?
|
|
34
|
+
|
|
35
|
+
self.class.new(
|
|
36
|
+
input: _add(input, other.input),
|
|
37
|
+
output: _add(output, other.output),
|
|
38
|
+
cached: _add(cached, other.cached),
|
|
39
|
+
cache_creation: _add(cache_creation, other.cache_creation)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def ==(other)
|
|
44
|
+
other.is_a?(TokenUsage) &&
|
|
45
|
+
input == other.input &&
|
|
46
|
+
output == other.output &&
|
|
47
|
+
cached == other.cached &&
|
|
48
|
+
cache_creation == other.cache_creation
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_h
|
|
52
|
+
{input: input, output: output, cached: cached, cache_creation: cache_creation}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def _add(a, b)
|
|
58
|
+
return nil if a.nil? && b.nil?
|
|
59
|
+
|
|
60
|
+
(a || 0) + (b || 0)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/phronomy/llm_adapter.rb
CHANGED
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# {LLMAdapter::Base#stream}. Those methods receive Phronomy's currently
|
|
8
|
-
# materialized chat/runtime object and perform the provider call. Phronomy owns
|
|
9
|
-
# the async/offload bridge around that synchronous contract.
|
|
10
|
-
#
|
|
11
|
-
# This SPI does not by itself make the complete input-materialization pipeline
|
|
12
|
-
# provider-neutral: the current Agent pipeline still materializes the canonical
|
|
13
|
-
# LLM input through RubyLLM-specific runtime objects before invoking the adapter.
|
|
14
|
-
#
|
|
15
|
-
# @example Configuring a custom adapter
|
|
16
|
-
# Phronomy.configure do |c|
|
|
17
|
-
# c.llm_adapter = MyCustomAdapter.new
|
|
18
|
-
# end
|
|
19
|
-
#
|
|
4
|
+
# LLM domain operations and public request, response, usage and failure values.
|
|
5
|
+
# A backend implements Base's protected hooks. SDK objects remain inside the
|
|
6
|
+
# backend; AsyncClient executes the entire synchronous operation on a worker.
|
|
20
7
|
# @api public
|
|
21
8
|
module LLMAdapter
|
|
22
9
|
end
|
|
@@ -2,20 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module LlmContextWindow
|
|
5
|
-
# Immutable arithmetic
|
|
6
|
-
# Model-registry lookup belongs to Agent::TokenBudgetResolver.
|
|
5
|
+
# Immutable input-budget arithmetic. RubyLLM owns model capabilities.
|
|
7
6
|
class TokenBudget
|
|
8
|
-
attr_reader :
|
|
7
|
+
attr_reader :max_input_tokens
|
|
9
8
|
|
|
10
9
|
# @api private
|
|
11
|
-
def initialize(
|
|
12
|
-
@
|
|
13
|
-
|
|
10
|
+
def initialize(max_input_tokens:)
|
|
11
|
+
@max_input_tokens = Integer(max_input_tokens)
|
|
12
|
+
raise ArgumentError, "max_input_tokens must be positive" unless @max_input_tokens.positive?
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
# @api private
|
|
17
16
|
def effective_input_limit
|
|
18
|
-
|
|
17
|
+
@max_input_tokens
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
# @api private
|
|
@@ -2,18 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module MultiAgent
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# Environment-local admission for one synchronous coordination call per owner
|
|
6
|
+
# (a Handoff main Agent or TeamCoordinator). This does not own durable runs
|
|
7
|
+
# or active-agent mutation; those remain in their existing domains.
|
|
8
|
+
# @api private
|
|
7
9
|
class AdmissionRegistry
|
|
8
10
|
def initialize
|
|
9
11
|
@mutex = Mutex.new
|
|
10
12
|
@cond = ConditionVariable.new
|
|
11
13
|
@owners = {}
|
|
14
|
+
@draining = false
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def admit!(coordinator)
|
|
15
18
|
key = coordinator.object_id
|
|
16
19
|
@mutex.synchronize do
|
|
20
|
+
if @draining
|
|
21
|
+
raise Phronomy::RuntimeShutdownError,
|
|
22
|
+
"Runtime is shutting down or failed; new Multi-Agent turns are not accepted"
|
|
23
|
+
end
|
|
17
24
|
if @owners.key?(key)
|
|
18
25
|
raise Phronomy::HandoffError,
|
|
19
26
|
"a Multi-Agent turn is already active for this main Agent instance"
|
|
@@ -36,6 +43,12 @@ module Phronomy
|
|
|
36
43
|
@mutex.synchronize { @owners.empty? }
|
|
37
44
|
end
|
|
38
45
|
|
|
46
|
+
# Close admission atomically with respect to admit!, without waiting for
|
|
47
|
+
# admitted callers or calling back into Runtime. Repeated closure is safe.
|
|
48
|
+
def begin_draining
|
|
49
|
+
@mutex.synchronize { @draining = true }
|
|
50
|
+
end
|
|
51
|
+
|
|
39
52
|
def wait_until_idle(deadline)
|
|
40
53
|
@mutex.synchronize do
|
|
41
54
|
until @owners.empty?
|
|
@@ -1,127 +1,151 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "digest"
|
|
4
4
|
|
|
5
5
|
module Phronomy
|
|
6
6
|
module MultiAgent
|
|
7
|
-
#
|
|
7
|
+
# Owns slot reservations and import progress. The Agent participant stores
|
|
8
|
+
# only this binding's opaque state reference alongside its own transition.
|
|
8
9
|
# @api private
|
|
9
10
|
class DurableSubagentCoordinator
|
|
10
|
-
|
|
11
|
+
BINDING_KEY = "phronomy.subagent"
|
|
12
|
+
attr_reader :persistence
|
|
11
13
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
def initialize(agent_class:, persistence:)
|
|
15
|
+
@agent_class, @persistence = agent_class, persistence
|
|
16
|
+
@definitions = agent_class.registered_subagents
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def binding
|
|
20
|
+
Phronomy::Agent::ExecutionExtensionState.new(binding_key: BINDING_KEY, binding_version: 1)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def guard_change(change, scope)
|
|
24
|
+
snapshot = state_for(change.extension)
|
|
25
|
+
ids = [change.agent_id] + snapshot.fetch("children").filter_map do |child|
|
|
26
|
+
child.fetch("agent_id") if persistence.agent_store.exist?(child.fetch("agent_id"))
|
|
24
27
|
end
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
persistence.agent_store.guard_agents(scope, agent_ids: ids)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The referenced content is immutable and is part of Agent's commit proof.
|
|
32
|
+
def change_evidence(change, scope)
|
|
33
|
+
persistence.agent_store.retained_references(agent_id: change.agent_id, scope: scope).map(&:to_h)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def commit(change)
|
|
37
|
+
persistence.coordinator.atomic do |scope|
|
|
38
|
+
guard_change(change, scope)
|
|
39
|
+
change.prepare_in(scope)
|
|
40
|
+
snapshot = state_for(change.extension)
|
|
41
|
+
children = snapshot.fetch("children").map(&:dup)
|
|
42
|
+
cancelled = snapshot["cancel_requested"] || change.cancel_requested ||
|
|
43
|
+
persistence.agent_store.cancellation_requested?(agent_id: change.agent_id, execution_id: change.execution_id, scope: scope)
|
|
44
|
+
children.each do |child|
|
|
45
|
+
observed = persistence.agent_store.observe_execution(agent_id: child.fetch("agent_id"), execution_id: child.fetch("execution_id"), scope: scope)
|
|
46
|
+
# This is import progress, not a replicated child execution state.
|
|
47
|
+
child["imported"] = true if observed.terminal? && change.kind == :dispatch && change.operations.empty?
|
|
48
|
+
end
|
|
49
|
+
change.operations.each do |operation|
|
|
50
|
+
entry = @definitions.find { |name, _| "dispatch_to_#{name}" == operation.name }
|
|
51
|
+
next unless entry
|
|
52
|
+
next if children.any? { |child| child.fetch("slot") == operation.invocation_id }
|
|
53
|
+
name, settings = entry
|
|
54
|
+
digest = Digest::SHA256.hexdigest(Phronomy::CanonicalJSON.dump(
|
|
55
|
+
["phronomy-subagent-v1", change.agent_id, change.execution_id, operation.invocation_id]
|
|
56
|
+
))
|
|
57
|
+
knowledge = settings.fetch(:inherit_knowledge) ? persistence.agent_store.knowledge_snapshot(agent_id: change.agent_id, scope: scope) : []
|
|
58
|
+
children << {"slot" => operation.invocation_id, "name" => name.to_s,
|
|
59
|
+
"definition" => settings.fetch(:agent_class).agent_definition.transform_keys(&:to_s),
|
|
60
|
+
"agent_id" => "subagent-#{digest}", "execution_id" => "subagent-execution-#{digest}",
|
|
61
|
+
"input" => operation.arguments.fetch("input"), "durable_context" => change.durable_context,
|
|
62
|
+
"knowledge" => knowledge.map { |item| {"content" => item.content, "metadata" => item.metadata} },
|
|
63
|
+
"on_error" => settings.fetch(:on_error).to_s, "imported" => false}
|
|
64
|
+
end
|
|
65
|
+
unless children.empty?
|
|
66
|
+
persistence.agent_store.retain(Phronomy::Agent::Retention.new(agent_id: change.agent_id,
|
|
67
|
+
execution_id: change.execution_id, owner_key: "subagent:#{change.execution_id}"), scope: scope)
|
|
68
|
+
end
|
|
69
|
+
unresolved = children.any? do |child|
|
|
70
|
+
observed = persistence.agent_store.observe_execution(agent_id: child.fetch("agent_id"), execution_id: child.fetch("execution_id"), scope: scope)
|
|
71
|
+
observed.active? || (observed.absent? && !cancelled)
|
|
72
|
+
end
|
|
73
|
+
persistence.participate(scope) do |records|
|
|
74
|
+
ref = records.contents.put_json(snapshot.merge("children" => children, "cancel_requested" => !!cancelled))
|
|
75
|
+
state = Phronomy::Agent::ExecutionExtensionState.new(binding_key: BINDING_KEY, binding_version: 1, state_ref: ref)
|
|
76
|
+
change.commit_in(scope, state: state,
|
|
77
|
+
pending: change.kind == :terminal && unresolved && (change.cancel_requested || change.recovery_required))
|
|
78
|
+
end
|
|
42
79
|
end
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def state_for(extension)
|
|
83
|
+
extension&.state_ref ? persistence.contents.fetch_json(extension.state_ref) : {"children" => [], "cancel_requested" => false}
|
|
46
84
|
end
|
|
47
85
|
|
|
48
86
|
def self.start(parent:, tool_invocation_id:, parent_execution_id:, config:)
|
|
49
|
-
runtime = Phronomy::Runtime.instance
|
|
50
87
|
completion = Phronomy::TaskResult.deferred(name: "durable-subagent:#{tool_invocation_id}")
|
|
51
|
-
preparation =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
88
|
+
preparation = Phronomy::Execution.submit(on_full: :raise) do
|
|
89
|
+
store = parent.coordination_store
|
|
90
|
+
extension = parent.persistence.execution_extension(agent_id: parent.agent_id, execution_id: parent_execution_id, binding_key: BINDING_KEY)
|
|
91
|
+
raise Phronomy::ExecutionRehydrationRequiredError, "Missing subagent reservation state" unless extension&.state_ref
|
|
92
|
+
snapshot = store.contents.fetch_json(extension.state_ref)
|
|
55
93
|
child = snapshot.fetch("children").find { |entry| entry.fetch("slot") == tool_invocation_id }
|
|
56
94
|
raise Phronomy::ExecutionRehydrationRequiredError, "Missing reserved child slot" unless child
|
|
57
|
-
|
|
58
|
-
unless
|
|
59
|
-
raise Phronomy::ConfigurationError, "Registered child definition changed
|
|
95
|
+
settings = parent.class.registered_subagents.find { |name, _| name.to_s == child.fetch("name") }&.last
|
|
96
|
+
unless settings && settings.fetch(:agent_class).agent_definition.transform_keys(&:to_s) == child.fetch("definition")
|
|
97
|
+
raise Phronomy::ConfigurationError, "Registered child definition changed"
|
|
60
98
|
end
|
|
61
|
-
klass =
|
|
99
|
+
klass = settings.fetch(:agent_class)
|
|
62
100
|
id = child.fetch("agent_id")
|
|
101
|
+
retention = Phronomy::Agent::Retention.new(agent_id: id, execution_id: child.fetch("execution_id"), owner_key: "subagent:#{parent_execution_id}")
|
|
63
102
|
agent = klass.get(id)
|
|
64
103
|
unless agent
|
|
65
|
-
|
|
66
|
-
parent.persistence.
|
|
67
|
-
exists = true
|
|
68
|
-
rescue Phronomy::Persistence::NotFoundError
|
|
69
|
-
exists = false
|
|
70
|
-
end
|
|
71
|
-
listener = parent.send(:_phronomy_event_listener)
|
|
72
|
-
agent = if exists
|
|
73
|
-
klass.load(id, persistence: parent.persistence, on_event: listener)
|
|
104
|
+
if parent.persistence.exist?(id)
|
|
105
|
+
agent = klass.load(id, persistence: parent.persistence, on_event: parent.event_listener)
|
|
74
106
|
else
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
content_format: :text, metadata: item.fetch("metadata"))
|
|
80
|
-
end)
|
|
81
|
-
klass.create(agent_id: id, persistence: parent.persistence,
|
|
82
|
-
context: context, on_event: listener)
|
|
107
|
+
raise Phronomy::CancellationError, "Parent reservation was cancelled" if snapshot["cancel_requested"]
|
|
108
|
+
knowledge = child.fetch("knowledge").map { |item| Phronomy::Agent::KnowledgeItem.new(content: item.fetch("content"), metadata: item.fetch("metadata")) }
|
|
109
|
+
agent = klass.create(agent_id: id, persistence: parent.persistence, knowledge: knowledge,
|
|
110
|
+
retention: retention, on_event: parent.event_listener)
|
|
83
111
|
end
|
|
84
112
|
end
|
|
85
|
-
unless agent.persistence.equal?(parent.persistence)
|
|
86
|
-
|
|
87
|
-
end
|
|
88
|
-
durable_context = child["durable_context_ref"] && parent.persistence.contents.fetch_json(child["durable_context_ref"])
|
|
89
|
-
[child, agent, parent.persistence.contents.fetch_text(child.fetch("input_ref")), durable_context]
|
|
113
|
+
raise Phronomy::ConfigurationError, "Child Persistence mismatch" unless agent.persistence.equal?(parent.persistence)
|
|
114
|
+
[child, agent, store]
|
|
90
115
|
end
|
|
91
116
|
preparation.on_complete do |prepared, failure|
|
|
92
117
|
if failure
|
|
93
|
-
completion.fail(Phronomy::ExecutionRehydrationRequiredError.new("Child reservation requires recovery: #{failure.message}"))
|
|
118
|
+
completion.fail(failure.is_a?(Phronomy::CancellationError) ? failure : Phronomy::ExecutionRehydrationRequiredError.new("Child reservation requires recovery: #{failure.message}"))
|
|
94
119
|
next
|
|
95
120
|
end
|
|
96
|
-
child, agent,
|
|
121
|
+
child, agent, store = prepared
|
|
122
|
+
owner = {"kind" => "subagent", "parent_execution_id" => parent_execution_id,
|
|
123
|
+
"parent_agent_id" => parent.agent_id, "slot" => tool_invocation_id}
|
|
97
124
|
child_config = {cancellation_token: config[:cancellation_token] || Phronomy::Concurrency::CancellationToken.new,
|
|
98
125
|
invocation_context: config[:invocation_context],
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
execution_id: child.fetch("execution_id"), input: input, config: child_config)
|
|
104
|
-
source.on_complete do |result, error|
|
|
126
|
+
phronomy_admission: ReservedChildAdmission.new(persistence: store, owner: owner)}.compact
|
|
127
|
+
child_config[:durable_context] = child["durable_context"] if child["durable_context"]
|
|
128
|
+
reservation = Phronomy::Agent::ReservedExecution.new(agent_id: agent.agent_id, execution_id: child.fetch("execution_id"), correlation: owner)
|
|
129
|
+
agent.start_reserved_async(reservation: reservation, input: child.fetch("input"), config: child_config).on_complete do |result, error|
|
|
105
130
|
if error
|
|
106
|
-
|
|
107
|
-
completion.fail(error)
|
|
108
|
-
else
|
|
109
|
-
completion.fail(Phronomy::ExecutionRehydrationRequiredError.new(
|
|
110
|
-
"Child #{child.fetch("execution_id")} is unfinished: #{error.message}"
|
|
111
|
-
))
|
|
112
|
-
end
|
|
131
|
+
completion.fail(error.is_a?(Phronomy::CancellationError) ? error : Phronomy::ExecutionRehydrationRequiredError.new("Child #{reservation.execution_id} is unfinished: #{error.message}"))
|
|
113
132
|
elsif result[:error]
|
|
114
|
-
if child.fetch("on_error") == "skip"
|
|
133
|
+
if child.fetch("on_error") == "skip" && result[:status] != :cancelled
|
|
115
134
|
completion.complete(nil)
|
|
116
135
|
else
|
|
117
|
-
|
|
136
|
+
observed = Phronomy::Agent::ExecutionObservation.from_result(result)
|
|
137
|
+
begin
|
|
138
|
+
observed.value!
|
|
139
|
+
rescue => failure
|
|
140
|
+
completion.fail(failure)
|
|
141
|
+
end
|
|
118
142
|
end
|
|
119
143
|
else
|
|
120
144
|
completion.complete(result[:output])
|
|
121
145
|
end
|
|
122
146
|
end
|
|
123
147
|
rescue => error
|
|
124
|
-
completion.fail(
|
|
148
|
+
completion.fail(error)
|
|
125
149
|
end
|
|
126
150
|
completion
|
|
127
151
|
rescue => error
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
# Process-local connection selected by composition. MultiAgent owns the
|
|
6
|
+
# admission and Team identity rules; the environment connects their
|
|
7
|
+
# registries to shutdown and submits work to the originating executor.
|
|
8
|
+
# Environments and registries are never durable execution authority.
|
|
9
|
+
# @api private
|
|
10
|
+
class ExecutionEnvironment
|
|
11
|
+
class << self
|
|
12
|
+
def install_provider(provider)
|
|
13
|
+
raise ArgumentError, "MultiAgent execution provider must be callable" unless provider.respond_to?(:call)
|
|
14
|
+
@provider = provider
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def current
|
|
18
|
+
@provider&.call || raise(Phronomy::ConfigurationError, "MultiAgent execution environment has not been configured")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def admissions = raise(NotImplementedError)
|
|
23
|
+
def ownership = raise(NotImplementedError)
|
|
24
|
+
def existing_ownership = raise(NotImplementedError)
|
|
25
|
+
def current? = raise(NotImplementedError)
|
|
26
|
+
def submit(**options, &operation) = raise(NotImplementedError)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
5
|
module Phronomy
|
|
6
|
-
module
|
|
6
|
+
module MultiAgent
|
|
7
7
|
# Application-defined semantic edge for transferring active responsibility
|
|
8
8
|
# from one live Agent instance to another.
|
|
9
9
|
class Handoff
|
|
@@ -19,7 +19,7 @@ module Phronomy
|
|
|
19
19
|
raise ArgumentError, "Handoff source_agent and target_agent must be different instances"
|
|
20
20
|
end
|
|
21
21
|
unless policy.is_a?(HandoffPolicy)
|
|
22
|
-
raise ArgumentError, "policy must be a Phronomy::
|
|
22
|
+
raise ArgumentError, "policy must be a Phronomy::MultiAgent::HandoffPolicy"
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
@source_agent = source_agent
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module MultiAgent
|
|
5
5
|
class HandoffCapabilityFactory
|
|
6
6
|
Binding = Data.define(:handoff, :tool_class, :tool_name) do
|
|
7
|
+
def source_agent_id = handoff.source_agent.agent_id
|
|
8
|
+
|
|
9
|
+
def request(arguments:, llm_call_id:, tool_call_id:)
|
|
10
|
+
args = arguments.transform_keys(&:to_sym)
|
|
11
|
+
allowed = [:responsibility] + handoff.policy.selectable_categories.map { |category| :"include_#{category}" }
|
|
12
|
+
raise ArgumentError, "Unknown Handoff argument" unless (args.keys - allowed).empty?
|
|
13
|
+
selection = HandoffPolicy::CATEGORIES.to_h do |category|
|
|
14
|
+
value = if handoff.policy.selectable?(category)
|
|
15
|
+
args.fetch(:"include_#{category}") { handoff.policy.default_include?(category) }
|
|
16
|
+
else
|
|
17
|
+
handoff.policy.required?(category)
|
|
18
|
+
end
|
|
19
|
+
[category, value]
|
|
20
|
+
end
|
|
21
|
+
Phronomy::Agent::ControlRequest.new(target_agent_id: handoff.target_agent.agent_id,
|
|
22
|
+
responsibility: args.fetch(:responsibility), selection: selection,
|
|
23
|
+
llm_call_id: llm_call_id, tool_call_id: tool_call_id)
|
|
24
|
+
end
|
|
25
|
+
|
|
7
26
|
def initialize(handoff:, tool_class:, tool_name:)
|
|
8
27
|
super(handoff: handoff, tool_class: tool_class, tool_name: tool_name.to_s.freeze)
|
|
9
28
|
freeze
|
|
@@ -16,7 +35,7 @@ module Phronomy
|
|
|
16
35
|
description = handoff.description
|
|
17
36
|
policy = handoff.policy
|
|
18
37
|
|
|
19
|
-
klass = Class.new(Phronomy::
|
|
38
|
+
klass = Class.new(Phronomy::Tool::Base) do
|
|
20
39
|
self.tool_name(tool_name)
|
|
21
40
|
self.description(description)
|
|
22
41
|
execution_mode :cooperative
|