phronomy 0.27.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 +52 -0
- data/CONTRIBUTING.md +8 -8
- data/README.md +9 -3
- 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/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/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/features.md +8 -3
- data/docs/getting-started.md +9 -6
- 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/persistence-backends.md +38 -21
- data/docs/runtime-and-concurrency.md +26 -1
- 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/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
- data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
- data/lib/phronomy/agent/async_event_api.rb +37 -32
- data/lib/phronomy/agent/base.rb +74 -72
- data/lib/phronomy/agent/before_llm_input.rb +64 -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 +44 -23
- data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
- data/lib/phronomy/agent/execution/execution_coordinator.rb +214 -395
- data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +70 -126
- 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 +18 -116
- 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 +7 -7
- 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/{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/ownership_registry.rb +18 -23
- 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 +10 -9
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +27 -27
- data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +19 -21
- data/lib/phronomy/agent/recovery/recovery_coordinator.rb +23 -16
- data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
- data/lib/phronomy/agent/recovery/recovery_support.rb +8 -11
- 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} +1 -1
- 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/configuration/runtime_settings.rb +49 -0
- 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 +202 -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/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 +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/execution/execution_receiver.rb +78 -0
- data/lib/phronomy/execution/execution_scope_state.rb +35 -0
- 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 +3 -3
- data/lib/phronomy/execution/wait_policy.rb +24 -0
- data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +1 -1
- 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/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 +58 -42
- 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_coordinator.rb +83 -74
- 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/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 +549 -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 +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/execution/workflow.rb +7 -7
- 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 +27 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +3 -5
- data/lib/phronomy/workflow/execution/workflow_runner.rb +58 -79
- data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -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 +38 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
- data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
- data/lib/phronomy.rb +57 -16
- data/scripts/api_snapshot.rb +35 -15
- 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 -138
- 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 +10 -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 +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} +2 -16
- 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 +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 +318 -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}/execution_cancellation_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/execution_timeout_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
|
@@ -11,12 +11,27 @@ module Phronomy
|
|
|
11
11
|
# Scheduling and aggregation must be pure, replay-safe result calculations.
|
|
12
12
|
# @api public
|
|
13
13
|
class TeamCoordinator
|
|
14
|
-
WorkerState = Data.define(:index, :agent_id, :
|
|
14
|
+
WorkerState = Data.define(:index, :agent_id, :assignment_count, :status) do
|
|
15
15
|
def available? = status == :available
|
|
16
16
|
end
|
|
17
17
|
private_constant :WorkerState
|
|
18
18
|
|
|
19
|
+
# Raised before writing an operation; update still verifies readback.
|
|
20
|
+
OperationRejected = Class.new(Phronomy::ConfigurationError)
|
|
21
|
+
private_constant :OperationRejected
|
|
22
|
+
|
|
19
23
|
class << self
|
|
24
|
+
# Boot composition supplies a fresh store without selecting it here.
|
|
25
|
+
# @api private
|
|
26
|
+
def install_persistence_factory(factory)
|
|
27
|
+
raise ArgumentError, "factory must respond to call" unless factory.respond_to?(:call)
|
|
28
|
+
raise Phronomy::ConfigurationError, "Team persistence factory already installed" if @persistence_factory
|
|
29
|
+
@persistence_factory = factory
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @api private
|
|
33
|
+
def build_default_persistence = @persistence_factory.call
|
|
34
|
+
|
|
20
35
|
# @api public
|
|
21
36
|
def team_definition(id: nil, version: nil)
|
|
22
37
|
if id || version
|
|
@@ -69,7 +84,7 @@ module Phronomy
|
|
|
69
84
|
|
|
70
85
|
# @api public
|
|
71
86
|
def get(team_id)
|
|
72
|
-
|
|
87
|
+
ExecutionEnvironment.current.existing_ownership&.get(team_id.to_s, klass: self)
|
|
73
88
|
end
|
|
74
89
|
|
|
75
90
|
# @api private
|
|
@@ -92,14 +107,14 @@ module Phronomy
|
|
|
92
107
|
private
|
|
93
108
|
|
|
94
109
|
def construct(id, persistence, metadata, listener, create:)
|
|
95
|
-
raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::
|
|
110
|
+
raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
|
|
96
111
|
key = id.to_s
|
|
97
112
|
raise ArgumentError, "team_id must not be empty" if key.empty?
|
|
98
|
-
store = persistence || Phronomy.
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
store = persistence || Phronomy::MultiAgent::Store.configured || TeamCoordinator.build_default_persistence
|
|
114
|
+
environment = ExecutionEnvironment.current
|
|
115
|
+
environment.ownership.fetch(key, klass: self, create: create, persistence: store) do
|
|
101
116
|
instance = allocate
|
|
102
|
-
instance.send(:initialize, key.freeze, store, metadata, listener,
|
|
117
|
+
instance.send(:initialize, key.freeze, store, metadata, listener, environment, create: create)
|
|
103
118
|
instance
|
|
104
119
|
end
|
|
105
120
|
end
|
|
@@ -134,12 +149,12 @@ module Phronomy
|
|
|
134
149
|
# @api public
|
|
135
150
|
def result(team_execution_id)
|
|
136
151
|
execution = read_execution(team_execution_id)
|
|
137
|
-
persistence.
|
|
152
|
+
persistence.result(execution.team_execution_id)
|
|
138
153
|
end
|
|
139
154
|
|
|
140
155
|
# @api public
|
|
141
156
|
def executions(after: nil, limit: 100)
|
|
142
|
-
persistence.
|
|
157
|
+
persistence.runs(team_id, after: after, limit: limit)
|
|
143
158
|
end
|
|
144
159
|
|
|
145
160
|
# Requests cancellation of this run only. Existing Agent cancellation and
|
|
@@ -159,9 +174,9 @@ module Phronomy
|
|
|
159
174
|
|
|
160
175
|
private
|
|
161
176
|
|
|
162
|
-
def initialize(id, store, metadata, listener,
|
|
163
|
-
@team_id, @persistence, @listener, @
|
|
164
|
-
@admissions =
|
|
177
|
+
def initialize(id, store, metadata, listener, environment, create:)
|
|
178
|
+
@team_id, @persistence, @listener, @environment = id, store, listener, environment
|
|
179
|
+
@admissions = @environment.admissions
|
|
165
180
|
@tokens_mutex, @tokens = Mutex.new, {}
|
|
166
181
|
@coordinator_classes = {}
|
|
167
182
|
definition = self.class.team_definition
|
|
@@ -184,8 +199,8 @@ module Phronomy
|
|
|
184
199
|
end
|
|
185
200
|
|
|
186
201
|
def assert_caller!
|
|
187
|
-
raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::
|
|
188
|
-
unless @
|
|
202
|
+
raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
|
|
203
|
+
unless @environment.current?
|
|
189
204
|
raise Phronomy::RuntimeShutdownError, "Team #{team_id} belongs to a previous Runtime"
|
|
190
205
|
end
|
|
191
206
|
end
|
|
@@ -202,7 +217,7 @@ module Phronomy
|
|
|
202
217
|
def read_execution(id)
|
|
203
218
|
execution = persistence.team_executions.load(id)
|
|
204
219
|
unless execution.team_id == team_id
|
|
205
|
-
raise Phronomy::
|
|
220
|
+
raise Phronomy::Persistence::StateConflictError, "Team execution #{id} belongs to another Team"
|
|
206
221
|
end
|
|
207
222
|
execution
|
|
208
223
|
end
|
|
@@ -221,13 +236,13 @@ module Phronomy
|
|
|
221
236
|
now = Time.now.utc.iso8601(6)
|
|
222
237
|
intended = nil
|
|
223
238
|
begin
|
|
224
|
-
persistence.transaction do |tx|
|
|
239
|
+
persistence.transaction do |tx, scope|
|
|
225
240
|
root = tx.teams.load(team_id)
|
|
226
241
|
raise Phronomy::AgentBusyError, "Team #{team_id} has an active run; use resume" unless tx.team_executions.list_active(team_id).empty?
|
|
227
242
|
intended = TeamExecution.new(team_execution_id: id, team_id: team_id,
|
|
228
243
|
execution_revision: 0, status: "active", phase: "coordinator", input_ref: tx.contents.put_text(input),
|
|
229
244
|
coordinator: {"agent_id" => SecureRandom.uuid, "execution_id" => SecureRandom.uuid, "state" => "reserved"},
|
|
230
|
-
workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid
|
|
245
|
+
workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid} },
|
|
231
246
|
tasks: [], assignments: [], result_ref: nil, error_ref: nil, created_at: now, updated_at: now,
|
|
232
247
|
metadata: {"definition" => definition, "operations" => {}, "cancel_requested" => false,
|
|
233
248
|
"durable_context_ref" => config.key?(:durable_context) ? tx.contents.put_json(config.fetch(:durable_context)) : nil})
|
|
@@ -238,7 +253,7 @@ module Phronomy
|
|
|
238
253
|
raise error unless intended
|
|
239
254
|
begin
|
|
240
255
|
confirmed = read_execution(id)
|
|
241
|
-
rescue Phronomy::
|
|
256
|
+
rescue Phronomy::Persistence::NotFoundError
|
|
242
257
|
raise error
|
|
243
258
|
end
|
|
244
259
|
raise error unless confirmed.to_h == intended.to_h
|
|
@@ -252,10 +267,10 @@ module Phronomy
|
|
|
252
267
|
attempts = 0
|
|
253
268
|
begin
|
|
254
269
|
intended = nil
|
|
255
|
-
persistence.transaction do |tx|
|
|
270
|
+
persistence.transaction do |tx, scope|
|
|
256
271
|
current = tx.team_executions.load(id)
|
|
257
|
-
raise Phronomy::
|
|
258
|
-
intended = yield(current, tx)
|
|
272
|
+
raise Phronomy::Persistence::StateConflictError, "Team execution owner mismatch" unless current.team_id == team_id
|
|
273
|
+
intended = yield(current, tx, scope)
|
|
259
274
|
next if intended.equal?(current)
|
|
260
275
|
tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
|
|
261
276
|
if intended.terminal? && current.active?
|
|
@@ -267,7 +282,7 @@ module Phronomy
|
|
|
267
282
|
rescue => error
|
|
268
283
|
confirmed = read_execution(id)
|
|
269
284
|
return confirmed if intended && confirmed.to_h == intended.to_h
|
|
270
|
-
if error.is_a?(Phronomy::
|
|
285
|
+
if error.is_a?(Phronomy::Persistence::ConflictError) && (attempts += 1) < 8
|
|
271
286
|
retry
|
|
272
287
|
end
|
|
273
288
|
raise error
|
|
@@ -282,9 +297,9 @@ module Phronomy
|
|
|
282
297
|
end
|
|
283
298
|
token = Phronomy::Concurrency::CancellationToken.new
|
|
284
299
|
@tokens_mutex.synchronize { @tokens[id] = token }
|
|
285
|
-
|
|
286
|
-
callback = proc { @
|
|
287
|
-
|
|
300
|
+
cancellation_subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
|
|
301
|
+
callback = proc { @environment.submit(on_full: :raise) { cancel(id) } }
|
|
302
|
+
cancellation_subscriptions.explicit_cancellation(config[:cancellation_token], &callback)
|
|
288
303
|
token.cancel! if current.metadata["cancel_requested"]
|
|
289
304
|
begin
|
|
290
305
|
loop do
|
|
@@ -323,13 +338,13 @@ module Phronomy
|
|
|
323
338
|
values = assignment_values(current)
|
|
324
339
|
aggregate = self.class._aggregator
|
|
325
340
|
begin
|
|
326
|
-
output = Phronomy::
|
|
341
|
+
output = Phronomy::Values::Serializable.convert(aggregate ? aggregate.call(values) : values, unsupported_message: "Team result is not canonically serializable")
|
|
327
342
|
Phronomy::CanonicalJSON.dump(output)
|
|
328
343
|
rescue => error
|
|
329
|
-
return terminal_value(finish_error(id,
|
|
344
|
+
return terminal_value(finish_error(id, {"class" => error.class.name.to_s, "message" => error.message.to_s}))
|
|
330
345
|
end
|
|
331
346
|
# Aggregate may be replayed only until the canonical outcome commits.
|
|
332
|
-
completed = update(id) do |fresh, tx|
|
|
347
|
+
completed = update(id) do |fresh, tx, scope|
|
|
333
348
|
next fresh if fresh.terminal? || fresh.metadata["cancel_requested"]
|
|
334
349
|
fresh.with(status: "completed", phase: "completed", result_ref: tx.contents.put_json(output))
|
|
335
350
|
end
|
|
@@ -345,7 +360,7 @@ module Phronomy
|
|
|
345
360
|
terminal_value(finish_error(id, {"class" => "Phronomy::CancellationError", "message" => "Team run cancelled"}, status: "cancelled"))
|
|
346
361
|
end
|
|
347
362
|
ensure
|
|
348
|
-
|
|
363
|
+
cancellation_subscriptions.close
|
|
349
364
|
@tokens_mutex.synchronize { @tokens.delete(id) }
|
|
350
365
|
end
|
|
351
366
|
end
|
|
@@ -383,30 +398,31 @@ module Phronomy
|
|
|
383
398
|
id = slot.fetch("agent_id")
|
|
384
399
|
child = klass.get(id)
|
|
385
400
|
unless child
|
|
386
|
-
|
|
387
|
-
persistence.agents.load(id)
|
|
388
|
-
present = true
|
|
389
|
-
rescue Phronomy::Storage::NotFoundError
|
|
390
|
-
present = false
|
|
391
|
-
end
|
|
401
|
+
present = persistence.agent_store.exist?(id)
|
|
392
402
|
token.raise_if_cancelled! unless present
|
|
393
|
-
child = present ? klass.load(id, persistence: persistence, on_event: @listener) : klass.create(agent_id: id, persistence: persistence, on_event: @listener)
|
|
403
|
+
child = present ? klass.load(id, persistence: persistence.agent_store, on_event: @listener) : klass.create(agent_id: id, persistence: persistence.agent_store, on_event: @listener)
|
|
394
404
|
end
|
|
395
|
-
unless child.persistence.equal?(persistence)
|
|
405
|
+
unless child.persistence.equal?(persistence.agent_store)
|
|
396
406
|
raise Phronomy::ConfigurationError, "Team child #{id} Persistence mismatch"
|
|
397
407
|
end
|
|
398
408
|
child_config = config.merge(cancellation_token: token,
|
|
399
|
-
|
|
400
|
-
|
|
409
|
+
phronomy_reservation: {"kind" => "team", "team_id" => team_id,
|
|
410
|
+
"team_execution_id" => current.team_execution_id, "slot" => purpose})
|
|
411
|
+
child_config = child_config.merge(phronomy_admission: ReservedChildAdmission.new(
|
|
412
|
+
persistence: persistence, owner: child_config.fetch(:phronomy_reservation)
|
|
413
|
+
))
|
|
401
414
|
if (context_ref = current.metadata["durable_context_ref"])
|
|
402
415
|
child_config = child_config.merge(durable_context: persistence.contents.fetch_json(context_ref))
|
|
403
416
|
end
|
|
404
|
-
Phronomy::Agent::
|
|
417
|
+
child.start_reserved_async(reservation: Phronomy::Agent::ReservedExecution.new(agent_id: id, execution_id: slot.fetch("execution_id"), correlation: child_config.fetch(:phronomy_reservation)), input: input, config: child_config).wait_result
|
|
405
418
|
end
|
|
406
419
|
|
|
407
420
|
def reserve_assignment(current, task)
|
|
408
|
-
|
|
409
|
-
|
|
421
|
+
# Team owns this count. Deriving it from retained assignments also
|
|
422
|
+
# makes replay idempotent and avoids consulting the child's journal.
|
|
423
|
+
counts = current.assignments.each_with_object(Hash.new(0)) { |entry, totals| totals[entry.fetch("worker")] += 1 }
|
|
424
|
+
available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), assignment_count: counts[w.fetch("index")], status: :available) }.freeze
|
|
425
|
+
worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:assignment_count)
|
|
410
426
|
raise Phronomy::ConfigurationError, "Scheduler must select an available worker slot" unless available.include?(worker)
|
|
411
427
|
reserved = {"task_id" => task.fetch("id"), "worker" => worker.index,
|
|
412
428
|
"execution_id" => SecureRandom.uuid, "state" => "reserved", "result_ref" => nil, "error_ref" => nil}
|
|
@@ -417,20 +433,15 @@ module Phronomy
|
|
|
417
433
|
end
|
|
418
434
|
|
|
419
435
|
def record_assignment(id, assigned, outcome)
|
|
420
|
-
update(id) do |fresh,
|
|
436
|
+
update(id) do |fresh, _tx|
|
|
421
437
|
entries = fresh.assignments.map do |entry|
|
|
422
438
|
next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
|
|
423
439
|
next entry unless entry.fetch("state") == "reserved"
|
|
424
|
-
raise Phronomy::
|
|
440
|
+
raise Phronomy::Persistence::StateConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
|
|
425
441
|
entry.merge("state" => outcome.fetch(:status).to_s,
|
|
426
442
|
"result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
|
|
427
443
|
end
|
|
428
|
-
|
|
429
|
-
next worker unless worker.fetch("index") == assigned.fetch("worker")
|
|
430
|
-
root = tx.agents.load(worker.fetch("agent_id"))
|
|
431
|
-
worker.merge("transcript_size" => root.journal_position)
|
|
432
|
-
end
|
|
433
|
-
fresh.with(assignments: entries, workers: workers)
|
|
444
|
+
fresh.with(assignments: entries)
|
|
434
445
|
end
|
|
435
446
|
end
|
|
436
447
|
|
|
@@ -444,7 +455,7 @@ module Phronomy
|
|
|
444
455
|
end
|
|
445
456
|
|
|
446
457
|
def finish_error(id, error, status: "failed")
|
|
447
|
-
update(id) do |fresh, tx|
|
|
458
|
+
update(id) do |fresh, tx, scope|
|
|
448
459
|
next fresh if fresh.terminal?
|
|
449
460
|
fresh.with(status: status, phase: status, error_ref: tx.contents.put_json(error))
|
|
450
461
|
end
|
|
@@ -452,7 +463,10 @@ module Phronomy
|
|
|
452
463
|
|
|
453
464
|
def terminal_value(execution)
|
|
454
465
|
raise Phronomy::CancellationError, "Team run #{execution.team_execution_id} cancelled" if execution.status == "cancelled"
|
|
455
|
-
|
|
466
|
+
if execution.error_ref
|
|
467
|
+
failure = persistence.contents.fetch_json(execution.error_ref)
|
|
468
|
+
raise Phronomy::Error, "#{failure.fetch("class")}: #{failure.fetch("message")}"
|
|
469
|
+
end
|
|
456
470
|
persistence.contents.fetch_json(execution.result_ref)
|
|
457
471
|
end
|
|
458
472
|
|
|
@@ -477,7 +491,7 @@ module Phronomy
|
|
|
477
491
|
|
|
478
492
|
def build_operation_tool(run_id, operation)
|
|
479
493
|
team = self
|
|
480
|
-
Class.new(Phronomy::
|
|
494
|
+
Class.new(Phronomy::Tool::Base) do
|
|
481
495
|
def self.__framework_owned_operation? = true
|
|
482
496
|
tool_name operation.to_s
|
|
483
497
|
description((operation == :enqueue_task) ? "Add a task to the worker queue." : "Finish task generation.")
|
|
@@ -489,7 +503,7 @@ module Phronomy
|
|
|
489
503
|
param :summary, type: :string, desc: "Task generation summary", required: false
|
|
490
504
|
end
|
|
491
505
|
define_method(:call_async) do |args, cancellation_token: nil, config: {}|
|
|
492
|
-
validated, schema_error =
|
|
506
|
+
validated, schema_error = validate_arguments(args)
|
|
493
507
|
raise Phronomy::ToolError, schema_error if schema_error
|
|
494
508
|
execute_async(**validated, cancellation_token: cancellation_token, config: config)
|
|
495
509
|
rescue => error
|
|
@@ -497,8 +511,10 @@ module Phronomy
|
|
|
497
511
|
end
|
|
498
512
|
define_method(:execute_async) do |config: {}, cancellation_token: nil, **arguments|
|
|
499
513
|
key = config.fetch(:phronomy_tool_invocation_id)
|
|
500
|
-
Phronomy::
|
|
514
|
+
Phronomy::Execution.submit(on_full: :raise) do
|
|
501
515
|
team.send(:apply_operation, run_id, key, operation, arguments)
|
|
516
|
+
rescue OperationRejected => error
|
|
517
|
+
raise Phronomy::ConfigurationError, error.message
|
|
502
518
|
rescue Phronomy::CancellationError
|
|
503
519
|
raise
|
|
504
520
|
rescue => error
|
|
@@ -510,39 +526,32 @@ module Phronomy
|
|
|
510
526
|
end
|
|
511
527
|
|
|
512
528
|
def apply_operation(run_id, key, operation, arguments)
|
|
513
|
-
argument_values = Phronomy::
|
|
514
|
-
current = update(run_id) do |fresh, tx|
|
|
529
|
+
argument_values = Phronomy::Values::Serializable.convert(arguments, unsupported_message: "Team arguments are not canonically serializable")
|
|
530
|
+
current = update(run_id) do |fresh, tx, scope|
|
|
515
531
|
operations = fresh.metadata.fetch("operations")
|
|
516
532
|
if (prior = operations[key])
|
|
517
533
|
unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
|
|
518
|
-
raise Phronomy::
|
|
534
|
+
raise Phronomy::Persistence::StateConflictError, "Team operation #{key} identity mismatch"
|
|
519
535
|
end
|
|
520
536
|
next fresh
|
|
521
537
|
end
|
|
522
538
|
raise Phronomy::CancellationError, "Team run cancelled" if fresh.metadata["cancel_requested"]
|
|
523
|
-
raise Phronomy::
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
end
|
|
528
|
-
batch = Array(coordinator.metadata[Phronomy::Agent::ExecutionMetadata::TOOL_BATCH_METADATA_KEY])
|
|
529
|
-
requested = batch.find { |entry| entry.fetch("tool_invocation_id") == key }
|
|
530
|
-
unless requested && requested.fetch("status") == "authorized" && requested.fetch("tool_name") == operation.to_s && requested.fetch("arguments").compact == argument_values
|
|
531
|
-
raise Phronomy::Storage::ConflictError, "Team operation #{key} is not the authorized call"
|
|
532
|
-
end
|
|
539
|
+
raise Phronomy::Persistence::StateConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
|
|
540
|
+
batch = persistence.agent_store.authorized_operations(scope,
|
|
541
|
+
agent_id: fresh.coordinator.fetch("agent_id"), execution_id: fresh.coordinator.fetch("execution_id"),
|
|
542
|
+
invocation_id: key, name: operation.to_s, arguments: argument_values, names: %w[enqueue_task finalize])
|
|
533
543
|
# All calls passed the Agent authorization barrier. Commit this finite
|
|
534
544
|
# batch in Provider order, so finalize cannot overtake queued tasks.
|
|
535
545
|
tasks = fresh.tasks.dup
|
|
536
546
|
operations = operations.dup
|
|
537
547
|
metadata = fresh.metadata.dup
|
|
538
548
|
batch.each do |entry|
|
|
539
|
-
entry_id = entry.
|
|
540
|
-
next if operations.key?(entry_id)
|
|
541
|
-
name = entry.
|
|
542
|
-
|
|
543
|
-
values = entry.fetch("arguments").compact
|
|
549
|
+
entry_id = entry.invocation_id
|
|
550
|
+
next if operations.key?(entry_id)
|
|
551
|
+
name = entry.name
|
|
552
|
+
values = entry.arguments
|
|
544
553
|
if name == "enqueue_task"
|
|
545
|
-
raise
|
|
554
|
+
raise OperationRejected, "Cannot enqueue after finalize" if metadata["finalized"]
|
|
546
555
|
task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
|
|
547
556
|
tasks << task
|
|
548
557
|
output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
|
|
@@ -2,19 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module MultiAgent
|
|
5
|
-
#
|
|
5
|
+
# Environment-local Team identity and construction exclusion; no execution state.
|
|
6
6
|
# @api private
|
|
7
7
|
class TeamOwnershipRegistry
|
|
8
|
-
def self.for(runtime)
|
|
9
|
-
existing_for(runtime) || runtime.__register_shutdown_participant(
|
|
10
|
-
key: self, participant: new
|
|
11
|
-
)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.existing_for(runtime)
|
|
15
|
-
runtime.__shutdown_participant(key: self)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
8
|
def initialize
|
|
19
9
|
@mutex = Mutex.new
|
|
20
10
|
@condition = ConditionVariable.new
|
|
@@ -28,7 +18,7 @@ module Phronomy
|
|
|
28
18
|
loop do
|
|
29
19
|
raise Phronomy::RuntimeShutdownError, "Runtime is draining" if @draining
|
|
30
20
|
if (owner = @owners[id])
|
|
31
|
-
raise Phronomy::
|
|
21
|
+
raise Phronomy::Persistence::StateConflictError, "Team #{id} already exists" if create
|
|
32
22
|
unless owner.is_a?(klass) && owner.persistence.equal?(persistence)
|
|
33
23
|
raise Phronomy::ConfigurationError, "Team #{id} owner or Persistence mismatch"
|
|
34
24
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Domain-facing content access uses the Persistence failure vocabulary.
|
|
6
|
+
# ContentStore implementations retain their independent backend contract.
|
|
7
|
+
# @api private
|
|
8
|
+
class ContentRepository < Phronomy::ContentStore::Base
|
|
9
|
+
def initialize(store)
|
|
10
|
+
@store = store
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def put(bytes, canonicalization_version:)
|
|
14
|
+
StorageBoundary.call { @store.put(bytes, canonicalization_version: canonicalization_version) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fetch(content_id)
|
|
18
|
+
StorageBoundary.call { @store.fetch(content_id) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def exist?(content_id)
|
|
22
|
+
StorageBoundary.call { @store.exist?(content_id) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../common/error"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
class Persistence
|
|
7
|
+
# Failures describe a category, not a universal claim about commit outcome.
|
|
8
|
+
class Error < Phronomy::Error; end
|
|
9
|
+
class ConflictError < Error; end
|
|
10
|
+
class StateConflictError < ConflictError; end
|
|
11
|
+
class NotFoundError < Error; end
|
|
12
|
+
class SerializationError < Error; end
|
|
13
|
+
class UnsupportedBackendError < Error; end
|
|
14
|
+
class TransactionError < Error; end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -108,8 +108,8 @@ module Phronomy
|
|
|
108
108
|
raise Phronomy::Storage::SerializationError,
|
|
109
109
|
"unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
|
|
110
110
|
end
|
|
111
|
-
source["version"] = Phronomy::
|
|
112
|
-
Phronomy::
|
|
111
|
+
source["version"] = Phronomy::Context::LLMInputManifest::VERSION
|
|
112
|
+
Phronomy::Context::LLMInputManifest.from_h(source).to_h
|
|
113
113
|
rescue Phronomy::Storage::SerializationError
|
|
114
114
|
raise
|
|
115
115
|
rescue => error
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Domain-neutral transaction ownership and participation. Domain operations
|
|
5
|
+
# choose their atomic boundary; composition supplies their storage adapters.
|
|
6
|
+
# @api public
|
|
7
|
+
class Persistence
|
|
8
|
+
attr_reader :backend
|
|
9
|
+
|
|
10
|
+
def initialize(backend:)
|
|
11
|
+
StorageBoundary.call { Storage::Backend.validate_capabilities!(backend) }
|
|
12
|
+
@backend = backend
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Bind an injected adapter for ordinary synchronous reads/standalone writes.
|
|
16
|
+
# Only that adapter receives Storage's raw view. Multi-operation changes use
|
|
17
|
+
# atomic/Transaction#participate so all participants use the exact same view.
|
|
18
|
+
# @api private
|
|
19
|
+
def bind(adapter)
|
|
20
|
+
StorageBoundary.call { adapter.new(backend.view) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Classifies evidence supplied by the owning domains. No retry, callback,
|
|
6
|
+
# ownership release or external operation is performed here.
|
|
7
|
+
# @api public
|
|
8
|
+
SaveOutcome = Data.define(:disposition, :original_error, :read_error) do
|
|
9
|
+
def self.compare(before:, after:, original_error: nil)
|
|
10
|
+
current = yield
|
|
11
|
+
disposition = if current == after
|
|
12
|
+
:committed
|
|
13
|
+
elsif current == before
|
|
14
|
+
:not_committed
|
|
15
|
+
else
|
|
16
|
+
:unknown
|
|
17
|
+
end
|
|
18
|
+
new(disposition: disposition, original_error: original_error, read_error: nil)
|
|
19
|
+
rescue => error
|
|
20
|
+
new(disposition: :unknown, original_error: original_error, read_error: error)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Compares revision and snapshot evidence supplied by the owning domain.
|
|
6
|
+
# Does not load, save, retry, resolve external operations or decide resumption.
|
|
7
|
+
# @api private
|
|
8
|
+
module SnapshotComparison
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Compare authoritative current state with a known pre/post revision pair.
|
|
12
|
+
# This is deliberately small: callers still validate entity-specific content.
|
|
13
|
+
def compare_revisions(current_revision:, expected_pre_revision:, intended_post_revision:)
|
|
14
|
+
current = current_revision
|
|
15
|
+
return :post_state if current == intended_post_revision
|
|
16
|
+
return :pre_state if current == expected_pre_revision
|
|
17
|
+
|
|
18
|
+
:conflict
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Evidence comparison for revisioned snapshot repositories.
|
|
22
|
+
# Some repositories return a newly allocated revision only after save, so
|
|
23
|
+
# callers may not know the intended post revision in advance. In that case
|
|
24
|
+
# the intended snapshot plus a revision different from the expected pre
|
|
25
|
+
# revision identifies the post-state.
|
|
26
|
+
def compare_revisioned_snapshot(
|
|
27
|
+
record:,
|
|
28
|
+
expected_pre_revision:,
|
|
29
|
+
intended_snapshot:,
|
|
30
|
+
intended_post_revision: nil
|
|
31
|
+
)
|
|
32
|
+
return :pre_state if record.nil? && expected_pre_revision.nil?
|
|
33
|
+
return :conflict if record.nil?
|
|
34
|
+
|
|
35
|
+
revision = fetch_value(record, :revision)
|
|
36
|
+
snapshot = fetch_value(record, :snapshot)
|
|
37
|
+
normalized_snapshot = normalize_value(snapshot)
|
|
38
|
+
normalized_intended = normalize_value(intended_snapshot)
|
|
39
|
+
|
|
40
|
+
if intended_post_revision
|
|
41
|
+
if revision == intended_post_revision &&
|
|
42
|
+
normalized_snapshot == normalized_intended
|
|
43
|
+
return :post_state
|
|
44
|
+
end
|
|
45
|
+
elsif normalized_snapshot == normalized_intended &&
|
|
46
|
+
revision != expected_pre_revision
|
|
47
|
+
return :post_state
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return :pre_state if revision == expected_pre_revision
|
|
51
|
+
|
|
52
|
+
:conflict
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def normalize_value(value)
|
|
56
|
+
case value
|
|
57
|
+
when Hash
|
|
58
|
+
value.each_with_object({}) do |(key, child), result|
|
|
59
|
+
result[key.to_s] = normalize_value(child)
|
|
60
|
+
end
|
|
61
|
+
when Array
|
|
62
|
+
value.map { |child| normalize_value(child) }
|
|
63
|
+
when Symbol
|
|
64
|
+
value.to_s
|
|
65
|
+
else
|
|
66
|
+
value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def fetch_value(record, key)
|
|
71
|
+
return nil unless record
|
|
72
|
+
return record.public_send(key) if record.respond_to?(key)
|
|
73
|
+
return record[key] if record.respond_to?(:key?) && record.key?(key)
|
|
74
|
+
string_key = key.to_s
|
|
75
|
+
return record[string_key] if record.respond_to?(:key?) && record.key?(string_key)
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
# Translate only the backend's existing known failure categories. Unknown
|
|
6
|
+
# transport/commit outcomes and application exceptions pass
|
|
7
|
+
# through unchanged so the operation's existing reconciliation stays in charge.
|
|
8
|
+
# @api private
|
|
9
|
+
module StorageBoundary
|
|
10
|
+
def self.call
|
|
11
|
+
yield
|
|
12
|
+
rescue Phronomy::Storage::ConflictError => error
|
|
13
|
+
raise_domain_error(ConflictError, error)
|
|
14
|
+
rescue Phronomy::Storage::NotFoundError => error
|
|
15
|
+
raise_domain_error(NotFoundError, error)
|
|
16
|
+
rescue Phronomy::Storage::SerializationError => error
|
|
17
|
+
raise_domain_error(SerializationError, error)
|
|
18
|
+
rescue Phronomy::Storage::TransactionError => error
|
|
19
|
+
raise_domain_error(TransactionError, error)
|
|
20
|
+
rescue Phronomy::Storage::UnsupportedBackendError => error
|
|
21
|
+
raise_domain_error(UnsupportedBackendError, error)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.raise_domain_error(type, original)
|
|
25
|
+
mapped = type.new(original.message)
|
|
26
|
+
mapped.set_backtrace(original.backtrace)
|
|
27
|
+
raise mapped, cause: original
|
|
28
|
+
end
|
|
29
|
+
private_class_method :raise_domain_error
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|