phronomy 0.22.0 → 0.23.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +3 -4
  3. data/CHANGELOG.md +200 -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 +152 -0
  15. data/docs/architecture/persistence.md +175 -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 +82 -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 +112 -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/README.md +172 -0
  45. data/docs/features.md +31 -11
  46. data/docs/getting-started.md +77 -45
  47. data/docs/migrations/0.19.md +14 -7
  48. data/docs/migrations/0.22.md +390 -0
  49. data/docs/persistence-backends.md +88 -38
  50. data/docs/runtime-and-concurrency.md +227 -33
  51. data/examples/README.md +13 -0
  52. data/lib/phronomy/agent/agent_execution.rb +19 -15
  53. data/lib/phronomy/agent/agent_invocation.rb +288 -93
  54. data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
  55. data/lib/phronomy/agent/agent_root.rb +3 -3
  56. data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
  57. data/lib/phronomy/agent/async_event_api.rb +145 -72
  58. data/lib/phronomy/agent/base.rb +388 -181
  59. data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
  60. data/lib/phronomy/agent/context_assembler.rb +437 -178
  61. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  62. data/lib/phronomy/agent/context_plan.rb +18 -13
  63. data/lib/phronomy/agent/context_plan_validator.rb +246 -88
  64. data/lib/phronomy/agent/context_policies/default.rb +123 -34
  65. data/lib/phronomy/agent/context_policy.rb +109 -3
  66. data/lib/phronomy/agent/context_policy_input.rb +244 -0
  67. data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
  68. data/lib/phronomy/agent/execution_coordinator.rb +1975 -587
  69. data/lib/phronomy/agent/journal_record.rb +17 -4
  70. data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
  71. data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
  72. data/lib/phronomy/agent/llm_operation_result.rb +12 -7
  73. data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
  74. data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
  75. data/lib/phronomy/agent/recovery_coordinator/continuation.rb +271 -0
  76. data/lib/phronomy/agent/recovery_coordinator/installation.rb +427 -0
  77. data/lib/phronomy/agent/recovery_coordinator/resolution.rb +635 -0
  78. data/lib/phronomy/agent/recovery_coordinator.rb +211 -0
  79. data/lib/phronomy/agent/recovery_support.rb +512 -0
  80. data/lib/phronomy/agent/ruby_llm_materializer.rb +22 -13
  81. data/lib/phronomy/agent/selection/candidate.rb +53 -0
  82. data/lib/phronomy/agent/selection/constraint.rb +49 -0
  83. data/lib/phronomy/agent/shared_state.rb +38 -1
  84. data/lib/phronomy/agent/tool_approval_request.rb +33 -5
  85. data/lib/phronomy/agent/tool_definition_set.rb +49 -3
  86. data/lib/phronomy/agent/tool_invocation.rb +336 -102
  87. data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
  88. data/lib/phronomy/agent.rb +20 -2
  89. data/lib/phronomy/agent_already_exists_error.rb +5 -0
  90. data/lib/phronomy/agent_purged_error.rb +5 -0
  91. data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
  92. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
  93. data/lib/phronomy/engine/event_loop.rb +622 -63
  94. data/lib/phronomy/engine/fsm_session.rb +194 -21
  95. data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
  96. data/lib/phronomy/engine/runtime.rb +77 -20
  97. data/lib/phronomy/generator_verifier.rb +12 -14
  98. data/lib/phronomy/invocation_context.rb +9 -29
  99. data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
  100. data/lib/phronomy/multi_agent/coordination_state.rb +18 -0
  101. data/lib/phronomy/multi_agent/coordinator.rb +154 -0
  102. data/lib/phronomy/multi_agent/execution_coordinator.rb +116 -0
  103. data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
  104. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
  105. data/lib/phronomy/multi_agent/handoff.rb +24 -45
  106. data/lib/phronomy/multi_agent/handoff_capability_factory.rb +87 -0
  107. data/lib/phronomy/multi_agent/handoff_context.rb +95 -0
  108. data/lib/phronomy/multi_agent/handoff_policy.rb +137 -0
  109. data/lib/phronomy/multi_agent/handoff_projection.rb +191 -0
  110. data/lib/phronomy/multi_agent/handoff_request.rb +45 -0
  111. data/lib/phronomy/multi_agent/orchestrator.rb +12 -15
  112. data/lib/phronomy/multi_agent/runner.rb +98 -0
  113. data/lib/phronomy/persistence/durable_codec.rb +646 -0
  114. data/lib/phronomy/persistence/durable_record.rb +117 -0
  115. data/lib/phronomy/persistence/in_memory.rb +210 -134
  116. data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
  117. data/lib/phronomy/persistence/repository_facades.rb +316 -0
  118. data/lib/phronomy/persistence.rb +81 -41
  119. data/lib/phronomy/recovery.rb +186 -0
  120. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  121. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +1 -1
  122. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
  123. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
  124. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  125. data/lib/phronomy/tracing/automatic.rb +176 -0
  126. data/lib/phronomy/tracing/base.rb +11 -2
  127. data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
  128. data/lib/phronomy/version.rb +1 -1
  129. data/lib/phronomy/workflow.rb +3 -6
  130. data/lib/phronomy/workflow_context.rb +14 -5
  131. data/lib/phronomy/workflow_recovery.rb +123 -0
  132. data/lib/phronomy/workflow_runner.rb +468 -256
  133. data/lib/phronomy.rb +6 -0
  134. data/scripts/api_snapshot.rb +12 -0
  135. data/sig/phronomy/agent.rbs +209 -7
  136. data/sig/phronomy/multi_agent.rbs +39 -0
  137. data/sig/phronomy/persistence.rbs +62 -4
  138. data/sig/phronomy/runtime.rbs +1 -4
  139. data/sig/phronomy/workflow.rbs +2 -2
  140. data/sig/phronomy.rbs +10 -0
  141. metadata +65 -17
  142. data/examples/workflows/agent_event_mapping.rb +0 -101
  143. data/examples/workflows/generic_task_event_mapping.rb +0 -66
  144. data/lib/phronomy/agent/activation_registry.rb +0 -28
  145. data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
  146. data/lib/phronomy/agent/context_candidate.rb +0 -47
  147. data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
  148. data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
  149. data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
  150. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
  151. data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
  152. data/lib/phronomy/agent/context_policy_registry.rb +0 -46
  153. data/lib/phronomy/agent/context_request.rb +0 -35
  154. data/lib/phronomy/agent/context_selection_unit.rb +0 -38
  155. data/lib/phronomy/agent/derived_content_spec.rb +0 -34
  156. data/lib/phronomy/agent/runner.rb +0 -97
