phronomy 0.20.0 → 0.21.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a9f9f2ab4acdb4e17edeb4e0b7f0b33660b7102e5b345c2bd8d411e6b0760de
4
- data.tar.gz: 331265c52dfadbe4be8f85b9a48b35de384042664538f4fa74db0d34829e3b62
3
+ metadata.gz: 1da8f29678594e3b2c0b8db70e6485f3576ffe72bc19db0dd340abefbdc3118b
4
+ data.tar.gz: 8dbf2cacbca536c99b7552f3494edf208986b9e45c3817fac700da445d27d2c3
5
5
  SHA512:
6
- metadata.gz: f3c7e0b15ede3129a2ac426ed86ebde36e353c664b5c64fc559878996ab6331b86f88c289e2f47179f0d1c58e91ec23c0c3d23060f066168c1a441e349dd534f
7
- data.tar.gz: 527e858a300d3e8fb305782cdab29d6195b3836686df5b4d41a7abf8a40b584de8a8f0dcdab8ccd5b7b5dcfbcbf0c0dad45a7be2f092eae69c2c643ff69e9734
6
+ metadata.gz: b513420381c3eb137470d834baca90938daded756b6d3e480eed295c592b9ada062f565eaf0a77c175075378fc747080aa7c8b6bf3ccd72da22dd0c4037e2608
7
+ data.tar.gz: 6cea982e98ef21605ff8b55de55a6693fd2b3c3002c0212c8c9c4912f68c5bbd74204669cea3658ec7c174f506e19bda5b5f1084cccee78fa49615b181863941
data/.mutant.yml CHANGED
@@ -9,7 +9,9 @@ matcher:
9
9
  subjects:
10
10
  - Phronomy::WorkflowContext
11
11
  - Phronomy::WorkflowRunner
12
- - Phronomy::Tool::Base
12
+ # Phronomy::Tool::Base is a public constant alias to this same Class object.
13
+ # Use the implementation canonical name returned by Class#name for Mutant.
14
+ - Phronomy::Agent::Context::Capability::Base
13
15
  - Phronomy::LlmContextWindow::TokenBudget
14
16
  - Phronomy::Agent::ContextAssembler
15
17
  - Phronomy::Agent::ContextPolicies::Default
data/CHANGELOG.md CHANGED
@@ -37,6 +37,31 @@ Release history for 0.14.0 and earlier is archived in
37
37
  - `Persistence#activations`; ActivationRegistry is transient Runtime state.
38
38
  - Class-level `Agent::Base.approve` / `Agent::Base.approve_async` routing APIs and their caller-supplied `persistence:` argument; approval execution now goes through the resolved live Agent instance.
39
39
 
40
+ ### Public API façade and typed contracts
41
+
42
+ #### Added
43
+
44
+ - `Phronomy::Tool::Base` as the application-facing Tool authoring façade. It is
45
+ an exact alias of `Phronomy::Agent::Context::Capability::Base`, so existing
46
+ Tool definitions remain compatible and share the same class identity/DSL state.
47
+ - Initial hand-written RBS signatures for the primary application API and
48
+ explicit extension SPIs, with a dedicated RBS validation CI job.
49
+ - ADR-015 defining Tool façade ownership, the LLMAdapter SPI, RBS scope, and
50
+ extension dependency direction.
51
+
52
+ #### Changed
53
+
54
+ - `Phronomy::LLMAdapter::Base#complete` and `#stream` are explicit Beta extension
55
+ SPI methods. Phronomy continues to own `complete_async` / `stream_async` and
56
+ OffloadPool integration.
57
+ - VectorStore and Embeddings backends expose synchronous implementation contracts;
58
+ their framework-provided async convenience methods offload through Phronomy and
59
+ return `Task`.
60
+ - `InvocationContext` construction is classified consistently with its documented
61
+ Beta application API.
62
+ - OutputParser `parse` is classified as the public subclass extension point that
63
+ concrete parsers implement.
64
+
40
65
  ### OffloadPool execution model
41
66
 
