phronomy 0.16.0 → 0.18.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 +8 -9
- data/CHANGELOG.md +151 -1134
- data/CONTRIBUTING.md +28 -16
- data/README.md +69 -1249
- data/benchmark/baseline.json +2 -3
- data/benchmark/bench_agent_invoke.rb +4 -4
- data/benchmark/bench_context_assembler.rb +134 -34
- data/benchmark/bench_regression.rb +26 -6
- data/benchmark/bench_tool_schema.rb +2 -35
- data/docs/changelog/0.14-and-earlier.md +1137 -0
- data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
- data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
- data/docs/decisions/010-cooperative-first-concurrency.md +155 -228
- data/docs/decisions/011-build-context-as-single-llm-input-authority.md +2 -2
- data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
- data/docs/features.md +87 -0
- data/docs/getting-started.md +351 -0
- data/docs/migrations/0.15.md +35 -0
- data/docs/migrations/0.16.md +43 -0
- data/docs/runtime-and-concurrency.md +258 -0
- data/examples/workflows/generic_task_event_mapping.rb +14 -6
- data/lib/phronomy/agent/agent_invocation.rb +2 -36
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +157 -94
- data/lib/phronomy/agent/agent_root.rb +1 -2
- data/lib/phronomy/agent/async_event_api.rb +3 -3
- data/lib/phronomy/agent/base.rb +170 -333
- data/lib/phronomy/agent/context/capability/base.rb +177 -298
- data/lib/phronomy/agent/context_assembler.rb +65 -29
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +19 -89
- data/lib/phronomy/agent/context_plan_validator.rb +0 -33
- data/lib/phronomy/agent/execution_coordinator.rb +6 -7
- data/lib/phronomy/agent/journal_projection.rb +28 -2
- data/lib/phronomy/agent/ruby_llm_materializer.rb +2 -111
- data/lib/phronomy/agent/shared_state.rb +48 -138
- data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
- data/lib/phronomy/agent/tool_executor.rb +29 -71
- data/lib/phronomy/agent/tool_invocation.rb +180 -336
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
- data/lib/phronomy/agent.rb +6 -10
- data/lib/phronomy/configuration.rb +4 -171
- data/lib/phronomy/diagnostics.rb +12 -41
- data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +51 -79
- data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
- data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
- data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
- data/lib/phronomy/engine/event_loop.rb +89 -190
- data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
- data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
- data/lib/phronomy/engine/runtime.rb +49 -377
- data/lib/phronomy/engine/task.rb +136 -277
- data/lib/phronomy/llm_adapter/base.rb +14 -14
- data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
- data/lib/phronomy/llm_adapter.rb +2 -2
- data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
- data/lib/phronomy/metrics.rb +15 -30
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
- data/lib/phronomy/multi_agent/handoff.rb +1 -0
- data/lib/phronomy/multi_agent/orchestrator.rb +252 -256
- data/lib/phronomy/multi_agent/team_coordinator.rb +44 -133
- data/lib/phronomy/testing/eval/comparison.rb +23 -0
- data/lib/phronomy/testing/eval/dataset.rb +27 -0
- data/lib/phronomy/testing/eval/eval_case.rb +13 -0
- data/lib/phronomy/testing/eval/eval_result.rb +16 -0
- data/lib/phronomy/testing/eval/metrics.rb +43 -0
- data/lib/phronomy/testing/eval/runner.rb +52 -0
- data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
- data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
- data/lib/phronomy/testing/eval/scorer.rb +10 -0
- data/lib/phronomy/testing/eval.rb +9 -0
- data/lib/phronomy/testing/fake_clock.rb +6 -53
- data/lib/phronomy/testing.rb +2 -6
- data/lib/phronomy/tools/agent.rb +141 -6
- data/lib/phronomy/vector_store/async_backend.rb +21 -17
- data/lib/phronomy/vector_store/base.rb +2 -2
- data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
- data/lib/phronomy/vector_store/in_memory.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow_runner.rb +2 -4
- data/lib/phronomy.rb +7 -121
- data/scripts/api_snapshot.rb +4 -15
- metadata +24 -38
- data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
- data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
- data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
- data/lib/phronomy/agent/fsm_runtime_adapter.rb +0 -210
- data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +0 -561
- data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
- data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
- data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
- data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
- data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
- data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
- data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
- data/lib/phronomy/engine/task/backend.rb +0 -80
- data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
- data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
- data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
- data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
- data/lib/phronomy/engine/task/thread_backend.rb +0 -84
- data/lib/phronomy/engine/task_group.rb +0 -265
- data/lib/phronomy/eval/comparison.rb +0 -47
- data/lib/phronomy/eval/dataset.rb +0 -45
- data/lib/phronomy/eval/eval_case.rb +0 -17
- data/lib/phronomy/eval/eval_result.rb +0 -29
- data/lib/phronomy/eval/metrics.rb +0 -66
- data/lib/phronomy/eval/runner.rb +0 -94
- data/lib/phronomy/eval/scorer/base.rb +0 -22
- data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
- data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
- data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
- data/lib/phronomy/eval/scorer.rb +0 -9
- data/lib/phronomy/eval.rb +0 -7
- data/lib/phronomy/knowledge_source.rb +0 -12
- data/lib/phronomy/llm_context_window/assembler.rb +0 -191
- data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
- data/lib/phronomy/testing/fake_scheduler.rb +0 -104
- data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Runtime and concurrency
|
|
2
|
+
|
|
3
|
+
Phronomy uses an **EventLoop / FSMSession first** architecture for framework
|
|
4
|
+
lifecycle coordination. `FSMSession` is the framework finite-state-machine session
|
|
5
|
+
used to represent explicit lifecycle state and events. A `Task` is a completion
|
|
6
|
+
handle, not an execution backend. Synchronous work that must stay off EventLoop is isolated in the bounded
|
|
7
|
+
`OffloadPool`.
|
|
8
|
+
|
|
9
|
+
For the design rationale, see Architecture Decision Record (ADR)
|
|
10
|
+
[ADR-010: EventLoop / FSMSession First Concurrency](decisions/010-cooperative-first-concurrency.md).
|
|
11
|
+
|
|
12
|
+
## Runtime model
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
Runtime
|
|
16
|
+
├─ EventLoop (one control-plane operating-system Thread)
|
|
17
|
+
│ └─ FSMSession
|
|
18
|
+
│ ├─ Agent
|
|
19
|
+
│ ├─ Workflow
|
|
20
|
+
│ ├─ ToolInvocation
|
|
21
|
+
│ └─ MultiAgent fan-out
|
|
22
|
+
├─ OffloadPool (bounded operating-system Threads)
|
|
23
|
+
│ ├─ blocking input/output (I/O)
|
|
24
|
+
│ ├─ central-processing-unit (CPU)-bound synchronous work
|
|
25
|
+
│ └─ other long synchronous work
|
|
26
|
+
├─ named OffloadPools
|
|
27
|
+
└─ EventLoop-driven timers
|
|
28
|
+
|
|
29
|
+
Task = completion handle
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The framework does not allocate one operating-system Thread per logical Agent/Workflow/Tool
|
|
33
|
+
lifecycle. Logical waits remain explicit states plus later EventLoop events.
|
|
34
|
+
|
|
35
|
+
## Tool execution modes
|
|
36
|
+
|
|
37
|
+
Phronomy exposes two execution modes for capabilities:
|
|
38
|
+
|
|
39
|
+
- `:cooperative` — short EventLoop-safe work, or a specialized asynchronous Tool
|
|
40
|
+
that starts another Phronomy lifecycle and returns immediately.
|
|
41
|
+
- `:offloaded` — synchronous work that must not run to completion on EventLoop.
|
|
42
|
+
|
|
43
|
+
Phronomy does not classify application work into framework-level I/O/CPU/process
|
|
44
|
+
execution modes. That workload classification and capacity planning belong to the
|
|
45
|
+
application.
|
|
46
|
+
|
|
47
|
+
A CPU-heavy operation may therefore use `:offloaded`, but thread offload does not
|
|
48
|
+
remove CRuby Global VM Lock contention or physical CPU contention.
|
|
49
|
+
|
|
50
|
+
## Logical waiting versus offload
|
|
51
|
+
|
|
52
|
+
Do not offload a logical wait merely to make it asynchronous.
|
|
53
|
+
|
|
54
|
+
Prohibited shape:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
OffloadPool worker
|
|
58
|
+
→ child_agent.invoke_async
|
|
59
|
+
→ wait_result
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Correct shape:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
parent FSMSession
|
|
66
|
+
→ start child lifecycle
|
|
67
|
+
→ return immediately
|
|
68
|
+
→ child settles
|
|
69
|
+
→ post parent EventLoop event
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This distinction prevents worker-slot starvation when many logical lifecycles are
|
|
73
|
+
waiting at the same time.
|
|
74
|
+
|
|
75
|
+
## Sync versus async application APIs
|
|
76
|
+
|
|
77
|
+
| Calling context | Recommended approach |
|
|
78
|
+
|---|---|
|
|
79
|
+
| Top-level application code | `agent.invoke(...)` when blocking the caller is acceptable |
|
|
80
|
+
| Top-level explicit async | `task = agent.invoke_async(...)`; optionally `task.wait_result` outside EventLoop |
|
|
81
|
+
| Workflow entry/transition action | Start async work and continue through `Workflow#signal` |
|
|
82
|
+
| EventLoop callback | Never block waiting for a Task that requires EventLoop progress |
|
|
83
|
+
| Top-level streaming | `agent.stream(...)` |
|
|
84
|
+
| Non-blocking streaming | `agent.stream_async(...)` |
|
|
85
|
+
| Approval from EventLoop callback | `approve_async` |
|
|
86
|
+
|
|
87
|
+
Blocking synchronous APIs reject EventLoop re-entry with
|
|
88
|
+
`Phronomy::EventLoopReentrancyError` when waiting would stall the same EventLoop
|
|
89
|
+
needed for progress.
|
|
90
|
+
|
|
91
|
+
## Task
|
|
92
|
+
|
|
93
|
+
`Phronomy::Task` is thread-free. It represents one terminal result:
|
|
94
|
+
|
|
95
|
+
- completed value,
|
|
96
|
+
- failure,
|
|
97
|
+
- cancellation.
|
|
98
|
+
|
|
99
|
+
`Task#wait_result(timeout:)` is a bridge for external synchronous callers. It is
|
|
100
|
+
not the framework continuation mechanism.
|
|
101
|
+
|
|
102
|
+
`Task#map` is application-level composition. A transformation exception settles
|
|
103
|
+
the mapped Task as failed. This is different from independent notification
|
|
104
|
+
callbacks, described below.
|
|
105
|
+
|
|
106
|
+
## OffloadPool
|
|
107
|
+
|
|
108
|
+
`OffloadPool` is a bounded worker pool for synchronous work that must not execute
|
|
109
|
+
on EventLoop.
|
|
110
|
+
|
|
111
|
+
Its guarantees include:
|
|
112
|
+
|
|
113
|
+
- bounded worker count,
|
|
114
|
+
- bounded queue depth,
|
|
115
|
+
- queue backpressure,
|
|
116
|
+
- operation-wide submit timeout/cancellation settlement,
|
|
117
|
+
- abandoned-worker accounting,
|
|
118
|
+
- runtime metrics,
|
|
119
|
+
- shutdown/drain behavior.
|
|
120
|
+
|
|
121
|
+
It does not guarantee CPU/I/O fairness or CPU isolation. Applications that need
|
|
122
|
+
resource isolation can create named Runtime pools.
|
|
123
|
+
|
|
124
|
+
### EventLoop queue admission
|
|
125
|
+
|
|
126
|
+
Framework-owned EventLoop-origin submissions must not wait for a free worker
|
|
127
|
+
queue slot. They use non-blocking admission (`on_full: :raise`) and route
|
|
128
|
+
`BackpressureError` through the ordinary FSM/completion path.
|
|
129
|
+
|
|
130
|
+
External management threads may choose a blocking admission policy when blocking
|
|
131
|
+
the caller is acceptable.
|
|
132
|
+
|
|
133
|
+
## PendingOperation and blocking_wait
|
|
134
|
+
|
|
135
|
+
`OffloadPool#submit` returns a private `PendingOperation` immediately after queue
|
|
136
|
+
admission.
|
|
137
|
+
|
|
138
|
+
`PendingOperation#blocking_wait(timeout:)` is intentionally a **low-level
|
|
139
|
+
synchronous bridge** for non-EventLoop callers such as tests and diagnostics.
|
|
140
|
+
The timeout belongs only to that waiter:
|
|
141
|
+
|
|
142
|
+
- it raises `TimeoutError` to that calling thread,
|
|
143
|
+
- it does not settle the PendingOperation,
|
|
144
|
+
- it does not cancel the submitted operation,
|
|
145
|
+
- it does not mark the operation abandoned.
|
|
146
|
+
|
|
147
|
+
There is no waiter-local `cancellation_token:` argument. Operation-wide
|
|
148
|
+
cancellation belongs exclusively to `OffloadPool#submit(cancellation_token:)`.
|
|
149
|
+
|
|
150
|
+
## Submit timeout and cancellation
|
|
151
|
+
|
|
152
|
+
Submit-time timeout and submit cancellation settle the caller-facing operation.
|
|
153
|
+
They do **not** asynchronously interrupt an already-running synchronous worker.
|
|
154
|
+
|
|
155
|
+
### Before worker start
|
|
156
|
+
|
|
157
|
+
If timeout/cancellation wins before execution starts:
|
|
158
|
+
|
|
159
|
+
- the PendingOperation settles,
|
|
160
|
+
- the submitted block does not run,
|
|
161
|
+
- the operation is not counted as abandoned.
|
|
162
|
+
|
|
163
|
+
### After worker start
|
|
164
|
+
|
|
165
|
+
If timeout/cancellation wins after execution starts:
|
|
166
|
+
|
|
167
|
+
- the PendingOperation settles immediately,
|
|
168
|
+
- the operation is marked abandoned,
|
|
169
|
+
- the worker is allowed to continue until its synchronous call returns,
|
|
170
|
+
- the eventual worker result is discarded.
|
|
171
|
+
|
|
172
|
+
Phronomy does not use `Thread#raise` to inject an exception into the worker.
|
|
173
|
+
Application/library code that needs hard or transport-level deadlines should use
|
|
174
|
+
its native timeout or, in the future, an appropriate process-isolation mechanism.
|
|
175
|
+
|
|
176
|
+
### CancellationToken deadlines
|
|
177
|
+
|
|
178
|
+
`CancellationToken.timeout_after(seconds)` uses a monotonic deadline.
|
|
179
|
+
`cancelled?` becomes true after that deadline, but the token itself does not own a
|
|
180
|
+
Thread.
|
|
181
|
+
|
|
182
|
+
Components requiring callback delivery for a monotonic deadline must promote the
|
|
183
|
+
deadline to explicit `cancel!` through the Runtime timer queue. OffloadPool does
|
|
184
|
+
this for its submit cancellation token.
|
|
185
|
+
|
|
186
|
+
`CancellationScope#deadline_in` is appropriate when the application needs a
|
|
187
|
+
Runtime-timer-backed cancellation scope whose `on_cancel` subscribers are fired
|
|
188
|
+
on expiry.
|
|
189
|
+
|
|
190
|
+
## Independent notification callbacks
|
|
191
|
+
|
|
192
|
+
Independent notification fan-out is fault-isolated.
|
|
193
|
+
|
|
194
|
+
The rule applies to:
|
|
195
|
+
|
|
196
|
+
- `CancellationToken#on_cancel`,
|
|
197
|
+
- `Task#on_complete`,
|
|
198
|
+
- `PendingOperation#on_complete`,
|
|
199
|
+
- EventLoop timer callbacks.
|
|
200
|
+
|
|
201
|
+
A `StandardError` from one independent subscriber is logged and does not suppress
|
|
202
|
+
later subscribers.
|
|
203
|
+
|
|
204
|
+
This is deliberately different from a continuation/transform such as
|
|
205
|
+
`Task#map`: a transform exception is the outcome of the derived operation and is
|
|
206
|
+
therefore propagated into that derived Task.
|
|
207
|
+
|
|
208
|
+
Callback execution thread is not guaranteed for low-level completion handles.
|
|
209
|
+
Callbacks must therefore be thread-safe and should complete quickly. Framework
|
|
210
|
+
lifecycle code normally turns completion into an explicit EventLoop event rather
|
|
211
|
+
than mutating unrelated logical state from a worker thread.
|
|
212
|
+
|
|
213
|
+
## Abandoned-worker metrics
|
|
214
|
+
|
|
215
|
+
Two metrics answer different operational questions:
|
|
216
|
+
|
|
217
|
+
- `offload_pool_abandoned_total` — cumulative count of operations that became
|
|
218
|
+
abandoned after worker execution had started.
|
|
219
|
+
- `offload_pool_abandoned_active` — current number of abandoned operations whose
|
|
220
|
+
synchronous workers still occupy pool capacity.
|
|
221
|
+
|
|
222
|
+
Example:
|
|
223
|
+
|
|
224
|
+
```text
|
|
225
|
+
offload_pool_size = 10
|
|
226
|
+
offload_pool_active = 10
|
|
227
|
+
offload_pool_abandoned_active = 8
|
|
228
|
+
offload_pool_abandoned_total = 523
|
|
229
|
+
offload_pool_queue_length = 40
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
This means 10 workers are currently executing, 8 of them are doing work whose
|
|
233
|
+
caller-facing result has already been abandoned, 523 abandonment events have
|
|
234
|
+
occurred since process start, and 40 operations are queued.
|
|
235
|
+
|
|
236
|
+
`Phronomy::Diagnostics.dump` exposes the same distinction for point-in-time
|
|
237
|
+
troubleshooting.
|
|
238
|
+
|
|
239
|
+
## EventLoop metrics
|
|
240
|
+
|
|
241
|
+
`Phronomy::Metrics.snapshot` also reports EventLoop queue depth and lag values.
|
|
242
|
+
Use these to distinguish worker saturation from EventLoop backlog/latency.
|
|
243
|
+
|
|
244
|
+
## Shutdown
|
|
245
|
+
|
|
246
|
+
`Runtime#shutdown` is terminal for that Runtime. It drains/terminates the
|
|
247
|
+
Runtime-owned EventLoop, then closes pools and timers according to the Runtime
|
|
248
|
+
shutdown contract.
|
|
249
|
+
|
|
250
|
+
`Phronomy.reset_runtime!` exists primarily for test isolation and performs a real
|
|
251
|
+
Runtime shutdown before resetting configuration.
|
|
252
|
+
|
|
253
|
+
## Further design records
|
|
254
|
+
|
|
255
|
+
The `docs/decisions/` directory contains the historical and current Architecture
|
|
256
|
+
Decision Records (ADRs). When an older ADR is superseded, use the superseding
|
|
257
|
+
section/current ADR as the active design contract and keep the earlier document
|
|
258
|
+
as historical rationale.
|
|
@@ -19,18 +19,25 @@ class ImportContext
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Example application service for synchronous work that must stay off EventLoop.
|
|
23
|
+
# The worker Thread belongs to Phronomy's bounded OffloadPool; the Workflow
|
|
24
|
+
# itself never blocks EventLoop waiting for the operation.
|
|
25
|
+
def start_import_async
|
|
26
|
+
Phronomy::Runtime.instance.offload.submit(on_full: :raise) do
|
|
27
|
+
# Replace with blocking file/DB/network work or another long synchronous call.
|
|
28
|
+
100
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
22
32
|
workflow = nil
|
|
23
33
|
|
|
24
34
|
workflow = Phronomy::Workflow.define(ImportContext) do
|
|
25
35
|
initial :importing
|
|
26
36
|
|
|
27
37
|
state :importing, action: ->(context) {
|
|
28
|
-
|
|
29
|
-
# Replace with application-owned asynchronous work.
|
|
30
|
-
100
|
|
31
|
-
end
|
|
38
|
+
operation = start_import_async
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
operation.on_complete do |record_count, error|
|
|
34
41
|
workflow.signal(
|
|
35
42
|
thread_id: context.thread_id,
|
|
36
43
|
event: error ? :import_failed : :import_completed,
|
|
@@ -41,7 +48,8 @@ workflow = Phronomy::Workflow.define(ImportContext) do
|
|
|
41
48
|
)
|
|
42
49
|
end
|
|
43
50
|
|
|
44
|
-
# Do not return
|
|
51
|
+
# Do not return the completion handle. The state remains active after this
|
|
52
|
+
# synchronous action returns, and later completion arrives as an FSM event.
|
|
45
53
|
context
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -29,7 +29,6 @@ module Phronomy
|
|
|
29
29
|
CALLBACK_FAILED_EVENTS = %i[application_callback_failed].freeze
|
|
30
30
|
|
|
31
31
|
attr_accessor :input,
|
|
32
|
-
:messages,
|
|
33
32
|
:chat,
|
|
34
33
|
:output,
|
|
35
34
|
:usage,
|
|
@@ -59,18 +58,15 @@ module Phronomy
|
|
|
59
58
|
def initialize(
|
|
60
59
|
agent:,
|
|
61
60
|
input:,
|
|
62
|
-
messages:,
|
|
63
61
|
config:,
|
|
64
62
|
approval_policy: nil,
|
|
65
63
|
approval_listener: nil,
|
|
66
64
|
event_listener: nil,
|
|
67
|
-
stream_listener: nil,
|
|
68
65
|
mode: nil,
|
|
69
66
|
id: nil
|
|
70
67
|
)
|
|
71
68
|
@agent = agent
|
|
72
69
|
@input = input
|
|
73
|
-
@messages = Array(messages)
|
|
74
70
|
@config = config
|
|
75
71
|
@thread_id = config[:thread_id]
|
|
76
72
|
@id = (id || config[:agent_invocation_id] || SecureRandom.uuid).to_s
|
|
@@ -80,8 +76,8 @@ module Phronomy
|
|
|
80
76
|
end
|
|
81
77
|
@approval_policy = invocation_policy || approval_policy
|
|
82
78
|
@approval_listener = approval_listener
|
|
83
|
-
@event_listener = event_listener
|
|
84
|
-
@mode = (mode ||
|
|
79
|
+
@event_listener = event_listener
|
|
80
|
+
@mode = (mode || :invoke).to_sym
|
|
85
81
|
|
|
86
82
|
@chat = nil
|
|
87
83
|
@output = nil
|
|
@@ -104,14 +100,6 @@ module Phronomy
|
|
|
104
100
|
@tool_batch_llm_call_id = nil
|
|
105
101
|
end
|
|
106
102
|
|
|
107
|
-
def stream_listener
|
|
108
|
-
@event_listener
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def stream_listener=(listener)
|
|
112
|
-
@event_listener = listener
|
|
113
|
-
end
|
|
114
|
-
|
|
115
103
|
def streaming?
|
|
116
104
|
@mode == :stream
|
|
117
105
|
end
|
|
@@ -180,29 +168,9 @@ module Phronomy
|
|
|
180
168
|
true
|
|
181
169
|
end
|
|
182
170
|
|
|
183
|
-
def apply_fsm_action_result(result)
|
|
184
|
-
event_type =
|
|
185
|
-
if result.respond_to?(:error) &&
|
|
186
|
-
result.error &&
|
|
187
|
-
!result.error.is_a?(ToolCallIntercepted)
|
|
188
|
-
:llm_failed
|
|
189
|
-
else
|
|
190
|
-
:llm_completed
|
|
191
|
-
end
|
|
192
|
-
handle_fsm_event(
|
|
193
|
-
Phronomy::Event.new(
|
|
194
|
-
type: event_type,
|
|
195
|
-
target_id: @id,
|
|
196
|
-
payload: result
|
|
197
|
-
)
|
|
198
|
-
)
|
|
199
|
-
self
|
|
200
|
-
end
|
|
201
|
-
|
|
202
171
|
def accept_tool_calls!(tool_calls, llm_call_id: nil)
|
|
203
172
|
@user_message_sent = true
|
|
204
173
|
@pending_tool_calls = Array(tool_calls)
|
|
205
|
-
@messages = @chat.messages
|
|
206
174
|
@tool_batch_llm_call_id = (llm_call_id || @current_llm_call_id)&.to_s
|
|
207
175
|
@current_llm_call_id = nil
|
|
208
176
|
@pending_tool_calls.each do |tool_call|
|
|
@@ -227,7 +195,6 @@ module Phronomy
|
|
|
227
195
|
@user_message_sent = true
|
|
228
196
|
@output = response.content
|
|
229
197
|
@usage = Phronomy::TokenUsage.from_tokens(response.tokens)
|
|
230
|
-
@messages = @chat.messages
|
|
231
198
|
@pending_tool_calls = []
|
|
232
199
|
@current_llm_call_id = nil
|
|
233
200
|
self
|
|
@@ -298,7 +265,6 @@ module Phronomy
|
|
|
298
265
|
)
|
|
299
266
|
)
|
|
300
267
|
end
|
|
301
|
-
@messages = @chat.messages
|
|
302
268
|
clear_tool_batch!
|
|
303
269
|
self
|
|
304
270
|
end
|