@@ -0,0 +1,390 @@
1
+ # Migration to 0.22
2
+
3
+ Phronomy 0.22 includes clean-break Agent definition identity normalization and
4
+ semantic Multi-Agent Handoff changes.
5
+
6
+ ## Agent definition identity and revision
7
+
8
+ Agent instance identity and Agent definition identity are separate:
9
+
10
+ ```text
11
+ Agent instance agent_id
12
+ Agent definition lineage agent_definition_id
13
+ Agent definition revision agent_definition_version
14
+ ```
15
+
16
+ A named concrete Agent may now use its fully-qualified Ruby class name as the
17
+ default definition lineage:
18
+
19
+ ```ruby
20
+ module Billing
21
+ class Agent < Phronomy::Agent::Base
22
+ agent_definition version: 2
23
+ end
24
+ end
25
+ ```
26
+
27
+ The definition ID above is `Billing::Agent`. An explicit stable lineage remains
28
+ supported:
29
+
30
+ ```ruby
31
+ agent_definition id: "billing-agent", version: 2
32
+ ```
33
+
34
+ Concrete subclasses do not implicitly inherit a parent's definition revision.
35
+ Anonymous Agent subclasses cannot derive a fully-qualified class-name lineage
36
+ and therefore require explicit `id:` when declaring their definition identity.
37
+
38
+ The semantic revision field is a clean rename:
39
+
40
+ ```text
41
+ AgentRoot#definition_version
42
+ -> AgentRoot#agent_definition_version
43
+
44
+ AgentRoot durable key "definition_version"
45
+ -> "agent_definition_version"
46
+
47
+ LLMInputBuildContext#definition_version
48
+ -> #agent_definition_version
49
+ ```
50
+
51
+ No deprecated field alias or legacy AgentRoot backward decoder is provided.
52
+ Persisted AgentRoot hashes that contain only `"definition_version"` must be
53
+ migrated by the application before they are loaded by 0.22. Phronomy does not
54
+ rewrite existing AgentRoot data automatically in this change.
55
+
56
+ Existing `agent_id` values keep their identity semantics. Agent loading still
57
+ uses the current exact-match policy for `(agent_definition_id,
58
+ agent_definition_version)`. This semantic definition revision is not a
59
+ Persistence codec/schema/format version.
60
+
61
+ ## Multi-Agent Handoff
62
+
63
+ Multi-Agent Handoff changes from Tool-result/sentinel routing to an explicit
64
+ framework control-transfer model. This is also a clean break: compatibility
65
+ aliases for the removed Runner and Handoff routing API are intentionally not
66
+ provided.
67
+
68
+ ## Runner API
69
+
70
+ Before:
71
+
72
+ ```ruby
73
+ runner = Phronomy::Agent::Runner.new(
74
+ agents: [triage, billing],
75
+ routes: {triage => [billing]}
76
+ )
77
+ ```
78
+
79
+ After:
80
+
81
+ ```ruby
82
+ handoff = Phronomy::MultiAgent::Handoff.new(
83
+ source_agent: triage,
84
+ target_agent: billing
85
+ )
86
+
87
+ runner = Phronomy::MultiAgent::Runner.new(
88
+ main_agent: triage,
89
+ handoffs: [handoff]
90
+ )
91
+ ```
92
+
93
+ Removed public surface:
94
+
95
+ - `Phronomy::Agent::Runner`
96
+ - Runner constructor keywords `agents:` and `routes:`
97
+ - Runner `#agents`
98
+ - sentinel-based routing internals as a public compatibility boundary
99
+
100
+ ## Handoff model
101
+
102
+ `Phronomy::MultiAgent::Handoff` now represents one semantic Source-to-Target
103
+ edge between concrete live Agent instances:
104
+
105
+ ```ruby
106
+ handoff = Phronomy::MultiAgent::Handoff.new(
107
+ source_agent: triage,
108
+ target_agent: billing,
109
+ description: "Transfer billing responsibility"
110
+ )
111
+ ```
112
+
113
+ Generated Tool names are transport details only. Applications must not persist,
114
+ inspect, or route by those generated names. `Handoff#tool_name`,
115
+ `Handoff#sentinel`, and `Handoff#to_tool_class` are not part of the new API.
116
+
117
+ A Handoff request contains a required `responsibility` string. It is intercepted
118
+ before ordinary Tool execution, so no synthetic Tool result is inserted merely
119
+ to signal routing.
120
+
121
+ One Provider outcome may contain either ordinary Tool Calls or one Handoff
122
+ request. Mixing Handoff with ordinary Tool Calls, or returning multiple Handoff
123
+ requests in one Provider outcome, raises `Phronomy::HandoffError`.
124
+
125
+ ## Handoff Policy and Context transfer
126
+
127
+ `Phronomy::MultiAgent::HandoffPolicy` controls what may cross the Agent ownership
128
+ boundary. The initial categories are:
129
+
130
+ - `current_request`
131
+ - `history`
132
+ - `knowledge`
133
+ - `tool_exchanges`
134
+
135
+ Each category is `required`, `forbidden`, or `selectable`. Selectable categories
136
+ have an Application-defined default of include or exclude. Required and forbidden
137
+ categories are not exposed to the Source Agent as switches.
138
+
139
+ The default policy requires the current request, includes history and Tool
140
+ exchanges by default, and keeps persistent Knowledge opt-in.
141
+
142
+ Transferred material is immutable request-scoped Handoff Context. It is not
143
+ adopted automatically into the Target Journal or Target Knowledge. Provenance is
144
+ preserved across multi-hop transfers.
145
+
146
+ Handoff Policy and Context Policy have different authority:
147
+
148
+ ```text
149
+ Handoff Policy
150
+ -> decides what may cross Source -> Target
151
+
152
+ Target Context Policy
153
+ -> decides what is used for one Target LLM call
154
+ ```
155
+
156
+ Transferred items therefore enter Target context assembly as ordinary selectable
157
+ Context candidates before Target Context Policy runs.
158
+
159
+ ## Active Agent continuity
160
+
161
+ The `main_agent` instance anchors one Runtime-local coordination lifetime. After
162
+ a successful Handoff, the Target remains active for later user turns while the
163
+ same main Agent instance and Runtime remain alive. Recreating only the
164
+ `MultiAgent::Runner` facade does not reset that state.
165
+
166
+ This active-Agent ownership is deliberately not durable. A process or Runtime
167
+ reset starts a new coordination lifetime at `main_agent`; Phronomy does not infer
168
+ active-Agent continuation from historical Handoff audit records.
169
+
170
+ ## Source execution outcome
171
+
172
+ A Source Agent execution that transfers responsibility terminates explicitly as
173
+ `handed_off`. The complete Provider outcome is retained for audit, but the
174
+ Handoff control Tool Call is not made a future conversational Context candidate
175
+ and no ordinary Handoff Tool result is generated.
176
+
177
+ ## Context Policy clean break (ACS-04)
178
+
179
+ ACS-04 replaces the intermediate Context selection composition API with the
180
+ final four-category Context Policy SPI. Application-defined policies receive an
181
+ immutable `Phronomy::Agent::ContextPolicyInput` and return an immutable
182
+ `Phronomy::Agent::ContextPlan`, each organized as `instruction`, `knowledge`,
183
+ `tools`, and grouped `conversation`.
184
+
185
+ Agent classes bind one Application-constructed Policy instance:
186
+
187
+ ```ruby
188
+ class ResearchAgent < Phronomy::Agent::Base
189
+ context_policy RESEARCH_CONTEXT_POLICY
190
+ end
191
+ ```
192
+
193
+ The following intermediate APIs/internals are removed without compatibility
194
+ aliases:
195
+
196
+ - `ContextRequest`
197
+ - `ContextPolicyDescriptor` / `ContextPolicyRegistry`
198
+ - `DerivedContentSpec` and `ContextPlan#derived_contents`
199
+ - `ContextPlan#selected_unit_ids`, `#ordering_hints`, `#policy_descriptor`
200
+ - `Selection::Unit` / `Selection::Validator`
201
+ - `DependencyAwareUnitBuilder`
202
+ - `RequiredContextResolver`
203
+ - `RecentFirstSelector`
204
+ - `TokenBudgetPacker`
205
+ - public Policy `parts` composition
206
+
207
+ `Selection::Candidate` and `Selection::Constraint` remain internal normalization
208
+ mechanisms while `ContextPolicyInput` is built; they are not the Application
209
+ Policy SPI.
210
+
211
+ ContextPolicy is Application code/runtime wiring rather than durable Agent
212
+ state. The Policy object/descriptor is not persisted or reconstructed. A
213
+ finalized `LLMInputManifest` remains the Recovery authority for an already-fixed
214
+ Provider input.
215
+
216
+ No Phronomy Persistence transaction spans `ContextPolicy#call`. Context is
217
+ prepared from an immutable snapshot outside the transaction, then the durable
218
+ base/revision is revalidated before the short Manifest/execution commit.
219
+
220
+ The Context assembly policy version is now `8` because the semantic input/Plan
221
+ contract, Tool subset handling, Policy-generated current-call material, and
222
+ transaction boundary changed. Persisted Manifest readers that validate this
223
+ version should accept version 8.
224
+
225
+ ## Workflow instance identity
226
+
227
+ Workflow durable identity is a clean rename:
228
+
229
+ ```text
230
+ thread_id
231
+ -> workflow_instance_id
232
+ ```
233
+
234
+ The new name identifies one logical Workflow instance and is also the logical
235
+ key supplied to `Persistence#workflow_states`.
236
+
237
+ Application code must change Workflow invocation configuration and live signals:
238
+
239
+ ```ruby
240
+ workflow.invoke(
241
+ input,
242
+ config: {workflow_instance_id: "order-123"}
243
+ )
244
+
245
+ workflow.signal(
246
+ workflow_instance_id: "order-123",
247
+ event: :external_result,
248
+ payload: result
249
+ )
250
+ ```
251
+
252
+ `WorkflowContext#thread_id` is removed and replaced by
253
+ `WorkflowContext#workflow_instance_id`. No deprecated alias is provided.
254
+ Supplying the old Workflow config key `:thread_id` raises `ArgumentError`
255
+ instead of silently allocating a new Workflow identity.
256
+
257
+ `workflow_instance_id` is framework-owned WorkflowContext metadata and is now a
258
+ reserved application field name. If an existing context declares
259
+ `field :workflow_instance_id`, rename that application field before upgrading;
260
+ 0.22 rejects the declaration with `ArgumentError`.
261
+
262
+ The identifier value itself does not change. Existing durable key value
263
+ `"order-123"` remains `"order-123"` and is interpreted as the same
264
+ `workflow_instance_id`. Custom Persistence backends must rename the logical
265
+ parameter in their Workflow repository SPI, but a database adapter does not need
266
+ to rename a physical column solely for this migration.
267
+
268
+ `InvocationContext#thread_id` is not used as an implicit Workflow identity after
269
+ this change. CG-01 established the Workflow identity boundary; the generic Agent
270
+ invocation identity cleanup described below completes the separate Agent-side
271
+ removal.
272
+
273
+ ## Generic Agent invocation identity
274
+
275
+ Agent-side generic invocation correlation is removed as a clean break.
276
+
277
+ Removed public surface:
278
+
279
+ ```text
280
+ InvocationContext#thread_id
281
+ InvocationContext#session_id
282
+ Agent#invoke(thread_id:)
283
+ Agent#invoke_async(thread_id:)
284
+ Agent#stream(thread_id:)
285
+ Agent#stream_async(thread_id:)
286
+ MultiAgent::Orchestrator#fan_out(thread_id:)
287
+ MultiAgent::Orchestrator#fan_out_async(thread_id:)
288
+ MultiAgent::Orchestrator#subagent(thread_id:)
289
+ ```
290
+
291
+ No `correlation_id`, `conversation_id`, or `application_session_id` replacement
292
+ is introduced. Applications that need correlation should keep it in their own
293
+ tracing/observability metadata or use the purpose-specific domain identifier
294
+ that owns the relevant lifecycle.
295
+
296
+ `InvocationContext#task_id` and `#parent_task_id` remain tracing/observability
297
+ identifiers. They are not Agent, Workflow, Tool, or Runtime routing identity.
298
+
299
+ Agent invocation config also rejects the removed `thread_id` and `session_id`
300
+ keys so the old generic identity cannot survive through an untyped config
301
+ backdoor.
302
+
303
+ The later CG-03b / ACS-10 Runtime foundation removes the shared generic
304
+ FSMSession graph-identity metadata bridge. Agent, Tool, and Multi-Agent routing
305
+ now uses concrete session-local sinks; Workflow metadata is passed explicitly as
306
+ `workflow_instance_id` while its admission-owner redesign remains ACS-13 work.
307
+
308
+ `JournalRecord#correlation_id` is also removed from the current canonical
309
+ Journal model and `JournalRecord#to_h` representation. New approval and
310
+ execution Journal records do not populate a generic correlation field.
311
+
312
+ Existing durable Journal Hashes are not rewritten merely to remove the legacy
313
+ key. `JournalRecord.from_h` accepts a legacy `correlation_id` key and ignores it
314
+ while restoring the current canonical fields. The same rule applies to legacy
315
+ Journal records nested in `AgentExecution#working_records`, because
316
+ `AgentExecution.from_h` delegates their reconstruction to `JournalRecord.from_h`.
317
+
318
+ Custom Persistence backends should stop producing `correlation_id` in new
319
+ canonical Journal representations. A backend does not need to rewrite every
320
+ existing stored row or drop a physical legacy column solely for this migration;
321
+ old values may remain backend-internal while legacy records are decoded into the
322
+ current correlation-free domain model.
323
+
324
+ ## Agent execution parent identity
325
+
326
+ Tool and approval work now identifies its owning Agent execution with
327
+ `execution_id`. The old `agent_invocation_id` terminology is not retained as a
328
+ deprecated alias.
329
+
330
+ Application-facing changes:
331
+
332
+ ```text
333
+ ToolApprovalRequest#agent_invocation_id
334
+ -> ToolApprovalRequest#execution_id
335
+
336
+ ToolApprovalRequest#to_h[:agent_invocation_id]
337
+ -> ToolApprovalRequest#to_h[:execution_id]
338
+
339
+ ApprovalEvaluationRequest#agent_invocation_id
340
+ -> ApprovalEvaluationRequest#execution_id
341
+ ```
342
+
343
+ ACS-11 also makes Tool approval-policy input a value-only worker-boundary
344
+ contract. The live-object accessors are removed as a clean break:
345
+
346
+ ```text
347
+ ApprovalEvaluationRequest#agent
348
+ -> #agent_id / #agent_definition_id / #agent_definition_version
349
+
350
+ ApprovalEvaluationRequest#tool
351
+ -> #tool_name / #tool_schema / #origin / #metadata
352
+ ```
353
+
354
+ The authorization worker captures Tool approval behavior before offload and
355
+ does not receive a live Agent, Tool, or ToolInvocation object. Application
356
+ approval/facts/requirement callables remain supported as Application-owned
357
+ behavior handles.
358
+
359
+ `tool_invocation_id`, provider `tool_call_id`, and the approval request's own
360
+ `id` remain separate purpose-specific identifiers.
361
+
362
+ Agent invocation config also rejects `agent_invocation_id` so application code
363
+ cannot continue controlling the temporary Runtime routing identifier through an
364
+ untyped config key.
365
+
366
+ Suspended `AgentExecution` records created by an older version may contain
367
+ `approval_request["agent_invocation_id"]`. `AgentExecution.from_h` accepts that
368
+ legacy embedded shape, discards the old Runtime-oriented value, and derives the
369
+ current logical parent from the enclosing execution's own `execution_id`. It
370
+ does not reinterpret the historical `agent_invocation_id` value as an execution
371
+ ID.
372
+
373
+ Existing content-addressed approval audit bodies are not rewritten and may
374
+ retain their historical key. New approval request writes use `execution_id`.
375
+ This targeted compatibility handling does not define the general Persistence
376
+ schema/versioning policy.
377
+
378
+ CG-03b / ACS-10 now advances the Runtime routing half. `AgentInvocation` has no
379
+ independent routing ID or duplicate `session_id`; `ToolInvocation#id` remains
380
+ only the Tool lifecycle identity; and the private `parent_agent_invocation_id`
381
+ routing carrier is removed. Agent, Tool, and Multi-Agent concrete sessions use
382
+ fresh FSMSession IDs and session-local event sinks. Provider completion is routed
383
+ to the owning FSMSession and carries `llm_call_id` before any live result apply.
384
+
385
+ ACS-11 removes `AgentExecutionActivation` / `ActivationRegistry`: EventLoop is the
386
+ single writer of Phronomy-managed live Agent execution state, and operation-
387
+ specific Offload results are validated against current FSMSession/FSM state and
388
+ semantic IDs before apply. Together with ADR-023, this closes the Agent/Tool
389
+ CG-03 Runtime-foundation gap. Workflow opaque admission ownership remains
390
+ separate ACS-13 work.
@@ -23,10 +23,13 @@ Phronomy::Persistence synchronous Backend SPI
23
23
  Database / durable storage