42
67
  #### Changed
@@ -52,20 +77,22 @@ Release history for 0.14.0 and earlier is archived in
52
77
  - Framework-owned EventLoop-origin offload submissions use non-blocking queue
53
78
  admission so a full worker queue raises backpressure instead of blocking
54
79
  the EventLoop control thread.
55
- - Submit cancellation now settles the caller-facing `PendingOperation`
56
- immediately. Cancellation before worker start prevents execution;
57
- cancellation after worker start marks the operation abandoned while allowing
58
- the synchronous worker to finish without asynchronous `Thread#raise`.
80
+ - `OffloadPool#submit` now returns the common caller-facing `Phronomy::Task`.
81
+ OffloadPool queue/worker/timeout/abandonment state is held by a private
82
+ Operation record rather than by a second completion-handle class.
83
+ - Submit cancellation now settles the caller-facing `Task` immediately.
84
+ Cancellation before worker start prevents execution; cancellation after worker
85
+ start marks the private Operation abandoned while allowing the synchronous
86
+ worker to finish without asynchronous `Thread#raise`.
59
87
  - Monotonic deadlines carried by an OffloadPool submit cancellation token are
60
88
  promoted by the Runtime timer queue, so cancellation completion does not
61
89
  require a polling Thread.
62
90
  - Independent notification callbacks now isolate subscriber failures. An
63
- `StandardError` from one `CancellationToken#on_cancel`, `Task#on_complete`, or
64
- `PendingOperation#on_complete` callback is logged and does not suppress later
65
- subscribers.
66
- - `PendingOperation#blocking_wait(timeout:)` remains a waiter-local synchronous
67
- timeout only. It does not settle or cancel the operation; operation-wide
68
- cancellation is represented only by `OffloadPool#submit(cancellation_token:)`.
91
+ `StandardError` from one `CancellationToken#on_cancel` or `Task#on_complete`
92
+ callback is logged and does not suppress later subscribers.
93
+ - `Task#wait_result(timeout:)` is the waiter-local synchronous timeout bridge.
94
+ It does not settle or cancel the Task; operation-wide cancellation is
95
+ represented by the CancellationToken supplied to the creating API.
69
96
  - `offload_pool_abandoned_total` is the cumulative number of operations whose
70
97
  caller-facing submit timeout or cancellation settled after worker execution
71
98
  started. `offload_pool_abandoned_active` is the current number of those
@@ -78,8 +105,11 @@ Release history for 0.14.0 and earlier is archived in
78
105
  - Tool execution modes `:blocking_io`, `:cpu_bound`, and `:external_process`.
79
106
  - `Runtime#blocking_io`, `blocking_io_pool_size`, and
80
107
  `blocking_io_queue_size`.
108
+ - Caller-facing `OffloadPool::PendingOperation` and
109
+ `PendingOperation#blocking_wait`; asynchronous Phronomy APIs use `Task` as the
110
+ completion contract.
81
111
  - Waiter-local `cancellation_token:` from `PendingOperation#blocking_wait`.
82
- The low-level synchronous bridge continues to support waiter-local `timeout:`.
112
+ Operation-wide cancellation remains on `OffloadPool#submit(cancellation_token:)`.
83
113
 
84
114
 
85
115
  ### EventLoop-first runtime cleanup
data/CONTRIBUTING.md CHANGED
@@ -43,14 +43,28 @@ ruby scripts/check_japanese.rb
43
43
 
44
44
  ## Public API Changes
45
45
 
46
- When adding, removing, or renaming a public method or class:
46
+ When adding, removing, renaming, or changing the contract of a public API or
47
+ extension SPI:
47
48
 
48
- 1. Update the stability table in `README.md`.
49
- 2. Add or update `@api private` YARD annotations for internal APIs.
50
- 3. Regenerate the API compatibility snapshot:
49
+ 1. Update `docs/features.md` when the public feature/stability description changes.
50
+ 2. Add or update YARD `@api public` / `@api private` classification.
51
+ 3. Regenerate the API compatibility snapshot when the reflected compatibility
52
+ surface changes:
51
53
  ```bash
52
54
  bundle exec ruby scripts/api_snapshot.rb --write
53
55
  ```
