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
|
@@ -8,7 +8,7 @@ module Phronomy
|
|
|
8
8
|
#
|
|
9
9
|
# Requires:
|
|
10
10
|
# - The +redis+ gem (add to your Gemfile)
|
|
11
|
-
# - A Redis server with the RediSearch
|
|
11
|
+
# - A RESP2 Redis client and a server with the RediSearch module enabled
|
|
12
12
|
# (or Redis Stack which bundles RediSearch)
|
|
13
13
|
#
|
|
14
14
|
# Vectors are stored as FLOAT32 binary blobs in Redis Hash fields and
|
|
@@ -46,17 +46,17 @@ module Phronomy
|
|
|
46
46
|
@mutex = Mutex.new
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
protected
|
|
50
|
+
|
|
49
51
|
# @param id [String]
|
|
50
52
|
# @param embedding [Array<Float>]
|
|
51
53
|
# @param metadata [Hash]
|
|
52
54
|
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
53
55
|
# @api public
|
|
54
|
-
def
|
|
55
|
-
cancellation_token&.raise_if_cancelled!
|
|
56
|
+
def perform_add(id:, embedding:, metadata: {}, cancellation_token: nil)
|
|
56
57
|
# Establish expected dimension on first add (not race-free for concurrent
|
|
57
|
-
# first adds), then
|
|
58
|
+
# first adds), then create/reuse the index. Base has validated the input.
|
|
58
59
|
@dimension ||= embedding.size
|
|
59
|
-
validate_embedding_dimension!(embedding, @dimension)
|
|
60
60
|
ensure_index!(@dimension)
|
|
61
61
|
@redis.call(
|
|
62
62
|
"HSET", "#{DOC_PREFIX}#{id}",
|
|
@@ -71,20 +71,17 @@ module Phronomy
|
|
|
71
71
|
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
72
72
|
# @return [Array<Hash>] sorted by descending similarity score
|
|
73
73
|
# @api public
|
|
74
|
-
def
|
|
75
|
-
cancellation_token&.raise_if_cancelled!
|
|
74
|
+
def perform_search(query_embedding:, k: 5, cancellation_token: nil)
|
|
76
75
|
# search never establishes dimension. If dimension is unknown and the
|
|
77
76
|
# index has not been created yet, there are no documents to return.
|
|
78
77
|
return [] if @dimension.nil? && !@index_created
|
|
79
78
|
|
|
80
|
-
validate_embedding_dimension!(query_embedding, @dimension)
|
|
81
79
|
ensure_index!(@dimension)
|
|
82
|
-
k_safe = validate_k!(k)
|
|
83
80
|
blob = pack_vector(query_embedding)
|
|
84
81
|
|
|
85
82
|
raw = @redis.call(
|
|
86
83
|
"FT.SEARCH", @index_name,
|
|
87
|
-
"*=>[KNN #{
|
|
84
|
+
"*=>[KNN #{k} @embedding $BLOB AS score]",
|
|
88
85
|
"PARAMS", 2, "BLOB", blob,
|
|
89
86
|
"SORTBY", "score",
|
|
90
87
|
"RETURN", 2, "score", "metadata",
|
|
@@ -94,26 +91,26 @@ module Phronomy
|
|
|
94
91
|
parse_results(raw)
|
|
95
92
|
end
|
|
96
93
|
|
|
97
|
-
def
|
|
94
|
+
def perform_remove(id:)
|
|
98
95
|
@redis.call("DEL", "#{DOC_PREFIX}#{id}")
|
|
99
96
|
self
|
|
100
97
|
end
|
|
101
98
|
|
|
102
99
|
# Returns the number of documents indexed.
|
|
103
100
|
# Queries FT.INFO when the index has been created; returns 0 otherwise.
|
|
104
|
-
def
|
|
101
|
+
def perform_size
|
|
105
102
|
return 0 unless @index_created
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
fields = parse_fields(@redis.call("FT.INFO", @index_name), "FT.INFO")
|
|
105
|
+
count = fields["num_docs"]
|
|
106
|
+
count = Integer(count, 10) if count.is_a?(String) && count.match?(/\A\d+\z/)
|
|
107
|
+
unless count.is_a?(Integer) && count >= 0
|
|
108
|
+
raise InvalidResultError, "Redis FT.INFO requires a non-negative integer num_docs"
|
|
109
|
+
end
|
|
110
|
+
count
|
|
114
111
|
end
|
|
115
112
|
|
|
116
|
-
def
|
|
113
|
+
def perform_clear
|
|
117
114
|
@mutex.synchronize do
|
|
118
115
|
begin
|
|
119
116
|
@redis.call("FT.DROPINDEX", @index_name, "DD")
|
|
@@ -125,6 +122,11 @@ module Phronomy
|
|
|
125
122
|
self
|
|
126
123
|
end
|
|
127
124
|
|
|
125
|
+
# @api public
|
|
126
|
+
def embedding_dimension
|
|
127
|
+
@dimension
|
|
128
|
+
end
|
|
129
|
+
|
|
128
130
|
private
|
|
129
131
|
|
|
130
132
|
def ensure_index!(dim)
|
|
@@ -160,33 +162,68 @@ module Phronomy
|
|
|
160
162
|
#
|
|
161
163
|
# Redis FT.SEARCH returns: [count, key1, [field, value, ...], key2, ...]
|
|
162
164
|
def parse_results(raw)
|
|
163
|
-
|
|
165
|
+
unless raw.is_a?(Array) && raw.size.odd? && raw.first.is_a?(Integer) &&
|
|
166
|
+
raw.first >= (raw.size - 1) / 2 && raw.first.zero? == (raw.size == 1)
|
|
167
|
+
raise InvalidResultError, "Redis FT.SEARCH requires a count and document/field pairs"
|
|
168
|
+
end
|
|
164
169
|
|
|
165
170
|
results = []
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
next
|
|
173
|
-
|
|
174
|
-
field_hash = fields.each_slice(2).to_h
|
|
175
|
-
score_str = field_hash["score"]
|
|
176
|
-
metadata_str = field_hash["metadata"]
|
|
177
|
-
|
|
178
|
-
next if score_str.nil?
|
|
171
|
+
raw.drop(1).each_slice(2) do |key, fields|
|
|
172
|
+
unless key.is_a?(String) && key.start_with?(DOC_PREFIX)
|
|
173
|
+
raise InvalidResultError, "Redis FT.SEARCH returned an invalid document key"
|
|
174
|
+
end
|
|
175
|
+
# Redis documents this null content when a key expires/is updated
|
|
176
|
+
# during the search. The key remains in the total count.
|
|
177
|
+
next if fields.nil?
|
|
179
178
|
|
|
180
|
-
|
|
179
|
+
field_hash = parse_fields(fields, "FT.SEARCH")
|
|
180
|
+
id = key.delete_prefix(DOC_PREFIX)
|
|
181
181
|
# RediSearch returns cosine distance (0=identical, 2=opposite);
|
|
182
182
|
# convert to cosine similarity for consistency with other backends.
|
|
183
|
-
score = 1.0 -
|
|
184
|
-
metadata =
|
|
183
|
+
score = 1.0 - parse_distance(field_hash["score"])
|
|
184
|
+
metadata = parse_metadata(field_hash["metadata"])
|
|
185
185
|
|
|
186
186
|
results << {id: id, score: score, metadata: metadata}
|
|
187
187
|
end
|
|
188
188
|
results
|
|
189
189
|
end
|
|
190
|
+
|
|
191
|
+
# RESP2 field/value decoding belongs to this backend, not the Contract.
|
|
192
|
+
def parse_fields(raw, command)
|
|
193
|
+
unless raw.is_a?(Array) && raw.size.even?
|
|
194
|
+
raise InvalidResultError, "Redis #{command} requires field/value pairs"
|
|
195
|
+
end
|
|
196
|
+
raw.each_slice(2).each_with_object({}) do |(key, value), fields|
|
|
197
|
+
unless key.is_a?(String) && !fields.key?(key)
|
|
198
|
+
raise InvalidResultError, "Redis #{command} returned an invalid or duplicate field"
|
|
199
|
+
end
|
|
200
|
+
fields[key] = value
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def parse_distance(raw)
|
|
205
|
+
valid = (raw.is_a?(String) && raw.match?(/\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\z/)) ||
|
|
206
|
+
(raw.is_a?(Numeric) && raw.real?)
|
|
207
|
+
value = Float(raw, exception: false) if valid
|
|
208
|
+
unless value&.finite?
|
|
209
|
+
raise InvalidResultError, "Redis FT.SEARCH requires a finite numeric score"
|
|
210
|
+
end
|
|
211
|
+
value
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def parse_metadata(raw)
|
|
215
|
+
return {} if raw.nil?
|
|
216
|
+
unless raw.is_a?(String)
|
|
217
|
+
raise InvalidResultError, "Redis metadata must contain a JSON object"
|
|
218
|
+
end
|
|
219
|
+
parsed = JSON.parse(raw, symbolize_names: true)
|
|
220
|
+
unless parsed.is_a?(Hash)
|
|
221
|
+
raise InvalidResultError, "Redis metadata must contain a JSON object"
|
|
222
|
+
end
|
|
223
|
+
parsed
|
|
224
|
+
rescue JSON::ParserError
|
|
225
|
+
raise InvalidResultError, "Redis metadata contains invalid JSON"
|
|
226
|
+
end
|
|
190
227
|
end
|
|
191
228
|
end
|
|
192
229
|
end
|
|
@@ -1,87 +1,156 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "invalid_result_error"
|
|
4
|
+
|
|
3
5
|
module Phronomy
|
|
4
6
|
module VectorStore
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Phronomy supplies async convenience methods through {AsyncBackend}; blocking
|
|
9
|
-
# backend work is offloaded through the framework-owned bounded OffloadPool.
|
|
10
|
-
#
|
|
7
|
+
# Owns synchronous operations, input validation and the public result shape.
|
|
8
|
+
# Backends implement protected perform_* operations and embedding_dimension.
|
|
9
|
+
# No execution client, SDK or embedding provider is selected by this class.
|
|
11
10
|
# @api public
|
|
12
11
|
class Base
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# @param
|
|
18
|
-
# @param
|
|
19
|
-
# @
|
|
20
|
-
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
12
|
+
# Validate before invoking the backend. Cancellation is checked before the
|
|
13
|
+
# write, never reported after a successful write. No operation is retried.
|
|
14
|
+
# @param id [String]
|
|
15
|
+
# @param embedding [Array<Numeric>]
|
|
16
|
+
# @param metadata [Hash]
|
|
17
|
+
# @param cancellation_token [#raise_if_cancelled!, nil]
|
|
18
|
+
# @return [self]
|
|
21
19
|
# @api public
|
|
22
20
|
def add(id:, embedding:, metadata: {}, cancellation_token: nil)
|
|
23
21
|
cancellation_token&.raise_if_cancelled!
|
|
24
|
-
|
|
22
|
+
validate_id!(id)
|
|
23
|
+
raise ArgumentError, "metadata must be a Hash" unless metadata.is_a?(Hash)
|
|
24
|
+
vector = validate_vector!(embedding)
|
|
25
|
+
perform_add(id: id, embedding: vector, metadata: metadata, cancellation_token: cancellation_token)
|
|
26
|
+
self
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
# Return
|
|
28
|
-
#
|
|
29
|
-
# @param query_embedding
|
|
30
|
-
# @param k
|
|
31
|
-
# @param cancellation_token [
|
|
32
|
-
# @return [Array<Hash>]
|
|
29
|
+
# Return at most k results ordered by descending similarity. Malformed
|
|
30
|
+
# backend output raises InvalidResultError; transport errors propagate.
|
|
31
|
+
# @param query_embedding [Array<Numeric>]
|
|
32
|
+
# @param k [Integer, String] positive integer or its decimal string
|
|
33
|
+
# @param cancellation_token [#raise_if_cancelled!, nil]
|
|
34
|
+
# @return [Array<Hash>]
|
|
33
35
|
# @api public
|
|
34
36
|
def search(query_embedding:, k: 5, cancellation_token: nil)
|
|
35
37
|
cancellation_token&.raise_if_cancelled!
|
|
36
|
-
|
|
38
|
+
limit = validate_k!(k)
|
|
39
|
+
vector = validate_vector!(query_embedding)
|
|
40
|
+
results = perform_search(query_embedding: vector, k: limit, cancellation_token: cancellation_token)
|
|
41
|
+
validate_results!(results, limit)
|
|
42
|
+
results
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
# Remove a
|
|
40
|
-
#
|
|
41
|
-
# @
|
|
45
|
+
# Remove a document; an absent id is a successful no-op.
|
|
46
|
+
# @param id [String]
|
|
47
|
+
# @return [self]
|
|
42
48
|
# @api public
|
|
43
49
|
def remove(id:)
|
|
44
|
-
|
|
50
|
+
validate_id!(id)
|
|
51
|
+
perform_remove(id: id)
|
|
52
|
+
self
|
|
45
53
|
end
|
|
46
54
|
|
|
47
|
-
#
|
|
55
|
+
# Clear documents without changing the configured dimension.
|
|
56
|
+
# @return [self]
|
|
48
57
|
# @api public
|
|
49
58
|
def clear
|
|
50
|
-
|
|
59
|
+
perform_clear
|
|
60
|
+
self
|
|
51
61
|
end
|
|
52
62
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# @return [Integer]
|
|
63
|
+
# @return [Integer] non-negative document count
|
|
56
64
|
# @api public
|
|
57
65
|
def size
|
|
58
|
-
|
|
66
|
+
count = perform_size
|
|
67
|
+
unless count.is_a?(Integer) && count >= 0
|
|
68
|
+
raise InvalidResultError, "size must return a non-negative Integer"
|
|
69
|
+
end
|
|
70
|
+
count
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
protected
|
|
74
|
+
|
|
75
|
+
# @return [Integer, nil] nil delegates dimension enforcement to the backend
|
|
76
|
+
# @api public
|
|
77
|
+
def embedding_dimension
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Storage extension point; inputs have passed common validation.
|
|
82
|
+
# @api public
|
|
83
|
+
def perform_add(id:, embedding:, metadata:, cancellation_token:)
|
|
84
|
+
raise NotImplementedError, "#{self.class}#perform_add is not implemented"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Search extension point; return the documented result hashes in order.
|
|
88
|
+
# @api public
|
|
89
|
+
def perform_search(query_embedding:, k:, cancellation_token:)
|
|
90
|
+
raise NotImplementedError, "#{self.class}#perform_search is not implemented"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @api public
|
|
94
|
+
def perform_remove(id:)
|
|
95
|
+
raise NotImplementedError, "#{self.class}#perform_remove is not implemented"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @api public
|
|
99
|
+
def perform_clear
|
|
100
|
+
raise NotImplementedError, "#{self.class}#perform_clear is not implemented"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @api public
|
|
104
|
+
def perform_size
|
|
105
|
+
raise NotImplementedError, "#{self.class}#perform_size is not implemented"
|
|
59
106
|
end
|
|
60
107
|
|
|
61
108
|
private
|
|
62
109
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def validate_embedding_dimension!(embedding, expected_dimension)
|
|
67
|
-
return unless expected_dimension
|
|
110
|
+
def validate_id!(id)
|
|
111
|
+
raise ArgumentError, "id must be a String" unless id.is_a?(String)
|
|
112
|
+
end
|
|
68
113
|
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
def finite_real?(value)
|
|
115
|
+
value.is_a?(Numeric) && value.real? && value.finite? && value.to_f.finite?
|
|
116
|
+
end
|
|
71
117
|
|
|
72
|
-
|
|
73
|
-
|
|
118
|
+
def validate_vector!(embedding)
|
|
119
|
+
unless embedding.is_a?(Array) && embedding.all? { |value| finite_real?(value) }
|
|
120
|
+
raise ArgumentError, "embedding must be an Array of finite real numbers"
|
|
121
|
+
end
|
|
122
|
+
dimension = embedding_dimension
|
|
123
|
+
if dimension && embedding.size != dimension
|
|
124
|
+
raise ArgumentError, "Embedding dimension mismatch: expected #{dimension}, got #{embedding.size}"
|
|
125
|
+
end
|
|
126
|
+
embedding.map(&:to_f)
|
|
74
127
|
end
|
|
75
128
|
|
|
76
|
-
# Validates that k is a positive integer.
|
|
77
|
-
# Accepts any value accepted by Integer() (e.g. "5"), but raises
|
|
78
|
-
# ArgumentError for non-integer strings, zero, and negative values.
|
|
79
129
|
def validate_k!(k)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
130
|
+
valid = k.is_a?(Integer) || (k.is_a?(String) && k.match?(/\A\+?\d+\z/))
|
|
131
|
+
unless valid
|
|
132
|
+
raise ArgumentError, "k must be a positive integer"
|
|
133
|
+
end
|
|
134
|
+
value = k.is_a?(String) ? Integer(k, 10) : k
|
|
135
|
+
raise ArgumentError, "k must be a positive integer" unless value >= 1
|
|
136
|
+
value
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def validate_results!(results, limit)
|
|
140
|
+
unless results.is_a?(Array) && results.size <= limit
|
|
141
|
+
raise InvalidResultError, "search must return an Array with at most k results"
|
|
142
|
+
end
|
|
143
|
+
previous = Float::INFINITY
|
|
144
|
+
results.each do |result|
|
|
145
|
+
unless result.is_a?(Hash) && result[:id].is_a?(String) &&
|
|
146
|
+
result[:metadata].is_a?(Hash) && finite_real?(result[:score])
|
|
147
|
+
raise InvalidResultError, "search results require a String id, finite score and Hash metadata"
|
|
148
|
+
end
|
|
149
|
+
if result[:score] > previous
|
|
150
|
+
raise InvalidResultError, "search results must be ordered by descending score"
|
|
151
|
+
end
|
|
152
|
+
previous = result[:score]
|
|
153
|
+
end
|
|
85
154
|
end
|
|
86
155
|
end
|
|
87
156
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module VectorStore
|
|
7
|
+
# A backend could not produce a valid synchronous result. This includes
|
|
8
|
+
# malformed external responses that must not be replaced with success values.
|
|
9
|
+
# @api public
|
|
10
|
+
class InvalidResultError < Phronomy::Error; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
#
|
|
5
|
-
# and
|
|
6
|
-
#
|
|
7
|
-
# Sub-namespaces are auto-loaded by Zeitwerk:
|
|
8
|
-
# Phronomy::VectorStore::Base
|
|
9
|
-
# Phronomy::VectorStore::InMemory
|
|
10
|
-
# Phronomy::VectorStore::Pgvector
|
|
11
|
-
# Phronomy::VectorStore::RedisSearch
|
|
12
|
-
# Phronomy::VectorStore::Embeddings::Base
|
|
13
|
-
# Phronomy::VectorStore::Embeddings::RubyLLMEmbeddings
|
|
14
|
-
# Phronomy::VectorStore::Loader::Base
|
|
15
|
-
# Phronomy::VectorStore::Loader::PlainTextLoader
|
|
16
|
-
# Phronomy::VectorStore::Loader::MarkdownLoader
|
|
17
|
-
# Phronomy::VectorStore::Loader::CsvLoader
|
|
18
|
-
# Phronomy::VectorStore::Splitter::Base
|
|
19
|
-
# Phronomy::VectorStore::Splitter::FixedSizeSplitter
|
|
20
|
-
# Phronomy::VectorStore::Splitter::RecursiveSplitter
|
|
4
|
+
# Synchronous vector search/storage contract, its backends and execution client.
|
|
5
|
+
# Embeddings and Documents are independent Phronomy responsibilities.
|
|
21
6
|
module VectorStore
|
|
22
7
|
end
|
|
23
8
|
end
|
data/lib/phronomy/version.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../
|
|
3
|
+
require_relative "../../execution/concurrency/worker_input_restricted"
|
|
4
4
|
|
|
5
5
|
require_relative "workflow_runner"
|
|
6
|
-
require_relative "../../
|
|
6
|
+
require_relative "../../execution/runnable"
|
|
7
7
|
|
|
8
8
|
module Phronomy
|
|
9
9
|
# StateChart-style Workflow definition DSL.
|
|
@@ -67,7 +67,7 @@ module Phronomy
|
|
|
67
67
|
def _apply_invocation_context(config, invocation_context)
|
|
68
68
|
effective = config.merge(invocation_context: invocation_context)
|
|
69
69
|
if effective[:cancellation_token].nil?
|
|
70
|
-
token =
|
|
70
|
+
token = Phronomy::InvocationControls.effective_timeout_token(invocation_context)
|
|
71
71
|
effective = effective.merge(cancellation_token: token) if token
|
|
72
72
|
end
|
|
73
73
|
effective
|
|
@@ -85,6 +85,7 @@ module Phronomy
|
|
|
85
85
|
@exit_actions = {}
|
|
86
86
|
@transitions = []
|
|
87
87
|
@wait_state_names = []
|
|
88
|
+
@task_states = {}
|
|
88
89
|
end
|
|
89
90
|
|
|
90
91
|
def initial(state_name) # rubocop:disable Style/TrivialAccessors
|
|
@@ -94,9 +95,10 @@ module Phronomy
|
|
|
94
95
|
# Declares a Workflow state.
|
|
95
96
|
#
|
|
96
97
|
# Entry actions are synchronous Run-to-Completion callbacks. To start
|
|
97
|
-
# asynchronous work
|
|
98
|
-
# return the context or nil
|
|
98
|
+
# asynchronous work owned by this state, use task_state. Ordinary actions
|
|
99
|
+
# return the context or nil; returning Phronomy::TaskResult is an error.
|
|
99
100
|
def state(name, action: nil)
|
|
101
|
+
reject_task_state_overlap!(name)
|
|
100
102
|
@declared_states << name
|
|
101
103
|
entry(name, action) if action
|
|
102
104
|
end
|
|
@@ -110,9 +112,30 @@ module Phronomy
|
|
|
110
112
|
end
|
|
111
113
|
|
|
112
114
|
def wait_state(name)
|
|
115
|
+
reject_task_state_overlap!(name)
|
|
113
116
|
@wait_state_names << name
|
|
114
117
|
end
|
|
115
118
|
|
|
119
|
+
# Starts one asynchronous operation on state entry and advances only
|
|
120
|
+
# after its successful result has been applied on the execution owner.
|
|
121
|
+
# Task completion is correlated to this state entry, not a durable ID.
|
|
122
|
+
# @param name [Symbol] state name
|
|
123
|
+
# @param start [#call] short callback accepting a context snapshot and returning TaskResult
|
|
124
|
+
# @param apply [#call] short callback accepting current context and value; returns context or nil
|
|
125
|
+
# @return [void]
|
|
126
|
+
# @api public
|
|
127
|
+
def task_state(name, start:, apply:)
|
|
128
|
+
unless name.is_a?(Symbol) && start.respond_to?(:call) && apply.respond_to?(:call)
|
|
129
|
+
raise ArgumentError, "task_state requires a Symbol name and callable start/apply"
|
|
130
|
+
end
|
|
131
|
+
if @declared_states.include?(name) || @wait_state_names.include?(name)
|
|
132
|
+
raise ArgumentError, "Workflow state #{name.inspect} is already declared"
|
|
133
|
+
end
|
|
134
|
+
@declared_states << name
|
|
135
|
+
@task_states[name] = {start: start, apply: apply}.freeze
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
|
|
116
139
|
# Declares a transition.
|
|
117
140
|
#
|
|
118
141
|
# Guards and actions may accept either (context) or (context, event).
|
|
@@ -121,7 +144,7 @@ module Phronomy
|
|
|
121
144
|
# They may start asynchronous work and register listeners, but returning
|
|
122
145
|
# Phronomy::TaskResult is an error; completion must arrive as a later event.
|
|
123
146
|
def transition(from:, to:, guard: nil, on: nil, action: nil)
|
|
124
|
-
destination =
|
|
147
|
+
destination = to
|
|
125
148
|
@transitions << {
|
|
126
149
|
from: from,
|
|
127
150
|
to: destination,
|
|
@@ -167,6 +190,7 @@ module Phronomy
|
|
|
167
190
|
external_events: external_events,
|
|
168
191
|
entry_point: @initial || @declared_states.first,
|
|
169
192
|
wait_state_names: @wait_state_names.dup,
|
|
193
|
+
task_states: @task_states.dup.freeze,
|
|
170
194
|
persistence: @persistence
|
|
171
195
|
)
|
|
172
196
|
Workflow.new(runner)
|
|
@@ -174,6 +198,10 @@ module Phronomy
|
|
|
174
198
|
|
|
175
199
|
private
|
|
176
200
|
|
|
201
|
+
def reject_task_state_overlap!(name)
|
|
202
|
+
raise ArgumentError, "Workflow task_state #{name.inspect} is already declared" if @task_states.key?(name)
|
|
203
|
+
end
|
|
204
|
+
|
|
177
205
|
def validate_graph!
|
|
178
206
|
all_states = (@declared_states + @wait_state_names).uniq
|
|
179
207
|
entry_point = @initial || @declared_states.first
|
|
@@ -186,7 +214,7 @@ module Phronomy
|
|
|
186
214
|
|
|
187
215
|
undefined_targets = @transitions
|
|
188
216
|
.map { |transition| transition[:to] }
|
|
189
|
-
.reject { |target| target ==
|
|
217
|
+
.reject { |target| target == Completion::FINISH } - all_states
|
|
190
218
|
unless undefined_targets.empty?
|
|
191
219
|
raise ArgumentError,
|
|
192
220
|
"Workflow transition(s) reference undefined state(s): " \
|
|
@@ -207,7 +235,7 @@ module Phronomy
|
|
|
207
235
|
current = queue.shift
|
|
208
236
|
@transitions.each do |transition|
|
|
209
237
|
next unless transition[:from] == current
|
|
210
|
-
next if transition[:to] ==
|
|
238
|
+
next if transition[:to] == Completion::FINISH
|
|
211
239
|
next if reachable.include?(transition[:to])
|
|
212
240
|
|
|
213
241
|
reachable.add(transition[:to])
|
|
@@ -222,7 +250,7 @@ module Phronomy
|
|
|
222
250
|
"[Phronomy] Workflow has unreachable state(s): " \
|
|
223
251
|
"#{unreachable.sort.inspect}. These states can never be entered " \
|
|
224
252
|
"from the initial state #{entry_point.inspect}."
|
|
225
|
-
logger = Phronomy.
|
|
253
|
+
logger = Phronomy::RuntimeSettings.current.logger
|
|
226
254
|
logger ? logger.warn(message) : Kernel.warn(message)
|
|
227
255
|
end
|
|
228
256
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Workflow's synchronous callback rules, independent of FSM representation.
|
|
5
|
+
# Guards and transition actions may receive the triggering application event;
|
|
6
|
+
# actions may initiate async work but must not return a TaskResult.
|
|
7
|
+
# @api private
|
|
8
|
+
module WorkflowActionRules
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def allowed?(guard, context, event)
|
|
12
|
+
guard.nil? || call_with_optional_event(guard, context, event)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def transition(callable, context, event, metadata:)
|
|
16
|
+
return context if callable.nil?
|
|
17
|
+
|
|
18
|
+
result = call_with_optional_event(callable, context, event)
|
|
19
|
+
if result.is_a?(Phronomy::TaskResult)
|
|
20
|
+
raise Phronomy::InvalidAsyncTransitionActionError,
|
|
21
|
+
"Transition action " \
|
|
22
|
+
"#{metadata[:from].inspect} --#{metadata[:event].inspect}--> " \
|
|
23
|
+
"#{metadata[:to].inspect} returned Phronomy::TaskResult. " \
|
|
24
|
+
"Start the asynchronous operation, register its callback/listener, " \
|
|
25
|
+
"and return the WorkflowContext or nil."
|
|
26
|
+
end
|
|
27
|
+
workflow_context_result?(result) ? result : context
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def entry(callable, context, state_name)
|
|
31
|
+
result = callable.call(context)
|
|
32
|
+
if result.is_a?(Phronomy::TaskResult)
|
|
33
|
+
raise Phronomy::InvalidAsyncEntryActionError,
|
|
34
|
+
"Entry action for state #{state_name.inspect} returned Phronomy::TaskResult. " \
|
|
35
|
+
"Start the asynchronous operation, register its callback/listener, " \
|
|
36
|
+
"and return the WorkflowContext or nil."
|
|
37
|
+
end
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def apply_entry(callable, context, state_name)
|
|
42
|
+
result = entry(callable, context, state_name)
|
|
43
|
+
workflow_context_result?(result) ? result : context
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def exit(callable, context, state_name)
|
|
47
|
+
result = callable.call(context)
|
|
48
|
+
if result.is_a?(Phronomy::TaskResult)
|
|
49
|
+
raise Phronomy::InvalidAsyncEntryActionError,
|
|
50
|
+
"Exit action for state #{state_name.inspect} returned Phronomy::TaskResult. " \
|
|
51
|
+
"Exit actions are synchronous Run-to-Completion callbacks."
|
|
52
|
+
end
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def call_with_optional_event(callable, context, event)
|
|
57
|
+
parameters = callable.respond_to?(:parameters) ? callable.parameters : callable.method(:call).parameters
|
|
58
|
+
(parameters.length >= 2) ? callable.call(context, event) : callable.call(context)
|
|
59
|
+
end
|
|
60
|
+
private_class_method :call_with_optional_event
|
|
61
|
+
|
|
62
|
+
def workflow_context_result?(result)
|
|
63
|
+
result.respond_to?(:set_graph_metadata)
|
|
64
|
+
end
|
|
65
|
+
private_class_method :workflow_context_result?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../
|
|
3
|
+
require_relative "../../execution/concurrency/worker_input_restricted"
|
|
4
4
|
|
|
5
5
|
module Phronomy
|
|
6
6
|
# Module for defining Workflow context data.
|
|
@@ -138,7 +138,7 @@ module Phronomy
|
|
|
138
138
|
# dispatch thread. All Workflow execution APIs now use that same path; there
|
|
139
139
|
# is no caller-thread synchronous exception.
|
|
140
140
|
def _assert_write_permitted!
|
|
141
|
-
return if Phronomy::
|
|
141
|
+
return if Phronomy::WaitPolicy.blocking_forbidden?
|
|
142
142
|
|
|
143
143
|
raise Phronomy::WorkflowContextOwnershipError,
|
|
144
144
|
"WorkflowContext fields may only be mutated from the EventLoop dispatch " \
|