phronomy 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +122 -1134
  3. data/README.md +70 -1280
  4. data/benchmark/bench_regression.rb +25 -5
  5. data/docs/changelog/0.14-and-earlier.md +1137 -0
  6. data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
  7. data/docs/decisions/009-state-store-abstraction.md +1 -1
  8. data/docs/decisions/010-cooperative-first-concurrency.md +155 -235
  9. data/docs/decisions/014-unified-persistence-durable-state.md +268 -0
  10. data/docs/features.md +93 -0
  11. data/docs/getting-started.md +387 -0
  12. data/docs/migrations/0.15.md +35 -0
  13. data/docs/migrations/0.16.md +43 -0
  14. data/docs/migrations/0.19.md +148 -0
  15. data/docs/runtime-and-concurrency.md +349 -0
  16. data/examples/workflows/generic_task_event_mapping.rb +14 -6
  17. data/lib/phronomy/agent/agent_invocation_session_builder.rb +2 -2
  18. data/lib/phronomy/agent/async_event_api.rb +3 -3
  19. data/lib/phronomy/agent/base.rb +116 -55
  20. data/lib/phronomy/agent/context/capability/base.rb +13 -3
  21. data/lib/phronomy/agent/context_assembler.rb +13 -3
  22. data/lib/phronomy/agent/execution_coordinator.rb +423 -252
  23. data/lib/phronomy/agent/journal_projection.rb +5 -1
  24. data/lib/phronomy/agent/shared_state.rb +2 -0
  25. data/lib/phronomy/agent/tool_executor.rb +29 -71
  26. data/lib/phronomy/agent/tool_invocation.rb +97 -47
  27. data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
  28. data/lib/phronomy/configuration.rb +7 -30
  29. data/lib/phronomy/diagnostics.rb +12 -41
  30. data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
  31. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
  32. data/lib/phronomy/engine/concurrency/cancellation_token.rb +48 -3
  33. data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
  34. data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
  35. data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
  36. data/lib/phronomy/engine/event_loop.rb +170 -193
  37. data/lib/phronomy/engine/fsm_session.rb +6 -4
  38. data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
  39. data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
  40. data/lib/phronomy/engine/runtime.rb +52 -158
  41. data/lib/phronomy/engine/task.rb +136 -277
  42. data/lib/phronomy/llm_adapter/base.rb +14 -14
  43. data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
  44. data/lib/phronomy/llm_adapter.rb +2 -2
  45. data/lib/phronomy/metrics.rb +15 -30
  46. data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
  47. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
  48. data/lib/phronomy/multi_agent/handoff.rb +1 -0
  49. data/lib/phronomy/multi_agent/orchestrator.rb +147 -99
  50. data/lib/phronomy/multi_agent/team_coordinator.rb +2 -0
  51. data/lib/phronomy/persistence/in_memory.rb +113 -8
  52. data/lib/phronomy/persistence.rb +12 -3
  53. data/lib/phronomy/testing/eval/comparison.rb +23 -0
  54. data/lib/phronomy/testing/eval/dataset.rb +27 -0
  55. data/lib/phronomy/testing/eval/eval_case.rb +13 -0
  56. data/lib/phronomy/testing/eval/eval_result.rb +16 -0
  57. data/lib/phronomy/testing/eval/metrics.rb +43 -0
  58. data/lib/phronomy/testing/eval/runner.rb +52 -0
  59. data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
  60. data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
  61. data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
  62. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
  63. data/lib/phronomy/testing/eval/scorer.rb +10 -0
  64. data/lib/phronomy/testing/eval.rb +9 -0
  65. data/lib/phronomy/testing/fake_clock.rb +6 -53
  66. data/lib/phronomy/testing.rb +2 -6
  67. data/lib/phronomy/tools/agent.rb +141 -6
  68. data/lib/phronomy/vector_store/async_backend.rb +21 -17
  69. data/lib/phronomy/vector_store/base.rb +2 -2
  70. data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
  71. data/lib/phronomy/version.rb +1 -1
  72. data/lib/phronomy/workflow.rb +10 -9
  73. data/lib/phronomy/workflow_runner.rb +363 -99
  74. data/lib/phronomy.rb +10 -4
  75. data/scripts/api_snapshot.rb +4 -4
  76. metadata +25 -33
  77. data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +0 -561
  78. data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
  79. data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
  80. data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
  81. data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
  82. data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
  83. data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
  84. data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
  85. data/lib/phronomy/engine/task/backend.rb +0 -80
  86. data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
  87. data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
  88. data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
  89. data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
  90. data/lib/phronomy/engine/task/thread_backend.rb +0 -84
  91. data/lib/phronomy/engine/task_group.rb +0 -193
  92. data/lib/phronomy/eval/comparison.rb +0 -47
  93. data/lib/phronomy/eval/dataset.rb +0 -45
  94. data/lib/phronomy/eval/eval_case.rb +0 -17
  95. data/lib/phronomy/eval/eval_result.rb +0 -29
  96. data/lib/phronomy/eval/metrics.rb +0 -66
  97. data/lib/phronomy/eval/runner.rb +0 -94
  98. data/lib/phronomy/eval/scorer/base.rb +0 -22
  99. data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
  100. data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
  101. data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
  102. data/lib/phronomy/eval/scorer.rb +0 -9
  103. data/lib/phronomy/eval.rb +0 -7
  104. data/lib/phronomy/state_store/base.rb +0 -48
  105. data/lib/phronomy/state_store/in_memory.rb +0 -62
  106. data/lib/phronomy/testing/fake_scheduler.rb +0 -104
  107. data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
