phronomy 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.mutant.yml +9 -9
- data/CHANGELOG.md +52 -0
- data/CONTRIBUTING.md +8 -8
- data/README.md +28 -7
- data/benchmark/bench_agent_invoke.rb +25 -50
- data/benchmark/bench_context_assembler.rb +14 -11
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +1 -1
- data/docs/application-recipes.md +229 -0
- data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
- data/docs/architecture/agent-transition-ownership.md +13 -89
- data/docs/architecture/backend-groups.md +152 -0
- data/docs/architecture/before-llm-input.md +3 -3
- data/docs/architecture/context-management.md +1 -1
- data/docs/architecture/dx05-agent-scope.md +119 -0
- data/docs/architecture/dx05-workflow-task-state.md +134 -0
- data/docs/architecture/error-api-review.md +245 -0
- data/docs/architecture/execution-abstraction-review.md +88 -0
- data/docs/architecture/execution-boundaries.md +28 -0
- data/docs/architecture/execution-metadata-and-values.md +6 -0
- data/docs/architecture/multi-agent-handoff.md +6 -5
- data/docs/architecture/persistence.md +27 -9
- data/docs/architecture/r8-unit1.md +104 -0
- data/docs/architecture/r8-unit10.md +72 -0
- data/docs/architecture/r8-unit11.md +71 -0
- data/docs/architecture/r8-unit12.md +67 -0
- data/docs/architecture/r8-unit13.md +61 -0
- data/docs/architecture/r8-unit14.md +72 -0
- data/docs/architecture/r8-unit15.md +65 -0
- data/docs/architecture/r8-unit16.md +54 -0
- data/docs/architecture/r8-unit17.md +73 -0
- data/docs/architecture/r8-unit18.md +92 -0
- data/docs/architecture/r8-unit19.md +61 -0
- data/docs/architecture/r8-unit2.md +55 -0
- data/docs/architecture/r8-unit20.md +51 -0
- data/docs/architecture/r8-unit21.md +79 -0
- data/docs/architecture/r8-unit22.md +74 -0
- data/docs/architecture/r8-unit23.md +81 -0
- data/docs/architecture/r8-unit3.md +130 -0
- data/docs/architecture/r8-unit4.md +79 -0
- data/docs/architecture/r8-unit5.md +115 -0
- data/docs/architecture/r8-unit6.md +71 -0
- data/docs/architecture/r8-unit7.md +70 -0
- data/docs/architecture/r8-unit8.md +78 -0
- data/docs/architecture/r8-unit9.md +73 -0
- data/docs/architecture/refactoring-closure.md +10 -1
- data/docs/architecture/remaining-refactoring-plan.md +16 -0
- data/docs/architecture/storage-execution-boundaries.md +54 -0
- data/docs/architecture/tool-schema-recording-gap.md +6 -0
- data/docs/architecture/tracing.md +1 -1
- data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
- data/docs/architecture.md +24 -11
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
- data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
- data/docs/decisions/032-storage-backend-composition.md +5 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
- data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
- data/docs/decisions/040-configuration-default-composition.md +12 -1
- data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
- data/docs/decisions/058-neutral-storage-primitives.md +5 -0
- data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
- data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
- data/docs/decisions/061-persistence-failure-contracts.md +89 -0
- data/docs/decisions/062-content-store-service.md +52 -0
- data/docs/decisions/063-execution-contracts-and-services.md +88 -0
- data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
- data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
- data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
- data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
- data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
- data/docs/decisions/README.md +18 -1
- data/docs/execution-boundaries-preview.md +45 -0
- data/docs/failure-reason-codes.md +45 -0
- data/docs/features.md +12 -4
- data/docs/getting-started.md +52 -48
- data/docs/migrations/0.22.md +2 -2
- data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
- data/docs/migrations/handoff-runner-multi-agent.md +2 -0
- data/docs/migrations/persistence-failure-contracts.md +67 -0
- data/docs/migrations/r8-unit10.md +36 -0
- data/docs/migrations/r8-unit11.md +33 -0
- data/docs/migrations/r8-unit12.md +31 -0
- data/docs/migrations/r8-unit13.md +36 -0
- data/docs/migrations/r8-unit14.md +31 -0
- data/docs/migrations/r8-unit15.md +31 -0
- data/docs/migrations/r8-unit16.md +26 -0
- data/docs/migrations/r8-unit17.md +28 -0
- data/docs/migrations/r8-unit18.md +29 -0
- data/docs/migrations/r8-unit19.md +28 -0
- data/docs/migrations/r8-unit20.md +24 -0
- data/docs/migrations/r8-unit21.md +28 -0
- data/docs/migrations/r8-unit22.md +29 -0
- data/docs/migrations/r8-unit23.md +20 -0
- data/docs/migrations/r8-unit4.md +67 -0
- data/docs/migrations/r8-unit5.md +75 -0
- data/docs/migrations/r8-unit6.md +109 -0
- data/docs/migrations/r8-unit7.md +31 -0
- data/docs/migrations/r8-unit8.md +33 -0
- data/docs/migrations/r8-unit9.md +28 -0
- data/docs/migrations/storage-async-client.md +103 -0
- data/docs/migrations/vector-async-clients.md +110 -0
- data/docs/ordered-event-delivery.md +54 -0
- data/docs/persistence-backends.md +38 -21
- data/docs/runtime-and-concurrency.md +26 -1
- data/docs/workflow-completion.md +92 -0
- data/gemfiles/http_minimum.gemfile +5 -0
- data/lib/phronomy/agent/admission.rb +78 -0
- data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
- data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
- data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
- data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
- data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
- data/lib/phronomy/agent/api/agent.rb +13 -0
- data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
- data/lib/phronomy/agent/async_event_api.rb +132 -103
- data/lib/phronomy/agent/base.rb +146 -113
- data/lib/phronomy/agent/before_llm_input.rb +70 -0
- data/lib/phronomy/agent/composition/run_once.rb +1 -1
- data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
- data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
- data/lib/phronomy/agent/control_request.rb +21 -0
- data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
- data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
- data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
- data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
- data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
- data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
- data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
- data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
- data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
- data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
- data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
- data/lib/phronomy/agent/execution_change.rb +161 -0
- data/lib/phronomy/agent/execution_environment.rb +36 -0
- data/lib/phronomy/agent/execution_extension_state.rb +21 -0
- data/lib/phronomy/agent/execution_observation.rb +35 -0
- data/lib/phronomy/agent/filterable.rb +188 -0
- data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
- data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
- data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
- data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
- data/lib/phronomy/agent/knowledge_item.rb +13 -0
- data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
- data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
- data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
- data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
- data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
- data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
- data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
- data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
- data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
- data/lib/phronomy/agent/persistence/codec.rb +1 -21
- data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
- data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
- data/lib/phronomy/agent/persistence/records.rb +33 -0
- data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
- data/lib/phronomy/agent/persistence/watermark.rb +13 -11
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
- data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
- data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
- data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
- data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
- data/lib/phronomy/agent/reserved_execution.rb +16 -0
- data/lib/phronomy/agent/retention.rb +17 -0
- data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
- data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
- data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
- data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
- data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
- data/lib/phronomy/agent/settings.rb +47 -0
- data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
- data/lib/phronomy/agent/store.rb +253 -0
- data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
- data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
- data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
- data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
- data/lib/phronomy/common/error.rb +65 -1
- data/lib/phronomy/configuration/runtime_settings.rb +49 -0
- data/lib/phronomy/content_store/base.rb +7 -2
- data/lib/phronomy/content_store/storage_schema.rb +1 -0
- data/lib/phronomy/content_store/stored_contents.rb +2 -1
- data/lib/phronomy/context/assembly.rb +195 -0
- data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
- data/lib/phronomy/context/candidate.rb +51 -0
- data/lib/phronomy/context/constraint.rb +47 -0
- data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
- data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
- data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
- data/lib/phronomy/context/default_policy.rb +140 -0
- data/lib/phronomy/context/final_budget_validator.rb +33 -0
- data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
- data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
- data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
- data/lib/phronomy/context/manifest_error.rb +10 -0
- data/lib/phronomy/context/prompt_template.rb +98 -0
- data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
- data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
- data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
- data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
- data/lib/phronomy/embeddings/async/async_client.rb +44 -0
- data/lib/phronomy/embeddings/authentication_error.rb +11 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
- data/lib/phronomy/embeddings/base.rb +43 -0
- data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
- data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
- data/lib/phronomy/embeddings/transport_error.rb +11 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
- data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
- data/lib/phronomy/engine/diagnostics.rb +1 -1
- data/lib/phronomy/engine/event_loop.rb +7 -7
- data/lib/phronomy/engine/execution_binding.rb +23 -0
- data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
- data/lib/phronomy/engine/fsm_protocol.rb +1 -6
- data/lib/phronomy/engine/fsm_session.rb +3 -3
- data/lib/phronomy/engine/metrics.rb +16 -14
- data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
- data/lib/phronomy/engine/runtime.rb +25 -12
- data/lib/phronomy/engine/terminal_decision.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +41 -0
- data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
- data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
- data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
- data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
- data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
- data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
- data/lib/phronomy/{engine → execution}/execution.rb +61 -27
- data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
- data/lib/phronomy/execution/execution_receiver.rb +78 -0
- data/lib/phronomy/execution/execution_scope_state.rb +35 -0
- data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
- data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
- data/lib/phronomy/execution/invocation_controls.rb +73 -0
- data/lib/phronomy/execution/runnable.rb +24 -0
- data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
- data/lib/phronomy/execution/wait_policy.rb +24 -0
- data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
- data/lib/phronomy/generation/generator_verifier.rb +7 -7
- data/lib/phronomy/generation/low_confidence_error.rb +2 -2
- data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
- data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
- data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
- data/lib/phronomy/llm_adapter/base.rb +49 -68
- data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
- data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
- data/lib/phronomy/llm_adapter/message.rb +62 -0
- data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
- data/lib/phronomy/llm_adapter/request.rb +79 -0
- data/lib/phronomy/llm_adapter/response.rb +36 -0
- data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
- data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
- data/lib/phronomy/llm_adapter.rb +3 -16
- data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
- data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
- data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
- data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
- data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
- data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
- data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
- data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
- data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
- data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +60 -44
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
- data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
- data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
- data/lib/phronomy/multi_agent/shared_state.rb +2 -2
- data/lib/phronomy/multi_agent/store.rb +156 -0
- data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
- data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
- data/lib/phronomy/output_parser/parse_error.rb +7 -0
- data/lib/phronomy/persistence/content_repository.rb +26 -0
- data/lib/phronomy/persistence/errors.rb +16 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
- data/lib/phronomy/persistence/persistence.rb +23 -0
- data/lib/phronomy/persistence/save_outcome.rb +24 -0
- data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
- data/lib/phronomy/persistence/storage_boundary.rb +32 -0
- data/lib/phronomy/persistence/transaction.rb +86 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
- data/lib/phronomy/persistence_composition/stores.rb +34 -0
- data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
- data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
- data/lib/phronomy/runtime_composition/configuration.rb +146 -0
- data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
- data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
- data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
- data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
- data/lib/phronomy/storage/async/async_client.rb +60 -0
- data/lib/phronomy/storage/backend.rb +8 -2
- data/lib/phronomy/storage/condition_failed_error.rb +2 -2
- data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
- data/lib/phronomy/testing/eval/metrics.rb +1 -1
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
- data/lib/phronomy/tool/authorization.rb +77 -0
- data/lib/phronomy/tool/base.rb +551 -9
- data/lib/phronomy/tool/call_request.rb +40 -0
- data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
- data/lib/phronomy/tool/operation.rb +88 -0
- data/lib/phronomy/tool/schema.rb +214 -0
- data/lib/phronomy/tool/settings.rb +31 -0
- data/lib/phronomy/tool/tool_error.rb +7 -0
- data/lib/phronomy/tool/tool_executor.rb +58 -0
- data/lib/phronomy/tools/agent.rb +22 -90
- data/lib/phronomy/tools/mcp.rb +21 -14
- data/lib/phronomy/tools/vector_search.rb +4 -4
- data/lib/phronomy/tracing/automatic.rb +11 -6
- data/lib/phronomy/tracing/base.rb +36 -7
- data/lib/phronomy/tracing/observation.rb +37 -0
- data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
- data/lib/phronomy/tracing/settings.rb +36 -0
- data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
- data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
- data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
- data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
- data/lib/phronomy/vector_store/base.rb +118 -49
- data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
- data/lib/phronomy/vector_store.rb +2 -17
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/completion.rb +11 -0
- data/lib/phronomy/workflow/execution/workflow.rb +37 -9
- data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
- data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
- data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
- data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
- data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
- data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
- data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
- data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
- data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
- data/lib/phronomy.rb +63 -16
- data/scripts/api_snapshot.rb +47 -15
- data/scripts/check_readme_ruby.rb +24 -29
- data/scripts/check_readme_runnable.rb +92 -81
- data/sig/_private/phronomy/agent/settings.rbs +19 -0
- data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
- data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
- data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
- data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
- data/sig/_private/phronomy/tool/settings.rbs +13 -0
- data/sig/_private/phronomy/tracing/settings.rbs +15 -0
- data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
- data/sig/phronomy/agent/admission.rbs +19 -0
- data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
- data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
- data/sig/phronomy/agent/execution_contract.rbs +132 -0
- data/sig/phronomy/agent/execution_environment.rbs +20 -0
- data/sig/phronomy/agent/store.rbs +73 -0
- data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
- data/sig/phronomy/agent.rbs +29 -135
- data/sig/phronomy/common.rbs +7 -0
- data/sig/phronomy/content_store/contracts.rbs +13 -0
- data/sig/phronomy/context.rbs +152 -0
- data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
- data/sig/phronomy/embeddings/base.rbs +23 -0
- data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
- data/sig/phronomy/engine/control_bindings.rbs +11 -0
- data/sig/phronomy/engine/control_protocols.rbs +23 -0
- data/sig/phronomy/engine/execution_binding.rbs +6 -0
- data/sig/phronomy/engine/execution_receiver.rbs +15 -0
- data/sig/phronomy/engine/observability.rbs +13 -0
- data/sig/phronomy/execution/async_operation.rbs +7 -0
- data/sig/phronomy/execution/blocking.rbs +5 -0
- data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
- data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
- data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
- data/sig/phronomy/execution/control_protocols.rbs +18 -0
- data/sig/phronomy/execution/errors.rbs +7 -0
- data/sig/phronomy/execution/execution.rbs +41 -0
- data/sig/phronomy/execution/execution_receiver.rbs +40 -0
- data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
- data/sig/phronomy/execution/invocation_context.rbs +18 -0
- data/sig/phronomy/execution/invocation_controls.rbs +13 -0
- data/sig/phronomy/execution/task_result.rbs +29 -0
- data/sig/phronomy/extensions.rbs +1 -21
- data/sig/phronomy/filter/filter_block_error.rbs +6 -0
- data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
- data/sig/phronomy/handoff.rbs +10 -4
- data/sig/phronomy/integrations.rbs +14 -0
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
- data/sig/phronomy/llm_adapter/base.rbs +14 -0
- data/sig/phronomy/llm_adapter/usage.rbs +14 -0
- data/sig/phronomy/llm_adapter/values.rbs +43 -0
- data/sig/phronomy/llm_context_window.rbs +10 -0
- data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
- data/sig/phronomy/multi_agent/store.rbs +54 -0
- data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
- data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
- data/sig/phronomy/multi_agent.rbs +6 -3
- data/sig/phronomy/persistence/errors.rbs +18 -0
- data/sig/phronomy/persistence/migration.rbs +13 -0
- data/sig/phronomy/persistence/transaction.rbs +19 -0
- data/sig/phronomy/persistence.rbs +1 -99
- data/sig/phronomy/persistence_composition.rbs +19 -0
- data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
- data/sig/phronomy/storage/async/async_client.rbs +8 -0
- data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
- data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +4 -18
- data/sig/phronomy/tool.rbs +48 -4
- data/sig/phronomy/tracing/observation.rbs +7 -0
- data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
- data/sig/phronomy/vector_store/base.rbs +23 -0
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +15 -0
- data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
- data/sig/phronomy/workflow/store.rbs +8 -0
- data/sig/phronomy/workflow.rbs +5 -1
- data/sig/phronomy.rbs +4 -49
- metadata +365 -117
- data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
- data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
- data/lib/phronomy/agent/concerns/filterable.rb +0 -184
- data/lib/phronomy/agent/context/capability/base.rb +0 -548
- data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
- data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
- data/lib/phronomy/agent/context_policies/default.rb +0 -142
- data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
- data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
- data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
- data/lib/phronomy/agent/persistence/queries.rb +0 -61
- data/lib/phronomy/agent/selection/candidate.rb +0 -53
- data/lib/phronomy/agent/selection/constraint.rb +0 -49
- data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
- data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
- data/lib/phronomy/configuration/configuration.rb +0 -68
- data/lib/phronomy/configuration/global_configuration.rb +0 -26
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
- data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
- data/lib/phronomy/engine/execution_receiver.rb +0 -65
- data/lib/phronomy/engine/runnable.rb +0 -54
- data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
- data/lib/phronomy/llm_contract/token_usage.rb +0 -85
- data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
- data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
- data/lib/phronomy/persistence/api/persistence.rb +0 -140
- data/lib/phronomy/persistence_composition/repositories.rb +0 -77
- data/lib/phronomy/recovery/recovery.rb +0 -186
- data/lib/phronomy/tool/contract/tool_error.rb +0 -7
- data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
- data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
- data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
- data/sig/phronomy/execution_receiver.rbs +0 -34
- data/sig/phronomy/llm_adapter.rbs +0 -11
- data/sig/phronomy/runtime.rbs +0 -76
- /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
- /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
- /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
- /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ee0a6e2cc02a9014a1f6b4b0587474255697960b0e1589070a7846d038452ad
|
|
4
|
+
data.tar.gz: eb866c788b4dbaf5c560ec0a6a9fe1ef641d630932c7f4309c5ca0741dce2ad0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4970e936adca3d87ee8a8a18469630a563df8fb8a07f06796f4a4bfcae3b1e97b67a51869fdbb68760892201deb12f5f765c082cdfe8559c5f6d0c5ad10dd1a
|
|
7
|
+
data.tar.gz: 10c461ef9e86a476cc73020dc1167d6b80564f02f47c3da0161a7da8a34953be96efa236ebcfdf938c7b8a5bfeb2ea6ad3fdaca137d6f70cd5238a71bd6cb829
|
data/.mutant.yml
CHANGED
|
@@ -9,14 +9,14 @@ matcher:
|
|
|
9
9
|
subjects:
|
|
10
10
|
- Phronomy::WorkflowContext
|
|
11
11
|
- Phronomy::WorkflowRunner
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- Phronomy::Agent::Context::Capability::Base
|
|
12
|
+
- Phronomy::Tool::Base
|
|
13
|
+
- Phronomy::Tool::Schema
|
|
15
14
|
- Phronomy::LlmContextWindow::TokenBudget
|
|
16
|
-
- Phronomy::
|
|
17
|
-
- Phronomy::Agent::
|
|
18
|
-
- Phronomy::
|
|
19
|
-
- Phronomy::
|
|
20
|
-
- Phronomy::
|
|
21
|
-
- Phronomy::
|
|
15
|
+
- Phronomy::Context::Assembly
|
|
16
|
+
- Phronomy::Agent::ContextPreparation
|
|
17
|
+
- Phronomy::Context::DefaultPolicy
|
|
18
|
+
- Phronomy::Context::ContextPolicyInputBuilder
|
|
19
|
+
- Phronomy::Context::ContextPlanValidator
|
|
20
|
+
- Phronomy::MultiAgent::HandoffPolicy
|
|
21
|
+
- Phronomy::Agent::TransferProjection
|
|
22
22
|
- Phronomy::VectorStore::InMemory
|
data/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,58 @@ Release history for 0.14.0 and earlier is archived in
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
### r8 first implementation unit (breaking, unreleased)
|
|
16
|
+
|
|
17
|
+
- Move Context policy/plan/manifest/budget/prompt operations into `Phronomy::Context`.
|
|
18
|
+
Agent retains snapshot, journal and lifecycle ownership. Manifest and execution
|
|
19
|
+
references remain written within the same transaction.
|
|
20
|
+
- Make `Tool::Base` the actual implementation; move common definition and
|
|
21
|
+
authorization operations into Tool. Remove the old Agent Context namespace.
|
|
22
|
+
- Move chat construction and SDK failure translation into the LLM adapter.
|
|
23
|
+
Custom synchronous adapters must implement the expanded construction SPI.
|
|
24
|
+
- Unite execution contracts and services in `execution/`; move FSM values and
|
|
25
|
+
scheduling bindings to Engine, observation to Tracing. Keep result/control
|
|
26
|
+
rules independent of Engine startup. Update Ruby/RBS analysis and diagrams.
|
|
27
|
+
- Remove empty contract-only subdivisions and old API aliases. This unit does
|
|
28
|
+
not complete r8 Persistence, parent admission or all domain FSM separation.
|
|
29
|
+
See [the scope and migration report](docs/architecture/r8-unit1.md).
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Add public Beta `Storage::AsyncClient#transaction_async` and route 16 existing
|
|
34
|
+
durable Agent/Workflow/coordination execution units through its private submit
|
|
35
|
+
boundary. Preserve transaction/savepoint, result reconciliation and admission
|
|
36
|
+
policy; do not add cancellation or timeout to existing terminal writes.
|
|
37
|
+
- Move StoredContents into `content_store/backends/` without changing its
|
|
38
|
+
constant or behavior. Complete P4; see the
|
|
39
|
+
[Storage client guide](docs/migrations/storage-async-client.md).
|
|
40
|
+
- Preserve transparent text panels and module outlines in generated diagrams.
|
|
41
|
+
Hide arrows into M33/M41/M44 as a display choice; keep their measured source
|
|
42
|
+
dependencies, evidence and complete matrix.
|
|
43
|
+
|
|
44
|
+
- **Beta API receiver migration:** move VectorStore `add_async`, `search_async`,
|
|
45
|
+
`remove_async`, `clear_async` and Embeddings `embed_async` onto the public
|
|
46
|
+
feature-specific `AsyncClient` objects. Synchronous backend methods and
|
|
47
|
+
concrete class names remain unchanged. Remove the inherited async methods
|
|
48
|
+
and `VectorStore::AsyncBackend`; see the
|
|
49
|
+
[migration guide](docs/migrations/vector-async-clients.md).
|
|
50
|
+
- Split VectorStore/Embeddings implementations into collapsed `backends/`
|
|
51
|
+
directories. Synchronous RBS uses structural cancellation; separate client
|
|
52
|
+
signatures retain TaskResult and the full execution token. Complete P3 and
|
|
53
|
+
remove the remaining backend-to-Engine edges from the measured graph.
|
|
54
|
+
|
|
55
|
+
- Separate the synchronous LLMAdapter SPI, RubyLLM implementation and internal
|
|
56
|
+
AsyncClient. Adapter authors implement only `complete` and `stream`; default
|
|
57
|
+
configuration, provider behavior, admission/cancellation and Agent event
|
|
58
|
+
delivery remain unchanged. See
|
|
59
|
+
[ADR-059](docs/decisions/059-backend-contracts-and-async-clients.md).
|
|
60
|
+
- Separate responsibility groups from the diagram's B1-B6 display layers. Add
|
|
61
|
+
the source-derived SVG/matrix generator and Architecture CI boundary gate;
|
|
62
|
+
retain individual module boxes, stable module IDs and the earlier horizontal
|
|
63
|
+
layer layout with topical columns. Separated Contracts belong in B5 beside
|
|
64
|
+
the independent Engine group; clients, implementations and pending mixed
|
|
65
|
+
backends are displayed in B4. Storage/ContentStore migration remains pending.
|
|
66
|
+
|
|
15
67
|
---
|
|
16
68
|
|
|
17
69
|
## [0.27.0] - 2026-09-24
|
data/CONTRIBUTING.md
CHANGED
|
@@ -219,14 +219,14 @@ The authoritative subject list is `.mutant.yml`. It currently includes:
|
|
|
219
219
|
|
|
220
220
|
- `Phronomy::WorkflowContext`
|
|
221
221
|
- `Phronomy::WorkflowRunner`
|
|
222
|
-
- `Phronomy::
|
|
222
|
+
- `Phronomy::Tool::Base`
|
|
223
223
|
- `Phronomy::LlmContextWindow::TokenBudget`
|
|
224
|
-
- `Phronomy::Agent::
|
|
225
|
-
- `Phronomy::
|
|
226
|
-
- `Phronomy::
|
|
227
|
-
- `Phronomy::
|
|
228
|
-
- `Phronomy::
|
|
229
|
-
- `Phronomy::Agent::
|
|
224
|
+
- `Phronomy::Agent::ContextPreparation`
|
|
225
|
+
- `Phronomy::Context::DefaultPolicy`
|
|
226
|
+
- `Phronomy::Context::ContextPolicyInputBuilder`
|
|
227
|
+
- `Phronomy::Context::ContextPlanValidator`
|
|
228
|
+
- `Phronomy::MultiAgent::HandoffPolicy`
|
|
229
|
+
- `Phronomy::Agent::TransferProjection`
|
|
230
230
|
- `Phronomy::VectorStore::InMemory`
|
|
231
231
|
|
|
232
232
|
The nightly mutation matrix mirrors this authoritative list so each subject can
|
|
@@ -235,7 +235,7 @@ configuration-consistency guard that fails if the nightly subject set diverges
|
|
|
235
235
|
from `.mutant.yml`.
|
|
236
236
|
|
|
237
237
|
The Tool mutation subject intentionally uses
|
|
238
|
-
`Phronomy::
|
|
238
|
+
`Phronomy::Tool::Base`, which is the implementation
|
|
239
239
|
canonical name returned by the single Class object's runtime `Class#name`.
|
|
240
240
|
`Phronomy::Tool::Base` is the public facade constant alias to that same Class
|
|
241
241
|
object; it is not a second Tool base class.
|
data/README.md
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
**Phronomy** is a Ruby AI agent framework for stateful Agents, Workflows, Tools,
|
|
10
10
|
context management, filtering, tracing, and multi-agent coordination. Large Language
|
|
11
|
-
Model (LLM)
|
|
11
|
+
Model (LLM) operations use `Phronomy::LLMAdapter`; the default backend uses
|
|
12
|
+
[RubyLLM](https://github.com/crmne/ruby_llm). Custom backends implement the
|
|
13
|
+
[SDK-independent LLM contract](docs/migrations/r8-unit6.md).
|
|
14
|
+
Tool declarations and argument validation belong to `Phronomy::Tool`, independently
|
|
15
|
+
of the selected LLM backend.
|
|
12
16
|
|
|
13
17
|
Phronomy is pre-1.0. Pin to a released gem version for production use rather than
|
|
14
18
|
tracking `main` directly.
|
|
@@ -16,8 +20,8 @@ tracking `main` directly.
|
|
|
16
20
|
## Core concepts
|
|
17
21
|
|
|
18
22
|
- **Agent** — stateful, persistence-backed LLM agent with canonical execution history.
|
|
19
|
-
- **Persistence** —
|
|
20
|
-
- **Workflow** — state-machine-driven application workflow with explicit events and wait states.
|
|
23
|
+
- **Persistence** — shared atomic scopes with domain-owned Agent/Team stores and Workflow checkpoints.
|
|
24
|
+
- **Workflow** — state-machine-driven application workflow with task states, explicit events and wait states.
|
|
21
25
|
- **Tool / Capability** — callable application capability exposed to an Agent; application-defined Tools subclass `Phronomy::Tool::Base`.
|
|
22
26
|
- **Multi-Agent Handoff** — semantic Source-to-Target responsibility transfer with policy-bounded Context projection and persisted active responsibility and exact Target recovery within one Persistence domain.
|
|
23
27
|
- **EventLoop + FSMSession** — the framework control plane for logical lifecycle coordination.
|
|
@@ -31,6 +35,19 @@ tracking `main` directly.
|
|
|
31
35
|
|
|
32
36
|
See [Features and Application Programming Interface (API) stability](docs/features.md) for the full feature matrix.
|
|
33
37
|
|
|
38
|
+
The following application APIs are part of unreleased development:
|
|
39
|
+
|
|
40
|
+
| API | Stability | Contract |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `Agent.open_scope`, `Agent::Scope`, `Agent::Base#unload_async / #unload!`, `AgentUnloadedError` and `Agent::ScopeCleanupError` | Beta | Runtime-bound ownership and safe release of idle Agent incarnations without changing saved history or closing the Store; [contract](docs/architecture/dx05-agent-scope.md) |
|
|
43
|
+
| `Workflow::Builder#task_state` and `WorkflowTaskResumeError` | Beta | State-entry-scoped Task completion, live cancellation and explicit restart rejection; [contract](docs/architecture/dx05-workflow-task-state.md) |
|
|
44
|
+
| `Phronomy::Integrations::OrderedEventDelivery` | Beta | Optional ordered delivery with bounded admission and explicit drain lifetime; [usage](docs/ordered-event-delivery.md) |
|
|
45
|
+
| `Phronomy::Error#code` and error constructor `code:` | Beta | Optional reason codes and bounded causes retained in saved failure diagnostics; [meaning and compatibility](docs/architecture/error-api-review.md) |
|
|
46
|
+
| `Phronomy::AgentNotFoundError#agent_id` | Beta | Identifies absence of the requested Agent root; referenced history and Content failures retain their own errors; [migration](docs/architecture/error-api-review.md) |
|
|
47
|
+
| `Phronomy::MultiAgent::TeamNotFoundError` and `TeamBusyError`, with `team_id` | Beta | Team-owned absence and busy errors; child Agent failures retain their Agent classification; [migration](docs/architecture/error-api-review.md) |
|
|
48
|
+
| `Phronomy::Embeddings::AuthenticationError` and `RateLimitError` | Beta | Embeddings-owned transport error subclasses retaining the provider cause; [migration](docs/architecture/error-api-review.md) |
|
|
49
|
+
| `Phronomy::Tracing::Base.trace_with` | Beta | Uses the tracer start/finish SPI, attempts finish once, and preserves a primary application error; [contract](docs/architecture/error-api-review.md) |
|
|
50
|
+
|
|
34
51
|
## Installation
|
|
35
52
|
|
|
36
53
|
Add Phronomy to your Gemfile:
|
|
@@ -45,7 +62,10 @@ Then run:
|
|
|
45
62
|
bundle install
|
|
46
63
|
```
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
Phronomy 0.28 requires RubyLLM 2.0.x. Faraday 2.1.0 is the tested minimum
|
|
66
|
+
supported version; the `~> 2.1` requirement allows versions from 2.1 up to,
|
|
67
|
+
but excluding, 3.0. CI checks fresh dependency resolution and the exact minimum
|
|
68
|
+
with MCP 1.0.0. See the
|
|
49
69
|
[RubyLLM 2 and token-ownership migration](docs/architecture/rubyllm-2-token-ownership.md)
|
|
50
70
|
for removed input-budget overrides and the in-flight Tool-manifest upgrade boundary.
|
|
51
71
|
|
|
@@ -87,9 +107,7 @@ result = ResearchAgent.new.invoke("What happened in AI research this week?")
|
|
|
87
107
|
puts result[:output]
|
|
88
108
|
```
|
|
89
109
|
|
|
90
|
-
`Phronomy::Tool::Base` is the public
|
|
91
|
-
class. The legacy `Phronomy::Agent::Context::Capability::Base` constant remains
|
|
92
|
-
valid for compatibility.
|
|
110
|
+
`Phronomy::Tool::Base` is the public base class for application-defined Tools.
|
|
93
111
|
|
|
94
112
|
For non-blocking top-level use, call `invoke_async` and keep the returned
|
|
95
113
|
`Phronomy::TaskResult`. Inside Phronomy lifecycle callbacks, do not block waiting for
|
|
@@ -148,6 +166,7 @@ contracts, timeout/cancellation semantics, metrics, and callback rules.
|
|
|
148
166
|
## Documentation
|
|
149
167
|
|
|
150
168
|
- [Getting started](docs/getting-started.md) — installation, RubyLLM setup, Agent/Workflow basics, persistence, streaming.
|
|
169
|
+
- [Application recipes](docs/application-recipes.md) — complete persistence, streaming, and Workflow examples, including explicit failure handling.
|
|
151
170
|
- [Features and API stability](docs/features.md) — public feature matrix and stability labels.
|
|
152
171
|
- [Architecture](docs/architecture.md) — canonical current explanatory architecture entry and authority navigation.
|
|
153
172
|
- [Runtime and concurrency](docs/runtime-and-concurrency.md) — EventLoop, FSMSession, TaskResult, OffloadPool, cancellation, observability.
|
|
@@ -155,6 +174,8 @@ contracts, timeout/cancellation semantics, metrics, and callback rules.
|
|
|
155
174
|
- [MCP client](docs/mcp-client.md) — Model Context Protocol (MCP) integration and supported schema subset.
|
|
156
175
|
- [Migration from 0.15-era APIs](docs/migrations/0.15.md).
|
|
157
176
|
- [0.16 cleanup migration](docs/migrations/0.16.md).
|
|
177
|
+
- [r8 persistence boundary migration](docs/architecture/r8-unit3.md) — domain stores, shared scopes, and removed facade APIs.
|
|
178
|
+
- [Persistence failure contract migration](docs/migrations/persistence-failure-contracts.md) — domain rescue clauses and unchanged raw backend errors.
|
|
158
179
|
- [0.19 unified Persistence migration](docs/migrations/0.19.md).
|
|
159
180
|
- [0.22 semantic Multi-Agent Handoff migration](docs/migrations/0.22.md).
|
|
160
181
|
- [Architecture Decision Records](docs/decisions/) — design rationale and superseding decisions.
|
|
@@ -19,49 +19,21 @@ require_relative "../lib/phronomy"
|
|
|
19
19
|
# Shared stubs
|
|
20
20
|
# ---------------------------------------------------------------------------
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
RubyLLM::Message.new(
|
|
25
|
-
role: :assistant, content: content,
|
|
26
|
-
tokens: RubyLLM::Tokens.new(input: 5, output: 5, cache_read: 0, cache_write: 0)
|
|
27
|
-
)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# A minimal stub Chat that returns a pre-built response immediately.
|
|
32
|
-
class BenchStubChat
|
|
33
|
-
attr_reader :messages
|
|
34
|
-
|
|
22
|
+
# A stub backend that preserves the adapter's validation/cancellation template.
|
|
23
|
+
class BenchStubLLMAdapter < Phronomy::LLMAdapter::Base
|
|
35
24
|
def initialize(response)
|
|
36
25
|
@response = response
|
|
37
|
-
@messages = []
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def with_instructions(_, **_options) = self
|
|
41
|
-
def with_tools(*) = self
|
|
42
|
-
def with_temperature(_) = self
|
|
43
|
-
def with_cache_instructions(_) = self
|
|
44
|
-
def with_output_schema(_) = self
|
|
45
|
-
def last_message = @response
|
|
46
|
-
|
|
47
|
-
def after_message(&block)
|
|
48
|
-
@after_message = block
|
|
49
|
-
self
|
|
50
26
|
end
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
complete
|
|
54
|
-
end
|
|
28
|
+
protected
|
|
55
29
|
|
|
56
|
-
def
|
|
57
|
-
@messages << @response
|
|
58
|
-
@after_message&.call(@response)
|
|
30
|
+
def perform_complete(request, cancellation_token:)
|
|
59
31
|
@response
|
|
60
32
|
end
|
|
61
33
|
end
|
|
62
34
|
|
|
63
35
|
# A stub tool that does nothing but conforms to the Tool::Base interface.
|
|
64
|
-
class BenchNullTool < Phronomy::
|
|
36
|
+
class BenchNullTool < Phronomy::Tool::Base
|
|
65
37
|
description "No-op benchmark tool"
|
|
66
38
|
param :x, type: :string, desc: "input"
|
|
67
39
|
|
|
@@ -74,38 +46,41 @@ end
|
|
|
74
46
|
# Agent classes
|
|
75
47
|
# ---------------------------------------------------------------------------
|
|
76
48
|
|
|
77
|
-
BENCH_RESP =
|
|
49
|
+
BENCH_RESP = Phronomy::LLMAdapter::Response.new(
|
|
50
|
+
content: "benchmark complete",
|
|
51
|
+
usage: Phronomy::LLMAdapter::TokenUsage.new(input: 5, output: 5, cached: 0, cache_creation: 0)
|
|
52
|
+
)
|
|
78
53
|
|
|
79
54
|
bench_minimal_class = Class.new(Phronomy::Agent::Base) do
|
|
80
55
|
agent_definition id: "bench-minimal", version: 1
|
|
81
56
|
model "stub-model"
|
|
82
|
-
|
|
83
|
-
define_method(:build_chat) { |*| BenchStubChat.new(BENCH_RESP) }
|
|
84
57
|
end
|
|
85
58
|
|
|
86
59
|
bench_tool_class = Class.new(Phronomy::Agent::Base) do
|
|
87
60
|
agent_definition id: "bench-tool", version: 1
|
|
88
61
|
model "stub-model"
|
|
89
62
|
tools(BenchNullTool => nil)
|
|
90
|
-
|
|
91
|
-
define_method(:build_chat) { |*| BenchStubChat.new(BENCH_RESP) }
|
|
92
63
|
end
|
|
93
64
|
|
|
94
65
|
AGENT_INVOKE_ITERATIONS = 200
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
|
|
67
|
+
Phronomy.with_configuration do |config|
|
|
68
|
+
config.llm_adapter = BenchStubLLMAdapter.new(BENCH_RESP)
|
|
69
|
+
bench_agents_minimal = Array.new(AGENT_INVOKE_ITERATIONS) { bench_minimal_class.new }.freeze
|
|
70
|
+
bench_agents_tools = Array.new(AGENT_INVOKE_ITERATIONS) { bench_tool_class.new }.freeze
|
|
71
|
+
|
|
72
|
+
puts "=== bench_agent_invoke ==="
|
|
73
|
+
Benchmark.bm(50) do |x|
|
|
74
|
+
x.report("Agent#invoke — fresh, no tools, #{AGENT_INVOKE_ITERATIONS} iters") do
|
|
75
|
+
bench_agents_minimal.each do |agent|
|
|
76
|
+
agent.invoke("ping")
|
|
77
|
+
end
|
|
103
78
|
end
|
|
104
|
-
end
|
|
105
79
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
80
|
+
x.report("Agent#invoke — fresh, tool-aware, #{AGENT_INVOKE_ITERATIONS} iters") do
|
|
81
|
+
bench_agents_tools.each do |agent|
|
|
82
|
+
agent.invoke("ping")
|
|
83
|
+
end
|
|
109
84
|
end
|
|
110
85
|
end
|
|
111
86
|
end
|
|
@@ -14,7 +14,7 @@ module BenchContextAssembler
|
|
|
14
14
|
module_function
|
|
15
15
|
|
|
16
16
|
def provenance
|
|
17
|
-
Phronomy::
|
|
17
|
+
Phronomy::Context::ContextPolicyInput::Provenance.new(origin: :journal)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def policy_input(item_count)
|
|
@@ -22,13 +22,13 @@ module BenchContextAssembler
|
|
|
22
22
|
conversation = []
|
|
23
23
|
item_count.times do |index|
|
|
24
24
|
if (index % 10).zero?
|
|
25
|
-
knowledge << Phronomy::
|
|
25
|
+
knowledge << Phronomy::Context::ContextPolicyInput::KnowledgeItem.new(
|
|
26
26
|
id: "knowledge-#{index}", kind: :knowledge, role: :user,
|
|
27
27
|
content: "knowledge #{index}", content_format: :text,
|
|
28
28
|
estimated_tokens: 8, required: false, provenance: provenance, metadata: {}
|
|
29
29
|
)
|
|
30
30
|
else
|
|
31
|
-
conversation << [Phronomy::
|
|
31
|
+
conversation << [Phronomy::Context::ContextPolicyInput::ConversationItem.new(
|
|
32
32
|
id: "message-#{index}", kind: :external_message, role: :user,
|
|
33
33
|
content: "message #{index}", content_format: :text,
|
|
34
34
|
sequence: index, estimated_tokens: 8, required: false,
|
|
@@ -38,7 +38,7 @@ module BenchContextAssembler
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
Phronomy::
|
|
41
|
+
Phronomy::Context::ContextPolicyInput.new(
|
|
42
42
|
agent_id: "bench-agent", execution_id: "bench-execution",
|
|
43
43
|
call_sequence: 2, call_mode: :complete,
|
|
44
44
|
instruction: [], knowledge: knowledge, tools: [], conversation: conversation,
|
|
@@ -50,7 +50,7 @@ module BenchContextAssembler
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def assembler_fixture
|
|
53
|
-
persistence = Phronomy::
|
|
53
|
+
persistence = Phronomy::PersistenceComposition.in_memory.agent
|
|
54
54
|
agent_class = Class.new(Phronomy::Agent::Base) do
|
|
55
55
|
agent_definition id: "bench-manifest-context-assembler", version: 1
|
|
56
56
|
# Resolve capabilities from RubyLLM's bundled registry; no provider call.
|
|
@@ -86,9 +86,10 @@ module BenchContextAssembler
|
|
|
86
86
|
)
|
|
87
87
|
|
|
88
88
|
[
|
|
89
|
-
Phronomy::Agent::
|
|
89
|
+
Phronomy::Agent::ContextPreparation.new(agent: agent, persistence: persistence),
|
|
90
90
|
root,
|
|
91
|
-
execution
|
|
91
|
+
execution,
|
|
92
|
+
persistence
|
|
92
93
|
]
|
|
93
94
|
end
|
|
94
95
|
end
|
|
@@ -97,7 +98,7 @@ puts "Typed Context Policy benchmark"
|
|
|
97
98
|
puts "Ruby #{RUBY_VERSION} on #{RUBY_PLATFORM}"
|
|
98
99
|
puts "=" * 72
|
|
99
100
|
|
|
100
|
-
policy = Phronomy::
|
|
101
|
+
policy = Phronomy::Context::DefaultPolicy.instance
|
|
101
102
|
policy_inputs = [10, 100, 1_000].to_h do |count|
|
|
102
103
|
[count, BenchContextAssembler.policy_input(count)]
|
|
103
104
|
end
|
|
@@ -110,15 +111,17 @@ Benchmark.bm(46) do |x|
|
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
113
|
|
|
113
|
-
assembler, root, execution = BenchContextAssembler.assembler_fixture
|
|
114
|
-
x.report("
|
|
114
|
+
assembler, root, execution, persistence = BenchContextAssembler.assembler_fixture
|
|
115
|
+
x.report("Context prepare/store x500") do
|
|
115
116
|
500.times do
|
|
116
117
|
prepared = assembler.prepare_initial(
|
|
117
118
|
input: "benchmark input",
|
|
118
119
|
agent_root: root,
|
|
119
120
|
execution: execution
|
|
120
121
|
)
|
|
121
|
-
|
|
122
|
+
persistence.transaction do |tx|
|
|
123
|
+
Phronomy::Context::Assembly.new.store(prepared, contents: tx.contents)
|
|
124
|
+
end
|
|
122
125
|
end
|
|
123
126
|
end
|
|
124
127
|
end
|
|
@@ -69,7 +69,7 @@ end
|
|
|
69
69
|
# ---------------------------------------------------------------------------
|
|
70
70
|
# Target 3: Tool::Base#params_schema generation (10 params)
|
|
71
71
|
# ---------------------------------------------------------------------------
|
|
72
|
-
tool_class = Class.new(Phronomy::
|
|
72
|
+
tool_class = Class.new(Phronomy::Tool::Base) do
|
|
73
73
|
description "Test tool with 10 params"
|
|
74
74
|
param :p1, type: :string, desc: "param 1"
|
|
75
75
|
param :p2, type: :string, desc: "param 2"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
require "benchmark"
|
|
8
8
|
require_relative "../lib/phronomy"
|
|
9
9
|
|
|
10
|
-
class BenchTool10Params < Phronomy::
|
|
10
|
+
class BenchTool10Params < Phronomy::Tool::Base
|
|
11
11
|
description "A tool with 10 parameters for benchmarking purposes"
|
|
12
12
|
param :param1, type: :string, desc: "First parameter"
|
|
13
13
|
param :param2, type: :integer, desc: "Second parameter"
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Application recipes
|
|
2
|
+
|
|
3
|
+
Start with [Getting started](getting-started.md). Every `ruby runnable` block
|
|
4
|
+
below is a complete example after `require "phronomy"` and provider configuration.
|
|
5
|
+
CI substitutes only the LLM adapter; Agent, Tool and Workflow operations are real.
|
|
6
|
+
|
|
7
|
+
## Choose the completion channel
|
|
8
|
+
|
|
9
|
+
| Need | Public API | Caller responsibility |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| One final answer | `agent.invoke(input)` | Call from an external thread |
|
|
12
|
+
| Nonblocking final answer | `agent.invoke_async(input)` | Observe the returned TaskResult |
|
|
13
|
+
| Intermediate events | Construction-time `on_event:` | Keep the callback short |
|
|
14
|
+
| Streamed tokens | `stream` / `stream_async` with a listener | Move network delivery out of the callback |
|
|
15
|
+
| Durable approval | `approve_async` with the request IDs | Apply the application's authorization decision |
|
|
16
|
+
| State-owned asynchronous result | `task_state(name, start:, apply:)` | Framework correlates completion to state entry |
|
|
17
|
+
| Live Workflow notification | `workflow.signal(...)` | Check admission; correlate the waiting operation |
|
|
18
|
+
|
|
19
|
+
`TaskResult#wait_result` waits at an external application boundary. Completion
|
|
20
|
+
callbacks and Agent listeners must not block EventLoop. A wait timeout does not
|
|
21
|
+
cancel the underlying operation. The caller owns cancellation and cleanup.
|
|
22
|
+
|
|
23
|
+
## Create, load and get
|
|
24
|
+
|
|
25
|
+
```ruby runnable
|
|
26
|
+
class ConversationAgent < Phronomy::Agent::Base
|
|
27
|
+
agent_definition id: "recipe-conversation", version: 1
|
|
28
|
+
model "gpt-4o-mini"
|
|
29
|
+
instructions "Answer briefly."
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
store = Phronomy::PersistenceComposition.in_memory.agent
|
|
33
|
+
agent = ConversationAgent.create(agent_id: "recipe-session", persistence: store)
|
|
34
|
+
puts agent.invoke("Hello").fetch(:output)
|
|
35
|
+
|
|
36
|
+
# A live lookup is not a database read. Loading a live identity returns its owner.
|
|
37
|
+
raise "Unexpected owner" unless ConversationAgent.get("recipe-session").equal?(agent)
|
|
38
|
+
raise "Unexpected load" unless ConversationAgent.load("recipe-session", persistence: store).equal?(agent)
|
|
39
|
+
|
|
40
|
+
def load_existing_agent(agent_class:, agent_id:, persistence:)
|
|
41
|
+
agent_class.load(agent_id, persistence: persistence)
|
|
42
|
+
rescue Phronomy::AgentNotFoundError => error
|
|
43
|
+
raise unless error.agent_id == agent_id.to_s
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
missing = load_existing_agent(
|
|
48
|
+
agent_class: ConversationAgent, agent_id: "missing-session", persistence: store
|
|
49
|
+
)
|
|
50
|
+
raise "Expected a missing Agent" unless missing.nil?
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This application treats only absence as `nil`. Storage unavailability, state
|
|
54
|
+
conflicts and recovery requirements remain errors. `create` is not upsert; do
|
|
55
|
+
not catch every persistence failure and create a replacement conversation.
|
|
56
|
+
The in-memory store is for this example; a durable backend must outlive Runtime
|
|
57
|
+
to support process restart. See [persistence failures](migrations/persistence-failure-contracts.md).
|
|
58
|
+
|
|
59
|
+
## Streaming and final results
|
|
60
|
+
|
|
61
|
+
```ruby runnable
|
|
62
|
+
class StreamingAgent < Phronomy::Agent::Base
|
|
63
|
+
agent_definition id: "recipe-streaming", version: 1
|
|
64
|
+
model "gpt-4o-mini"
|
|
65
|
+
instructions "Explain briefly."
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
events = Queue.new
|
|
69
|
+
agent = StreamingAgent.new(on_event: ->(event) {
|
|
70
|
+
events << event if event.type == :token
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
task = agent.stream_async("Explain asynchronous results")
|
|
74
|
+
result = task.wait_result
|
|
75
|
+
puts result.fetch(:output)
|
|
76
|
+
puts "Received #{events.size} token events"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The unbounded Queue is an in-process teaching example. A Web application needs
|
|
80
|
+
bounded admission and ordered delivery outside the listener, such as Example 18.
|
|
81
|
+
Register the listener at `new`/`create`/first `load`; per-invocation listener
|
|
82
|
+
arguments or blocks are rejected. Do not replace a listener by loading an
|
|
83
|
+
already-live Agent. See [approval](getting-started.md#human-in-the-loop-approval)
|
|
84
|
+
for the separate durable approval identifiers and application decision.
|
|
85
|
+
|
|
86
|
+
## Workflow task states
|
|
87
|
+
|
|
88
|
+
When a Workflow owns the start of an operation, use `task_state` to keep the
|
|
89
|
+
application focused on its operation, result fields and routing decisions.
|
|
90
|
+
The example below returns the original error to its caller if either invocation
|
|
91
|
+
fails; it does not silently retry or convert a failed invocation into an answer.
|
|
92
|
+
|
|
93
|
+
```ruby runnable
|
|
94
|
+
class TwoAnswersState
|
|
95
|
+
include Phronomy::WorkflowContext
|
|
96
|
+
field :question, type: :replace
|
|
97
|
+
field :answers, type: :replace, default: -> { [] }
|
|
98
|
+
end
|
|
99
|
+
class TwoAnswersAgent < Phronomy::Agent::Base
|
|
100
|
+
agent_definition id: "recipe-two-answers", version: 1
|
|
101
|
+
model "gpt-4o-mini"
|
|
102
|
+
instructions "Answer briefly."
|
|
103
|
+
end
|
|
104
|
+
agent = TwoAnswersAgent.new
|
|
105
|
+
workflow = Phronomy::Workflow.define(TwoAnswersState) do
|
|
106
|
+
initial :asking
|
|
107
|
+
task_state :asking,
|
|
108
|
+
start: ->(state) { agent.invoke_async(state.question) },
|
|
109
|
+
apply: ->(state, result) { state.merge(answers: state.answers + [result.fetch(:output)]) }
|
|
110
|
+
state :decide
|
|
111
|
+
transition from: :asking, to: :decide
|
|
112
|
+
transition from: :decide, to: :__finish__, guard: ->(state) { state.answers.length >= 2 }
|
|
113
|
+
transition from: :decide, to: :asking
|
|
114
|
+
end
|
|
115
|
+
puts workflow.invoke({question: "Explain completion handles"}).answers
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Expected business failures can be converted into application values before
|
|
119
|
+
returning the source Task, for example using `TaskResult.all_settled` and `map`.
|
|
120
|
+
Phronomy does not select a fallback or retry policy. See the
|
|
121
|
+
[full contract](architecture/dx05-workflow-task-state.md).
|
|
122
|
+
|
|
123
|
+
## Workflow completion and failures
|
|
124
|
+
|
|
125
|
+
The following **legacy low-level signal recipe** is retained for existing users
|
|
126
|
+
and its compatibility tests. New state-owned asynchronous operations should use
|
|
127
|
+
`task_state` above. The restrictions below apply to this manual recipe, not to
|
|
128
|
+
the new task-state API.
|
|
129
|
+
|
|
130
|
+
This recipe targets **one live waiting stage**. Do not restart, reenter or replace
|
|
131
|
+
that stage while a result is pending. A logical `workflow_instance_id` alone does
|
|
132
|
+
not identify a particular execution or waiting operation. For those wider cases,
|
|
133
|
+
application operation IDs and receiver-side guards are necessary.
|
|
134
|
+
|
|
135
|
+
```ruby runnable
|
|
136
|
+
def request_answer(agent:, workflow:, workflow_instance_id:, question:,
|
|
137
|
+
on_delivery_error:)
|
|
138
|
+
task = agent.invoke_async(question).map do |result|
|
|
139
|
+
{answer: result.fetch(:output)}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
task.on_complete do |payload, operation_error|
|
|
143
|
+
delivery_error = nil
|
|
144
|
+
begin
|
|
145
|
+
accepted = workflow.signal(
|
|
146
|
+
workflow_instance_id: workflow_instance_id,
|
|
147
|
+
event: :answer_ready,
|
|
148
|
+
payload: operation_error ? {error: operation_error} : payload
|
|
149
|
+
)
|
|
150
|
+
unless accepted
|
|
151
|
+
delivery_error = Phronomy::Error.new("Workflow did not accept answer_ready")
|
|
152
|
+
end
|
|
153
|
+
rescue => error
|
|
154
|
+
delivery_error = error
|
|
155
|
+
end
|
|
156
|
+
on_delivery_error.call(delivery_error) if delivery_error
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class AnswerAgent < Phronomy::Agent::Base
|
|
163
|
+
agent_definition id: "recipe-answer", version: 1
|
|
164
|
+
model "gpt-4o-mini"
|
|
165
|
+
instructions "Answer briefly."
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
class AnswerState
|
|
169
|
+
include Phronomy::WorkflowContext
|
|
170
|
+
field :question, type: :replace
|
|
171
|
+
field :answer, type: :replace
|
|
172
|
+
field :error_message, type: :replace
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
agent = AnswerAgent.new
|
|
176
|
+
delivery_errors = Queue.new
|
|
177
|
+
workflow = nil
|
|
178
|
+
workflow = Phronomy::Workflow.define(AnswerState) do
|
|
179
|
+
initial :asking
|
|
180
|
+
state :asking
|
|
181
|
+
state :done
|
|
182
|
+
entry :asking, ->(state) {
|
|
183
|
+
request_answer(
|
|
184
|
+
agent: agent, workflow: workflow,
|
|
185
|
+
workflow_instance_id: state.workflow_instance_id, question: state.question,
|
|
186
|
+
on_delivery_error: ->(error) { delivery_errors << error }
|
|
187
|
+
)
|
|
188
|
+
state
|
|
189
|
+
}
|
|
190
|
+
transition from: :asking, on: :answer_ready, to: :done,
|
|
191
|
+
action: ->(state, event) {
|
|
192
|
+
error = event.payload[:error]
|
|
193
|
+
error ? state.merge(error_message: error.message) : state.merge(answer: event.payload.fetch(:answer))
|
|
194
|
+
}
|
|
195
|
+
transition from: :done, to: :__finish__
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
final = workflow.invoke(
|
|
199
|
+
{question: "What is a completion handle?"},
|
|
200
|
+
config: {workflow_instance_id: "recipe-answer"}
|
|
201
|
+
)
|
|
202
|
+
puts final.error_message || final.answer
|
|
203
|
+
raise delivery_errors.pop unless delivery_errors.empty?
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The receiver handles both answer and error; changing only the sender would be
|
|
207
|
+
incomplete. Store a serializable diagnostic, not the exception object, in the
|
|
208
|
+
Workflow context. Result mapping failures (such as a missing `:output`) also
|
|
209
|
+
become operation failures. Synchronous submission errors still propagate to
|
|
210
|
+
the caller of `request_answer`.
|
|
211
|
+
|
|
212
|
+
`signal == true` means admission, not transition completion or durable success.
|
|
213
|
+
`false` and signal exceptions reach `on_delivery_error` once without automatic
|
|
214
|
+
retry. This callback must be short and nonblocking. Its own exceptions follow
|
|
215
|
+
TaskResult's existing callback logging policy; they do not change the settled
|
|
216
|
+
operation result. Logging depends on the configured logger.
|
|
217
|
+
|
|
218
|
+
Delivery rejection does not itself settle a waiting Workflow. The application
|
|
219
|
+
must observe its diagnostic channel and decide whether to cancel or recover.
|
|
220
|
+
The synchronous invocation above illustrates successful delivery. Production
|
|
221
|
+
callers needing a wait limit should use `invoke_async` and manage both the
|
|
222
|
+
Workflow TaskResult and the diagnostic channel at their external boundary.
|
|
223
|
+
|
|
224
|
+
No generic Workflow forwarding helper is required by these recipes. A helper
|
|
225
|
+
must first establish how an old result is prevented from reaching a new waiting
|
|
226
|
+
stage; internal execution generations or automatic recovery are not implied.
|
|
227
|
+
|
|
228
|
+
For an application-local extraction with the same limited lifetime, see the
|
|
229
|
+
[bounded completion recipe](workflow-completion.md). It is not a new library API.
|