phronomy 0.27.0 → 0.29.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 +52 -0
- data/CONTRIBUTING.md +8 -8
- data/README.md +28 -7
- data/benchmark/bench_agent_invoke.rb +25 -50
- data/benchmark/bench_context_assembler.rb +14 -11
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +1 -1
- data/docs/application-recipes.md +229 -0
- data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
- data/docs/architecture/agent-transition-ownership.md +13 -89
- data/docs/architecture/backend-groups.md +152 -0
- data/docs/architecture/before-llm-input.md +3 -3
- data/docs/architecture/context-management.md +1 -1
- data/docs/architecture/dx05-agent-scope.md +119 -0
- data/docs/architecture/dx05-workflow-task-state.md +134 -0
- data/docs/architecture/error-api-review.md +245 -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 +6 -0
- data/docs/architecture/multi-agent-handoff.md +6 -5
- data/docs/architecture/persistence.md +27 -9
- 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 +10 -1
- data/docs/architecture/remaining-refactoring-plan.md +16 -0
- data/docs/architecture/storage-execution-boundaries.md +54 -0
- data/docs/architecture/tool-schema-recording-gap.md +6 -0
- data/docs/architecture/tracing.md +1 -1
- data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
- data/docs/architecture.md +24 -11
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
- data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
- data/docs/decisions/032-storage-backend-composition.md +5 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
- data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
- data/docs/decisions/040-configuration-default-composition.md +12 -1
- data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
- data/docs/decisions/058-neutral-storage-primitives.md +5 -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 +18 -1
- data/docs/execution-boundaries-preview.md +45 -0
- data/docs/failure-reason-codes.md +45 -0
- data/docs/features.md +12 -4
- data/docs/getting-started.md +52 -48
- data/docs/migrations/0.22.md +2 -2
- data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
- data/docs/migrations/handoff-runner-multi-agent.md +2 -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/storage-async-client.md +103 -0
- data/docs/migrations/vector-async-clients.md +110 -0
- data/docs/ordered-event-delivery.md +54 -0
- data/docs/persistence-backends.md +38 -21
- data/docs/runtime-and-concurrency.md +26 -1
- data/docs/workflow-completion.md +92 -0
- data/gemfiles/http_minimum.gemfile +5 -0
- data/lib/phronomy/agent/admission.rb +78 -0
- data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
- data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
- data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
- data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
- data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
- data/lib/phronomy/agent/api/agent.rb +13 -0
- data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
- data/lib/phronomy/agent/async_event_api.rb +132 -103
- data/lib/phronomy/agent/base.rb +146 -113
- data/lib/phronomy/agent/before_llm_input.rb +70 -0
- data/lib/phronomy/agent/composition/run_once.rb +1 -1
- data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
- data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
- data/lib/phronomy/agent/control_request.rb +21 -0
- data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
- data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
- data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
- data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
- data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
- data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
- data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
- data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
- data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
- data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
- data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
- 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 +188 -0
- data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
- data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
- data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
- data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
- data/lib/phronomy/agent/knowledge_item.rb +13 -0
- data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
- data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
- data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
- data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
- data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
- data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
- data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
- data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
- data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
- data/lib/phronomy/agent/persistence/codec.rb +1 -21
- data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
- data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
- 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 +7 -2
- data/lib/phronomy/agent/persistence/watermark.rb +13 -11
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
- data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
- data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
- data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
- data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
- 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/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
- data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
- data/lib/phronomy/agent/settings.rb +47 -0
- data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
- data/lib/phronomy/agent/store.rb +253 -0
- data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
- data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
- 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/error.rb +65 -1
- data/lib/phronomy/configuration/runtime_settings.rb +49 -0
- data/lib/phronomy/content_store/base.rb +7 -2
- data/lib/phronomy/content_store/storage_schema.rb +1 -0
- data/lib/phronomy/content_store/stored_contents.rb +2 -1
- data/lib/phronomy/context/assembly.rb +195 -0
- data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
- data/lib/phronomy/context/candidate.rb +51 -0
- data/lib/phronomy/context/constraint.rb +47 -0
- data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
- data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
- data/lib/phronomy/{agent/context_assembly → 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_contract → context}/llm_input_build_context.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
- data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
- 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/authentication_error.rb +11 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
- data/lib/phronomy/embeddings/base.rb +43 -0
- data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
- data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
- data/lib/phronomy/embeddings/transport_error.rb +11 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
- data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
- data/lib/phronomy/engine/diagnostics.rb +1 -1
- data/lib/phronomy/engine/event_loop.rb +7 -7
- 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 +1 -6
- data/lib/phronomy/engine/fsm_session.rb +3 -3
- data/lib/phronomy/engine/metrics.rb +16 -14
- data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
- data/lib/phronomy/engine/runtime.rb +25 -12
- data/lib/phronomy/engine/terminal_decision.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +41 -0
- data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
- data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -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/{engine → execution}/execution.rb +61 -27
- data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
- data/lib/phronomy/execution/execution_receiver.rb +78 -0
- data/lib/phronomy/execution/execution_scope_state.rb +35 -0
- data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
- data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
- data/lib/phronomy/execution/invocation_controls.rb +73 -0
- data/lib/phronomy/execution/runnable.rb +24 -0
- data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
- data/lib/phronomy/execution/wait_policy.rb +24 -0
- data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
- data/lib/phronomy/generation/generator_verifier.rb +7 -7
- data/lib/phronomy/generation/low_confidence_error.rb +2 -2
- data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
- data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
- data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
- data/lib/phronomy/llm_adapter/base.rb +49 -68
- data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
- data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
- data/lib/phronomy/llm_adapter/message.rb +62 -0
- data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
- 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/multi_agent/admission_registry.rb +1 -8
- 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/handoff → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
- data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
- data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
- data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
- data/lib/phronomy/multi_agent/persistence/codec.rb +20 -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 +60 -44
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
- 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/multi_agent/shared_state.rb +2 -2
- data/lib/phronomy/multi_agent/store.rb +156 -0
- data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
- data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
- 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 +2 -2
- 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 +9 -4
- 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 +5 -1
- data/lib/phronomy/runtime_composition/configuration.rb +146 -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/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 +8 -2
- data/lib/phronomy/storage/condition_failed_error.rb +2 -2
- data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
- 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 +2 -2
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
- data/lib/phronomy/tool/authorization.rb +77 -0
- data/lib/phronomy/tool/base.rb +551 -9
- data/lib/phronomy/tool/call_request.rb +40 -0
- data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
- 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/tool/tool_executor.rb +58 -0
- 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 +36 -7
- data/lib/phronomy/tracing/observation.rb +37 -0
- data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
- 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/execution/workflow.rb +37 -9
- data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
- data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
- data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
- data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
- data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
- data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
- data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
- data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
- data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
- data/lib/phronomy.rb +63 -16
- data/scripts/api_snapshot.rb +47 -15
- data/scripts/check_readme_ruby.rb +24 -29
- data/scripts/check_readme_runnable.rb +92 -81
- 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/agent_not_found_error.rbs +6 -0
- data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -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/stream_callback_error.rbs +8 -0
- data/sig/phronomy/agent.rbs +29 -135
- 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 +23 -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 +1 -21
- data/sig/phronomy/filter/filter_block_error.rbs +6 -0
- data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
- data/sig/phronomy/handoff.rbs +10 -4
- data/sig/phronomy/integrations.rbs +14 -0
- 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/team_busy_error.rbs +8 -0
- data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
- data/sig/phronomy/multi_agent.rbs +6 -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 +1 -99
- 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.rbs → storage/contracts.rbs} +4 -18
- data/sig/phronomy/tool.rbs +48 -4
- 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 +15 -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 +5 -1
- data/sig/phronomy.rbs +4 -49
- metadata +365 -117
- 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 -548
- data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
- 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/phase_machine_builder.rb +0 -101
- data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
- data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
- data/lib/phronomy/agent/persistence/queries.rb +0 -61
- data/lib/phronomy/agent/selection/candidate.rb +0 -53
- data/lib/phronomy/agent/selection/constraint.rb +0 -49
- data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
- data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
- data/lib/phronomy/configuration/configuration.rb +0 -68
- data/lib/phronomy/configuration/global_configuration.rb +0 -26
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
- data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
- data/lib/phronomy/engine/execution_receiver.rb +0 -65
- data/lib/phronomy/engine/runnable.rb +0 -54
- data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
- data/lib/phronomy/llm_contract/token_usage.rb +0 -85
- data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
- data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
- data/lib/phronomy/persistence/api/persistence.rb +0 -140
- data/lib/phronomy/persistence_composition/repositories.rb +0 -77
- data/lib/phronomy/recovery/recovery.rb +0 -186
- data/lib/phronomy/tool/contract/tool_error.rb +0 -7
- 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/storage_contract/workflow_storage_schema.rb +0 -9
- data/sig/phronomy/execution_receiver.rbs +0 -34
- data/sig/phronomy/llm_adapter.rbs +0 -11
- data/sig/phronomy/runtime.rbs +0 -76
- /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
- /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
- /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class GeneratorVerifier
|
|
5
|
+
# Connects one reused Agent incarnation through its public operation and
|
|
6
|
+
# notification contracts. Terminal results belong to each returned TaskResult;
|
|
7
|
+
# only approval suspension needs the incarnation listener while that Task is
|
|
8
|
+
# still pending. Generation owns the request-to-Workflow correlation.
|
|
9
|
+
# @api private
|
|
10
|
+
class AgentOperation
|
|
11
|
+
def initialize(agent_class)
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@pending = []
|
|
14
|
+
@agent = agent_class.new(on_event: method(:receive_event))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def start(input, listener:)
|
|
18
|
+
request = Object.new
|
|
19
|
+
# Workflow entry calls are serial. Keep submitted requests until their
|
|
20
|
+
# own result settles: Agent admission itself may run after an earlier
|
|
21
|
+
# request completes, so a pending submission is not necessarily busy.
|
|
22
|
+
@mutex.synchronize { @pending << [request, listener] }
|
|
23
|
+
task = @agent.invoke_async(input)
|
|
24
|
+
task.on_complete do |result, error|
|
|
25
|
+
release(request)
|
|
26
|
+
# Handoff was not a generation completion in the event-based path.
|
|
27
|
+
next if !error && result.is_a?(Hash) && result[:handoff_request]
|
|
28
|
+
|
|
29
|
+
event = if error
|
|
30
|
+
type = if error.is_a?(Phronomy::TimeoutError)
|
|
31
|
+
:timeout
|
|
32
|
+
elsif error.is_a?(Phronomy::CancellationError)
|
|
33
|
+
:cancelled
|
|
34
|
+
else
|
|
35
|
+
:error
|
|
36
|
+
end
|
|
37
|
+
Phronomy::Agent::StreamEvent.new(type: type, payload: {error: error})
|
|
38
|
+
else
|
|
39
|
+
Phronomy::Agent::StreamEvent.new(type: :done, payload: result)
|
|
40
|
+
end
|
|
41
|
+
listener.call(event)
|
|
42
|
+
end
|
|
43
|
+
task
|
|
44
|
+
rescue
|
|
45
|
+
release(request)
|
|
46
|
+
raise
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def receive_event(event)
|
|
52
|
+
return unless event.type == :approval_required
|
|
53
|
+
|
|
54
|
+
listener = @mutex.synchronize { @pending.first&.last }
|
|
55
|
+
listener&.call(event)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def release(request)
|
|
59
|
+
@mutex.synchronize do
|
|
60
|
+
@pending.delete_if { |entry| entry.first.equal?(request) }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class GeneratorVerifier
|
|
5
|
+
# Boot supplies the default concrete parser. The generation algorithm owns
|
|
6
|
+
# its output interpretation and fallback, not the concrete parser selection.
|
|
7
|
+
# @api private
|
|
8
|
+
module DefaultParser
|
|
9
|
+
FACTORIES = {}
|
|
10
|
+
private_constant :FACTORIES
|
|
11
|
+
|
|
12
|
+
# Private one-time composition binding; not an application registry.
|
|
13
|
+
# @api private
|
|
14
|
+
def self.install_factory(factory)
|
|
15
|
+
raise ArgumentError, "factory must respond to call" unless factory.respond_to?(:call)
|
|
16
|
+
|
|
17
|
+
FACTORIES.replace(parser: factory).freeze
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @api private
|
|
22
|
+
def self.build
|
|
23
|
+
FACTORIES.fetch(:parser).call
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -71,7 +71,7 @@ module Phronomy
|
|
|
71
71
|
workflow_instance_id: next_state.workflow_instance_id,
|
|
72
72
|
request_id: request_id
|
|
73
73
|
)
|
|
74
|
-
@draft_agent.
|
|
74
|
+
@draft_agent.start(prompt, listener: listener)
|
|
75
75
|
next_state
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -86,7 +86,7 @@ module Phronomy
|
|
|
86
86
|
workflow_instance_id: next_state.workflow_instance_id,
|
|
87
87
|
request_id: request_id
|
|
88
88
|
)
|
|
89
|
-
@review_agent.
|
|
89
|
+
@review_agent.start(prompt, listener: listener)
|
|
90
90
|
next_state
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -20,7 +20,7 @@ module Phronomy
|
|
|
20
20
|
alias_method :trusted?, :trusted
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
private_constant :PipelineState, :WorkflowBuilder, :AgentResultReceiver
|
|
23
|
+
private_constant :PipelineState, :WorkflowBuilder, :AgentResultReceiver, :AgentOperation
|
|
24
24
|
|
|
25
25
|
def initialize(
|
|
26
26
|
draft_agent:,
|
|
@@ -80,8 +80,8 @@ module Phronomy
|
|
|
80
80
|
|
|
81
81
|
def build_workflow
|
|
82
82
|
WorkflowBuilder.new(
|
|
83
|
-
draft_agent: @draft_agent_class
|
|
84
|
-
review_agent: @review_agent_class
|
|
83
|
+
draft_agent: AgentOperation.new(@draft_agent_class),
|
|
84
|
+
review_agent: AgentOperation.new(@review_agent_class),
|
|
85
85
|
draft_prompt_builder: @draft_prompt_builder,
|
|
86
86
|
review_prompt_builder: @review_prompt_builder,
|
|
87
87
|
draft_result_parser: @draft_result_parser,
|
|
@@ -92,7 +92,7 @@ module Phronomy
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
def default_parse_draft(text)
|
|
95
|
-
|
|
95
|
+
default_parser.parse(text)
|
|
96
96
|
rescue Phronomy::ParseError
|
|
97
97
|
{
|
|
98
98
|
answer: text.to_s,
|
|
@@ -102,7 +102,7 @@ module Phronomy
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def default_parse_review(text)
|
|
105
|
-
|
|
105
|
+
default_parser.parse(text)
|
|
106
106
|
rescue Phronomy::ParseError
|
|
107
107
|
{
|
|
108
108
|
approved: false,
|
|
@@ -111,8 +111,8 @@ module Phronomy
|
|
|
111
111
|
}
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
-
def
|
|
115
|
-
@
|
|
114
|
+
def default_parser
|
|
115
|
+
@default_parser ||= DefaultParser.build
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
end
|
|
@@ -6,9 +6,9 @@ module Phronomy
|
|
|
6
6
|
class LowConfidenceError < Error
|
|
7
7
|
attr_reader :result
|
|
8
8
|
|
|
9
|
-
def initialize(result)
|
|
9
|
+
def initialize(result, code: nil)
|
|
10
10
|
@result = result
|
|
11
|
-
super("Answer confidence #{result.confidence} is below the required threshold")
|
|
11
|
+
super("Answer confidence #{result.confidence} is below the required threshold", code: code)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module Integrations
|
|
7
|
+
# Optional transport helper. Payload meaning and destinations belong to the app.
|
|
8
|
+
# @api public
|
|
9
|
+
class OrderedEventDelivery
|
|
10
|
+
class ClosedError < Phronomy::Error; end
|
|
11
|
+
class OverflowError < Phronomy::Error; end
|
|
12
|
+
|
|
13
|
+
# Close and drain after the block, preserving its result or original failure.
|
|
14
|
+
# A flush timeout limits waiting, not an in-flight network request.
|
|
15
|
+
# @api public
|
|
16
|
+
def self.open(deliver:, capacity: 256, batch_size: 32, flush_timeout: 30, prepare_batch: nil)
|
|
17
|
+
raise ArgumentError, "block required" unless block_given?
|
|
18
|
+
reject_event_loop!
|
|
19
|
+
validate_timeout!(flush_timeout)
|
|
20
|
+
raise ArgumentError, "deliver must be callable" unless deliver.respond_to?(:call)
|
|
21
|
+
delivery = new(capacity: capacity, batch_size: batch_size, prepare_batch: prepare_batch, &deliver)
|
|
22
|
+
begin
|
|
23
|
+
yield delivery
|
|
24
|
+
ensure
|
|
25
|
+
primary = $!
|
|
26
|
+
begin
|
|
27
|
+
delivery.close_and_wait(timeout: flush_timeout)
|
|
28
|
+
rescue => secondary
|
|
29
|
+
if primary
|
|
30
|
+
report_secondary(secondary)
|
|
31
|
+
else
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.reject_event_loop!
|
|
39
|
+
if Phronomy::Runtime.in_event_loop_context?
|
|
40
|
+
raise Phronomy::EventLoopReentrancyError, "delivery flush cannot block EventLoop"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.validate_timeout!(timeout)
|
|
45
|
+
valid = timeout.nil? || (timeout.is_a?(Numeric) && timeout.finite? && timeout >= 0)
|
|
46
|
+
raise ArgumentError, "flush timeout must be a finite nonnegative number or nil" unless valid
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.report_secondary(error)
|
|
50
|
+
Phronomy.configuration.logger&.error("[OrderedEventDelivery] secondary delivery failure: #{error.class}: #{error.message}")
|
|
51
|
+
rescue
|
|
52
|
+
# Diagnostic reporting must not replace the primary application error.
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
private_class_method :reject_event_loop!, :validate_timeout!, :report_secondary
|
|
56
|
+
|
|
57
|
+
# Construction does not start a worker or create a pool.
|
|
58
|
+
# @api public
|
|
59
|
+
def initialize(capacity: 256, batch_size: 32, prepare_batch: nil, &deliver)
|
|
60
|
+
raise ArgumentError, "deliver block required" unless deliver
|
|
61
|
+
unless capacity.is_a?(Integer) && capacity.positive? && batch_size.is_a?(Integer) && batch_size.positive?
|
|
62
|
+
raise ArgumentError, "capacity and batch_size must be positive Integers"
|
|
63
|
+
end
|
|
64
|
+
raise ArgumentError, "prepare_batch must be callable" if prepare_batch && !prepare_batch.respond_to?(:call)
|
|
65
|
+
@capacity, @batch_size = capacity, batch_size
|
|
66
|
+
@prepare_batch, @deliver = prepare_batch, deliver
|
|
67
|
+
@mutex = Mutex.new
|
|
68
|
+
@condition = ConditionVariable.new
|
|
69
|
+
@queue = []
|
|
70
|
+
@accepting = true
|
|
71
|
+
@worker = nil
|
|
72
|
+
@failure = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Snapshot plain data; admission order is the queue insertion order.
|
|
76
|
+
# Capacity counts queued payloads, excluding the active batch.
|
|
77
|
+
# @api public
|
|
78
|
+
def publish(payload)
|
|
79
|
+
value = immutable_copy(payload)
|
|
80
|
+
ticket = @mutex.synchronize do
|
|
81
|
+
raise @failure if @failure
|
|
82
|
+
raise ClosedError.new("event delivery closed", code: "delivery.closed") unless @accepting
|
|
83
|
+
raise OverflowError.new("event delivery queue full", code: "delivery.capacity_exceeded") if @queue.length >= @capacity
|
|
84
|
+
@queue << value
|
|
85
|
+
next nil if @worker
|
|
86
|
+
@worker = {started: false}
|
|
87
|
+
end
|
|
88
|
+
start_drain(ticket) if ticket
|
|
89
|
+
error = @mutex.synchronize { @failure }
|
|
90
|
+
raise error if error
|
|
91
|
+
self
|
|
92
|
+
rescue => error
|
|
93
|
+
# Listener dispatch can log an exception and continue. Retain the failure
|
|
94
|
+
# for close while continuing to drain already accepted work if possible.
|
|
95
|
+
@mutex.synchronize do
|
|
96
|
+
@failure ||= error
|
|
97
|
+
@accepting = false
|
|
98
|
+
end
|
|
99
|
+
raise
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# External callers only. Concurrent close calls observe the same drain.
|
|
103
|
+
# Timeout does not abort a network operation or settle the worker's result.
|
|
104
|
+
# @api public
|
|
105
|
+
def close_and_wait(timeout: nil)
|
|
106
|
+
self.class.send(:reject_event_loop!)
|
|
107
|
+
self.class.send(:validate_timeout!, timeout)
|
|
108
|
+
deadline = timeout && monotonic_now + timeout
|
|
109
|
+
@mutex.synchronize do
|
|
110
|
+
@accepting = false
|
|
111
|
+
while @worker
|
|
112
|
+
remaining = deadline && deadline - monotonic_now
|
|
113
|
+
raise Phronomy::TimeoutError, "event delivery flush timed out" if remaining && remaining <= 0
|
|
114
|
+
@condition.wait(@mutex, remaining)
|
|
115
|
+
end
|
|
116
|
+
raise @failure if @failure
|
|
117
|
+
end
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def start_drain(ticket)
|
|
124
|
+
Phronomy::Blocking.call_async do
|
|
125
|
+
started = @mutex.synchronize do
|
|
126
|
+
ticket[:started] = true if @worker.equal?(ticket)
|
|
127
|
+
end
|
|
128
|
+
if started
|
|
129
|
+
begin
|
|
130
|
+
drain(ticket)
|
|
131
|
+
rescue => error
|
|
132
|
+
fail_worker(ticket, error)
|
|
133
|
+
ensure
|
|
134
|
+
finish_worker(ticket)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end.on_complete do |_value, error|
|
|
138
|
+
fail_worker(ticket, error) if error
|
|
139
|
+
end
|
|
140
|
+
rescue => error
|
|
141
|
+
fail_worker(ticket, error)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def drain(ticket)
|
|
145
|
+
loop do
|
|
146
|
+
batch = @mutex.synchronize do
|
|
147
|
+
if @queue.empty?
|
|
148
|
+
# Publish must observe queue emptiness and ownership release together.
|
|
149
|
+
release_worker(ticket)
|
|
150
|
+
nil
|
|
151
|
+
else
|
|
152
|
+
@queue.shift(@batch_size)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
return unless batch
|
|
156
|
+
prepared = @prepare_batch ? @prepare_batch.call(batch.freeze) : batch
|
|
157
|
+
raise ArgumentError, "prepare_batch must return an Array" unless prepared.is_a?(Array)
|
|
158
|
+
immutable_copy(prepared).each { |payload| @deliver.call(payload) }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def fail_worker(ticket, error)
|
|
163
|
+
@mutex.synchronize do
|
|
164
|
+
@failure ||= error
|
|
165
|
+
@accepting = false
|
|
166
|
+
@queue.clear
|
|
167
|
+
# Logical cancellation can precede physical worker exit. Keep waiting
|
|
168
|
+
# for a started delivery; never mistake settled TaskResult for exit.
|
|
169
|
+
release_worker(ticket) unless ticket[:started]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def finish_worker(ticket)
|
|
174
|
+
@mutex.synchronize { release_worker(ticket) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def release_worker(ticket)
|
|
178
|
+
return unless @worker.equal?(ticket)
|
|
179
|
+
@worker = nil
|
|
180
|
+
@condition.broadcast
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def immutable_copy(value, ancestors = {})
|
|
184
|
+
case value
|
|
185
|
+
when Hash, Array
|
|
186
|
+
raise ArgumentError, "event payload must contain acyclic plain data" if ancestors[value.object_id]
|
|
187
|
+
path = ancestors.merge(value.object_id => true)
|
|
188
|
+
if value.is_a?(Hash)
|
|
189
|
+
value.to_h { |key, child| [immutable_copy(key, path), immutable_copy(child, path)] }.freeze
|
|
190
|
+
else
|
|
191
|
+
value.map { |child| immutable_copy(child, path) }.freeze
|
|
192
|
+
end
|
|
193
|
+
when String then value.dup.freeze
|
|
194
|
+
when Integer, Symbol, TrueClass, FalseClass, NilClass then value
|
|
195
|
+
when Float
|
|
196
|
+
raise ArgumentError, "event payload numbers must be finite" unless value.finite?
|
|
197
|
+
value
|
|
198
|
+
else
|
|
199
|
+
raise ArgumentError, "event payload must contain plain data: #{value.class}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def monotonic_now
|
|
204
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
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
|