56
+ 4. Update the corresponding hand-written `sig/**/*.rbs` signature when the
57
+ public type shape changes.
58
+ 5. Validate the RBS environment:
59
+ ```bash
60
+ bundle exec rbs -I sig validate
61
+ ```
62
+ 6. Add or update a focused spec for the public API or extension contract.
63
+
64
+ RBS is a typed representation of an API contract that has already been defined
65
+ by source behavior, YARD classification, documentation, and focused tests. Do
66
+ not make an internal method public, change runtime semantics, or invent a new
67
+ extension guarantee merely to make an RBS signature easier to write.
54
68
 
55
69
  ### `@api` classification vs Ruby visibility
56
70
 
@@ -82,6 +96,22 @@ contract rather than by a repository-wide visibility inference rule.
82
96
 
83
97
  Do not change Ruby visibility merely to make it match an `@api` annotation.
84
98
 
99
+ ### Async completion boundary
100
+
101
+ `Phronomy::Task` is the caller-facing completion handle. EventLoop/FSMSession and
102
+ OffloadPool are execution/continuation mechanisms, not competing completion
103
+ abstractions.
104
+
105
+ Synchronous work that requires execution away from EventLoop must use an
106
+ OffloadPool. Do not create production worker Threads in adapters, backends, or
107
+ Tools to emulate asynchronous behavior. Logical waits between Phronomy
108
+ lifecycles stay on EventLoop/FSMSession and settle a Task later.
109
+
110
+ The framework owns Task settlement (`complete`, `fail`, and framework-driven
111
+ cancellation). Application code should observe Tasks through `wait_result`,
112
+ `on_complete`, `map`, and settlement state. Operation-wide cancellation of
113
+ OffloadPool work is supplied through `CancellationToken`.
114
+
85
115
  ---
86
116
 
87
117
  ## Architecture Decision Records
@@ -91,7 +121,9 @@ Key design decisions are documented as ADRs in
91
121
  to the threading model, persistence/context authority, or public API shape.
92
122
 
93
123
  For Agent Context work, ADR-012 and ADR-013 define the current Journal,
94
- Manifest, Context Policy and persistent Knowledge model.
124
+ Manifest, Context Policy and persistent Knowledge model. ADR-010 defines the
125
+ EventLoop/FSMSession, Task, and OffloadPool execution boundary. ADR-015 defines
126
+ the Tool public façade, extension-SPI boundary, and RBS ownership rules.
95
127
 
96
128
  ---
97
129
 
@@ -119,7 +151,7 @@ The authoritative subject list is `.mutant.yml`. It currently includes:
119
151
 
120
152
  - `Phronomy::WorkflowContext`
121
153
  - `Phronomy::WorkflowRunner`
122
- - `Phronomy::Tool::Base`
154
+ - `Phronomy::Agent::Context::Capability::Base`
123
155
  - `Phronomy::LlmContextWindow::TokenBudget`
124
156
  - `Phronomy::Agent::ContextAssembler`
125
157
  - `Phronomy::Agent::ContextPolicies::Default`
@@ -130,6 +162,12 @@ The authoritative subject list is `.mutant.yml`. It currently includes:
130
162
  - `Phronomy::Agent::ContextPlanValidator`
131
163
  - `Phronomy::VectorStore::InMemory`
132
164
 
165
+ The Tool mutation subject intentionally uses
166
+ `Phronomy::Agent::Context::Capability::Base`, which is the implementation
167
+ canonical name returned by the single Class object's runtime `Class#name`.
168
+ `Phronomy::Tool::Base` is the public facade constant alias to that same Class
169
+ object; it is not a second Tool base class.
170
+
133
171
  When you add or modify tests for a covered subject, run mutation tests locally
134
172
  when practical and investigate meaningful score regressions.
135
173
 