24
24
  ```
25
25
 
26
- Persistence does not own live execution state. In particular, a backend must not
27
- persist or reconstruct the following as part of this SPI:
26
+ Persistence does not own live Agent identity, top-level Runtime admission, or
27
+ live execution state. In particular, a backend must not persist or reconstruct
28
+ the following as part of this SPI:
28
29
 
29
- - `AgentExecutionActivation`;
30
+ - Runtime Agent ownership-registry entries;
31
+ - EventLoop Agent top-level admission entries;
32
+ - EventLoop Agent execution-directory entries;
30
33
  - `AgentInvocation`;
31
34
  - `FSMSession`;
32
35
  - `Task` or callbacks;
@@ -96,8 +99,9 @@ failure rather than pretending the outcome is known.
96
99
 
97
100
  ### `atomic_admission`
98
101
 
99
- This capability refers to **Agent execution admission**, not Workflow distributed
100
- locking.
102
+ This capability is a **durable Agent execution integrity defense**. It is not the
103
+ primary same-process Agent ownership/admission mechanism and it is not Workflow
104
+ distributed locking.
101
105
 
102
106
  For one Agent, `executions.create_active` must atomically guarantee both:
103
107
 
@@ -110,9 +114,15 @@ no active/suspended execution already exists for agent_id
110
114
  A conflict with an existing active/suspended execution raises
111
115
  `Phronomy::AgentBusyError`.
112
116
 
113
- Workflow admission remains Runtime/process-local. Cross-process Workflow
114
- lease/fencing is an application/distributed-coordination concern and is not part
115
- of this Backend SPI.
117
+ Within one process, Runtime/EventLoop admission is acquired before the initial
118
+ Persistence operation and is the primary competing-execution exclusion
119
+ mechanism. `atomic_admission` remains required as the durable second line of
120
+ defense against stale paths, durable conflicts, and unsupported cross-process
121
+ races. It must not be removed merely because Runtime admission exists.
122
+
123
+ Workflow admission remains Runtime/process-local. Cross-process Agent or
124
+ Workflow ownership/lease/fencing is a separate distributed-coordination concern
125
+ and is not part of this Backend SPI.
116
126
 
117
127
  ### `optimistic_revision`
118
128
 
@@ -137,8 +147,10 @@ identity, duplicate-ID, or compare-and-swap conflicts.
137
147
 
138
148
  ### `Phronomy::AgentBusyError`
139
149
 
140
- An Agent already has an active or suspended execution and another execution
141
- cannot be admitted.
150
+ A durable nonterminal Agent execution already exists and another durable
151
+ execution record cannot be established. Phronomy also uses the same public error
152
+ for a competing process-local top-level request rejected by Runtime/EventLoop
153
+ before the backend is called.
142
154
 
143
155
  ### `Phronomy::Persistence::SerializationError`
144
156
 
@@ -202,6 +214,11 @@ def delete(agent_id)
202
214
  - returns `Phronomy::Agent::AgentRoot`, not a raw database Hash;
203
215
  - raises `NotFoundError` when missing.
204
216
 
217
+ These repository operations are durable-storage primitives. The higher-level
218
+ `Agent::Base.load` API first consults Runtime's process-local live ownership
219
+ registry and does not call the repository when the requested Agent is already
220
+ live.
221
+
205
222
  `save` atomically checks:
206
223
 
207
224
  ```text
