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.
- checksums.yaml +4 -4
- data/.mutant.yml +3 -4
- data/CHANGELOG.md +200 -10
- data/CONTRIBUTING.md +81 -9
- data/README.md +15 -6
- data/VERIFY.sh +587 -0
- data/benchmark/bench_agent_invoke.rb +2 -2
- data/benchmark/bench_context_assembler.rb +39 -68
- data/benchmark/bench_regression.rb +2 -2
- data/docs/architecture/agent-context.md +174 -0
- data/docs/architecture/before-llm-input.md +78 -0
- data/docs/architecture/context-management.md +232 -0
- data/docs/architecture/knowledge-and-rag.md +130 -0
- data/docs/architecture/multi-agent-handoff.md +152 -0
- data/docs/architecture/persistence.md +175 -0
- data/docs/architecture/removed/agent-context.md +72 -0
- data/docs/architecture/security-boundaries.md +173 -0
- data/docs/architecture/tracing.md +194 -0
- data/docs/architecture.md +82 -0
- data/docs/archive/design/archived/04_api_design.md +507 -0
- data/docs/archive/design/archived/09_guardrails.md +186 -0
- data/docs/archive/design/archived/17_rails_integration.md +175 -0
- data/docs/archive/design/historical/00_design_philosophy.md +122 -0
- data/docs/archive/design/historical/01_rubyllm_evaluation.md +178 -0
- data/docs/archive/design/historical/06_design_decisions.md +143 -0
- data/docs/changelog/0.14-and-earlier.md +1 -1
- data/docs/decisions/001-rubyllm-as-provider-layer.md +6 -1
- data/docs/decisions/002-workflow-context-immutability.md +26 -1
- data/docs/decisions/006-no-built-in-guardrails.md +2 -1
- data/docs/decisions/012-canonical-execution-log-and-context-policy.md +120 -38
- data/docs/decisions/014-unified-persistence-durable-state.md +9 -2
- data/docs/decisions/016-semantic-multi-agent-handoff.md +112 -0
- data/docs/decisions/017-design-authority-and-adr-governance.md +200 -0
- data/docs/decisions/018-durability-guarantees-and-failure-model.md +488 -0
- data/docs/decisions/019-filter-contract-and-security-boundaries.md +229 -0
- data/docs/decisions/020-canonical-workflow-instance-identity.md +177 -0
- data/docs/decisions/021-generic-agent-invocation-identity-removal.md +119 -0
- data/docs/decisions/022-agent-execution-parent-identity-and-runtime-routing-boundary.md +193 -0
- data/docs/decisions/023-fsm-session-incarnation-identity-and-routing.md +139 -0
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +188 -0
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +249 -0
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +257 -0
- data/docs/decisions/027-llm-adapter-provider-boundary.md +93 -0
- data/docs/decisions/README.md +172 -0
- data/docs/features.md +31 -11
- data/docs/getting-started.md +77 -45
- data/docs/migrations/0.19.md +14 -7
- data/docs/migrations/0.22.md +390 -0
- data/docs/persistence-backends.md +88 -38
- data/docs/runtime-and-concurrency.md +227 -33
- data/examples/README.md +13 -0
- data/lib/phronomy/agent/agent_execution.rb +19 -15
- data/lib/phronomy/agent/agent_invocation.rb +288 -93
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
- data/lib/phronomy/agent/agent_root.rb +3 -3
- data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
- data/lib/phronomy/agent/async_event_api.rb +145 -72
- data/lib/phronomy/agent/base.rb +388 -181
- data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
- data/lib/phronomy/agent/context_assembler.rb +437 -178
- data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
- data/lib/phronomy/agent/context_plan.rb +18 -13
- data/lib/phronomy/agent/context_plan_validator.rb +246 -88
- data/lib/phronomy/agent/context_policies/default.rb +123 -34
- data/lib/phronomy/agent/context_policy.rb +109 -3
- data/lib/phronomy/agent/context_policy_input.rb +244 -0
- data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
- data/lib/phronomy/agent/execution_coordinator.rb +1975 -587
- data/lib/phronomy/agent/journal_record.rb +17 -4
- data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
- data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
- data/lib/phronomy/agent/llm_operation_result.rb +12 -7
- data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
- data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
- data/lib/phronomy/agent/recovery_coordinator/continuation.rb +271 -0
- data/lib/phronomy/agent/recovery_coordinator/installation.rb +427 -0
- data/lib/phronomy/agent/recovery_coordinator/resolution.rb +635 -0
- data/lib/phronomy/agent/recovery_coordinator.rb +211 -0
- data/lib/phronomy/agent/recovery_support.rb +512 -0
- data/lib/phronomy/agent/ruby_llm_materializer.rb +22 -13
- data/lib/phronomy/agent/selection/candidate.rb +53 -0
- data/lib/phronomy/agent/selection/constraint.rb +49 -0
- data/lib/phronomy/agent/shared_state.rb +38 -1
- data/lib/phronomy/agent/tool_approval_request.rb +33 -5
- data/lib/phronomy/agent/tool_definition_set.rb +49 -3
- data/lib/phronomy/agent/tool_invocation.rb +336 -102
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
- data/lib/phronomy/agent.rb +20 -2
- data/lib/phronomy/agent_already_exists_error.rb +5 -0
- data/lib/phronomy/agent_purged_error.rb +5 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
- data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
- data/lib/phronomy/engine/event_loop.rb +622 -63
- data/lib/phronomy/engine/fsm_session.rb +194 -21
- data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
- data/lib/phronomy/engine/runtime.rb +77 -20
- data/lib/phronomy/generator_verifier.rb +12 -14
- data/lib/phronomy/invocation_context.rb +9 -29
- data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
- data/lib/phronomy/multi_agent/coordination_state.rb +18 -0
- data/lib/phronomy/multi_agent/coordinator.rb +154 -0
- data/lib/phronomy/multi_agent/execution_coordinator.rb +116 -0
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
- data/lib/phronomy/multi_agent/handoff.rb +24 -45
- data/lib/phronomy/multi_agent/handoff_capability_factory.rb +87 -0
- data/lib/phronomy/multi_agent/handoff_context.rb +95 -0
- data/lib/phronomy/multi_agent/handoff_policy.rb +137 -0
- data/lib/phronomy/multi_agent/handoff_projection.rb +191 -0
- data/lib/phronomy/multi_agent/handoff_request.rb +45 -0
- data/lib/phronomy/multi_agent/orchestrator.rb +12 -15
- data/lib/phronomy/multi_agent/runner.rb +98 -0
- data/lib/phronomy/persistence/durable_codec.rb +646 -0
- data/lib/phronomy/persistence/durable_record.rb +117 -0
- data/lib/phronomy/persistence/in_memory.rb +210 -134
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
- data/lib/phronomy/persistence/repository_facades.rb +316 -0
- data/lib/phronomy/persistence.rb +81 -41
- data/lib/phronomy/recovery.rb +186 -0
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
- data/lib/phronomy/tracing/automatic.rb +176 -0
- data/lib/phronomy/tracing/base.rb +11 -2
- data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +3 -6
- data/lib/phronomy/workflow_context.rb +14 -5
- data/lib/phronomy/workflow_recovery.rb +123 -0
- data/lib/phronomy/workflow_runner.rb +468 -256
- data/lib/phronomy.rb +6 -0
- data/scripts/api_snapshot.rb +12 -0
- data/sig/phronomy/agent.rbs +209 -7
- data/sig/phronomy/multi_agent.rbs +39 -0
- data/sig/phronomy/persistence.rbs +62 -4
- data/sig/phronomy/runtime.rbs +1 -4
- data/sig/phronomy/workflow.rbs +2 -2
- data/sig/phronomy.rbs +10 -0
- metadata +65 -17
- data/examples/workflows/agent_event_mapping.rb +0 -101
- data/examples/workflows/generic_task_event_mapping.rb +0 -66
- data/lib/phronomy/agent/activation_registry.rb +0 -28
- data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
- data/lib/phronomy/agent/context_candidate.rb +0 -47
- data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
- data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
- data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
- data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
- data/lib/phronomy/agent/context_policy_registry.rb +0 -46
- data/lib/phronomy/agent/context_request.rb +0 -35
- data/lib/phronomy/agent/context_selection_unit.rb +0 -38
- data/lib/phronomy/agent/derived_content_spec.rb +0 -34
- 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
|
|
27
|
-
|
|
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
|
-
-
|
|
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
|
|
100
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
141
|
-
cannot be
|
|
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
|
|
261
|
-
raises `ConflictError`; an already busy Agent raises
|
|
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
|
|
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(
|
|
290
|
-
def save(
|
|
291
|
-
def delete(
|
|
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,
|
|
471
|
-
mutation isolation, and whole-backend transaction behavior.
|
|
472
|
-
|
|
473
|
-
|
|
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).
|