data/README.md CHANGED
@@ -18,10 +18,10 @@ tracking `main` directly.
18
18
  - **Agent** — stateful, persistence-backed LLM agent with canonical execution history.
19
19
  - **Persistence** — unified durable backend for Agent state and Workflow `workflow_states`.
20
20
  - **Workflow** — state-machine-driven application workflow with explicit events and wait states.
21
- - **Tool / Capability** — callable application capability exposed to an Agent.
21
+ - **Tool / Capability** — callable application capability exposed to an Agent; application-defined Tools subclass `Phronomy::Tool::Base`.
22
22
  - **EventLoop + FSMSession** — the framework control plane for logical lifecycle coordination.
23
23
  - **OffloadPool** — bounded operating-system-thread execution boundary for synchronous work that must not run on EventLoop.
24
- - **Task** — thread-free completion handle for asynchronous Phronomy lifecycles.
24
+ - **Task** — the common thread-free completion handle returned by Phronomy asynchronous APIs, including OffloadPool-backed work.
25
25
  - **Journal / Context Policy / Manifest** — canonical history plus per-LLM-call context selection.
26
26
 
27
27
  See [Features and Application Programming Interface (API) stability](docs/features.md) for the full feature matrix.
@@ -57,7 +57,7 @@ dependencies, stateful Agent setup, streaming, and Workflow examples.
57
57
  ## Quick start
58
58
 
59
59
  ```ruby runnable
60
- class WebSearch < Phronomy::Agent::Context::Capability::Base
60
+ class WebSearch < Phronomy::Tool::Base
61
61
  description "Search the web"
62
62
  param :query, type: :string, desc: "Search query"
63
63
 
@@ -78,6 +78,10 @@ result = ResearchAgent.new.invoke("What happened in AI research this week?")
78
78
  puts result[:output]
79
79
  ```
80
80
 
81
+ `Phronomy::Tool::Base` is the public authoring name for the existing Tool base
82
+ class. The legacy `Phronomy::Agent::Context::Capability::Base` constant remains
83
+ valid for compatibility.
84
+
81
85
  For non-blocking top-level use, call `invoke_async` and keep the returned
82
86
  `Phronomy::Task`. Inside Phronomy lifecycle callbacks, do not block waiting for
83
87
  another Task; continue through explicit events instead.
@@ -89,7 +93,7 @@ result = task.wait_result # top-level/external caller only
89
93
 
90
94
  ## Runtime model
91
95
 
92
- Phronomy uses one explicit lifecycle model:
96
+ Phronomy uses one completion model with two execution mechanisms:
93
97
 
94
98
  ```text
95
99
  Runtime
@@ -100,13 +104,18 @@ Runtime
100
104
  │ ├─ ToolInvocation
101
105
  │ └─ MultiAgent fan-out
102
106
  ├─ OffloadPool
107
+ │ └─ synchronous off-EventLoop work
103
108
  └─ EventLoop-driven timers
104
109
 
105
- Task = completion handle
110
+ EventLoop / FSMSession ─┐
111
+ ├─> Task = completion handle
112
+ OffloadPool ────────────┘
106
113
  ```
107
114
 
108
115
  Logical waiting remains in EventLoop/FSMSession state. Synchronous work that
109
- would block EventLoop uses the bounded OffloadPool. See
116
+ would block EventLoop uses the bounded OffloadPool. OffloadPool-specific queue,
117
+ worker, timeout, and abandonment state remains private runtime machinery; callers
118
+ observe completion through `Phronomy::Task`. See
110
119
  [Runtime and concurrency](docs/runtime-and-concurrency.md) for the detailed
111
120
  contracts, timeout/cancellation semantics, metrics, and callback rules.
112
121
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Accepted — revised for the OffloadPool execution boundary.
5
+ Accepted — revised for a single Task completion contract and the OffloadPool
6
+ execution boundary.
6
7
 
7
8
  ## Context
8
9
 
@@ -11,28 +12,31 @@ and MultiAgent lifecycles without allocating one OS Thread per logical task.
11
12
  At the same time, application and third-party code may contain synchronous work
