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
|
@@ -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;
|
|
@@ -41,7 +44,7 @@ contract.
|
|
|
41
44
|
|
|
42
45
|
## Required root surface
|
|
43
46
|
|
|
44
|
-
A Persistence backend exposes
|
|
47
|
+
A Persistence backend exposes eight durable repositories:
|
|
45
48
|
|
|
46
49
|
```text
|
|
47
50
|
contents
|
|
@@ -49,6 +52,9 @@ agents
|
|
|
49
52
|
journals
|
|
50
53
|
executions
|
|
51
54
|
workflow_states
|
|
55
|
+
handoff_states
|
|
56
|
+
teams
|
|
57
|
+
team_executions
|
|
52
58
|
```
|
|
53
59
|
|
|
54
60
|
and two root operations:
|
|
@@ -63,7 +69,7 @@ persistence.assert_agent_watermark!(
|
|
|
63
69
|
```
|
|
64
70
|
|
|
65
71
|
The object yielded by `transaction` is a transaction-scoped Persistence view. It
|
|
66
|
-
must respond to all
|
|
72
|
+
must respond to all eight repository accessors and
|
|
67
73
|
`assert_agent_watermark!`. It may be the Persistence instance itself, but SQL
|
|
68
74
|
backends may instead yield an object bound to a checked-out connection or
|
|
69
75
|
transaction session.
|
|
@@ -87,7 +93,7 @@ this requirement.
|
|
|
87
93
|
|
|
88
94
|
All durable repositories must be able to participate in one atomic transaction
|
|
89
95
|
domain. A transaction may change `contents`, `agents`, `journals`, `executions`,
|
|
90
|
-
|
|
96
|
+
`workflow_states`, `handoff_states`, `teams`, and `team_executions`, and then either commit all changes or roll them all back.
|
|
91
97
|
|
|
92
98
|
This requirement deliberately does not claim exactly-once semantics after an
|
|
93
99
|
indeterminate database/network failure. If the underlying database cannot tell
|
|
@@ -96,8 +102,9 @@ failure rather than pretending the outcome is known.
|
|
|
96
102
|
|
|
97
103
|
### `atomic_admission`
|
|
98
104
|
|
|
99
|
-
This capability
|
|
100
|
-
|
|
105
|
+
This capability is a **durable Agent execution integrity defense**. It is not the
|
|
106
|
+
primary same-process Agent ownership/admission mechanism and it is not Workflow
|
|
107
|
+
distributed locking.
|
|
101
108
|
|
|
102
109
|
For one Agent, `executions.create_active` must atomically guarantee both:
|
|
103
110
|
|
|
@@ -110,9 +117,15 @@ no active/suspended execution already exists for agent_id
|
|
|
110
117
|
A conflict with an existing active/suspended execution raises
|
|
111
118
|
`Phronomy::AgentBusyError`.
|
|
112
119
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
Within one process, Runtime/EventLoop admission is acquired before the initial
|
|
121
|
+
Persistence operation and is the primary competing-execution exclusion
|
|
122
|
+
mechanism. `atomic_admission` remains required as the durable second line of
|
|
123
|
+
defense against stale paths, durable conflicts, and unsupported cross-process
|
|
124
|
+
races. It must not be removed merely because Runtime admission exists.
|
|
125
|
+
|
|
126
|
+
Workflow admission remains Runtime/process-local. Cross-process Agent or
|
|
127
|
+
Workflow ownership/lease/fencing is a separate distributed-coordination concern
|
|
128
|
+
and is not part of this Backend SPI.
|
|
116
129
|
|
|
117
130
|
### `optimistic_revision`
|
|
118
131
|
|
|
@@ -137,8 +150,10 @@ identity, duplicate-ID, or compare-and-swap conflicts.
|
|
|
137
150
|
|
|
138
151
|
### `Phronomy::AgentBusyError`
|
|
139
152
|
|
|
140
|
-
|
|
141
|
-
cannot be
|
|
153
|
+
A durable nonterminal Agent execution already exists and another durable
|
|
154
|
+
execution record cannot be established. Phronomy also uses the same public error
|
|
155
|
+
for a competing process-local top-level request rejected by Runtime/EventLoop
|
|
156
|
+
before the backend is called.
|
|
142
157
|
|
|
143
158
|
### `Phronomy::Persistence::SerializationError`
|
|
144
159
|
|
|
@@ -202,6 +217,11 @@ def delete(agent_id)
|
|
|
202
217
|
- returns `Phronomy::Agent::AgentRoot`, not a raw database Hash;
|
|
203
218
|
- raises `NotFoundError` when missing.
|
|
204
219
|
|
|
220
|
+
These repository operations are durable-storage primitives. The higher-level
|
|
221
|
+
`Agent::Base.load` API first consults Runtime's process-local live ownership
|
|
222
|
+
registry and does not call the repository when the requested Agent is already
|
|
223
|
+
live.
|
|
224
|
+
|
|
205
225
|
`save` atomically checks:
|
|
206
226
|
|
|
207
227
|
```text
|
|
@@ -252,13 +272,17 @@ def create_active(execution)
|
|
|
252
272
|
def load(execution_id)
|
|
253
273
|
def save(execution_id, expected_revision:, execution:)
|
|
254
274
|
def list_active(agent_id)
|
|
275
|
+
def list(agent_id, after: nil, limit: 100)
|
|
255
276
|
def delete(execution_id)
|
|
256
277
|
def delete_for_agent(agent_id)
|
|
257
278
|
def assert_idle!(agent_id)
|
|
258
279
|
```
|
|
259
280
|
|
|
260
|
-
`create_active` performs atomic Agent admission. A duplicate
|
|
261
|
-
raises `ConflictError`; an already busy Agent raises
|
|
281
|
+
`create_active` performs atomic **durable** Agent execution admission. A duplicate
|
|
282
|
+
`execution_id` raises `ConflictError`; an already busy Agent raises
|
|
283
|
+
`AgentBusyError`. Runtime/EventLoop has already acquired the process-local
|
|
284
|
+
logical execution slot on the normal Phronomy path before this repository method
|
|
285
|
+
runs.
|
|
262
286
|
|
|
263
287
|
`load` returns `Phronomy::Agent::AgentExecution`, not a raw database Hash, and
|
|
264
288
|
raises `NotFoundError` when missing.
|
|
@@ -278,17 +302,19 @@ A failed precondition raises `ConflictError`.
|
|
|
278
302
|
`assert_idle!` is used inside transactions before Agent context/Knowledge changes
|
|
279
303
|
and destructive operations. It must raise `AgentBusyError` if an active/suspended
|
|
280
304
|
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.
|
|
305
|
+
boundary that cannot race with durable Agent execution admission; a best-effort
|
|
306
|
+
SELECT outside the transaction is not sufficient. Process-local Runtime
|
|
307
|
+
admission is an additional upstream coordination layer, not a replacement for
|
|
308
|
+
this durable check.
|
|
283
309
|
|
|
284
310
|
## Workflow states repository
|
|
285
311
|
|
|
286
312
|
Required surface:
|
|
287
313
|
|
|
288
314
|
```ruby
|
|
289
|
-
def load(
|
|
290
|
-
def save(
|
|
291
|
-
def delete(
|
|
315
|
+
def load(workflow_instance_id)
|
|
316
|
+
def save(workflow_instance_id, expected_revision:, snapshot:)
|
|
317
|
+
def delete(workflow_instance_id, expected_revision:)
|
|
292
318
|
```
|
|
293
319
|
|
|
294
320
|
`load` returns `nil` when no row exists. Otherwise it returns a Hash containing a
|
|
@@ -315,6 +341,37 @@ snapshot and revision. String or Symbol Hash keys are accepted by Phronomy:
|
|
|
315
341
|
|
|
316
342
|
Caller mutation of a loaded snapshot must not mutate durable storage.
|
|
317
343
|
|
|
344
|
+
### Workflow Runtime admission and terminal-save outcome
|
|
345
|
+
|
|
346
|
+
Same-process Workflow admission is owned by Runtime/EventLoop, not by this
|
|
347
|
+
repository. EventLoop acquires an opaque owner token for `workflow_instance_id`
|
|
348
|
+
before mutable durable load/hydration, then binds a separately generated
|
|
349
|
+
`fsm_session_id` only after the concrete FSMSession is constructed.
|
|
350
|
+
|
|
351
|
+
A durable Workflow terminal/halt snapshot is saved through OffloadPool while the
|
|
352
|
+
owning FSMSession remains nonterminal. The backend still implements only the
|
|
353
|
+
synchronous `save` contract above; it does not post Runtime events or decide FSM
|
|
354
|
+
state.
|
|
355
|
+
|
|
356
|
+
Phronomy interprets terminal-save results by semantic certainty:
|
|
357
|
+
|
|
358
|
+
```text
|
|
359
|
+
known successful save
|
|
360
|
+
-> durable barrier may be crossed
|
|
361
|
+
|
|
362
|
+
portable known failure / known not committed
|
|
363
|
+
-> barrier remains closed; Workflow error path
|
|
364
|
+
|
|
365
|
+
arbitrary storage/transport failure whose commit outcome is not established
|
|
366
|
+
-> outcome unknown; barrier remains closed and Runtime fails closed
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
This distinction is independent of physical topology. A local backend can have
|
|
370
|
+
an uncertain outcome, and a remote backend can return a definite optimistic
|
|
371
|
+
conflict. Backends must therefore preserve meaningful portable errors when the
|
|
372
|
+
contract establishes them, and must surface other storage/transport failures
|
|
373
|
+
honestly rather than converting them into `ConflictError`.
|
|
374
|
+
|
|
318
375
|
### Workflow value serialization
|
|
319
376
|
|
|
320
377
|
`WorkflowContext#to_h` may contain ordinary Ruby application values. The
|
|
@@ -414,6 +471,17 @@ Phronomy::Agent::AgentExecution.from_h(hash)
|
|
|
414
471
|
String and Symbol top-level keys are accepted by these new execution/call codecs,
|
|
415
472
|
which permits adapters to use parsed JSON without reimplementing constructors.
|
|
416
473
|
|
|
474
|
+
The current canonical `JournalRecord` Hash does not contain `correlation_id`.
|
|
475
|
+
Legacy durable Journal Hashes that still contain that key may be passed to
|
|
476
|
+
`JournalRecord.from_h`; the legacy key is accepted and ignored. Backends must not
|
|
477
|
+
synthesize or populate `correlation_id` for new canonical Journal records, and
|
|
478
|
+
they are not required to eagerly rewrite existing durable rows solely to remove
|
|
479
|
+
the old physical value.
|
|
480
|
+
|
|
481
|
+
This is a targeted migration rule for the removed generic identity field. It does
|
|
482
|
+
not establish a general unknown-field or long-term codec/schema-versioning
|
|
483
|
+
policy.
|
|
484
|
+
|
|
417
485
|
The canonical Hash representation is the Phronomy/domain boundary. A backend is
|
|
418
486
|
free to map that representation to normalized SQL columns, JSON, or another
|
|
419
487
|
storage format internally.
|
|
@@ -457,6 +525,9 @@ RSpec.describe MyPersistenceBackend do
|
|
|
457
525
|
it_behaves_like "a Journal repository"
|
|
458
526
|
it_behaves_like "an Execution repository"
|
|
459
527
|
it_behaves_like "a workflow state repository"
|
|
528
|
+
it_behaves_like "a Handoff state repository"
|
|
529
|
+
it_behaves_like "a Team repository"
|
|
530
|
+
it_behaves_like "a Team execution repository"
|
|
460
531
|
it_behaves_like "a Persistence backend"
|
|
461
532
|
end
|
|
462
533
|
```
|
|
@@ -467,10 +538,12 @@ require wrappers only; the authoritative shared-example implementations live
|
|
|
467
538
|
under `lib/phronomy/testing/persistence_contract/` so the core suite and external
|
|
468
539
|
backends cannot drift through copied definitions.
|
|
469
540
|
|
|
470
|
-
The generic suite verifies repository behavior, CAS semantics,
|
|
471
|
-
mutation isolation, and whole-backend transaction behavior.
|
|
472
|
-
|
|
473
|
-
|
|
541
|
+
The generic suite verifies repository behavior, CAS semantics, durable execution
|
|
542
|
+
admission, mutation isolation, and whole-backend transaction behavior. Runtime
|
|
543
|
+
same-process ownership/admission is tested separately because it is not a
|
|
544
|
+
Persistence Backend SPI responsibility. Database-specific concurrency/locking
|
|
545
|
+
mechanisms remain backend integration-test concerns; the SPI specifies outcomes
|
|
546
|
+
rather than a particular SQL locking strategy.
|
|
474
547
|
|
|
475
548
|
## SQL implementation guidance
|
|
476
549
|
|
|
@@ -481,24 +554,40 @@ may use combinations of:
|
|
|
481
554
|
- conditional `UPDATE ... WHERE revision = ?`;
|
|
482
555
|
- row locks;
|
|
483
556
|
- serializable/repeatable-read isolation where appropriate;
|
|
484
|
-
- partial unique indexes for active Agent execution admission;
|
|
557
|
+
- partial unique indexes for durable active Agent execution admission;
|
|
485
558
|
- transaction-scoped checks for Agent revision + Journal head.
|
|
486
559
|
|
|
487
560
|
Backend-specific database exceptions should be translated to the Phronomy error
|
|
488
561
|
contract where their meaning is known.
|
|
489
562
|
|
|
490
|
-
##
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
563
|
+
## V2 coordination Backend SPI (clean break)
|
|
564
|
+
|
|
565
|
+
Both `Persistence.new` and `build_transaction_view` require all eight raw
|
|
566
|
+
repositories. Five-repository fallback is removed. Existing record formats remain
|
|
567
|
+
`0.1`; three additional record types are `phronomy.handoff_state`,
|
|
568
|
+
`phronomy.team_root`, and `phronomy.team_execution`. Backend index metadata is
|
|
569
|
+
explicit and MUST NOT be reconstructed by parsing DurableRecord payloads.
|
|
570
|
+
|
|
571
|
+
| Raw repository | Required operations and explicit metadata |
|
|
572
|
+
|---|---|
|
|
573
|
+
| `handoff_states` | `load(main_agent_id)`; `save(main_agent_id, expected_revision:, next_revision:, active_agent_id:, record:)`; `delete(main_agent_id, expected_revision:)` |
|
|
574
|
+
| `teams` | `create(team_id:, team_revision:, record:)`; `load(team_id)`; `save(team_id, expected_revision:, next_revision:, record:)`; `delete(team_id)` |
|
|
575
|
+
| `team_executions` | `create_active(team_execution_id:, team_id:, execution_revision:, record:)`; `load(id)`; `save(id, expected_revision:, next_revision:, team_id:, active:, record:)`; `list_active(team_id)`; `list(team_id, after: nil, limit: 100)`; `delete(id)`; `delete_for_team(team_id)`; `assert_idle!(team_id)` |
|
|
576
|
+
| `executions` extension | `list(agent_id, after: nil, limit: 100)` for retained active and terminal records |
|
|
577
|
+
|
|
578
|
+
Handoff starts at revision 1 with expected revision nil; later saves advance
|
|
579
|
+
exactly one revision. Team roots and executions begin at revision 0. Team
|
|
580
|
+
admission has the existing `AgentBusyError` contract for an already active owner;
|
|
581
|
+
stale CAS/duplicate identity uses `ConflictError`. Missing Team/execution loads
|
|
582
|
+
raise `NotFoundError`; absent Handoff state returns nil. Unavailable reads and
|
|
583
|
+
codec errors must propagate separately.
|
|
584
|
+
|
|
585
|
+
Lists sort IDs lexically, use an exclusive ID cursor, return at most the positive
|
|
586
|
+
integer limit, include only the requested owner, and return immutable copies.
|
|
587
|
+
Backends define retention; enumeration does not implement input correlation or
|
|
588
|
+
request deduplication. Terminal-to-active rewrites are invalid.
|
|
589
|
+
|
|
590
|
+
For F1, atomic commit does not imply a known response. Phronomy reads back the
|
|
591
|
+
same intended ID/fact before advancing. A backend must provide authoritative
|
|
592
|
+
reads/CAS; failure of readback is returned without new semantic work. No callback
|
|
593
|
+
ACK/index is part of the SPI. Existing cross-process exclusion limitations remain.
|
|
@@ -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`.
|