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
|
@@ -8,24 +8,40 @@ the bounded `OffloadPool`.
|
|
|
8
8
|
|
|
9
9
|
For the design rationale, see [ADR-010](decisions/010-cooperative-first-concurrency.md).
|
|
10
10
|
Durable-state ownership is defined by
|
|
11
|
-
[ADR-014](decisions/014-unified-persistence-durable-state.md)
|
|
11
|
+
[ADR-014](decisions/014-unified-persistence-durable-state.md), with live Agent
|
|
12
|
+
Runtime execution-state ownership refined by
|
|
13
|
+
[ADR-024](decisions/024-event-loop-single-writer-agent-runtime.md) and process-local
|
|
14
|
+
Agent identity/admission ownership defined by
|
|
15
|
+
[ADR-025](decisions/025-process-local-agent-ownership-and-runtime-admission.md).
|
|
16
|
+
Canonical Workflow instance identity is defined by
|
|
17
|
+
[ADR-020](decisions/020-canonical-workflow-instance-identity.md).
|
|
18
|
+
Concrete FSMSession incarnation identity and session-local Runtime routing are
|
|
19
|
+
defined by [ADR-023](decisions/023-fsm-session-incarnation-identity-and-routing.md).
|
|
20
|
+
Same-process Workflow admission ownership and durable terminal-barrier ordering
|
|
21
|
+
are defined by
|
|
22
|
+
[ADR-026](decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md).
|
|
12
23
|
|
|
13
24
|
## Runtime model
|
|
14
25
|
|
|
15
26
|
```text
|
|
16
27
|
Runtime
|
|
28
|
+
├─ Agent ownership registry
|
|
29
|
+
│ └─ agent_id -> one mutable live Agent instance
|
|
17
30
|
├─ EventLoop (one control-plane operating-system Thread)
|
|
18
|
-
│
|
|
19
|
-
│
|
|
20
|
-
│
|
|
21
|
-
│
|
|
22
|
-
│
|
|
23
|
-
├─
|
|
31
|
+
│ ├─ FSMSession
|
|
32
|
+
│ │ ├─ Agent
|
|
33
|
+
│ │ ├─ Workflow
|
|
34
|
+
│ │ ├─ ToolInvocation
|
|
35
|
+
│ │ └─ MultiAgent fan-out
|
|
36
|
+
│ ├─ Agent top-level admission
|
|
37
|
+
│ │ └─ agent_id -> one nonterminal logical Execution admission
|
|
38
|
+
│ └─ Agent execution directory
|
|
39
|
+
│ └─ execution_id -> immutable live-state record
|
|
24
40
|
├─ OffloadPool (bounded operating-system Threads)
|
|
25
41
|
│ ├─ private Operation records
|
|
26
42
|
│ ├─ blocking input/output (I/O)
|
|
27
43
|
│ ├─ central-processing-unit (CPU)-bound synchronous work
|
|
28
|
-
│ └─
|
|
44
|
+
│ └─ operation-specific durable Agent/Workflow work
|
|
29
45
|
├─ named OffloadPools
|
|
30
46
|
└─ EventLoop-driven timers
|
|
31
47
|
|
|
@@ -44,12 +60,22 @@ A live Agent or Workflow owns its current logical state. `Persistence` is the
|
|
|
44
60
|
last committed durable representation and recovery source; it is not reloaded at
|
|
45
61
|
every semantic boundary.
|
|
46
62
|
|
|
47
|
-
For Agents,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
For active Agents, **EventLoop is the single writer of Phronomy-managed live
|
|
64
|
+
execution state**. EventLoop owns a process-local execution directory keyed by
|
|
65
|
+
canonical `execution_id`. Each directory value is immutable and is replaced on
|
|
66
|
+
EventLoop when the current AgentExecution, RuntimeProjection, AgentInvocation, or
|
|
67
|
+
owning FSMSession changes. The former mutex-protected
|
|
68
|
+
`AgentExecutionActivation` / `ActivationRegistry` model is removed.
|
|
69
|
+
|
|
70
|
+
`AgentInvocation` is the FSM-local mutable context and holds uncommitted Provider
|
|
71
|
+
outcomes, Tool/runtime events, active Provider-call provenance, and callback
|
|
72
|
+
failure state. These fields are advanced only by EventLoop-driven FSMSession
|
|
73
|
+
handling; workers do not receive AgentInvocation as a mutable state authority.
|
|
74
|
+
|
|
75
|
+
Mutable Agent/Execution/Journal state is not automatically reloaded before every
|
|
76
|
+
LLM or Tool step. Durable writes use optimistic revision/position guardrails; an
|
|
77
|
+
external writer that advances the durable base causes
|
|
78
|
+
`Persistence::ConflictError` rather than automatic reload or merge.
|
|
53
79
|
|
|
54
80
|
For Workflows, the current `WorkflowContext` and FSMSession own the active
|
|
55
81
|
logical state. A durable Workflow hydrates once at invocation/resume and saves at
|
|
@@ -58,34 +84,198 @@ the halted/terminal boundary.
|
|
|
58
84
|
Content-addressed `Persistence#contents` values are immutable. Fetching a known
|
|
59
85
|
content reference is value materialization rather than mutable state refresh.
|
|
60
86
|
|
|
61
|
-
##
|
|
87
|
+
## Process-local Agent identity ownership and admission
|
|
88
|
+
|
|
89
|
+
`agent_id` identifies one logical Agent, not a reusable lookup key for independent
|
|
90
|
+
mutable objects. One Runtime therefore publishes at most one mutable live Agent
|
|
91
|
+
instance for a given `agent_id`. The Runtime-owned registry is an authority, not a
|
|
92
|
+
cache, and reserves the identity before create/load materialization.
|
|
93
|
+
|
|
94
|
+
The application-facing identity operations are distinct:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
new / create
|
|
98
|
+
create a new Agent; existing live or durable identity is an error
|
|
99
|
+
|
|
100
|
+
load(agent_id, persistence:)
|
|
101
|
+
live -> exact same Ruby object, with no Persistence reload
|
|
102
|
+
durable-only -> hydrate and publish once
|
|
103
|
+
missing -> Persistence::NotFoundError
|
|
104
|
+
|
|
105
|
+
get(agent_id)
|
|
106
|
+
live Runtime lookup only; missing -> nil
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A live Agent is strongly owned for the Runtime lifetime even while idle and
|
|
110
|
+
across sequential Executions. Execution completion does not evict it. Clean
|
|
111
|
+
Runtime shutdown detaches old Agent objects so they cannot remain mutable beside
|
|
112
|
+
a later Runtime owner. `purge!` is the explicit earlier destruction boundary: it
|
|
113
|
+
invalidates the old object, deletes durable state, releases the process-local
|
|
114
|
+
identity, and allows a later new Agent to reuse the textual ID.
|
|
115
|
+
|
|
116
|
+
Live Agent ownership and top-level Execution admission are separate lifetimes.
|
|
117
|
+
For one live Agent, EventLoop admits at most one nonterminal top-level Execution.
|
|
118
|
+
Admission is acquired **before** the initial Offload/Persistence operation:
|
|
62
119
|
|
|
63
|
-
|
|
120
|
+
```text
|
|
121
|
+
invoke
|
|
122
|
+
-> EventLoop Agent admission
|
|
123
|
+
-> Offload/Persistence executions.create_active
|
|
124
|
+
-> EventLoop live execution state
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`preparing`, `active`, and `suspended` all retain the slot. A competing request is
|
|
128
|
+
rejected with `AgentBusyError`; core does not promise automatic queueing. A
|
|
129
|
+
known-successful durable terminal transition releases the slot. A known
|
|
130
|
+
pre-durable failure may release it; an uncertain durable outcome remains
|
|
131
|
+
fail-closed/recovery-required.
|
|
132
|
+
|
|
133
|
+
`Persistence#executions.create_active`, optimistic revision, Journal position, and
|
|
134
|
+
watermark checks remain required durable defenses. They do not become the
|
|
135
|
+
primary same-process live ownership/admission mechanism and do not provide
|
|
136
|
+
cross-process exclusion.
|
|
137
|
+
|
|
138
|
+
## EventLoop single-writer and Offload result application
|
|
139
|
+
|
|
140
|
+
Persistence repositories are synchronous, so durable work must remain off the
|
|
141
|
+
EventLoop thread. The ownership rule is therefore not "run everything on
|
|
142
|
+
EventLoop". It is:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
EventLoop
|
|
146
|
+
capture operation-specific immutable state
|
|
147
|
+
↓
|
|
148
|
+
OffloadPool
|
|
149
|
+
blocking I/O / CPU / operation-local calculation
|
|
150
|
+
durable commit
|
|
151
|
+
↓ operation-specific result
|
|
152
|
+
EventLoop
|
|
153
|
+
validate current authority
|
|
154
|
+
apply committed result to live state
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Agent initial preparation, follow-up Manifest preparation, approval resume, and
|
|
158
|
+
terminal commit use distinct command/result values. An Offload worker may commit
|
|
159
|
+
Persistence but does not update the live Agent root, Journal view, current
|
|
160
|
+
AgentExecution, RuntimeProjection, AgentInvocation runtime queues, or EventLoop
|
|
161
|
+
execution directory.
|
|
162
|
+
|
|
163
|
+
Completion callbacks are lightweight bridges that enqueue the result back to the
|
|
164
|
+
EventLoop. If EventLoop no longer accepts the result, the callback does not fall
|
|
165
|
+
back to direct live mutation.
|
|
166
|
+
|
|
167
|
+
## Provider Call result authority
|
|
168
|
+
|
|
169
|
+
Provider Call identity is purpose-specific semantic provenance. EventLoop
|
|
170
|
+
allocates `llm_call_id` before transport begins and binds it to the Manifest used
|
|
171
|
+
for that call.
|
|
172
|
+
|
|
173
|
+
Provider completion and streaming chunks return through the owning FSMSession's
|
|
174
|
+
EventSink and carry the `llm_call_id`. A result is applicable only when:
|
|
175
|
+
|
|
176
|
+
- it still targets the current FSMSession incarnation;
|
|
177
|
+
- the FSM is in the state that accepts that result; and
|
|
178
|
+
- the AgentInvocation still owns the same active `llm_call_id`.
|
|
179
|
+
|
|
180
|
+
A callback to an old FSMSession incarnation is dropped by session-local routing.
|
|
181
|
+
A result with a stale `llm_call_id` is consumed without advancing the current
|
|
182
|
+
FSM. Phronomy does not add a generic generation/correlation token as another
|
|
183
|
+
result authority.
|
|
184
|
+
|
|
185
|
+
Tool operations follow the same ownership direction. `tool_invocation_id` is the
|
|
186
|
+
semantic Tool-operation identity, while FSMSession ID is Runtime routing identity.
|
|
187
|
+
Tool authorization captures Agent identity and Tool description data as values on
|
|
188
|
+
EventLoop before offload. The authorization worker receives no live Agent, Tool, or
|
|
189
|
+
ToolInvocation reference. Application-owned approval/facts/requirement callables
|
|
190
|
+
are explicitly classified behavior handles and receive a value-only
|
|
191
|
+
`ApprovalEvaluationRequest`.
|
|
192
|
+
|
|
193
|
+
Hash, Array, and String authorization command data is recursively copied/frozen.
|
|
194
|
+
Phronomy-managed live domain objects are rejected from that value data. A complete
|
|
195
|
+
value-type/serialization contract for arbitrary Application-owned opaque objects is
|
|
196
|
+
deferred; such objects remain Application-owned and must be worker-safe.
|
|
197
|
+
|
|
198
|
+
Worker authorization/execution outcomes return as values carrying
|
|
199
|
+
`tool_invocation_id`; the Tool FSMSession consumes a mismatched semantic result
|
|
200
|
+
without advancing its current state.
|
|
201
|
+
|
|
202
|
+
## Approval suspension and live owner lookup
|
|
203
|
+
|
|
204
|
+
Approval suspension retains the same process-local Agent and AgentInvocation but
|
|
205
|
+
has no active owning FSMSession until resume. EventLoop retains the suspended
|
|
206
|
+
execution entry.
|
|
207
|
+
|
|
208
|
+
`Agent::Base.live_for_execution(execution_id)` resolves a read-only Runtime owner
|
|
209
|
+
view and returns the existing Agent instance. `agent.approve_async(...)` routes to
|
|
210
|
+
the same live coordinator. Neither operation reloads a replacement Agent or
|
|
211
|
+
Execution from Persistence.
|
|
212
|
+
|
|
213
|
+
A resume performs its durable approval transition through OffloadPool, applies
|
|
214
|
+
the result on EventLoop, and then builds a **fresh** FSMSession incarnation.
|
|
215
|
+
If the process-local owner no longer exists, durable continuation reconstruction
|
|
216
|
+
is not implied; `ExecutionRehydrationRequiredError` is raised.
|
|
217
|
+
|
|
218
|
+
## Workflow identities, admission, and durable terminal barrier
|
|
219
|
+
|
|
220
|
+
Workflow runtime keeps identity and coordination responsibilities separate:
|
|
64
221
|
|
|
65
222
|
```text
|
|
66
223
|
session_id
|
|
67
|
-
application session/correlation
|
|
224
|
+
application session/correlation metadata
|
|
68
225
|
|
|
69
|
-
|
|
70
|
-
durable Workflow identity and Persistence#workflow_states key
|
|
226
|
+
workflow_instance_id
|
|
227
|
+
logical/durable Workflow identity and Persistence#workflow_states key
|
|
228
|
+
|
|
229
|
+
admission owner token
|
|
230
|
+
opaque process-local Runtime coordination capability
|
|
71
231
|
|
|
72
232
|
fsm_session_id
|
|
73
|
-
one Runtime FSMSession
|
|
233
|
+
one concrete Runtime FSMSession routing identity
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
EventLoop acquires the `workflow_instance_id` admission with a fresh opaque owner
|
|
237
|
+
token **before** `workflow_states.load(workflow_instance_id)` is submitted. The
|
|
238
|
+
token is not a domain identity and is never an Event target. After durable
|
|
239
|
+
hydration, EventLoop constructs the concrete FSMSession and binds its fresh
|
|
240
|
+
`fsm_session_id` to the existing admission for `Workflow#signal` routing.
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
admit workflow_instance_id with owner token
|
|
244
|
+
↓
|
|
245
|
+
Offload workflow_states.load
|
|
246
|
+
↓
|
|
247
|
+
EventLoop hydrate / create FSMSession
|
|
248
|
+
↓
|
|
249
|
+
bind fsm_session_id for routing
|
|
74
250
|
```
|
|
75
251
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
252
|
+
A durable Workflow also keeps terminal persistence inside the FSMSession
|
|
253
|
+
lifecycle. Logical halt/completion first enters a private
|
|
254
|
+
`persisting_terminal` lifecycle condition; the FSMSession remains nonterminal
|
|
255
|
+
while WorkflowRunner saves the terminal snapshot through OffloadPool. Only a
|
|
256
|
+
known-successful save result returned to that same FSMSession permits
|
|
257
|
+
`HALTED`/`COMPLETED`, admission release, and caller Task settlement.
|
|
79
258
|
|
|
80
259
|
```text
|
|
81
|
-
|
|
260
|
+
RUNNING
|
|
261
|
+
↓ logical halt/completion
|
|
262
|
+
PERSISTING_TERMINAL
|
|
263
|
+
├─ known success -> HALTED / COMPLETED -> release
|
|
264
|
+
├─ known failure -> ERROR -> release
|
|
265
|
+
└─ outcome unknown -> RECOVERY_REQUIRED (fail closed)
|
|
82
266
|
```
|
|
83
267
|
|
|
84
|
-
The
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
268
|
+
The FSMSession does not know whether Persistence is local, remote, SQL, HTTP, or
|
|
269
|
+
networked. The Workflow persistence operation normalizes the result into
|
|
270
|
+
`success`, `known_failure`, or `outcome_unknown`. Only known success crosses the
|
|
271
|
+
durable barrier. If the backend/storage error does not establish non-commit,
|
|
272
|
+
Phronomy treats the terminal outcome as uncertain rather than guessing.
|
|
273
|
+
|
|
274
|
+
`recovery_required` prevents a fresh same-Workflow execution segment from being
|
|
275
|
+
admitted, but ACS-13 does not claim restart-safe reconciliation; that remains
|
|
276
|
+
ACS-15 work. The admission map itself is process-local. Cross-process duplicate
|
|
277
|
+
execution requires the later coordination/fencing work; optimistic revisions
|
|
278
|
+
remain durable conflict defense rather than distributed ownership.
|
|
89
279
|
|
|
90
280
|
## Tool execution modes
|
|
91
281
|
|
|
@@ -132,8 +322,7 @@ waiting at the same time.
|
|
|
132
322
|
`Persistence` repositories expose synchronous operations. Framework lifecycle
|
|
133
323
|
code must not perform potentially blocking durable reads/writes on EventLoop.
|
|
134
324
|
Agent preparation/commit and Workflow hydrate/save operations are submitted to
|
|
135
|
-
`OffloadPool`; completion continues through
|
|
136
|
-
events.
|
|
325
|
+
`OffloadPool`; completion continues through explicit EventLoop events.
|
|
137
326
|
|
|
138
327
|
A durable barrier may pause one logical lifecycle without blocking EventLoop.
|
|
139
328
|
Persistence does not implement async repository variants and must not depend on
|
|
@@ -174,8 +363,9 @@ abandonment state.
|
|
|
174
363
|
`Task#on_complete` registers an independent notification callback. Callback
|
|
175
364
|
execution thread is not guaranteed. A callback may be delivered by an OffloadPool
|
|
176
365
|
worker, a timer/cancellation caller, an EventLoop-related control path, or the
|
|
177
|
-
thread that registers after settlement. Callbacks must be thread-safe
|
|
178
|
-
complete quickly.
|
|
366
|
+
thread that registers after settlement. Callbacks must therefore be thread-safe
|
|
367
|
+
and should complete quickly. Framework lifecycle code normally converts worker
|
|
368
|
+
completion into an explicit EventLoop event before applying live state.
|
|
179
369
|
|
|
180
370
|
`Task#map` is application-level composition. A transformation exception settles
|
|
181
371
|
the mapped Task as failed.
|
|
@@ -301,6 +491,10 @@ Workflow durable admission participates in EventLoop idleness: a Workflow whose
|
|
|
301
491
|
FSMSession has ended but whose durable save is still in flight remains owned until
|
|
302
492
|
that save completes and owner-aware admission is released.
|
|
303
493
|
|
|
494
|
+
Suspended Agent execution owner entries are process-local continuation state and
|
|
495
|
+
do not by themselves keep Runtime shutdown waiting. Runtime/process loss does not
|
|
496
|
+
imply durable Agent continuation reconstruction.
|
|
497
|
+
|
|
304
498
|
`Phronomy.reset_runtime!` exists primarily for test isolation and performs a real
|
|
305
499
|
Runtime shutdown before resetting configuration.
|
|
306
500
|
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Phronomy examples
|
|
2
|
+
|
|
3
|
+
Runnable application examples are maintained in the dedicated
|
|
4
|
+
[`Raizo-TCS/phronomy-examples`](https://github.com/Raizo-TCS/phronomy-examples)
|
|
5
|
+
repository.
|
|
6
|
+
|
|
7
|
+
This repository intentionally does not keep a second runnable example set under
|
|
8
|
+
`examples/`. Keeping one canonical sample repository avoids API drift between
|
|
9
|
+
Phronomy's implementation/documentation and independently maintained copies of
|
|
10
|
+
sample code.
|
|
11
|
+
|
|
12
|
+
For framework-level executable behavior and regression coverage, use the specs
|
|
13
|
+
under `spec/`. For application-oriented examples, use `phronomy-examples`.
|
|
@@ -7,12 +7,13 @@ module Phronomy
|
|
|
7
7
|
module Agent
|
|
8
8
|
class AgentExecution
|
|
9
9
|
ACTIVE_STATUSES = %i[preparing active suspended].freeze
|
|
10
|
-
TERMINAL_STATUSES = %i[completed failed cancelled rejected blocked].freeze
|
|
10
|
+
TERMINAL_STATUSES = %i[completed handed_off failed cancelled rejected blocked].freeze
|
|
11
11
|
TRANSITIONS = {
|
|
12
12
|
preparing: %i[preparing active failed cancelled blocked],
|
|
13
|
-
active: %i[active suspended completed failed cancelled rejected blocked],
|
|
13
|
+
active: %i[active suspended completed handed_off failed cancelled rejected blocked],
|
|
14
14
|
suspended: %i[suspended active failed cancelled],
|
|
15
15
|
completed: %i[completed],
|
|
16
|
+
handed_off: %i[handed_off],
|
|
16
17
|
failed: %i[failed],
|
|
17
18
|
cancelled: %i[cancelled],
|
|
18
19
|
rejected: %i[rejected],
|
|
@@ -84,10 +85,8 @@ module Phronomy
|
|
|
84
85
|
self.class.new(**values)
|
|
85
86
|
end
|
|
86
87
|
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# @return [Hash{String => Object}]
|
|
88
|
+
# Current semantic payload representation. Persistence format identity and
|
|
89
|
+
# compatibility validation are owned by Persistence::DurableCodec.
|
|
91
90
|
# @api public
|
|
92
91
|
def to_h
|
|
93
92
|
ATTRIBUTES.to_h do |name|
|
|
@@ -98,18 +97,23 @@ module Phronomy
|
|
|
98
97
|
end
|
|
99
98
|
end
|
|
100
99
|
|
|
101
|
-
# Restores
|
|
102
|
-
#
|
|
103
|
-
# records and LLM Call records are restored through their public codecs so
|
|
104
|
-
# storage backends do not need to know their constructor details.
|
|
105
|
-
#
|
|
106
|
-
# @param hash [Hash]
|
|
107
|
-
# @return [AgentExecution]
|
|
100
|
+
# Restores only the current semantic payload shape. Historical durable
|
|
101
|
+
# representations must go through explicit Persistence migration first.
|
|
108
102
|
# @api public
|
|
109
103
|
def self.from_h(hash)
|
|
104
|
+
source = hash.to_h { |key, value| [key.to_s, value] }
|
|
105
|
+
expected = ATTRIBUTES.map(&:to_s).sort
|
|
106
|
+
actual = source.keys.sort
|
|
107
|
+
unless actual == expected
|
|
108
|
+
missing = expected - actual
|
|
109
|
+
unknown = actual - expected
|
|
110
|
+
raise ArgumentError,
|
|
111
|
+
"AgentExecution payload schema mismatch: " \
|
|
112
|
+
"missing=#{missing.inspect}, unknown=#{unknown.inspect}"
|
|
113
|
+
end
|
|
114
|
+
|
|
110
115
|
attributes = ATTRIBUTES.to_h do |name|
|
|
111
|
-
|
|
112
|
-
[name, hash.fetch(key)]
|
|
116
|
+
[name, source.fetch(name.to_s)]
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
attributes[:working_records] = attributes.fetch(:working_records).map do |record|
|