12
13
  that must not execute on the single Runtime EventLoop thread.
13
14
 
14
- Classifying arbitrary application work as I/O-bound, CPU-bound, or external
15
- process work is not a responsibility the framework can reliably infer. The
16
- architecturally relevant distinction for Phronomy is whether a unit of work can
17
- safely run to completion on EventLoop or must be moved off the control thread.
15
+ The architecturally relevant distinction is whether a unit of work can safely run
16
+ to completion on EventLoop or must be moved off the control thread. The way work
17
+ executes is separate from the object used by callers to observe completion.
18
18
 
19
19
  ## Decision
20
20
 
21
21
  Framework lifecycle coordination uses one Runtime-owned EventLoop and explicit
22
- FSMSession state/events. **Task is a completion handle**, not an execution
23
- backend.
22
+ FSMSession state/events. **`Phronomy::Task` is the single caller-facing completion
23
+ handle**, not an execution backend.
24
+
25
+ There are two framework execution mechanisms:
26
+
27
+ - **EventLoop / FSMSession** for logical lifecycle progression and waits;
28
+ - **OffloadPool** for synchronous work that must execute on bounded worker OS
29
+ Threads away from EventLoop.
30
+
31
+ Both mechanisms surface asynchronous completion as `Phronomy::Task`.
24
32
 
25
33
  Phronomy defines two Tool execution modes:
26
34
 
27
35
  - `:cooperative` — short, EventLoop-safe work, or a specialized asynchronous
28
36
  implementation that starts another Phronomy lifecycle and immediately returns
29
- a completion handle.
37
+ a Task;
30
38
  - `:offloaded` — synchronous work that must not run on EventLoop. It executes in
31
- the bounded `OffloadPool`.
32
-
33
- `OffloadPool` is the thread execution boundary for synchronous work that must be
34
- kept off EventLoop. It may contain blocking I/O, CPU-bound Ruby work, or other
35
- application-defined long-running synchronous calls.
39
+ the bounded OffloadPool and returns a Task.
36
40
 
37
41
  Workload classification such as I/O-bound versus CPU-bound is application-owned.
38
42
  Phronomy does not provide separate `:blocking_io`, `:cpu_bound`, or
@@ -53,23 +57,29 @@ Runtime
53
57
  │ ├─ ToolInvocation
54
58
  │ └─ MultiAgent fan-out
55
59
  ├─ OffloadPool (bounded OS Threads)
60
+ │ ├─ private Operation records
56
61
  │ ├─ blocking I/O
57
62
  │ ├─ CPU-bound synchronous work
58
63
  │ └─ other long synchronous work
59
64
  ├─ named OffloadPools
60
65
  └─ EventLoop-driven timers
61
66
 
