phronomy 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.mutant.yml +9 -9
- data/CHANGELOG.md +92 -0
- data/CONTRIBUTING.md +8 -8
- data/README.md +13 -3
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -47
- data/benchmark/bench_context_assembler.rb +17 -15
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +1 -1
- data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
- data/docs/architecture/agent-context.md +4 -2
- data/docs/architecture/agent-transition-ownership.md +13 -0
- data/docs/architecture/backend-groups.md +152 -0
- data/docs/architecture/before-llm-input.md +9 -3
- data/docs/architecture/context-management.md +31 -1
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-abstraction-review.md +88 -0
- data/docs/architecture/execution-boundaries.md +28 -0
- data/docs/architecture/execution-metadata-and-values.md +86 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +11 -4
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +45 -4
- data/docs/architecture/r8-unit1.md +104 -0
- data/docs/architecture/r8-unit10.md +72 -0
- data/docs/architecture/r8-unit11.md +71 -0
- data/docs/architecture/r8-unit12.md +67 -0
- data/docs/architecture/r8-unit13.md +61 -0
- data/docs/architecture/r8-unit14.md +72 -0
- data/docs/architecture/r8-unit15.md +65 -0
- data/docs/architecture/r8-unit16.md +54 -0
- data/docs/architecture/r8-unit17.md +73 -0
- data/docs/architecture/r8-unit18.md +92 -0
- data/docs/architecture/r8-unit19.md +61 -0
- data/docs/architecture/r8-unit2.md +55 -0
- data/docs/architecture/r8-unit20.md +51 -0
- data/docs/architecture/r8-unit21.md +79 -0
- data/docs/architecture/r8-unit22.md +74 -0
- data/docs/architecture/r8-unit23.md +81 -0
- data/docs/architecture/r8-unit3.md +130 -0
- data/docs/architecture/r8-unit4.md +79 -0
- data/docs/architecture/r8-unit5.md +115 -0
- data/docs/architecture/r8-unit6.md +71 -0
- data/docs/architecture/r8-unit7.md +70 -0
- data/docs/architecture/r8-unit8.md +78 -0
- data/docs/architecture/r8-unit9.md +73 -0
- data/docs/architecture/refactoring-closure.md +343 -0
- data/docs/architecture/remaining-refactoring-plan.md +390 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/storage-execution-boundaries.md +54 -0
- data/docs/architecture/tool-schema-recording-gap.md +56 -0
- data/docs/architecture/tracing.md +2 -2
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +222 -0
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
- data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
- data/docs/decisions/032-storage-backend-composition.md +85 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
- data/docs/decisions/036-context-contract-ownership.md +86 -0
- data/docs/decisions/037-common-definition-ownership.md +62 -0
- data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
- data/docs/decisions/040-configuration-default-composition.md +105 -0
- data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
- data/docs/decisions/042-feature-owned-execution-state.md +106 -0
- data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
- data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
- data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
- data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
- data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
- data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
- data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
- data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
- data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
- data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
- data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
- data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
- data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
- data/docs/decisions/058-neutral-storage-primitives.md +83 -0
- data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
- data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
- data/docs/decisions/061-persistence-failure-contracts.md +89 -0
- data/docs/decisions/062-content-store-service.md +52 -0
- data/docs/decisions/063-execution-contracts-and-services.md +88 -0
- data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
- data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
- data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
- data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
- data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
- data/docs/decisions/README.md +52 -6
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
- data/docs/execution-boundaries-preview.md +45 -0
- data/docs/features.md +16 -5
- data/docs/getting-started.md +11 -8
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/0.22.md +2 -2
- data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
- data/docs/migrations/handoff-runner-multi-agent.md +37 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/persistence-failure-contracts.md +67 -0
- data/docs/migrations/r8-unit10.md +36 -0
- data/docs/migrations/r8-unit11.md +33 -0
- data/docs/migrations/r8-unit12.md +31 -0
- data/docs/migrations/r8-unit13.md +36 -0
- data/docs/migrations/r8-unit14.md +31 -0
- data/docs/migrations/r8-unit15.md +31 -0
- data/docs/migrations/r8-unit16.md +26 -0
- data/docs/migrations/r8-unit17.md +28 -0
- data/docs/migrations/r8-unit18.md +29 -0
- data/docs/migrations/r8-unit19.md +28 -0
- data/docs/migrations/r8-unit20.md +24 -0
- data/docs/migrations/r8-unit21.md +28 -0
- data/docs/migrations/r8-unit22.md +29 -0
- data/docs/migrations/r8-unit23.md +20 -0
- data/docs/migrations/r8-unit4.md +67 -0
- data/docs/migrations/r8-unit5.md +75 -0
- data/docs/migrations/r8-unit6.md +109 -0
- data/docs/migrations/r8-unit7.md +31 -0
- data/docs/migrations/r8-unit8.md +33 -0
- data/docs/migrations/r8-unit9.md +28 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-async-client.md +103 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/migrations/vector-async-clients.md +110 -0
- data/docs/persistence-backends.md +190 -215
- data/docs/runtime-and-concurrency.md +142 -32
- data/lib/phronomy/agent/admission.rb +78 -0
- data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/agent/async_event_api.rb +38 -33
- data/lib/phronomy/agent/base.rb +106 -294
- data/lib/phronomy/agent/before_llm_input.rb +64 -0
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
- data/lib/phronomy/agent/context_importer.rb +2 -2
- data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
- data/lib/phronomy/agent/control_request.rb +21 -0
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
- data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/execution_change.rb +161 -0
- data/lib/phronomy/agent/execution_environment.rb +36 -0
- data/lib/phronomy/agent/execution_extension_state.rb +21 -0
- data/lib/phronomy/agent/execution_observation.rb +35 -0
- data/lib/phronomy/agent/filterable.rb +182 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
- data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
- data/lib/phronomy/agent/handoff_error.rb +7 -0
- data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
- data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
- data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
- data/lib/phronomy/agent/knowledge_item.rb +13 -0
- data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
- data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
- data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
- data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
- data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
- data/lib/phronomy/agent/persistence/codec.rb +338 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
- data/lib/phronomy/agent/persistence/records.rb +33 -0
- data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
- data/lib/phronomy/agent/persistence/watermark.rb +29 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
- data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
- data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
- data/lib/phronomy/agent/reserved_execution.rb +16 -0
- data/lib/phronomy/agent/retention.rb +17 -0
- data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
- data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
- data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
- data/lib/phronomy/agent/saved_context_reader.rb +55 -0
- data/lib/phronomy/agent/settings.rb +47 -0
- data/lib/phronomy/agent/state_writer.rb +166 -0
- data/lib/phronomy/agent/store.rb +253 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
- data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
- data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
- data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
- data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
- data/lib/phronomy/common/configuration_error.rb +7 -0
- data/lib/phronomy/common/error.rb +5 -0
- data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
- data/lib/phronomy/common/values/serializable.rb +32 -0
- data/lib/phronomy/configuration/runtime_settings.rb +49 -0
- data/lib/phronomy/content_store/storage_schema.rb +12 -0
- data/lib/phronomy/content_store/stored_contents.rb +44 -0
- data/lib/phronomy/context/assembly.rb +202 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
- data/lib/phronomy/context/candidate.rb +51 -0
- data/lib/phronomy/context/constraint.rb +47 -0
- data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
- data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
- data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
- data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
- data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
- data/lib/phronomy/context/default_policy.rb +140 -0
- data/lib/phronomy/context/final_budget_validator.rb +33 -0
- data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
- data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
- data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
- data/lib/phronomy/context/manifest_error.rb +10 -0
- data/lib/phronomy/context/prompt_template.rb +98 -0
- data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
- data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
- data/lib/phronomy/embeddings/async/async_client.rb +44 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
- data/lib/phronomy/embeddings/base.rb +43 -0
- data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
- data/lib/phronomy/embeddings/transport_error.rb +11 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
- data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
- data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
- data/lib/phronomy/engine/event_loop.rb +164 -630
- data/lib/phronomy/engine/execution_binding.rb +23 -0
- data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
- data/lib/phronomy/engine/fsm_protocol.rb +9 -0
- data/lib/phronomy/engine/fsm_session.rb +37 -30
- data/lib/phronomy/engine/metrics.rb +28 -0
- data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
- data/lib/phronomy/engine/runtime.rb +104 -95
- data/lib/phronomy/engine/terminal_decision.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +41 -0
- data/lib/phronomy/execution/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
- data/lib/phronomy/execution/cancellation_error.rb +7 -0
- data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
- data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
- data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
- data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
- data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
- data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
- data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
- data/lib/phronomy/execution/execution_receiver.rb +78 -0
- data/lib/phronomy/execution/execution_scope_state.rb +35 -0
- data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
- data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
- data/lib/phronomy/execution/invocation_controls.rb +73 -0
- data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
- data/lib/phronomy/execution/runnable.rb +24 -0
- data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
- data/lib/phronomy/execution/timeout_error.rb +7 -0
- data/lib/phronomy/execution/wait_policy.rb +24 -0
- data/lib/phronomy/filter/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
- data/lib/phronomy/generation/generator_verifier.rb +118 -0
- data/lib/phronomy/generation/low_confidence_error.rb +14 -0
- data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
- data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
- data/lib/phronomy/llm_adapter/base.rb +49 -68
- data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
- data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
- data/lib/phronomy/llm_adapter/message.rb +62 -0
- data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
- data/lib/phronomy/llm_adapter/request.rb +79 -0
- data/lib/phronomy/llm_adapter/response.rb +36 -0
- data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
- data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
- data/lib/phronomy/llm_adapter.rb +3 -16
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
- data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
- data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
- data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
- data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
- data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
- data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
- data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
- data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
- data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
- data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
- data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
- data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
- data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/store.rb +156 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/parse_error.rb +7 -0
- data/lib/phronomy/persistence/content_repository.rb +26 -0
- data/lib/phronomy/persistence/errors.rb +16 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
- data/lib/phronomy/persistence/persistence.rb +23 -0
- data/lib/phronomy/persistence/save_outcome.rb +24 -0
- data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
- data/lib/phronomy/persistence/storage_boundary.rb +32 -0
- data/lib/phronomy/persistence/transaction.rb +86 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
- data/lib/phronomy/persistence_composition/stores.rb +34 -0
- data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
- data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
- data/lib/phronomy/runtime_composition/configuration.rb +146 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
- data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
- data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
- data/lib/phronomy/storage/async/async_client.rb +60 -0
- data/lib/phronomy/storage/backend.rb +107 -0
- data/lib/phronomy/storage/backends/in_memory.rb +157 -0
- data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
- data/lib/phronomy/storage/blobs.rb +31 -0
- data/lib/phronomy/storage/condition.rb +25 -0
- data/lib/phronomy/storage/condition_failed_error.rb +16 -0
- data/lib/phronomy/storage/conflict_error.rb +9 -0
- data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
- data/lib/phronomy/storage/entry.rb +33 -0
- data/lib/phronomy/storage/guard_ref.rb +13 -0
- data/lib/phronomy/storage/not_found_error.rb +9 -0
- data/lib/phronomy/storage/record_codec.rb +177 -0
- data/lib/phronomy/storage/records.rb +61 -0
- data/lib/phronomy/storage/resource.rb +126 -0
- data/lib/phronomy/storage/scope.rb +25 -0
- data/lib/phronomy/storage/serialization_error.rb +9 -0
- data/lib/phronomy/storage/streams.rb +44 -0
- data/lib/phronomy/storage/transaction_error.rb +10 -0
- data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
- data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
- data/lib/phronomy/storage/validation.rb +53 -0
- data/lib/phronomy/storage/view.rb +131 -0
- data/lib/phronomy/testing/eval/metrics.rb +1 -1
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/authorization.rb +77 -0
- data/lib/phronomy/tool/base.rb +549 -9
- data/lib/phronomy/tool/call_request.rb +40 -0
- data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
- data/lib/phronomy/tool/operation.rb +88 -0
- data/lib/phronomy/tool/schema.rb +214 -0
- data/lib/phronomy/tool/settings.rb +31 -0
- data/lib/phronomy/tool/tool_error.rb +7 -0
- data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
- data/lib/phronomy/tools/agent.rb +22 -90
- data/lib/phronomy/tools/mcp.rb +21 -14
- data/lib/phronomy/tools/vector_search.rb +4 -4
- data/lib/phronomy/tracing/automatic.rb +11 -6
- data/lib/phronomy/tracing/base.rb +1 -1
- data/lib/phronomy/tracing/observation.rb +37 -0
- data/lib/phronomy/tracing/settings.rb +36 -0
- data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
- data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
- data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
- data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
- data/lib/phronomy/vector_store/base.rb +118 -49
- data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
- data/lib/phronomy/vector_store.rb +2 -17
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/completion.rb +11 -0
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
- data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
- data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
- data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy.rb +97 -98
- data/scripts/api_snapshot.rb +36 -16
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/_private/phronomy/agent/settings.rbs +19 -0
- data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
- data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
- data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
- data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
- data/sig/_private/phronomy/tool/settings.rbs +13 -0
- data/sig/_private/phronomy/tracing/settings.rbs +15 -0
- data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
- data/sig/phronomy/agent/admission.rbs +19 -0
- data/sig/phronomy/agent/execution_contract.rbs +132 -0
- data/sig/phronomy/agent/execution_environment.rbs +20 -0
- data/sig/phronomy/agent/store.rbs +73 -0
- data/sig/phronomy/agent.rbs +14 -139
- data/sig/phronomy/common.rbs +7 -0
- data/sig/phronomy/content_store/contracts.rbs +13 -0
- data/sig/phronomy/context.rbs +152 -0
- data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
- data/sig/phronomy/embeddings/base.rbs +17 -0
- data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
- data/sig/phronomy/engine/control_bindings.rbs +11 -0
- data/sig/phronomy/engine/control_protocols.rbs +23 -0
- data/sig/phronomy/engine/execution_binding.rbs +6 -0
- data/sig/phronomy/engine/execution_receiver.rbs +15 -0
- data/sig/phronomy/engine/observability.rbs +13 -0
- data/sig/phronomy/execution/async_operation.rbs +7 -0
- data/sig/phronomy/execution/blocking.rbs +5 -0
- data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
- data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
- data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
- data/sig/phronomy/execution/control_protocols.rbs +18 -0
- data/sig/phronomy/execution/errors.rbs +7 -0
- data/sig/phronomy/execution/execution.rbs +41 -0
- data/sig/phronomy/execution/execution_receiver.rbs +40 -0
- data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
- data/sig/phronomy/execution/invocation_context.rbs +18 -0
- data/sig/phronomy/execution/invocation_controls.rbs +13 -0
- data/sig/phronomy/execution/task_result.rbs +29 -0
- data/sig/phronomy/extensions.rbs +0 -21
- data/sig/phronomy/handoff.rbs +12 -4
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
- data/sig/phronomy/llm_adapter/base.rbs +14 -0
- data/sig/phronomy/llm_adapter/usage.rbs +14 -0
- data/sig/phronomy/llm_adapter/values.rbs +43 -0
- data/sig/phronomy/llm_context_window.rbs +10 -0
- data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
- data/sig/phronomy/multi_agent/store.rbs +54 -0
- data/sig/phronomy/multi_agent.rbs +24 -3
- data/sig/phronomy/persistence/errors.rbs +18 -0
- data/sig/phronomy/persistence/migration.rbs +13 -0
- data/sig/phronomy/persistence/transaction.rbs +19 -0
- data/sig/phronomy/persistence.rbs +3 -185
- data/sig/phronomy/persistence_composition.rbs +19 -0
- data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
- data/sig/phronomy/storage/async/async_client.rbs +8 -0
- data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
- data/sig/phronomy/storage/contracts.rbs +160 -0
- data/sig/phronomy/tool.rbs +58 -5
- data/sig/phronomy/tracing/observation.rbs +7 -0
- data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
- data/sig/phronomy/vector_store/base.rbs +23 -0
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
- data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
- data/sig/phronomy/workflow/store.rbs +8 -0
- data/sig/phronomy/workflow.rbs +1 -1
- data/sig/phronomy.rbs +0 -49
- metadata +462 -130
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
- data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
- data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
- data/lib/phronomy/agent/concerns/filterable.rb +0 -184
- data/lib/phronomy/agent/context/capability/base.rb +0 -507
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
- data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
- data/lib/phronomy/agent/context_policies/default.rb +0 -142
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/handoff_request.rb +0 -45
- data/lib/phronomy/agent/handoff_runner.rb +0 -178
- data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
- data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/selection/candidate.rb +0 -53
- data/lib/phronomy/agent/selection/constraint.rb +0 -49
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
- data/lib/phronomy/configuration.rb +0 -59
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
- data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
- data/lib/phronomy/metrics.rb +0 -26
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
- data/lib/phronomy/persistence/durable_codec.rb +0 -706
- data/lib/phronomy/persistence/in_memory.rb +0 -690
- data/lib/phronomy/persistence/repository_facades.rb +0 -535
- data/lib/phronomy/persistence.rb +0 -276
- data/lib/phronomy/recovery.rb +0 -186
- data/lib/phronomy/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/runnable.rb +0 -54
- data/lib/phronomy/token_usage.rb +0 -85
- data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
- data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
- data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
- data/lib/phronomy/workflow_recovery.rb +0 -123
- data/sig/phronomy/llm_adapter.rbs +0 -11
- data/sig/phronomy/runtime.rbs +0 -76
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Context
|
|
5
|
+
class DefaultPolicy < ContextPolicy
|
|
6
|
+
CONVERSATION_SHARE = 0.60
|
|
7
|
+
|
|
8
|
+
def self.instance
|
|
9
|
+
@instance ||= new.freeze
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(input)
|
|
13
|
+
unless input.is_a?(ContextPolicyInput)
|
|
14
|
+
raise ArgumentError, "Default ContextPolicy expected ContextPolicyInput"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return all_context(input) unless input.token_budget
|
|
18
|
+
|
|
19
|
+
limit = input.token_budget.effective_input_limit
|
|
20
|
+
required_knowledge = input.knowledge.select(&:required?)
|
|
21
|
+
optional_knowledge = input.knowledge.reject(&:required?)
|
|
22
|
+
required_conversation = input.conversation.select { |group| group.any?(&:required?) }
|
|
23
|
+
optional_conversation = input.conversation.reject { |group| group.any?(&:required?) }
|
|
24
|
+
|
|
25
|
+
fixed_cost = item_cost(input.instruction) + item_cost(input.tools) +
|
|
26
|
+
item_cost(required_knowledge) + group_cost(required_conversation)
|
|
27
|
+
if fixed_cost > limit
|
|
28
|
+
raise Phronomy::Context::BudgetExceededError,
|
|
29
|
+
"Required Context (estimated #{fixed_cost} tokens) exceeds " \
|
|
30
|
+
"available input budget (#{limit} tokens)"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
remaining = limit - fixed_cost
|
|
34
|
+
conversation_budget = (remaining * CONVERSATION_SHARE).floor
|
|
35
|
+
knowledge_budget = remaining - conversation_budget
|
|
36
|
+
|
|
37
|
+
selected_conversation, older_conversation, conversation_used =
|
|
38
|
+
select_recent_suffix(optional_conversation, conversation_budget)
|
|
39
|
+
selected_knowledge, remaining_knowledge, knowledge_used =
|
|
40
|
+
select_stable_fit(optional_knowledge, knowledge_budget)
|
|
41
|
+
|
|
42
|
+
reusable = remaining - conversation_used - knowledge_used
|
|
43
|
+
if reusable.positive? && !older_conversation.empty?
|
|
44
|
+
more_conversation, _, used =
|
|
45
|
+
select_recent_suffix(older_conversation, reusable)
|
|
46
|
+
selected_conversation = more_conversation + selected_conversation
|
|
47
|
+
reusable -= used
|
|
48
|
+
end
|
|
49
|
+
if reusable.positive? && !remaining_knowledge.empty?
|
|
50
|
+
more_knowledge, _remaining_knowledge, used =
|
|
51
|
+
select_stable_fit(remaining_knowledge, reusable)
|
|
52
|
+
selected_knowledge += more_knowledge
|
|
53
|
+
reusable -= used
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
conversation = (required_conversation + selected_conversation)
|
|
57
|
+
.uniq
|
|
58
|
+
.sort_by { |group| group_sequence(group) }
|
|
59
|
+
knowledge_ids = (required_knowledge + selected_knowledge).to_h { |item| [item.id, true] }
|
|
60
|
+
knowledge = input.knowledge.select { |item| knowledge_ids[item.id] }
|
|
61
|
+
|
|
62
|
+
plan(
|
|
63
|
+
instruction: input.instruction,
|
|
64
|
+
knowledge: knowledge,
|
|
65
|
+
tools: input.tools,
|
|
66
|
+
conversation: conversation,
|
|
67
|
+
metadata: {
|
|
68
|
+
"default_policy" => true,
|
|
69
|
+
"estimated_fixed_tokens" => fixed_cost,
|
|
70
|
+
"estimated_unused_tokens" => reusable
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def all_context(input)
|
|
78
|
+
plan(
|
|
79
|
+
instruction: input.instruction,
|
|
80
|
+
knowledge: input.knowledge,
|
|
81
|
+
tools: input.tools,
|
|
82
|
+
conversation: input.conversation,
|
|
83
|
+
metadata: {"default_policy" => true}
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def item_cost(items)
|
|
88
|
+
Array(items).sum { |item| Integer(item.estimated_tokens || 0) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def group_cost(groups)
|
|
92
|
+
Array(groups).sum { |group| item_cost(group) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def group_sequence(group)
|
|
96
|
+
group.filter_map(&:sequence).min || 0
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def select_recent_suffix(groups, budget)
|
|
100
|
+
ordered = Array(groups).sort_by { |group| group_sequence(group) }
|
|
101
|
+
selected = []
|
|
102
|
+
remaining = Integer(budget)
|
|
103
|
+
stop_index = -1
|
|
104
|
+
|
|
105
|
+
(ordered.length - 1).downto(0) do |index|
|
|
106
|
+
cost = item_cost(ordered[index])
|
|
107
|
+
if cost > remaining
|
|
108
|
+
stop_index = index
|
|
109
|
+
break
|
|
110
|
+
end
|
|
111
|
+
selected.unshift(ordered[index])
|
|
112
|
+
remaining -= cost
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
older = if stop_index >= 0
|
|
116
|
+
ordered[0..stop_index]
|
|
117
|
+
else
|
|
118
|
+
[]
|
|
119
|
+
end
|
|
120
|
+
[selected.freeze, older.freeze, Integer(budget) - remaining]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def select_stable_fit(items, budget)
|
|
124
|
+
remaining = Integer(budget)
|
|
125
|
+
selected = []
|
|
126
|
+
unselected = []
|
|
127
|
+
Array(items).each do |item|
|
|
128
|
+
cost = Integer(item.estimated_tokens || 0)
|
|
129
|
+
if cost <= remaining
|
|
130
|
+
selected << item
|
|
131
|
+
remaining -= cost
|
|
132
|
+
else
|
|
133
|
+
unselected << item
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
[selected.freeze, unselected.freeze, Integer(budget) - remaining]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Context
|
|
5
|
+
class FinalBudgetValidator
|
|
6
|
+
def initialize(content_loader:)
|
|
7
|
+
@content_loader = content_loader
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def validate!(token_budget:, segments:, extra_values: [])
|
|
11
|
+
return 0 unless token_budget
|
|
12
|
+
|
|
13
|
+
estimated = Array(segments).sum do |segment|
|
|
14
|
+
Phronomy::LlmContextWindow::TokenEstimator.estimate(
|
|
15
|
+
@content_loader.call(segment.fetch(:content_ref))
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
estimated += Array(extra_values).sum do |value|
|
|
19
|
+
bytes = value.is_a?(String) ? value : Phronomy::CanonicalJSON.dump(value)
|
|
20
|
+
Phronomy::LlmContextWindow::TokenEstimator.estimate(bytes)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
limit = token_budget.effective_input_limit
|
|
24
|
+
if estimated > limit
|
|
25
|
+
raise Phronomy::Context::BudgetExceededError,
|
|
26
|
+
"Canonical LLM input (estimated #{estimated} tokens) exceeds " \
|
|
27
|
+
"available input budget (#{limit} tokens)"
|
|
28
|
+
end
|
|
29
|
+
estimated
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Context
|
|
5
5
|
# Immutable metadata passed to before_llm_input hooks. Runtime objects such
|
|
6
6
|
# as RubyLLM::Chat, messages and the mutable Agent instance are not exposed.
|
|
7
7
|
LLMInputBuildContext = Data.define(
|
|
@@ -9,7 +9,7 @@ module Phronomy
|
|
|
9
9
|
:config, :call_sequence
|
|
10
10
|
) do
|
|
11
11
|
def initialize(**values)
|
|
12
|
-
super(**values.merge(config: Immutable.copy(values.fetch(:config, {}))))
|
|
12
|
+
super(**values.merge(config: Phronomy::Values::Immutable.copy(values.fetch(:config, {}))))
|
|
13
13
|
freeze
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Context
|
|
5
5
|
# ContentStore-backed durable codec boundary for one Provider input manifest.
|
|
6
|
-
# It is intentionally separate from
|
|
6
|
+
# It is intentionally separate from Storage::DurableRecord.
|
|
7
7
|
class LLMInputManifest
|
|
8
8
|
VERSION = "0.1"
|
|
9
9
|
CALL_MODES = %i[ask complete].freeze
|
|
@@ -18,7 +18,7 @@ module Phronomy
|
|
|
18
18
|
if delivery.nil? || !SEGMENT_DELIVERIES.include?(delivery.to_s)
|
|
19
19
|
raise ArgumentError, "unknown Segment delivery: #{delivery.inspect}"
|
|
20
20
|
end
|
|
21
|
-
super(**values.merge(metadata: Immutable.copy(values[:metadata] || {})))
|
|
21
|
+
super(**values.merge(metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})))
|
|
22
22
|
freeze
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -62,7 +62,7 @@ module Phronomy
|
|
|
62
62
|
label: label
|
|
63
63
|
)
|
|
64
64
|
unless SEGMENT_DELIVERIES.include?(delivery)
|
|
65
|
-
raise Phronomy::
|
|
65
|
+
raise Phronomy::Context::ManifestError,
|
|
66
66
|
"LLMInputManifest segment delivery must be one of #{SEGMENT_DELIVERIES.inspect}, got #{delivery.inspect}"
|
|
67
67
|
end
|
|
68
68
|
tool_call_id = LLMInputManifest.send(
|
|
@@ -87,10 +87,10 @@ module Phronomy
|
|
|
87
87
|
tool_call_id: tool_call_id,
|
|
88
88
|
metadata: metadata
|
|
89
89
|
)
|
|
90
|
-
rescue Phronomy::
|
|
90
|
+
rescue Phronomy::Context::ManifestError
|
|
91
91
|
raise
|
|
92
92
|
rescue => error
|
|
93
|
-
raise Phronomy::
|
|
93
|
+
raise Phronomy::Context::ManifestError,
|
|
94
94
|
"invalid LLMInputManifest segment: #{error.class}: #{error.message}"
|
|
95
95
|
end
|
|
96
96
|
|
|
@@ -162,7 +162,7 @@ module Phronomy
|
|
|
162
162
|
label = "LLMInputManifest"
|
|
163
163
|
version = source.fetch("version")
|
|
164
164
|
unless version.is_a?(String) && version == VERSION
|
|
165
|
-
raise Phronomy::
|
|
165
|
+
raise Phronomy::Context::ManifestError,
|
|
166
166
|
"unsupported LLMInputManifest version: #{version.inspect}; " \
|
|
167
167
|
"current version is #{VERSION.inspect}"
|
|
168
168
|
end
|
|
@@ -229,10 +229,10 @@ module Phronomy
|
|
|
229
229
|
adapter_name: adapter_name,
|
|
230
230
|
adapter_version: adapter_version
|
|
231
231
|
)
|
|
232
|
-
rescue Phronomy::
|
|
232
|
+
rescue Phronomy::Context::ManifestError
|
|
233
233
|
raise
|
|
234
234
|
rescue => error
|
|
235
|
-
raise Phronomy::
|
|
235
|
+
raise Phronomy::Context::ManifestError,
|
|
236
236
|
"invalid LLMInputManifest: #{error.class}: #{error.message}"
|
|
237
237
|
end
|
|
238
238
|
|
|
@@ -262,11 +262,11 @@ module Phronomy
|
|
|
262
262
|
|
|
263
263
|
def strict_source!(hash, required:, optional:, label:)
|
|
264
264
|
unless hash.is_a?(Hash)
|
|
265
|
-
raise Phronomy::
|
|
265
|
+
raise Phronomy::Context::ManifestError,
|
|
266
266
|
"#{label} must be a Hash"
|
|
267
267
|
end
|
|
268
268
|
unless hash.keys.all? { |key| key.is_a?(String) }
|
|
269
|
-
raise Phronomy::
|
|
269
|
+
raise Phronomy::Context::ManifestError,
|
|
270
270
|
"#{label} keys must all be String"
|
|
271
271
|
end
|
|
272
272
|
|
|
@@ -277,13 +277,13 @@ module Phronomy
|
|
|
277
277
|
details = []
|
|
278
278
|
details << "missing=#{missing.inspect}" unless missing.empty?
|
|
279
279
|
details << "unknown=#{unknown.inspect}" unless unknown.empty?
|
|
280
|
-
raise Phronomy::
|
|
280
|
+
raise Phronomy::Context::ManifestError,
|
|
281
281
|
"#{label} schema mismatch (#{details.join(", ")})"
|
|
282
282
|
end
|
|
283
283
|
Phronomy::CanonicalJSON.dump(hash)
|
|
284
284
|
hash
|
|
285
285
|
rescue ArgumentError => error
|
|
286
|
-
raise Phronomy::
|
|
286
|
+
raise Phronomy::Context::ManifestError,
|
|
287
287
|
"#{label} is not canonical JSON compatible: #{error.message}"
|
|
288
288
|
end
|
|
289
289
|
|
|
@@ -291,7 +291,7 @@ module Phronomy
|
|
|
291
291
|
value = hash.fetch(key)
|
|
292
292
|
return value if value.is_a?(Integer)
|
|
293
293
|
|
|
294
|
-
raise Phronomy::
|
|
294
|
+
raise Phronomy::Context::ManifestError,
|
|
295
295
|
"#{label} #{key} must be an Integer"
|
|
296
296
|
end
|
|
297
297
|
|
|
@@ -299,7 +299,7 @@ module Phronomy
|
|
|
299
299
|
value = hash.fetch(key)
|
|
300
300
|
return value if value.is_a?(Integer) && value.positive?
|
|
301
301
|
|
|
302
|
-
raise Phronomy::
|
|
302
|
+
raise Phronomy::Context::ManifestError,
|
|
303
303
|
"#{label} #{key} must be a positive Integer"
|
|
304
304
|
end
|
|
305
305
|
|
|
@@ -307,7 +307,7 @@ module Phronomy
|
|
|
307
307
|
value = hash.fetch(key)
|
|
308
308
|
return value if value.is_a?(Integer) && value >= 0
|
|
309
309
|
|
|
310
|
-
raise Phronomy::
|
|
310
|
+
raise Phronomy::Context::ManifestError,
|
|
311
311
|
"#{label} #{key} must be a non-negative Integer"
|
|
312
312
|
end
|
|
313
313
|
|
|
@@ -315,7 +315,7 @@ module Phronomy
|
|
|
315
315
|
value = hash.fetch(key)
|
|
316
316
|
return value if value.is_a?(String) && !value.empty?
|
|
317
317
|
|
|
318
|
-
raise Phronomy::
|
|
318
|
+
raise Phronomy::Context::ManifestError,
|
|
319
319
|
"#{label} #{key} must be a non-empty String"
|
|
320
320
|
end
|
|
321
321
|
|
|
@@ -323,7 +323,7 @@ module Phronomy
|
|
|
323
323
|
value = hash[key]
|
|
324
324
|
return value if value.nil? || value.is_a?(String)
|
|
325
325
|
|
|
326
|
-
raise Phronomy::
|
|
326
|
+
raise Phronomy::Context::ManifestError,
|
|
327
327
|
"#{label} #{key} must be a String or nil"
|
|
328
328
|
end
|
|
329
329
|
|
|
@@ -331,7 +331,7 @@ module Phronomy
|
|
|
331
331
|
value = hash.fetch(key)
|
|
332
332
|
return value if value.is_a?(String) && allowed.include?(value)
|
|
333
333
|
|
|
334
|
-
raise Phronomy::
|
|
334
|
+
raise Phronomy::Context::ManifestError,
|
|
335
335
|
"#{label} #{key} must be one of #{allowed.inspect}"
|
|
336
336
|
end
|
|
337
337
|
|
|
@@ -339,7 +339,7 @@ module Phronomy
|
|
|
339
339
|
value = hash.fetch(key)
|
|
340
340
|
return value if value.is_a?(Array)
|
|
341
341
|
|
|
342
|
-
raise Phronomy::
|
|
342
|
+
raise Phronomy::Context::ManifestError,
|
|
343
343
|
"#{label} #{key} must be an Array"
|
|
344
344
|
end
|
|
345
345
|
|
|
@@ -347,7 +347,7 @@ module Phronomy
|
|
|
347
347
|
value = hash.fetch(key)
|
|
348
348
|
return value if value.is_a?(Hash)
|
|
349
349
|
|
|
350
|
-
raise Phronomy::
|
|
350
|
+
raise Phronomy::Context::ManifestError,
|
|
351
351
|
"#{label} #{key} must be a Hash"
|
|
352
352
|
end
|
|
353
353
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Context
|
|
5
5
|
# Typed, canonicalizable input customization returned by before_llm_input.
|
|
6
6
|
# v1 deliberately exposes only fields that are fully applied to every call.
|
|
7
7
|
LLMInputPatch = Data.define(:model_config_patch, :segment_candidates) do
|
|
8
8
|
def initialize(model_config_patch: nil, segment_candidates: nil)
|
|
9
9
|
super(
|
|
10
|
-
model_config_patch: model_config_patch && Immutable.copy(model_config_patch),
|
|
11
|
-
segment_candidates: segment_candidates && Immutable.copy(Array(segment_candidates))
|
|
10
|
+
model_config_patch: model_config_patch && Phronomy::Values::Immutable.copy(model_config_patch),
|
|
11
|
+
segment_candidates: segment_candidates && Phronomy::Values::Immutable.copy(Array(segment_candidates))
|
|
12
12
|
)
|
|
13
13
|
freeze
|
|
14
14
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Context
|
|
5
|
+
# A prompt template that substitutes {{variable}} placeholders in a string.
|
|
6
|
+
#
|
|
7
|
+
# @example Simple human template
|
|
8
|
+
# t = Phronomy::Context::PromptTemplate.new(template: "Translate to {{lang}}: {{text}}")
|
|
9
|
+
# t.format(lang: "French", text: "Hello")
|
|
10
|
+
# # => "Translate to French: Hello"
|
|
11
|
+
#
|
|
12
|
+
# @example With a system template
|
|
13
|
+
# t = Phronomy::Context::PromptTemplate.new(
|
|
14
|
+
# template: "{{question}}",
|
|
15
|
+
# system_template: "You are a {{role}} assistant."
|
|
16
|
+
# )
|
|
17
|
+
# t.format_system(role: "helpful")
|
|
18
|
+
# # => "You are a helpful assistant."
|
|
19
|
+
#
|
|
20
|
+
# As a Runnable, #invoke accepts a Hash of variables and returns a Hash
|
|
21
|
+
# with :prompt (and optionally :system) keys.
|
|
22
|
+
class PromptTemplate
|
|
23
|
+
include Phronomy::Runnable
|
|
24
|
+
|
|
25
|
+
PLACEHOLDER = /\{\{(\w+)\}\}/
|
|
26
|
+
|
|
27
|
+
attr_reader :template, :system_template
|
|
28
|
+
|
|
29
|
+
# @param template [String] human message template with {{var}} placeholders
|
|
30
|
+
# @param system_template [String, nil] optional system message template
|
|
31
|
+
# @api public
|
|
32
|
+
def initialize(template:, system_template: nil)
|
|
33
|
+
@template = template
|
|
34
|
+
@system_template = system_template
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Substitute all {{var}} placeholders in the human template.
|
|
38
|
+
#
|
|
39
|
+
# @param variables [Hash{Symbol => String}]
|
|
40
|
+
# @return [String]
|
|
41
|
+
# @api public
|
|
42
|
+
def format(**variables)
|
|
43
|
+
substitute(@template, variables)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Substitute all {{var}} placeholders in the system template.
|
|
47
|
+
# Returns nil when no system template was set.
|
|
48
|
+
#
|
|
49
|
+
# @param variables [Hash{Symbol => String}]
|
|
50
|
+
# @return [String, nil]
|
|
51
|
+
# @api public
|
|
52
|
+
def format_system(**variables)
|
|
53
|
+
@system_template && substitute(@system_template, variables)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Runnable interface: accepts a Hash of variable values.
|
|
57
|
+
# Returns { prompt: String, system: String|nil }.
|
|
58
|
+
#
|
|
59
|
+
# @param input [Hash{Symbol => String}]
|
|
60
|
+
# @return [Hash]
|
|
61
|
+
# @api public
|
|
62
|
+
def invoke(input, config: {})
|
|
63
|
+
vars = normalize_input(input)
|
|
64
|
+
result = {prompt: format(**vars)}
|
|
65
|
+
sys = format_system(**vars)
|
|
66
|
+
result[:system] = sys if sys
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns the list of placeholder names found in both templates.
|
|
71
|
+
#
|
|
72
|
+
# @return [Array<Symbol>]
|
|
73
|
+
# @api public
|
|
74
|
+
def variables
|
|
75
|
+
names = @template.scan(PLACEHOLDER).flatten
|
|
76
|
+
names += @system_template.scan(PLACEHOLDER).flatten if @system_template
|
|
77
|
+
names.map(&:to_sym).uniq
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def substitute(text, variables)
|
|
83
|
+
text.gsub(PLACEHOLDER) do |match|
|
|
84
|
+
key = Regexp.last_match(1).to_sym
|
|
85
|
+
variables.fetch(key) { raise KeyError, "Missing variable: {{#{key}}}" }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def normalize_input(input)
|
|
90
|
+
case input
|
|
91
|
+
when Hash then input
|
|
92
|
+
when String then {input: input}
|
|
93
|
+
else raise ArgumentError, "PromptTemplate#invoke expects a Hash of variables, got #{input.class}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "csv"
|
|
4
4
|
|
|
5
5
|
module Phronomy
|
|
6
|
-
module
|
|
6
|
+
module Documents
|
|
7
7
|
module Loader
|
|
8
8
|
# Loads a CSV file, converting each row into a separate document.
|
|
9
9
|
#
|
|
@@ -12,7 +12,7 @@ module Phronomy
|
|
|
12
12
|
# a human-readable "key: value" string for embedding.
|
|
13
13
|
#
|
|
14
14
|
# @example
|
|
15
|
-
# loader = Phronomy::
|
|
15
|
+
# loader = Phronomy::Documents::Loader::CsvLoader.new
|
|
16
16
|
# docs = loader.load("products.csv")
|
|
17
17
|
# # => [
|
|
18
18
|
# # { text: "name: Widget\nprice: 9.99", metadata: { source: "...", row: 1, name: "Widget", price: "9.99" } },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Documents
|
|
5
5
|
module Loader
|
|
6
6
|
# Loads a Markdown file, optionally splitting on top-level headings.
|
|
7
7
|
#
|
|
@@ -10,12 +10,12 @@ module Phronomy
|
|
|
10
10
|
# rather than the full file at once.
|
|
11
11
|
#
|
|
12
12
|
# @example Single document (heading split disabled)
|
|
13
|
-
# loader = Phronomy::
|
|
13
|
+
# loader = Phronomy::Documents::Loader::MarkdownLoader.new(split_on_headings: false)
|
|
14
14
|
# docs = loader.load("README.md")
|
|
15
15
|
# # => [{ text: "# Title\n...", metadata: { source: "README.md" } }]
|
|
16
16
|
#
|
|
17
17
|
# @example Split per heading (default)
|
|
18
|
-
# loader = Phronomy::
|
|
18
|
+
# loader = Phronomy::Documents::Loader::MarkdownLoader.new
|
|
19
19
|
# docs = loader.load("guide.md")
|
|
20
20
|
# # => [
|
|
21
21
|
# # { text: "# Section 1\n...", metadata: { source: "guide.md", section: "Section 1" } },
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Documents
|
|
5
5
|
module Loader
|
|
6
6
|
# Loads a plain-text file as a single document.
|
|
7
7
|
#
|
|
8
8
|
# @example
|
|
9
|
-
# loader = Phronomy::
|
|
9
|
+
# loader = Phronomy::Documents::Loader::PlainTextLoader.new
|
|
10
10
|
# docs = loader.load("/path/to/file.txt")
|
|
11
11
|
# # => [{ text: "...", metadata: { source: "/path/to/file.txt" } }]
|
|
12
12
|
class PlainTextLoader < Base
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Documents
|
|
5
5
|
module Splitter
|
|
6
6
|
# Splits text into fixed-size character chunks with optional overlap.
|
|
7
7
|
#
|
|
8
8
|
# @example
|
|
9
|
-
# splitter = Phronomy::
|
|
9
|
+
# splitter = Phronomy::Documents::Splitter::FixedSizeSplitter.new(chunk_size: 200, chunk_overlap: 20)
|
|
10
10
|
# chunks = splitter.split({ text: long_text, metadata: { source: "doc.txt" } })
|
|
11
11
|
# # => [
|
|
12
12
|
# # { text: "...(200 chars)...", metadata: { source: "doc.txt", chunk: 0 } },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
4
|
+
module Documents
|
|
5
5
|
module Splitter
|
|
6
6
|
# Splits text recursively using a prioritised list of separator strings.
|
|
7
7
|
#
|
|
@@ -18,7 +18,7 @@ module Phronomy
|
|
|
18
18
|
# 5. "" — character-level fallback
|
|
19
19
|
#
|
|
20
20
|
# @example
|
|
21
|
-
# splitter = Phronomy::
|
|
21
|
+
# splitter = Phronomy::Documents::Splitter::RecursiveSplitter.new(chunk_size: 300, chunk_overlap: 30)
|
|
22
22
|
# chunks = splitter.split({ text: long_markdown, metadata: { source: "guide.md" } })
|
|
23
23
|
class RecursiveSplitter < Base
|
|
24
24
|
DEFAULT_SEPARATORS = ["\n\n", "\n", ". ", " ", ""].freeze
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Embeddings
|
|
5
|
+
# Framework-owned asynchronous execution of a synchronous embedding adapter.
|
|
6
|
+
#
|
|
7
|
+
# Construction starts no Runtime. Each operation uses the current default
|
|
8
|
+
# pool unless a caller-owned pool was injected. Timeout includes queue wait
|
|
9
|
+
# and settles the result without forcibly interrupting an executing adapter.
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
class AsyncClient
|
|
13
|
+
# @param adapter [Embeddings::Base] synchronous embedding adapter
|
|
14
|
+
# @param pool [Concurrency::OffloadPool, nil] optional execution pool
|
|
15
|
+
# @api public
|
|
16
|
+
def initialize(adapter:, pool: nil)
|
|
17
|
+
@adapter = adapter
|
|
18
|
+
@pool = pool
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Submits a synchronous embed call without waiting for queue capacity.
|
|
22
|
+
#
|
|
23
|
+
# @param text [String]
|
|
24
|
+
# @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
|
|
25
|
+
# @param timeout [Numeric, nil] operation-wide submit timeout
|
|
26
|
+
# @return [Phronomy::TaskResult] the pool's original completion handle
|
|
27
|
+
# @api public
|
|
28
|
+
def embed_async(text, cancellation_token = nil, timeout: nil)
|
|
29
|
+
Phronomy::Execution.submit(pool: default_pool,
|
|
30
|
+
timeout: timeout,
|
|
31
|
+
cancellation_token: cancellation_token,
|
|
32
|
+
on_full: :raise) do
|
|
33
|
+
@adapter.embed(text, cancellation_token)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def default_pool
|
|
40
|
+
@pool
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|