phronomy 0.25.0 → 0.27.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/CHANGELOG.md +71 -0
- data/CONTRIBUTING.md +4 -4
- data/README.md +14 -7
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -22
- data/benchmark/bench_context_assembler.rb +4 -5
- data/benchmark/bench_regression.rb +1 -1
- data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
- data/docs/architecture/agent-context.md +5 -3
- data/docs/architecture/agent-transition-ownership.md +89 -0
- data/docs/architecture/before-llm-input.md +6 -0
- data/docs/architecture/context-management.md +30 -0
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-metadata-and-values.md +80 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +8 -2
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +29 -6
- data/docs/architecture/refactoring-closure.md +334 -0
- data/docs/architecture/remaining-refactoring-plan.md +374 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/tool-schema-recording-gap.md +50 -0
- data/docs/architecture/tracing.md +4 -4
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +209 -0
- data/docs/async-composition.md +205 -0
- data/docs/decisions/010-cooperative-first-concurrency.md +23 -23
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- 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/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
- data/docs/decisions/032-storage-backend-composition.md +80 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -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 +131 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
- data/docs/decisions/040-configuration-default-composition.md +94 -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 +100 -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 +151 -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 +78 -0
- data/docs/decisions/README.md +35 -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/features.md +21 -12
- data/docs/getting-started.md +10 -10
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +7 -1
- data/docs/migrations/handoff-runner-multi-agent.md +35 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/persistence-backends.md +162 -204
- data/docs/runtime-and-concurrency.md +205 -60
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/agent/async_event_api.rb +2 -2
- data/lib/phronomy/agent/base.rb +55 -251
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context/capability/base.rb +67 -26
- data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
- data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
- data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
- data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
- data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
- data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
- data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
- data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
- data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
- data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
- data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +20 -96
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +12 -13
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
- data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +22 -73
- data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
- data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
- 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/{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 -10
- data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
- data/lib/phronomy/agent/persistence/codec.rb +358 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
- data/lib/phronomy/agent/persistence/queries.rb +61 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
- data/lib/phronomy/agent/persistence/watermark.rb +27 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +36 -41
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +9 -13
- data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
- data/lib/phronomy/agent/selection/candidate.rb +1 -1
- 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 +90 -0
- data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
- data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
- data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +2 -2
- 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.rb → configuration/configuration.rb} +14 -5
- data/lib/phronomy/configuration/global_configuration.rb +26 -0
- data/lib/phronomy/content_store/storage_schema.rb +11 -0
- data/lib/phronomy/content_store/stored_contents.rb +43 -0
- data/lib/phronomy/engine/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → engine/blocking.rb} +15 -8
- data/lib/phronomy/engine/cancellation_error.rb +7 -0
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +29 -16
- data/lib/phronomy/engine/concurrency/operation_binding.rb +43 -0
- data/lib/phronomy/engine/concurrency/physical_completion_task.rb +4 -62
- data/lib/phronomy/engine/concurrency/result_collector.rb +99 -0
- data/lib/phronomy/engine/concurrency/result_composition.rb +145 -0
- data/lib/phronomy/engine/concurrency/subscriptions.rb +68 -0
- data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/engine/event_loop.rb +160 -626
- data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/engine/execution.rb +229 -0
- data/lib/phronomy/engine/execution_cancellation_error.rb +14 -0
- data/lib/phronomy/engine/execution_receiver.rb +65 -0
- data/lib/phronomy/engine/execution_timeout_error.rb +14 -0
- data/lib/phronomy/engine/fsm_protocol.rb +14 -0
- data/lib/phronomy/engine/fsm_session.rb +38 -31
- data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +3 -1
- data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +3 -1
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +3 -1
- data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +13 -1
- data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
- data/lib/phronomy/engine/runtime/timer_queue.rb +11 -0
- data/lib/phronomy/engine/runtime.rb +80 -84
- data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/engine/{task.rb → task_result.rb} +101 -42
- data/lib/phronomy/engine/timeout_error.rb +7 -0
- data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -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/base.rb +2 -2
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
- data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
- data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
- data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
- data/lib/phronomy/llm_contract/transport_error.rb +7 -0
- data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +8 -8
- data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +36 -68
- data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
- data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
- data/lib/phronomy/persistence/api/persistence.rb +140 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
- data/lib/phronomy/persistence_composition/repositories.rb +77 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/storage/backend.rb +101 -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/scorer/llm_judge.rb +5 -3
- data/lib/phronomy/testing/fake_clock.rb +13 -9
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/contract/tool_error.rb +7 -0
- data/lib/phronomy/tools/agent.rb +6 -6
- data/lib/phronomy/vector_store/async_backend.rb +5 -5
- data/lib/phronomy/vector_store/embeddings/base.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +126 -75
- data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
- data/lib/phronomy/workflow/phase_machine_builder.rb +8 -8
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
- data/lib/phronomy.rb +52 -94
- data/scripts/api_snapshot.rb +4 -2
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/phronomy/agent.rbs +4 -5
- data/sig/phronomy/execution_receiver.rbs +34 -0
- data/sig/phronomy/extensions.rbs +5 -5
- data/sig/phronomy/handoff.rbs +4 -2
- data/sig/phronomy/multi_agent.rbs +19 -3
- data/sig/phronomy/persistence.rbs +7 -91
- data/sig/phronomy/runtime.rbs +34 -7
- data/sig/phronomy/storage.rbs +174 -0
- data/sig/phronomy/tool.rbs +11 -2
- data/sig/phronomy/workflow.rbs +1 -1
- metadata +236 -99
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3138
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_executor.rb +0 -55
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +0 -137
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +0 -118
- 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/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/workflow_recovery.rb +0 -123
- /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
- /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
- /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
- /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
|
@@ -6,6 +6,9 @@ module Phronomy
|
|
|
6
6
|
class Orchestrator < Agent::Base
|
|
7
7
|
agent_definition id: "orchestrator", version: 1
|
|
8
8
|
|
|
9
|
+
ParallelChild = Data.define(:index, :agent, :input, :config)
|
|
10
|
+
private_constant :ParallelChild
|
|
11
|
+
|
|
9
12
|
# Own one live cancellation token shared with this invocation's static
|
|
10
13
|
# children, including when Recovery reconstructs the invocation.
|
|
11
14
|
# @api private
|
|
@@ -20,7 +23,7 @@ module Phronomy
|
|
|
20
23
|
raise Phronomy::EventLoopReentrancyError, "Orchestrator#resume cannot block EventLoop"
|
|
21
24
|
end
|
|
22
25
|
execution = persistence.executions.load(execution_id)
|
|
23
|
-
raise Phronomy::
|
|
26
|
+
raise Phronomy::Storage::ConflictError, "Parent execution owner mismatch" unless execution.agent_id == agent_id
|
|
24
27
|
input = persistence.contents.fetch_text(execution.metadata.fetch("current_input_ref"))
|
|
25
28
|
result = Phronomy::Agent::ExactExecution.start(agent: self, execution_id: execution_id, input: input, config: config).wait_result
|
|
26
29
|
raise Phronomy::Agent::RecoverySupport.error_from_failure(result[:error]) if result[:error]
|
|
@@ -29,7 +32,7 @@ module Phronomy
|
|
|
29
32
|
|
|
30
33
|
def self.subagent(name, agent_class, on_error: :raise, inherit_knowledge: true)
|
|
31
34
|
# A subagent Tool is logically asynchronous: ToolInvocation starts the
|
|
32
|
-
# child Agent and resumes when its completion
|
|
35
|
+
# child Agent and resumes when its completion TaskResult settles. It must not
|
|
33
36
|
# occupy an OffloadPool worker while waiting for the child.
|
|
34
37
|
tool_class = Class.new(Phronomy::Tools::Agent) do
|
|
35
38
|
def self.__framework_owned_operation? = true
|
|
@@ -57,7 +60,7 @@ module Phronomy
|
|
|
57
60
|
super(args, cancellation_token: cancellation_token, config: config)
|
|
58
61
|
end
|
|
59
62
|
rescue => error
|
|
60
|
-
Phronomy::
|
|
63
|
+
Phronomy::TaskResult.deferred(name: "subagent-dispatch-failed").tap { |task| task.fail(error) }
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
define_method(:execute_async) do |input:, cancellation_token: nil, config: {}|
|
|
@@ -87,7 +90,7 @@ module Phronomy
|
|
|
87
90
|
end
|
|
88
91
|
end
|
|
89
92
|
source = agent.invoke_async(input, config: task_config)
|
|
90
|
-
result_task = Phronomy::
|
|
93
|
+
result_task = Phronomy::TaskResult.deferred(
|
|
91
94
|
name: "subagent-tool-#{name}"
|
|
92
95
|
)
|
|
93
96
|
source.on_complete do |result, error|
|
|
@@ -99,7 +102,7 @@ module Phronomy
|
|
|
99
102
|
end
|
|
100
103
|
result_task
|
|
101
104
|
rescue => error
|
|
102
|
-
result_task ||= Phronomy::
|
|
105
|
+
result_task ||= Phronomy::TaskResult.deferred(
|
|
103
106
|
name: "subagent-tool-#{name}"
|
|
104
107
|
)
|
|
105
108
|
(on_error == :raise) ? result_task.fail(error) : result_task.complete(nil)
|
|
@@ -160,73 +163,38 @@ module Phronomy
|
|
|
160
163
|
inherit_knowledge: true
|
|
161
164
|
)
|
|
162
165
|
validate_parallel_options!(tasks, max_concurrency, on_error)
|
|
163
|
-
return Phronomy::Task.deferred(name: "fan-out-empty").tap { |task| task.complete([]) } if tasks.empty?
|
|
164
|
-
|
|
165
166
|
children = build_fan_out_children(
|
|
166
167
|
tasks,
|
|
167
|
-
cancellation_token:
|
|
168
|
-
invocation_context:
|
|
169
|
-
inherit_knowledge: inherit_knowledge
|
|
170
|
-
)
|
|
171
|
-
invocation = FanOutInvocation.new(
|
|
172
|
-
children: children,
|
|
173
|
-
max_concurrency: max_concurrency || children.length,
|
|
174
|
-
on_error: on_error
|
|
175
|
-
)
|
|
176
|
-
effective_token = cancellation_token || invocation_context&.cancellation_token
|
|
177
|
-
FanOutSessionBuilder.start(
|
|
178
|
-
invocation: invocation,
|
|
179
|
-
timeout: timeout,
|
|
180
|
-
cancellation_token: effective_token
|
|
181
|
-
)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def fan_out(
|
|
185
|
-
agent:,
|
|
186
|
-
inputs:,
|
|
187
|
-
config: {},
|
|
188
|
-
max_concurrency: nil,
|
|
189
|
-
on_error: :raise,
|
|
190
|
-
timeout: nil,
|
|
191
|
-
cancellation_token: nil,
|
|
192
|
-
invocation_context: nil,
|
|
193
|
-
inherit_knowledge: true
|
|
194
|
-
)
|
|
195
|
-
dispatch_parallel(
|
|
196
|
-
*inputs.map do |input|
|
|
197
|
-
{agent: agent, input: input, config: config}
|
|
198
|
-
end,
|
|
199
|
-
max_concurrency: max_concurrency,
|
|
200
|
-
on_error: on_error,
|
|
201
|
-
timeout: timeout,
|
|
202
|
-
cancellation_token: cancellation_token,
|
|
203
|
-
invocation_context: invocation_context,
|
|
168
|
+
cancellation_token: nil,
|
|
169
|
+
invocation_context: nil,
|
|
204
170
|
inherit_knowledge: inherit_knowledge
|
|
205
171
|
)
|
|
206
|
-
|
|
172
|
+
Phronomy::Execution.send(:__run_async, children,
|
|
173
|
+
timeout: timeout, cancellation_token: cancellation_token,
|
|
174
|
+
invocation_context: invocation_context, concurrency_limit: max_concurrency) do |child, execution|
|
|
175
|
+
# Preserve a child-specific context as well as this dispatch's controls.
|
|
176
|
+
# Agent admission adds that context's constraints to the supplied token.
|
|
177
|
+
binding = Phronomy::Concurrency::OperationBinding.new(
|
|
178
|
+
invocation_context: execution.invocation_context,
|
|
179
|
+
cancellation_token: child.config[:cancellation_token]
|
|
180
|
+
)
|
|
181
|
+
child_context = child.config[:invocation_context] ||
|
|
182
|
+
execution.invocation_context.merge(parent_task_id: invocation_context&.task_id)
|
|
183
|
+
begin
|
|
184
|
+
source = child.agent.invoke_async(child.input,
|
|
185
|
+
config: child.config.merge(cancellation_token: binding.token),
|
|
186
|
+
invocation_context: child_context)
|
|
187
|
+
binding.track(source)
|
|
188
|
+
rescue
|
|
189
|
+
binding.close
|
|
190
|
+
raise
|
|
191
|
+
end
|
|
192
|
+
end.map do |outcomes|
|
|
193
|
+
failure = outcomes.find { |outcome| outcome.status != :completed }
|
|
194
|
+
raise failure.error if failure && on_error == :raise
|
|
207
195
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
inputs:,
|
|
211
|
-
config: {},
|
|
212
|
-
max_concurrency: nil,
|
|
213
|
-
on_error: :raise,
|
|
214
|
-
timeout: nil,
|
|
215
|
-
cancellation_token: nil,
|
|
216
|
-
invocation_context: nil,
|
|
217
|
-
inherit_knowledge: true
|
|
218
|
-
)
|
|
219
|
-
dispatch_parallel_async(
|
|
220
|
-
*inputs.map do |input|
|
|
221
|
-
{agent: agent, input: input, config: config}
|
|
222
|
-
end,
|
|
223
|
-
max_concurrency: max_concurrency,
|
|
224
|
-
on_error: on_error,
|
|
225
|
-
timeout: timeout,
|
|
226
|
-
cancellation_token: cancellation_token,
|
|
227
|
-
invocation_context: invocation_context,
|
|
228
|
-
inherit_knowledge: inherit_knowledge
|
|
229
|
-
)
|
|
196
|
+
outcomes.map { |outcome| (outcome.status == :completed) ? outcome.value : nil }
|
|
197
|
+
end
|
|
230
198
|
end
|
|
231
199
|
|
|
232
200
|
def subagent(
|
|
@@ -366,7 +334,7 @@ module Phronomy
|
|
|
366
334
|
knowledge_snapshot: knowledge_snapshot
|
|
367
335
|
)
|
|
368
336
|
|
|
369
|
-
|
|
337
|
+
ParallelChild.new(
|
|
370
338
|
index: index,
|
|
371
339
|
agent: child_agent,
|
|
372
340
|
input: task.fetch(:input),
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
module Persistence
|
|
6
|
+
# Current-format record schemas and conversion for this domain.
|
|
7
|
+
# @api private
|
|
8
|
+
module Codec
|
|
9
|
+
extend Phronomy::Storage::RecordCodec
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def encode_team_root(value)
|
|
14
|
+
payload = value.to_h
|
|
15
|
+
Phronomy::MultiAgent::TeamRoot.from_h(payload)
|
|
16
|
+
build_record("phronomy.team_root", "0.1", payload)
|
|
17
|
+
rescue Phronomy::Storage::SerializationError
|
|
18
|
+
raise
|
|
19
|
+
rescue => error
|
|
20
|
+
serialization_error("cannot encode TeamRoot", error)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def decode_team_root(record)
|
|
24
|
+
payload = current_payload!(record, record_type: "phronomy.team_root",
|
|
25
|
+
format_version: "0.1", keys: Phronomy::MultiAgent::TeamRoot::ATTRIBUTES, label: "TeamRoot")
|
|
26
|
+
Phronomy::MultiAgent::TeamRoot.from_h(payload)
|
|
27
|
+
rescue Phronomy::Storage::SerializationError
|
|
28
|
+
raise
|
|
29
|
+
rescue => error
|
|
30
|
+
serialization_error("cannot decode TeamRoot", error)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def encode_team_execution(value)
|
|
34
|
+
payload = value.to_h
|
|
35
|
+
Phronomy::MultiAgent::TeamExecution.from_h(payload)
|
|
36
|
+
build_record("phronomy.team_execution", "0.1", payload)
|
|
37
|
+
rescue Phronomy::Storage::SerializationError
|
|
38
|
+
raise
|
|
39
|
+
rescue => error
|
|
40
|
+
serialization_error("cannot encode TeamExecution", error)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def decode_team_execution(record)
|
|
44
|
+
payload = current_payload!(record, record_type: "phronomy.team_execution",
|
|
45
|
+
format_version: "0.1", keys: Phronomy::MultiAgent::TeamExecution::ATTRIBUTES, label: "TeamExecution")
|
|
46
|
+
Phronomy::MultiAgent::TeamExecution.from_h(payload)
|
|
47
|
+
rescue Phronomy::Storage::SerializationError
|
|
48
|
+
raise
|
|
49
|
+
rescue => error
|
|
50
|
+
serialization_error("cannot decode TeamExecution", error)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
module Persistence
|
|
6
|
+
# Read and assemble domain results; the public entry point owns the execution-thread guard.
|
|
7
|
+
# @api private
|
|
8
|
+
class Queries
|
|
9
|
+
def initialize(repositories)
|
|
10
|
+
@repositories = repositories
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def team_execution_result(team_execution_id)
|
|
14
|
+
execution = @repositories.team_executions.load(team_execution_id)
|
|
15
|
+
{
|
|
16
|
+
team_execution_id: execution.team_execution_id, team_id: execution.team_id,
|
|
17
|
+
status: execution.status, phase: execution.phase,
|
|
18
|
+
result_ref: execution.result_ref, error_ref: execution.error_ref,
|
|
19
|
+
result: execution.result_ref && @repositories.contents.fetch_json(execution.result_ref),
|
|
20
|
+
error: execution.error_ref && @repositories.contents.fetch_json(execution.error_ref)
|
|
21
|
+
}.then { |value| Phronomy::Values::Immutable.copy(value) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def list_team_executions(team_id, after: nil, limit: 100)
|
|
25
|
+
@repositories.team_executions.list(team_id, after: after, limit: limit)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
module Persistence
|
|
6
|
+
# Owns execution activity, ownership, admission and durable record encoding.
|
|
7
|
+
# @api private
|
|
8
|
+
class TeamExecutionRepository
|
|
9
|
+
def initialize(view) = @view = view
|
|
10
|
+
|
|
11
|
+
def create_active(execution)
|
|
12
|
+
raise Phronomy::Storage::SerializationError, "create_active requires an active TeamExecution" unless execution.active?
|
|
13
|
+
record = Codec.encode_team_execution(execution)
|
|
14
|
+
admission do
|
|
15
|
+
@view.atomic do |bound|
|
|
16
|
+
entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).insert(key: execution.team_execution_id.to_s,
|
|
17
|
+
revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
|
|
18
|
+
decode(entry, execution.team_execution_id, owner: execution.team_id,
|
|
19
|
+
revision: execution.execution_revision, active: true)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def load(team_execution_id)
|
|
25
|
+
value = @view.atomic do |bound|
|
|
26
|
+
entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).read(team_execution_id.to_s)
|
|
27
|
+
entry && decode(entry, team_execution_id)
|
|
28
|
+
end
|
|
29
|
+
value || raise(Phronomy::Storage::NotFoundError, "TeamExecution not found: #{team_execution_id}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def save(team_execution_id, expected_revision:, execution:)
|
|
33
|
+
expected = Integer(expected_revision)
|
|
34
|
+
revision = Integer(execution.execution_revision)
|
|
35
|
+
raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
|
|
36
|
+
unless execution.team_execution_id.to_s == team_execution_id.to_s
|
|
37
|
+
raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
|
|
38
|
+
end
|
|
39
|
+
record = Codec.encode_team_execution(execution)
|
|
40
|
+
admission do
|
|
41
|
+
@view.atomic do |bound|
|
|
42
|
+
entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).replace(key: team_execution_id.to_s,
|
|
43
|
+
expected_revision: expected, next_revision: revision, attributes: attributes(execution),
|
|
44
|
+
expected_attributes: execution.active? ? {active: true} : {}, record: record)
|
|
45
|
+
decode(entry, team_execution_id, owner: execution.team_id, revision: revision, active: execution.active?)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def list_active(team_id)
|
|
51
|
+
@view.atomic do |bound|
|
|
52
|
+
bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).scan(index: :owner_active,
|
|
53
|
+
equals: {owner: team_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: team_id, active: true) }.freeze
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def list(team_id, after: nil, limit: 100)
|
|
58
|
+
raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
|
|
59
|
+
@view.atomic do |bound|
|
|
60
|
+
bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: team_id.to_s},
|
|
61
|
+
after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: team_id) }.freeze
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def delete(team_execution_id)
|
|
66
|
+
@view.records(Phronomy::TeamStorageSchema::EXECUTIONS).delete(key: team_execution_id.to_s)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def delete_for_team(team_id)
|
|
70
|
+
@view.records(Phronomy::TeamStorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: team_id.to_s})
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def assert_idle!(team_id)
|
|
74
|
+
condition = Phronomy::Storage::Condition::NoRows.new(resource: Phronomy::TeamStorageSchema::EXECUTIONS,
|
|
75
|
+
index: :owner_active, equals: {owner: team_id.to_s, active: true})
|
|
76
|
+
@view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: Phronomy::TeamStorageSchema::ROOTS, key: team_id.to_s)],
|
|
77
|
+
conditions: [condition])
|
|
78
|
+
rescue Phronomy::Storage::ConditionFailedError => error
|
|
79
|
+
raise unless error.condition.equal?(condition)
|
|
80
|
+
raise Phronomy::AgentBusyError, error.message
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def attributes(execution) = {owner: execution.team_id.to_s, active: execution.active?}
|
|
86
|
+
|
|
87
|
+
def admission
|
|
88
|
+
yield
|
|
89
|
+
rescue Phronomy::Storage::UniqueConstraintError => error
|
|
90
|
+
raise unless error.resource == Phronomy::TeamStorageSchema::EXECUTIONS.id && error.constraint == :one_active_owner
|
|
91
|
+
raise Phronomy::AgentBusyError, error.message
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def decode(entry, team_execution_id, owner: nil, revision: nil, active: nil)
|
|
95
|
+
execution = Codec.decode_team_execution(entry.record)
|
|
96
|
+
valid = entry.key == team_execution_id.to_s && execution.team_execution_id == entry.key &&
|
|
97
|
+
execution.execution_revision == entry.revision && attributes(execution) == entry.attributes &&
|
|
98
|
+
(!owner || execution.team_id == owner.to_s) && (!revision || execution.execution_revision == revision) &&
|
|
99
|
+
(active.nil? || execution.active? == active)
|
|
100
|
+
unless valid
|
|
101
|
+
raise Phronomy::Storage::SerializationError, "backend returned another Team Execution identity/metadata"
|
|
102
|
+
end
|
|
103
|
+
execution
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module MultiAgent
|
|
5
|
+
module Persistence
|
|
6
|
+
# Owns Team root encoding and validates returned storage metadata.
|
|
7
|
+
# @api private
|
|
8
|
+
class TeamRepository
|
|
9
|
+
def initialize(view) = @view = view
|
|
10
|
+
|
|
11
|
+
def create(root)
|
|
12
|
+
record = Codec.encode_team_root(root)
|
|
13
|
+
@view.atomic do |bound|
|
|
14
|
+
entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).insert(key: root.team_id.to_s,
|
|
15
|
+
revision: Integer(root.team_revision), attributes: {}, record: record)
|
|
16
|
+
decode(entry, root.team_id, revision: root.team_revision)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def load(team_id)
|
|
21
|
+
value = @view.atomic do |bound|
|
|
22
|
+
entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).read(team_id.to_s)
|
|
23
|
+
entry && decode(entry, team_id)
|
|
24
|
+
end
|
|
25
|
+
value || raise(Phronomy::Storage::NotFoundError, "Team not found: #{team_id}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def save(team_id, expected_revision:, root:)
|
|
29
|
+
expected = Integer(expected_revision)
|
|
30
|
+
revision = Integer(root.team_revision)
|
|
31
|
+
raise Phronomy::Storage::ConflictError, "root save must advance revision exactly once" unless revision == expected + 1
|
|
32
|
+
unless root.team_id.to_s == team_id.to_s
|
|
33
|
+
raise Phronomy::Storage::SerializationError, "Team root identity mismatch"
|
|
34
|
+
end
|
|
35
|
+
record = Codec.encode_team_root(root)
|
|
36
|
+
@view.atomic do |bound|
|
|
37
|
+
entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).replace(key: team_id.to_s,
|
|
38
|
+
expected_revision: expected, next_revision: revision, attributes: {}, record: record)
|
|
39
|
+
decode(entry, team_id, revision: revision)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def delete(team_id)
|
|
44
|
+
@view.records(Phronomy::TeamStorageSchema::ROOTS).delete(key: team_id.to_s)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def decode(entry, team_id, revision: nil)
|
|
50
|
+
root = Codec.decode_team_root(entry.record)
|
|
51
|
+
valid = entry.key == team_id.to_s && root.team_id == entry.key &&
|
|
52
|
+
root.team_revision == entry.revision && (!revision || revision == entry.revision)
|
|
53
|
+
unless valid
|
|
54
|
+
raise Phronomy::Storage::SerializationError, "backend returned another Team identity/revision"
|
|
55
|
+
end
|
|
56
|
+
root
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
module
|
|
5
|
-
# Implements peer coordination through
|
|
4
|
+
module MultiAgent
|
|
5
|
+
# Implements peer coordination through an invocation-local KnowledgeStore.
|
|
6
|
+
# @api public
|
|
6
7
|
class SharedState
|
|
7
8
|
# Semantic revision of the framework-owned SharedState instrumentation
|
|
8
9
|
# applied on top of one specific researcher definition revision.
|
|
@@ -13,6 +14,8 @@ module Phronomy
|
|
|
13
14
|
INSTRUMENTATION_DEFINITION_VERSION = 1
|
|
14
15
|
private_constant :INSTRUMENTATION_DEFINITION_VERSION
|
|
15
16
|
|
|
17
|
+
# Findings shared by the sequential members of one invocation.
|
|
18
|
+
# @api public
|
|
16
19
|
class KnowledgeStore
|
|
17
20
|
def initialize
|
|
18
21
|
@findings = []
|
|
@@ -78,8 +81,15 @@ module Phronomy
|
|
|
78
81
|
# @api public
|
|
79
82
|
def invoke(input, config: {})
|
|
80
83
|
validate_termination!
|
|
81
|
-
|
|
82
84
|
store = KnowledgeStore.new
|
|
85
|
+
cycles, terminated_by = coordinate_members(input, store)
|
|
86
|
+
|
|
87
|
+
{output: aggregate_findings(store), cycles: cycles, terminated_by: terminated_by}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def coordinate_members(input, store)
|
|
83
93
|
max_cycles = self.class._max_cycles
|
|
84
94
|
deadline = self.class._timeout ? Time.now + self.class._timeout : nil
|
|
85
95
|
terminated_by = :max_cycles
|
|
@@ -87,15 +97,7 @@ module Phronomy
|
|
|
87
97
|
cycle_limit = max_cycles || Float::INFINITY
|
|
88
98
|
|
|
89
99
|
(1..cycle_limit).each do |cycle|
|
|
90
|
-
|
|
91
|
-
invoke_researcher(
|
|
92
|
-
member_config[:klass],
|
|
93
|
-
store,
|
|
94
|
-
cycle,
|
|
95
|
-
input,
|
|
96
|
-
member_config[:instruction]
|
|
97
|
-
)
|
|
98
|
-
end
|
|
100
|
+
invoke_members(input, store, cycle)
|
|
99
101
|
completed_cycles = cycle
|
|
100
102
|
|
|
101
103
|
if self.class._terminate_when&.call(store)
|
|
@@ -109,17 +111,25 @@ module Phronomy
|
|
|
109
111
|
end
|
|
110
112
|
end
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
[completed_cycles, terminated_by]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def invoke_members(input, store, cycle)
|
|
118
|
+
self.class._members.each do |member_config|
|
|
119
|
+
invoke_researcher(
|
|
120
|
+
member_config[:klass], store, cycle, input, member_config[:instruction]
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def aggregate_findings(store)
|
|
126
|
+
if self.class._aggregator
|
|
113
127
|
self.class._aggregator.call(store)
|
|
114
128
|
else
|
|
115
129
|
store.read_all
|
|
116
130
|
end
|
|
117
|
-
|
|
118
|
-
{output: output, cycles: completed_cycles, terminated_by: terminated_by}
|
|
119
131
|
end
|
|
120
132
|
|
|
121
|
-
private
|
|
122
|
-
|
|
123
133
|
def validate_termination!
|
|
124
134
|
return if self.class._max_cycles || self.class._timeout
|
|
125
135
|
|
|
@@ -148,28 +158,8 @@ module Phronomy
|
|
|
148
158
|
|
|
149
159
|
def build_instrumented_researcher(researcher_class, store, cycle)
|
|
150
160
|
agent_key = researcher_class.name&.to_sym || researcher_class.object_id.to_s.to_sym
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
tool_name "read_store"
|
|
154
|
-
description "Read all current findings from the shared knowledge store. " \
|
|
155
|
-
"Call this to see what other researchers have discovered."
|
|
156
|
-
execution_mode :cooperative
|
|
157
|
-
|
|
158
|
-
define_method(:execute) { store.read_all.to_json }
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
write_tool = Class.new(Phronomy::Agent::Context::Capability::Base) do
|
|
162
|
-
tool_name "write_finding"
|
|
163
|
-
description "Record a new finding into the shared knowledge store so " \
|
|
164
|
-
"that other researchers can build on your discovery."
|
|
165
|
-
execution_mode :cooperative
|
|
166
|
-
param :content, type: :string, desc: "The finding to record"
|
|
167
|
-
|
|
168
|
-
define_method(:execute) do |content:|
|
|
169
|
-
store.write(agent: agent_key, content: content, cycle: cycle)
|
|
170
|
-
"Finding recorded."
|
|
171
|
-
end
|
|
172
|
-
end
|
|
161
|
+
read_tool = build_read_tool(store)
|
|
162
|
+
write_tool = build_write_tool(store, cycle, agent_key)
|
|
173
163
|
|
|
174
164
|
definitions = researcher_class.tools.to_h do |tool_class|
|
|
175
165
|
[tool_class, researcher_class.tool_aliases[tool_class]]
|
|
@@ -197,12 +187,39 @@ module Phronomy
|
|
|
197
187
|
end
|
|
198
188
|
end
|
|
199
189
|
|
|
190
|
+
def build_read_tool(store)
|
|
191
|
+
Class.new(Phronomy::Agent::Context::Capability::Base) do
|
|
192
|
+
tool_name "read_store"
|
|
193
|
+
description "Read all current findings from the shared knowledge store. " \
|
|
194
|
+
"Call this to see what other researchers have discovered."
|
|
195
|
+
execution_mode :cooperative
|
|
196
|
+
|
|
197
|
+
define_method(:execute) { store.read_all.to_json }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def build_write_tool(store, cycle, agent_key)
|
|
202
|
+
Class.new(Phronomy::Agent::Context::Capability::Base) do
|
|
203
|
+
tool_name "write_finding"
|
|
204
|
+
description "Record a new finding into the shared knowledge store so " \
|
|
205
|
+
"that other researchers can build on your discovery."
|
|
206
|
+
execution_mode :cooperative
|
|
207
|
+
param :content, type: :string, desc: "The finding to record"
|
|
208
|
+
|
|
209
|
+
define_method(:execute) do |content:|
|
|
210
|
+
store.write(agent: agent_key, content: content, cycle: cycle)
|
|
211
|
+
"Finding recorded."
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
200
216
|
def instrumented_definition_for(researcher_class)
|
|
201
217
|
parent_def = researcher_class.agent_definition
|
|
202
218
|
parent_id = parent_def.fetch(:id)
|
|
203
219
|
parent_version = parent_def.fetch(:version)
|
|
204
220
|
|
|
205
221
|
{
|
|
222
|
+
# Keep the existing semantic ID across the Ruby namespace migration.
|
|
206
223
|
id: "Phronomy::Agent::SharedState::Instrumented/#{parent_id}@#{parent_version}".freeze,
|
|
207
224
|
version: INSTRUMENTATION_DEFINITION_VERSION
|
|
208
225
|
}.freeze
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
# Domain-owned Team metadata, independent of coordination runtime loading.
|
|
5
|
+
# @api private
|
|
6
|
+
module TeamStorageSchema
|
|
7
|
+
ROOTS = Phronomy::Storage::Resource.new(id: "team.roots", kind: :records,
|
|
8
|
+
guard: {resource: "team.roots", via: :key})
|
|
9
|
+
EXECUTIONS = Phronomy::Storage::Resource.new(id: "team.executions", kind: :records,
|
|
10
|
+
attributes: {owner: :string, active: :boolean}, immutable_attributes: [:owner],
|
|
11
|
+
indexes: {owner: [:owner], owner_active: [:owner, :active]},
|
|
12
|
+
unique: [{name: :one_active_owner, fields: [:owner], where: {active: true}}],
|
|
13
|
+
guard: {resource: ROOTS.id, via: :owner})
|
|
14
|
+
end
|
|
15
|
+
end
|