@@ -257,8 +274,11 @@ def delete_for_agent(agent_id)
257
274
  def assert_idle!(agent_id)
258
275
  ```
259
276
 
260
- `create_active` performs atomic Agent admission. A duplicate `execution_id`
261
- raises `ConflictError`; an already busy Agent raises `AgentBusyError`.
277
+ `create_active` performs atomic **durable** Agent execution admission. A duplicate
278
+ `execution_id` raises `ConflictError`; an already busy Agent raises
279
+ `AgentBusyError`. Runtime/EventLoop has already acquired the process-local
280
+ logical execution slot on the normal Phronomy path before this repository method
281
+ runs.
262
282
 
263
283
  `load` returns `Phronomy::Agent::AgentExecution`, not a raw database Hash, and
264
284
  raises `NotFoundError` when missing.
@@ -278,17 +298,19 @@ A failed precondition raises `ConflictError`.
278
298
  `assert_idle!` is used inside transactions before Agent context/Knowledge changes
279
299
  and destructive operations. It must raise `AgentBusyError` if an active/suspended
280
300
  execution exists. A SQL implementation must make this check part of a consistency
281
- boundary that cannot race with Agent execution admission; a best-effort SELECT
282
- outside the transaction is not sufficient.
301
+ boundary that cannot race with durable Agent execution admission; a best-effort
302
+ SELECT outside the transaction is not sufficient. Process-local Runtime
303
+ admission is an additional upstream coordination layer, not a replacement for
304
+ this durable check.
283
305
 
284
306
  ## Workflow states repository
285
307
 
286
308
  Required surface:
287
309
 
288
310
  ```ruby
