phronomy 0.22.0 → 0.24.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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +3 -4
  3. data/CHANGELOG.md +215 -10
  4. data/CONTRIBUTING.md +81 -9
  5. data/README.md +15 -6
  6. data/VERIFY.sh +587 -0
  7. data/benchmark/bench_agent_invoke.rb +2 -2
  8. data/benchmark/bench_context_assembler.rb +39 -68
  9. data/benchmark/bench_regression.rb +2 -2
  10. data/docs/architecture/agent-context.md +174 -0
  11. data/docs/architecture/before-llm-input.md +78 -0
  12. data/docs/architecture/context-management.md +232 -0
  13. data/docs/architecture/knowledge-and-rag.md +130 -0
  14. data/docs/architecture/multi-agent-handoff.md +147 -0
  15. data/docs/architecture/persistence.md +186 -0
  16. data/docs/architecture/removed/agent-context.md +72 -0
  17. data/docs/architecture/security-boundaries.md +173 -0
  18. data/docs/architecture/tracing.md +194 -0
  19. data/docs/architecture.md +89 -0
  20. data/docs/archive/design/archived/04_api_design.md +507 -0
  21. data/docs/archive/design/archived/09_guardrails.md +186 -0
  22. data/docs/archive/design/archived/17_rails_integration.md +175 -0
  23. data/docs/archive/design/historical/00_design_philosophy.md +122 -0
  24. data/docs/archive/design/historical/01_rubyllm_evaluation.md +178 -0
  25. data/docs/archive/design/historical/06_design_decisions.md +143 -0
  26. data/docs/changelog/0.14-and-earlier.md +1 -1
  27. data/docs/decisions/001-rubyllm-as-provider-layer.md +6 -1
  28. data/docs/decisions/002-workflow-context-immutability.md +26 -1
  29. data/docs/decisions/006-no-built-in-guardrails.md +2 -1
  30. data/docs/decisions/012-canonical-execution-log-and-context-policy.md +120 -38
  31. data/docs/decisions/014-unified-persistence-durable-state.md +9 -2
  32. data/docs/decisions/016-semantic-multi-agent-handoff.md +114 -0
  33. data/docs/decisions/017-design-authority-and-adr-governance.md +200 -0
  34. data/docs/decisions/018-durability-guarantees-and-failure-model.md +488 -0
  35. data/docs/decisions/019-filter-contract-and-security-boundaries.md +229 -0
  36. data/docs/decisions/020-canonical-workflow-instance-identity.md +177 -0
  37. data/docs/decisions/021-generic-agent-invocation-identity-removal.md +119 -0
  38. data/docs/decisions/022-agent-execution-parent-identity-and-runtime-routing-boundary.md +193 -0
  39. data/docs/decisions/023-fsm-session-incarnation-identity-and-routing.md +139 -0
  40. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +188 -0
  41. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +249 -0
  42. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +257 -0
  43. data/docs/decisions/027-llm-adapter-provider-boundary.md +93 -0
  44. data/docs/decisions/028-preparing-recovery-replay-contract.md +106 -0
  45. data/docs/decisions/029-semantic-completion-and-application-effect-boundary.md +220 -0
  46. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +235 -0
  47. data/docs/decisions/031-durable-multi-agent-coordination.md +301 -0
  48. data/docs/decisions/README.md +176 -0
  49. data/docs/design/durable-semantic-coordination/CHANGELOG_V2_REVISION_2.md +33 -0
  50. data/docs/design/durable-semantic-coordination/CONTINUATION_DECISION_REFACTOR.md +191 -0
  51. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +862 -0
  52. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +106 -0
  53. data/docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md +179 -0
  54. data/docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md +302 -0
  55. data/docs/features.md +65 -11
  56. data/docs/getting-started.md +77 -45
  57. data/docs/migrations/0.19.md +14 -7
  58. data/docs/migrations/0.22.md +390 -0
  59. data/docs/migrations/durable-semantic-coordination-v2.md +65 -0
  60. data/docs/persistence-backends.md +129 -40
  61. data/docs/runtime-and-concurrency.md +227 -33
  62. data/examples/README.md +13 -0
  63. data/lib/phronomy/agent/agent_execution.rb +21 -17
  64. data/lib/phronomy/agent/agent_invocation.rb +288 -93
  65. data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
  66. data/lib/phronomy/agent/agent_root.rb +3 -3
  67. data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
  68. data/lib/phronomy/agent/async_event_api.rb +162 -72
  69. data/lib/phronomy/agent/base.rb +416 -181
  70. data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
  71. data/lib/phronomy/agent/context_assembler.rb +437 -178
  72. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  73. data/lib/phronomy/agent/context_plan.rb +18 -13
  74. data/lib/phronomy/agent/context_plan_validator.rb +246 -88
  75. data/lib/phronomy/agent/context_policies/default.rb +123 -34
  76. data/lib/phronomy/agent/context_policy.rb +109 -3
  77. data/lib/phronomy/agent/context_policy_input.rb +244 -0
  78. data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
  79. data/lib/phronomy/agent/exact_execution.rb +153 -0
  80. data/lib/phronomy/agent/execution_cancellation.rb +25 -0
  81. data/lib/phronomy/agent/execution_coordinator.rb +2417 -572
  82. data/lib/phronomy/agent/handoff.rb +43 -0
  83. data/lib/phronomy/agent/handoff_capability_factory.rb +45 -0
  84. data/lib/phronomy/agent/handoff_context.rb +120 -0
  85. data/lib/phronomy/agent/handoff_execution_coordinator.rb +143 -0
  86. data/lib/phronomy/agent/handoff_policy.rb +143 -0
  87. data/lib/phronomy/agent/handoff_projection.rb +207 -0
  88. data/lib/phronomy/agent/handoff_request.rb +45 -0
  89. data/lib/phronomy/agent/handoff_runner.rb +178 -0
  90. data/lib/phronomy/agent/handoff_state.rb +43 -0
  91. data/lib/phronomy/agent/journal_record.rb +17 -4
  92. data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
  93. data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
  94. data/lib/phronomy/agent/llm_operation_result.rb +12 -7
  95. data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
  96. data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
  97. data/lib/phronomy/agent/recovery_coordinator/continuation.rb +174 -0
  98. data/lib/phronomy/agent/recovery_coordinator/installation.rb +381 -0
  99. data/lib/phronomy/agent/recovery_coordinator/resolution.rb +511 -0
  100. data/lib/phronomy/agent/recovery_coordinator.rb +217 -0
  101. data/lib/phronomy/agent/recovery_support.rb +504 -0
  102. data/lib/phronomy/agent/ruby_llm_materializer.rb +16 -13
  103. data/lib/phronomy/agent/selection/candidate.rb +53 -0
  104. data/lib/phronomy/agent/selection/constraint.rb +49 -0
  105. data/lib/phronomy/agent/shared_state.rb +38 -1
  106. data/lib/phronomy/agent/tool_approval_request.rb +33 -5
  107. data/lib/phronomy/agent/tool_definition_set.rb +49 -3
  108. data/lib/phronomy/agent/tool_invocation.rb +339 -103
  109. data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
  110. data/lib/phronomy/agent.rb +20 -2
  111. data/lib/phronomy/agent_already_exists_error.rb +5 -0
  112. data/lib/phronomy/agent_purged_error.rb +5 -0
  113. data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
  114. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
  115. data/lib/phronomy/engine/event_loop.rb +622 -63
  116. data/lib/phronomy/engine/fsm_session.rb +194 -21
  117. data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
  118. data/lib/phronomy/engine/runtime/team_ownership_registry.rb +77 -0
  119. data/lib/phronomy/engine/runtime.rb +92 -20
  120. data/lib/phronomy/generator_verifier.rb +12 -14
  121. data/lib/phronomy/invocation_context.rb +9 -29
  122. data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
  123. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +134 -0
  124. data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
  125. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
  126. data/lib/phronomy/multi_agent/orchestrator.rb +71 -26
  127. data/lib/phronomy/multi_agent/team_coordinator.rb +473 -125
  128. data/lib/phronomy/multi_agent/team_execution.rb +44 -0
  129. data/lib/phronomy/multi_agent/team_root.rb +41 -0
  130. data/lib/phronomy/persistence/durable_codec.rb +706 -0
  131. data/lib/phronomy/persistence/durable_record.rb +117 -0
  132. data/lib/phronomy/persistence/in_memory.rb +471 -133
  133. data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
  134. data/lib/phronomy/persistence/repository_facades.rb +535 -0
  135. data/lib/phronomy/persistence.rb +174 -40
  136. data/lib/phronomy/recovery.rb +186 -0
  137. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  138. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +2 -1
  139. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
  140. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
  141. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  142. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +137 -0
  143. data/lib/phronomy/testing/persistence_contract.rb +5 -0
  144. data/lib/phronomy/tools/agent.rb +1 -1
  145. data/lib/phronomy/tracing/automatic.rb +176 -0
  146. data/lib/phronomy/tracing/base.rb +11 -2
  147. data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
  148. data/lib/phronomy/version.rb +1 -1
  149. data/lib/phronomy/workflow.rb +3 -6
  150. data/lib/phronomy/workflow_context.rb +14 -5
  151. data/lib/phronomy/workflow_recovery.rb +123 -0
  152. data/lib/phronomy/workflow_runner.rb +468 -256
  153. data/lib/phronomy.rb +6 -0
  154. data/scripts/api_snapshot.rb +12 -0
  155. data/sig/phronomy/agent.rbs +209 -7
  156. data/sig/phronomy/handoff.rbs +41 -0
  157. data/sig/phronomy/multi_agent.rbs +35 -0
  158. data/sig/phronomy/persistence.rbs +123 -4
  159. data/sig/phronomy/runtime.rbs +1 -4
  160. data/sig/phronomy/workflow.rbs +2 -2
  161. data/sig/phronomy.rbs +10 -0
  162. metadata +84 -18
  163. data/examples/workflows/agent_event_mapping.rb +0 -101
  164. data/examples/workflows/generic_task_event_mapping.rb +0 -66
  165. data/lib/phronomy/agent/activation_registry.rb +0 -28
  166. data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
  167. data/lib/phronomy/agent/context_candidate.rb +0 -47
  168. data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
  169. data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
  170. data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
  171. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
  172. data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
  173. data/lib/phronomy/agent/context_policy_registry.rb +0 -46
  174. data/lib/phronomy/agent/context_request.rb +0 -35
  175. data/lib/phronomy/agent/context_selection_unit.rb +0 -38
  176. data/lib/phronomy/agent/derived_content_spec.rb +0 -34
  177. data/lib/phronomy/agent/runner.rb +0 -97
  178. data/lib/phronomy/multi_agent/handoff.rb +0 -64