62
- Task = completion handle
67
+ EventLoop / FSMSession ─┐
68
+ ├─> Task = completion handle
69
+ OffloadPool ────────────┘
63
70
  ```
64
71
 
72
+ OffloadPool's private Operation record owns the submitted block, queue/worker
73
+ state, submit-time timeout/cancellation linearization, wait-time accounting, and
74
+ abandonment. It is not returned to application or extension code.
75
+
65
76
  ## Capacity and starvation
66
77
 
67
- The default OffloadPool is a shared bounded resource. CPU-heavy work can occupy
68
- slots that would otherwise be available to I/O, and slow I/O can do the same in
69
- reverse. Phronomy guarantees bounded worker count, bounded queue depth,
70
- backpressure, timeout/cancellation settlement, abandoned-worker accounting,
71
- metrics, and lifecycle shutdown. It does **not** guarantee work-class fairness,
72
- CPU isolation, core reservation, or CPU-bound speedup.
78
+ The default OffloadPool is a shared bounded resource. Phronomy guarantees bounded
79
+ worker count, bounded queue depth, backpressure, timeout/cancellation settlement,
80
+ abandoned-worker accounting, metrics, and lifecycle shutdown. It does not
81
+ guarantee work-class fairness, CPU isolation, core reservation, or CPU-bound
82
+ speedup.
73
83
 
74
84
  Applications own capacity planning through `offload_pool_size` and
75
85
  `offload_queue_size`. Where isolation is required, applications may use
@@ -80,28 +90,23 @@ resource domains.
80
90
 
81
91
  An EventLoop action must not block while waiting for a free OffloadPool queue
82
92
  slot. Framework-owned EventLoop-origin submissions therefore use non-blocking
83
- admission (`on_full: :raise`) and propagate `BackpressureError` through the
84
- normal FSM/completion path.
85
-
86
- External management threads may deliberately choose other admission policies
87
- when blocking the caller is acceptable.
93
+ admission (`on_full: :raise`) and propagate `BackpressureError` through the normal
94
+ FSM/Task completion path.
88
95
 
89
96
  ## Timeout and cancellation
90
97
 
91
- An OffloadPool submit-time timeout settles the caller-facing PendingOperation.
92
- It does not asynchronously interrupt a running worker Thread. If execution has
93
- already started, the operation becomes abandoned, the worker may continue until
94
- the submitted synchronous call returns, and that eventual worker result is
95
- discarded.
98
+ An OffloadPool submit-time timeout settles the caller-facing Task with
99
+ `TimeoutError`. It does not asynchronously interrupt a running worker Thread. If
100
+ execution has already started, the private Operation becomes abandoned, the
101
+ worker may continue until the submitted synchronous call returns, and that
102
+ eventual worker result is discarded.
96
103
 
97
- The cancellation token passed to `OffloadPool#submit` follows the same
98
- caller-facing settlement model:
104
+ The cancellation token passed to `OffloadPool#submit` follows the same model:
99
105
 
100
106
  - cancellation before worker execution prevents the submitted block from
101
- starting;
102
- - cancellation after worker execution starts settles the caller-facing
103
- PendingOperation immediately, marks the operation abandoned, and allows the
104
- worker to continue until the synchronous call returns;
107
+ starting and settles the Task as cancelled;
108
+ - cancellation after worker execution starts settles the Task immediately, marks
109
+ the private Operation abandoned, and allows the worker to continue;
105
110
  - cancellation does not use `Thread#raise`;
106
111
  - application code may observe the same CancellationToken and terminate its own
107
112
  synchronous operation cooperatively.
@@ -109,39 +114,51 @@ caller-facing settlement model:
109
114
  A submit token with a monotonic deadline is connected to the Runtime timer queue,
110
115
  so deadline expiry becomes explicit cancellation without a polling Thread.
111
116
 
112
- `PendingOperation#blocking_wait(timeout:)` is a low-level synchronous bridge for
113
- non-EventLoop callers such as tests and diagnostics. Its timeout is waiter-local:
114
- it raises `TimeoutError` only to that caller and does not settle the
115
- PendingOperation, cancel the submitted operation, or mark it abandoned.
116
- PendingOperation does not define a waiter-local cancellation token;
117
- operation-wide cancellation is represented only by the token passed to
118
- `OffloadPool#submit`.
119
-
120
- Independent notification callbacks are fault-isolated. A `StandardError` from one
121
- `CancellationToken#on_cancel`, `Task#on_complete`, or
122
- `PendingOperation#on_complete` subscriber is logged and does not suppress later
123
- subscribers. This rule applies to notification fan-out; continuation or
124
- transformation callbacks still report their own failures through the operation
125
- they construct.
126
-
127
- `abandoned_count` and the exported `offload_pool_abandoned_total` metric are
128
- cumulative: they count operations whose caller-facing submit timeout or submit
129
- cancellation settled after worker execution had already started.
130
- `abandoned_active_count` and `offload_pool_abandoned_active` are current-state
131
- values: they count only abandoned operations whose synchronous worker is still
132
- occupying OffloadPool capacity.
117
+ `Task#wait_result(timeout:)` is a synchronous bridge for non-EventLoop callers.
118
+ Its timeout is waiter-local: it raises `TimeoutError` only to that caller and does
119
+ not settle the Task, cancel the submitted operation, or mark an OffloadPool
120
+ Operation abandoned.
121
+
122
+ Framework components own Task settlement. Application code should request
123
+ operation-wide cancellation through the CancellationToken accepted by the API
124
+ that created the Task rather than calling Task settlement methods directly. A
125
+ Task cancellation must not implicitly cancel a shared CancellationToken in the
126
+ reverse direction.
127
+
128
+ Independent `Task#on_complete` notification callbacks are fault-isolated. Their
129
+ execution thread is not guaranteed, so callbacks must be thread-safe and should
130
+ return quickly. Framework lifecycle code normally converts completion into an
131
+ explicit EventLoop event.
132
+
133
+ ## Abandoned-worker metrics
134
+
135
+ `offload_pool_abandoned_total` is cumulative: it counts operations whose
136
+ caller-facing timeout or cancellation won after worker execution had already
137
+ started. `offload_pool_abandoned_active` is current-state: it counts only those
138
+ abandoned operations whose synchronous workers are still occupying OffloadPool
139
+ capacity. Task does not expose abandonment as caller-facing completion state;
140
+ that distinction remains private OffloadPool execution/observability state.
133
141
 
