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.
- checksums.yaml +4 -4
- data/.mutant.yml +3 -4
- data/CHANGELOG.md +215 -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 +147 -0
- data/docs/architecture/persistence.md +186 -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 +89 -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 +114 -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/028-preparing-recovery-replay-contract.md +106 -0
- data/docs/decisions/029-semantic-completion-and-application-effect-boundary.md +220 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +235 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +301 -0
- data/docs/decisions/README.md +176 -0
- data/docs/design/durable-semantic-coordination/CHANGELOG_V2_REVISION_2.md +33 -0
- data/docs/design/durable-semantic-coordination/CONTINUATION_DECISION_REFACTOR.md +191 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +862 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +106 -0
- data/docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md +179 -0
- data/docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md +302 -0
- data/docs/features.md +65 -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/migrations/durable-semantic-coordination-v2.md +65 -0
- data/docs/persistence-backends.md +129 -40
- data/docs/runtime-and-concurrency.md +227 -33
- data/examples/README.md +13 -0
- data/lib/phronomy/agent/agent_execution.rb +21 -17
- 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 +162 -72
- data/lib/phronomy/agent/base.rb +416 -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/exact_execution.rb +153 -0
- data/lib/phronomy/agent/execution_cancellation.rb +25 -0
- data/lib/phronomy/agent/execution_coordinator.rb +2417 -572
- data/lib/phronomy/agent/handoff.rb +43 -0
- data/lib/phronomy/agent/handoff_capability_factory.rb +45 -0
- data/lib/phronomy/agent/handoff_context.rb +120 -0
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +143 -0
- data/lib/phronomy/agent/handoff_policy.rb +143 -0
- data/lib/phronomy/agent/handoff_projection.rb +207 -0
- data/lib/phronomy/agent/handoff_request.rb +45 -0
- data/lib/phronomy/agent/handoff_runner.rb +178 -0
- data/lib/phronomy/agent/handoff_state.rb +43 -0
- 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 +174 -0
- data/lib/phronomy/agent/recovery_coordinator/installation.rb +381 -0
- data/lib/phronomy/agent/recovery_coordinator/resolution.rb +511 -0
- data/lib/phronomy/agent/recovery_coordinator.rb +217 -0
- data/lib/phronomy/agent/recovery_support.rb +504 -0
- data/lib/phronomy/agent/ruby_llm_materializer.rb +16 -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 +339 -103
- 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/team_ownership_registry.rb +77 -0
- data/lib/phronomy/engine/runtime.rb +92 -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/durable_subagent_coordinator.rb +134 -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/orchestrator.rb +71 -26
- data/lib/phronomy/multi_agent/team_coordinator.rb +473 -125
- data/lib/phronomy/multi_agent/team_execution.rb +44 -0
- data/lib/phronomy/multi_agent/team_root.rb +41 -0
- data/lib/phronomy/persistence/durable_codec.rb +706 -0
- data/lib/phronomy/persistence/durable_record.rb +117 -0
- data/lib/phronomy/persistence/in_memory.rb +471 -133
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
- data/lib/phronomy/persistence/repository_facades.rb +535 -0
- data/lib/phronomy/persistence.rb +174 -40
- 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 +2 -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/testing/persistence_contract/coordination_repositories.rb +137 -0
- data/lib/phronomy/testing/persistence_contract.rb +5 -0
- data/lib/phronomy/tools/agent.rb +1 -1
- 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/handoff.rbs +41 -0
- data/sig/phronomy/multi_agent.rbs +35 -0
- data/sig/phronomy/persistence.rbs +123 -4
- data/sig/phronomy/runtime.rbs +1 -4
- data/sig/phronomy/workflow.rbs +2 -2
- data/sig/phronomy.rbs +10 -0
- metadata +84 -18
- 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
- data/lib/phronomy/multi_agent/handoff.rb +0 -64
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# ADR 022: Agent Execution Parent Identity and Runtime Routing Boundary
|
|
2
|
+
|
|
3
|
+
**Status**: Accepted
|
|
4
|
+
**Date**: 2026-08-23
|
|
5
|
+
**Related**:
|
|
6
|
+
- [ADR-014](014-unified-persistence-durable-state.md)
|
|
7
|
+
- [ADR-021](021-generic-agent-invocation-identity-removal.md)
|
|
8
|
+
- [ADR-023](023-fsm-session-incarnation-identity-and-routing.md)
|
|
9
|
+
- [ADR-024](024-event-loop-single-writer-agent-runtime.md)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
Phronomy has a canonical durable Agent execution identity, `execution_id`, but
|
|
16
|
+
Tool and approval paths still expose or carry the older
|
|
17
|
+
`agent_invocation_id`/`parent_agent_invocation_id` terminology.
|
|
18
|
+
|
|
19
|
+
Those older values mix two different concerns:
|
|
20
|
+
|
|
21
|
+
1. the logical parent Agent execution; and
|
|
22
|
+
2. the concrete Runtime FSMSession target used to deliver an event.
|
|
23
|
+
|
|
24
|
+
The two lifetimes differ. An Agent execution can survive a suspension while its
|
|
25
|
+
concrete FSMSession incarnation is rebuilt. A Runtime delivery target therefore
|
|
26
|
+
must not become the durable or application-facing parent identity.
|
|
27
|
+
|
|
28
|
+
`AgentInvocation` is the live FSM context of an `AgentExecution`; it is not a
|
|
29
|
+
separate logical/domain entity. `ToolInvocation` has its own Tool lifecycle
|
|
30
|
+
identity and provider Tool Call identity, but its logical parent is the owning
|
|
31
|
+
Agent execution.
|
|
32
|
+
|
|
33
|
+
## Decision
|
|
34
|
+
|
|
35
|
+
The canonical logical parent of Agent-owned Tool and approval work is
|
|
36
|
+
`execution_id`.
|
|
37
|
+
|
|
38
|
+
The current identity relationship is:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
AgentExecution
|
|
42
|
+
execution_id
|
|
43
|
+
|
|
44
|
+
AgentInvocation
|
|
45
|
+
execution_id # parent reference, not an independent identity
|
|
46
|
+
|
|
47
|
+
ToolInvocation
|
|
48
|
+
execution_id # logical parent
|
|
49
|
+
tool_invocation_id # Tool lifecycle identity
|
|
50
|
+
tool_call_id # Provider-originated Tool Call identity
|
|
51
|
+
|
|
52
|
+
ToolApprovalRequest
|
|
53
|
+
approval request id
|
|
54
|
+
execution_id # logical parent
|
|
55
|
+
|
|
56
|
+
ApprovalEvaluationRequest
|
|
57
|
+
execution_id # logical parent
|
|
58
|
+
tool_invocation_id
|
|
59
|
+
tool_call_id
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Application-facing approval surfaces change as a clean break:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
ToolApprovalRequest#agent_invocation_id
|
|
66
|
+
-> ToolApprovalRequest#execution_id
|
|
67
|
+
|
|
68
|
+
ToolApprovalRequest#to_h[:agent_invocation_id]
|
|
69
|
+
-> ToolApprovalRequest#to_h[:execution_id]
|
|
70
|
+
|
|
71
|
+
ApprovalEvaluationRequest#agent_invocation_id
|
|
72
|
+
-> ApprovalEvaluationRequest#execution_id
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
No deprecated alias is retained. Agent invocation configuration also rejects the
|
|
76
|
+
legacy `agent_invocation_id` key instead of allowing application code to control
|
|
77
|
+
a Runtime routing identifier.
|
|
78
|
+
|
|
79
|
+
## Runtime routing is separate
|
|
80
|
+
|
|
81
|
+
CG-03a does not redesign Runtime routing.
|
|
82
|
+
|
|
83
|
+
The implementation may temporarily retain private
|
|
84
|
+
`parent_agent_invocation_id`-named storage as the existing parent FSMSession
|
|
85
|
+
routing carrier until the Runtime foundation is reconciled. That temporary
|
|
86
|
+
carrier:
|
|
87
|
+
|
|
88
|
+
- is not the logical/domain parent;
|
|
89
|
+
- is not application-facing identity;
|
|
90
|
+
- is not a durable field;
|
|
91
|
+
- must not be renamed to `execution_id` and then used as an EventLoop target.
|
|
92
|
+
|
|
93
|
+
CG-03b, integrated with ACS-10/ACS-11, will give each concrete FSMSession its own
|
|
94
|
+
fresh ID and replace domain-object routing reuse with session-local Runtime
|
|
95
|
+
bindings. Rebuilt sessions receive rebuilt bindings. Stale completion must not be
|
|
96
|
+
applied to a new session merely because the logical execution is the same.
|
|
97
|
+
|
|
98
|
+
No long-lived `parent_fsm_session_id` field is introduced on `ToolInvocation`.
|
|
99
|
+
|
|
100
|
+
## Durable approval compatibility
|
|
101
|
+
|
|
102
|
+
Current approval suspension stores `ToolApprovalRequest#to_h` both as referenced
|
|
103
|
+
ContentStore audit content and in `AgentExecution#approval_request`.
|
|
104
|
+
|
|
105
|
+
New canonical approval request representations use `execution_id`.
|
|
106
|
+
|
|
107
|
+
Existing durable `AgentExecution` hashes may contain an embedded approval request
|
|
108
|
+
with `agent_invocation_id`. During `AgentExecution.from_h`, that legacy key is
|
|
109
|
+
discarded and the current logical parent is derived from the enclosing
|
|
110
|
+
`AgentExecution#execution_id`. The old `agent_invocation_id` value is not renamed
|
|
111
|
+
or reinterpreted as an execution ID.
|
|
112
|
+
|
|
113
|
+
Existing content-addressed approval audit bodies are not rewritten. Their bytes
|
|
114
|
+
are historical execution evidence and may retain the legacy field. New writes use
|
|
115
|
+
the current representation.
|
|
116
|
+
|
|
117
|
+
This is a targeted compatibility rule. It does not establish a general durable
|
|
118
|
+
schema/versioning policy, which remains separate persistence work.
|
|
119
|
+
|
|
120
|
+
## Staging
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
CG-03a
|
|
124
|
+
execution_id logical parent
|
|
125
|
+
public approval clean break
|
|
126
|
+
targeted embedded durable-read compatibility
|
|
127
|
+
status: reconciled by the CG-03a change
|
|
128
|
+
|
|
129
|
+
CG-03b
|
|
130
|
+
fresh FSMSession identity
|
|
131
|
+
session-local Runtime routing binding
|
|
132
|
+
stale-session completion rejection
|
|
133
|
+
duplicate AgentInvocation/ToolInvocation session fields cleanup
|
|
134
|
+
status: identity/routing slice implemented by ADR-023; result/live-state
|
|
135
|
+
authority completed by ADR-024 / ACS-11
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
CG-03's Agent/Tool Runtime-foundation criteria are reconciled by the joint
|
|
139
|
+
ADR-023 / ADR-024 implementation: concrete-session routing is separated from
|
|
140
|
+
domain identity, and worker results return to EventLoop for current-state and
|
|
141
|
+
semantic-ID validation before live apply. Workflow admission ownership remains
|
|
142
|
+
separate ACS-13 work.
|
|
143
|
+
|
|
144
|
+
## CG-03b routing foundation implementation
|
|
145
|
+
|
|
146
|
+
The Runtime now binds Agent/Tool/Multi-Agent asynchronous completion through a
|
|
147
|
+
session-local `FSMSession::EventSink`. Rebuilt sessions receive fresh sinks and
|
|
148
|
+
IDs; old sinks are never rebound to a new `execution_id` incarnation.
|
|
149
|
+
|
|
150
|
+
`AgentInvocation` therefore has no independent `id`/`session_id`.
|
|
151
|
+
`ToolInvocation#id` remains the semantic Tool lifecycle identity and is no
|
|
152
|
+
longer reused as an FSMSession ID. Workflow uses a private FSMSession-owned
|
|
153
|
+
identity reservation only because its existing pre-load admission still uses the
|
|
154
|
+
future FSMSession ID; opaque Workflow admission ownership is explicitly ACS-13.
|
|
155
|
+
|
|
156
|
+
## Explicit non-goals
|
|
157
|
+
|
|
158
|
+
This decision does not in CG-03a:
|
|
159
|
+
|
|
160
|
+
- stop using `AgentInvocation#id` as an Agent FSMSession ID;
|
|
161
|
+
- stop using `ToolInvocation#id` as a Tool FSMSession ID;
|
|
162
|
+
- remove the temporary `parent_agent_invocation_id` Runtime routing carrier;
|
|
163
|
+
- remove `AgentInvocation#session_id` or `ToolInvocation#session_id`;
|
|
164
|
+
- add a long-lived `parent_fsm_session_id`;
|
|
165
|
+
- remove `AgentExecutionActivation`;
|
|
166
|
+
- implement EventLoop single-writer ownership;
|
|
167
|
+
- implement recovery, rehydration, fencing, or cross-process ownership;
|
|
168
|
+
- define general Persistence schema/version evolution.
|
|
169
|
+
|
|
170
|
+
## Consequences
|
|
171
|
+
|
|
172
|
+
### Positive
|
|
173
|
+
|
|
174
|
+
- Agent, Tool, and approval logical-parent vocabulary uses an existing
|
|
175
|
+
purpose-specific domain identity.
|
|
176
|
+
- Application policy/notification code no longer receives a Runtime-oriented
|
|
177
|
+
Agent invocation identity.
|
|
178
|
+
- Provider `tool_call_id`, Phronomy `tool_invocation_id`, approval request ID, and
|
|
179
|
+
Agent `execution_id` remain distinct.
|
|
180
|
+
- Old embedded suspended-execution data remains readable without inventing a
|
|
181
|
+
false identity mapping.
|
|
182
|
+
- The Runtime foundation can later change FSMSession incarnation/routing
|
|
183
|
+
independently of durable and application-facing parent identity.
|
|
184
|
+
|
|
185
|
+
### Trade-offs
|
|
186
|
+
|
|
187
|
+
- This is a pre-1.0 breaking application API change.
|
|
188
|
+
- Applications that persisted `ToolApprovalRequest#to_h` must read the new
|
|
189
|
+
`execution_id` key for newly produced requests.
|
|
190
|
+
- Historical content-addressed approval audit bodies are intentionally not
|
|
191
|
+
rewritten and may still contain `agent_invocation_id`.
|
|
192
|
+
- The Agent/Tool portion of CG-03 is complete only when ADR-023 routing and
|
|
193
|
+
ADR-024 EventLoop result authority are both present.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# ADR-023: FSMSession Incarnation Identity and Runtime Routing Foundation
|
|
2
|
+
|
|
3
|
+
**Status**: Accepted
|
|
4
|
+
**Date**: 2026-08-23
|
|
5
|
+
**Partially superseded by**: [ADR-026](026-workflow-runtime-admission-and-durable-terminal-barrier.md) for the transitional Workflow FSMSession-identity reservation/admission bridge
|
|
6
|
+
**Related**:
|
|
7
|
+
- [ADR-010](010-cooperative-first-concurrency.md)
|
|
8
|
+
- [ADR-014](014-unified-persistence-durable-state.md)
|
|
9
|
+
- [ADR-020](020-canonical-workflow-instance-identity.md)
|
|
10
|
+
- [ADR-022](022-agent-execution-parent-identity-and-runtime-routing-boundary.md)
|
|
11
|
+
- [ADR-024](024-event-loop-single-writer-agent-runtime.md)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Context
|
|
16
|
+
|
|
17
|
+
Phronomy's EventLoop routes events to concrete `FSMSession` instances. Before
|
|
18
|
+
this decision, Agent, Tool, and Multi-Agent context objects generated IDs that
|
|
19
|
+
were injected into `FSMSession`, and callbacks later reused those object IDs as
|
|
20
|
+
EventLoop routing targets. Workflow likewise pre-generated a Runtime ID because
|
|
21
|
+
its current same-process admission is acquired before durable hydration.
|
|
22
|
+
|
|
23
|
+
This mixes domain/context identity with the identity of one concrete Runtime FSM
|
|
24
|
+
incarnation and makes suspend/resume stale-result safety difficult to reason
|
|
25
|
+
about.
|
|
26
|
+
|
|
27
|
+
## Decision
|
|
28
|
+
|
|
29
|
+
### 1. Concrete FSMSession identity is allocated by FSMSession infrastructure
|
|
30
|
+
|
|
31
|
+
A normal concrete `FSMSession` generates and owns a fresh `FSMSession#id` when
|
|
32
|
+
constructed. Agent, Tool, and Multi-Agent domain/context IDs are not injected as
|
|
33
|
+
that identity.
|
|
34
|
+
|
|
35
|
+
Across Runtime object/event boundaries the value is named `fsm_session_id`.
|
|
36
|
+
Terminal management payloads use `fsm_session_id` rather than generic
|
|
37
|
+
`session_id`.
|
|
38
|
+
|
|
39
|
+
Workflow is a narrow transitional case. Its current admission must be acquired
|
|
40
|
+
before durable state load, while the concrete FSMSession is constructed only
|
|
41
|
+
after hydration. Until ACS-13 separates Workflow admission ownership from
|
|
42
|
+
FSMSession routing identity, Workflow obtains a single-use identity reservation
|
|
43
|
+
from `FSMSession.reserve_identity`; the concrete FSMSession later claims exactly
|
|
44
|
+
that Runtime-owned reservation. Arbitrary raw `id:` injection is removed.
|
|
45
|
+
|
|
46
|
+
This reservation is Runtime-only and is not a Workflow/domain identity.
|
|
47
|
+
|
|
48
|
+
### 2. Async routing uses session-local event sinks
|
|
49
|
+
|
|
50
|
+
A concrete FSMSession has a Runtime-only event sink bound exactly once to its
|
|
51
|
+
`fsm_session_id`. Async work captures the sink belonging to the session that
|
|
52
|
+
started it. A rebuilt session receives a different sink and ID.
|
|
53
|
+
|
|
54
|
+
An old sink is never rebound or retargeted. If its session has terminated,
|
|
55
|
+
`EventLoop#post_to_session` rejects the old target rather than translating it to
|
|
56
|
+
a new session of the same logical execution.
|
|
57
|
+
|
|
58
|
+
This foundation is applied to Agent LLM completion/stream chunks, Tool
|
|
59
|
+
authorization/execution, Tool-to-parent notifications, callback-failure
|
|
60
|
+
notification, and Multi-Agent fan-out completion/timeout/cancellation.
|
|
61
|
+
|
|
62
|
+
### 3. Agent and Tool live/domain objects do not duplicate routing identity
|
|
63
|
+
|
|
64
|
+
`AgentInvocation` is a live FSM context belonging to `execution_id`; it has no
|
|
65
|
+
independent `id` and no duplicate Runtime `session_id`.
|
|
66
|
+
|
|
67
|
+
`ToolInvocation#id` remains the semantic `tool_invocation_id`. It is not an
|
|
68
|
+
FSMSession ID. ToolInvocation stores neither a duplicate session ID nor a
|
|
69
|
+
long-lived parent FSMSession ID. Parent routing is supplied as a session-local
|
|
70
|
+
sink when a Tool FSMSession is constructed.
|
|
71
|
+
|
|
72
|
+
No `parent_fsm_session_id`, generic generation token, or replacement generic
|
|
73
|
+
correlation identity is introduced.
|
|
74
|
+
|
|
75
|
+
### 4. Provider completion is routed before live result application
|
|
76
|
+
|
|
77
|
+
Provider completion callbacks post an immutable `LLMOperationResult` through the
|
|
78
|
+
session-local sink. The result carries the Provider Call's semantic `llm_call_id`.
|
|
79
|
+
The AgentInvocation EventLoop handler applies it only when the concrete session
|
|
80
|
+
still owns the event and the `llm_call_id` still matches the current Provider
|
|
81
|
+
Call.
|
|
82
|
+
|
|
83
|
+
ADR-024 completes this result-authority rule by making EventLoop the single
|
|
84
|
+
writer of Phronomy-managed live Agent execution state and by removing the former
|
|
85
|
+
Activation shared-mutable state model.
|
|
86
|
+
|
|
87
|
+
### 5. Runtime incarnation identity is not durable state
|
|
88
|
+
|
|
89
|
+
`fsm_session_id`, identity reservations, event sinks, callbacks, Tasks, and
|
|
90
|
+
other process-local Runtime values are not persisted as logical recovery state.
|
|
91
|
+
Recovery creates fresh Runtime objects from confirmed durable semantic state.
|
|
92
|
+
|
|
93
|
+
## Relationship to ACS-11 and ACS-13
|
|
94
|
+
|
|
95
|
+
This decision is the ACS-10 identity/routing foundation and the implementation
|
|
96
|
+
half of CG-03b. ADR-024/ACS-11 builds directly on it: Offload work now returns
|
|
97
|
+
operation-specific results and EventLoop validates/applies those results against
|
|
98
|
+
current Runtime state and purpose-specific semantic identity.
|
|
99
|
+
|
|
100
|
+
Together, ADR-023 and ADR-024 close the Agent/Tool result-routing and live-state
|
|
101
|
+
authority portion of CG-03 without introducing another generic identity.
|
|
102
|
+
|
|
103
|
+
ACS-13 separately owns Workflow's opaque admission owner handle and the durable
|
|
104
|
+
terminal-save barrier. This ADR does not pull those Workflow lifecycle changes
|
|
105
|
+
forward. The current `owner_fsm_session_id` admission representation remains an
|
|
106
|
+
explicit transitional mismatch until ACS-13.
|
|
107
|
+
|
|
108
|
+
## Explicit non-goals
|
|
109
|
+
|
|
110
|
+
This decision does not implement:
|
|
111
|
+
|
|
112
|
+
- ACS-13 Workflow opaque admission owner or durable-save-before-terminal barrier;
|
|
113
|
+
- restart-safe HITL/Workflow rehydration;
|
|
114
|
+
- same-process Agent admission redesign;
|
|
115
|
+
- cross-process ownership, leases, or fencing;
|
|
116
|
+
- general Persistence schema/version evolution.
|
|
117
|
+
|
|
118
|
+
Those items remain governed by their own later change sets. EventLoop single-
|
|
119
|
+
writer Agent ownership itself is defined by ADR-024 rather than duplicated here.
|
|
120
|
+
|
|
121
|
+
## Consequences
|
|
122
|
+
|
|
123
|
+
### Positive
|
|
124
|
+
|
|
125
|
+
- Agent/Tool/Multi-Agent domain IDs are no longer EventLoop routing IDs.
|
|
126
|
+
- Rebuilt sessions receive fresh session-local routing sinks.
|
|
127
|
+
- Old-session Provider completion is not applied to a newer session merely
|
|
128
|
+
because the logical `execution_id` is unchanged.
|
|
129
|
+
- Provider results are additionally protected by current semantic `llm_call_id`.
|
|
130
|
+
- Workflow preserves pre-load admission ordering without retaining arbitrary
|
|
131
|
+
caller-supplied FSMSession IDs.
|
|
132
|
+
- ACS-13 admission-owner redesign remains cleanly separated.
|
|
133
|
+
|
|
134
|
+
### Trade-offs
|
|
135
|
+
|
|
136
|
+
- Workflow temporarily uses a private FSMSession identity reservation because
|
|
137
|
+
its current admission owner is still the future FSMSession ID.
|
|
138
|
+
- Runtime result application now requires explicit state/semantic-ID validation
|
|
139
|
+
rather than relying on a shared mutable continuation container.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# ADR-024: EventLoop Single-Writer Agent Runtime State
|
|
2
|
+
|
|
3
|
+
**Status**: Accepted
|
|
4
|
+
**Date**: 2026-08-24
|
|
5
|
+
**Partially supersedes**: [ADR-014](014-unified-persistence-durable-state.md) for live Agent Runtime ownership and `AgentExecutionActivation`
|
|
6
|
+
**Complements**: [ADR-010](010-cooperative-first-concurrency.md), [ADR-012](012-canonical-execution-log-and-context-policy.md), [ADR-018](018-durability-guarantees-and-failure-model.md), [ADR-022](022-agent-execution-parent-identity-and-runtime-routing-boundary.md), [ADR-023](023-fsm-session-incarnation-identity-and-routing.md)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
ADR-014 correctly separated durable Persistence from process-local Agent continuation state, but its implementation introduced `AgentExecutionActivation` and `ActivationRegistry` as a shared mutable Runtime container. The Activation was protected by a Mutex and was mutated from EventLoop, OffloadPool workers, and asynchronous completion callbacks.
|
|
13
|
+
|
|
14
|
+
That model prevents one component from being the unambiguous live-state authority. In particular, Agent durable preparation and terminal commit paths could successfully persist a transition and then directly mutate live `AgentExecution`, `AgentRoot`, Journal projection, Provider-call state, or Runtime projection from an OffloadPool worker.
|
|
15
|
+
|
|
16
|
+
CG-03b / ADR-023 established the prerequisite routing foundation: every concrete Agent/Tool/Multi-Agent FSMSession owns a fresh Runtime incarnation identity and asynchronous completion is routed through that session's local EventSink. ACS-11 closes the remaining state-ownership gap.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
### EventLoop is the single writer of Phronomy-managed live Agent execution state
|
|
21
|
+
|
|
22
|
+
All Phronomy-managed live mutation associated with an active Agent execution is applied on the Runtime EventLoop thread.
|
|
23
|
+
|
|
24
|
+
The Runtime no longer owns an `ActivationRegistry`, and `AgentExecutionActivation` is removed. EventLoop owns a process-local execution directory:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
execution_id
|
|
28
|
+
-> immutable AgentExecutionState value
|
|
29
|
+
agent
|
|
30
|
+
coordinator
|
|
31
|
+
current AgentExecution
|
|
32
|
+
current RuntimeProjection
|
|
33
|
+
base Manifest
|
|
34
|
+
current AgentInvocation
|
|
35
|
+
current owning fsm_session_id
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The directory is the mutable authority. Individual entries are immutable values and are replaced atomically on EventLoop rather than mutated in place.
|
|
39
|
+
|
|
40
|
+
External live-owner lookup does not expose this state. `Runtime#__agent_execution_owner` returns only a read-only process-local owner view sufficient to resolve the current Agent/coordinator and execution status.
|
|
41
|
+
|
|
42
|
+
### AgentInvocation owns FSM-local mutable invocation facts
|
|
43
|
+
|
|
44
|
+
Mutable facts that are meaningful only while one Agent FSMSession progresses belong to `AgentInvocation` and are mutated only from EventLoop-driven FSM handling. These include:
|
|
45
|
+
|
|
46
|
+
- the active Provider Call provenance;
|
|
47
|
+
- uncommitted Provider outcomes;
|
|
48
|
+
- uncommitted Tool/runtime events;
|
|
49
|
+
- application callback failure state;
|
|
50
|
+
- Tool batch and approval-resume state.
|
|
51
|
+
|
|
52
|
+
This is not a replacement Activation. `AgentInvocation` is the FSM context of one logical Agent execution and is not shared as a worker-side mutable authority.
|
|
53
|
+
|
|
54
|
+
### OffloadPool receives operation-specific snapshots and returns operation-specific results
|
|
55
|
+
|
|
56
|
+
Synchronous Persistence I/O and other long synchronous work remain off EventLoop. Each operation captures the value data it needs before submission. Hash, Array, and String command data is recursively copied/frozen at the Tool authorization worker boundary.
|
|
57
|
+
|
|
58
|
+
A worker command may also carry an explicitly classified Application-owned behavior handle, such as an approval-policy callable. Such a callable is executable behavior, not Phronomy live-state authority. Phronomy does not place live Agent, Tool, ToolInvocation, FSMSession, Runtime, EventLoop, or other Phronomy-managed live domain objects into the callable's command/request data.
|
|
59
|
+
|
|
60
|
+
Application-defined opaque objects embedded in Application-owned context/metadata are not given a complete general value-type protocol by ACS-11. They remain Application-owned and must be safe for the Application's chosen worker usage. General serialization/value-type enforcement for those opaque objects is deferred hardening.
|
|
61
|
+
|
|
62
|
+
The Agent pipeline uses distinct operation shapes for at least:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
InitialPreparationCommand -> InitialPreparationResult
|
|
66
|
+
FollowupPreparationCommand -> FollowupPreparationResult
|
|
67
|
+
ResumeCommitCommand -> ResumeCommitResult
|
|
68
|
+
TerminalCommitCommand -> TerminalOutcome
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A worker may perform blocking Persistence I/O and operation-local calculation. It must not call live-state mutation hooks such as EventLoop execution replacement, Agent root replacement, Journal live-view append, or AgentInvocation runtime-fact acknowledgement.
|
|
72
|
+
|
|
73
|
+
After worker completion, a lightweight callback posts the result to EventLoop. EventLoop validates authority and only then applies the committed result to live state.
|
|
74
|
+
|
|
75
|
+
### Durable commit and live apply are distinct phases
|
|
76
|
+
|
|
77
|
+
Persistence is the last committed durable representation and recovery source. Successful Persistence operations return the resulting immutable durable values to EventLoop; they do not make Persistence the live read authority.
|
|
78
|
+
|
|
79
|
+
The normal path remains:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
EventLoop-owned live snapshot
|
|
83
|
+
-> OffloadPool durable operation
|
|
84
|
+
-> optimistic durable commit
|
|
85
|
+
-> operation result
|
|
86
|
+
-> EventLoop authority validation
|
|
87
|
+
-> EventLoop live apply
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Mutable Agent root, execution, and Journal state are not reloaded from Persistence merely to obtain freshness. Existing revision and Agent watermark checks remain the conflict boundary.
|
|
91
|
+
|
|
92
|
+
### Provider result authority uses FSMSession state and `llm_call_id`
|
|
93
|
+
|
|
94
|
+
A Provider Call receives its semantic `llm_call_id` on EventLoop before transport begins. Provider completion and streaming chunks carry that ID back through the owning FSMSession EventSink.
|
|
95
|
+
|
|
96
|
+
A result is applicable only when the Runtime still recognizes the owning FSMSession incarnation and the AgentInvocation still has the same active `llm_call_id` in the required FSM state.
|
|
97
|
+
|
|
98
|
+
A result for an old Provider Call is consumed as stale and does not advance the FSM. A callback targeting an old FSMSession incarnation is rejected by the session-local routing boundary established by ADR-023.
|
|
99
|
+
|
|
100
|
+
Phronomy does not introduce a generic generation token, generic invocation ID, or Offload operation identity as a second semantic authority.
|
|
101
|
+
|
|
102
|
+
### Tool worker results use the same direction of ownership
|
|
103
|
+
|
|
104
|
+
Tool authorization and execution continue to settle through explicit FSMSession events. Authorization worker input is captured on EventLoop before submission. Its value data contains Agent identity metadata and Tool description/operation data, not live Agent or Tool objects. Tool authorization behavior (`approval_facts`, `requires_approval`, and Agent approval policy when callable) is captured as explicitly classified Application-owned behavior handles.
|
|
105
|
+
|
|
106
|
+
`ApprovalEvaluationRequest` is therefore a value-only policy input. It exposes `agent_id`, `agent_definition_id`, `agent_definition_version`, execution identity, Tool name/schema, arguments, facts, context, origin, metadata, and default decision; it does not expose live `agent` or `tool` references.
|
|
107
|
+
|
|
108
|
+
Actual Tool execution is a separate behavior boundary: executing the configured Tool is the purpose of that operation. The authorization worker does not need the Tool instance and must not use one as live authorization input.
|
|
109
|
+
|
|
110
|
+
Authorization/execution outcomes are immutable result carriers and are applied to `ToolInvocation` only by EventLoop-driven FSM handling. They carry `tool_invocation_id`; EventLoop-driven Tool FSM handling consumes an outcome as stale when that semantic ID does not match the current ToolInvocation. `tool_invocation_id` remains the semantic Tool-operation identity. FSMSession identity remains Runtime routing identity. The two are not conflated.
|
|
111
|
+
|
|
112
|
+
### Approval suspension retains the same live owner without Activation
|
|
113
|
+
|
|
114
|
+
Approval suspension retains the same process-local Agent and AgentInvocation. The suspended execution remains present in EventLoop's execution directory, but no active FSMSession owns it while suspended.
|
|
115
|
+
|
|
116
|
+
`Agent::Base.live_for_execution(execution_id)` and `agent.approve_async(...)` resolve the process-local execution owner through EventLoop's read-only owner view. They do not load a replacement Agent/Execution from Persistence.
|
|
117
|
+
|
|
118
|
+
A resume performs its durable approval transition off EventLoop and applies the result on EventLoop before constructing a fresh resume FSMSession incarnation.
|
|
119
|
+
|
|
120
|
+
If no live owner exists, durable rehydration remains a separate capability and `ExecutionRehydrationRequiredError` is raised.
|
|
121
|
+
|
|
122
|
+
### Application callbacks do not become a worker-side authority
|
|
123
|
+
|
|
124
|
+
Application stream/event callbacks are invoked from EventLoop-owned AgentInvocation event handling. Callback failure is recorded independently of canonical runtime event capture, converted into an explicit FSM failure event, and cannot mutate durable or live execution state from an Offload worker.
|
|
125
|
+
|
|
126
|
+
Approval notification callbacks may execute off EventLoop because they are application work. Their execution does not advance Phronomy-managed live state.
|
|
127
|
+
|
|
128
|
+
## Required invariants
|
|
129
|
+
|
|
130
|
+
The implementation must preserve all of the following:
|
|
131
|
+
|
|
132
|
+
1. Journal / Manifest / ContentStore remain the canonical execution/context record authorities defined by ADR-012.
|
|
133
|
+
2. Persistence remains the durable recovery authority, not the normal live refresh source.
|
|
134
|
+
3. FSMSession incarnation identity and EventSink routing remain as defined by ADR-023.
|
|
135
|
+
4. `execution_id` remains the logical Agent execution parent identity defined by ADR-022.
|
|
136
|
+
5. Provider result authority is checked with current FSMSession/FSM state plus `llm_call_id`.
|
|
137
|
+
6. Tool result authority remains tied to the current Tool FSMSession and `tool_invocation_id`.
|
|
138
|
+
7. OffloadPool never waits synchronously for logical EventLoop progress.
|
|
139
|
+
8. Worker completion callbacks do not fall back to direct live mutation when EventLoop is unavailable.
|
|
140
|
+
9. Tool authorization command/request value data contains no Phronomy-managed live domain object; explicitly classified Application-owned behavior handles remain permitted.
|
|
141
|
+
|
|
142
|
+
## Consequences
|
|
143
|
+
|
|
144
|
+
### Positive
|
|
145
|
+
|
|
146
|
+
- Agent live-state ownership is explicit and mechanically enforceable.
|
|
147
|
+
- Mutex-protected shared Activation state disappears.
|
|
148
|
+
- Worker-side durable I/O can scale independently without becoming a second live-state writer.
|
|
149
|
+
- Late Provider results cannot overwrite the provenance of a newer Provider Call.
|
|
150
|
+
- Approval lookup remains process-local without exposing mutable execution internals.
|
|
151
|
+
- The design provides the state/result foundation required by later recovery, cancellation, and durable-barrier work.
|
|
152
|
+
|
|
153
|
+
### Trade-offs
|
|
154
|
+
|
|
155
|
+
- Durable commit and EventLoop live apply are separate phases, so code must explicitly model and validate result application.
|
|
156
|
+
- Internal Agent execution coordination uses more typed command/result values than the Activation model.
|
|
157
|
+
- Process loss still loses in-flight Runtime continuation; this decision does not implement rehydration.
|
|
158
|
+
|
|
159
|
+
## Explicitly deferred work
|
|
160
|
+
|
|
161
|
+
This decision does **not** implement:
|
|
162
|
+
|
|
163
|
+
- ACS-12: same-process Agent admission/exclusion policy;
|
|
164
|
+
- ACS-13: opaque Workflow admission ownership and Workflow terminal-save ordering;
|
|
165
|
+
- ACS-14: cross-process leases/fencing;
|
|
166
|
+
- ACS-15: durable Agent/FSMSession rehydration;
|
|
167
|
+
- ACS-16: cancellation/HITL Task semantic completion;
|
|
168
|
+
- ACS-17: semantic retry and causal durable barriers.
|
|
169
|
+
|
|
170
|
+
Those changes build on this ownership/result model and must not be folded into ACS-11 implicitly.
|
|
171
|
+
|
|
172
|
+
## Rejected alternatives
|
|
173
|
+
|
|
174
|
+
### Rename Activation and keep the same shared mutable object
|
|
175
|
+
|
|
176
|
+
Rejected. A renamed mutex-protected container shared by EventLoop and workers preserves the ownership defect.
|
|
177
|
+
|
|
178
|
+
### Let workers update Agent live state after a successful commit
|
|
179
|
+
|
|
180
|
+
Rejected. Successful durability does not grant a worker live mutation authority. The result must return to EventLoop for apply.
|
|
181
|
+
|
|
182
|
+
### Reload Agent state from Persistence before every apply
|
|
183
|
+
|
|
184
|
+
Rejected. This would make Persistence the live source of truth and reintroduce implicit refresh/merge semantics rejected by ADR-014.
|
|
185
|
+
|
|
186
|
+
### Add a generic generation/correlation token
|
|
187
|
+
|
|
188
|
+
Rejected. Result authority is expressed using the current FSM state plus existing purpose-specific semantic identifiers. A new generic token would recreate identity ambiguity already removed by ADR-021 through ADR-023.
|