@@ -0,0 +1,106 @@
1
+ # ADR-028: Replay-safe recovery for durably admitted `:preparing` Agent executions
2
+
3
+ Status: Accepted
4
+
5
+ ## Context
6
+
7
+ ADR-018 defines Agent execution resumption as continuation of the same logical
8
+ `execution_id` from durable recovery state when there is no unresolved semantic
9
+ operation that prevents safe continuation. The implementation already persists a
10
+ new `AgentExecution` in `status: :preparing, phase: :preparing` before input
11
+ filters, `before_llm_input`, Context Policy, and Manifest finalization run.
12
+
13
+ Before this decision, process loss in that window left a durable active execution
14
+ with no automatic continuation path.
15
+
16
+ ## Decision
17
+
18
+ A `:preparing` execution may be resumed automatically only when the framework
19
+ durably recorded `preparation_replayable == true`. Missing, false, or unsupported
20
+ values fail closed. The recovered continuation keeps the same `execution_id`.
21
+
22
+ The preparation region is **replay-safe, not deterministic**. Input filtering,
23
+ `before_llm_input`, Context Policy, retrieval, and other preparation work may be
24
+ executed again after process loss. Applications must ensure those callbacks are
25
+ safe under at-least-once execution. The framework does not require repeated
26
+ preparation to produce byte-identical results.
27
+
28
+ Before Manifest finalization, preparation results may be recomputed. After the
29
+ Manifest is durably committed, the Manifest is the authority and existing
30
+ post-Manifest recovery rules apply.
31
+
32
+ ### Durable application context
33
+
34
+ `config` remains a runtime/application Hash and is not generally durable. An
35
+ Application value that affects preparation semantics and must survive restart is
36
+ placed under one reserved key:
37
+
38
+ ```ruby
39
+ config: {
40
+ durable_context: {
41
+ "tenant" => "A",
42
+ "search_profile" => "legal"
43
+ }
44
+ }
45
+ ```
46
+
47
+ `config[:durable_context]`, when present, must be a Hash accepted by
48
+ `Phronomy::CanonicalJSON`. The framework performs a Canonical JSON round trip
49
+ before execution admission and uses the detached immutable snapshot for both the
50
+ initial run and any recovery replay. `durable_context: nil` and non-Hash values
51
+ are rejected before an `AgentExecution` is created. Missing and explicit `{}` are
52
+ distinct.
53
+
54
+ The snapshot is stored in the Content Store and the execution retains only
55
+ `durable_context_ref`.
56
+
57
+ ### Conservative replay eligibility
58
+
59
+ This change intentionally does not add a new arbitrary raw-input serialization
60
+ format. Current initial admission stores `extract_message(input)` as text, while
61
+ filters and instruction construction may inspect the original Ruby input.
62
+ Therefore automatic `:preparing` replay is enabled only for String invocation
63
+ inputs. Non-String inputs fail closed after process loss at this phase.
64
+
65
+ Automatic preparation replay is also disabled when the framework can see a
66
+ Runtime-only semantic dependency that it cannot reconstruct, including:
67
+
68
+ - Multi-Agent handoff/routing wiring;
69
+ - a custom Agent invocation approval policy;
70
+ - invocation-context approval/redaction/token-budget policy values.
71
+
72
+ These conditions are represented by the single durable boolean
73
+ `preparation_replayable`; the framework does not persist those Runtime objects.
74
+ A true value records that no framework-known blocker was present; it does not
75
+ replace the Application replay-safety contract for callbacks or custom config.
76
+
77
+ Applications that use other ordinary `config` entries to influence preparation,
78
+ Provider-adapter, Tool, or other continuation semantics are responsible for moving
79
+ restart-required values into `config[:durable_context]` and reading them from that
80
+ sub-Hash after recovery.
81
+
82
+ ### Agent definition compatibility
83
+
84
+ Existing `agent_definition_id` / `agent_definition_version` load validation
85
+ continues to guard runtime definition compatibility. Applications must increment
86
+ the Agent definition version when a change to filters, hooks, Context Policy, or
87
+ other preparation behavior is not recovery-compatible.
88
+
89
+ ## Non-goals
90
+
91
+ This decision does not add:
92
+
93
+ - recovery of caller `Task` objects;
94
+ - durable Multi-Agent active routing or Handoff Context;
95
+ - a durable execution-query API;
96
+ - a new Recovery event;
97
+ - Content Store garbage collection;
98
+ - Workflow checkpoint changes;
99
+ - deterministic replay of Application callbacks.
100
+
101
+ ## Consequences
102
+
103
+ Direct, replay-safe String invocations can continue the same durable logical
104
+ execution after process loss in the initial preparation window. Unsupported or
105
+ ambiguous cases remain fail-closed rather than being guessed or silently
106
+ abandoned.
@@ -0,0 +1,220 @@
1
+ # ADR-029: Semantic Completion and Application Effect Boundary
2
+
3
+ ## Status
4
+
5
+ Accepted. V2 revision 2, 2026-09-06.
6
+
7
+ User approval covers the V2 boundary and the five recovery-contract clarifications.
8
+ Acceptance is design authority; it is not a claim of repository integration or test success.
9
+
10
+ ## Date
11
+
12
+ 2026-09-06
13
+
14
+ ## Context
15
+
16
+ Phronomy durably records Agent semantic execution progress and terminal outcomes.
17
+ After a terminal durable commit, Runtime may notify Application code through
18
+ `on_event` and settle process-local caller Tasks.
19
+
20
+ A previous proposal attempted to make terminal callback delivery itself durable
21
+ across process loss by storing a pending-delivery descriptor, rediscovering
22
+ pending callbacks on `Agent.load`, and durably acknowledging callback attempts.
23
+
24
+ That approach crossed an architectural boundary.
25
+
26
+ An arbitrary Application callback may perform an external effect outside
27
+ Phronomy's Persistence transaction domain. Phronomy cannot make that effect
28
+ exactly once. A process can die after the callback has produced an effect but
29
+ before Phronomy records an acknowledgement, so retrying the callback still
30
+ requires Application-level idempotency.
31
+
32
+ Maintaining a framework outbox/ACK protocol therefore does not remove the
33
+ Application responsibility that matters most, while it adds persistence indexes,
34
+ recovery ordering, acknowledgement reconciliation and callback-specific state to
35
+ the Agent execution engine.
36
+
37
+ ## Decision
38
+
39
+ ### 1. Semantic terminal state remains the durable boundary
40
+
41
+ The existing AgentExecution terminal statuses remain semantic terminal states:
42
+
43
+ ```text
44
+ completed
45
+ handed_off
46
+ failed
47
+ cancelled
48
+ rejected
49
+ blocked
50
+ ```
51
+
52
+ Phronomy does not add a callback-only `:completing` status.
53
+
54
+ A known-successful terminal durable transition ends the logical AgentExecution and
55
+ releases normal Agent admission according to ADR-025.
56
+
57
+ ### 2. Terminal semantic result/error evidence is durable
58
+
59
+ The terminal transaction continues to persist the canonical semantic evidence
60
+ already required by Agent durability, including as applicable:
61
+
62
+ ```text
63
+ terminal AgentExecution
64
+ AgentRoot revision/lifecycle state
65
+ Journal terminal facts
66
+ result_ref / error_ref
67
+ Provider / Tool durable evidence already owned by Agent execution
68
+ ```
69
+
70
+ Process loss after this commit must not cause the semantic execution to be rerun
71
+ merely because the Application did not observe its completion callback.
72
+
73
+ ### 3. `on_event` is a process-local observation contract
74
+
75
+ Application `on_event` callbacks are Runtime observations.
76
+
77
+ Phronomy invokes them in the current process according to the existing callback
78
+ error policy, but does not create a restart-spanning delivery obligation.
79
+
80
+ Phronomy does not persist:
81
+
82
+ ```text
83
+ terminal_delivery
84
+ delivery_pending
85
+ callback attempt_count
86
+ callback acknowledgement state
87
+ callback/Proc/Task references
88
+ ```
89
+
90
+ `Agent.load` does not scan for or redeliver missed terminal callbacks.
91
+
92
+ ### 4. Callback loss after process loss is allowed
93
+
94
+ The following failure is explicitly permitted:
95
+
96
+ ```text
97
+ terminal semantic commit succeeds
98
+ -> process dies before Application callback
99
+ -> callback is not reconstructed/redelivered
100
+ ```
101
+
102
+ The semantic outcome remains authoritative and must not be replayed.
103
+
104
+ An Application that requires restart-spanning notification must implement that
105
+ requirement in an Application-owned durable mechanism such as:
106
+
107
+ ```text
108
+ outbox
109
+ job queue
110
+ database transaction/status row
111
+ idempotency key
112
+ Application Workflow
113
+ ```
114
+
115
+ ### 5. Callback effects are outside Phronomy's exactly-once guarantee
116
+
117
+ Phronomy does not claim exactly-once semantics for:
118
+
119
+ ```text
120
+ email
121
+ webhook
122
+ external database mutation
123
+ message-broker publication
124
+ arbitrary Application callback side effect
125
+ ```
126
+
127
+ Applications own idempotency/deduplication appropriate to those systems.
128
+
129
+ ### 6. Caller Tasks remain Runtime-only
130
+
131
+ A caller-facing Task can observe same-process success/failure and callback policy.
132
+ It is not rehydrated after process loss.
133
+
134
+ No durable state exists solely to recreate or settle a lost caller Task.
135
+
136
+ ### 7. Handoff routing is independent from callback delivery
137
+
138
+ A Source Agent may terminalize as `:handed_off`.
139
+
140
+ Durable Handoff responsibility transfer is governed by ADR-030 and does not depend
141
+ on whether a local `:handoff` Application event was observed.
142
+
143
+ Losing the local callback must not lose the Target routing state.
144
+
145
+ ### 8. Read-only outcome access and execution discovery
146
+
147
+ Applications must be able to query an execution's owner, status and durable
148
+ result/error by exact semantic execution ID without invoking/recovering it or
149
+ redelivering callbacks. If admission committed before the caller received its ID,
150
+ a public discovery path from the known Agent/Team identity must cover retained
151
+ terminal as well as active executions. Candidate discovery does not guarantee
152
+ request deduplication or unambiguous correlation. Existing retention applies.
153
+
154
+ Reuse existing APIs where they meet this contract; map missing capabilities only
155
+ after baseline inspection. Do not invent a framework outbox or request registry.
156
+ See [RC-01](RECOVERY_CONTRACT_CLARIFICATIONS.md#1-rc-01--確定結果の参照と実行の発見).
157
+
158
+ ### 9. Unknown commit outcome and cancellation
159
+
160
+ A failed read is not proof of absence. A lost commit acknowledgement requires
161
+ readback with the same operation/reserved identities before new semantic work.
162
+ Unresolved storage uncertainty follows existing Persistence error/retry rules,
163
+ not Application factual invention (RC-02).
164
+
165
+ Stopping observation, losing a caller, or shutting down Runtime does not by
166
+ itself request semantic cancellation. Explicit semantic cancellation uses the
167
+ existing Agent contract and preserves exact child identities and confirmed
168
+ outcomes through its existing terminal/settlement boundaries (RC-04).
169
+
170
+ The guarantee is reuse of confirmed durable outcomes and recovery of unfinished
171
+ executions under the same semantic identity. Unknown external Provider/Tool
172
+ effects follow existing Agent Recovery; external effects are not exactly once
173
+ (RC-05).
174
+
175
+ ## Persistence / Runtime boundary
176
+
177
+ The ordering remains:
178
+
179
+ ```text
180
+ EventLoop-owned live authority
181
+ -> OffloadPool durable semantic transaction
182
+ -> Persistence commit
183
+ -> EventLoop apply / release admission
184
+ -> optional current-process Application callback
185
+ -> current-process Task settlement
186
+ ```
187
+
188
+ No callback acknowledgement transaction follows.
189
+
190
+ ## Relationship to existing ADRs
191
+
192
+ - Clarifies ADR-018 X0 boundaries: Application effects remain external.
193
+ - Preserves ADR-023: Runtime routing identities remain non-durable.
194
+ - Preserves ADR-024: EventLoop remains live-state writer.
195
+ - Preserves ADR-025: AgentExecution lifetime ends at semantic terminal commit.
196
+ - Does not alter ADR-028 preparation replay rules.
197
+ - ADR-030/031 may persist additional **semantic coordination facts**, but not
198
+ restart-spanning Application callback delivery.
199
+
200
+ ## Required invariants
201
+
202
+ 1. Known terminal semantic work is never replayed merely because a callback may
203
+ have been lost.
204
+ 2. No terminal callback-delivery metadata/index/ACK protocol is required.
205
+ 3. Callbacks, Tasks and external side effects remain Runtime/Application concerns.
206
+ 4. Agent terminal statuses remain semantic states, not notification states.
207
+ 5. Phronomy does not claim exactly-once external effects.
208
+ 6. Read-only result access and retained execution discovery do not trigger work.
209
+ 7. Read/commit uncertainty and observation loss do not authorize replacement work.
210
+
211
+ ## Non-goals
212
+
213
+ This ADR does not:
214
+
215
+ - provide restart-spanning callback delivery;
216
+ - provide arbitrary callback-side-effect deduplication;
217
+ - recover caller Tasks;
218
+ - introduce an Application outbox inside Persistence;
219
+ - change Handoff routing semantics;
220
+ - change cross-process ownership guarantees.
@@ -0,0 +1,235 @@
1
+ # ADR-030: Agent Handoff Domain and Durable Responsibility
2
+
3
+ ## Status
4
+
5
+ Accepted. V2 revision 2, 2026-09-06.
6
+
7
+ User approval covers the V2 boundary and the five recovery-contract clarifications.
8
+ Acceptance is design authority; it is not a claim of repository integration or test success.
9
+
10
+ ## Date
11
+
12
+ 2026-09-06
13
+
14
+ ## Partially supersedes
15
+
16
+ `016-semantic-multi-agent-handoff` for:
17
+
18
+ - namespace/domain placement of Handoff;
19
+ - Runtime-local-only active responsibility;
20
+ - allowance for independent Source/Target Persistence domains in the durable
21
+ Handoff path; and
22
+ - the public Handoff Runner namespace.
23
+
24
+ ADR-016 remains the historical rationale and remains authoritative for the
25
+ Source-to-Target semantic transfer model, HandoffPolicy category semantics,
26
+ Context dependency grouping, immutable transferred Context, Target ContextPolicy
27
+ ownership, and provenance rules except where this ADR explicitly changes them.
28
+
29
+ ## Context
30
+
31
+ ADR-016 intentionally made active Handoff responsibility Runtime-local. Process
32
+ reset therefore restarted responsibility at the main Agent.
33
+
34
+ That behavior is insufficient for a framework-owned Handoff abstraction once the
35
+ Source execution has durably committed `:handed_off`: process loss must not force
36
+ Source semantic work to run again merely to rediscover the Target.
37
+
38
+ This is a framework semantic-routing concern, not an Application callback concern.
39
+ ADR-029 therefore does not make Handoff durability depend on restart-spanning
40
+ notification delivery.
41
+
42
+ ## Decision
43
+
44
+ ### 1. Handoff moves to the Agent domain
45
+
46
+ Public types move as a clean break:
47
+
48
+ ```text
49
+ Phronomy::MultiAgent::Handoff
50
+ -> Phronomy::Agent::Handoff
51
+
52
+ Phronomy::MultiAgent::HandoffPolicy
53
+ -> Phronomy::Agent::HandoffPolicy
54
+
55
+ Phronomy::MultiAgent::Runner
56
+ -> Phronomy::Agent::HandoffRunner
57
+ ```
58
+
59
+ Handoff-specific private types move under the Agent Handoff implementation
60
+ boundary.
61
+
62
+ No compatibility alias is required by this ADR.
63
+
64
+ ### 2. `main_agent.agent_id` is the durable routing anchor
65
+
66
+ No generic coordination/thread/session identity is introduced.
67
+
68
+ ```text
69
+ main_agent.agent_id
70
+ = durable Handoff routing anchor
71
+ ```
72
+
73
+ One durable HandoffState is keyed by that identity.
74
+
75
+ ### 3. Persistence adds `handoff_states`
76
+
77
+ The durable state contains semantic routing facts only:
78
+
79
+ ```text
80
+ main_agent_id
81
+ handoff_revision
82
+ active_agent_id
83
+ active_handoff_context_ref
84
+ phase
85
+ pending_source_execution_id
86
+ pending_target_execution_id
87
+ created_at
88
+ updated_at
89
+ metadata
90
+ ```
91
+
92
+ It never stores Agent instances, Agent classes, Procs, HandoffPolicy objects,
93
+ Tasks, FSMSessions or EventLoop routing state.
94
+
95
+ ### 4. One durable Handoff graph uses one Persistence domain
96
+
97
+ The main/source/target Agents and HandoffState must use the same
98
+ `Phronomy::Persistence` transaction domain.
99
+
100
+ A graph requiring a distributed transaction across independent Persistence
101
+ domains is rejected before semantic work.
102
+
103
+ ### 5. Source terminalization and responsibility transfer are one semantic transaction
104
+
105
+ When a Source chooses a valid Handoff, Phronomy:
106
+
107
+ 1. resolves the current finalized Source Manifest;
108
+ 2. applies HandoffPolicy projection;
109
+ 3. materializes immutable HandoffContext;
110
+ 4. reserves the exact Target `execution_id`;
111
+ 5. commits Source `:handed_off` and HandoffState transfer atomically.
112
+
113
+ The transaction records at least:
114
+
115
+ ```text
116
+ Source AgentExecution -> :handed_off
117
+ Source AgentRoot terminal/idle revision
118
+ Source Journal audit facts
119
+ HandoffContext content reference
120
+ HandoffState.active_agent_id -> Target
121
+ HandoffState.active_handoff_context_ref -> transferred Context
122
+ HandoffState.phase -> target_pending
123
+ HandoffState.pending_source_execution_id -> Source execution_id
124
+ HandoffState.pending_target_execution_id -> reserved Target execution_id
125
+ ```
126
+
127
+ There is no terminal callback-delivery descriptor in this transaction.
128
+
129
+ ### 6. Target execution identity is reserved before Target semantic work
130
+
131
+ Recovery uses the exact reserved Target `execution_id`:
132
+
133
+ ```text
134
+ authoritatively absent after a successful read
135
+ -> establish that exact reserved execution only after admission is confirmed
136
+
137
+ nonterminal
138
+ -> recover that exact execution
139
+
140
+ terminal
141
+ -> consume its durable outcome; never create a replacement execution
142
+ ```
143
+
144
+ The execution ID is semantic Agent identity, not Runtime FSMSession identity.
145
+
146
+ ### 7. Active responsibility survives later turns and process loss
147
+
148
+ When a Handoff turn ends normally at Target B, `active_agent_id` remains B.
149
+
150
+ The next HandoffRunner turn starts at B.
151
+
152
+ After process reset, compatible HandoffRunner wiring loads the same HandoffState
153
+ and again starts/resumes from B rather than reverting to the original main Agent.
154
+
155
+ ### 8. Multi-hop updates the same HandoffState
156
+
157
+ A -> B -> C updates the original main-Agent-anchored HandoffState.
158
+
159
+ No nested generic coordination IDs are created.
160
+
161
+ ### 9. Runtime graph/Policy wiring is Application code
162
+
163
+ HandoffRunner requires the current Application-supplied Handoff graph and Policies
164
+ to reconstruct Runtime behavior.
165
+
166
+ Those Ruby objects are never persisted.
167
+
168
+ If required wiring is absent or incompatible, recovery fails closed instead of:
169
+
170
+ - reverting to main Agent;
171
+ - inventing a graph;
172
+ - blindly replaying Source work.
173
+
174
+ ### 10. HandoffContext is durably materializable but not adopted automatically
175
+
176
+ The canonical immutable HandoffContext value is stored in ContentStore and
177
+ referenced by HandoffState/execution metadata.
178
+
179
+ Transferred material remains request-scoped Target Context unless Target
180
+ execution creates its own canonical Journal/Knowledge facts.
181
+
182
+ ### 11. Local Application events are Runtime-only
183
+
184
+ A Source may emit a same-process `:handoff` event.
185
+
186
+ That event is not durable routing authority and is not redelivered after restart.
187
+
188
+ Handoff coordination correctness depends only on durable semantic routing facts.
189
+
190
+ ### 12. Recovery evidence, compatibility and cancellation
191
+
192
+ Apply [RC-01 through RC-05](../design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md).
193
+ Read failures/unknown commit outcomes must not be treated as Target absence.
194
+ Readback reconciles the same reserved execution and transfer facts; admission
195
+ races use existing atomic admission/CAS, never a replacement Target ID.
196
+
197
+ Before continuation, check main/active/Target identities, current required graph
198
+ connections, declared definition compatibility and the same Persistence instance.
199
+ Absent an existing explicit migration/compatibility contract, definition id/version
200
+ must match. Current wiring never reprojects committed HandoffContext. Proc/code
201
+ hashing and automatic semantic code-compatibility detection are not introduced.
202
+
203
+ Result reads follow the specified run's recorded Target, not an unrelated later
204
+ turn's latest active result. Continuation wiring is not required merely to read
205
+ stored status/canonical results.
206
+
207
+ After transfer commit the Source remains handed_off. Observation loss or cancel
208
+ must not roll active responsibility back to main. An explicit cancellation of the
209
+ current turn is routed to that turn's exact reserved Target under existing Agent
210
+ cancellation rules; it does not cancel unrelated/later executions. Preserve the
211
+ facts needed to reconcile cancellation/admission races and process loss.
212
+
213
+ ## Required invariants
214
+
215
+ 1. Handoff is an Agent-domain capability.
216
+ 2. `main_agent.agent_id` is the durable routing anchor.
217
+ 3. Active responsibility survives process loss.
218
+ 4. Source `:handed_off` and durable responsibility transfer cannot diverge.
219
+ 5. Target semantic work never starts without a recoverable reserved execution ID.
220
+ 6. A committed Source Handoff is never blindly replayed.
221
+ 7. Recovery reconstructs fresh Runtime objects.
222
+ 8. Graph/Policy Ruby objects are supplied by Application code, not persisted.
223
+ 9. All durable graph participants share one Persistence domain.
224
+ 10. Application callback delivery is not part of Handoff durability.
225
+
226
+ ## Non-goals
227
+
228
+ This ADR does not:
229
+
230
+ - make arbitrary external effects exactly once;
231
+ - provide restart-spanning local Handoff callback delivery;
232
+ - add distributed transactions across Persistence domains;
233
+ - add generic coordination/session/thread identity;
234
+ - persist HandoffPolicy/Application code;
235
+ - merge Source and Target Agent state.