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
data/CHANGELOG.md
CHANGED
|
@@ -5,10 +5,161 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
Release history for 0.14.0 and earlier is archived in
|
|
9
|
+
[`docs/changelog/0.14-and-earlier.md`](docs/changelog/0.14-and-earlier.md).
|
|
10
|
+
|
|
8
11
|
---
|
|
9
12
|
|
|
10
13
|
## [Unreleased]
|
|
11
14
|
|
|
15
|
+
### OffloadPool execution model
|
|
16
|
+
|
|
17
|
+
#### Changed
|
|
18
|
+
|
|
19
|
+
- Renamed `BlockingAdapterPool` to `OffloadPool` and `Runtime#blocking_io` to
|
|
20
|
+
`Runtime#offload`; configuration keys now use `offload_pool_size` and
|
|
21
|
+
`offload_queue_size`, and metrics now use `offload_pool_*` names.
|
|
22
|
+
- Simplified Tool execution modes to `:cooperative` and `:offloaded`.
|
|
23
|
+
Workload classification (I/O-bound versus CPU-bound) is application-owned.
|
|
24
|
+
- CPU-bound synchronous Tools may execute through the bounded OffloadPool;
|
|
25
|
+
Phronomy does not guarantee CPU isolation, CPU/I/O fairness, or parallel
|
|
26
|
+
speedup for pure Ruby CPU work.
|
|
27
|
+
- Framework-owned EventLoop-origin offload submissions use non-blocking queue
|
|
28
|
+
admission so a full worker queue raises backpressure instead of blocking
|
|
29
|
+
the EventLoop control thread.
|
|
30
|
+
- Submit cancellation now settles the caller-facing `PendingOperation`
|
|
31
|
+
immediately. Cancellation before worker start prevents execution;
|
|
32
|
+
cancellation after worker start marks the operation abandoned while allowing
|
|
33
|
+
the synchronous worker to finish without asynchronous `Thread#raise`.
|
|
34
|
+
- Monotonic deadlines carried by an OffloadPool submit cancellation token are
|
|
35
|
+
promoted by the Runtime timer queue, so cancellation completion does not
|
|
36
|
+
require a polling Thread.
|
|
37
|
+
- Independent notification callbacks now isolate subscriber failures. An
|
|
38
|
+
`StandardError` from one `CancellationToken#on_cancel`, `Task#on_complete`, or
|
|
39
|
+
`PendingOperation#on_complete` callback is logged and does not suppress later
|
|
40
|
+
subscribers.
|
|
41
|
+
- `PendingOperation#blocking_wait(timeout:)` remains a waiter-local synchronous
|
|
42
|
+
timeout only. It does not settle or cancel the operation; operation-wide
|
|
43
|
+
cancellation is represented only by `OffloadPool#submit(cancellation_token:)`.
|
|
44
|
+
- `offload_pool_abandoned_total` is the cumulative number of operations whose
|
|
45
|
+
caller-facing submit timeout or cancellation settled after worker execution
|
|
46
|
+
started. `offload_pool_abandoned_active` is the current number of those
|
|
47
|
+
abandoned operations whose synchronous workers still occupy pool capacity.
|
|
48
|
+
- Named Runtime pools remain available for application-managed resource
|
|
49
|
+
isolation and capacity planning.
|
|
50
|
+
|
|
51
|
+
#### Removed
|
|
52
|
+
|
|
53
|
+
- Tool execution modes `:blocking_io`, `:cpu_bound`, and `:external_process`.
|
|
54
|
+
- `Runtime#blocking_io`, `blocking_io_pool_size`, and
|
|
55
|
+
`blocking_io_queue_size`.
|
|
56
|
+
- Waiter-local `cancellation_token:` from `PendingOperation#blocking_wait`.
|
|
57
|
+
The low-level synchronous bridge continues to support waiter-local `timeout:`.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### EventLoop-first runtime cleanup
|
|
61
|
+
|
|
62
|
+
#### Added
|
|
63
|
+
|
|
64
|
+
- Event-driven Agent-as-Tool completion: child Agents now run through their own
|
|
65
|
+
Agent FSMSession and settle the parent ToolInvocation through a completion
|
|
66
|
+
event without holding a OffloadPool worker while waiting.
|
|
67
|
+
- Architecture regression coverage for the production Thread/Fiber boundary,
|
|
68
|
+
test-only API separation, current concurrency documentation, and Agent-as-Tool
|
|
69
|
+
pool-starvation behavior.
|
|
70
|
+
|
|
71
|
+
#### Changed
|
|
72
|
+
|
|
73
|
+
- `FSMSession + EventLoop` is the single framework control plane for Agent,
|
|
74
|
+
Workflow, ToolInvocation, and MultiAgent lifecycle coordination.
|
|
75
|
+
- `Task` is a completion handle only; it does not execute work.
|
|
76
|
+
- `execution_mode :cooperative` now means short EventLoop-safe synchronous work,
|
|
77
|
+
or a specialized Tool that starts another Phronomy asynchronous lifecycle and
|
|
78
|
+
returns a completion handle immediately.
|
|
79
|
+
- `Tool#call_async` for ordinary cooperative Tools no longer consumes a
|
|
80
|
+
OffloadPool worker. `:offloaded` remains the worker-pool route.
|
|
81
|
+
- Framework-owned short in-memory Tools (handoff sentinels, TeamCoordinator
|
|
82
|
+
queue controls, and SharedState store access) explicitly declare
|
|
83
|
+
`execution_mode :cooperative` instead of using the blocking-I/O default.
|
|
84
|
+
- MultiAgent fan-out uses a FanOut FSMSession rather than per-child OS Threads.
|
|
85
|
+
- `TimerQueue` is driven by EventLoop and owns no Thread.
|
|
86
|
+
- Eval support is test infrastructure under `Phronomy::Testing::Eval` and is no
|
|
87
|
+
longer part of the product API compatibility snapshot or README feature set.
|
|
88
|
+
- The `dispatch_parallel` regression benchmark now uses thread-free fake child
|
|
89
|
+
completion so it measures FanOut/EventLoop overhead rather than fake Thread
|
|
90
|
+
creation. Its separate CancellationToken contention benchmark intentionally
|
|
91
|
+
continues to use application Threads.
|
|
92
|
+
|
|
93
|
+
#### Removed
|
|
94
|
+
|
|
95
|
+
- Active runtime-backend/scheduler documentation for the removed
|
|
96
|
+
`Runtime#spawn`, TaskGroup, Thread/Fiber/Immediate Task backends, and
|
|
97
|
+
configurable runtime backends.
|
|
98
|
+
- Product-facing Eval design documentation.
|
|
99
|
+
|
|
100
|
+
### Agent Context / Knowledge cleanup
|
|
101
|
+
|
|
102
|
+
#### Added
|
|
103
|
+
|
|
104
|
+
- Journal-backed persistent Agent Knowledge via creation-time `knowledge:` and
|
|
105
|
+
post-creation `Agent#add_knowledge`.
|
|
106
|
+
- `Agent#clear_knowledge!` for logical Knowledge invalidation without deleting
|
|
107
|
+
append-only Journal history.
|
|
108
|
+
- ADR-013, defining persistent Knowledge as optional Context candidates selected
|
|
109
|
+
through the Manifest-first Context Policy pipeline.
|
|
110
|
+
|
|
111
|
+
#### Changed
|
|
112
|
+
|
|
113
|
+
- Persistent Knowledge and `before_llm_input` segment candidates now participate
|
|
114
|
+
in Context Policy and token-budget selection instead of being injected as
|
|
115
|
+
mandatory system context.
|
|
116
|
+
- `clear_transcript!`, `clear_knowledge!`, and `reset_context!` now have distinct
|
|
117
|
+
transcript/Knowledge lifecycle semantics.
|
|
118
|
+
- Active Context tests, integration fixtures, benchmarks, mutation subjects,
|
|
119
|
+
design documents, and API snapshots now describe the canonical
|
|
120
|
+
Journal -> ContextCandidate -> Context Policy -> Manifest architecture.
|
|
121
|
+
|
|
122
|
+
#### Removed
|
|
123
|
+
|
|
124
|
+
- `Phronomy::KnowledgeSource`, `Knowledge::Base`, `StaticKnowledge`, and
|
|
125
|
+
`EntityKnowledge`.
|
|
126
|
+
- `static_knowledge*`, `add_knowledge_source`, `instance_knowledge_chunks`,
|
|
127
|
+
`Knowledge#static?`, `StaticKnowledge#source`, and the class-level static
|
|
128
|
+
Knowledge cache/fetch abstraction.
|
|
129
|
+
- `Agent#clear_memory!` and `AgentRoot#memory_generation`.
|
|
130
|
+
- Remaining no-op ContextVersionCache tests, fake legacy-import Context tests,
|
|
131
|
+
obsolete Memory/context integration helpers, legacy `:llm_message` benchmark
|
|
132
|
+
categories, and stale Agent `messages:` test-double signatures.
|
|
133
|
+
|
|
134
|
+
### Phase 3 cleanup
|
|
135
|
+
|
|
136
|
+
#### Changed
|
|
137
|
+
|
|
138
|
+
- Active documentation, compatibility snapshots, benchmarks, integration fixtures,
|
|
139
|
+
and mutation targets now describe the Manifest-first Context architecture.
|
|
140
|
+
- The Context benchmark now measures `Agent::ContextAssembler` and
|
|
141
|
+
`ContextPolicies::Default` instead of the removed legacy Assembler.
|
|
142
|
+
- ADR-011 is marked Superseded by ADR-012; its historical analysis is retained.
|
|
143
|
+
|
|
144
|
+
#### Removed
|
|
145
|
+
|
|
146
|
+
- Remaining active compatibility references to `context_overhead`,
|
|
147
|
+
`LlmContextWindow::Assembler`, `ContextVersionCache`, Tool
|
|
148
|
+
`on_error :return_empty`, `force_kill:`, `runtime_backend :cooperative`,
|
|
149
|
+
the `Runtime.instance=` setter, tools-splat registration, and wall-clock
|
|
150
|
+
`CancellationToken.new(deadline:)`.
|
|
151
|
+
- Obsolete legacy Assembler / ContextVersionCache executable specs.
|
|
152
|
+
|
|
153
|
+
### Documentation
|
|
154
|
+
|
|
155
|
+
- Reduced `README.md` to the project entry point and moved long-form setup,
|
|
156
|
+
feature stability, runtime/concurrency, and migration guidance into focused
|
|
157
|
+
documents under `docs/`.
|
|
158
|
+
- Archived changelog history for 0.14.0 and earlier in
|
|
159
|
+
`docs/changelog/0.14-and-earlier.md`; `CHANGELOG.md` now carries Unreleased
|
|
160
|
+
work and the most recent release history.
|
|
161
|
+
|
|
162
|
+
|
|
12
163
|
---
|
|
13
164
|
|
|
14
165
|
## [0.16.0] - 2026-08-08
|
|
@@ -162,1137 +313,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
162
313
|
|
|
163
314
|
- `Agent#approve` now rejects EventLoop re-entry instead of synchronously waiting
|
|
164
315
|
for work that can only be dispatched by that same EventLoop.
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## [0.14.0] - 2026-07-27
|
|
169
|
-
|
|
170
|
-
### Changed
|
|
171
|
-
|
|
172
|
-
- **Runtime-owned EventLoop lifecycle** (`refactor: Runtime-owned EventLoop lifecycle`):
|
|
173
|
-
- `Runtime#event_loop` lazy accessor replaces the standalone `EventLoop.instance` singleton.
|
|
174
|
-
- `EventLoop.instance` is retained as a deprecated compatibility bridge to `Runtime.instance.event_loop`.
|
|
175
|
-
- EventLoop dispatcher now runs in a dedicated real-thread service scheduler owned by `Runtime`,
|
|
176
|
-
removing the hidden `Runtime.new(scheduler: ThreadScheduler.new)` inside `EventLoop`.
|
|
177
|
-
- EventLoop lifecycle is terminal: `:running` → `:draining` → `:stopping` → `:terminated` / `:failed`.
|
|
178
|
-
- `STOP` Object sentinel replaces the `:__stop__` symbol; stale sentinel handling is removed.
|
|
179
|
-
- `@running` instance variable removed; dispatch loop driven solely by the STOP sentinel.
|
|
180
|
-
- EventLoop-specific `@shutdown_token` removed.
|
|
181
|
-
- `outstanding_sessions` counter covers queue-pending `:start` events from `register` time,
|
|
182
|
-
ensuring drain correctness when shutdown races with queued sessions.
|
|
183
|
-
- Shutdown timeout: `AbortAndStop` control command path plus `cancel!` → `join(cancel_grace)` fallback.
|
|
184
|
-
- `Runtime.reset_default!` replaces `EventLoop.reset!`; `Phronomy.reset_runtime!` performs real Runtime shutdown then config reset.
|
|
185
|
-
- `ShutdownResult` value object separates `runtime_outcome` from `cleanup_status`.
|
|
186
|
-
- `FSMSession` now receives `event_loop:` and `timer_queue_provider:` explicitly at build time.
|
|
187
|
-
- Agent / Workflow phase-machine builders capture the owning EventLoop at invocation time;
|
|
188
|
-
`EventLoop.instance` is no longer re-fetched inside async callbacks or timers.
|
|
189
|
-
|
|
190
|
-
### Fixed
|
|
191
|
-
|
|
192
|
-
- **Double-dispatch regression (P0 hotfix — commit `2cc8c6c1`)**: `EventLoop#stop` no longer clears `@task`
|
|
193
|
-
while the dispatcher task is still alive. A subsequent `start` call detects the live task and does
|
|
194
|
-
not spawn a second dispatch loop on the same queue. Includes `:cancel_timeout` status for the case
|
|
195
|
-
where `cancel!` does not terminate the task within `cancel_grace` seconds.
|
|
196
|
-
- **`EventLoop.reset!` safety**: raises `Phronomy::Error` when the dispatcher task is still alive
|
|
197
|
-
after stop instead of unconditionally clearing the singleton.
|
|
198
|
-
- **`EventLoop#task_alive?`**: new public helper; thread-safe boolean for use in reset logic.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
`BlockingAdapterPool#submit` previously stored the timeout value but never
|
|
202
|
-
registered a wall-clock timer, so `config: { llm_timeout: N }` and
|
|
203
|
-
`config: { tool_timeout: N }` had no effect for callers using `on_complete`
|
|
204
|
-
(the normal non-streaming Agent path). The timer is now armed before queue
|
|
205
|
-
admission and calls `fire_timeout!` when the deadline expires.
|
|
206
|
-
|
|
207
|
-
### Changed
|
|
208
|
-
|
|
209
|
-
- **`blocking_wait(timeout:)` is now a waiter-local deadline only**:
|
|
210
|
-
Previously, the timeout passed to `blocking_wait` (or `wait_result`) would settle the
|
|
211
|
-
operation, set `abandoned? = true`, and increment `abandoned_count` — affecting
|
|
212
|
-
all future waiters and callbacks. It is now scoped to the single calling thread:
|
|
213
|
-
the caller receives `TimeoutError`, but the operation remains unsettled. Other
|
|
214
|
-
waiters or `on_complete` callbacks will still receive the eventual result unless
|
|
215
|
-
a separate submit-time deadline or cancellation settles the operation first.
|
|
216
|
-
**Callers that relied on `blocking_wait(timeout:)` to abandon and count an
|
|
217
|
-
operation must switch to a submit-time `timeout:` passed to `pool.submit`.**
|
|
218
|
-
|
|
219
|
-
- **Queue-timeout operations are not counted as abandoned**:
|
|
220
|
-
When a submit-time timeout fires before the worker picks up the operation,
|
|
221
|
-
the operation is settled with `TimeoutError` but `abandoned? == false` and
|
|
222
|
-
`abandoned_count` is not incremented. Only timeouts that fire while the block
|
|
223
|
-
is executing set `abandoned? = true`.
|
|
224
|
-
|
|
225
|
-
- **MCP client support now requires `mcp` 1.x**:
|
|
226
|
-
The `mcp` SDK 0.x dependency is no longer supported. The constraint is now
|
|
227
|
-
`mcp ~> 1.0`. `faraday` and `event_stream_parser` are added as direct runtime
|
|
228
|
-
dependencies so HTTP/SSE transport works without relying on transitive
|
|
229
|
-
resolution through RubyLLM.
|
|
230
|
-
|
|
231
|
-
- **MCP Tool error handling follows MCP 1.x semantics**:
|
|
232
|
-
JSON-RPC errors (`MCP::Client::ServerError`) are converted to
|
|
233
|
-
`Phronomy::ToolError`. Cancellations (`MCP::CancelledError`) are converted to
|
|
234
|
-
`Phronomy::CancellationError`. Tool-level `isError: true` results are
|
|
235
|
-
returned to the model as error text rather than raising, allowing the LLM to
|
|
236
|
-
self-correct.
|
|
237
|
-
|
|
238
|
-
- **MCP input schemas now use a strict supported subset of JSON Schema 2020-12**:
|
|
239
|
-
Unsupported structural keywords (`oneOf`, `anyOf`, `allOf`, `$ref`, etc.),
|
|
240
|
-
nested object/array types, and nullable type arrays fail fast with
|
|
241
|
-
`Phronomy::ToolError` at `from_server` time. Constraint-only annotations
|
|
242
|
-
(`minimum`, `maxLength`, `format`, etc.) produce a logger warning and are
|
|
243
|
-
otherwise ignored. See [`docs/mcp-client.md`](docs/mcp-client.md) for the
|
|
244
|
-
full supported schema subset.
|
|
245
|
-
|
|
246
|
-
- **MCP client cancellation now invalidates the transport**:
|
|
247
|
-
After a `MCP::CancelledError` the internal client reference is set to `nil`
|
|
248
|
-
and the old transport is closed asynchronously. The next tool call creates a
|
|
249
|
-
fresh connection, preventing stdio response mis-routing from a lingering SDK
|
|
250
|
-
worker thread.
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
## [0.13.0] - 2026-07-23
|
|
255
|
-
|
|
256
|
-
### Added
|
|
257
|
-
|
|
258
|
-
- **`Phronomy::Filter::Base` — unified value filter interface** (#389):
|
|
259
|
-
A single abstract base class `Filter::Base` with one method `call(value, **context)`
|
|
260
|
-
covers all three agent boundaries — user input, LLM output, and tool return values.
|
|
261
|
-
Subclasses return the (possibly transformed) value to continue, or call `block!` /
|
|
262
|
-
`raise Phronomy::FilterBlockError` to reject. The same filter instance can be
|
|
263
|
-
registered at multiple sites. Guardrails registered via `add_input_guardrail` /
|
|
264
|
-
`add_output_guardrail` are automatically included at the front of the filter chain.
|
|
265
|
-
|
|
266
|
-
```ruby
|
|
267
|
-
class PiiMaskFilter < Phronomy::Filter::Base
|
|
268
|
-
def call(value, **_context)
|
|
269
|
-
value.to_s.gsub(/\b\d{2,4}-\d{2,4}-\d{4}\b/, "[PHONE]")
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
f = PiiMaskFilter.new
|
|
274
|
-
agent.add_input_filter(f)
|
|
275
|
-
agent.add_output_filter(f)
|
|
276
|
-
agent.add_tool_result_filter(CustomerDataTool, f)
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
Class-level DSL counterparts: `input_filter`, `output_filter`, `tool_result_filter`.
|
|
280
|
-
The `tools(Hash)` DSL also accepts a `:result_filter` key for per-tool scoping.
|
|
281
|
-
|
|
282
|
-
- **`Guardrail::Base#call` — Filter::Base-compatible interface**:
|
|
283
|
-
`Guardrail::Base` now implements `call(value, **_context)`, which calls `check(value)`
|
|
284
|
-
and returns the value unchanged (or raises `GuardrailError`). Existing guardrail
|
|
285
|
-
subclasses require no changes.
|
|
286
|
-
|
|
287
|
-
### Changed
|
|
288
|
-
|
|
289
|
-
- **Guardrail execution unified into the filter chain**:
|
|
290
|
-
Guardrails registered via `add_input_guardrail` / `add_output_guardrail` now run
|
|
291
|
-
as the first entries in `run_input_filters!` / `run_output_filters!`. The separate
|
|
292
|
-
`run_input_guardrails!` / `run_output_guardrails!` call sites in `invoke_once`,
|
|
293
|
-
`_stream_impl`, and `Suspendable#resume` have been removed. Behaviour is unchanged
|
|
294
|
-
— guardrails still run before any filters and `GuardrailError` still propagates.
|
|
295
|
-
|
|
296
|
-
### Changed
|
|
297
|
-
|
|
298
|
-
- **MCP transport replaced by official `mcp` gem** (closes #280, #365):
|
|
299
|
-
The hand-rolled `StdioTransport` and `HttpTransport` (~260 lines) have been
|
|
300
|
-
replaced by `MCP::Client::Stdio` and `MCP::Client::HTTP` from the official
|
|
301
|
-
`mcp` gem (v0.25.0+). Adds the `mcp >= 0.3` runtime dependency.
|
|
302
|
-
- Built-in 4 MiB size limits for stdout, stderr, and HTTP responses
|
|
303
|
-
- Automatic MCP `initialize` handshake on every connection
|
|
304
|
-
- SSE response parsing handled by the SDK
|
|
305
|
-
- `from_server` and `execute` convert SDK errors into `Phronomy::ToolError`
|
|
306
|
-
- Removes all `Thread.new` usage from `mcp.rb`; entry removed from
|
|
307
|
-
`THREAD_NEW_ALLOWLIST` in `thread_invariants_spec.rb`
|
|
308
|
-
|
|
309
|
-
- **`CancellationToken` bridged into MCP `call_tool`** (Issue #390):
|
|
310
|
-
`Mcp#execute` now accepts a `cancellation_token:` keyword argument.
|
|
311
|
-
When provided, the token is bridged to `MCP::Cancellation` via `on_cancel`,
|
|
312
|
-
so an explicit `cancel!` propagates into the in-flight `call_tool` request
|
|
313
|
-
as a MCP `notifications/cancelled` message.
|
|
314
|
-
|
|
315
|
-
- **`config[:tool_timeout]` forwarded to `BlockingAdapterPool`** (Issue #390):
|
|
316
|
-
`ToolExecutor.call_async` and `Capability::Base#call_async` now accept a
|
|
317
|
-
`config:` keyword. `config[:tool_timeout]` is passed as the `timeout:` to
|
|
318
|
-
`pool.submit`, enabling the same abandoned-operation tracking that LLM calls
|
|
319
|
-
already use via `config[:llm_timeout]`.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
## [0.10.0] - 2026-06-08
|
|
324
|
-
|
|
325
|
-
### Added
|
|
326
|
-
|
|
327
|
-
- **`Task#map` — transform a Task's completed value** (#384):
|
|
328
|
-
`task.map { |result| ctx.merge(answer: result[:output]) }` returns a new `Task`
|
|
329
|
-
whose completed value is the block's return value. Primary use-case: wire
|
|
330
|
-
`Agent::Base#invoke_async` into a Workflow entry action so the agent result
|
|
331
|
-
reaches `WorkflowContext` through the standard `:action_completed` path without
|
|
332
|
-
requiring any changes to `FSMSession`. If the source task fails or is cancelled,
|
|
333
|
-
the mapped task propagates the error without calling the block.
|
|
334
|
-
|
|
335
|
-
### Removed
|
|
336
|
-
|
|
337
|
-
- **`Agent::Base#run_as_child` removed** (#384):
|
|
338
|
-
Introduced when agents had their own FSM (`Agent::FSM`). After `Agent::FSM` was
|
|
339
|
-
removed in v0.9.0, the `:child_completed` event payload was silently discarded by
|
|
340
|
-
`FSMSession`, so `ctx.answer` was never populated. Migrate to
|
|
341
|
-
`invoke_async + Task#map`:
|
|
342
|
-
|
|
343
|
-
```ruby
|
|
344
|
-
# Before (removed)
|
|
345
|
-
entry :translate, ->(ctx) { TranslationAgent.new.run_as_child(ctx.query, ctx: ctx) }
|
|
346
|
-
transition from: :translate, on: :child_completed, to: :done
|
|
347
|
-
|
|
348
|
-
# After (recommended)
|
|
349
|
-
entry :translate, ->(ctx) {
|
|
350
|
-
TranslationAgent.new.invoke_async(ctx.query).map { |r| ctx.merge(answer: r[:output]) }
|
|
351
|
-
}
|
|
352
|
-
transition from: :translate, to: :done
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
### Added
|
|
356
|
-
|
|
357
|
-
- **`Task#map` — transform a Task's completed value** (post-v0.9.0):
|
|
358
|
-
`task.map { |result| ctx.merge(answer: result[:output]) }` returns a new `Task`
|
|
359
|
-
whose completed value is the block's return value. The primary use-case is
|
|
360
|
-
connecting `Agent::Base#invoke_async` to a Workflow entry action so the agent
|
|
361
|
-
result populates the `WorkflowContext` through the standard `:action_completed`
|
|
362
|
-
path. If the source task fails or is cancelled, the mapped task propagates the
|
|
363
|
-
error without calling the block.
|
|
364
|
-
|
|
365
|
-
- **`Phronomy::Diagnostics` and `SchedulerReentrancyError`** (#278, #279):
|
|
366
|
-
`Phronomy::Diagnostics` exposes a snapshot of current scheduler state
|
|
367
|
-
(`pending_count`, `active_tasks`, `pool_utilization`, etc.) for debugging and
|
|
368
|
-
monitoring. `SchedulerReentrancyError` is raised when a scheduler operation is
|
|
369
|
-
attempted from within a scheduler callback, preventing deadlocks.
|
|
370
|
-
`Phronomy.configure { |c| c.scheduler_debug = true }` enables verbose scheduler
|
|
371
|
-
logging.
|
|
372
|
-
|
|
373
|
-
- **`task_id` / `parent_task_id` on `InvocationContext`** (#277):
|
|
374
|
-
Every task spawned via `Task.spawn` now carries a `task_id` (a random UUID) and
|
|
375
|
-
an optional `parent_task_id`. These fields enable hierarchical task-tree tracing
|
|
376
|
-
and are forwarded automatically by `TaskGroup`.
|
|
377
|
-
|
|
378
|
-
- **`Phronomy::Metrics` — task-centric observability snapshot** (#276):
|
|
379
|
-
`Phronomy::Metrics.snapshot` returns a hash with scheduler statistics:
|
|
380
|
-
`tasks_started`, `tasks_completed`, `tasks_failed`, `pool_queue_depth`, and
|
|
381
|
-
`pool_active_threads`. Intended for metrics export and health-check endpoints.
|
|
382
|
-
|
|
383
|
-
- **`Phronomy::Testing::FakeClock` and `FakeScheduler`** (#273):
|
|
384
|
-
Two test helpers for deterministic concurrency testing.
|
|
385
|
-
`FakeClock` exposes `advance(seconds)` to control the passage of time without
|
|
386
|
-
sleeping. `FakeScheduler` replaces the real scheduler in specs, providing
|
|
387
|
-
synchronous execution and `flush` / `drain` helpers to drive task completion.
|
|
388
|
-
|
|
389
|
-
- **`ScopePolicy` and approval gate integration** (#270):
|
|
390
|
-
`Phronomy::Tool::ScopePolicy` is a callable that maps `(tool_class, scope, agent)`
|
|
391
|
-
to `:allow`, `:approve`, or `:reject`. The default policy (`ScopePolicy::DEFAULT`)
|
|
392
|
-
automatically routes tools declaring high-risk scopes (`:write`, `:admin`,
|
|
393
|
-
`:external_network`, `:filesystem`, `:process`, `:external_process`) through the
|
|
394
|
-
existing approval gate; tools with `scope :read_only` or no scope are allowed
|
|
395
|
-
unconditionally. Per-agent policy overrides are available via
|
|
396
|
-
`agent.scope_policy = my_policy`.
|
|
397
|
-
**Behaviour change**: tools with the above scopes that previously executed without
|
|
398
|
-
an approval handler will now be **rejected** unless an approval handler is
|
|
399
|
-
registered or the agent uses a custom permissive policy.
|
|
400
|
-
|
|
401
|
-
- **`PromptInjectionGuardrail`, `Tool::Base#redact_params`, and `#max_result_size`** (#271):
|
|
402
|
-
`Phronomy::Guardrail::PromptInjectionGuardrail` is a built-in `InputGuardrail`
|
|
403
|
-
subclass that detects prompt-injection patterns in user input.
|
|
404
|
-
`Tool::Base.redact_params(*names)` marks parameter names as sensitive; their
|
|
405
|
-
values are replaced with `"[REDACTED]"` in log and trace output.
|
|
406
|
-
`Tool::Base.max_result_size(n)` sets a per-tool character limit; results
|
|
407
|
-
exceeding the limit are truncated and a warning is logged. The global fallback is
|
|
408
|
-
`Phronomy.configure { |c| c.tool_result_max_size = n }` (default: no limit).
|
|
409
|
-
|
|
410
|
-
- **`execution_mode` DSL on `Tool::Base`** (#263):
|
|
411
|
-
`Tool::Base.execution_mode` accepts `:cooperative`, `:blocking_io` (default),
|
|
412
|
-
`:cpu_bound`, or `:external_process`. Tools marked `:blocking_io` (the default)
|
|
413
|
-
are dispatched through `BlockingAdapterPool` when a `Runtime` is available,
|
|
414
|
-
keeping the scheduler thread unblocked. Tools marked `:cooperative` are called
|
|
415
|
-
directly on the scheduler thread (suitable for pure in-memory operations).
|
|
416
|
-
|
|
417
|
-
- **`invoke_async` and `call_async` — async entry points** (#262):
|
|
418
|
-
`Agent::Base#invoke_async(input, **opts)` returns a `Phronomy::Task` wrapping
|
|
419
|
-
`#invoke`. `Workflow#invoke_async(input, config:)` does the same for workflows.
|
|
420
|
-
`Tool::Base#call_async(args, cancellation_token:)` returns a `Task` wrapping
|
|
421
|
-
`#call`. All three are backward-compatible with existing synchronous callers.
|
|
422
|
-
|
|
423
|
-
- **`LLMAdapter` abstraction** (#266):
|
|
424
|
-
`Phronomy::LLMAdapter::Base` decouples the agent pipeline from RubyLLM.
|
|
425
|
-
`Phronomy::LLMAdapter::RubyLLM` (registered by default) wraps the existing
|
|
426
|
-
integration. Custom adapters can be registered via
|
|
427
|
-
`Phronomy.configure { |c| c.llm_adapter = MyAdapter }` for testing or
|
|
428
|
-
alternative LLM backends.
|
|
429
|
-
|
|
430
|
-
- **`BlockingAdapterPool` backpressure limits** (#268):
|
|
431
|
-
`BlockingAdapterPool` now enforces configurable `pool_size` (default: 10) and
|
|
432
|
-
`queue_size` (default: 100) limits. Tasks submitted when the queue is full raise
|
|
433
|
-
`Phronomy::BackpressureError` immediately instead of growing the queue without
|
|
434
|
-
bound.
|
|
435
|
-
|
|
436
|
-
- **Cooperative scheduler fairness** (#269):
|
|
437
|
-
The scheduler measures per-task lag and emits starvation and dispatch warnings
|
|
438
|
-
via `Phronomy.configuration.logger` when tasks wait longer than configured
|
|
439
|
-
thresholds. Configurable via `scheduler_starvation_warn_ms` and
|
|
440
|
-
`scheduler_dispatch_warn_ms`.
|
|
441
|
-
|
|
442
|
-
- **Workflow entry actions awaitable with Task** (#264):
|
|
443
|
-
Entry action lambdas may now return a `Phronomy::Task`. The FSMSession awaits
|
|
444
|
-
the task on a background thread and posts `:action_completed` (with the resulting
|
|
445
|
-
`WorkflowContext`) or `:state_completed` back to the EventLoop without blocking
|
|
446
|
-
it. Backward-compatible: lambdas that return a `WorkflowContext` or `nil`
|
|
447
|
-
continue to work as before.
|
|
448
|
-
|
|
449
|
-
- **`Task`, `TaskGroup`, `AsyncQueue`, `Deadline`, `InvocationContext`, `Runtime` concurrency abstractions** (#255):
|
|
450
|
-
Six new concurrency primitives form the foundation of the async execution layer.
|
|
451
|
-
`Task` wraps a callable with cancellation, timeout (`Deadline`), and context
|
|
452
|
-
propagation (`InvocationContext`). `TaskGroup` runs tasks concurrently and waits
|
|
453
|
-
for all to finish (or the first failure). `AsyncQueue` is a bounded, cancellable
|
|
454
|
-
queue. `Runtime` is the top-level façade that resolves a `BlockingAdapterPool`
|
|
455
|
-
and provides `blocking_io { }` and `cpu_bound { }` dispatch helpers.
|
|
456
|
-
|
|
457
|
-
- **`BlockingAdapterPool`** (#256):
|
|
458
|
-
A bounded thread pool that isolates blocking I/O (LLM calls, database queries,
|
|
459
|
-
HTTP requests) from the cooperative scheduler thread. Default pool size is 10
|
|
460
|
-
threads with a queue depth of 100. Replaces direct `Thread.new` calls in core
|
|
461
|
-
agent and tool paths.
|
|
462
|
-
|
|
463
|
-
- **`VectorStore#size` — document count for all backends, contract coverage for RedisSearch and Pgvector** (#240):
|
|
464
|
-
`VectorStore::Base` gains `#size` as an abstract method; `InMemory`, `RedisSearch`,
|
|
465
|
-
and `Pgvector` all implement it. `RedisSearch#size` queries `FT.INFO num_docs`;
|
|
466
|
-
`Pgvector#size` delegates to `model_class.count`. The `a_vector_store` shared example
|
|
467
|
-
is applied to RedisSearch and Pgvector (nightly real-backend CI); unit specs add a
|
|
468
|
-
skip-guarded `it_behaves_like` reference and dedicated `#size` unit tests.
|
|
469
|
-
`empty_store` override hook added to the shared example for real-backend callers.
|
|
470
|
-
|
|
471
|
-
- **`force_kill: false` default in `dispatch_parallel`, `fan_out`, and `EventLoop#stop`** (#235):
|
|
472
|
-
Thread#kill is now opt-in. The default `force_kill: false` leaves timed-out workers
|
|
473
|
-
running and raises `TimeoutError` immediately, avoiding the risk of interrupted
|
|
474
|
-
`ensure` blocks or corrupted database transactions. Pass `force_kill: true` to
|
|
475
|
-
restore the previous behaviour (with a `logger.warn` to make it visible).
|
|
476
|
-
`EventLoop#stop` gains the same keyword and returns `:timeout` instead of
|
|
477
|
-
`:force_killed` when `force_kill: false` and the thread is still alive.
|
|
478
|
-
|
|
479
|
-
- **Public API compatibility snapshot spec** (#236):
|
|
480
|
-
`spec/phronomy/public_api_spec.rb` enumerates expected public methods for every
|
|
481
|
-
`Stable`-tagged constant. The spec runs as part of the default RSpec suite; any
|
|
482
|
-
accidental removal or rename of a listed method now fails CI immediately.
|
|
483
|
-
|
|
484
|
-
- **Nightly real-backend CI split into three independent job groups** (#238):
|
|
485
|
-
The nightly workflow (`nightly.yml`) now has three separately skippable jobs:
|
|
486
|
-
`real-backend-redis` (Redis Stack), `real-backend-pgvector` (PostgreSQL + pgvector),
|
|
487
|
-
and `real-backend-otel` (OpenTelemetry in-process SDK exporter). Each job runs only
|
|
488
|
-
the relevant spec with `--tag real_backend:<backend>`. The existing `redis_search_spec`
|
|
489
|
-
and `pgvector_spec` gain the `real_backend:` metadata tag. A new `otel_spec.rb`
|
|
490
|
-
verifies span emission, attribute attachment, and error recording via
|
|
491
|
-
`InMemorySpanExporter`.
|
|
492
|
-
|
|
493
|
-
- **`CancellationToken#raise_if_cancelled!` — convenience cancellation check** (#234):
|
|
494
|
-
New instance method that raises `Phronomy::CancellationError` when the token is
|
|
495
|
-
cancelled, or returns `nil` otherwise. Replaces the `if cancelled? then raise`
|
|
496
|
-
pattern inside tools, RAG loaders, and hooks.
|
|
497
|
-
|
|
498
|
-
- **Tool cooperative cancellation via `cancellation_token:` keyword** (#234):
|
|
499
|
-
`Tool::Base#call` now injects `Thread.current[:phronomy_cancellation_token]` as
|
|
500
|
-
`cancellation_token:` into `execute` when the method declares that keyword. Existing
|
|
501
|
-
tools without the keyword continue to work unchanged. Tool authors can opt in:
|
|
502
|
-
`def execute(query:, cancellation_token: nil)`.
|
|
503
|
-
|
|
504
|
-
- **`CancellationToken.timeout_after` — monotonic-clock deadline** (#225):
|
|
505
|
-
New `CancellationToken.timeout_after(seconds)` class method creates a token that
|
|
506
|
-
becomes cancelled after the specified number of seconds, measured with
|
|
507
|
-
`Process::CLOCK_MONOTONIC` (immune to NTP/DST drift). The existing `deadline:`
|
|
508
|
-
keyword for wall-clock deadlines remains supported for backward compatibility.
|
|
509
|
-
|
|
510
|
-
- **`EventLoop#stop` — drain mode and cooperative shutdown** (#233):
|
|
511
|
-
`EventLoop#stop` now accepts a `drain: true` keyword (default: `false`). When
|
|
512
|
-
set, the loop waits up to `Phronomy.configuration.event_loop_stop_grace_seconds`
|
|
513
|
-
(default: 5 s, configurable) for in-flight FSM sessions to complete before
|
|
514
|
-
joining threads. New sessions submitted while shutdown is pending are rejected
|
|
515
|
-
immediately with `Phronomy::CancellationError`. A new
|
|
516
|
-
`event_loop_stop_grace_seconds` configuration attribute is available on
|
|
517
|
-
`Phronomy::Configuration`.
|
|
518
|
-
|
|
519
|
-
- **`invoke_timeout` DSL and `Phronomy::TimeoutError`**: Agents can declare a per-invoke
|
|
520
|
-
timeout in seconds via `invoke_timeout N` in the class body. Exceeding the timeout raises
|
|
521
|
-
`Phronomy::TimeoutError` (a subclass of `Phronomy::Error`). The default remains unlimited.
|
|
522
|
-
|
|
523
|
-
- **`dispatch_parallel` / `fan_out` — per-call `timeout:` option** (#133): Both methods now
|
|
524
|
-
accept `timeout: nil` (default, unlimited) or a positive `Numeric` in seconds. Timed-out
|
|
525
|
-
tasks are treated the same as errors and follow the existing `on_error:` policy (`:raise`
|
|
526
|
-
or `:skip`).
|
|
527
|
-
|
|
528
|
-
- **MCP `HttpTransport` custom authentication headers** (#144): `Phronomy::Tools::Mcp::HttpTransport#initialize`
|
|
529
|
-
now accepts `headers: {}`. Arbitrary headers (e.g. `Authorization: Bearer …`) are injected
|
|
530
|
-
into every JSON-RPC request, enabling use of MCP servers that require bearer tokens or
|
|
531
|
-
API keys. Threading `headers:` through `Mcp.from_server` is tracked in issue #144 and
|
|
532
|
-
pending in PR #151.
|
|
533
|
-
|
|
534
|
-
- **`StdioTransport` — `env:`, `cwd:`, and `startup_timeout:` options** (#145):
|
|
535
|
-
Three new keyword arguments are now accepted when constructing a `StdioTransport` (and
|
|
536
|
-
therefore via `McpTool.from_server`): `env: {}` merges extra variables into the child
|
|
537
|
-
process environment; `cwd: nil` sets the working directory; `startup_timeout: 5` limits
|
|
538
|
-
how long to wait for the child process to become ready.
|
|
539
|
-
|
|
540
|
-
- **Workflow DSL validates graph structure at build time** (#124): `Phronomy::Workflow.define`
|
|
541
|
-
now raises `ArgumentError` immediately for hard structural errors (no states declared,
|
|
542
|
-
transitions referencing undefined targets). Unreachable states emit a warning but do not
|
|
543
|
-
raise. Errors surface at load time rather than at the first `invoke`.
|
|
544
|
-
|
|
545
|
-
- **Expanded error taxonomy** (#149): Five new subclasses of `Phronomy::Error` are now
|
|
546
|
-
available: `TransportError` (MCP or LLM network-layer failure; subclasses are
|
|
547
|
-
`RateLimitError` for HTTP 429 and `AuthenticationError` for HTTP 401/403),
|
|
548
|
-
`ContextLengthError` (prompt exceeds model context window), and
|
|
549
|
-
`CancellationError` (explicit invocation cancellation, distinct from the
|
|
550
|
-
deadline-exceeded `TimeoutError`). All five are defined as subclasses of
|
|
551
|
-
`Phronomy::Error` so application code can rescue them uniformly.
|
|
552
|
-
|
|
553
|
-
- **`Agent::Base.static_knowledge_refresh!`** (#164): New class-level method that clears the
|
|
554
|
-
cached `static_knowledge` chunks so the next `invoke` re-fetches from all registered
|
|
555
|
-
sources. Essential for long-running processes (web servers, job workers) where knowledge
|
|
556
|
-
sources may be updated at runtime without a process restart.
|
|
557
|
-
|
|
558
|
-
- **`Phronomy::Configuration#logger`** (#158): New optional configuration attribute. Any
|
|
559
|
-
object responding to `#warn` (e.g. `Rails.logger`) can be assigned. Framework diagnostic
|
|
560
|
-
messages — starting with the unreachable-state warning from `Workflow.define` — are routed
|
|
561
|
-
through this logger instead of writing directly to `$stderr` via `Kernel#warn`.
|
|
562
|
-
|
|
563
|
-
- **`Phronomy.with_configuration` and `Phronomy.reset_runtime!`** (#206): Two new class
|
|
564
|
-
methods for runtime isolation. `with_configuration` yields the current `Configuration`
|
|
565
|
-
object and restores the original after the block — even on exception — enabling per-request
|
|
566
|
-
overrides and scoped test configuration. `reset_runtime!` stops any running `EventLoop`,
|
|
567
|
-
clears its singleton, and resets configuration to defaults; intended for test suites to
|
|
568
|
-
ensure clean state between examples. `spec_helper.rb` now calls `reset_runtime!` in an
|
|
569
|
-
`after(:each)` hook automatically.
|
|
570
|
-
|
|
571
|
-
- **`CancellationToken` — cooperative cancellation for agent invocations** (#216):
|
|
572
|
-
New class `Phronomy::CancellationToken` enables cooperative cancellation without
|
|
573
|
-
`Thread#kill`. Tokens are passed via `config: { cancellation_token: token }`.
|
|
574
|
-
`cancel!` marks the token (thread-safe via Mutex); `cancelled?` returns `true`
|
|
575
|
-
once cancelled or once an optional `deadline: Time` has passed. Agents check the
|
|
576
|
-
token in `_invoke_impl` (fail-fast before any LLM call) and again immediately
|
|
577
|
-
before `chat.ask`. `CancellationError` is never retried by the retry policy.
|
|
578
|
-
`dispatch_parallel` and `fan_out` accept `cancellation_token:` and automatically
|
|
579
|
-
inject it into every worker task's config unless the task already supplies its own.
|
|
580
|
-
|
|
581
|
-
### Added (post-v0.9.0)
|
|
582
|
-
|
|
583
|
-
- **`Agent::Base#run_as_child` removed** (post-v0.9.0):
|
|
584
|
-
`run_as_child` was introduced when agents had their own FSM. After `Agent::FSM`
|
|
585
|
-
was removed, the `:child_completed` event payload was silently discarded by
|
|
586
|
-
`FSMSession`, meaning `ctx.answer` was never populated. The method has been
|
|
587
|
-
removed. Use `invoke_async + Task#map` instead:
|
|
588
|
-
|
|
589
|
-
```ruby
|
|
590
|
-
# Before (deprecated)
|
|
591
|
-
entry :translate, ->(ctx) { TranslationAgent.new.run_as_child(ctx.query, ctx: ctx) }
|
|
592
|
-
transition from: :translate, on: :child_completed, to: :done
|
|
593
|
-
|
|
594
|
-
# After (recommended)
|
|
595
|
-
entry :translate, ->(ctx) {
|
|
596
|
-
TranslationAgent.new.invoke_async(ctx.query).map { |r| ctx.merge(answer: r[:output]) }
|
|
597
|
-
}
|
|
598
|
-
transition from: :translate, to: :done
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
- **`Phronomy::Agent::CheckpointStore` — idempotency store for HITL resume** (post-v0.9.0):
|
|
602
|
-
New in-memory store tracks consumed checkpoint IDs. Calling `Agent::Base#resume` twice
|
|
603
|
-
with the same checkpoint raises `Phronomy::CheckpointAlreadyResumedError` instead of
|
|
604
|
-
silently re-executing the approved tool. Custom stores can be injected via
|
|
605
|
-
`agent.checkpoint_store = MyRedis::CheckpointStore.new`. Duck-type contract:
|
|
606
|
-
`consumed?(id)`, `consume!(id)`, and optionally `cleanup!(id)` / `clear!`.
|
|
607
|
-
|
|
608
|
-
- **`checkpoint_id`, `agent_class`, `requested_at` on `Checkpoint`; `Agent::Base.resume` class method** (post-v0.9.0):
|
|
609
|
-
`Checkpoint` now carries a UUID `checkpoint_id` (idempotency key), `agent_class`
|
|
610
|
-
(fully-qualified class name), and `requested_at` (UTC timestamp). The new class-level
|
|
611
|
-
`Agent::Base.resume(checkpoint, approved:)` method instantiates the correct agent class
|
|
612
|
-
automatically and delegates to `#resume`, simplifying job-queue resume flows.
|
|
613
|
-
|
|
614
|
-
- **`CheckpointStore#cleanup!` and `#clear!`** (post-v0.9.0):
|
|
615
|
-
Optional methods on the `CheckpointStore` duck-type contract. `cleanup!(checkpoint_id)`
|
|
616
|
-
removes a single checkpoint entry; `clear!` wipes all tracking state.
|
|
617
|
-
|
|
618
|
-
### Removed
|
|
619
|
-
|
|
620
|
-
- **`Phronomy::ReactAgent` class removed** (post-v0.9.0):
|
|
621
|
-
Use `Phronomy::Agent::Base` directly. `ReactAgent` had no distinct public API beyond
|
|
622
|
-
`Agent::Base` and was not listed in the stability table.
|
|
623
|
-
|
|
624
|
-
- **`Phronomy::Agent::FSM` class removed** (post-v0.9.0, internal):
|
|
625
|
-
The agent invocation path is now unified through `Agent::Base#invoke` with inline logic.
|
|
626
|
-
No public API impact.
|
|
627
|
-
|
|
628
|
-
- **`Phronomy::Agent::Lifecycle::FSMSession` and `::PhaseMachineBuilder` moved to `Workflow` namespace** (post-v0.9.0, internal):
|
|
629
|
-
These internal classes now live at `Phronomy::Workflow::FSMSession` and
|
|
630
|
-
`Phronomy::Workflow::PhaseMachineBuilder`. No public API impact.
|
|
631
|
-
|
|
632
|
-
- **BREAKING: `Agent::Base#run_as_child` drops `&result_writer` block parameter** (#265):
|
|
633
|
-
The optional block form `run_as_child(input, ctx: ctx) { |r| ctx.answer = r[:output] }`
|
|
634
|
-
is no longer supported. The result is now delivered **exclusively** as the
|
|
635
|
-
`:child_completed` event payload `{ output:, messages:, usage: }`. The parent
|
|
636
|
-
Workflow task is the sole owner of the `WorkflowContext`; no background thread
|
|
637
|
-
writes to it directly. Callers that were using the block to write back into the
|
|
638
|
-
context must update their workflow design (e.g. read the result in the target
|
|
639
|
-
state's entry action after the transition, or store output through an external
|
|
640
|
-
shared resource if needed).
|
|
641
|
-
|
|
642
|
-
- **BREAKING (internal): `AgentFSM#initialize` drops `result_writer:` keyword** (#265):
|
|
643
|
-
Direct callers of `AgentFSM.new(result_writer: ...)` must remove that keyword.
|
|
644
|
-
This class is considered internal; gem consumers should use `run_as_child` instead.
|
|
645
|
-
|
|
646
|
-
### Changed
|
|
647
|
-
|
|
648
|
-
- **`AgentFSM`, `ParallelToolChat`, and `Orchestrator` use `Task`/`TaskGroup` instead of bare `Thread.new`** (#257, #258, #259):
|
|
649
|
-
All three components now spawn async work through the `Task` and `TaskGroup`
|
|
650
|
-
abstractions. This enables cancellation propagation, context threading, and
|
|
651
|
-
`BlockingAdapterPool` routing. No public API changes; behaviour is equivalent.
|
|
652
|
-
|
|
653
|
-
- **`Thread.current[:phronomy_*]` context propagation replaced with explicit `InvocationContext`** (#260):
|
|
654
|
-
Thread-local keys `phronomy_event_loop_thread`, `phronomy_cancellation_token`,
|
|
655
|
-
and `phronomy_context_version_caches` are no longer used as the primary
|
|
656
|
-
propagation channel. `InvocationContext` is threaded explicitly through call
|
|
657
|
-
stacks. Importantly, `Tool::Base#call` no longer falls back to
|
|
658
|
-
`Thread.current[:phronomy_cancellation_token]`; cancellation is only observed
|
|
659
|
-
when the caller passes `cancellation_token:` explicitly (or when
|
|
660
|
-
`ParallelToolChat` injects it). Tools that relied on the thread-local fallback
|
|
661
|
-
must be updated.
|
|
662
|
-
|
|
663
|
-
- **`Timeout.timeout` removed from core paths; replaced with `CancellationScope`** (#261):
|
|
664
|
-
`Agent::Base#invoke` and `McpTool::StdioTransport` no longer use `Timeout.timeout`
|
|
665
|
-
(which is unsafe with `Thread.new` and `ensure` blocks). A `CancellationScope`
|
|
666
|
-
with `deadline_in(seconds)` provides equivalent semantics without the thread-
|
|
667
|
-
interruption hazards. `ScopeTimeoutError < TimeoutError` is raised on expiry.
|
|
668
|
-
|
|
669
|
-
- **RAG/VectorStore blocking I/O placed behind `BlockingAdapterPool` async boundary** (#267):
|
|
670
|
-
`KnowledgeSource#fetch` and all three `VectorStore` backends now execute their
|
|
671
|
-
blocking I/O through `Runtime#blocking_io` when a `Runtime` is present. Callers
|
|
672
|
-
in a synchronous context see no change; callers in an EventLoop context benefit
|
|
673
|
-
from non-blocking scheduler behaviour.
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
The cancellation token (passed via `config: { cancellation_token: token }`) is
|
|
677
|
-
now checked at multiple additional points beyond the initial LLM call boundary:
|
|
678
|
-
before each `KnowledgeSource#fetch` in `build_context` (RAG phase); after each
|
|
679
|
-
streaming chunk in `_stream_impl`; before each tool-call batch in
|
|
680
|
-
`ParallelToolChat`; and after each `before_completion` hook. This ensures that
|
|
681
|
-
long-running retrieval, streaming, and tool-dispatch phases respect cancellation
|
|
682
|
-
with minimal latency.
|
|
683
|
-
|
|
684
|
-
- **`Agent::Orchestrator` uses `CancellationToken` for internal stop flag** (#224):
|
|
685
|
-
The boolean stop flag in `Orchestrator` is replaced with an internal
|
|
686
|
-
`CancellationToken`. FSM session loops perform cooperative cancellation checks
|
|
687
|
-
via `cancelled?`; `Thread#kill` is retained only as a last resort after
|
|
688
|
-
cooperative shutdown.
|
|
689
|
-
|
|
690
|
-
- **Error taxonomy classes are now raised at the retry boundary** (#204): The classes
|
|
691
|
-
`Phronomy::RateLimitError`, `Phronomy::AuthenticationError`, `Phronomy::ContextLengthError`,
|
|
692
|
-
and `Phronomy::TransportError` (introduced in #149) are now actually raised when the
|
|
693
|
-
corresponding `RubyLLM` exceptions occur. A new internal `ErrorTranslation` concern wraps
|
|
694
|
-
the retry exhaust path and maps `RubyLLM::*` exceptions to their Phronomy counterparts,
|
|
695
|
-
preserving the original exception as `#cause`. **Migration**: callers rescuing
|
|
696
|
-
`RubyLLM::RateLimitError` (or other `RubyLLM::*` errors) directly should migrate to
|
|
697
|
-
`rescue Phronomy::RateLimitError` / `Phronomy::TransportError` etc.
|
|
698
|
-
|
|
699
|
-
- **`Orchestrator#bounded_map` uses cooperative cancellation before force-kill** (#203):
|
|
700
|
-
Workers now check a shared `cancelled` flag at each loop iteration and stop picking up new
|
|
701
|
-
tasks once the timeout deadline passes. A 0.5 s grace period is given to in-flight workers
|
|
702
|
-
before `Thread#kill` is used as a last resort. `EventLoop#stop` similarly logs a warning
|
|
703
|
-
via `Phronomy.configuration.logger` when force-kill is triggered.
|
|
704
|
-
|
|
705
|
-
- **`Orchestrator#bounded_map` timeout deadline uses monotonic clock** (#209): Replaced
|
|
706
|
-
`Time.now` deadline arithmetic with `Process.clock_gettime(Process::CLOCK_MONOTONIC)` to
|
|
707
|
-
avoid sensitivity to NTP adjustments, DST transitions, and system-clock changes that could
|
|
708
|
-
inflate or deflate effective timeouts.
|
|
709
|
-
|
|
710
|
-
- **`EventLoop` warns on events for unknown `target_id`**: When the event loop receives an
|
|
711
|
-
event whose `target_id` does not match any registered session, a warning is emitted instead
|
|
712
|
-
of silently discarding the event.
|
|
713
|
-
|
|
714
|
-
- **`VectorStore#search` validates `k` is a positive integer**: All three backends
|
|
715
|
-
(`InMemory`, `RedisSearch`, `Pgvector`) now raise `ArgumentError` immediately when `k` is
|
|
716
|
-
not a positive integer, providing a clear error instead of a silent empty result or an
|
|
717
|
-
obscure database error.
|
|
718
|
-
|
|
719
|
-
- **`max_parallel_tools` DSL**: Agents can cap the number of concurrent tool-call threads
|
|
720
|
-
with `max_parallel_tools N` in the class body. Useful for rate-limiting external API calls.
|
|
721
|
-
The default is **10** (inheriting from `Base`); set explicitly to raise or lower the cap.
|
|
722
|
-
|
|
723
|
-
- **`max_parallel_tools` and `invoke_timeout` DSL argument validation** (#152): Both setters
|
|
724
|
-
now raise `ArgumentError` at class-definition time if the supplied value is invalid
|
|
725
|
-
(`max_parallel_tools` requires an `Integer >= 1`; `invoke_timeout` requires a positive
|
|
726
|
-
`Numeric`), surfacing configuration mistakes immediately.
|
|
727
|
-
|
|
728
|
-
- **`on_error :suppress` — canonical alias for `:return_empty`** (#165): `:suppress` is the
|
|
729
|
-
new preferred name for the error-suppression behaviour in `Tool::Base`. `:return_empty`
|
|
730
|
-
continues to function but emits a deprecation warning and will be removed in a future major
|
|
731
|
-
release. Migrate by replacing `on_error :return_empty` with `on_error :suppress`.
|
|
732
|
-
|
|
733
|
-
- **Tool nested object properties injected into JSON Schema** (#162): `Tool::Base#params_schema`
|
|
734
|
-
now recursively serialises nested `:object` param specs (including `enum` constraints and
|
|
735
|
-
further nesting) into the JSON Schema `properties` structure forwarded to the LLM,
|
|
736
|
-
enabling accurate structured argument generation for complex tool parameters.
|
|
737
|
-
|
|
738
|
-
### Fixed
|
|
739
|
-
|
|
740
|
-
- **`tool_name` preserved in `Orchestrator#prepare_tool_class` anonymous subclass wrapper**:
|
|
741
|
-
When `Orchestrator#prepare_tool_class` wrapped a subagent tool in an anonymous
|
|
742
|
-
subclass (`Class.new(prepared)`), the class-level instance variable `@tool_name`
|
|
743
|
-
was not inherited, causing the wrapper's `tool_name` to return `nil`. RubyLLM
|
|
744
|
-
then registered the tool under a `nil` key, making it unreachable when the LLM
|
|
745
|
-
called it by name. The fix captures the effective name before subclassing and
|
|
746
|
-
calls `tool_name effective_name` explicitly inside the anonymous class body —
|
|
747
|
-
the same pattern already used by the approval-gate wrapper.
|
|
748
|
-
|
|
749
|
-
- **`EventLoop#start` is now idempotent; stale `:__stop__` sentinel race fixed** (#203):
|
|
750
|
-
Calling `start` on an already-running `EventLoop` is now a no-op. Fixed a race condition
|
|
751
|
-
where `stop` setting `@running = false` before the worker thread was scheduled left the
|
|
752
|
-
`:__stop__` sentinel unconsumed in the queue; a subsequent `start` would then immediately
|
|
753
|
-
terminate the new thread upon popping the stale sentinel. The sentinel is now treated as a
|
|
754
|
-
pure unblock signal for `queue.pop` (`next` instead of `break`) — loop termination is
|
|
755
|
-
driven solely by `@running`.
|
|
756
|
-
|
|
757
|
-
- **`trace_pii: false` now redacts both input and output**: Previously only the user input
|
|
758
|
-
was redacted when `trace_pii` was `false`; LLM responses and tool results were still
|
|
759
|
-
forwarded to the tracing backend unredacted. Both sides are now replaced with `[REDACTED]`.
|
|
760
|
-
|
|
761
|
-
- **`StdioTransport` — `read_timeout` prevents indefinite blocking**: A configurable
|
|
762
|
-
`read_timeout` (default 30 s) is now enforced on MCP stdio reads. A silent child process
|
|
763
|
-
could previously block the calling thread forever.
|
|
764
|
-
|
|
765
|
-
- **MCP schema `required` and `enum` constraints propagated to `param` DSL**:
|
|
766
|
-
`McpTool.from_server` now copies `required` and `enum` constraints from the MCP JSON Schema
|
|
767
|
-
into the generated `param` declarations so downstream validation sees them.
|
|
768
|
-
|
|
769
|
-
- **`FSMSession` notifies parent when child `AgentFSM` fails**: An unhandled error in a child
|
|
770
|
-
`AgentFSM` now correctly notifies the parent `FSMSession`, preventing it from waiting
|
|
771
|
-
indefinitely for a completion event that will never arrive.
|
|
772
|
-
|
|
773
|
-
- **`WorkflowContext.field` rejects plain `Array` or `Hash` defaults**: Passing a plain `Array`
|
|
774
|
-
or `Hash` as a field default now raises `ArgumentError` at class-definition time,
|
|
775
|
-
preventing accidental state sharing across workflow invocations. Other mutable objects
|
|
776
|
-
are not checked. Wrap collection defaults in a Proc: `default: -> { [] }`.
|
|
777
|
-
|
|
778
|
-
- **Tool aliases inherited by `Agent` subclasses**: `tool_aliases` declared in a parent
|
|
779
|
-
`Agent::Base` subclass are now correctly merged into subclasses rather than being silently
|
|
780
|
-
dropped.
|
|
781
|
-
|
|
782
|
-
- **`ReactAgent` output selection skips tool-role messages**: The final output selection
|
|
783
|
-
logic no longer misidentifies `tool`-role messages as the assistant response, fixing
|
|
784
|
-
spurious tool-call JSON appearing in `result[:output]`.
|
|
785
|
-
|
|
786
|
-
- **Thread-local context cache cleaned up after each `invoke`** (#128): `Agent::Base#invoke`
|
|
787
|
-
previously leaked thread-local context cache entries after each call, causing stale cache
|
|
788
|
-
hits in long-lived threads. The cache is now cleared in an `ensure` block.
|
|
789
|
-
|
|
790
|
-
- **Unknown tool parameters are rejected** (#130): `Tool::Base#call` now raises
|
|
791
|
-
`ArgumentError` when keyword arguments not declared via the `param` DSL are passed, instead
|
|
792
|
-
of forwarding them silently to `execute`.
|
|
793
|
-
|
|
794
|
-
- **`EventLoop#stop` uses cooperative shutdown instead of `Thread#kill`** (#135):
|
|
795
|
-
`Thread#kill` bypasses `ensure` blocks and is unsafe. The event loop now sets a sentinel
|
|
796
|
-
flag and joins the worker thread, allowing it to flush pending events before termination.
|
|
797
|
-
|
|
798
|
-
- **`Orchestrator` propagates parent `config` and `thread_id` to sub-agents** (#132):
|
|
799
|
-
Sub-agents spawned via `dispatch` or `dispatch_parallel` now inherit the caller's `config`
|
|
800
|
-
hash and `thread_id`, enabling correct memory isolation and distributed tracing in
|
|
801
|
-
multi-agent pipelines.
|
|
802
|
-
|
|
803
|
-
- **`Agent::Base` caches `static_knowledge` fetch at the class level** (#127): The RAG
|
|
804
|
-
knowledge fetch was re-executed on every `invoke`. The result is now memoized at the class
|
|
805
|
-
level (`@static_knowledge_chunks ||= ...`), eliminating redundant vector-store queries.
|
|
806
|
-
The cache is **not** invalidated automatically when source content changes; call
|
|
807
|
-
`static_knowledge_refresh!` explicitly to force a reload.
|
|
808
|
-
|
|
809
|
-
- **`WorkflowContext#initialize` raises on unknown field keys** (#121): Passing an
|
|
810
|
-
unrecognised key to `WorkflowContext.new` was silently ignored. The constructor now raises
|
|
811
|
-
`ArgumentError`, surfacing typos and API mismatches immediately.
|
|
812
|
-
|
|
813
|
-
- **`WorkflowContext#merge` raises `ArgumentError` for unknown field keys** (#154): Passing
|
|
814
|
-
an unrecognised key to `WorkflowContext#merge` was silently ignored. The method now raises
|
|
815
|
-
`ArgumentError`, matching the guard added to `#initialize` in #121.
|
|
816
|
-
|
|
817
|
-
- **`WorkflowContext#deep_dup_value` rescues `TypeError` for non-dupable objects** (#156):
|
|
818
|
-
Objects that raise `TypeError` from `#dup` (e.g. `Method`, frozen `Proc`, `Integer`,
|
|
819
|
-
`Symbol`) are now returned as-is instead of crashing.
|
|
820
|
-
|
|
821
|
-
- **`Workflow.define` raises for undefined `from:` state in transitions** (#157): Transitions
|
|
822
|
-
that reference a `from:` state not declared in the DSL now raise `ArgumentError` at
|
|
823
|
-
build time, complementing the existing check for undefined `to:` targets.
|
|
824
|
-
|
|
825
|
-
- **`Workflow.define` unreachable-state warning routes through configured logger** (#158):
|
|
826
|
-
The diagnostic warning for unreachable states now uses `Phronomy.configuration.logger`
|
|
827
|
-
when set, falling back to `Kernel#warn`. Previously the warning always went to `$stderr`.
|
|
828
|
-
|
|
829
|
-
- **`require "set"` added to `workflow.rb`** (#159): Eliminates an implicit dependency on
|
|
830
|
-
`Set` being pre-loaded by another gem.
|
|
831
|
-
|
|
832
|
-
- **`Tool::Base#validate_nested_object` rejects undeclared extra keys** (#166): Keys present
|
|
833
|
-
in the LLM-supplied hash but absent from the tool's nested `param` schema now produce a
|
|
834
|
-
validation error rather than being silently forwarded.
|
|
835
|
-
|
|
836
|
-
- **`WorkflowContext#merge` deep-copies unchanged fields** (#123): Fields absent from the
|
|
837
|
-
`merge` argument were previously shared by reference with the original context, allowing
|
|
838
|
-
one branch to mutate another branch's state. All fields are now independently copied.
|
|
839
|
-
|
|
840
|
-
- **Robust metadata parsing in `VectorStore::Pgvector#search`** (#139): Metadata stored as a
|
|
841
|
-
PostgreSQL JSON string is now parsed correctly regardless of whether the database driver
|
|
842
|
-
returns a `String` or an already-decoded `Hash`.
|
|
843
|
-
|
|
844
|
-
- **`OutputParser::JsonParser` tries all fenced code blocks before falling back** (#146):
|
|
845
|
-
The parser now scans every fenced block in the LLM response (in order) and returns the
|
|
846
|
-
first one that parses as valid JSON, rather than only checking the first block. This
|
|
847
|
-
improves reliability with models that include prose before the JSON block.
|
|
848
|
-
|
|
849
|
-
- **`on_error: :return_empty` emits a warning and returns a descriptive string** (#147):
|
|
850
|
-
Errors in tools that declare `on_error :return_empty` are now logged to `warn` before the
|
|
851
|
-
tool returns. The placeholder string includes the tool name and a brief reason, making
|
|
852
|
-
silent failures easier to diagnose.
|
|
853
|
-
|
|
854
|
-
- **`context_version_cache` accessible after `invoke` completes**: The thread-local cache is
|
|
855
|
-
cleared in `invoke`'s `ensure` block, which caused `context_version_cache` to return `nil`
|
|
856
|
-
immediately after every call. The value is now persisted in `@last_context_version_cache`
|
|
857
|
-
so it remains readable post-invoke.
|
|
858
|
-
|
|
859
|
-
- **`WorkflowContext` field type `:merge` comment corrected**: The inline comment incorrectly
|
|
860
|
-
described `:merge` as a deep-merge. It performs a shallow merge (`Hash#merge`). The comment
|
|
861
|
-
has been updated.
|
|
862
|
-
|
|
863
|
-
- **`WorkflowContext` return value from entry actions now adopted in EventLoop mode** (#107):
|
|
864
|
-
`FSMSession` previously discarded the `WorkflowContext` returned by entry action callables,
|
|
865
|
-
causing `s.merge(...)` updates to be silently lost when `event_loop = true`. The context is
|
|
866
|
-
now correctly propagated, bringing EventLoop semantics in line with the synchronous
|
|
867
|
-
`WorkflowRunner`. Regression tests added in `spec/phronomy/fsm_session_spec.rb` (unit)
|
|
868
|
-
and `spec/integration/workflow_spec.rb` (integration, both sync and EventLoop paths).
|
|
869
|
-
|
|
870
|
-
### Documentation
|
|
871
|
-
|
|
872
|
-
- **`trace_pii = false` description corrected** (#153): The inline comment and README Note
|
|
873
|
-
now correctly state that both the input and the output are redacted.
|
|
874
|
-
|
|
875
|
-
- **`invoke_timeout` is a wait timeout, not cancellation** (#163): YARD comment now
|
|
876
|
-
explicitly documents that the background agent thread and in-flight LLM/tool calls are
|
|
877
|
-
**not** interrupted when the timeout fires. Only the caller receives `TimeoutError`.
|
|
878
|
-
|
|
879
|
-
- **`context_version_cache` thread-safety limitation documented** (#161): A NOTE in the YARD
|
|
880
|
-
comment explains that the per-instance cache is not thread-safe when the same agent
|
|
881
|
-
instance is shared across threads.
|
|
882
|
-
|
|
883
|
-
- **`trace_pii` option documented in README**: The `trace_pii:` configuration key and its
|
|
884
|
-
behaviour (default `false`, redacts input and output in trace records) is now described in
|
|
885
|
-
the Configuration section of the README.
|
|
886
|
-
|
|
887
|
-
- **CJK token under-count warning in `TokenEstimator`**: A note in both the source and README
|
|
888
|
-
explains that the byte-based heuristic under-counts CJK characters by roughly 3×. Users
|
|
889
|
-
processing Chinese, Japanese, or Korean content should apply a correction factor or use a
|
|
890
|
-
model-specific tokenizer.
|
|
891
|
-
|
|
892
|
-
- **Stability labels, `reset_configuration!` caveat, CI, and gemspec** (#140 / #141 / #142 / #143 / #148 / #150):
|
|
893
|
-
README stability table revised for several APIs. `Phronomy.reset_configuration!` now carries
|
|
894
|
-
a warning that it is intended for test isolation only. Gemspec upper bounds added for
|
|
895
|
-
`ruby_llm` and `pg`. `ruby head` added to the CI test matrix. README API smoke tests added.
|
|
896
|
-
|
|
897
|
-
---
|
|
898
|
-
|
|
899
|
-
## [0.6.0] - 2026-05-21
|
|
900
|
-
|
|
901
|
-
### Removed
|
|
902
|
-
|
|
903
|
-
- **`Phronomy::Guardrail::Builtin` module removed**: `PromptInjectionDetector`
|
|
904
|
-
and `PIIPatternDetector` are opt-in pattern-matching helpers that encode
|
|
905
|
-
application-level policy decisions (which phrases to block, which PII
|
|
906
|
-
categories to detect, which languages to support). Shipping them as gem
|
|
907
|
-
defaults was misleading — their correct home is inside each application that
|
|
908
|
-
needs them. Reference implementations are now provided in example 06 of
|
|
909
|
-
`phronomy-examples`. Extend `Phronomy::Guardrail::InputGuardrail` directly to
|
|
910
|
-
create equivalent guardrails in your application.
|
|
911
|
-
|
|
912
|
-
---
|
|
913
|
-
|
|
914
|
-
## [0.5.4] - 2026-05-20
|
|
915
|
-
|
|
916
|
-
### New Features
|
|
917
|
-
|
|
918
|
-
- **VectorStore embedding dimension validation** (#98): All three vector store
|
|
919
|
-
implementations (`InMemory`, `RedisSearch`, `Pgvector`) now validate that every
|
|
920
|
-
embedding passed to `add` and `search` matches the expected dimension.
|
|
921
|
-
Dimension is inferred automatically from the first `add` call; alternatively
|
|
922
|
-
it can be set explicitly via `initialize(dimension: N)`. A mismatch raises
|
|
923
|
-
`ArgumentError` with a descriptive message. The `search` method never
|
|
924
|
-
establishes the dimension — it only validates when a dimension is already
|
|
925
|
-
known. `clear` retains the established dimension (schema property).
|
|
926
|
-
|
|
927
|
-
- **`dispatch_parallel` / `fan_out` concurrency controls** (#99): Two new
|
|
928
|
-
keyword arguments are now accepted by both methods.
|
|
929
|
-
- `max_concurrency: nil` (default) or a positive `Integer` — caps the number
|
|
930
|
-
of worker threads. `nil` means one thread per task (previous behaviour).
|
|
931
|
-
- `on_error: :raise` (default) or `:skip` — controls failure handling.
|
|
932
|
-
`:raise` runs all tasks to completion then re-raises the first error in
|
|
933
|
-
input order (fail-last, not fail-fast). `:skip` fills failed slots with
|
|
934
|
-
`nil` and never raises.
|
|
935
|
-
The underlying implementation uses a `Queue`-based bounded worker pool
|
|
936
|
-
(`bounded_map`) for predictable resource usage.
|
|
937
|
-
|
|
938
|
-
---
|
|
939
|
-
|
|
940
|
-
## [0.5.3] - 2026-05-20
|
|
941
|
-
|
|
942
|
-
### Bug Fixes
|
|
943
|
-
|
|
944
|
-
- **Ensure `from_server` closes transport on error** (#95): The short-lived
|
|
945
|
-
transport created inside `McpTool.from_server` is now wrapped in
|
|
946
|
-
`begin/ensure`, so the underlying child process (stdio) is always
|
|
947
|
-
terminated even when `fetch_tool` raises.
|
|
948
|
-
- **Correct `McpTool#close` documentation** (#94): The comment previously
|
|
949
|
-
stated that calling `execute` after `close` raises an error; in practice
|
|
950
|
-
the transport reopens automatically. The comment now reflects actual
|
|
951
|
-
behaviour.
|
|
952
|
-
|
|
953
|
-
### Documentation
|
|
954
|
-
|
|
955
|
-
- **Fix CHANGELOG date for v0.5.1** (#96): The v0.5.1 entry had an
|
|
956
|
-
incorrect date of 2026-05-21; corrected to 2026-05-20.
|
|
957
|
-
|
|
958
|
-
---
|
|
959
|
-
|
|
960
|
-
## [0.5.2] - 2026-05-20
|
|
961
|
-
|
|
962
|
-
### Bug Fixes
|
|
963
|
-
|
|
964
|
-
- **CHANGELOG correction for v0.5.1 MCP fix** (#90): The v0.5.1 entry
|
|
965
|
-
incorrectly stated that a `Mutex` was added to `StdioTransport#rpc_call`.
|
|
966
|
-
The actual fix was per-instance transport ownership (each `McpTool` instance
|
|
967
|
-
creates its own transport in `initialize`). Corrected the description.
|
|
968
|
-
|
|
969
|
-
### Enhancements
|
|
970
|
-
|
|
971
|
-
- **Add `McpTool#close`** (#92): Tool instances now expose a `close` method
|
|
972
|
-
that shuts down the underlying stdio child process (`StdioTransport`) or
|
|
973
|
-
releases the HTTP connection (`HttpTransport`). This gives callers a
|
|
974
|
-
deterministic way to clean up resources instead of relying on GC.
|
|
975
|
-
|
|
976
|
-
### Maintenance
|
|
977
|
-
|
|
978
|
-
- **Archive stale Rails integration design doc** (#91): Added an archived
|
|
979
|
-
notice to `spec/design/17_rails_integration.md` clarifying that Rails
|
|
980
|
-
integration was removed in v0.3.0–v0.5.1 and the document is for
|
|
981
|
-
historical reference only.
|
|
982
|
-
|
|
983
|
-
- **Remove zombie `register_workflow_context` API** (#93): The
|
|
984
|
-
`Phronomy.register_workflow_context`, `workflow_context_registry`, and
|
|
985
|
-
`reset_workflow_context_registry!` methods (along with the backing
|
|
986
|
-
`@workflow_context_registry` and `@registry_mutex` module-level variables)
|
|
987
|
-
were removed from `lib/phronomy.rb`. These existed to support the
|
|
988
|
-
`StateStore` deserialization guard, which was removed in a prior release.
|
|
989
|
-
The API had no remaining callers in the codebase and was not listed in
|
|
990
|
-
the README stability table.
|
|
991
|
-
|
|
992
|
-
---
|
|
993
|
-
|
|
994
|
-
## [0.5.1] - 2026-05-20
|
|
995
|
-
|
|
996
|
-
### Bug Fixes
|
|
997
|
-
|
|
998
|
-
- **Remove broken Rails generator and Railtie** (#85): The generator template
|
|
999
|
-
referenced `Phronomy::ActiveRecord::ActsAs` which no longer exists, causing
|
|
1000
|
-
`rails generate phronomy:install` to produce broken model files. Removed
|
|
1001
|
-
`lib/generators/`, `lib/phronomy/railtie.rb`, and all references in
|
|
1002
|
-
`lib/phronomy.rb`.
|
|
1003
|
-
|
|
1004
|
-
- **Fix MCP transport ownership** (#86): `McpTool` no longer stores a shared
|
|
1005
|
-
transport at class level. `from_server` now uses a short-lived transport only
|
|
1006
|
-
to fetch tool metadata and calls `close` immediately after. Each tool instance
|
|
1007
|
-
creates its own `StdioTransport` or `HttpTransport` in `initialize`, so
|
|
1008
|
-
concurrent callers (e.g. via `Orchestrator#dispatch_parallel`) never share
|
|
1009
|
-
stdio streams. No `Mutex` is needed. Also adds missing
|
|
1010
|
-
`require "securerandom"` and a no-op `HttpTransport#close` for interface
|
|
1011
|
-
consistency.
|
|
1012
|
-
|
|
1013
|
-
### Documentation
|
|
1014
|
-
|
|
1015
|
-
- **README corrections** (#87): Remove stale Rails generator installation
|
|
1016
|
-
instructions. Clarify that `TeamCoordinator` worker state is local to a
|
|
1017
|
-
single `invoke` call (not persistent across calls). Annotate app-level
|
|
1018
|
-
examples (`09_rails_chat`, `15_rails_secure_chat`, `18_rails_agent_job`,
|
|
1019
|
-
`19_trust_pipeline`) as requiring external infrastructure. Add scope note
|
|
1020
|
-
to `Agent::Orchestrator` section.
|
|
1021
|
-
|
|
1022
|
-
### Maintenance
|
|
1023
|
-
|
|
1024
|
-
- **Add version guard to `ruby_llm_patches.rb`** (#88): The monkey-patch for
|
|
1025
|
-
the upstream `handle_error_chunk` bug (ruby_llm <= 1.15.0) is now
|
|
1026
|
-
gated behind a `Gem::Version` check so upgrading ruby_llm will
|
|
1027
|
-
automatically disable the override.
|
|
1028
|
-
|
|
1029
|
-
---
|
|
1030
|
-
|
|
1031
|
-
## [0.5.0] - 2026-05-20
|
|
1032
|
-
|
|
1033
|
-
### Breaking Changes
|
|
1034
|
-
|
|
1035
|
-
- **`Agent::Base#invoke` and `#stream` — `messages` and `thread_id` promoted to
|
|
1036
|
-
top-level keyword arguments**:
|
|
1037
|
-
Previously these values were passed inside the `config:` hash. They are now
|
|
1038
|
-
explicit keyword arguments. The `config:` hash retains other runtime options
|
|
1039
|
-
such as `:knowledge_sources`, `:user_id`, and `:session_id`.
|
|
1040
|
-
|
|
1041
|
-
**Before (v0.4.x)**:
|
|
1042
|
-
```ruby
|
|
1043
|
-
agent.invoke(input, config: { messages: prior_msgs, thread_id: "t1" })
|
|
1044
|
-
agent.stream(input, config: { messages: prior_msgs, thread_id: "t1" }) { |e| ... }
|
|
1045
|
-
```
|
|
1046
|
-
**After (v0.5.0)**:
|
|
1047
|
-
```ruby
|
|
1048
|
-
agent.invoke(input, messages: prior_msgs, thread_id: "t1")
|
|
1049
|
-
agent.stream(input, messages: prior_msgs, thread_id: "t1") { |e| ... }
|
|
1050
|
-
```
|
|
1051
|
-
Applications that only pass `:knowledge_sources`, `:user_id`, or `:session_id`
|
|
1052
|
-
in `config:` require no changes.
|
|
1053
|
-
|
|
1054
|
-
- **`Agent::Checkpoint#initialize` — `original_input:` is now a required keyword
|
|
1055
|
-
argument**: Applications that construct `Checkpoint` instances directly must
|
|
1056
|
-
add `original_input: input`. Checkpoints produced by `#invoke` already include
|
|
1057
|
-
this field automatically.
|
|
1058
|
-
|
|
1059
|
-
### Fixed
|
|
1060
|
-
|
|
1061
|
-
- **`ReactAgent#step` — system instructions were never applied**: The first
|
|
1062
|
-
iteration of the ReAct loop now calls `build_context` to assemble the system
|
|
1063
|
-
prompt and history, matching the behaviour of `Agent::Base`. Subsequent
|
|
1064
|
-
iterations re-apply instructions via `build_cached_system_text` before calling
|
|
1065
|
-
`chat.complete`. Previously, all iterations silently omitted the system prompt.
|
|
1066
|
-
|
|
1067
|
-
- **`Agent::Base#resume` — system instructions were not re-applied after
|
|
1068
|
-
suspension**: Resuming from a `Checkpoint` now calls `build_cached_system_text`
|
|
1069
|
-
using the original input stored in the checkpoint, so the LLM receives the
|
|
1070
|
-
correct system prompt when the conversation continues. Previously, the LLM was
|
|
1071
|
-
called without any system instructions on resume.
|
|
1072
|
-
|
|
1073
|
-
---
|
|
1074
|
-
|
|
1075
|
-
## [0.4.0] - 2026-05-19
|
|
1076
|
-
|
|
1077
|
-
### Removed
|
|
1078
|
-
|
|
1079
|
-
- **`Phronomy::TrustPipeline` removed**: The `TrustPipeline` class and its inner
|
|
1080
|
-
`TrustResult` value object have been deleted. Use `Phronomy::GeneratorVerifier`
|
|
1081
|
-
instead, which provides the same generator-verifier pattern with a cleaner,
|
|
1082
|
-
fully injectable API.
|
|
1083
|
-
|
|
1084
|
-
### Added
|
|
1085
|
-
|
|
1086
|
-
- **`Phronomy::GeneratorVerifier`** — Generator-Verifier coordination loop
|
|
1087
|
-
(Anthropic blog, Pattern 1). Wraps a generator agent and a verifier agent with
|
|
1088
|
-
fully injectable prompt builders, response parsers, a configurable iteration
|
|
1089
|
-
limit, and an approval-outcome raise policy.
|
|
1090
|
-
- **`Phronomy::Agent::Orchestrator`** — Base class for orchestrator agents
|
|
1091
|
-
(Anthropic blog, Pattern 2). Extends `Agent::Base` with a `subagent` DSL for
|
|
1092
|
-
declarative subagent registration as LLM-callable tools, plus `dispatch_parallel`
|
|
1093
|
-
and `fan_out` for programmatic parallel invocation.
|
|
1094
|
-
- **`Phronomy::Agent::TeamCoordinator`** — Agent teams coordination pattern
|
|
1095
|
-
(Anthropic blog, Pattern 3). An LLM-powered coordinator with a shared task
|
|
1096
|
-
queue and a pool of worker agents that carry conversation history across task
|
|
1097
|
-
assignments. Adds `coordinator_provider` DSL for independent LLM routing.
|
|
1098
|
-
- **`Phronomy::Agent::SharedState`** — Shared-state coordination pattern
|
|
1099
|
-
(Anthropic blog, Pattern 5). Peer agents collaborate via a `KnowledgeStore`;
|
|
1100
|
-
the `member` DSL registers agents with per-agent instructions; `coordination`
|
|
1101
|
-
sets the team protocol; `build_prompt` injects a tool-usage guide automatically.
|
|
1102
|
-
- **`Phronomy::LowConfidenceError`** — Exception raised by `GeneratorVerifier`
|
|
1103
|
-
when `raise_policy: :raise` and verification fails after exhausting the
|
|
1104
|
-
iteration limit.
|
|
1105
|
-
|
|
1106
|
-
### Changed
|
|
1107
|
-
|
|
1108
|
-
- **`Phronomy::Graph::StateGraph` event system refactored**: Per-node `advance`
|
|
1109
|
-
events replaced with a unified `node_completed` event queue, reducing
|
|
1110
|
-
event-handler registration overhead and simplifying listener registration.
|
|
1111
|
-
|
|
1112
|
-
---
|
|
1113
|
-
|
|
1114
|
-
## [0.3.0] - 2026-05-18
|
|
1115
|
-
|
|
1116
|
-
### Removed
|
|
1117
|
-
|
|
1118
|
-
- **`Phronomy::Memory` module fully removed**: `ConversationManager`, all
|
|
1119
|
-
`Storage` backends (InMemory, ActiveRecord), all `Retrieval` strategies
|
|
1120
|
-
(Recent, Semantic, Composite), and all `Compression` helpers (ToolOutputPruner,
|
|
1121
|
-
Summary) have been deleted. Conversation history is now the responsibility of
|
|
1122
|
-
the calling application — pass prior messages via `config[:messages]`
|
|
1123
|
-
(`Array<RubyLLM::Message>`) and receive the updated array in `result[:messages]`.
|
|
1124
|
-
- **`Phronomy::StateStore` module fully removed**: `InMemory`, `ActiveRecord`,
|
|
1125
|
-
`Redis`, and `FileSystem` state-store backends have been deleted. The Workflow
|
|
1126
|
-
halted-state object is now returned directly from `invoke` and `send_event`
|
|
1127
|
-
and must be stored by the caller if resumption is needed.
|
|
1128
|
-
- **`Phronomy::Configuration#default_state_store` removed**: No longer meaningful
|
|
1129
|
-
without a built-in state store.
|
|
1130
|
-
- **`Phronomy::Configuration#default_memory` / `#memory_async` / `#memory_job_queue` removed**:
|
|
1131
|
-
No longer meaningful without the Memory module.
|
|
1132
|
-
- **Rails integration removed**: `Railtie` initializers for `AgentJob` and
|
|
1133
|
-
`acts_as_phronomy_message` no longer load. The `rails/` and `active_record/`
|
|
1134
|
-
directories have been deleted.
|
|
1135
|
-
- **`Phronomy::Actor` and `Phronomy::ThreadActorRegistry` deleted**: The Active
|
|
1136
|
-
Object pattern implementation (`actor.rb`, `thread_actor_registry.rb`) has been
|
|
1137
|
-
removed. It provided synchronous blocking only (no true async) and was
|
|
1138
|
-
architecturally inconsistent with the `WorkflowRunner` halt/resume model. All
|
|
1139
|
-
thread coordination now uses plain `Mutex` where needed.
|
|
1140
|
-
- **`Phronomy.configuration.max_actors` removed**: The configuration option is no
|
|
1141
|
-
longer meaningful without `ThreadActorRegistry`.
|
|
1142
|
-
|
|
1143
|
-
### Changed
|
|
1144
|
-
|
|
1145
|
-
- **`Agent::Base#invoke` and `#stream`** no longer route execution through a
|
|
1146
|
-
per-thread Actor. Both methods now call `_invoke_impl` / `_stream_impl` directly
|
|
1147
|
-
on the calling thread.
|
|
1148
|
-
- **`Memory::Storage::InMemory`** now stores all thread data in an instance-level
|
|
1149
|
-
`Hash` instead of `Thread.current` thread-local storage. The class-level
|
|
1150
|
-
`THREAD_DATA_KEY` constant has been removed. `with_thread_lock` uses a
|
|
1151
|
-
per-thread-id `Mutex` to preserve concurrent-compaction safety (issue #44).
|
|
1152
|
-
- **`StateStore::InMemory`** now stores state in an instance-level `Hash`.
|
|
1153
|
-
The `THREAD_DATA_KEY` constant has been removed.
|
|
1154
|
-
- **`VectorStore::RedisSearch`** uses a `Mutex` for `ensure_index!` and `clear`
|
|
1155
|
-
instead of an Actor, preserving the thread-safety invariant on `@index_created`.
|
|
1156
|
-
- **`Tool::McpTool::StdioTransport`**, **`Tracing::LangfuseTracer`**,
|
|
1157
|
-
**`TrustPipeline`**, and **`Memory::Retrieval::Semantic`** no longer hold a
|
|
1158
|
-
dedicated Actor instance. All operations execute directly on the calling thread.
|
|
1159
|
-
- **`PIIPatternDetector` — `:my_number` replaced by `:ssn`** ([#77]): The built-in PII
|
|
1160
|
-
detector now checks for US Social Security Numbers (`\b\d{3}-\d{2}-\d{4}\b`) instead
|
|
1161
|
-
of Japanese My Numbers. The JIS X 0076 check-digit validation and `my_number_valid?`
|
|
1162
|
-
helper have been removed. Category key renamed from `:my_number` to `:ssn`.
|
|
1163
|
-
- **`PIIPatternDetector` — phone pattern updated to international format** ([#77]):
|
|
1164
|
-
The `:phone` pattern now matches 3-digit area code + 3–4-digit exchange + 4-digit
|
|
1165
|
-
subscriber number with optional E.164 country-code prefix
|
|
1166
|
-
(`(?:\+\d{1,3}[.\- ]?)?\(?\d{3}\)?[.\- ]?\d{3,4}[.\- ]?\d{4}\b`),
|
|
1167
|
-
replacing the previous Japan-specific pattern.
|
|
1168
|
-
|
|
1169
|
-
### Fixed
|
|
1170
|
-
|
|
1171
|
-
- **`RubyLLM::Providers::OpenAI#handle_error_chunk` — `NoMethodError` on single-line SSE error chunks**:
|
|
1172
|
-
Some models (e.g. Qwen running via LM Studio) return SSE error events as a
|
|
1173
|
-
single line (`data: {...}`) without a preceding `event:` line. The upstream
|
|
1174
|
-
implementation called `chunk.split("\n")[1].delete_prefix(...)`, which raised
|
|
1175
|
-
`NoMethodError: undefined method 'delete_prefix' for nil` when the second
|
|
1176
|
-
element was absent. A monkey-patch in `lib/phronomy/ruby_llm_patches.rb` guards
|
|
1177
|
-
against this by returning an empty string when the split result has fewer than
|
|
1178
|
-
two elements.
|
|
1179
|
-
- **`README` — stale Memory API examples** ([#76]): All references to the
|
|
1180
|
-
non-existent `WindowMemory`, `ActiveRecordMemory`, `SemanticMemory` classes and
|
|
1181
|
-
`load_messages` / `memory_compression` API have been replaced with the correct
|
|
1182
|
-
`ConversationManager`-based API.
|
|
1183
|
-
- **`README` — `PIIPatternDetector` comment** ([#77]): Inline comment updated to
|
|
1184
|
-
`# Detect SSNs, credit cards, emails, and phone numbers`.
|
|
1185
|
-
- **`README` — Configuration block markdown** ([#80]): The `max_actors` Note block
|
|
1186
|
-
was incorrectly placed inside the Ruby code fence; moved outside so it renders
|
|
1187
|
-
as a blockquote.
|
|
1188
|
-
- **`README` — `Guardrails` stability label** ([#76]): Changed from `Stable` to `Beta`
|
|
1189
|
-
to reflect that the built-in detector patterns may evolve.
|
|
1190
|
-
- **`CHANGELOG` — stale entries** ([#78]): Removed the orphaned `[Unreleased]` section
|
|
1191
|
-
describing a never-released API, and replaced a forward `"As of 0.3.0"` reference
|
|
1192
|
-
with future-tense wording.
|
|
1193
|
-
- **`McpTool` — YARD class comment** ([#79]): Updated to document both the
|
|
1194
|
-
`stdio://` and `http://`/`https://` transport schemes.
|
|
1195
|
-
- **`README` — `max_actors` configuration reference** ([#80]): Added `c.max_actors`
|
|
1196
|
-
example and LRU eviction note to the Configuration section.
|
|
1197
|
-
|
|
1198
|
-
---
|
|
1199
|
-
|
|
1200
|
-
## [0.2.2] - 2026-05-17
|
|
1201
|
-
|
|
1202
|
-
### Fixed
|
|
1203
|
-
|
|
1204
|
-
- **`Tool::Base` type validation — strict mode** ([#73]): Removed string-coercion
|
|
1205
|
-
pass-through for `:integer`, `:number`/`:float`, and `:boolean` parameters.
|
|
1206
|
-
A `String` value such as `"42"` now correctly raises a type error regardless
|
|
1207
|
-
of the `on_schema_error` mode. Fixes silent data corruption where the raw
|
|
1208
|
-
string was forwarded to `execute` instead of the expected numeric/boolean type.
|
|
1209
|
-
- **`README` — correct `send_event` API example** ([#69]): Fixed code sample that
|
|
1210
|
-
called `app.send_event(:approve, config: { thread_id: ... })` (positional args)
|
|
1211
|
-
which raises `ArgumentError` at runtime. Corrected to
|
|
1212
|
-
`app.send_event(state: state, event: :approve)`.
|
|
1213
|
-
- **`phronomy.gemspec` — exclude `vendor/` from gem package** ([#70]): `vendor/bundle`
|
|
1214
|
-
(~3 500 files, ~14 MB) was included in released gems. Added `"vendor/"` to the
|
|
1215
|
-
file reject list.
|
|
1216
|
-
|
|
1217
|
-
### Added
|
|
1218
|
-
|
|
1219
|
-
- **`Phronomy::Configuration#max_actors`** ([#72]): New optional attribute
|
|
1220
|
-
(default `nil` = unlimited, backward-compatible). When set, `ThreadActorRegistry`
|
|
1221
|
-
enforces an LRU eviction policy: the least-recently-used actor is stopped and
|
|
1222
|
-
removed before a new one is created, preventing unbounded thread growth in
|
|
1223
|
-
long-running processes.
|
|
1224
|
-
- **`README` — feature stability table** ([#71]): Features section now uses a
|
|
1225
|
-
table with Stable / Beta / Experimental labels so users can assess maturity at
|
|
1226
|
-
a glance.
|
|
1227
|
-
- **`TrustPipeline::Result#citations` — unverified-source warning** ([#74]):
|
|
1228
|
-
YARD documentation now explicitly states that citations are extracted from the
|
|
1229
|
-
LLM's own output and have not been verified against any external source.
|
|
1230
|
-
|
|
1231
|
-
### CI
|
|
1232
|
-
|
|
1233
|
-
- **Ruby 3.4 added to CI matrix** ([#75]): Aligns test coverage with the gemspec
|
|
1234
|
-
requirement (`>= 3.2.0`) and verifies compatibility with the current stable
|
|
1235
|
-
Ruby release.
|
|
1236
|
-
|
|
1237
|
-
[#69]: https://github.com/Raizo-TCS/phronomy/issues/69
|
|
1238
|
-
[#70]: https://github.com/Raizo-TCS/phronomy/issues/70
|
|
1239
|
-
[#71]: https://github.com/Raizo-TCS/phronomy/issues/71
|
|
1240
|
-
[#72]: https://github.com/Raizo-TCS/phronomy/issues/72
|
|
1241
|
-
[#73]: https://github.com/Raizo-TCS/phronomy/issues/73
|
|
1242
|
-
[#74]: https://github.com/Raizo-TCS/phronomy/issues/74
|
|
1243
|
-
[#75]: https://github.com/Raizo-TCS/phronomy/issues/75
|
|
1244
|
-
|
|
1245
|
-
---
|
|
1246
|
-
|
|
1247
|
-
## [0.2.1] — Unreleased
|
|
1248
|
-
|
|
1249
|
-
### Changed
|
|
1250
|
-
|
|
1251
|
-
- **`WorkflowRunner` — state_machines fully drives execution** (architecture overhaul).
|
|
1252
|
-
Previously `state_machines` was used only for post-hoc transition validation;
|
|
1253
|
-
the next-node was calculated by Phronomy internally (`resolve_next_node`).
|
|
1254
|
-
After this change, all state transition decisions — including guard evaluation for
|
|
1255
|
-
routing events — will be delegated entirely to `state_machines`.
|
|
1256
|
-
- `PhaseTracker` now exposes `attr_accessor :context` so guard lambdas can
|
|
1257
|
-
access the `WorkflowContext` via `m.context`.
|
|
1258
|
-
- Guard bridge pattern: `if: ->(m) { guard_proc.call(m.context) }`.
|
|
1259
|
-
- Three event types registered per workflow:
|
|
1260
|
-
1. `advance_<from>` — unconditional after-transitions
|
|
1261
|
-
2. `<routing_event>` — guarded branching from action states (name is the
|
|
1262
|
-
event name used in the DSL, e.g. `:route`, `:route_review`)
|
|
1263
|
-
3. `<external_event>` — human-in-the-loop triggers from wait states
|
|
1264
|
-
- Invalid transitions now raise `ArgumentError` instead of logging warnings.
|
|
1265
|
-
- **`WorkflowRunner` initializer signature changed** — `edges:`,
|
|
1266
|
-
`conditional_edges:`, and `wait_states:` replaced by `after_transitions:`,
|
|
1267
|
-
`route_transitions:`, `external_events:`, and `wait_state_names:`.
|
|
1268
|
-
This is an **internal-only** change; the public `Phronomy::Workflow.define` DSL
|
|
1269
|
-
is unchanged.
|
|
1270
|
-
|
|
1271
|
-
### Removed (internal)
|
|
1272
|
-
|
|
1273
|
-
- `WorkflowRunner#resolve_next_node` — logic moved to state_machines
|
|
1274
|
-
- `WorkflowRunner#advance_phase` — replaced by `fire_event!`
|
|
1275
|
-
- `Workflow::Builder#build_edges`, `#build_conditional_edges`,
|
|
1276
|
-
`#build_wait_states` — replaced by unified event classification in `build`
|
|
1277
|
-
|
|
1278
|
-
---
|
|
1279
|
-
|
|
1280
|
-
## [0.2.0] - 2026-05-13
|
|
1281
|
-
|
|
1282
|
-
### Added
|
|
1283
|
-
|
|
1284
|
-
- `Phronomy::Graph::WorkflowRunner` — state_machines-based execution engine
|
|
1285
|
-
(introduced as the internal successor to `CompiledGraph`).
|
|
1286
|
-
- `state.phase` — single source of truth for graph execution state (replaces
|
|
1287
|
-
`current_nodes` + `halted_before` dual attributes).
|
|
1288
|
-
- `state.halted?` — returns `true` when the graph is paused.
|
|
1289
|
-
- `CompiledGraph#add_wait_state` — declared a named wait state that halts
|
|
1290
|
-
automatically when reached (later superseded by `wait_state` DSL in `Workflow.define`).
|
|
1291
|
-
- `CompiledGraph#send_event(state:, event:, input: nil)` — event-driven resume API
|
|
1292
|
-
(later superseded by `app.send_event`).
|
|
1293
|
-
|
|
1294
|
-
### Removed
|
|
1295
|
-
|
|
1296
|
-
- `ParallelNode` and `add_parallel_node` DSL. Use `Thread.new` or
|
|
1297
|
-
`Concurrent::Future` at the application level instead.
|
|
1298
|
-
- `Phronomy::Graph::TimeoutError` (was only used by `ParallelNode`).
|