289
- def load(thread_id)
290
- def save(thread_id, expected_revision:, snapshot:)
291
- def delete(thread_id, expected_revision:)
311
+ def load(workflow_instance_id)
312
+ def save(workflow_instance_id, expected_revision:, snapshot:)
313
+ def delete(workflow_instance_id, expected_revision:)
292
314
  ```
293
315
 
294
316
  `load` returns `nil` when no row exists. Otherwise it returns a Hash containing a
@@ -315,6 +337,37 @@ snapshot and revision. String or Symbol Hash keys are accepted by Phronomy:
315
337
 
316
338
  Caller mutation of a loaded snapshot must not mutate durable storage.
317
339
 
340
+ ### Workflow Runtime admission and terminal-save outcome
341
+
342
+ Same-process Workflow admission is owned by Runtime/EventLoop, not by this
343
+ repository. EventLoop acquires an opaque owner token for `workflow_instance_id`
344
+ before mutable durable load/hydration, then binds a separately generated
345
+ `fsm_session_id` only after the concrete FSMSession is constructed.
346
+
347
+ A durable Workflow terminal/halt snapshot is saved through OffloadPool while the
348
+ owning FSMSession remains nonterminal. The backend still implements only the
349
+ synchronous `save` contract above; it does not post Runtime events or decide FSM
350
+ state.
351
+
352
+ Phronomy interprets terminal-save results by semantic certainty:
353
+
354
+ ```text
355
+ known successful save
356
+ -> durable barrier may be crossed
357
+
358
+ portable known failure / known not committed
359
+ -> barrier remains closed; Workflow error path
360
+
361
+ arbitrary storage/transport failure whose commit outcome is not established
362
+ -> outcome unknown; barrier remains closed and Runtime fails closed
363
+ ```
364
+
365
+ This distinction is independent of physical topology. A local backend can have
366
+ an uncertain outcome, and a remote backend can return a definite optimistic
367
+ conflict. Backends must therefore preserve meaningful portable errors when the
368
+ contract establishes them, and must surface other storage/transport failures
369
+ honestly rather than converting them into `ConflictError`.
370
+
318
371
  ### Workflow value serialization
319
372
 
320
373
  `WorkflowContext#to_h` may contain ordinary Ruby application values. The
