phronomy 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +127 -30
  3. data/README.md +106 -122
  4. data/lib/phronomy/agent/base.rb +135 -57
  5. data/lib/phronomy/agent/checkpoint.rb +53 -0
  6. data/lib/phronomy/agent/orchestrator.rb +119 -0
  7. data/lib/phronomy/agent/react_agent.rb +18 -28
  8. data/lib/phronomy/agent/shared_state.rb +303 -0
  9. data/lib/phronomy/agent/suspend_signal.rb +35 -0
  10. data/lib/phronomy/agent/team_coordinator.rb +285 -0
  11. data/lib/phronomy/agent.rb +2 -1
  12. data/lib/phronomy/configuration.rb +0 -24
  13. data/lib/phronomy/generator_verifier.rb +250 -0
  14. data/lib/phronomy/guardrail/builtin/pii_pattern_detector.rb +10 -27
  15. data/lib/phronomy/railtie.rb +0 -6
  16. data/lib/phronomy/ruby_llm_patches.rb +20 -0
  17. data/lib/phronomy/tool/mcp_tool.rb +23 -26
  18. data/lib/phronomy/tracing/langfuse_tracer.rb +3 -6
  19. data/lib/phronomy/vector_store/redis_search.rb +4 -4
  20. data/lib/phronomy/version.rb +1 -1
  21. data/lib/phronomy/workflow.rb +4 -7
  22. data/lib/phronomy/workflow_runner.rb +42 -30
  23. data/lib/phronomy.rb +18 -0
  24. data/scripts/check_readme_ruby.rb +38 -0
  25. metadata +12 -38
  26. data/docs/trustworthy_ai_enhancements.md +0 -332
  27. data/lib/phronomy/active_record/acts_as.rb +0 -48
  28. data/lib/phronomy/active_record/checkpoint.rb +0 -20
  29. data/lib/phronomy/active_record/extensions.rb +0 -14
  30. data/lib/phronomy/active_record/message.rb +0 -20
  31. data/lib/phronomy/actor.rb +0 -68
  32. data/lib/phronomy/memory/compression/base.rb +0 -37
  33. data/lib/phronomy/memory/compression/summary.rb +0 -107
  34. data/lib/phronomy/memory/compression/tool_output_pruner.rb +0 -67
  35. data/lib/phronomy/memory/compression.rb +0 -11
  36. data/lib/phronomy/memory/conversation_manager.rb +0 -213
  37. data/lib/phronomy/memory/retrieval/base.rb +0 -22
  38. data/lib/phronomy/memory/retrieval/composite.rb +0 -76
  39. data/lib/phronomy/memory/retrieval/recent.rb +0 -35
  40. data/lib/phronomy/memory/retrieval/semantic.rb +0 -114
  41. data/lib/phronomy/memory/retrieval.rb +0 -12
  42. data/lib/phronomy/memory/storage/active_record.rb +0 -248
  43. data/lib/phronomy/memory/storage/base.rb +0 -155
  44. data/lib/phronomy/memory/storage/in_memory.rb +0 -152
  45. data/lib/phronomy/memory/storage.rb +0 -11
  46. data/lib/phronomy/memory.rb +0 -21
  47. data/lib/phronomy/rails/agent_job.rb +0 -75
  48. data/lib/phronomy/state_store/active_record.rb +0 -76
  49. data/lib/phronomy/state_store/base.rb +0 -112
  50. data/lib/phronomy/state_store/encryptor/active_support.rb +0 -49
  51. data/lib/phronomy/state_store/encryptor/base.rb +0 -34
  52. data/lib/phronomy/state_store/encryptor.rb +0 -16
  53. data/lib/phronomy/state_store/file.rb +0 -85
  54. data/lib/phronomy/state_store/in_memory.rb +0 -53
  55. data/lib/phronomy/state_store/redis.rb +0 -70
  56. data/lib/phronomy/state_store.rb +0 -9
  57. data/lib/phronomy/thread_actor_registry.rb +0 -85
  58. data/lib/phronomy/trust_pipeline.rb +0 -264
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e4dd11eb68b4f8d9f1eb9b75630a21424ee2d5c32035e2be9f9d8c34318feee
4
- data.tar.gz: ba85c6000643e00d6bc20298385728cc67df70b17f3542b439f516b4a23e92e5
3
+ metadata.gz: a5d8907f1d87037c09f78f63dd8ddbd21605536b1aa53c2d43e52be4a6ab6791
4
+ data.tar.gz: 37260258f4ece53e6e5b748cb2236f9c99f6f7e5c754fb0c2d72c415b0137386
5
5
  SHA512:
6
- metadata.gz: 1086a00d3ee1957b00954ac0a1ea70464c7e873e0cb873e23e53f27a38972d9a2bacca90204742c4c2c6cff90a3fa7972984ef2f16431187d5aa050ba4989ac3
7
- data.tar.gz: 8728503fb317f3ce9f05624d648e8080001cac7096898bd5ff2c95c736a040159a42d7ce26eebb28451afd367331d7a50fbe26ce37bf879d20a2de337247a76d
6
+ metadata.gz: 1533e2e0d82283066bab5f80b6583497330b274396640b91a6b27b1706533e4d25842600eb023c0fd469c8da076101f43e5691218703faedce194cd74476c590
7
+ data.tar.gz: af0513115bfcd23f786dfbedb7bfa7947346bb36168cc62175bd900f8536d984789469afc1fd929de90fca9a6b8cd8496451a054ea3ca86af401ef142b1c3e95
data/CHANGELOG.md CHANGED
@@ -7,6 +7,131 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.4.0] - 2026-05-19
11
+
12
+ ### Removed
13
+
14
+ - **`Phronomy::TrustPipeline` removed**: The `TrustPipeline` class and its inner
15
+ `TrustResult` value object have been deleted. Use `Phronomy::GeneratorVerifier`
16
+ instead, which provides the same generator-verifier pattern with a cleaner,
17
+ fully injectable API.
18
+
19
+ ### Added
20
+
21
+ - **`Phronomy::GeneratorVerifier`** — Generator-Verifier coordination loop
22
+ (Anthropic blog, Pattern 1). Wraps a generator agent and a verifier agent with
23
+ fully injectable prompt builders, response parsers, a configurable iteration
24
+ limit, and an approval-outcome raise policy.
25
+ - **`Phronomy::Agent::Orchestrator`** — Base class for orchestrator agents
26
+ (Anthropic blog, Pattern 2). Extends `Agent::Base` with a `subagent` DSL for
27
+ declarative subagent registration as LLM-callable tools, plus `dispatch_parallel`
28
+ and `fan_out` for programmatic parallel invocation.
29
+ - **`Phronomy::Agent::TeamCoordinator`** — Agent teams coordination pattern
30
+ (Anthropic blog, Pattern 3). An LLM-powered coordinator with a shared task
31
+ queue and a pool of worker agents that carry conversation history across task
32
+ assignments. Adds `coordinator_provider` DSL for independent LLM routing.
33
+ - **`Phronomy::Agent::SharedState`** — Shared-state coordination pattern
34
+ (Anthropic blog, Pattern 5). Peer agents collaborate via a `KnowledgeStore`;
35
+ the `member` DSL registers agents with per-agent instructions; `coordination`
36
+ sets the team protocol; `build_prompt` injects a tool-usage guide automatically.
37
+ - **`Phronomy::LowConfidenceError`** — Exception raised by `GeneratorVerifier`
38
+ when `raise_policy: :raise` and verification fails after exhausting the
39
+ iteration limit.
40
+
41
+ ### Changed
42
+
43
+ - **`Phronomy::Graph::StateGraph` event system refactored**: Per-node `advance`
44
+ events replaced with a unified `node_completed` event queue, reducing
45
+ event-handler registration overhead and simplifying listener registration.
46
+
47
+ ---
48
+
49
+ ## [0.3.0] - 2026-05-18
50
+
51
+ ### Removed
52
+
53
+ - **`Phronomy::Memory` module fully removed**: `ConversationManager`, all
54
+ `Storage` backends (InMemory, ActiveRecord), all `Retrieval` strategies
55
+ (Recent, Semantic, Composite), and all `Compression` helpers (ToolOutputPruner,
56
+ Summary) have been deleted. Conversation history is now the responsibility of
57
+ the calling application — pass prior messages via `config[:messages]`
58
+ (`Array<RubyLLM::Message>`) and receive the updated array in `result[:messages]`.
59
+ - **`Phronomy::StateStore` module fully removed**: `InMemory`, `ActiveRecord`,
60
+ `Redis`, and `FileSystem` state-store backends have been deleted. The Workflow
61
+ halted-state object is now returned directly from `invoke` and `send_event`
62
+ and must be stored by the caller if resumption is needed.
63
+ - **`Phronomy::Configuration#default_state_store` removed**: No longer meaningful
64
+ without a built-in state store.
65
+ - **`Phronomy::Configuration#default_memory` / `#memory_async` / `#memory_job_queue` removed**:
66
+ No longer meaningful without the Memory module.
67
+ - **Rails integration removed**: `Railtie` initializers for `AgentJob` and
68
+ `acts_as_phronomy_message` no longer load. The `rails/` and `active_record/`
69
+ directories have been deleted.
70
+ - **`Phronomy::Actor` and `Phronomy::ThreadActorRegistry` deleted**: The Active
71
+ Object pattern implementation (`actor.rb`, `thread_actor_registry.rb`) has been
72
+ removed. It provided synchronous blocking only (no true async) and was
73
+ architecturally inconsistent with the `WorkflowRunner` halt/resume model. All
74
+ thread coordination now uses plain `Mutex` where needed.
75
+ - **`Phronomy.configuration.max_actors` removed**: The configuration option is no
76
+ longer meaningful without `ThreadActorRegistry`.
77
+
78
+ ### Changed
79
+
80
+ - **`Agent::Base#invoke` and `#stream`** no longer route execution through a
81
+ per-thread Actor. Both methods now call `_invoke_impl` / `_stream_impl` directly
82
+ on the calling thread.
83
+ - **`Memory::Storage::InMemory`** now stores all thread data in an instance-level
84
+ `Hash` instead of `Thread.current` thread-local storage. The class-level
85
+ `THREAD_DATA_KEY` constant has been removed. `with_thread_lock` uses a
86
+ per-thread-id `Mutex` to preserve concurrent-compaction safety (issue #44).
87
+ - **`StateStore::InMemory`** now stores state in an instance-level `Hash`.
88
+ The `THREAD_DATA_KEY` constant has been removed.
89
+ - **`VectorStore::RedisSearch`** uses a `Mutex` for `ensure_index!` and `clear`
90
+ instead of an Actor, preserving the thread-safety invariant on `@index_created`.
91
+ - **`Tool::McpTool::StdioTransport`**, **`Tracing::LangfuseTracer`**,
92
+ **`TrustPipeline`**, and **`Memory::Retrieval::Semantic`** no longer hold a
93
+ dedicated Actor instance. All operations execute directly on the calling thread.
94
+ - **`PIIPatternDetector` — `:my_number` replaced by `:ssn`** ([#77]): The built-in PII
95
+ detector now checks for US Social Security Numbers (`\b\d{3}-\d{2}-\d{4}\b`) instead
96
+ of Japanese My Numbers. The JIS X 0076 check-digit validation and `my_number_valid?`
97
+ helper have been removed. Category key renamed from `:my_number` to `:ssn`.
98
+ - **`PIIPatternDetector` — phone pattern updated to international format** ([#77]):
99
+ The `:phone` pattern now matches 3-digit area code + 3–4-digit exchange + 4-digit
100
+ subscriber number with optional E.164 country-code prefix
101
+ (`(?:\+\d{1,3}[.\- ]?)?\(?\d{3}\)?[.\- ]?\d{3,4}[.\- ]?\d{4}\b`),
102
+ replacing the previous Japan-specific pattern.
103
+
104
+ ### Fixed
105
+
106
+ - **`RubyLLM::Providers::OpenAI#handle_error_chunk` — `NoMethodError` on single-line SSE error chunks**:
107
+ Some models (e.g. Qwen running via LM Studio) return SSE error events as a
108
+ single line (`data: {...}`) without a preceding `event:` line. The upstream
109
+ implementation called `chunk.split("\n")[1].delete_prefix(...)`, which raised
110
+ `NoMethodError: undefined method 'delete_prefix' for nil` when the second
111
+ element was absent. A monkey-patch in `lib/phronomy/ruby_llm_patches.rb` guards
112
+ against this by returning an empty string when the split result has fewer than
113
+ two elements.
114
+ - **`README` — stale Memory API examples** ([#76]): All references to the
115
+ non-existent `WindowMemory`, `ActiveRecordMemory`, `SemanticMemory` classes and
116
+ `load_messages` / `memory_compression` API have been replaced with the correct
117
+ `ConversationManager`-based API.
118
+ - **`README` — `PIIPatternDetector` comment** ([#77]): Inline comment updated to
119
+ `# Detect SSNs, credit cards, emails, and phone numbers`.
120
+ - **`README` — Configuration block markdown** ([#80]): The `max_actors` Note block
121
+ was incorrectly placed inside the Ruby code fence; moved outside so it renders
122
+ as a blockquote.
123
+ - **`README` — `Guardrails` stability label** ([#76]): Changed from `Stable` to `Beta`
124
+ to reflect that the built-in detector patterns may evolve.
125
+ - **`CHANGELOG` — stale entries** ([#78]): Removed the orphaned `[Unreleased]` section
126
+ describing a never-released API, and replaced a forward `"As of 0.3.0"` reference
127
+ with future-tense wording.
128
+ - **`McpTool` — YARD class comment** ([#79]): Updated to document both the
129
+ `stdio://` and `http://`/`https://` transport schemes.
130
+ - **`README` — `max_actors` configuration reference** ([#80]): Added `c.max_actors`
131
+ example and LRU eviction note to the Configuration section.
132
+
133
+ ---
134
+
10
135
  ## [0.2.2] - 2026-05-17
11
136
 
12
137
  ### Fixed
@@ -61,8 +186,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
61
186
  - **`WorkflowRunner` — state_machines fully drives execution** (architecture overhaul).
62
187
  Previously `state_machines` was used only for post-hoc transition validation;
63
188
  the next-node was calculated by Phronomy internally (`resolve_next_node`).
64
- As of 0.3.0, all state transition decisions — including guard evaluation for
65
- routing events — are delegated entirely to `state_machines`.
189
+ After this change, all state transition decisions — including guard evaluation for
190
+ routing events — will be delegated entirely to `state_machines`.
66
191
  - `PhaseTracker` now exposes `attr_accessor :context` so guard lambdas can
67
192
  access the `WorkflowContext` via `m.context`.
68
193
  - Guard bridge pattern: `if: ->(m) { guard_proc.call(m.context) }`.
@@ -87,34 +212,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
87
212
 
88
213
  ---
89
214
 
90
- ## [Unreleased]
91
-
92
- ### Added
93
-
94
- - **`Phronomy::Graph::Context`** module — canonical module for defining workflow
95
- context classes (replaces the removed `Phronomy::Graph::State`).
96
- - **`Phronomy::Graph.register_context_class`** — registers context classes for
97
- deserialization from external stores (Redis, DB).
98
- - **`Phronomy::Workflow.define`** DSL — primary high-level API for declaring
99
- stateful workflows (`state`, `wait_state`, `event`, `after`, `initial`).
100
- - **`Phronomy::Graph::WorkflowRunner`** — state-machine execution engine backing
101
- the Workflow DSL. Replaces the removed `CompiledGraph`.
102
- - **`app.send_event(event, config:)`** — event-driven resume for workflows halted
103
- at a `wait_state`.
104
- - **`state.halted?`** — returns `true` when the workflow is paused at a `wait_state`.
105
- - **`state.phase`** — single source of truth for execution state.
106
-
107
- ### Removed
108
-
109
- - `Phronomy::Graph::StateGraph` / `CompiledGraph` — use `Phronomy::Workflow.define`.
110
- - `Phronomy::Graph::State` — use `Phronomy::Graph::Context`.
111
- - `Phronomy::Graph.register_state_class` — use `register_context_class`.
112
- - `state.current_nodes` / `state.halted_before` — use `state.phase` / `state.halted?`.
113
- - `compiled.interrupt_before` / `compiled.interrupt_after` — use `wait_state` + `event`.
114
- - `compiled.resume` — use `app.send_event`.
115
-
116
- ---
117
-
118
215
  ## [0.2.0] - 2026-05-13
119
216
 
120
217
  ### Added
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Phronomy
2
2
 
3
3
  **Phronomy** is a Ruby AI agent framework inspired by open-source AI agent frameworks.
4
- It provides composable building blocks — Workflows, Agents, and Memory — all powered by [RubyLLM](https://github.com/crmne/ruby_llm) for LLM abstraction.
4
+ It provides composable building blocks — Workflows, Agents, Tools, Guardrails, RAG, and Tracing — all powered by [RubyLLM](https://github.com/crmne/ruby_llm) for LLM abstraction.
5
5
 
6
6
  ## Features
7
7
 
@@ -13,21 +13,20 @@ It provides composable building blocks — Workflows, Agents, and Memory — all
13
13
  |---|---|
14
14
  | **Workflow** — Stateful, branching workflows with wait_state/send_event | Stable |
15
15
  | **Workflow Parallel Node** — Concurrent branches via application-level threads | Beta |
16
- | **Agent** — ReAct-style tool-calling agents with memory and guardrails | Stable |
16
+ | **Agent** — ReAct-style tool-calling agents with guardrails and conversation history | Stable |
17
17
  | **Before-Completion Hook** — Three-tier LLM parameter injection | Stable |
18
- | **Memory** — Window, summary, ActiveRecord-backed, semantic, and composite memory | Stable |
19
- | **Memory Compression** — Automatic summarisation and tool-output pruning | Beta |
20
18
  | **Context Management** — Token budget calculation, estimation, and pruning | Stable |
21
19
  | **Knowledge/RAG** — Retrieval sources with pluggable loaders, splitters, and vector stores | Beta |
22
20
  | **Multi-agent** — Agent-as-Tool pattern and hub-and-spoke handoff routing | Beta |
23
- | **TrustPipeline** — Self-review loop and confidence gate (citations are LLM-self-reported) | Experimental |
24
- | **Guardrails** — Input/output validation; built-in PII and prompt-injection detectors | Stable |
21
+ | **GeneratorVerifier** — Generator-Verifier loop with injectable prompt builders/parsers | Beta |
22
+ | **Agent::Orchestrator** — Parallel subagent dispatch, fan-out, and `subagent` DSL | Beta |
23
+ | **Agent::TeamCoordinator** — Agent teams pattern: LLM coordinator + persistent worker pool with task queue | Beta |
24
+ | **Agent::SharedState** — Shared state pattern: peer agents collaborate via a shared KnowledgeStore; `member` DSL with per-agent instructions and `coordination` team protocol | Experimental |
25
+ | **Guardrails** — Input/output validation; built-in PII and prompt-injection detectors | Beta |
25
26
  | **Output Parser** — JSON and Struct-mapped parsers for structured LLM responses | Stable |
26
27
  | **Eval Framework** — Dataset-driven evaluation with multiple scorer types | Beta |
27
28
  | **Tracing** — Pluggable span-based observability | Stable |
28
- | **StateStore** — Persist graph state to memory, ActiveRecord, Redis, or file system | Stable |
29
29
  | **MCP Tool** — Model Context Protocol server integration | Beta |
30
- | **Rails integration** — `AgentJob`, `acts_as_phronomy_message`, and generators | Beta |
31
30
 
32
31
  ## Installation
33
32
 
@@ -49,7 +48,7 @@ For Rails apps, run the install generator after bundling:
49
48
  rails generate phronomy:install
50
49
  ```
51
50
 
52
- This creates an initializer and the required database migrations.
51
+ This creates a configuration initializer.
53
52
 
54
53
  ## Quick Start
55
54
 
@@ -99,8 +98,6 @@ app = Phronomy::Workflow.define(ReviewContext) do
99
98
  event :reject, from: :awaiting_approval, to: :write
100
99
  end
101
100
 
102
- Phronomy.configure { |c| c.default_state_store = Phronomy::StateStore::InMemory.new }
103
-
104
101
  # First run — halts at :awaiting_approval
105
102
  state = app.invoke({ draft: "" }, config: { thread_id: "doc-1" })
106
103
  puts "Halted: #{state.halted?}" # => true
@@ -160,7 +157,7 @@ agent.add_input_guardrail(NoSensitiveDataGuardrail.new)
160
157
  ### Built-in Guardrails — PII and prompt injection detection
161
158
 
162
159
  ```ruby
163
- # Detect credit cards, SSNs, emails, and phone numbers automatically
160
+ # Detect SSNs, credit cards, emails, and phone numbers
164
161
  agent.add_input_guardrail(Phronomy::Guardrail::Builtin::PIIPatternDetector.new)
165
162
 
166
163
  # Block common prompt-injection attempts
@@ -232,23 +229,88 @@ end
232
229
 
233
230
  Hooks are called in order — global → class → instance — and deep-merged.
234
231
 
235
- ### TrustPipelineTrustworthy outputs with citations and review
232
+ ### GeneratorVerifierGenerator-Verifier loop with custom prompt builders
236
233
 
237
234
  ```ruby
238
- pipeline = Phronomy::TrustPipeline.new(
239
- draft_agent: PolicyDraftAgent,
240
- review_agent: PolicyReviewAgent,
235
+ pipeline = Phronomy::GeneratorVerifier.new(
236
+ draft_agent: PolicyDraftAgent,
237
+ review_agent: PolicyReviewAgent,
238
+
239
+ # Full control over the LLM dialogue — supply your own prompts.
240
+ draft_prompt_builder: ->(input, feedback) {
241
+ base = "Answer precisely: #{input}"
242
+ feedback ? "#{base}\n\nPrevious feedback: #{feedback}" : base
243
+ },
244
+ review_prompt_builder: ->(input, draft, citations) {
245
+ "Is this draft accurate? Draft: #{draft}"
246
+ },
247
+
241
248
  confidence_threshold: 0.7,
242
- max_iterations: 3
249
+ max_iterations: 3,
250
+ raise_if_untrusted: false # set true to raise LowConfidenceError
243
251
  )
244
252
 
245
253
  result = pipeline.invoke("What is the refund policy?")
246
- puts result.output # final answer
247
- puts result.trusted? # true when confidence >= 0.7
248
- puts result.confidence # Float 0.0–1.0
254
+ puts result.output # final answer
255
+ puts result.trusted? # true when confidence >= 0.7
256
+ puts result.confidence # Float 0.0–1.0
257
+ result.citations.each { |c| puts "#{c[:source]}: #{c[:excerpt]}" }
258
+ ```
259
+
260
+ Optionally inject a custom result parser to decode non-JSON LLM output:
261
+
262
+ ```ruby
263
+ pipeline = Phronomy::GeneratorVerifier.new(
264
+ # ... (required params as shown above)
265
+ draft_result_parser: ->(text) { my_custom_draft_parser(text) },
266
+ review_result_parser: ->(text) { my_custom_review_parser(text) }
267
+ )
268
+ ```
269
+
270
+ Raise on low confidence:
271
+
272
+ ```ruby
273
+ begin
274
+ result = pipeline.invoke("question")
275
+ rescue Phronomy::LowConfidenceError => e
276
+ puts "Untrusted (confidence #{e.result.confidence}): #{e.result.output}"
277
+ end
278
+ ```
279
+
280
+ ### Agent::Orchestrator — Parallel subagent dispatch
281
+
282
+ ```ruby
283
+ class ResearchOrchestrator < Phronomy::Agent::Orchestrator
284
+ model "gpt-4o"
285
+ instructions "Coordinate research tasks by dispatching to specialised agents."
286
+
287
+ # Each subagent is automatically exposed as an LLM-callable tool.
288
+ subagent :searcher, SearchAgent
289
+ subagent :summarizer, SummaryAgent, on_error: :skip
290
+ end
291
+
292
+ result = ResearchOrchestrator.new.invoke("Research the latest AI news.")
293
+ ```
294
+
295
+ Programmatic parallel dispatch (no LLM loop):
296
+
297
+ ```ruby
298
+ class MyOrchestrator < Phronomy::Agent::Orchestrator
299
+ model "gpt-4o"
300
+ instructions "Orchestrate."
301
+
302
+ def run(query)
303
+ # Heterogeneous agents in parallel
304
+ results = dispatch_parallel(
305
+ {agent: SearchAgent, input: "topic A"},
306
+ {agent: AnalysisAgent, input: query}
307
+ )
308
+
309
+ # Fan-out — same agent, multiple inputs
310
+ translations = fan_out(agent: TranslationAgent, inputs: %w[Hello World])
249
311
 
250
- result.citations.each do |c|
251
- puts "#{c[:source]}: #{c[:excerpt]}"
312
+ results.map { |r| r[:output] }.join("\n")
313
+ end
252
314
  end
253
315
  ```
254
316
 
@@ -332,35 +394,28 @@ search_tool = Phronomy::Tool::McpTool.from_server(
332
394
  )
333
395
  ```
334
396
 
335
- ### RailsActiveRecord persistence
397
+ ### Conversation History passing prior messages
336
398
 
337
- ```ruby
338
- # In your migration (generated by rails generate phronomy:install):
339
- # create_table :phronomy_messages ...
340
- # create_table :phronomy_states ...
399
+ Phronomy does not manage conversation history internally. Instead, the application owns the
400
+ message array and passes it in via `config[:messages]`:
341
401
 
342
- class PhronomyMessage < ApplicationRecord
343
- acts_as_phronomy_message
344
- end
345
-
346
- # config/initializers/phronomy.rb
347
- Phronomy.configure do |c|
348
- c.default_state_store = Phronomy::StateStore::ActiveRecord.new(
349
- model_class: PhronomyState # AR model backed by phronomy_states table
350
- )
351
- end
352
-
353
- # Use in a controller:
354
- agent = ResearchAgent.new
355
- result = agent.invoke(
356
- params[:message],
357
- config: {
358
- thread_id: "user_#{current_user.id}",
359
- memory: PhronomyMessage.phronomy_memory
360
- }
402
+ ```ruby
403
+ # First turn
404
+ result1 = MyAgent.new.invoke("Hello! I'm Alice.", config: { thread_id: "session-1" })
405
+ prior_messages = result1[:messages] # Array<RubyLLM::Message>
406
+
407
+ # Second turn — pass prior messages so the agent has context
408
+ result2 = MyAgent.new.invoke(
409
+ "What is my name?",
410
+ config: { messages: prior_messages, thread_id: "session-1" }
361
411
  )
412
+ puts result2[:output] # => "Your name is Alice."
362
413
  ```
363
414
 
415
+ `result[:messages]` contains the complete message history after each invocation.
416
+ Persist it however suits your application (in-memory hash, Redis, ActiveRecord, etc.).
417
+
418
+
364
419
  ## Configuration
365
420
 
366
421
  ```ruby
@@ -368,9 +423,7 @@ Phronomy.configure do |c|
368
423
  c.default_model = "gpt-4o-mini"
369
424
  c.recursion_limit = 25
370
425
  c.tracer = Phronomy::Tracing::NullTracer.new
371
- c.default_state_store = Phronomy::StateStore::InMemory.new # optional
372
- c.memory_compression = [] # optional; Array of compressors
373
- c.before_completion = nil # optional; global hook lambda
426
+ c.before_completion = nil # optional; global hook lambda
374
427
  end
375
428
  ```
376
429
 
@@ -402,24 +455,6 @@ budget = Phronomy::Context::TokenBudget.new(
402
455
  )
403
456
  ```
404
457
 
405
- ### Budget-aware Memory
406
-
407
- Pass a budget to `load_messages` and only the newest messages that fit are returned:
408
-
409
- ```ruby
410
- memory = Phronomy::Memory::WindowMemory.new
411
- messages = memory.load_messages(thread_id: "t1", token_budget: budget)
412
- ```
413
-
414
- `ActiveRecordMemory` also accepts `pruner:` to truncate oversized tool results:
415
-
416
- ```ruby
417
- memory = Phronomy::Memory::ActiveRecordMemory.new(
418
- model_class: PhronomyMessage,
419
- pruner: Phronomy::Memory::Compression::ToolOutputPruner.new(max_chars: 4000)
420
- )
421
- ```
422
-
423
458
  ### Agent DSL extensions
424
459
 
425
460
  ```ruby
@@ -430,59 +465,8 @@ class MyAgent < Phronomy::Agent::Base
430
465
  end
431
466
  ```
432
467
 
433
- `Agent::Base#invoke` builds a `TokenBudget` automatically and passes it to
434
- `memory.load_messages`. When the model is not in the registry the budget is
435
- silently skipped.
436
-
437
- ### SemanticMemory
438
-
439
- Embedding-based retrieval of relevant past messages:
440
-
441
- ```ruby
442
- semantic = Phronomy::Memory::SemanticMemory.new(
443
- embedding_model: "text-embedding-3-small",
444
- k: 10
445
- )
446
- messages = semantic.load_messages(thread_id: "t1", query: "user's current question")
447
- ```
448
-
449
- ### Composite retrieval
450
-
451
- Merge multiple retrieval strategies within a shared `ConversationManager`:
452
-
453
- ```ruby
454
- composite_retrieval = Phronomy::Memory::Retrieval::Composite.new(
455
- sources: [
456
- { retrieval: Phronomy::Memory::Retrieval::Recent.new(k: 5), weight: 0.4 },
457
- { retrieval: Phronomy::Memory::Retrieval::Semantic.new(k: 10), weight: 0.6 }
458
- ]
459
- )
460
-
461
- manager = Phronomy::Memory::ConversationManager.new(
462
- storage: Phronomy::Memory::Storage::InMemory.new,
463
- retrieval: composite_retrieval
464
- )
465
- ```
466
-
467
- ### Memory Compression
468
-
469
- Automatically shrink conversation history before it reaches the LLM.
470
-
471
- ```ruby
472
- # Truncate oversized tool outputs (no LLM call, cheap)
473
- pruner = Phronomy::Memory::Compression::ToolOutputPruner.new(max_chars: 4000)
474
-
475
- # Summarise old messages when history exceeds max_tokens (calls summarizer_model)
476
- summary = Phronomy::Memory::Compression::Summary.new(
477
- max_tokens: 4000,
478
- keep: 10, # always preserve the N most recent messages
479
- summarizer_model: "gpt-4o-mini"
480
- )
481
-
482
- Phronomy.configure do |c|
483
- c.memory_compression = [pruner, summary] # applied in order: pruner first, then summary
484
- end
485
- ```
468
+ `Agent::Base#invoke` builds a `TokenBudget` automatically. When the model is not in the
469
+ registry the budget is silently skipped.
486
470
 
487
471
 
488
472
  ## Examples
@@ -512,11 +496,11 @@ bundle exec ruby NN_example_name/run.rb
512
496
  | 12 | `12_prompt_template/` | Advanced prompt templates |
513
497
  | 13 | `13_mcp_http_tool/` | HTTP-based MCP tool server |
514
498
  | 14 | `14_code_review/` | Automated code review agent |
515
- | 15 | `15_rails_secure_chat/` | Rails chat with PII guardrails and secure memory |
499
+ | 15 | `15_rails_secure_chat/` | Rails chat with PII guardrails |
516
500
  | 16 | `16_before_completion_hook/` | Global/class/instance before_completion hooks |
517
501
  | 17 | `17_multi_agent_handoff/` | Hub-and-spoke agent routing via Runner |
518
502
  | 18 | `18_rails_agent_job/` | Rails app with AgentJob + ActionCable streaming |
519
- | 19 | `19_trust_pipeline/` | Trustworthy output via Citation Tracking + Self-Review + Confidence Gate |
503
+ | 19 | `19_trust_pipeline/` | Generator-Verifier pattern with citation tracking, self-review loop and confidence gate |
520
504
 
521
505
  ## Development
522
506