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 +4 -4
- data/.mutant.yml +3 -1
- data/CHANGELOG.md +41 -11
- data/CONTRIBUTING.md +44 -6
- data/README.md +15 -6
- data/docs/decisions/010-cooperative-first-concurrency.md +86 -69
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +184 -0
- data/docs/features.md +22 -13
- data/docs/getting-started.md +10 -1
- data/docs/runtime-and-concurrency.md +110 -147
- data/lib/phronomy/agent/tool_executor.rb +7 -3
- data/lib/phronomy/engine/concurrency/offload_pool.rb +142 -245
- data/lib/phronomy/engine/task.rb +50 -6
- data/lib/phronomy/invocation_context.rb +11 -52
- data/lib/phronomy/llm_adapter/base.rb +29 -32
- data/lib/phronomy/llm_adapter/ruby_llm.rb +13 -12
- data/lib/phronomy/llm_adapter.rb +10 -7
- data/lib/phronomy/output_parser/base.rb +5 -1
- data/lib/phronomy/tool/base.rb +15 -0
- data/lib/phronomy/tool.rb +11 -0
- data/lib/phronomy/vector_store/async_backend.rb +15 -38
- data/lib/phronomy/vector_store/base.rb +12 -13
- data/lib/phronomy/vector_store/embeddings/base.rb +11 -9
- data/lib/phronomy/version.rb +1 -1
- data/scripts/run_mutation.sh +2 -1
- data/sig/phronomy/agent.rbs +36 -0
- data/sig/phronomy/extensions.rbs +50 -0
- data/sig/phronomy/llm_adapter.rbs +11 -0
- data/sig/phronomy/persistence.rbs +70 -0
- data/sig/phronomy/runtime.rbs +43 -0
- data/sig/phronomy/tool.rbs +39 -0
- data/sig/phronomy/workflow.rbs +30 -0
- data/sig/phronomy.rbs +49 -1
- metadata +12 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1da8f29678594e3b2c0b8db70e6485f3576ffe72bc19db0dd340abefbdc3118b
|
|
4
|
+
data.tar.gz: 8dbf2cacbca536c99b7552f3494edf208986b9e45c3817fac700da445d27d2c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
46
|
+
When adding, removing, renaming, or changing the contract of a public API or
|
|
47
|
+
extension SPI:
|
|
47
48
|
|
|
48
|
-
1. Update the stability
|
|
49
|
-
2. Add or update `@api
|
|
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::
|
|
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
|
|
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::
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
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
|
|
37
|
+
a Task;
|
|
30
38
|
- `:offloaded` — synchronous work that must not run on EventLoop. It executes in
|
|
31
|
-
the bounded
|
|
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
|
-
|
|
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.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
92
|
-
It does not asynchronously interrupt a running worker Thread. If
|
|
93
|
-
already started, the
|
|
94
|
-
the submitted synchronous call returns, and that
|
|
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
|
|
103
|
-
|
|
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
|
-
`
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
|
|
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.
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
171
|
-
-
|
|
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.
|