@@ -414,6 +467,17 @@ Phronomy::Agent::AgentExecution.from_h(hash)
414
467
  String and Symbol top-level keys are accepted by these new execution/call codecs,
415
468
  which permits adapters to use parsed JSON without reimplementing constructors.
416
469
 
470
+ The current canonical `JournalRecord` Hash does not contain `correlation_id`.
471
+ Legacy durable Journal Hashes that still contain that key may be passed to
472
+ `JournalRecord.from_h`; the legacy key is accepted and ignored. Backends must not
473
+ synthesize or populate `correlation_id` for new canonical Journal records, and
474
+ they are not required to eagerly rewrite existing durable rows solely to remove
475
+ the old physical value.
476
+
477
+ This is a targeted migration rule for the removed generic identity field. It does
478
+ not establish a general unknown-field or long-term codec/schema-versioning
479
+ policy.
480
+
417
481
  The canonical Hash representation is the Phronomy/domain boundary. A backend is
418
482
  free to map that representation to normalized SQL columns, JSON, or another
419
483
  storage format internally.
@@ -467,10 +531,12 @@ require wrappers only; the authoritative shared-example implementations live
467
531
  under `lib/phronomy/testing/persistence_contract/` so the core suite and external
468
532
  backends cannot drift through copied definitions.