134
142
  ## CPU-bound work
135
143
 
136
144
  CPU-bound work is allowed through `:offloaded`. Thread offload protects the
137
145
  EventLoop from direct long synchronous execution but does not remove CRuby GVL
138
- contention or physical CPU contention. Those are explicitly outside the core
139
- OffloadPool guarantee.
146
+ contention or physical CPU contention.
147
+
148
+ A future subprocess capability may provide process isolation and hard process
149
+ termination. That future implementation belongs to the offload subsystem and
150
+ does not reintroduce a Tool-level workload class.
151
+
152
+ ## Genuine native async
153
+
154
+ A component that truly uses a native asynchronous driver and does not create a
155
+ Phronomy-owned OS Thread does not need an OffloadPool worker. If such an
156
+ extension point is formally introduced, it must still adapt completion into a
157
+ `Phronomy::Task`; it must not expose provider-specific futures or private Runtime
158
+ operation records as Phronomy's completion contract.
140
159
 
141
- A future subprocess capability may provide process isolation, hard process
142
- termination, stdout/stderr capture, and CPU-worker separation. That future
143
- implementation belongs to the offload subsystem and does not reintroduce a
144
- Tool-level `:external_process` execution class.
160
+ The current VectorStore and Embeddings extension contracts are synchronous; their
161
+ framework-provided async convenience methods use OffloadPool.
145
162
 
146
163
  ## Prohibited pattern
147
164
 
@@ -153,13 +170,12 @@ OffloadPool worker
153
170
 
154
171
  and equivalently for Workflow/ToolInvocation/Task lifecycles.
155
172
 
156
- That pattern converts a logical wait into worker-slot occupancy and can create
157
- pool starvation. The correct model is:
173
+ The correct model is:
158
174
 
159
175
  ```text
160
176
  parent FSMSession
161
177
  → start child lifecycle
162
- → return completion handle immediately
178
+ → return Task immediately
163
179
  → child settles
164
180
  → post parent EventLoop event
165
181
  ```
@@ -167,9 +183,10 @@ parent FSMSession
167
183
  ## Consequences
168
184
 
169
185
  - There is one explicit framework continuation model: FSMSession + EventLoop.
170
- - `Task` stays thread-free and represents settlement only.
171
- - Tool execution classification becomes `:cooperative` / `:offloaded`.
186
+ - There is one caller-facing completion abstraction: Task.
187
+ - Task stays thread-free and represents settlement only.
188
+ - OffloadPool owns bounded OS-thread execution and its private Operation state.
189
+ - Tool execution classification remains `:cooperative` / `:offloaded`.
172
190
  - CPU/I/O classification and resource sizing are application responsibilities.
173
- - Named pools remain available for application-managed resource isolation.
174
191
  - Production Fiber execution is not part of the architecture.
175
192
  - Raw production Threads remain confined to EventLoop and OffloadPool.