phronomy 0.28.0 → 0.30.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 +96 -1
- data/README.md +32 -6
- data/docs/application-recipes.md +229 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +8 -0
- data/docs/architecture/agent-iteration-budget.md +81 -0
- data/docs/architecture/dx05-agent-scope.md +126 -0
- data/docs/architecture/dx05-workflow-task-state.md +134 -0
- data/docs/architecture/error-api-review.md +245 -0
- data/docs/architecture/generator-verifier-lifecycle-result.md +126 -0
- data/docs/architecture/generator-verifier-ownership.md +18 -109
- data/docs/architecture/llm-continuation-metadata.md +88 -0
- data/docs/architecture/lm-studio-output-failures.md +111 -0
- data/docs/architecture/r8-unit15.md +3 -0
- data/docs/architecture/r8-unit7.md +2 -1
- data/docs/architecture/structured-output-and-scope.md +183 -0
- data/docs/architecture/tracing.md +22 -4
- data/docs/architecture/transport-error-boundaries.md +103 -0
- data/docs/architecture/trust-acceptance-followup.md +87 -0
- data/docs/failure-reason-codes.md +45 -0
- data/docs/features.md +14 -2
- data/docs/getting-started.md +44 -43
- data/docs/migrations/r8-unit15.md +3 -0
- data/docs/ordered-event-delivery.md +54 -0
- data/docs/workflow-completion.md +92 -0
- data/gemfiles/http_minimum.gemfile +10 -0
- data/lib/phronomy/agent/admission.rb +1 -1
- data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
- data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
- data/lib/phronomy/agent/api/agent.rb +13 -0
- data/lib/phronomy/agent/async_event_api.rb +121 -88
- data/lib/phronomy/agent/base.rb +82 -44
- data/lib/phronomy/agent/before_llm_input.rb +7 -1
- data/lib/phronomy/agent/context_importer.rb +4 -0
- data/lib/phronomy/agent/execution/agent_invocation.rb +6 -2
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +6 -1
- data/lib/phronomy/agent/execution/exact_execution.rb +20 -9
- data/lib/phronomy/agent/execution/execution_cancellation.rb +1 -1
- data/lib/phronomy/agent/execution/execution_coordinator.rb +79 -18
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +24 -4
- data/lib/phronomy/agent/execution/execution_registry.rb +5 -1
- data/lib/phronomy/agent/execution/initial_preparation.rb +11 -5
- data/lib/phronomy/agent/execution/invocation_actions.rb +1 -1
- data/lib/phronomy/agent/execution/invocation_transitions.rb +4 -4
- data/lib/phronomy/agent/execution/iteration_budget.rb +35 -0
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +4 -4
- data/lib/phronomy/agent/execution_observation.rb +5 -5
- data/lib/phronomy/agent/filterable.rb +21 -15
- 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 +99 -8
- data/lib/phronomy/agent/lifecycle/scope.rb +208 -0
- data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +21 -0
- data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
- data/lib/phronomy/agent/output_contract.rb +64 -0
- data/lib/phronomy/agent/output_error.rb +53 -0
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +6 -6
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +8 -7
- data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +14 -18
- data/lib/phronomy/agent/recovery/recovery_coordinator.rb +6 -10
- data/lib/phronomy/agent/recovery/recovery_support.rb +6 -8
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +2 -1
- data/lib/phronomy/agent/runtime_input.rb +15 -4
- data/lib/phronomy/agent/saved_context_reader.rb +2 -1
- data/lib/phronomy/agent/store.rb +10 -2
- data/lib/phronomy/agent/stream_callback_error.rb +2 -2
- data/lib/phronomy/common/error.rb +65 -1
- data/lib/phronomy/common/values/immutable.rb +33 -0
- data/lib/phronomy/content_store/base.rb +7 -2
- data/lib/phronomy/context/assembly.rb +18 -22
- data/lib/phronomy/embeddings/authentication_error.rb +11 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +8 -2
- data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
- data/lib/phronomy/engine/fsm_session.rb +16 -1
- data/lib/phronomy/execution/execution_cancellation_error.rb +2 -2
- data/lib/phronomy/execution/execution_timeout_error.rb +2 -2
- data/lib/phronomy/execution/task_result.rb +7 -2
- data/lib/phronomy/filter/filter_block_error.rb +2 -2
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +28 -5
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +14 -27
- data/lib/phronomy/generation/generator_verifier/approval.rb +30 -0
- data/lib/phronomy/generation/generator_verifier/cleanup_error.rb +15 -0
- data/lib/phronomy/generation/generator_verifier/invocation.rb +147 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +16 -0
- data/lib/phronomy/generation/generator_verifier/protocol.rb +55 -0
- data/lib/phronomy/generation/generator_verifier/result.rb +121 -0
- data/lib/phronomy/generation/generator_verifier/unaccepted_result_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +13 -11
- data/lib/phronomy/generation/generator_verifier.rb +170 -85
- data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
- data/lib/phronomy/llm_adapter/backends/lm_studio.rb +139 -0
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +134 -12
- data/lib/phronomy/llm_adapter/backends/structured_response_capture.rb +41 -0
- data/lib/phronomy/llm_adapter/message.rb +4 -0
- data/lib/phronomy/llm_adapter/request.rb +17 -2
- data/lib/phronomy/llm_adapter/response.rb +11 -4
- data/lib/phronomy/llm_adapter/structured_output_unsupported_error.rb +14 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +4 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +5 -5
- data/lib/phronomy/multi_agent/shared_state.rb +1 -0
- data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +20 -7
- data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
- data/lib/phronomy/output_parser/json_schema_parser.rb +113 -0
- data/lib/phronomy/output_parser/parse_error.rb +10 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +4 -0
- data/lib/phronomy/storage/condition_failed_error.rb +2 -2
- data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +1 -1
- data/lib/phronomy/tool/base.rb +25 -23
- data/lib/phronomy/tracing/automatic.rb +14 -8
- data/lib/phronomy/tracing/base.rb +37 -7
- data/lib/phronomy/tracing/langfuse_tracer.rb +3 -3
- data/lib/phronomy/tracing/observation.rb +2 -1
- data/lib/phronomy/tracing/open_telemetry_tracer.rb +7 -32
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/execution/workflow.rb +30 -2
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +1 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +74 -3
- data/lib/phronomy/workflow/execution/workflow_runner.rb +48 -14
- 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/runtime_binding/workflow_engine_environment.rb +26 -3
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +3 -1
- data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
- data/lib/phronomy.rb +7 -2
- data/scripts/api_snapshot.rb +15 -0
- data/scripts/check_readme_ruby.rb +24 -29
- data/scripts/check_readme_runnable.rb +92 -81
- data/scripts/migrate_generator_verifier_result.rb +18 -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 +4 -3
- data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
- data/sig/phronomy/agent.rbs +36 -5
- data/sig/phronomy/embeddings/base.rbs +6 -0
- data/sig/phronomy/execution/execution.rbs +2 -2
- data/sig/phronomy/extensions.rbs +8 -2
- data/sig/phronomy/filter/filter_block_error.rbs +6 -0
- data/sig/phronomy/generation/generator_verifier.rbs +70 -0
- data/sig/phronomy/integrations.rbs +14 -0
- data/sig/phronomy/llm_adapter/backends/lm_studio.rbs +8 -0
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +1 -0
- data/sig/phronomy/llm_adapter/values.rbs +8 -2
- 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/storage/contracts.rbs +2 -2
- data/sig/phronomy/tracing/observation.rbs +1 -1
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +1 -0
- data/sig/phronomy/workflow.rbs +4 -0
- data/sig/phronomy.rbs +6 -0
- metadata +103 -5
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +0 -27
- data/lib/phronomy/generation/low_confidence_error.rb +0 -14
- data/lib/phronomy/runtime_composition/generation_defaults.rb +0 -6
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# DX05-D: Workflow-owned asynchronous task states
|
|
2
|
+
|
|
3
|
+
Status: unreleased Beta implementation. Based on DX05-A core `c673c958c`.
|
|
4
|
+
|
|
5
|
+
## Problem and intended application boundary
|
|
6
|
+
|
|
7
|
+
A Workflow application describes which operation a stage starts, how its result
|
|
8
|
+
changes business state, and where that state goes next. Repeating callbacks,
|
|
9
|
+
completion events, error envelopes and state-entry identifiers obscures those
|
|
10
|
+
choices. Workflow already owns the live execution and state-entry lifetime, so it
|
|
11
|
+
can own this correlation without knowing how an Agent, Tool or LLM runs.
|
|
12
|
+
|
|
13
|
+
The business flow is preserved. Failure, cancellation, stale-result and delivery
|
|
14
|
+
semantics become explicit and stronger; this is not a claim that every failure
|
|
15
|
+
path of the previous manual recipe was equivalent.
|
|
16
|
+
|
|
17
|
+
## Before and after
|
|
18
|
+
|
|
19
|
+
Previously, each state manually arranged delivery (simplified successful path):
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
workflow = nil
|
|
23
|
+
workflow = Phronomy::Workflow.define(AnswerState) do
|
|
24
|
+
initial :asking
|
|
25
|
+
state :asking
|
|
26
|
+
entry :asking, ->(state) {
|
|
27
|
+
id = state.workflow_instance_id
|
|
28
|
+
agent.invoke_async(state.question).on_complete do |value, error|
|
|
29
|
+
workflow.signal(workflow_instance_id: id, event: :answer_ready,
|
|
30
|
+
payload: {value: value, error: error})
|
|
31
|
+
end
|
|
32
|
+
state
|
|
33
|
+
}
|
|
34
|
+
transition from: :asking, on: :answer_ready, to: :__finish__,
|
|
35
|
+
action: ->(state, event) {
|
|
36
|
+
raise event.payload[:error] if event.payload[:error]
|
|
37
|
+
state.merge(answer: event.payload.fetch(:value).fetch(:output))
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That snippet also needs rejection handling and entry-specific guards for safe
|
|
43
|
+
reentry. The application now writes only the operation and successful update:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
workflow = Phronomy::Workflow.define(AnswerState) do
|
|
47
|
+
initial :asking
|
|
48
|
+
task_state :asking,
|
|
49
|
+
start: ->(state) { agent.invoke_async(state.question) },
|
|
50
|
+
apply: ->(state, result) { state.merge(answer: result.fetch(:output)) }
|
|
51
|
+
transition from: :asking, to: :__finish__
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`start` receives an isolated context snapshot, including current phase and
|
|
56
|
+
Workflow identity. It must return a `Phronomy::TaskResult` promptly. It must not
|
|
57
|
+
perform blocking I/O or wait for another Task. Use `Blocking.call_async` for real
|
|
58
|
+
blocking work. `apply` receives the current context and successful value, runs
|
|
59
|
+
briefly on EventLoop, and returns the same context type or nil. Normal transition
|
|
60
|
+
guards run after `apply`. Nil means no context replacement, not failure.
|
|
61
|
+
|
|
62
|
+
A task state may have ordinary entry/exit actions and external transitions.
|
|
63
|
+
Leaving it abandons observation of its source. Declaring the same state as both
|
|
64
|
+
`state`/`wait_state` and `task_state` is rejected. Ordinary entry/transition actions
|
|
65
|
+
still cannot return a TaskResult. Business signals keep their existing API.
|
|
66
|
+
|
|
67
|
+
## Contract ownership
|
|
68
|
+
|
|
69
|
+
| Owner | Responsibility |
|
|
70
|
+
|---|---|
|
|
71
|
+
| C02 Workflow | Task-state definition; live execution and state-entry reception; once-only consumption; success application, failure/cancellation classification; stale-result and queued-progression rejection; restart policy |
|
|
72
|
+
| C13 Execution | Thread-safe TaskResult notification and cancellation subscription mechanisms |
|
|
73
|
+
| I07 Workflow runtime binding | Connect C02 decisions to the existing FSM, owner EventLoop and owner Runtime timer/shutdown services |
|
|
74
|
+
| Engine | Generic event/session delivery and lifecycle; no task-state or Agent policy is added |
|
|
75
|
+
| C04 Agent and application | Agent execution, business transformation/fallback, child cancellation policy and producer lifetime |
|
|
76
|
+
|
|
77
|
+
C02 uses its execution-environment port and receives an injected session
|
|
78
|
+
connection. It does not select Runtime or depend on I07. `WorkflowTaskSession`
|
|
79
|
+
in I07 adapts the generic FSMSession. Engine source is unchanged.
|
|
80
|
+
|
|
81
|
+
A revocable C02 reception is created before `start`. Source callbacks retain only
|
|
82
|
+
that reception. Closing it drops source and registry references. Completion is
|
|
83
|
+
sent to the originating registry under an opaque execution owner token, never
|
|
84
|
+
rerouted by a durable Workflow ID. Receiver-side validation checks the current
|
|
85
|
+
entry and consumes once. Exits, reentry, cancellation and terminal decisions
|
|
86
|
+
revoke it. Queued automatic progression also carries an entry-specific key:
|
|
87
|
+
an external transition between result application and automatic dispatch cannot
|
|
88
|
+
advance a later entry. These private messages are not public business signals.
|
|
89
|
+
|
|
90
|
+
## Outcomes and lifetime
|
|
91
|
+
|
|
92
|
+
| Condition | Caller-visible result |
|
|
93
|
+
|---|---|
|
|
94
|
+
| Start throws, source fails, or apply throws | Workflow fails with the same error object |
|
|
95
|
+
| Source status is `cancelled` | Workflow TaskResult is cancelled with the source error |
|
|
96
|
+
| Source status is `failed` with a CancellationError | Remains failed; class alone does not imply cancellation |
|
|
97
|
+
| Explicit parent token cancellation/deadline | Cancel Workflow observation; do not implicitly cancel the source |
|
|
98
|
+
| Old or duplicated source notification | Ignore; no application or retry |
|
|
99
|
+
| Current completion delivery rejects/throws | Fail on the owner; if owner delivery is wholly unavailable, settle observation only and retain admission until shutdown cleanup |
|
|
100
|
+
| Runtime shutdown while waiting | Settle observation and revoke subscriptions; no claim that child work physically stopped |
|
|
101
|
+
| Known durable terminal result | Use existing save/readback classification and release rules |
|
|
102
|
+
| Unknown durable terminal result | Fail the observation while retaining recovery-required ownership |
|
|
103
|
+
|
|
104
|
+
Cancellation is serialized on the owning EventLoop. Once terminal persistence
|
|
105
|
+
has begun, its existing durable barrier remains authoritative; later cancellation
|
|
106
|
+
does not reverse a committed or uncertain terminal decision. A source passed to
|
|
107
|
+
the state has an independent lifetime. Applications explicitly pass cancellation
|
|
108
|
+
controls to children when desired; they own Agent release and side effects.
|
|
109
|
+
|
|
110
|
+
This API creates no thread/worker for logical waits. Actual I/O and existing
|
|
111
|
+
tracing or checkpoint work may still use the bounded pool. Expected business
|
|
112
|
+
failures may be transformed into ordinary values through TaskResult composition.
|
|
113
|
+
The API does not infer retry, fallback, durable child execution or delivery ACKs.
|
|
114
|
+
|
|
115
|
+
## Persistence and restart
|
|
116
|
+
|
|
117
|
+
Normal completion and ordinary `wait_state` halts use existing Workflow
|
|
118
|
+
checkpoints. Resuming an ordinary wait into a new task state is supported. Pending
|
|
119
|
+
TaskResults, callbacks and entry keys are process-local and are never serialized.
|
|
120
|
+
Restoring a snapshot whose phase is a task state raises
|
|
121
|
+
`Phronomy::WorkflowTaskResumeError` (C02, subclass of `Phronomy::Error`, code
|
|
122
|
+
`workflow.task_resume_unsupported`) before running its start callback. The
|
|
123
|
+
application must reconcile that operation explicitly instead of automatically
|
|
124
|
+
repeating external side effects. F01 durable child execution remains separate.
|
|
125
|
+
|
|
126
|
+
## Verification
|
|
127
|
+
|
|
128
|
+
The new task-state regression suite covers immediate and delayed results,
|
|
129
|
+
repeated entry, old success/failure/cancellation, duplicate callbacks, external
|
|
130
|
+
transition races, rejected guards, delivery failure, shutdown, source/parent
|
|
131
|
+
cancellation, owner Runtime selection, retained-reference revocation, strict
|
|
132
|
+
callback result types, normal durable halt/resume, restart rejection and unknown
|
|
133
|
+
durable outcomes. Existing Workflow, TaskResult, API and executable-documentation
|
|
134
|
+
tests remain active. Examples 01/03/04/07/14/20/25/26 use the new API.
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Error API review (DX05-A / EA01–EA06)
|
|
2
|
+
|
|
3
|
+
Applications should branch on the meaning of a failed public operation without
|
|
4
|
+
knowing its backend or reconstructing it from an error message. The review uses
|
|
5
|
+
core `5b9d1047cc1351a935a7871ae7705c65166ea971` and examples
|
|
6
|
+
`5f4d58dc9432a84cb4421d3cc7dc2da92aee3c72` as its baseline.
|
|
7
|
+
|
|
8
|
+
The original DX05-A change implements the error API part of the follow-up design. DX05-B
|
|
9
|
+
(notification wiring), C (review result collection), D (`task_state`) and E
|
|
10
|
+
(Agent scope/unload) remain separate work. Their proposed APIs are not provided
|
|
11
|
+
by this patch. EA06's integration with D/E cannot be accepted before those APIs
|
|
12
|
+
exist. Live PostgreSQL, Redis, provider services and complete Rails execution
|
|
13
|
+
are separate acceptance gates; see the delivery's verification record.
|
|
14
|
+
|
|
15
|
+
The subsequent, unreleased DX05-D change now provides `task_state`; see
|
|
16
|
+
[its design and acceptance boundary](dx05-workflow-task-state.md).
|
|
17
|
+
Its error decision is included below. Agent scope/unload (DX05-E) remains
|
|
18
|
+
separate work. This does not change the historical DX05-A baseline above.
|
|
19
|
+
|
|
20
|
+
## Rules and ownership
|
|
21
|
+
|
|
22
|
+
* The owning Contract defines the failure category. A class is added only when
|
|
23
|
+
a caller has a distinct, stable decision to make. Detailed reasons use an
|
|
24
|
+
optional String `code`; they do not imply retry safety, rollback or worker exit.
|
|
25
|
+
* H00 owns the base Error and generic diagnostic projection/reconstruction.
|
|
26
|
+
It does not identify Agents, choose a retry policy or interpret domain codes.
|
|
27
|
+
* C07 Persistence and C12 Storage retain independent error hierarchies.
|
|
28
|
+
StorageBoundary translates only its existing known categories with an explicit
|
|
29
|
+
cause. A storage reason is not automatically a persistence reason.
|
|
30
|
+
* C13 transports the exception. It does not classify Agent, Team or Workflow
|
|
31
|
+
failures. `:cancelled` and a failed task carrying CancellationError remain
|
|
32
|
+
different facts. Partial `outcomes` remain on their existing execution errors.
|
|
33
|
+
* ArgumentError/TypeError describe Ruby/API input or mapping errors where already
|
|
34
|
+
documented. NotImplementedError marks an abstract extension point; it is not
|
|
35
|
+
converted into a provider failure.
|
|
36
|
+
* Existing message-only constructors remain valid. Seven public specialized
|
|
37
|
+
constructors now accept optional `code:` without changing their attributes.
|
|
38
|
+
New target-absence/busy constructors own fixed codes and object identities.
|
|
39
|
+
* Sync/async operation failures keep their category, reason and original cause.
|
|
40
|
+
Existing entry-time validation and submission rejection may still raise before
|
|
41
|
+
a TaskResult is returned. A TaskResult is not a promise of rollback.
|
|
42
|
+
|
|
43
|
+
## Operation decisions and acceptance
|
|
44
|
+
|
|
45
|
+
Each row names the operation family reviewed, its observed channel, the retained
|
|
46
|
+
asymmetry and the action. This is an evaluation of these operations, not a claim
|
|
47
|
+
that every possible backend failure has been injected.
|
|
48
|
+
|
|
49
|
+
| Owner | Public operations / failure meaning / channel | Decision and application effect | Acceptance evidence |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| C01 | TeamCoordinator.create/load: root absence, duplicate identity, uncertain create; direct raise | Add TeamNotFoundError only around the requested root load. Duplicate create retains Persistence conflict: a separate AlreadyExists class adds no necessary recovery choice here. A failed create readback keeps the original failure. | team_coordinator_spec; durable_coordination_spec |
|
|
52
|
+
| C01 | invoke/admission, TeamExecutionRepository.create_active/save/assert_idle!: active Team run | TeamBusyError with team_id and team.busy replaces AgentBusyError only for Team ownership. Child-Agent failures retain their own classes. No AgentBusy alias or inheritance. | execution_constraint_translation_spec; shared SQL coordination contract |
|
|
53
|
+
| C01 | HandoffRunner.invoke/cancel, Orchestrator fan-out, operation rejection | Retain ConfigurationError, HandoffError, StateConflictError, recovery requirements and on_error policy. Missing retained children are not missing Teams. Readback reconciliation may itself fail and does not prove absence. OperationRejected stays private; public code team.enqueue_after_finalize is unchanged. | durable_coordination_spec; integration/team_coordinator_spec; SQL reason diagnostics |
|
|
54
|
+
| C02 | Workflow definition/action, WorkflowContext writes, state CAS, waiting/resume | Retain ConfigurationError, WorkflowContextOwnershipError, WorkflowAsyncActionError and persistence conflict. Workflow has no equivalent target-root load API to justify another NotFound class. DX05-D adds WorkflowTaskResumeError with workflow.task_resume_unsupported only for unsupported restoration of a task-state phase, before any start side effect. Start/source/apply failures keep their original errors, and task cancellation remains distinct from a failed CancellationError. Agent scope is separate. | workflow and execution unit/integration suites; task_state_spec; shared SQL task-state contract (live PostgreSQL acceptance pending) |
|
|
55
|
+
| C03 | GeneratorVerifier.invoke; schema-qualified confidence decision | Agent owns OutputError; Generation records output_failed with its diagnostic; GeneratorVerifier::UnacceptedResultError carries Result and an optional code. CleanupError retains cleanup and primary failures. See generator-verifier-lifecycle-result.md. | generator_verifier specs; error_api_diagnostics_spec |
|
|
56
|
+
| C04 | Base.load, create/get, invoke/resume/purge | Add AgentNotFoundError only at the requested root. Journal/content/execution absence remains persistence failure. Preserve foreign AgentNotFound identity. AlreadyExists/Busy/Purged, uncertain purge and recovery-required retain their meaning. | error_api_boundaries_spec; Agent lifecycle/recovery suites |
|
|
57
|
+
| C04 | stream listener policy; result/failure persistence | StreamCallbackError keeps event_type/original_error/result, with optional code. Listener :fail_task versus :log_and_continue remains explicit; no unconditional callback swallowing. | stream_callback_error_policy_spec; error_api_diagnostics_spec |
|
|
58
|
+
| C05 | Assembly.prepare and policy invocation; plan/budget validation | Keep BudgetExceededError and ManifestError. Reuse C15 public trace_with, metadata only; a finish failure cannot mask policy failure or export the returned context. | error_api_boundaries_spec; Context suites |
|
|
59
|
+
| C06 | Tool.call / native async operation; schema, execution, async handle validity | Keep ToolError class; supply tool.invalid_arguments / tool.execution_failed / tool.invalid_async_result at owned conversion sites. Explicit async cause fixes delayed-failure information loss. Existing ToolError/cancellation pass through unchanged. :suppress survives logger failure. | tool/async_operation_spec; tool/base_spec; schema_error_spec |
|
|
60
|
+
| C07 | StorageBoundary, transaction, SaveOutcome, SnapshotComparison | Retain known-category mapping and original cause. Unknown I/O/application failures pass through. Keep committed/not_committed/unknown separately from exception class; no code-based reconciliation. | storage_boundary_spec; failure_contract_spec; snapshot_comparison_spec; SQL suites |
|
|
61
|
+
| C08 | fetch, fetch_text, fetch_json and content identity | Invalid requested JSON is IntegrityError with content.invalid_json and original ParserError; invalid UTF-8 is content.invalid_encoding. Raw fetch failures pass through; no dependency on OutputParser. Requesting JSON for plain text is also invalid representation, not proof of physical corruption. | error_api_boundaries_spec; content conformance; SQLite |
|
|
62
|
+
| C09 | LLM complete/stream, request/result checks, RubyLLM conversion | Authentication/RateLimit remain TransportError children; ContextLength and InvalidResult remain separate. The RubyLLM backend also maps identified Faraday TimeoutError/ConnectionFailed to TransportError with the original cause chain. Unknown exceptions, cancellation/deadlines and callbacks (including same-class Faraday failures) retain identity. Classification does not imply retry safety. | LLMAdapter suites; transport_error unit and real-HTTP integration contracts |
|
|
63
|
+
| C10 | VectorStore add/search/remove/clear/size and AsyncClient | Keep InvalidResultError for invalid response and driver communication errors unchanged. Driver-specific transaction/effect uncertainty is not normalized without a backend-specific contract. No universal VectorStore TransportError or sibling-contract borrowing. | VectorStore and async-client suites; live PG/Redis pending |
|
|
64
|
+
| C11 | Embeddings.embed / AsyncClient; RubyLLM errors | Owner-local AuthenticationError and RateLimitError remain under TransportError. Other RubyLLM errors and identified Faraday TimeoutError/ConnectionFailed map to TransportError in the concrete backend, preserving causes. Arbitrary I/O failures, cancellation/deadlines and programming errors are not guessed to be SDK failures. | error_api_boundaries_spec; embeddings/async_client_spec; transport_error unit and real-HTTP integration contracts |
|
|
65
|
+
| C12 | Records.read/fetch, conditions/unique constraints, streams/blobs, atomic views | Keep nil read versus raising fetch; preserve condition/resource/constraint. Add optional code to specialized constructors; SPI 2 and storage format unchanged. No Agent or Persistence semantics in the backend. | storage SPI snapshot; backend conformance; SQLite |
|
|
66
|
+
| C13 | TaskResult completion, map/flat_map/all_settled, wait/timeout/cancel | Transport errors unchanged. Observer failure leaves settled result intact and continues later observers even if logging fails. Transformation failure settles the derived task. Outcome attributes and physical completion remain separate. | task_result_spec; error_api_boundaries_spec; async composition suites |
|
|
67
|
+
| C15 | Base.trace, trace_with, OpenTelemetry.trace, Automatic | Explicit tracing propagates start/finish-only failure. After a primary business failure finish is attempted once and secondary report failure is suppressed. No finish after start failure. OpenTelemetry keeps with_span context. Automatic retains best-effort observation policy; Langfuse keeps its adapter-specific network suppression. | tracing/base_spec; open_telemetry_tracer_spec; automatic and extension specs |
|
|
68
|
+
| C16 | Filter.call/block!, PromptInjectionFilter | Keep explicit FilterBlockError and filter attribute; add optional constructor code. Implementation errors are not transformed into policy refusal. No unnecessary common rejection hierarchy. | filter specs; error_api_diagnostics_spec |
|
|
69
|
+
| C17 | JsonParser.parse, StructuredParser.parse | Keep ParseError for JSON and documented ArgumentError/TypeError schema-instantiation rejection. Other schema bugs pass through. User schemas should raise domain-specific errors when they must not be treated as mapping rejection. No blanket StandardError rescue added. | output_parser specs; schema integration |
|
|
70
|
+
| M91 | OrderedEventDelivery.publish/open/drain | ClosedError/OverflowError now inherit Phronomy::Error and report delivery.closed / delivery.capacity_exceeded. StandardError rescue still works; Phronomy::Error rescue now includes them. Reporting failure cannot replace an application error. No automatic resend. | ordered_event_delivery specs; error_api_boundaries_spec |
|
|
71
|
+
| I05/I06/I07/I09 | Concrete binding, AsyncClients, runtime admission/shutdown | Preserve backend-specific failures and execution admission/timeout categories. No new reverse references into domain contracts. | architecture gate; backend/execution integration |
|
|
72
|
+
| Other concrete helpers | Documents loaders, tools, evaluation and configuration | Preserve documented external operation errors and configuration checks; do not manufacture an application-wide ErrorTranslator. Generic Tool conversion applies when called through its public contract. | concrete tool/document/evaluation suites |
|
|
73
|
+
|
|
74
|
+
## Diagnostic format and recovery
|
|
75
|
+
|
|
76
|
+
Top-level `class`, `message` and optional `code` remain unchanged. An optional
|
|
77
|
+
`cause` object transports the same neutral fields. New cause data is bounded to
|
|
78
|
+
three ancestors and 2,048 characters per field. A further cause is represented
|
|
79
|
+
by `{ "truncated": true }`; an oversized code is omitted, never shortened into
|
|
80
|
+
another valid-looking code. Old top-level message behavior is unchanged.
|
|
81
|
+
Backtraces, arbitrary exception attributes and live objects are not serialized.
|
|
82
|
+
|
|
83
|
+
Agent and Team recovery use the same H00 decoder. It constructs only generic
|
|
84
|
+
Phronomy::Error values, including their optional causes, and never constantizes
|
|
85
|
+
saved names. Unknown/nil codes are retained. A restored cause is diagnostic data,
|
|
86
|
+
not the original exception object or its domain-specific attributes. Applications
|
|
87
|
+
may inspect `error.cause&.code` without depending on a storage exception class.
|
|
88
|
+
The live exception still retains its exact original cause.
|
|
89
|
+
|
|
90
|
+
## Migration and before/after use
|
|
91
|
+
|
|
92
|
+
Before, a broad Persistence/Storage NotFound rescue could mistake a broken
|
|
93
|
+
transcript for a missing Agent. After, limit creation to the requested identity:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
begin
|
|
97
|
+
agent = ConversationAgent.load(agent_id, persistence: store)
|
|
98
|
+
rescue Phronomy::AgentNotFoundError => error
|
|
99
|
+
raise unless error.agent_id == agent_id.to_s
|
|
100
|
+
agent = ConversationAgent.create(agent_id: agent_id, persistence: store)
|
|
101
|
+
end
|
|
102
|
+
# A later transcript failure is not handled as Agent absence.
|
|
103
|
+
messages = PhronomyExamples::TranscriptMessages.read(agent)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Before, an application handled a busy Team by rescuing AgentBusyError. After:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
begin
|
|
110
|
+
team.invoke(input)
|
|
111
|
+
rescue Phronomy::MultiAgent::TeamBusyError => error
|
|
112
|
+
# Application decides whether to expose an existing run or defer new work.
|
|
113
|
+
show_active_team(error.team_id)
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Before, Embeddings::TransportError alone required SDK knowledge to distinguish
|
|
118
|
+
credentials and quota. After, both remain catchable through TransportError:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
begin
|
|
122
|
+
vector = embeddings.embed(text)
|
|
123
|
+
rescue Phronomy::Embeddings::AuthenticationError
|
|
124
|
+
request_credentials
|
|
125
|
+
rescue Phronomy::Embeddings::RateLimitError
|
|
126
|
+
display_limit
|
|
127
|
+
rescue Phronomy::Embeddings::TransportError => error
|
|
128
|
+
report_provider_failure(error)
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
These are application choices, not core-provided UI or automatic retry helpers.
|
|
133
|
+
For Tool failures the application code need not change: `error.cause` now works
|
|
134
|
+
for late async failures too. New reason codes are additive; custom ToolError
|
|
135
|
+
instances pass through with their original nil/unknown code.
|
|
136
|
+
|
|
137
|
+
| Existing consumer | Migration / compatibility |
|
|
138
|
+
|---|---|
|
|
139
|
+
| Persistence::NotFoundError rescue around Agent/Team load | Still catches the new subclass; narrow it to the domain type and target ID for create-on-absence |
|
|
140
|
+
| Storage::NotFoundError rescue around Agent load | Already crossed the wrong boundary; update as the examples do |
|
|
141
|
+
| AgentBusyError rescue for Team's own admission | Change to MultiAgent::TeamBusyError; this is an intentional rescue-set change |
|
|
142
|
+
| JSON::ParserError rescue around content.fetch_json | Change to ContentStore::IntegrityError and inspect code; original ParserError is cause |
|
|
143
|
+
| Embeddings::TransportError rescue | Still catches both new subclasses |
|
|
144
|
+
| Phronomy::Error rescue around optional delivery | Now includes ClosedError/OverflowError; evaluate the application's handling |
|
|
145
|
+
| Exact equality of saved diagnostic hashes | Allow/test the optional cause object. Existing keys/codes and old JSON remain readable |
|
|
146
|
+
| Error message parsing or class-name constantization | Use public categories/codes for live failures; generic decoded diagnostics for recovery |
|
|
147
|
+
| Generic error logger retrying any error | Review separately; code/type does not prove rollback or idempotence |
|
|
148
|
+
|
|
149
|
+
## Dependency acceptance
|
|
150
|
+
|
|
151
|
+
No new Contract is introduced. New types live with C01/C04/C11; M91 remains an
|
|
152
|
+
optional consumer. C04/C01 specialize C07's absence category using existing
|
|
153
|
+
forward dependencies. H00 has no new domain reference. C05 uses C15's public
|
|
154
|
+
SPI wrapper rather than a private helper. C09/C10/C11 do not borrow each other's
|
|
155
|
+
error types. Ruby and RBS dependency graphs, not just Contract counts, are checked
|
|
156
|
+
by the existing architecture tool. See the generated audit for candidate tree
|
|
157
|
+
and exact deltas; the separate Contract diagram must describe the candidate as
|
|
158
|
+
unmerged until adoption is verified.
|
|
159
|
+
|
|
160
|
+
### Correction to the earlier Contract count
|
|
161
|
+
|
|
162
|
+
The baseline and candidate both have **32**, not 30, static Contract pairs.
|
|
163
|
+
The previous report omitted the existing RBS-only C01 → C08 references in
|
|
164
|
+
`sig/phronomy/multi_agent/store.rbs` (Store/_Records.contents) and C05 → C08
|
|
165
|
+
in `sig/phronomy/context.rbs` (Assembly.store). Both name the C08-owned
|
|
166
|
+
`_ContentRepository` interface. These are valid forward public-contract
|
|
167
|
+
references, not implementation dependencies introduced by this patch.
|
|
168
|
+
The candidate diagram restores these two edges and labels the correction.
|
|
169
|
+
|
|
170
|
+
At directory level the union still has 273 pairs, but two entries change:
|
|
171
|
+
M91 integrations → H00 common is added; multi_agent/persistence → agent is
|
|
172
|
+
removed because Team admission now owns its BusyError. Equal totals alone
|
|
173
|
+
would conceal this responsibility improvement. The 32 Contract pairs are
|
|
174
|
+
unchanged and acyclic; Ruby+RBS architecture validation passes.
|
|
175
|
+
|
|
176
|
+
## Static exception inventory
|
|
177
|
+
|
|
178
|
+
Ripper inspected 339 Ruby files. The following 63 definitions include base, private and optional-integration types; this is not a count of public APIs. Ruby declarations establish ownership. Existing RBS definitions and new owning-domain signatures are checked together.
|
|
179
|
+
|
|
180
|
+
| Class | Parent | Declaration |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| `Phronomy::AgentAlreadyExistsError` | `Phronomy::Error` | `lib/phronomy/agent/agent_already_exists_error.rb` |
|
|
183
|
+
| `Phronomy::AgentBusyError` | `Phronomy::Error` | `lib/phronomy/agent/agent_busy_error.rb` |
|
|
184
|
+
| `Phronomy::AgentNotFoundError` | `Phronomy::Persistence::NotFoundError` | `lib/phronomy/agent/agent_not_found_error.rb` |
|
|
185
|
+
| `Phronomy::AgentPurgedError` | `Phronomy::Error` | `lib/phronomy/agent/agent_purged_error.rb` |
|
|
186
|
+
| `Phronomy::ExecutionRehydrationRequiredError` | `Phronomy::Error` | `lib/phronomy/agent/api/execution_rehydration_required_error.rb` |
|
|
187
|
+
| `Phronomy::HandoffError` | `Phronomy::Error` | `lib/phronomy/agent/handoff_error.rb` |
|
|
188
|
+
| `Phronomy::Agent::RecoveryCoordinator::ResolutionOutcomeUnknownError` | `Phronomy::Error` | `lib/phronomy/agent/recovery/recovery_coordinator.rb` |
|
|
189
|
+
| `Phronomy::StreamCallbackError` | `Phronomy::Error` | `lib/phronomy/agent/stream_callback_error.rb` |
|
|
190
|
+
| `Phronomy::ConfigurationError` | `Phronomy::Error` | `lib/phronomy/common/configuration_error.rb` |
|
|
191
|
+
| `Phronomy::Error` | `StandardError` | `lib/phronomy/common/error.rb` |
|
|
192
|
+
| `Phronomy::ContentStore::IntegrityError` | `Phronomy::Error` | `lib/phronomy/content_store/base.rb` |
|
|
193
|
+
| `Phronomy::Context::BudgetExceededError` | `Phronomy::Error` | `lib/phronomy/context/budget_exceeded_error.rb` |
|
|
194
|
+
| `Phronomy::Context::ManifestError` | `Phronomy::Error` | `lib/phronomy/context/manifest_error.rb` |
|
|
195
|
+
| `Phronomy::Embeddings::AuthenticationError` | `Phronomy::Embeddings::TransportError` | `lib/phronomy/embeddings/authentication_error.rb` |
|
|
196
|
+
| `Phronomy::Embeddings::InvalidResultError` | `Phronomy::Error` | `lib/phronomy/embeddings/invalid_result_error.rb` |
|
|
197
|
+
| `Phronomy::Embeddings::RateLimitError` | `Phronomy::Embeddings::TransportError` | `lib/phronomy/embeddings/rate_limit_error.rb` |
|
|
198
|
+
| `Phronomy::Embeddings::TransportError` | `Phronomy::Error` | `lib/phronomy/embeddings/transport_error.rb` |
|
|
199
|
+
| `Phronomy::BackpressureError` | `Phronomy::Error` | `lib/phronomy/execution/backpressure_error.rb` |
|
|
200
|
+
| `Phronomy::CancellationError` | `Phronomy::Error` | `lib/phronomy/execution/cancellation_error.rb` |
|
|
201
|
+
| `Phronomy::EventLoopReentrancyError` | `Phronomy::Error` | `lib/phronomy/execution/event_loop_reentrancy_error.rb` |
|
|
202
|
+
| `Phronomy::ExecutionCancellationError` | `Phronomy::CancellationError` | `lib/phronomy/execution/execution_cancellation_error.rb` |
|
|
203
|
+
| `Phronomy::ExecutionTimeoutError` | `Phronomy::TimeoutError` | `lib/phronomy/execution/execution_timeout_error.rb` |
|
|
204
|
+
| `Phronomy::InvalidAsyncEntryActionError` | `Phronomy::InvalidAsyncWorkflowActionError` | `lib/phronomy/execution/invalid_async_entry_action_error.rb` |
|
|
205
|
+
| `Phronomy::InvalidAsyncTransitionActionError` | `Phronomy::InvalidAsyncWorkflowActionError` | `lib/phronomy/execution/invalid_async_transition_action_error.rb` |
|
|
206
|
+
| `Phronomy::InvalidAsyncWorkflowActionError` | `Phronomy::Error` | `lib/phronomy/execution/invalid_async_workflow_action_error.rb` |
|
|
207
|
+
| `Phronomy::PoolShutdownError` | `Phronomy::Error` | `lib/phronomy/execution/pool_shutdown_error.rb` |
|
|
208
|
+
| `Phronomy::RecursionLimitError` | `Phronomy::Error` | `lib/phronomy/execution/recursion_limit_error.rb` |
|
|
209
|
+
| `Phronomy::RuntimeShutdownError` | `Phronomy::Error` | `lib/phronomy/execution/runtime_shutdown_error.rb` |
|
|
210
|
+
| `Phronomy::RuntimeShutdownReentrancyError` | `Phronomy::RuntimeShutdownError` | `lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb` |
|
|
211
|
+
| `Phronomy::SchedulerReentrancyError` | `Phronomy::EventLoopReentrancyError` | `lib/phronomy/execution/scheduler_reentrancy_error.rb` |
|
|
212
|
+
| `Phronomy::TimeoutError` | `Phronomy::Error` | `lib/phronomy/execution/timeout_error.rb` |
|
|
213
|
+
| `Phronomy::FilterBlockError` | `Phronomy::Error` | `lib/phronomy/filter/filter_block_error.rb` |
|
|
214
|
+
| `Phronomy::GeneratorVerifier::UnacceptedResultError` | `Phronomy::Error` | `lib/phronomy/generation/generator_verifier/unaccepted_result_error.rb` |
|
|
215
|
+
| `Phronomy::Integrations::OrderedEventDelivery::ClosedError` | `Phronomy::Error` | `lib/phronomy/integrations/ordered_event_delivery.rb` |
|
|
216
|
+
| `Phronomy::Integrations::OrderedEventDelivery::OverflowError` | `Phronomy::Error` | `lib/phronomy/integrations/ordered_event_delivery.rb` |
|
|
217
|
+
| `Phronomy::LLMAdapter::AuthenticationError` | `Phronomy::LLMAdapter::TransportError` | `lib/phronomy/llm_adapter/authentication_error.rb` |
|
|
218
|
+
| `Phronomy::LLMAdapter::RubyLLM::ToolsRequested` | `StandardError` | `lib/phronomy/llm_adapter/backends/ruby_llm.rb` |
|
|
219
|
+
| `Phronomy::LLMAdapter::ContextLengthError` | `Phronomy::Error` | `lib/phronomy/llm_adapter/context_length_error.rb` |
|
|
220
|
+
| `Phronomy::LLMAdapter::InvalidResultError` | `Phronomy::Error` | `lib/phronomy/llm_adapter/invalid_result_error.rb` |
|
|
221
|
+
| `Phronomy::LLMAdapter::RateLimitError` | `Phronomy::LLMAdapter::TransportError` | `lib/phronomy/llm_adapter/rate_limit_error.rb` |
|
|
222
|
+
| `Phronomy::LLMAdapter::TransportError` | `Phronomy::Error` | `lib/phronomy/llm_adapter/transport_error.rb` |
|
|
223
|
+
| `Phronomy::MultiAgent::TeamBusyError` | `Phronomy::Error` | `lib/phronomy/multi_agent/team_busy_error.rb` |
|
|
224
|
+
| `Phronomy::MultiAgent::TeamCoordinator::OperationRejected` | `Phronomy::ConfigurationError` | `lib/phronomy/multi_agent/team_coordinator.rb` |
|
|
225
|
+
| `Phronomy::MultiAgent::TeamNotFoundError` | `Phronomy::Persistence::NotFoundError` | `lib/phronomy/multi_agent/team_not_found_error.rb` |
|
|
226
|
+
| `Phronomy::ParseError` | `Phronomy::Error` | `lib/phronomy/output_parser/parse_error.rb` |
|
|
227
|
+
| `Phronomy::Persistence::Error` | `Phronomy::Error` | `lib/phronomy/persistence/errors.rb` |
|
|
228
|
+
| `Phronomy::Persistence::ConflictError` | `Phronomy::Persistence::Error` | `lib/phronomy/persistence/errors.rb` |
|
|
229
|
+
| `Phronomy::Persistence::StateConflictError` | `Phronomy::Persistence::ConflictError` | `lib/phronomy/persistence/errors.rb` |
|
|
230
|
+
| `Phronomy::Persistence::NotFoundError` | `Phronomy::Persistence::Error` | `lib/phronomy/persistence/errors.rb` |
|
|
231
|
+
| `Phronomy::Persistence::SerializationError` | `Phronomy::Persistence::Error` | `lib/phronomy/persistence/errors.rb` |
|
|
232
|
+
| `Phronomy::Persistence::UnsupportedBackendError` | `Phronomy::Persistence::Error` | `lib/phronomy/persistence/errors.rb` |
|
|
233
|
+
| `Phronomy::Persistence::TransactionError` | `Phronomy::Persistence::Error` | `lib/phronomy/persistence/errors.rb` |
|
|
234
|
+
| `Phronomy::Storage::BlobConflictError` | `Phronomy::Storage::ConflictError` | `lib/phronomy/storage/blob_conflict_error.rb` |
|
|
235
|
+
| `Phronomy::Storage::ConditionFailedError` | `Phronomy::Storage::ConflictError` | `lib/phronomy/storage/condition_failed_error.rb` |
|
|
236
|
+
| `Phronomy::Storage::ConflictError` | `Phronomy::Error` | `lib/phronomy/storage/conflict_error.rb` |
|
|
237
|
+
| `Phronomy::Storage::NotFoundError` | `Phronomy::Error` | `lib/phronomy/storage/not_found_error.rb` |
|
|
238
|
+
| `Phronomy::Storage::SerializationError` | `Phronomy::Error` | `lib/phronomy/storage/serialization_error.rb` |
|
|
239
|
+
| `Phronomy::Storage::TransactionError` | `Phronomy::Error` | `lib/phronomy/storage/transaction_error.rb` |
|
|
240
|
+
| `Phronomy::Storage::UniqueConstraintError` | `Phronomy::Storage::ConflictError` | `lib/phronomy/storage/unique_constraint_error.rb` |
|
|
241
|
+
| `Phronomy::Storage::UnsupportedBackendError` | `Phronomy::Error` | `lib/phronomy/storage/unsupported_backend_error.rb` |
|
|
242
|
+
| `Phronomy::ToolError` | `Phronomy::Error` | `lib/phronomy/tool/tool_error.rb` |
|
|
243
|
+
| `Phronomy::VectorStore::InvalidResultError` | `Phronomy::Error` | `lib/phronomy/vector_store/invalid_result_error.rb` |
|
|
244
|
+
| `Phronomy::WorkflowContextOwnershipError` | `Phronomy::Error` | `lib/phronomy/workflow/execution/workflow_context_ownership_error.rb` |
|
|
245
|
+
| `Phronomy::WorkflowTaskResumeError` (DX05-D) | `Phronomy::Error` | `lib/phronomy/workflow/execution/workflow_task_resume_error.rb` |
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# GeneratorVerifier: approval, lifetime and result contract
|
|
2
|
+
|
|
3
|
+
Generation (C03) is a responsibility group, not a Ruby namespace.
|
|
4
|
+
Phronomy::GeneratorVerifier is the public draft/review/retry API. Applications
|
|
5
|
+
choose the Agent classes, Tools, prompts, evaluation thresholds and authorization
|
|
6
|
+
policy. Generation owns composition and evaluation; Agent (C04) owns Tool
|
|
7
|
+
authorization, durable execution, cancellation and safe unload.
|
|
8
|
+
|
|
9
|
+
Correct behavior takes priority over backward compatibility. This change removes
|
|
10
|
+
trusted/trusted?, raise_if_untrusted and LowConfidenceError. There are no legacy
|
|
11
|
+
aliases or compatibility branches.
|
|
12
|
+
|
|
13
|
+
## Application operations
|
|
14
|
+
|
|
15
|
+
Construct with new or open. Construction acquires both owned Agents, synchronously;
|
|
16
|
+
a failure acquiring the second releases the first. Prepare the pipeline before
|
|
17
|
+
entering EventLoop. Both Agents and their conversations are reused between
|
|
18
|
+
invocations. open requires a block, returns its value, and always attempts release!.
|
|
19
|
+
A directly returned TaskResult is awaited once before release; arrays and nested
|
|
20
|
+
Tasks are not recursively awaited. See [structured output and scope](structured-output-and-scope.md).
|
|
21
|
+
|
|
22
|
+
invoke waits for the final Result. invoke_async returns TaskResult. A pipeline
|
|
23
|
+
admits one invocation at a time; independent concurrent conversations need
|
|
24
|
+
independent pipelines. Workflow state, approvals and cancellation are per invocation.
|
|
25
|
+
config is forwarded to Workflow. Only the private cancellation token and any
|
|
26
|
+
InvocationContext are forwarded to Agent operations, not Workflow-specific
|
|
27
|
+
identity or settings.
|
|
28
|
+
|
|
29
|
+
on_approval receives an Approval with invocation_id, phase (:draft/:review) and
|
|
30
|
+
request (Agent::ToolApprovalRequest). It is a short, thread-safe notification,
|
|
31
|
+
not a blocking UI callback or a function whose return value authorizes a Tool.
|
|
32
|
+
Use approval.respond_async(approved: true/false), or enqueue the Approval for an
|
|
33
|
+
external UI to answer later. The response Task observes the resumed Agent's
|
|
34
|
+
terminal outcome and returns nil. Do not wait for it inside the callback.
|
|
35
|
+
|
|
36
|
+
Without a callback, approval_requests exposes notifications issued in the current
|
|
37
|
+
invocation, including answered ones. It is not a mirror of current durable
|
|
38
|
+
approval state. Suspension is not an error and no implicit approval or denial is
|
|
39
|
+
performed. Expiry and cancellation are application choices. Stale invocation
|
|
40
|
+
handles fail; Agent validates execution/request identity and duplicate decisions.
|
|
41
|
+
Invalid replies fail their own Task without cancelling the original invocation.
|
|
42
|
+
Callback exceptions cancel the owned invocation and remain its primary error.
|
|
43
|
+
|
|
44
|
+
cancel_async requests cancellation without mutating a caller-owned cancellation
|
|
45
|
+
token. It can also address a known Agent execution after a Handoff outcome.
|
|
46
|
+
Completion acknowledges the cancellation operations, not physical quiescence;
|
|
47
|
+
observe the invocation result separately. release! delegates safety to Agent's
|
|
48
|
+
unload!, preserves saved history and does not close the Store. It rejects active
|
|
49
|
+
invocations rather than cancelling or denying approvals implicitly.
|
|
50
|
+
|
|
51
|
+
All owners are attempted during release. Successful owners are removed; failed
|
|
52
|
+
ones remain available for cancellation or another release attempt. Partial release
|
|
53
|
+
cannot be followed by a new invocation. Successful release is idempotent (true,
|
|
54
|
+
then false). CleanupError exposes pipeline, failures, result and primary_error;
|
|
55
|
+
when both application work and cleanup fail, neither exception nor the recovery
|
|
56
|
+
handle is lost. Its cause is the primary exception, if present.
|
|
57
|
+
|
|
58
|
+
Handoff coordination is outside this composition's contract. A Handoff result
|
|
59
|
+
raises generation.unsupported_handoff rather than leaving the Workflow pending.
|
|
60
|
+
Agent retains authority over durable coordination and safe release; Generation
|
|
61
|
+
never purges unfinished work or clears retention to make cleanup appear successful.
|
|
62
|
+
Reconstructing an in-flight GeneratorVerifier after process restart is not offered.
|
|
63
|
+
|
|
64
|
+
## Agent cancellation during approval suspension
|
|
65
|
+
|
|
66
|
+
An approval suspension ends the current FSM session, but the logical execution
|
|
67
|
+
and its original Task remain pending. C04 now retains the cancellation connection
|
|
68
|
+
through this suspension, routes cancellation to its existing quiescence/terminal
|
|
69
|
+
commit barrier, and closes the subscription on resume or termination. Generation
|
|
70
|
+
does not implement this Agent transition itself. Cancellation and approval racing
|
|
71
|
+
at the suspension boundary must not create competing terminal/resume commits.
|
|
72
|
+
|
|
73
|
+
## Results and decisions
|
|
74
|
+
|
|
75
|
+
Result is immutable, including nested data. It contains output, confidence,
|
|
76
|
+
confidence_threshold, review_approved, citations, iterations, review_notes,
|
|
77
|
+
termination_reason, interrupted_phase and failure. accepted? describes satisfaction of the
|
|
78
|
+
configured evaluation conditions, not an application's decision to publish.
|
|
79
|
+
|
|
80
|
+
Confidence is the lower final draft/review score. A completed evaluation is accepted
|
|
81
|
+
only when review_approved is true and confidence meets the threshold. At the
|
|
82
|
+
iteration limit, acceptance wins if both conditions hold; otherwise the reason is
|
|
83
|
+
iteration_limit. Scores and threshold are finite 0..1; max_iterations is a positive
|
|
84
|
+
Integer. Agent validates the fixed output schema without fallback or normalization.
|
|
85
|
+
|
|
86
|
+
A Tool denial is a normal Agent rejected outcome, not a parser input or automatic
|
|
87
|
+
retry. Result reports tool_rejected and the interrupted phase. Confidence and
|
|
88
|
+
review_approved are nil because the evaluation did not complete. Rejection during
|
|
89
|
+
draft has no output; rejection during review retains the draft. iterations counts
|
|
90
|
+
completed drafts.
|
|
91
|
+
|
|
92
|
+
to_h includes all fields. Result.from_h accepts that Hash and its JSON round trip,
|
|
93
|
+
rejecting unknown fields, invalid types and contradictory reasons. Citation keys
|
|
94
|
+
are normalized to strings. Equality and hash use all semantic fields, so a change
|
|
95
|
+
in review decision or threshold is observable. Applications own storage/transport
|
|
96
|
+
choices and any intentional projection of these fields.
|
|
97
|
+
|
|
98
|
+
raise_if_unaccepted defaults to false. When explicitly true, an unaccepted Result
|
|
99
|
+
raises GeneratorVerifier::UnacceptedResultError with result and code. Provider,
|
|
100
|
+
cancellation and callback errors do not become evaluation failures. An Agent
|
|
101
|
+
OutputError becomes output_failed with its phase and diagnostic; there is no automatic
|
|
102
|
+
retry. raise_if_unaccepted preserves that OutputError as the cause.
|
|
103
|
+
|
|
104
|
+
## Ownership boundaries
|
|
105
|
+
|
|
106
|
+
C03 depends on C04 Agent, C02 Workflow and C13 Execution. C04 uses C17
|
|
107
|
+
OutputParser to implement its output promise; C03 has no C17 dependency. Cancellation subscriptions use the existing execution abstraction.
|
|
108
|
+
No Agent registry, private Scope construction, Storage operation or Engine
|
|
109
|
+
implementation is accessed from Generation.
|
|
110
|
+
|
|
111
|
+
Private AgentOperation connects public Agent commands and notifications.
|
|
112
|
+
Invocation owns one call's child observations, subscription lifetime and approval
|
|
113
|
+
routing. WorkflowBuilder/AgentResultReceiver/PipelineState retain graph, event
|
|
114
|
+
conversion and correlated state application. Result owns evaluation facts and
|
|
115
|
+
their invariants.
|
|
116
|
+
|
|
117
|
+
## Application migration
|
|
118
|
+
|
|
119
|
+
Use open around the complete application operation, including validation retries.
|
|
120
|
+
Use accepted? and termination_reason, not score-only trusted?. For an external
|
|
121
|
+
approval UI, start invoke_async, enqueue on_approval notifications, respond through
|
|
122
|
+
the original Approval handle, and observe the final Task before release.
|
|
123
|
+
|
|
124
|
+
examples/19_trust_pipeline is the direct consumer. examples/27_issue_analyzer only
|
|
125
|
+
mentions GeneratorVerifier in descriptive classification text and requires no API
|
|
126
|
+
migration. Full old/new runnable sources are available through the source diff.
|