469
533
 
470
- The generic suite verifies repository behavior, CAS semantics, admission,
471
- mutation isolation, and whole-backend transaction behavior. Database-specific
472
- concurrency/locking mechanisms remain backend integration-test concerns; the SPI
473
- specifies outcomes rather than a particular SQL locking strategy.
534
+ The generic suite verifies repository behavior, CAS semantics, durable execution
535
+ admission, mutation isolation, and whole-backend transaction behavior. Runtime
536
+ same-process ownership/admission is tested separately because it is not a
537
+ Persistence Backend SPI responsibility. Database-specific concurrency/locking
538
+ mechanisms remain backend integration-test concerns; the SPI specifies outcomes
539
+ rather than a particular SQL locking strategy.
474
540
 
475
541
  ## SQL implementation guidance
476
542
 
@@ -481,24 +547,8 @@ may use combinations of:
481
547
  - conditional `UPDATE ... WHERE revision = ?`;
482
548
  - row locks;
483
549
  - serializable/repeatable-read isolation where appropriate;
484
- - partial unique indexes for active Agent execution admission;
550
+ - partial unique indexes for durable active Agent execution admission;
485
551
  - transaction-scoped checks for Agent revision + Journal head.
486
552
 
487
553
  Backend-specific database exceptions should be translated to the Phronomy error
488
554
  contract where their meaning is known.
489
-
490
- ## Explicit non-goals
491
-
492
- This Backend SPI does not provide:
493
-
494
- - durable reconstruction of a lost Agent Activation;
495
- - serialization of Runtime objects;
496
- - cross-process Workflow execution exclusion;
497
- - exactly-once external Tool side effects;
498
- - automatic conflict reload/merge;
499
- - a generic serializer registry for arbitrary Workflow field classes;
500
- - an async Persistence API.
501
-
502
- For the architectural reasons behind these boundaries, see
503
- [ADR-014: Unified Persistence for Durable State](decisions/014-unified-persistence-durable-state.md)
504
- and [Runtime and concurrency](runtime-and-concurrency.md).