@@ -0,0 +1,268 @@
1
+ # ADR 014: Unified Persistence for Durable State
2
+
3
+ **Status**: Accepted
4
+ **Date**: 2026-08-14
5
+ **Supersedes**: ADR-009
6
+
7
+ ---
8
+
9
+ ## Context
10
+
11
+ Phronomy historically had two durable-state abstractions:
12
+
13
+ - `Phronomy::Persistence` for Agent roots, Journals, content and executions;
14
+ - `Phronomy::StateStore` for Workflow snapshots.
15
+
16
+ At the same time, `Persistence` exposed `activations`, although
17
+ `AgentExecutionActivation` contains live process state such as the active
18
+ AgentInvocation, FSMSession, runtime projection, callbacks and uncommitted runtime
19
+ facts. That state is not a durable database record.
20
+
21
+ Keeping two durable backend abstractions makes SQL/database backends implement the
22
+ same responsibility twice. Treating live Activations as Persistence also mixes
23
+ process-local execution ownership with durable storage ownership.
24
+
25
+ A second problem is state freshness. Re-reading mutable Agent records from
26
+ Persistence before every semantic boundary makes the durable copy act as the
27
+ source of truth even while a live Agent instance is executing. That makes
28
+ ownership ambiguous and encourages implicit merge/reload semantics.
29
+
30
+ Workflow runtime identity also needs to be distinct from its durable identity.
31
+ `InvocationContext#session_id` already means application session metadata (for
32
+ example a Rails session), while Workflow `thread_id` identifies durable Workflow
33
+ state. Neither value is the identity of one concrete FSMSession execution.
34
+
35
+ ## Decision
36
+
37
+ ### Persistence is the only durable backend abstraction
38
+
39
+ `Phronomy::Persistence` exposes the durable repositories:
40
+
41
+ ```text
42
+ contents
43
+ agents
44
+ journals
45
+ executions
46
+ workflow_states
47
+ ```
48
+
49
+ `Phronomy::StateStore` is removed without a compatibility adapter. Workflow
50
+ persistence is configured with `persistence:` or
51
+ `Phronomy.configuration.persistence` and stored through
52
+ `Persistence#workflow_states`. Agent `new`/`create` also uses the global
53
+ Persistence when no explicit backend is injected, so an application can select
54
+ one durable backend for both domains. Explicit injection takes precedence.
55
+
56
+ The `workflow_states` repository uses optimistic revision metadata:
57
+
58
+ ```ruby
59
+ record = persistence.workflow_states.load(thread_id)
60
+ # => nil or { snapshot: ..., revision: Integer }
61
+
62
+ persistence.workflow_states.save(
63
+ thread_id,
64
+ expected_revision: record&.fetch(:revision),
65
+ snapshot: snapshot
66
+ )
67
+ ```
68
+
69
+ A stale expected revision raises `Phronomy::Persistence::ConflictError`. Upper
70
+ layers do not automatically reload and merge after such a conflict.
71
+
72
+ ### Activation is transient Runtime state
73
+
74
+ `AgentExecutionActivation` is not part of the Persistence contract. Live
75
+ Activations are held by a Runtime-local `Agent::ActivationRegistry`.
76
+
77
+ Durable execution rehydration is a separate future capability. When no live
78
+ Activation exists, approval resume raises
79
+ `ExecutionRehydrationRequiredError`; the framework does not silently construct a
80
+ new Agent and pretend that the process-local execution was recovered.
81
+
82
+ ### A live Agent instance owns its logical current state
83
+
84
+ After hydration, the live Agent instance and its Activation are authoritative for
85
+ mutable logical state:
86
+
87
+ ```text
88
+ Agent instance
89
+ ├─ current AgentRoot
90
+ ├─ hydrated Journal/context view
91
+ └─ active execution entry
92
+
93
+ AgentExecutionActivation
94
+ ├─ current AgentExecution
95
+ ├─ runtime projection
96
+ ├─ Provider results
97
+ ├─ Tool/runtime events
98
+ └─ active AgentInvocation / call state
99
+ ```
100
+
101
+ Persistence is the last committed durable representation and recovery source.
102
+ Normal LLM/Tool cycles do not re-read mutable AgentRoot, AgentExecution or Journal
103
+ records merely to obtain freshness. Successful optimistic commits advance the
104
+ local root/execution/Journals; failed revision checks surface a conflict.
105
+
106
+ Before a next-LLM durable barrier, the backend verifies the live owner's Agent
107
+ revision and Journal position as a durable watermark. This check is a precondition
108
+ only: it does not return replacement state to the live Agent. If another writer
109
+ has advanced either value, the barrier raises `ConflictError` and no Provider
110
+ Call starts from the stale local owner.
111
+
112
+ Content-addressed `contents` are immutable values and may still be dereferenced by
113
+ content reference on demand. That is not a mutable-state refresh.
114
+
115
+ Context Policy therefore sees the current local logical state rather than a
116
+ Persistence readback. Runtime facts captured after a Persistence snapshot remain
117
+ in the Activation and are eligible at the next semantic boundary.
118
+
119
+ ### Approval resumes the same owner
120
+
121
+ An approval suspension retains the original Agent instance,
122
+ `AgentExecutionActivation` and `AgentInvocation`. Instance-level
123
+ `agent.approve` / `agent.approve_async` resume that live Activation.
124
+
125
+ When an application has only an `execution_id`, it resolves the current process's
126
+ live owner first:
127
+
128
+ ```text
129
+ execution_id
130
+ -> Agent::Base.live_for_execution(...)
131
+ -> Runtime ActivationRegistry
132
+ -> live Activation
133
+ -> activation.agent
134
+ ```
135
+
136
+ A concrete Agent class may call `MyAgent.live_for_execution(execution_id)` to
137
+ also verify that the live owner is an instance of that class. The lookup does not
138
+ load Agent or Execution state from Persistence. If the live Activation no longer
139
+ exists, `ExecutionRehydrationRequiredError` is raised rather than silently
140
+ rehydrating another owner.
141
+
142
+ Approval remains an Agent-instance operation after lookup:
143
+
144
+ ```ruby
145
+ agent = MyAgent.live_for_execution(execution_id)
146
+ agent.approve_async(execution_id, approval_request_id: request_id)
147
+ ```
148
+
149
+ ### Workflow has three distinct identities
150
+
151
+ The following identities must not be conflated:
152
+
153
+ ```text
154
+ application session_id
155
+ caller/tracing identity, e.g. Rails session
156
+
157
+ thread_id
158
+ durable Workflow identity / workflow_states key
159
+
160
+ fsm_session_id
161
+ Runtime-only identity of one FSMSession invocation or resume
162
+ ```
163
+
164
+ Each Workflow invocation/resume receives a fresh `fsm_session_id`. The EventLoop
165
+ registers the session by that ID.
166
+
167
+ For a Workflow with a durable `thread_id`, EventLoop also owns a separate
168
+ admission table:
169
+
170
+ ```text
171
+ thread_id -> owner_fsm_session_id
172
+ ```
173
+
174
+ Admission is acquired before Persistence load and held through FSM execution and
175
+ terminal snapshot save. Release succeeds only when the supplied
176
+ `owner_fsm_session_id` is the current owner. A failed competing invocation can
177
+ therefore never release another invocation's reservation.
178
+
179
+ The admission table belongs to one Runtime and is process-local. It prevents
180
+ competing execution of the same `thread_id` inside that Runtime, but it is not a
181
+ distributed lease and is not shared by multiple Ruby processes, containers, or
182
+ service replicas. Two processes may therefore execute the same durable
183
+ `thread_id` concurrently if an application routes work that way.
184
+
185
+ `workflow_states` optimistic revisions still detect stale/double commits between
186
+ those processes. They do not prevent both executions from starting and cannot
187
+ roll back external side effects already performed before one terminal save loses
188
+ the revision race. CAS is therefore stale/double-commit protection, not
189
+ distributed execution exclusion or duplicate-side-effect prevention.
190
+
191
+ `fsm_session_id` is Runtime metadata. It is not an application Workflow field and
192
+ is not stored in `workflow_states` snapshots.
193
+
194
+ ### Persistence I/O stays off EventLoop
195
+
196
+ The Persistence repository contract remains synchronous. Persistence operations
197
+ started by EventLoop-driven Workflow/Agent lifecycle code execute through the
198
+ bounded OffloadPool. Completion is converted back into an EventLoop event where
199
+ lifecycle ordering requires it.
200
+
201
+ No worker waits synchronously for an FSMSession or Task that requires EventLoop
202
+ progress.
203
+
204
+ ### Backend synchronization is an implementation detail
205
+
206
+ `Persistence::InMemory` uses one shared `Monitor` for all durable repositories,
207
+ including `workflow_states`. Workflow values retain StateStore-like deep-copy
208
+ semantics and are kept outside the Marshal snapshot used by the existing Agent
209
+ state container, while both remain inside the same transaction domain.
210
+
211
+ A SQL backend should implement the same repository/transaction contract using DB
212
+ transactions, isolation and optimistic revision constraints. Mutex/Monitor and
213
+ Ruby object identity are not part of the public repository contract.
214
+
215
+ ## Consequences
216
+
217
+ ### Positive
218
+
219
+ - Agent and Workflow durability share one backend configuration and transaction
220
+ abstraction.
221
+ - Process-local Activation state is no longer presented as durable Persistence.
222
+ - Active Agent ownership is explicit; Context Policy freshness no longer depends
223
+ on repeated mutable-state reloads.
224
+ - Approval owner lookup and approval execution are separate: class-level lookup
225
+ resolves the existing live Agent, while approval remains an instance operation.
226
+ - Revision conflicts are visible instead of being hidden by automatic merge.
227
+ - Workflow durable identity is independent of one Runtime FSMSession execution.
228
+ - Owner-aware Workflow admission closes the stale-load window during terminal
229
+ save inside one Runtime.
230
+ - InMemory and future SQL backends can share one contract test suite.
231
+
232
+ ### Trade-offs
233
+
234
+ - Removing `StateStore` is a pre-1.0 breaking change.
235
+ - A process restart cannot resume an approval until durable Activation/FSM
236
+ rehydration is implemented separately.
237
+ - Runtime Activation lookup and Workflow admission are process-local. Applications
238
+ requiring cross-process ownership or duplicate-execution prevention need a
239
+ separate routing/lease/fencing design.
240
+ - Optimistic Workflow revisions can reject a stale commit but cannot undo
241
+ duplicate external side effects performed before that conflict is observed.
242
+ - Applications that intentionally allow another process to edit the same live
243
+ Agent or Workflow must handle `ConflictError` and explicitly reload/reconcile.
244
+ - `Persistence` implementations must provide `workflow_states` and optimistic
245
+ revision semantics.
246
+
247
+ ## Rejected alternatives
248
+
249
+ ### Keep StateStore as a compatibility facade
250
+
251
+ Rejected. It preserves the duplicate durable-backend abstraction and makes future
252
+ backend behavior harder to reason about.
253
+
254
+ ### Reload Agent state before every LLM call
255
+
256
+ Rejected. It makes Persistence the live source of truth, creates implicit merge
257
+ semantics and conflicts with Agent-instance ownership.
258
+
259
+ ### Use Workflow thread_id as FSMSession id
260
+
261
+ Rejected. Durable identity and one Runtime execution have different lifetimes.
262
+ It also makes owner-aware admission and application `session_id` terminology
263
+ ambiguous.
264
+
265
+ ### Release Workflow admission by thread_id only
266
+
267
+ Rejected. Cleanup from an invocation that failed to acquire admission could then
268
+ release the real owner's reservation. Release is owner-aware by construction.
data/docs/features.md ADDED
@@ -0,0 +1,93 @@
1
+ # Features and API stability
2
+
3
+ API means Application Programming Interface in this document.
4
+
5
+ Phronomy is pre-1.0. Minor releases may include breaking changes even to APIs
6
+ labelled Stable; patch releases should remain non-breaking. Consult
7
+ [`CHANGELOG.md`](../CHANGELOG.md) when upgrading.
8
+
9
+ - **Stable** — API is considered complete and suitable for production use.
10
+ - **Beta** — functionality is complete and tested, but signatures or behaviour may evolve in a minor release.
11
+ - **Experimental** — functionality may change without notice and is not recommended as a long-term compatibility boundary.
12
+
13
+ The `main` branch contains unreleased development work. Pin a released gem version
14
+ for production deployments.
15
+
16
+ ## Core building blocks
17
+
18
+ | Feature | Stability |
19
+ |---|---|
20
+ | **Workflow** — Stateful, branching workflows with `wait_state` and explicit events | Stable |
21
+ | **Agent** — Stateful ReAct-style agents with stable `agent_id`, persistence-backed execution state, canonical history, guardrails, and conversation context | Stable |
22
+ | **Unified Persistence** — One durable backend abstraction for Agent state and Workflow `workflow_states`; live Agent/Workflow state remains owned by the active instance/session between durable commits | Beta |
23
+ | **Before-Large-Language-Model (LLM) Input Hook** — Three-tier per-call LLM input customization via `before_llm_input` and `LLMInputPatch` | Stable |
24
+ | **Context Management** — Journal + Context Policy + per-LLM-call Manifest with token-budget-aware selection and protocol-safe Tool Call / Tool message dependencies | Stable |
25
+ | **Filters** — Input/output transformation and blocking via `Filter::Base` | Beta |
26
+ | **`PromptInjectionFilter`** — Built-in pattern-based prompt-injection filter | Beta |
27
+ | **Capability redaction/result-size controls** — `redact_params` and `max_result_size` | Beta |
28
+ | **Output Parser** — JSON and Struct-mapped parsers for structured LLM responses | Stable |
29
+ | **Tracing** — Pluggable span-based observability | Stable |
30
+ | **Error Taxonomy** — Provider errors translated to Phronomy transport/authentication/rate-limit/context errors | Beta |
31
+
32
+ ## Knowledge and integration
33
+
34
+ | Feature | Stability |
35
+ |---|---|
36
+ | **Knowledge** — Journal-backed persistent Agent context registered with `knowledge:` / `add_knowledge`, selected per LLM call by Context Policy | Beta |
37
+ | **`VectorStore#size`** — Document count for InMemory, RedisSearch, and Pgvector backends | Beta |
38
+ | **`VectorStore::AsyncBackend`** — Pluggable async VectorStore interface with pool-backed defaults and native-async override points | Beta |
39
+ | **Model Context Protocol (MCP) Tool** — `Phronomy::Tools::Mcp` integration through the official `mcp` gem | Beta |
40
+ | **Agent Tool** — `Phronomy::Tools::Agent.from_agent` exposes a child Agent as a Tool without occupying a worker while waiting | Beta |
41
+ | **Vector Search Tool** — `Phronomy::Tools::VectorSearch` wraps VectorStore and Embeddings adapters | Beta |
42
+
43
+ ## Execution and reliability
44
+
45
+ | Feature | Stability |
46
+ |---|---|
47
+ | **EventLoop** — Runtime-owned event-driven execution core shared by Agent, ToolInvocation, Workflow, and MultiAgent sessions | Beta |
48
+ | **Workflow durable admission** — Durable `thread_id` ownership is held by a Runtime-only `fsm_session_id` from load through terminal save | Beta |
49
+ | **`invoke` / `invoke_async`** — Blocking and non-blocking Agent/Workflow entry points | Stable |
50
+ | **Agent async events** — `invoke_async(..., on_event:)` and `stream_async(..., on_event:)`; streaming additionally emits `:token` | Beta |
51
+ | **`stream` / `stream_async`** — Event callbacks execute on EventLoop and must return quickly | Beta |
52
+ | **`stream_callback_error_policy`** — Terminal event callback error policy (`:report` / `:fail_task`) | Beta |
53
+ | **`Task#map`** — Application-level Task result transformation and error propagation | Stable |
54
+ | **CancellationToken** — Cooperative cancellation with explicit `cancel!`, lazy monotonic deadlines, and callback registration | Experimental |
55
+ | **Tool `execution_mode`** — `:cooperative` for short EventLoop-safe work; `:offloaded` for synchronous work that must stay off EventLoop | Experimental |
56
+ | **OffloadPool sizing** — `offload_pool_size` / `offload_queue_size`; named pools available for application-owned isolation | Beta |
57
+ | **InvocationContext** — Explicit correlation/cancellation/deadline context for Agent and Workflow invocations | Beta |
58
+ | **Metrics** — OffloadPool active/queue/abandoned metrics plus EventLoop queue/lag metrics | Beta |
59
+ | **Runtime lifecycle** — Runtime-owned EventLoop and terminal `Runtime#shutdown` | Beta |
60
+
61
+ ## Agent and workflow patterns
62
+
63
+ | Feature | Stability |
64
+ |---|---|
65
+ | **Workflow asynchronous pattern** — Start async work, return immediately, and continue through `Workflow#signal` | Beta |
66
+ | **Multi-agent** — Agent-as-Tool and hub-and-spoke handoff routing | Beta |
67
+ | **GeneratorVerifier** — Generator-Verifier loop with injectable prompts/parsers | Beta |
68
+ | **`Phronomy::MultiAgent::Orchestrator`** — Parallel subagent dispatch, fan-out, and `subagent` DSL | Beta |
69
+ | **`Phronomy::MultiAgent::TeamCoordinator`** — LLM coordinator with stateful worker Agents | Beta |
70
+ | **SharedState** — Peer-agent shared-state coordination | Experimental |
71
+ | **Human-in-the-loop approval** — Suspension and approval/resume of Tool requests on the same live Agent/Activation owner | Beta |
72
+ | **`tool_approval_policy`** — Application-defined allow/approve/reject policy | Beta |
73
+
74
+ ## Public API boundary
75
+
76
+ The feature tables above describe the primary APIs intended for gem consumers.
77
+ Source declarations marked `@api private`, including most EventLoop/FSMSession and
78
+ OffloadPool internals, are implementation details and may change without the same
79
+ compatibility guarantees.
80
+
81
+ `Phronomy::StateStore` is no longer a public backend abstraction. Workflow
82
+ durability is provided through `Phronomy::Persistence#workflow_states`; see the
83
+ 0.19 migration guide when upgrading code that used `state_store:`.
84
+
85
+ ## Advanced and internal APIs
86
+
87
+ | Feature | Stability |
88
+ |---|---|
89
+ | **`Phronomy::Diagnostics`** — Snapshot of EventLoop lag/queue state and OffloadPool activity | Experimental |
90
+ | **`Phronomy::Testing::FakeClock`** — Test-only deterministic clock helper | Beta |
91
+
92
+ For runtime ownership and the distinction between public lifecycle APIs and
93
+ private execution machinery, see [Runtime and concurrency](runtime-and-concurrency.md).