phronomy 0.26.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 +40 -0
- data/README.md +4 -0
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -22
- data/benchmark/bench_context_assembler.rb +4 -5
- 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 +4 -2
- 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 +27 -4
- 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 +1 -1
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +209 -0
- 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 +9 -3
- data/docs/getting-started.md +4 -4
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
- 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 +117 -32
- 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 +51 -241
- 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} +19 -95
- 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} +9 -10
- 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} +20 -71
- 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 +32 -37
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
- 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/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} +1 -1
- 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 +4 -0
- data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
- data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/engine/event_loop.rb +158 -624
- data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
- data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
- data/lib/phronomy/engine/execution_receiver.rb +65 -0
- data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
- data/lib/phronomy/engine/fsm_protocol.rb +14 -0
- data/lib/phronomy/engine/fsm_session.rb +35 -28
- data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
- 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.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_result.rb +4 -0
- 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_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 +5 -5
- data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
- 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/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 +1 -1
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
- 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} +122 -71
- 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 +2 -2
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
- data/lib/phronomy.rb +52 -94
- data/scripts/api_snapshot.rb +1 -1
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/phronomy/agent.rbs +0 -1
- data/sig/phronomy/execution_receiver.rbs +34 -0
- data/sig/phronomy/handoff.rbs +4 -2
- data/sig/phronomy/multi_agent.rbs +18 -0
- data/sig/phronomy/persistence.rbs +7 -91
- data/sig/phronomy/storage.rbs +174 -0
- data/sig/phronomy/tool.rbs +10 -1
- metadata +230 -99
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
- 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/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/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.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/{invocation_context.rb → engine/invocation_context.rb} +0 -0
- /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
- /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
|
@@ -23,37 +23,38 @@ are defined by
|
|
|
23
23
|
|
|
24
24
|
## Runtime model
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
│ │ └─ agent_id -> one nonterminal logical Execution admission
|
|
38
|
-
│ └─ Agent execution directory
|
|
39
|
-
│ └─ execution_id -> immutable live-state record
|
|
40
|
-
├─ OffloadPool (bounded operating-system Threads)
|
|
41
|
-
│ ├─ private Operation records
|
|
42
|
-
│ ├─ blocking input/output (I/O)
|
|
43
|
-
│ ├─ central-processing-unit (CPU)-bound synchronous work
|
|
44
|
-
│ └─ operation-specific durable Agent/Workflow work
|
|
45
|
-
├─ named OffloadPools
|
|
46
|
-
└─ EventLoop-driven timers
|
|
47
|
-
|
|
48
|
-
EventLoop / FSMSession ─┐
|
|
49
|
-
├─> TaskResult = completion handle
|
|
50
|
-
OffloadPool ────────────┘
|
|
51
|
-
```
|
|
26
|
+
| Runtime-retained component | Implementation owner and responsibility |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Shutdown participants | Feature-owned registries: Agent identity, Team identity, and MultiAgent synchronous-call admission |
|
|
29
|
+
| EventLoop | Engine dispatcher thread, FSMSessions and registered feature receivers |
|
|
30
|
+
| Execution receivers | Agent/Workflow own execution admission, state and idle policies; mutations run on EventLoop |
|
|
31
|
+
| OffloadPool and named pools | Engine bounded workers and private physical-operation records |
|
|
32
|
+
| Timers | Engine EventLoop-driven scheduling |
|
|
33
|
+
|
|
34
|
+
Runtime retains participants through a generic lifecycle protocol; it does not
|
|
35
|
+
construct Agent or Team identity registries. Each feature registers its own
|
|
36
|
+
instance. See [ADR-041](decisions/041-feature-owned-identity-registries.md).
|
|
52
37
|
|
|
53
38
|
The framework does not allocate one operating-system Thread per logical
|
|
54
39
|
Agent/Workflow/Tool lifecycle. Logical waits remain explicit states plus later
|
|
55
40
|
EventLoop events.
|
|
56
41
|
|
|
42
|
+
## Feature execution receivers
|
|
43
|
+
|
|
44
|
+
[ADR-042](decisions/042-feature-owned-execution-state.md) separates implementation
|
|
45
|
+
ownership from the single-writer execution context. Agent and Workflow create
|
|
46
|
+
`Agent::ExecutionRegistry` and `WorkflowExecutionRegistry` respectively; Engine
|
|
47
|
+
retains registered receivers without selecting concrete feature types.
|
|
48
|
+
|
|
49
|
+
Receiver registration and new request admission close before idle checks.
|
|
50
|
+
Already queued requests, pending durable operations and supervised physical
|
|
51
|
+
work are counted until their feature continuation settles. Worker callbacks
|
|
52
|
+
only enqueue results. Workflow instance routing and generic FSM enqueue share
|
|
53
|
+
one lock. After a clean join, receiver cleanup invalidates retained state;
|
|
54
|
+
a failing receiver makes Runtime cleanup incomplete. Dispatcher failure instead
|
|
55
|
+
notifies receivers on the failing loop thread with the original error.
|
|
56
|
+
|
|
57
|
+
|
|
57
58
|
## Live state and durable state
|
|
58
59
|
|
|
59
60
|
A live Agent or Workflow owns its current logical state. `Persistence` is the
|
|
@@ -61,7 +62,7 @@ last committed durable representation and recovery source; it is not reloaded at
|
|
|
61
62
|
every semantic boundary.
|
|
62
63
|
|
|
63
64
|
For active Agents, **EventLoop is the single writer of Phronomy-managed live
|
|
64
|
-
execution state**.
|
|
65
|
+
execution state**. `Agent::ExecutionRegistry` owns the process-local directory keyed by
|
|
65
66
|
canonical `execution_id`. Each directory value is immutable and is replaced on
|
|
66
67
|
EventLoop when the current AgentExecution, RuntimeProjection, AgentInvocation, or
|
|
67
68
|
owning FSMSession changes. The former mutex-protected
|
|
@@ -75,7 +76,7 @@ handling; workers do not receive AgentInvocation as a mutable state authority.
|
|
|
75
76
|
Mutable Agent/Execution/Journal state is not automatically reloaded before every
|
|
76
77
|
LLM or Tool step. Durable writes use optimistic revision/position guardrails; an
|
|
77
78
|
external writer that advances the durable base causes
|
|
78
|
-
`
|
|
79
|
+
`Storage::ConflictError` rather than automatic reload or merge.
|
|
79
80
|
|
|
80
81
|
For Workflows, the current `WorkflowContext` and FSMSession own the active
|
|
81
82
|
logical state. A durable Workflow hydrates once at invocation/resume and saves at
|
|
@@ -88,8 +89,9 @@ content reference is value materialization rather than mutable state refresh.
|
|
|
88
89
|
|
|
89
90
|
`agent_id` identifies one logical Agent, not a reusable lookup key for independent
|
|
90
91
|
mutable objects. One Runtime therefore publishes at most one mutable live Agent
|
|
91
|
-
instance for a given `agent_id`. The
|
|
92
|
-
cache, and reserves the identity before
|
|
92
|
+
instance for a given `agent_id`. The Agent-owned registry, strongly retained by
|
|
93
|
+
Runtime, is an authority, not a cache, and reserves the identity before
|
|
94
|
+
create/load materialization.
|
|
93
95
|
|
|
94
96
|
The application-facing identity operations are distinct:
|
|
95
97
|
|
|
@@ -100,7 +102,7 @@ new / create
|
|
|
100
102
|
load(agent_id, persistence:)
|
|
101
103
|
live -> exact same Ruby object, with no Persistence reload
|
|
102
104
|
durable-only -> hydrate and publish once
|
|
103
|
-
missing ->
|
|
105
|
+
missing -> Storage::NotFoundError
|
|
104
106
|
|
|
105
107
|
get(agent_id)
|
|
106
108
|
live Runtime lookup only; missing -> nil
|
|
@@ -114,7 +116,8 @@ invalidates the old object, deletes durable state, releases the process-local
|
|
|
114
116
|
identity, and allows a later new Agent to reuse the textual ID.
|
|
115
117
|
|
|
116
118
|
Live Agent ownership and top-level Execution admission are separate lifetimes.
|
|
117
|
-
For one live Agent,
|
|
119
|
+
For one live Agent, `Agent::ExecutionRegistry` admits at most one nonterminal
|
|
120
|
+
top-level Execution on EventLoop.
|
|
118
121
|
Admission is acquired **before** the initial Offload/Persistence operation:
|
|
119
122
|
|
|
120
123
|
```text
|
|
@@ -195,6 +198,15 @@ Phronomy-managed live domain objects are rejected from that value data. A comple
|
|
|
195
198
|
value-type/serialization contract for arbitrary Application-owned opaque objects is
|
|
196
199
|
deferred; such objects remain Application-owned and must be worker-safe.
|
|
197
200
|
|
|
201
|
+
The existing restricted types declare the internal methodless
|
|
202
|
+
`Concurrency::WorkerInputRestricted` marker at their own definitions.
|
|
203
|
+
ToolInvocation checks that execution-boundary contract instead of enumerating
|
|
204
|
+
Agent/Workflow/Runtime classes. Frozen marked values and subclasses remain
|
|
205
|
+
restricted; Hash keys and values, nested Arrays and behavior handles are all
|
|
206
|
+
checked. The marker does not inspect opaque application fields or closure
|
|
207
|
+
captures and is not a general restriction on every OffloadPool command.
|
|
208
|
+
See [ADR-045](decisions/045-worker-input-restriction-ownership.md).
|
|
209
|
+
|
|
198
210
|
Worker authorization/execution outcomes return as values carrying
|
|
199
211
|
`tool_invocation_id`; the Tool FSMSession consumes a mismatched semantic result
|
|
200
212
|
without advancing its current state.
|
|
@@ -279,6 +291,32 @@ remain durable conflict defense rather than distributed ownership.
|
|
|
279
291
|
|
|
280
292
|
## Tool execution modes
|
|
281
293
|
|
|
294
|
+
The default dispatch helper is the private
|
|
295
|
+
`Phronomy::Agent::Context::Capability::ToolExecutor`, colocated with Capability
|
|
296
|
+
Base. Agent ToolInvocation supplies Runtime and admission policy for the
|
|
297
|
+
standard path, and owns authorization and logical result handling. Custom
|
|
298
|
+
`call_async` implementations keep the public Tool protocol. This ownership is
|
|
299
|
+
defined by [ADR-035](decisions/035-tool-executor-capability-ownership.md); the
|
|
300
|
+
public `Phronomy::Tool::Base` facade and its Class identity remain unchanged.
|
|
301
|
+
|
|
302
|
+
Agent uses ordinary `RubyLLM::Chat` for both complete and streaming Provider
|
|
303
|
+
calls. The `before_tool_call` callback takes the complete assistant Tool-call
|
|
304
|
+
batch before RubyLLM executes the first Tool body. `AgentInvocation` then owns
|
|
305
|
+
authorization, dispatch, approval suspension and result collection. Offloaded
|
|
306
|
+
Tools can overlap within Runtime capacity; short cooperative Tools run according
|
|
307
|
+
to their existing execution contract.
|
|
308
|
+
|
|
309
|
+
On successful batch completion, Agent records one Tool result per call ID in
|
|
310
|
+
request order and includes the whole exchange in the next Provider request.
|
|
311
|
+
Worker completion order does not split the conversation or trigger a partial
|
|
312
|
+
Provider continuation. Failure, cancellation and approval keep their existing
|
|
313
|
+
Agent lifecycle semantics.
|
|
314
|
+
|
|
315
|
+
There is no separate Chat execution path or `parallel_tool_execution` switch.
|
|
316
|
+
The removed internal `MultiAgent::ParallelToolChat` class is not replaced by an
|
|
317
|
+
alias. See the [Chat migration guide](migrations/parallel-tool-chat-removal.md)
|
|
318
|
+
for application configuration and existing stored model-config records.
|
|
319
|
+
|
|
282
320
|
Phronomy exposes two execution modes for capabilities:
|
|
283
321
|
|
|
284
322
|
- `:cooperative` — short EventLoop-safe work, or specialized asynchronous work
|
|
@@ -547,6 +585,53 @@ Use these to distinguish worker saturation from EventLoop backlog/latency.
|
|
|
547
585
|
Runtime-owned EventLoop, then closes pools and timers according to the Runtime
|
|
548
586
|
shutdown contract.
|
|
549
587
|
|
|
588
|
+
Internal subsystems can register one shutdown participant per Runtime-local key
|
|
589
|
+
through `Runtime#__register_shutdown_participant`. This is an internal lifecycle
|
|
590
|
+
contract, not a public extension API or an execution registry. Runtime does not
|
|
591
|
+
construct participants or interpret their admission rules.
|
|
592
|
+
|
|
593
|
+
Each participant implements two required operations:
|
|
594
|
+
|
|
595
|
+
- `begin_draining` closes admission under the participant's admission lock. It
|
|
596
|
+
must be idempotent, short and nonblocking, and must not call back into Runtime.
|
|
597
|
+
Runtime invokes it under its lifecycle lock when shutdown starts or EventLoop
|
|
598
|
+
fails, so no participant can be registered after closure begins.
|
|
599
|
+
- `wait_until_idle(deadline)` returns whether admitted calls have finished before
|
|
600
|
+
the shared absolute monotonic deadline. Runtime closes every participant before
|
|
601
|
+
waiting, and waits without holding its lifecycle lock. A timeout or failed hook
|
|
602
|
+
makes cleanup incomplete; other participants, pools and timers still receive
|
|
603
|
+
their shutdown calls. Incomplete cleanup prevents default Runtime replacement.
|
|
604
|
+
|
|
605
|
+
Participants may also implement `after_runtime_shutdown`. Runtime calls it
|
|
606
|
+
outside its lifecycle lock after every wait succeeds, EventLoop has stopped,
|
|
607
|
+
and pools/timers have shut down successfully. The hook must be idempotent,
|
|
608
|
+
short, and perform no I/O; its return value is ignored. A hook exception marks
|
|
609
|
+
cleanup incomplete, retains the first failure, and does not skip later hooks.
|
|
610
|
+
Completed releases are not rolled back; repeated shutdown returns its cached
|
|
611
|
+
result without retrying hooks. A prior closure/wait failure, timeout, or worker
|
|
612
|
+
shutdown failure skips finalization entirely.
|
|
613
|
+
|
|
614
|
+
`Runtime#__shutdown_participant(key:)` only retrieves an existing participant,
|
|
615
|
+
including during/after shutdown. Registration still requires a running Runtime,
|
|
616
|
+
even for an existing key. Agent/Team `get` uses lookup without registration;
|
|
617
|
+
feature gates reject new ownership changes through retained registries after
|
|
618
|
+
closure. Already admitted transitions may finish. Identity registries wait only
|
|
619
|
+
for active construction/purge, then detach Agent references or clear Team
|
|
620
|
+
owners at finalization; live idle objects do not themselves prolong shutdown.
|
|
621
|
+
|
|
622
|
+
`MultiAgent::AdmissionRegistry.for(runtime)` constructs and registers the shared
|
|
623
|
+
coordination admission registry. MultiAgent::HandoffRunner and TeamCoordinator retain that
|
|
624
|
+
registry, admit each synchronous call directly and release it in `ensure` after
|
|
625
|
+
successful admission. The registry closes admission atomically with respect to
|
|
626
|
+
`admit!`; a call admitted before closure is included in the wait, and a call after
|
|
627
|
+
closure raises `RuntimeShutdownError`, including after EventLoop failure. The
|
|
628
|
+
existing `HandoffError` for duplicate admission remains unchanged.
|
|
629
|
+
|
|
630
|
+
This tracks the current synchronous Handoff/Team call, not the lifetime or
|
|
631
|
+
completion of a durable AgentExecution or TeamExecution. Multi-Agent policy stays
|
|
632
|
+
in `multi_agent`; Runtime depends only on the shutdown operations. Application
|
|
633
|
+
authors do not register these participants themselves.
|
|
634
|
+
|
|
550
635
|
Workflow durable admission participates in EventLoop idleness: a Workflow whose
|
|
551
636
|
FSMSession has ended but whose durable save is still in flight remains owned until
|
|
552
637
|
that save completes and owner-aware admission is released.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Agent
|
|
5
|
+
StreamEvent = Data.define(:type, :payload)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require_relative "../async_event_api"
|
|
10
|
+
|
|
11
|
+
unless Phronomy::Agent::Base < Phronomy::Agent::AsyncEventApi
|
|
12
|
+
Phronomy::Agent::Base.prepend(Phronomy::Agent::AsyncEventApi)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Durable Agent Recovery and Agent-incarnation event binding.
|
|
16
|
+
require_relative "../recovery/recovery_support"
|
|
17
|
+
require_relative "../recovery/recovery_coordinator"
|
|
@@ -96,7 +96,7 @@ module Phronomy
|
|
|
96
96
|
|
|
97
97
|
def approve_async(execution_id, approval_request_id:, approved: true, config: {})
|
|
98
98
|
_reject_removed_generic_identity_keys!(config)
|
|
99
|
-
owner = Phronomy::Runtime.instance
|
|
99
|
+
owner = Phronomy::Agent::ExecutionRegistry.existing_for(Phronomy::Runtime.instance)&.agent_execution_owner(execution_id)
|
|
100
100
|
coordinator = if owner&.agent&.equal?(self)
|
|
101
101
|
owner.coordinator
|
|
102
102
|
else
|
|
@@ -190,7 +190,7 @@ module Phronomy
|
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
bytes = Phronomy::CanonicalJSON.dump(value)
|
|
193
|
-
snapshot = Phronomy::
|
|
193
|
+
snapshot = Phronomy::Values::Immutable.copy(
|
|
194
194
|
Phronomy::CanonicalJSON.load(bytes)
|
|
195
195
|
)
|
|
196
196
|
config.merge(durable_context: snapshot)
|
data/lib/phronomy/agent/base.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../engine/concurrency/worker_input_restricted"
|
|
4
|
+
|
|
3
5
|
require "securerandom"
|
|
4
6
|
require_relative "concerns/filterable"
|
|
5
7
|
require_relative "concerns/before_llm_input"
|
|
@@ -31,6 +33,8 @@ module Phronomy
|
|
|
31
33
|
# max_iterations 15
|
|
32
34
|
# end
|
|
33
35
|
class Base
|
|
36
|
+
include Phronomy::Concurrency::WorkerInputRestricted
|
|
37
|
+
|
|
34
38
|
include Phronomy::Runnable
|
|
35
39
|
include Concerns::Filterable
|
|
36
40
|
include Concerns::BeforeLLMInput
|
|
@@ -119,15 +123,9 @@ module Phronomy
|
|
|
119
123
|
if val.nil?
|
|
120
124
|
@max_output_tokens
|
|
121
125
|
else
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def context_window(val = nil)
|
|
127
|
-
if val.nil?
|
|
128
|
-
@context_window
|
|
129
|
-
else
|
|
130
|
-
@context_window = val.to_i
|
|
126
|
+
value = Integer(val)
|
|
127
|
+
raise ArgumentError, "max_output_tokens must be positive" unless value.positive?
|
|
128
|
+
@max_output_tokens = value
|
|
131
129
|
end
|
|
132
130
|
end
|
|
133
131
|
|
|
@@ -204,7 +202,7 @@ module Phronomy
|
|
|
204
202
|
runtime = Phronomy::Runtime.instance
|
|
205
203
|
materialized = false
|
|
206
204
|
|
|
207
|
-
agent = runtime.
|
|
205
|
+
agent = OwnershipRegistry.for(runtime).load(key, expected_class: self) do |owner_runtime|
|
|
208
206
|
materialized = true
|
|
209
207
|
instance = __construct_owned_agent(
|
|
210
208
|
owner_runtime,
|
|
@@ -235,14 +233,17 @@ module Phronomy
|
|
|
235
233
|
# Returns only the process-local live Agent owner. Does not access
|
|
236
234
|
# Persistence and returns nil when this Runtime has no live owner.
|
|
237
235
|
def get(agent_id)
|
|
238
|
-
|
|
236
|
+
key = agent_id.to_s
|
|
237
|
+
raise ArgumentError, "agent_id must not be empty" if key.empty?
|
|
238
|
+
|
|
239
|
+
OwnershipRegistry.existing_for(Phronomy::Runtime.instance)&.get(key, expected_class: self)
|
|
239
240
|
end
|
|
240
241
|
|
|
241
242
|
# Resolves the live Agent instance that currently owns execution_id in
|
|
242
243
|
# this process. The Runtime returns only a read-only ownership view;
|
|
243
244
|
# mutable Agent execution state remains EventLoop-owned.
|
|
244
245
|
def live_for_execution(execution_id)
|
|
245
|
-
owner = Phronomy::Runtime.instance
|
|
246
|
+
owner = Phronomy::Agent::ExecutionRegistry.existing_for(Phronomy::Runtime.instance)&.agent_execution_owner(execution_id)
|
|
246
247
|
unless owner
|
|
247
248
|
raise Phronomy::ExecutionRehydrationRequiredError,
|
|
248
249
|
"no live execution owner for #{execution_id}; durable rehydration is required"
|
|
@@ -338,7 +339,7 @@ module Phronomy
|
|
|
338
339
|
|
|
339
340
|
runtime = Phronomy::Runtime.instance
|
|
340
341
|
begin
|
|
341
|
-
runtime.
|
|
342
|
+
OwnershipRegistry.for(runtime).create(effective_agent_id, expected_class: self.class) do |owner_runtime|
|
|
342
343
|
__prepare_runtime_owner!(owner_runtime, effective_agent_id)
|
|
343
344
|
initialize_owned_state(
|
|
344
345
|
agent_id: effective_agent_id,
|
|
@@ -350,7 +351,7 @@ module Phronomy
|
|
|
350
351
|
)
|
|
351
352
|
self
|
|
352
353
|
end
|
|
353
|
-
rescue Phronomy::
|
|
354
|
+
rescue Phronomy::Storage::ConflictError => error
|
|
354
355
|
raise Phronomy::AgentAlreadyExistsError,
|
|
355
356
|
"Agent #{effective_agent_id.inspect} already exists durably: #{error.message}"
|
|
356
357
|
end
|
|
@@ -395,34 +396,8 @@ module Phronomy
|
|
|
395
396
|
def add_knowledge(content, metadata: {})
|
|
396
397
|
__assert_live_agent!
|
|
397
398
|
current = agent_root
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
persistence.transaction do |tx|
|
|
401
|
-
tx.executions.assert_idle!(agent_id)
|
|
402
|
-
record = build_knowledge_record(
|
|
403
|
-
tx: tx,
|
|
404
|
-
root: current,
|
|
405
|
-
content: content,
|
|
406
|
-
metadata: metadata
|
|
407
|
-
)
|
|
408
|
-
appended = tx.journals.append(
|
|
409
|
-
agent_id,
|
|
410
|
-
expected_position: current.journal_position,
|
|
411
|
-
records: [record]
|
|
412
|
-
)
|
|
413
|
-
next_root = current.with(
|
|
414
|
-
agent_revision: current.agent_revision + 1,
|
|
415
|
-
context_revision: current.context_revision + 1,
|
|
416
|
-
journal_position: current.journal_position + appended.length
|
|
417
|
-
)
|
|
418
|
-
tx.agents.save(
|
|
419
|
-
agent_id,
|
|
420
|
-
expected_revision: current.agent_revision,
|
|
421
|
-
root: next_root
|
|
422
|
-
)
|
|
423
|
-
end
|
|
424
|
-
_append_journal_records(appended)
|
|
425
|
-
@root = next_root
|
|
399
|
+
update = state_writer.add_knowledge(root: current, content: content, metadata: metadata)
|
|
400
|
+
apply_state_update(update)
|
|
426
401
|
self
|
|
427
402
|
end
|
|
428
403
|
|
|
@@ -450,9 +425,10 @@ module Phronomy
|
|
|
450
425
|
|
|
451
426
|
__assert_live_agent!
|
|
452
427
|
runtime = @_phronomy_runtime_owner
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
428
|
+
registry = OwnershipRegistry.for(runtime)
|
|
429
|
+
token = registry.begin_purge(self)
|
|
430
|
+
if Phronomy::Agent::ExecutionRegistry.existing_for(runtime)&.agent_execution_admitted?(agent_id)
|
|
431
|
+
registry.abort_purge(self, token)
|
|
456
432
|
raise Phronomy::AgentBusyError,
|
|
457
433
|
"Agent #{agent_id.inspect} has a nonterminal top-level execution"
|
|
458
434
|
end
|
|
@@ -471,20 +447,20 @@ module Phronomy
|
|
|
471
447
|
tx.agents.delete(agent_id)
|
|
472
448
|
end
|
|
473
449
|
rescue Phronomy::AgentBusyError,
|
|
474
|
-
Phronomy::
|
|
475
|
-
Phronomy::
|
|
476
|
-
Phronomy::
|
|
450
|
+
Phronomy::Storage::ConflictError,
|
|
451
|
+
Phronomy::Storage::NotFoundError,
|
|
452
|
+
Phronomy::Storage::SerializationError,
|
|
477
453
|
ArgumentError
|
|
478
|
-
|
|
454
|
+
registry.abort_purge(self, token)
|
|
479
455
|
raise
|
|
480
456
|
rescue
|
|
481
457
|
# Without F1 reconciliation support we cannot infer that a failed
|
|
482
458
|
# durable delete did not commit. Keep the identity fail-closed.
|
|
483
|
-
|
|
459
|
+
registry.leave_purge_uncertain(self, token)
|
|
484
460
|
raise
|
|
485
461
|
end
|
|
486
462
|
|
|
487
|
-
|
|
463
|
+
registry.complete_purge(self, token)
|
|
488
464
|
true
|
|
489
465
|
end
|
|
490
466
|
|
|
@@ -585,7 +561,7 @@ module Phronomy
|
|
|
585
561
|
)
|
|
586
562
|
@persistence = persistence ||
|
|
587
563
|
Phronomy.configuration.persistence ||
|
|
588
|
-
|
|
564
|
+
DefaultPersistence.build
|
|
589
565
|
@agent_id = agent_id.to_s.freeze
|
|
590
566
|
|
|
591
567
|
if load_existing
|
|
@@ -630,117 +606,27 @@ module Phronomy
|
|
|
630
606
|
|
|
631
607
|
def create_agent_root!(context:, knowledge:, metadata:)
|
|
632
608
|
definition = self.class.agent_definition
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
agent_definition_id: definition.fetch(:id),
|
|
636
|
-
agent_definition_version: definition.fetch(:version),
|
|
637
|
-
metadata: metadata
|
|
638
|
-
)
|
|
639
|
-
persistence.transaction do |tx|
|
|
640
|
-
tx.agents.create(root)
|
|
641
|
-
records = initial_context_records(tx: tx, root: root, context: context)
|
|
642
|
-
records.concat(initial_knowledge_records(tx: tx, root: root, knowledge: knowledge))
|
|
643
|
-
unless records.empty?
|
|
644
|
-
appended = tx.journals.append(agent_id, expected_position: 0, records: records)
|
|
645
|
-
root = root.with(
|
|
646
|
-
agent_revision: 1,
|
|
647
|
-
context_revision: 1,
|
|
648
|
-
journal_position: appended.length
|
|
649
|
-
)
|
|
650
|
-
tx.agents.save(agent_id, expected_revision: 0, root: root)
|
|
651
|
-
end
|
|
652
|
-
end
|
|
653
|
-
root
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
def initial_context_records(tx:, root:, context:)
|
|
657
|
-
return [] unless context
|
|
658
|
-
|
|
659
|
-
imported = context.respond_to?(:records) ? context :
|
|
660
|
-
Agent::ContextImporter.import_messages(context)
|
|
661
|
-
imported.records.map do |record|
|
|
662
|
-
content_ref = case record.content_format
|
|
663
|
-
when :text then tx.contents.put_text(record.content)
|
|
664
|
-
when :json then tx.contents.put_json(record.content)
|
|
665
|
-
else
|
|
666
|
-
raise ArgumentError,
|
|
667
|
-
"unsupported imported content format: #{record.content_format.inspect}"
|
|
668
|
-
end
|
|
669
|
-
Agent::JournalRecord.new(
|
|
670
|
-
agent_id: agent_id,
|
|
671
|
-
kind: record.kind,
|
|
672
|
-
channel: record.channel,
|
|
673
|
-
role: record.role,
|
|
674
|
-
content_ref: content_ref,
|
|
675
|
-
context_generation: root.transcript_generation,
|
|
676
|
-
context_candidate: true,
|
|
677
|
-
metadata: record.metadata
|
|
678
|
-
)
|
|
679
|
-
end
|
|
680
|
-
end
|
|
681
|
-
|
|
682
|
-
def initial_knowledge_records(tx:, root:, knowledge:)
|
|
683
|
-
Array(knowledge).map do |content|
|
|
684
|
-
build_knowledge_record(
|
|
685
|
-
tx: tx,
|
|
686
|
-
root: root,
|
|
687
|
-
content: content,
|
|
688
|
-
metadata: {}
|
|
689
|
-
)
|
|
690
|
-
end
|
|
691
|
-
end
|
|
692
|
-
|
|
693
|
-
def build_knowledge_record(tx:, root:, content:, metadata:)
|
|
694
|
-
Agent::JournalRecord.new(
|
|
695
|
-
agent_id: agent_id,
|
|
696
|
-
kind: :knowledge,
|
|
697
|
-
channel: :context,
|
|
698
|
-
role: :user,
|
|
699
|
-
content_ref: tx.contents.put_text(String(content)),
|
|
700
|
-
context_generation: root.transcript_generation,
|
|
701
|
-
context_candidate: true,
|
|
702
|
-
metadata: metadata || {}
|
|
609
|
+
state_writer.create_root(
|
|
610
|
+
definition: definition, context: context, knowledge: knowledge, metadata: metadata
|
|
703
611
|
)
|
|
704
612
|
end
|
|
705
613
|
|
|
706
614
|
def mutate_context!(kind, context_affecting: true)
|
|
707
615
|
__assert_live_agent!
|
|
708
616
|
current = agent_root
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
record = Agent::JournalRecord.new(
|
|
714
|
-
agent_id: agent_id,
|
|
715
|
-
kind: kind,
|
|
716
|
-
channel: :state,
|
|
717
|
-
context_generation: current.transcript_generation,
|
|
718
|
-
context_candidate: false
|
|
719
|
-
)
|
|
720
|
-
appended = tx.journals.append(
|
|
721
|
-
agent_id,
|
|
722
|
-
expected_position: current.journal_position,
|
|
723
|
-
records: [record]
|
|
724
|
-
)
|
|
725
|
-
proposed = yield(current)
|
|
726
|
-
next_root = proposed.with(
|
|
727
|
-
journal_position: current.journal_position + appended.length,
|
|
728
|
-
context_revision: context_affecting ?
|
|
729
|
-
yield_context_revision(current, proposed) : current.context_revision
|
|
730
|
-
)
|
|
731
|
-
tx.agents.save(
|
|
732
|
-
agent_id,
|
|
733
|
-
expected_revision: current.agent_revision,
|
|
734
|
-
root: next_root
|
|
735
|
-
)
|
|
736
|
-
end
|
|
737
|
-
_append_journal_records(appended)
|
|
738
|
-
@root = next_root
|
|
617
|
+
update = state_writer.mutate_context(
|
|
618
|
+
root: current, kind: kind, context_affecting: context_affecting
|
|
619
|
+
) { |root| yield(root) }
|
|
620
|
+
apply_state_update(update)
|
|
739
621
|
end
|
|
740
622
|
|
|
741
|
-
def
|
|
742
|
-
(
|
|
743
|
-
|
|
623
|
+
def state_writer
|
|
624
|
+
StateWriter.new(persistence: persistence, agent_id: agent_id)
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def apply_state_update(update)
|
|
628
|
+
_append_journal_records(update.records)
|
|
629
|
+
@root = update.root
|
|
744
630
|
end
|
|
745
631
|
|
|
746
632
|
def _journal_records_snapshot
|
|
@@ -789,7 +675,8 @@ module Phronomy
|
|
|
789
675
|
return true if @_phronomy_runtime_owner_state == :constructing
|
|
790
676
|
|
|
791
677
|
runtime = @_phronomy_runtime_owner
|
|
792
|
-
|
|
678
|
+
registry = OwnershipRegistry.existing_for(runtime) if runtime
|
|
679
|
+
unless registry&.owned?(self)
|
|
793
680
|
raise Phronomy::RuntimeShutdownError,
|
|
794
681
|
"Agent #{agent_id.inspect} is no longer the live owner in its Runtime"
|
|
795
682
|
end
|
|
@@ -943,7 +830,7 @@ module Phronomy
|
|
|
943
830
|
)
|
|
944
831
|
end
|
|
945
832
|
projection.tool_classes.each do |tool_class|
|
|
946
|
-
chat.
|
|
833
|
+
chat.with_tools(prepare_tool_class(tool_class, invocation: invocation))
|
|
947
834
|
end
|
|
948
835
|
projection.messages.each { |message| chat.messages << message }
|
|
949
836
|
chat
|
|
@@ -954,25 +841,9 @@ module Phronomy
|
|
|
954
841
|
"model" => self.class.model,
|
|
955
842
|
"provider" => self.class.provider,
|
|
956
843
|
"temperature" => self.class.temperature,
|
|
957
|
-
"max_output_tokens" => self.class.max_output_tokens
|
|
958
|
-
"parallel_tool_execution" => Phronomy.configuration.parallel_tool_execution
|
|
844
|
+
"max_output_tokens" => self.class.max_output_tokens
|
|
959
845
|
}
|
|
960
|
-
|
|
961
|
-
model = config["model"]
|
|
962
|
-
opts[:model] = model if model
|
|
963
|
-
provider = config["provider"]
|
|
964
|
-
if provider
|
|
965
|
-
opts[:provider] = provider.to_sym
|
|
966
|
-
opts[:assume_model_exists] = true
|
|
967
|
-
end
|
|
968
|
-
parallel_class = config["parallel_tool_execution"] ?
|
|
969
|
-
Phronomy::MultiAgent::ParallelToolChat : nil
|
|
970
|
-
chat = parallel_class ? parallel_class.new(**opts) : RubyLLM.chat(**opts)
|
|
971
|
-
chat.with_temperature(config["temperature"]) if config["temperature"]
|
|
972
|
-
if config["max_output_tokens"] && chat.respond_to?(:with_max_output_tokens)
|
|
973
|
-
chat.with_max_output_tokens(config["max_output_tokens"])
|
|
974
|
-
end
|
|
975
|
-
chat
|
|
846
|
+
RuntimeChatBuilder.build(config)
|
|
976
847
|
end
|
|
977
848
|
|
|
978
849
|
def build_instructions(input)
|
|
@@ -988,12 +859,7 @@ module Phronomy
|
|
|
988
859
|
end
|
|
989
860
|
|
|
990
861
|
def apply_instructions(chat, text, cache: false, provider: nil)
|
|
991
|
-
|
|
992
|
-
content = RubyLLM::Providers::Anthropic::Content.new(text, cache: true)
|
|
993
|
-
chat.with_instructions(content)
|
|
994
|
-
else
|
|
995
|
-
chat.with_instructions(text)
|
|
996
|
-
end
|
|
862
|
+
RuntimeChatBuilder.apply_instructions(chat, text, cache: cache, provider: provider)
|
|
997
863
|
end
|
|
998
864
|
|
|
999
865
|
def extract_message(input)
|
|
@@ -1021,66 +887,10 @@ module Phronomy
|
|
|
1021
887
|
def prepare_tool_class(tool_class, invocation: nil)
|
|
1022
888
|
return tool_class unless tool_class.is_a?(Class)
|
|
1023
889
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
else
|
|
1029
|
-
tool_class
|
|
1030
|
-
end
|
|
1031
|
-
|
|
1032
|
-
result_filters = _tool_result_filters_for(tool_class)
|
|
1033
|
-
return resolved if result_filters.empty?
|
|
1034
|
-
|
|
1035
|
-
effective_name = resolved.new.name
|
|
1036
|
-
custom_async_call =
|
|
1037
|
-
resolved.instance_method(:call_async).owner !=
|
|
1038
|
-
Phronomy::Agent::Context::Capability::Base
|
|
1039
|
-
|
|
1040
|
-
Class.new(resolved) do
|
|
1041
|
-
tool_name effective_name
|
|
1042
|
-
define_method(:call) do |args, **kwargs|
|
|
1043
|
-
result = super(args, **kwargs)
|
|
1044
|
-
result_filters.inject(result) { |val, filter|
|
|
1045
|
-
filter.call(val, tool_name: name, args: args)
|
|
1046
|
-
}
|
|
1047
|
-
end
|
|
1048
|
-
|
|
1049
|
-
if custom_async_call
|
|
1050
|
-
define_method(:call_async) do |args, **kwargs|
|
|
1051
|
-
source = super(args, **kwargs)
|
|
1052
|
-
filtered = Phronomy::Concurrency::PhysicalCompletionTask.deferred(
|
|
1053
|
-
name: "tool-filter-#{name}"
|
|
1054
|
-
)
|
|
1055
|
-
source_has_physical_signal = source.respond_to?(:on_physical_complete)
|
|
1056
|
-
source.on_physical_complete { filtered.mark_physical_complete! } if
|
|
1057
|
-
source_has_physical_signal
|
|
1058
|
-
source.on_complete do |value, error|
|
|
1059
|
-
if error
|
|
1060
|
-
filtered.mark_physical_complete! unless source_has_physical_signal
|
|
1061
|
-
if source.respond_to?(:status) && source.status == :cancelled
|
|
1062
|
-
filtered.cancel!(error)
|
|
1063
|
-
else
|
|
1064
|
-
filtered.fail(error)
|
|
1065
|
-
end
|
|
1066
|
-
next
|
|
1067
|
-
end
|
|
1068
|
-
|
|
1069
|
-
begin
|
|
1070
|
-
result = result_filters.inject(value) { |val, filter|
|
|
1071
|
-
filter.call(val, tool_name: name, args: args)
|
|
1072
|
-
}
|
|
1073
|
-
filtered.mark_physical_complete! unless source_has_physical_signal
|
|
1074
|
-
filtered.complete(result)
|
|
1075
|
-
rescue => filter_error
|
|
1076
|
-
filtered.mark_physical_complete! unless source_has_physical_signal
|
|
1077
|
-
filtered.fail(filter_error)
|
|
1078
|
-
end
|
|
1079
|
-
end
|
|
1080
|
-
filtered
|
|
1081
|
-
end
|
|
1082
|
-
end
|
|
1083
|
-
end
|
|
890
|
+
binding = ToolBinding.new(
|
|
891
|
+
tool_class, alias_name: self.class.tool_aliases[tool_class]
|
|
892
|
+
)
|
|
893
|
+
binding.prepare(result_filters: _tool_result_filters_for(tool_class))
|
|
1084
894
|
end
|
|
1085
895
|
end
|
|
1086
896
|
end
|