omni_agent 0.1.11 → 0.2.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +96 -2
  4. data/Rakefile +4 -0
  5. data/app/jobs/omni_agent/embed_job.rb +10 -0
  6. data/app/jobs/omni_agent/ingest_job.rb +12 -0
  7. data/docs/agent/callbacks.md +60 -0
  8. data/docs/agent/delegation.md +56 -0
  9. data/docs/agent/provider.md +93 -0
  10. data/docs/agent/run-entrypoints.md +81 -0
  11. data/docs/agent/streaming.md +62 -0
  12. data/docs/agent/structured-output.md +80 -0
  13. data/docs/configuration.md +68 -0
  14. data/docs/evals/caching.md +21 -0
  15. data/docs/evals/generating-and-running.md +87 -0
  16. data/docs/evals/golden-sets.md +32 -0
  17. data/docs/evals/judge-provider.md +20 -0
  18. data/docs/generators.md +143 -0
  19. data/docs/index.md +98 -0
  20. data/docs/intro.md +93 -0
  21. data/docs/mcp/authentication.md +226 -0
  22. data/docs/mcp/defining-servers.md +196 -0
  23. data/docs/mcp/transports.md +166 -0
  24. data/docs/rag.md +372 -0
  25. data/docs/stock-agents/ocr.md +52 -0
  26. data/docs/stock-agents/overview.md +91 -0
  27. data/docs/supported-providers.md +47 -0
  28. data/docs/tools.md +171 -0
  29. data/exe/omni_agent +48 -10
  30. data/lib/generators/omni_agent/chunks/chunks_generator.rb +130 -0
  31. data/lib/generators/omni_agent/embeddings/embeddings_generator.rb +72 -0
  32. data/lib/generators/omni_agent/install/install_generator.rb +78 -1
  33. data/lib/generators/omni_agent/stock_agent/stock_agent_generator.rb +73 -0
  34. data/lib/generators/omni_agent/vector_store.rb +113 -0
  35. data/lib/omni_agent/agent.rb +168 -39
  36. data/lib/omni_agent/chunkable.rb +181 -0
  37. data/lib/omni_agent/chunkers/hierarchical.rb +20 -0
  38. data/lib/omni_agent/chunkers/markdown.rb +41 -0
  39. data/lib/omni_agent/chunkers/recursive.rb +53 -0
  40. data/lib/omni_agent/chunkers.rb +31 -0
  41. data/lib/omni_agent/configuration.rb +14 -1
  42. data/lib/omni_agent/docs.rb +32 -0
  43. data/lib/omni_agent/embeddable.rb +131 -0
  44. data/lib/omni_agent/engine.rb +11 -0
  45. data/lib/omni_agent/errors.rb +15 -0
  46. data/lib/omni_agent/loaders/html.rb +21 -0
  47. data/lib/omni_agent/loaders/markdown.rb +18 -0
  48. data/lib/omni_agent/loaders/pdf.rb +25 -0
  49. data/lib/omni_agent/loaders/text.rb +15 -0
  50. data/lib/omni_agent/loaders.rb +68 -0
  51. data/lib/omni_agent/providers/base.rb +14 -0
  52. data/lib/omni_agent/providers/mock.rb +89 -4
  53. data/lib/omni_agent/providers/ollama.rb +4 -0
  54. data/lib/omni_agent/providers/openai.rb +33 -2
  55. data/lib/omni_agent/providers/response.rb +6 -1
  56. data/lib/omni_agent/providers.rb +7 -0
  57. data/lib/omni_agent/schema.rb +200 -0
  58. data/lib/omni_agent/semantic_search.rb +45 -0
  59. data/lib/omni_agent/stock_agent.rb +59 -0
  60. data/lib/omni_agent/stock_agents/ocr/prompt.md.erb +6 -0
  61. data/lib/omni_agent/stock_agents/ocr.rb +70 -0
  62. data/lib/omni_agent/stock_agents.rb +26 -0
  63. data/lib/omni_agent/tool.rb +7 -107
  64. data/lib/omni_agent/version.rb +1 -1
  65. data/lib/omni_agent.rb +12 -1
  66. data/lib/tasks/omni_agent_chunks_tasks.rake +16 -0
  67. data/lib/tasks/omni_agent_embeddings_tasks.rake +15 -0
  68. metadata +49 -8
  69. data/app/assets/stylesheets/omni_agent/application.css +0 -15
  70. data/app/controllers/omni_agent/application_controller.rb +0 -4
  71. data/app/helpers/omni_agent/application_helper.rb +0 -4
  72. data/app/jobs/omni_agent/application_job.rb +0 -4
  73. data/app/mailers/omni_agent/application_mailer.rb +0 -6
  74. data/app/models/omni_agent/application_record.rb +0 -5
  75. data/app/views/layouts/omni_agent/application.html.erb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc288d35e0b6c6e5e4ea763c36d05aa37ee95d20aeb2f4b1369e15a6ba22bc92
4
- data.tar.gz: 428b4d96e1b1f7ad2bf1f7cd2c99893333c0eac59d2add9a94faf18198376baa
3
+ metadata.gz: 619f71e19ebcd85894c197fa69aa6603f9c21eededca2c249e0a6ac3ceaa38a9
4
+ data.tar.gz: dcb21d896ea82f2127fe793d58668ea2e95162a3b77f12bee110ff9091d1aa96
5
5
  SHA512:
6
- metadata.gz: 9c55eaf9a31782bb1c16c152d15627943cef09f5a179c6039590b412dba505196aae36ed325905992bd26df567c74553a83fec629c59062c2cfa5d4981a03bf4
7
- data.tar.gz: 904fdc44a855a5b7cfe66817cd531cc86bd44d5a8f8bccd6b97c6bf3d313a4b4e4a7a2f18fb18305bf5ae5be4bca7424562c1e39f77ecbb12ee63bea4d00fcf3
6
+ metadata.gz: c92ffa2cc815d03ba69a6033cac9f96bfadc95725f926de704df33a31d7651f4afd586342dff02987ba86736ad52a08314308f4875ff68a18f98a4ae474c92a3
7
+ data.tar.gz: 00a41896240d9ef66e1cf739ae0bc693a424138fe47073ab0937a853067b274292d41ff21a193f2e5a78258d5c21977faa50fd9125a93136d6c00984e3d1ab8a
data/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.0](https://github.com/ACR1209/omni_agent/compare/omni_agent/v0.1.11...omni_agent/v0.2.0) (2026-10-29)
6
+
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ * **agent:** agent subclasses now inherit their parent's class-level settings: `provider`/model, `options`, tags, `before_generation`/ `after_generation` callbacks, tools, delegated tools and `output`. Apps with agent hierarchies (e.g. an `ApplicationAgent` base class) will pick up the parent's settings in every subclass.
11
+
12
+ ### Features
13
+
14
+ * add OmniAgent.embed with configurable embedding provider and model ([19214df](https://github.com/ACR1209/omni_agent/commit/19214df320c33b60559ce4eca9e65d77aedbafb8))
15
+ * **agent:** add searches macro for semantic search tools ([764a756](https://github.com/ACR1209/omni_agent/commit/764a7568cc23c47bf42cea8719757d01fc8a3dbd))
16
+ * **agent:** add structured output and stock agents scaffold ([c23fb4a](https://github.com/ACR1209/omni_agent/commit/c23fb4a89f30dbb55916ce886deafafd65d7dba8))
17
+ * **agent:** address review feedback on structured output and inheritance ([143ce4b](https://github.com/ACR1209/omni_agent/commit/143ce4b5668edab2c06ed65071fd208846fd7457))
18
+ * **agent:** search has_chunks models with searches ([b794bb2](https://github.com/ACR1209/omni_agent/commit/b794bb2d1ef0cb94f45dfec674cf3670828987b7))
19
+ * **chunkable:** add has_chunks to chunk, embed and store documents ([414504b](https://github.com/ACR1209/omni_agent/commit/414504b3e5a26b7868a2fa1da938d48dd635518c))
20
+ * **chunkers:** add recursive, markdown and hierarchical chunkers ([2f8a97d](https://github.com/ACR1209/omni_agent/commit/2f8a97d8b9cc7016092a0233882960fff9f9270c))
21
+ * **docs:** bundle plain-Markdown docs in the gem for coding agents ([3cf1270](https://github.com/ACR1209/omni_agent/commit/3cf12705bef39064680463dc088322969de50518))
22
+ * **embeddable:** add has_embedding macro for ActiveRecord models ([32d716d](https://github.com/ACR1209/omni_agent/commit/32d716dded4e9820f841a6c89fa59b9a04281bc4))
23
+ * **embeddable:** configure embed job options globally and per model ([f4dcb39](https://github.com/ACR1209/omni_agent/commit/f4dcb39850fe53e5b80a408f548ae4b8cdea3bb4))
24
+ * **generators:** add omni_agent:chunks to set up a chunk model ([ad60360](https://github.com/ACR1209/omni_agent/commit/ad60360f630c33eae6d32f9d2acdd3b246caad45))
25
+ * **generators:** add omni_agent:embeddings generator ([5af765b](https://github.com/ACR1209/omni_agent/commit/5af765b454975b23f85903558d1832ba34ec32aa))
26
+ * **generators:** write a commented initializer in omni_agent:install ([c0f22aa](https://github.com/ACR1209/omni_agent/commit/c0f22aabe3affc4e17e60548288fa855cb968764))
27
+ * **loaders:** read PDF, HTML, Markdown and text documents with auto-detection ([c294323](https://github.com/ACR1209/omni_agent/commit/c2943237772e44983fa3bc62bb669d256ba2b4a3))
28
+ * **providers:** add embed for text embeddings ([519f169](https://github.com/ACR1209/omni_agent/commit/519f16968896c8f0580591e2e7af6b93e26df3ae))
29
+ * **stock_agents:** add OCR stock agent with image and PDF input ([b2683f3](https://github.com/ACR1209/omni_agent/commit/b2683f3532772e3d00a037e22d425164f8f785dd))
30
+ * **tasks:** add omni_agent:embeddings:backfill rake task ([2aa06f7](https://github.com/ACR1209/omni_agent/commit/2aa06f7c24895caa2eb1b7ce8a1493f783e0ffe9))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * **agent:** separate search results with a rule ([bd018cc](https://github.com/ACR1209/omni_agent/commit/bd018cc09626dcfcde524fccdbcbc2fc8222052e))
36
+ * **chunkable:** lock the document while replacing its chunks ([5e695e6](https://github.com/ACR1209/omni_agent/commit/5e695e63384e57afbbd96571bbf59bd633a0bda7))
37
+ * **chunkable:** skip unreadable documents in ingest_all ([9b6b68d](https://github.com/ACR1209/omni_agent/commit/9b6b68dab3cb8686d3da7c46c0c4032015b2d4d7))
38
+ * **chunkers:** skip Markdown headings with nothing under them ([e228627](https://github.com/ACR1209/omni_agent/commit/e228627ac450902f33064f4dc1bd25c589b1f5bb))
39
+ * **embeddable:** discard embed jobs for deleted records ([1b69efc](https://github.com/ACR1209/omni_agent/commit/1b69efcba25e26419ffadaa9721f0c888b9a856a))
40
+ * **embeddable:** explain how to enable ActiveJob for later: true ([7f46446](https://github.com/ACR1209/omni_agent/commit/7f46446d993f036b4fa27fbb21035b65ae7bbae3))
41
+ * **engine:** load ActiveJob with the engine ([1392629](https://github.com/ACR1209/omni_agent/commit/1392629e4892a5f6ed1c38b5a2eb34cdddda7e00))
42
+ * **engine:** remove unused plugin scaffolding ([fa97033](https://github.com/ACR1209/omni_agent/commit/fa97033a96a7bff2ce4d4355008766d7fed7df89))
43
+ * **generators:** explain connection errors when detecting MariaDB ([a13a770](https://github.com/ACR1209/omni_agent/commit/a13a770ba607905fad8ee4d21fb168ed91b75429))
44
+ * **generators:** number migrations after the newest one ([01683a9](https://github.com/ACR1209/omni_agent/commit/01683a9e4efa7d05ea05e590615392ae78545180))
45
+ * **loaders:** drop scripts and styles anywhere in HTML ([972fa94](https://github.com/ACR1209/omni_agent/commit/972fa944fd224ace1c909ccbd1c4a57d254a81f7))
46
+ * **providers:** pass content-part arrays through to OpenAI ([22a5354](https://github.com/ACR1209/omni_agent/commit/22a535464b8053365a9fe4b8f1f850768e5a6472))
47
+
5
48
  ## [0.1.11](https://github.com/ACR1209/omni_agent/compare/omni_agent/v0.1.10...omni_agent/v0.1.11) (2026-10-15)
6
49
 
7
50
 
data/README.md CHANGED
@@ -8,10 +8,13 @@ tool schemas, and generation lifecycle callbacks.
8
8
 
9
9
  - `OmniAgent::Agent` runtime with provider abstraction and tool-calling loop
10
10
  - `OmniAgent::Tool` DSL with JSON-schema-style input definitions
11
+ - Structured, validated agent output declared with the same DSL as tool input
12
+ - Stock agents: ready-made agents shipped with the gem that projects subclass and customize
11
13
  - Prompt composition from ERB files in `app/agents/<agent_name>/`
12
14
  - Agent callbacks (`before_generation`, `after_generation`)
13
15
  - Agent and tool tags to support filtering strategies
14
16
  - OpenAI provider integration out of the box, plus an Ollama provider for local models
17
+ - RAG: `has_embedding` models, `has_chunks` documents (PDF, Markdown, HTML) and `searches` tools over pgvector, sqlite-vec, MariaDB or Meilisearch
15
18
  - MCP server support: expose tools and whole agents to Claude Code, Claude Desktop, Cursor and other MCP clients
16
19
  - Rake tasks and Rails generators for scaffolding
17
20
 
@@ -38,12 +41,14 @@ bundle install
38
41
 
39
42
  ## Quick Start
40
43
 
41
- 1. Install base directories:
44
+ 1. Install base directories and the initializer:
42
45
 
43
46
  ```bash
44
47
  bundle exec rails generate omni_agent:install
45
48
  ```
46
49
 
50
+ This also adds an OmniAgent section to `AGENTS.md` (and `CLAUDE.md`, if present) that points coding agents at the bundled docs. Pass `--skip-agents-md` to leave those files alone.
51
+
47
52
  2. Generate an agent scaffold:
48
53
 
49
54
  ```bash
@@ -108,6 +113,48 @@ module ResearchAgent::Tools
108
113
  end
109
114
  ```
110
115
 
116
+ ## Structured Output
117
+
118
+ Declare an `output` block, using the same DSL as tool `input`, to get a validated object back instead of free text:
119
+
120
+ ```ruby
121
+ class InvoiceAgent < OmniAgent::Agent
122
+ output do
123
+ string :invoice_number
124
+ number :total, min: 0
125
+ enum :currency, values: %w[USD EUR]
126
+ end
127
+ end
128
+
129
+ InvoiceAgent.new.run(invoice_text).structured_output
130
+ # => { invoice_number: "A-1042", total: 120.5, currency: "USD" }
131
+ ```
132
+
133
+ The schema is sent to the provider (as a `json_schema` `response_format` for OpenAI and Ollama), and the final answer is parsed and validated with the same rules as tool input. Invalid JSON or a schema mismatch raises `OmniAgent::StructuredOutputError`.
134
+
135
+ ## Stock Agents
136
+
137
+ Stock agents are ready-made agents shipped with the gem under `OmniAgent::StockAgents`. Subclass one in your app and declare the output you need; the subclass keeps the stock prompt, model and tools:
138
+
139
+ ```bash
140
+ rails generate omni_agent:stock_agent InvoiceOcr --from=ocr --fields=invoice_number:string total:number
141
+ ```
142
+
143
+ ```ruby
144
+ class InvoiceOcr < OmniAgent::StockAgents::OCR
145
+ output do
146
+ string :invoice_number
147
+ number :total
148
+ end
149
+ end
150
+
151
+ InvoiceOcr.new.run([ Pathname("page1.jpg"), Pathname("page2.jpg") ]).structured_output
152
+ ```
153
+
154
+ The OCR stock agent reads images and PDFs from paths, uploaded files, ActiveStorage attachments or URLs, one at a time or several pages together.
155
+
156
+ To add a stock agent to the gem itself, run `bin/rake "stock_agent:new[name]"` from the gem repository.
157
+
111
158
  ## Multi-Agent Delegation
112
159
 
113
160
  `delegate_to` turns an agent into a supervisor: it wraps another agent class as a tool, so the supervisor's LLM can decide when to hand off to it. The delegated agent is a normal, independently defined agent (e.g. `app/agents/research_agent.rb`) — no manual tool file needed.
@@ -157,6 +204,41 @@ end
157
204
 
158
205
  `.stream` must come before the call (`.stream.run(...)`, not `.run(...).stream`) — without it, or without a block, behavior is unchanged and the same `Response` is returned either way. Only the `openai`, `ollama`, and `mock` providers stream today. See [Streaming Responses](omniagent-docs/docs/agent/streaming.mdx) for the full event reference.
159
206
 
207
+ ## RAG
208
+
209
+ Make a model searchable and give an agent a search tool for it:
210
+
211
+ ```bash
212
+ rails generate omni_agent:embeddings Document title content # migration + has_embedding
213
+ bin/rails db:migrate
214
+ bin/rails "omni_agent:embeddings:backfill[Document]"
215
+ ```
216
+
217
+ ```ruby
218
+ class Document < ApplicationRecord
219
+ has_embedding :title, :content # embedded on save via OmniAgent.embed
220
+ end
221
+
222
+ class SupportAgent < OmniAgent::Agent
223
+ searches Document, scope: ->(context) { where(account_id: context[:account_id]) }
224
+ end
225
+ ```
226
+
227
+ Vectors are stored with [neighbor](https://github.com/ankane/neighbor) on Postgres (pgvector), SQLite (sqlite-vec) or MariaDB. Models using meilisearch-rails work with `searches` as they are, and so does any object with a `semantic_search(query, limit:)` method. For files and long text, `has_chunks` splits them into searchable chunks:
228
+
229
+ ```bash
230
+ rails generate omni_agent:chunks Document file # DocumentChunk model + has_chunks
231
+ ```
232
+
233
+ ```ruby
234
+ class Document < ApplicationRecord
235
+ has_one_attached :file
236
+ has_chunks :file # PDF, Markdown, HTML or text, detected per file
237
+ end
238
+ ```
239
+
240
+ See [RAG](omniagent-docs/docs/rag.mdx) for the options, loaders, chunkers, Meilisearch setup and embedding configuration.
241
+
160
242
  ## Evals
161
243
 
162
244
  `OmniAgent::Eval` lets you test agent quality: deterministic assertions (tool calls, output matching) and pluggable LLM-as-judge scoring.
@@ -292,7 +374,7 @@ Tools can declare MCP-only hints with `title "Get weather"` and `annotations rea
292
374
 
293
375
  ## Configuration
294
376
 
295
- Global defaults can be configured through `OmniAgent.configure`:
377
+ `rails generate omni_agent:install` writes `config/initializers/omni_agent.rb` with every setting commented out at its default. Global defaults are set through `OmniAgent.configure`:
296
378
 
297
379
  ```ruby
298
380
  OmniAgent.configure do |config|
@@ -301,6 +383,18 @@ OmniAgent.configure do |config|
301
383
  end
302
384
  ```
303
385
 
386
+ ## Documentation for Coding Agents
387
+
388
+ The full documentation from https://omniagent.activetaskers.com/docs/intro ships inside the gem as plain Markdown in `docs/`, so coding agents (Claude Code, Cursor, Codex...) working in your app can read it offline:
389
+
390
+ ```bash
391
+ bundle exec omni_agent docs # index: conventions, typical flows and every page
392
+ bundle exec omni_agent docs mcp/authentication # one page
393
+ bundle exec omni_agent docs --path # directory holding the Markdown files
394
+ ```
395
+
396
+ `docs/` is generated from `omniagent-docs/docs` by `bundle exec rake docs:sync`, which `rake build` runs first. It isn't committed, so a gem installed from git (`gem "omni_agent", github: ...`) doesn't include it; use the docs site instead.
397
+
304
398
  ## Running Tests
305
399
 
306
400
  ```bash
data/Rakefile CHANGED
@@ -13,6 +13,10 @@ require "bundler/gem_tasks"
13
13
  Rake::Task["release"].clear
14
14
  task release: [ "build", "release:rubygem_push" ]
15
15
 
16
+ # docs/ is generated from omniagent-docs/ (see rakelib/docs.rake) and not
17
+ # committed, but the gemspec packages it, so generate it before every build.
18
+ task build: "docs:sync"
19
+
16
20
  RSpec::Core::RakeTask.new(:spec)
17
21
 
18
22
  task default: :spec
@@ -0,0 +1,10 @@
1
+ module OmniAgent
2
+ class EmbedJob < ActiveJob::Base
3
+ # The record was deleted before the job ran; there's nothing to embed.
4
+ discard_on ActiveJob::DeserializationError
5
+
6
+ def perform(record)
7
+ record.embed!
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module OmniAgent
2
+ class IngestJob < ActiveJob::Base
3
+ # The record was deleted before or while the job ran; there's nothing to ingest.
4
+ discard_on ActiveJob::DeserializationError, ActiveRecord::RecordNotFound
5
+ # No loader can read the file, so retrying won't help.
6
+ discard_on OmniAgent::UnsupportedDocumentError
7
+
8
+ def perform(record)
9
+ record.ingest!
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ <!-- Generated from omniagent-docs/docs/agent/callbacks.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
2
+ <!-- Online version: https://omniagent.activetaskers.com/docs/agent/callbacks -->
3
+
4
+ # Generation Callbacks
5
+
6
+ Agents support `before_generation` and `after_generation` callbacks, letting you hook into the lifecycle of a single `run` without overriding it yourself.
7
+
8
+ ## Before Generation
9
+
10
+ Runs before any provider call is made — useful for setting up instance variables that your prompt template needs.
11
+
12
+ ```ruby
13
+ class ResearchAgent < OmniAgent::Agent
14
+ use_model "gpt-4o-mini"
15
+
16
+ before_generation :set_current_user
17
+
18
+ def set_current_user
19
+ @user = "Test User"
20
+ end
21
+ end
22
+ ```
23
+
24
+ Any instance variable you set here is automatically available inside `prompt.md.erb` via ERB interpolation.
25
+
26
+ ## After Generation
27
+
28
+ Runs once the agent has a final response (no more tool calls to make, or a tool explicitly stopped generation).
29
+
30
+ ```ruby
31
+ class ResearchAgent < OmniAgent::Agent
32
+ after_generation :log_response
33
+
34
+ def log_response(payload)
35
+ Rails.logger.info("Response: #{payload[:response].content}")
36
+ end
37
+ end
38
+ ```
39
+
40
+ Callback methods can take zero arguments or one. If they accept an argument, they receive a payload hash:
41
+
42
+ | Key | Before generation | After generation |
43
+ |---|---|---|
44
+ | `input` | ✓ | ✓ |
45
+ | `context` | ✓ | ✓ |
46
+ | `messages` | ✓ | ✓ |
47
+ | `generated_messages` | | ✓ |
48
+ | `response` | | ✓ |
49
+
50
+ ## Multiple Callbacks
51
+
52
+ Both directives accept multiple method names and are additive across calls — calling `before_generation` again appends rather than replacing:
53
+
54
+ ```ruby
55
+ before_generation :set_current_user, :load_preferences
56
+ ```
57
+
58
+ ## Context Binding
59
+
60
+ Instance variables you set inside a callback (or anywhere in the agent) are synced back into the run's `context` hash after each callback finishes, so later callbacks and the prompt template see the latest values.
@@ -0,0 +1,56 @@
1
+ <!-- Generated from omniagent-docs/docs/agent/delegation.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
2
+ <!-- Online version: https://omniagent.activetaskers.com/docs/agent/delegation -->
3
+
4
+ # Multi-Agent Delegation
5
+
6
+ `delegate_to` wraps another agent as a tool your agent's LLM can call, turning your agent into a **supervisor** that routes work to sub-agents. The delegated agent is defined and used like any other agent — a normal class under `app/agents/`, not a manual tool file.
7
+
8
+ ```ruby
9
+ class SupervisorAgent < OmniAgent::Agent
10
+ use_model "gpt-4o"
11
+
12
+ delegate_to ResearchAgent, as: :research, description: "Look up factual info"
13
+ delegate_to MathAgent, as: :calculate, description: "Do arithmetic"
14
+ end
15
+ ```
16
+
17
+ The model decides when (and whether) to call `research` or `calculate` based on the conversation, exactly like any other tool call. Each delegated agent runs its own `run` loop — including its own tool calls, if it has any — and returns its final answer as the tool result.
18
+
19
+ * **`agent_class`**: Any `OmniAgent::Agent` subclass.
20
+ * **`as:`**: Name exposed to the model as the tool/function name (e.g. `:research` → `Research`).
21
+ * **`description:`**: Sent to the provider so the model knows when to delegate. Defaults to `"Delegate to <AgentClass>."`.
22
+
23
+ ## Choosing a Run Entrypoint
24
+
25
+ By default, delegation calls the sub-agent's plain `#run`. Pass `run_alias:` to call a `run_aliases` method (or any zero-arg [run entrypoint](run-entrypoints.md)) instead — useful when the sub-agent should render a different prompt file for delegated calls:
26
+
27
+ ```ruby
28
+ class SupervisorAgent < OmniAgent::Agent
29
+ delegate_to SupportAgent, as: :triage_ticket, run_alias: :triage
30
+ end
31
+ ```
32
+
33
+ ## Forwarding Context
34
+
35
+ Delegated agents run **isolated** by default — they get only the `input` string, none of the supervisor's context. Pass `forward:` to share part or all of it:
36
+
37
+ ```ruby
38
+ class SupervisorAgent < OmniAgent::Agent
39
+ delegate_to ResearchAgent, as: :research, forward: [ :user, :locale ]
40
+ delegate_to MathAgent, as: :calculate, forward: true
41
+ end
42
+ ```
43
+
44
+ * **`forward: [:key, ...]`**: Only the listed context keys are passed to the sub-agent's `context:`.
45
+ * **`forward: true`**: The entire context hash is forwarded.
46
+ * **`forward: []`** (default): The sub-agent runs with an empty context.
47
+
48
+ ## Guarding Against Runaway Delegation
49
+
50
+ Since a delegated agent could itself delegate (directly or through a chain of other agents), delegation depth is capped by `OmniAgent.configuration.max_delegation_depth` (default `5`). Exceeding it raises `OmniAgent::MaxDelegationDepthError` instead of recursing forever. Raise the limit in your initializer if you have legitimately deep delegation chains:
51
+
52
+ ```ruby
53
+ OmniAgent.configure do |config|
54
+ config.max_delegation_depth = 8
55
+ end
56
+ ```
@@ -0,0 +1,93 @@
1
+ <!-- Generated from omniagent-docs/docs/agent/provider.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
2
+ <!-- Online version: https://omniagent.activetaskers.com/docs/agent/provider -->
3
+
4
+ # Change an Agent's Provider or Model
5
+
6
+ By default, agents use the configured `default_provider` and `default_model`. You can override these settings on a per-agent basis using one of the following DSL directives.
7
+
8
+ ## Change Provider and Model
9
+
10
+ Use `provider` when you want to change the provider and optionally specify a model.
11
+
12
+ ```ruby
13
+ provider :openai, model: 'gpt-5.4'
14
+ ```
15
+
16
+ You can also change only the provider and let it use that provider's default model:
17
+
18
+ ```ruby
19
+ provider :openai
20
+ ```
21
+
22
+ To run against a local model via [Ollama](../supported-providers.md#ollama) instead:
23
+
24
+ ```ruby
25
+ provider :ollama, model: 'llama3.1'
26
+ ```
27
+
28
+ ## Change Only the Model
29
+
30
+ Use `use_model` when you want to keep the current `default_provider` and only override the model.
31
+
32
+ ```ruby
33
+ use_model 'gpt-5.4'
34
+ ```
35
+
36
+ ## Important
37
+
38
+ `provider` and `use_model` are mutually exclusive within the same agent definition. You must choose **one** of the following approaches:
39
+
40
+ * Change the provider (and optionally the model) with `provider`
41
+ * Keep the default provider and change only the model with `use_model`
42
+
43
+ ### Valid:
44
+
45
+ ```ruby
46
+ provider :openai, model: 'gpt-5.4'
47
+ ```
48
+
49
+ ```ruby
50
+ provider :anthropic
51
+ ```
52
+
53
+ ```ruby
54
+ use_model 'gpt-5.4'
55
+ ```
56
+
57
+ ### Invalid:
58
+
59
+ ```ruby
60
+ provider :openai, model: 'gpt-5.4'
61
+ use_model 'gpt-5.5'
62
+ ```
63
+
64
+ The example above is not allowed because both directives are defined in the same agent.
65
+
66
+ > **Note:** An agent can either override its provider using `provider` or override its model using `use_model`, but it cannot use both directives simultaneously.
67
+
68
+ ## Inheritance
69
+
70
+ Subclasses inherit their parent agent's `provider`, model, `options`, tags, callbacks, tools and `output`, and can override any of them:
71
+
72
+ ```ruby
73
+ class ApplicationAgent < OmniAgent::Agent
74
+ provider :openai, model: "gpt-4o"
75
+ options temperature: 0.2
76
+ end
77
+
78
+ class SupportAgent < ApplicationAgent
79
+ options max_tokens: 500 # merged: { temperature: 0.2, max_tokens: 500 }
80
+ end
81
+ ```
82
+
83
+ A subclass may call `use_model` even if its parent called `provider`. As on its own, `use_model` means "default provider + this model", so the subclass does not keep the parent's provider:
84
+
85
+ ```ruby
86
+ class LocalSupportAgent < ApplicationAgent
87
+ use_model "llama3.1" # uses OmniAgent.configuration.default_provider, not :openai
88
+ end
89
+ ```
90
+
91
+ To keep the parent's provider and only change the model, repeat it: `provider :openai, model: "gpt-4o-mini"`.
92
+
93
+ A tool in the subclass replaces a parent tool with the same name (for example `SupportAgent::Tools::Lookup` replaces `ApplicationAgent::Tools::Lookup`), and the same applies to `delegate_to ..., as:` names.
@@ -0,0 +1,81 @@
1
+ <!-- Generated from omniagent-docs/docs/agent/run-entrypoints.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
2
+ <!-- Online version: https://omniagent.activetaskers.com/docs/agent/run-entrypoints -->
3
+
4
+ # Run Entrypoints
5
+
6
+ `OmniAgent::Agent` always exposes the low-level `run` method, but you'll usually call an agent through one of its higher-level entrypoints.
7
+
8
+ ## Implicit Entrypoints
9
+
10
+ Any zero-argument instance method you define on an agent automatically becomes a runnable entrypoint:
11
+
12
+ ```ruby
13
+ class ResearchAgent < OmniAgent::Agent
14
+ use_model "gpt-4o-mini"
15
+
16
+ def summarize
17
+ @message = "Summarize the latest market trends."
18
+ end
19
+ end
20
+ ```
21
+
22
+ Calling `ResearchAgent.new.summarize` with no arguments runs your method body and uses its return value (or `@message`, if set) as the input sent to the model. Calling it **with** arguments instead runs the agent directly:
23
+
24
+ ```ruby
25
+ ResearchAgent.new.summarize # runs `summarize`, then calls the model
26
+ ResearchAgent.new.summarize("Custom input") # skips to `run` with this input
27
+ ResearchAgent.new.summarize(context: { history: [...] })
28
+ ```
29
+
30
+ This is handled by `method_added`, so it works for any method you define — no extra declaration needed. It also means the prompt file used is named after the calling method: defining `summarize` will look for `app/agents/research_agent/summarize.md.erb` in addition to the base `prompt.md.erb`.
31
+
32
+ ## Explicit Run Aliases
33
+
34
+ If you'd rather declare entrypoints explicitly instead of relying on every zero-arg method, use `run_aliases`:
35
+
36
+ ```ruby
37
+ class ResearchAgent < OmniAgent::Agent
38
+ run_aliases :ask, :follow_up
39
+ end
40
+ ```
41
+
42
+ This defines `ask` and `follow_up` as direct wrappers around `run`, each tagged with their own name as `prompt_method` (so they can pick up a matching `<method_name>.md.erb` prompt file).
43
+
44
+ ## Calling the Agent Directly
45
+
46
+ The simplest way to run an agent is `.new.run(input)`:
47
+
48
+ ```ruby
49
+ ResearchAgent.new.run("Hello!")
50
+ ```
51
+
52
+ ## Scoped Instances with `with`
53
+
54
+ Use the class-level `with` helper to build a pre-configured instance without mutating the class itself:
55
+
56
+ ```ruby
57
+ ResearchAgent.with(user_id: 42).run("What's new?")
58
+
59
+ ResearchAgent.with(
60
+ provider_override: :mock,
61
+ model_override: "gpt-4.1-mini"
62
+ ).run("Test run")
63
+ ```
64
+
65
+ `with` accepts:
66
+
67
+ * **Context kwargs** (e.g. `user_id: 42`) — merged into every `run` call made on the returned instance.
68
+ * **`provider_override`** / **`model_override`** — temporarily swap the provider or model for this instance only.
69
+ * **`options_override`** — merged into the chat options sent to the provider.
70
+
71
+ ## Streaming
72
+
73
+ Any of the entrypoints above can stream by prefixing the call with `.stream` and passing a block:
74
+
75
+ ```ruby
76
+ ResearchAgent.with(user_id: 42).stream.run("What's new?") do |event|
77
+ print event.text if event.text?
78
+ end
79
+ ```
80
+
81
+ See [Streaming Responses](streaming.md) for the full event reference.
@@ -0,0 +1,62 @@
1
+ <!-- Generated from omniagent-docs/docs/agent/streaming.mdx by `bundle exec rake docs:sync`. Do not edit by hand. -->
2
+ <!-- Online version: https://omniagent.activetaskers.com/docs/agent/streaming -->
3
+
4
+ # Streaming Responses
5
+
6
+ Any run entrypoint — `run`, an implicit entrypoint, or a `run_aliases` method — can stream its response by prefixing the call with `.stream` and passing a block. Without `.stream` (or without a block), behavior is completely unchanged.
7
+
8
+ ```ruby
9
+ agent.stream.run("Hello") do |event|
10
+ print event.text if event.text?
11
+ end
12
+
13
+ ResearchAgent.with(user_id: 42).stream.summarize("Latest trends") do |event|
14
+ # ...
15
+ end
16
+ ```
17
+
18
+ `.stream` must come **before** the call, not after — the LLM request happens synchronously inside `run`/the entrypoint, so there's nothing to stream once it returns. The call still returns the same `OmniAgent::Providers::Response` it always would; `.stream` only adds a side channel of events emitted while it runs.
19
+
20
+ ## Event Types
21
+
22
+ Each yielded object is an `OmniAgent::Streaming::Event`:
23
+
24
+ | Type | Predicate | Fields | When |
25
+ | --- | --- | --- | --- |
26
+ | `:text` | `text?` | `text` | A chunk of the assistant's response text |
27
+ | `:tool_call` | `tool_call?` | `tool_name`, `tool_arguments`, `tool_id` | Right before a tool is invoked |
28
+ | `:tool_result` | `tool_result?` | `tool_name`, `tool_id`, `content`, `error?` | Right after a tool finishes (or raises) |
29
+ | `:done` | `done?` | `response` | Generation is complete; `response` is the final `Response` |
30
+
31
+ A full tool-calling round looks like:
32
+
33
+ ```
34
+ :text (any content before the tool call, if the model produced any)
35
+ :tool_call
36
+ :tool_result
37
+ :text, :text, ... (the final answer, streamed)
38
+ :done
39
+ ```
40
+
41
+ ## Example: Chat UI
42
+
43
+ ```ruby
44
+ TestAgent.with(user_id: 2).stream.user_query("Search and summarize") do |event|
45
+ case event.type
46
+ when :text
47
+ print event.text
48
+ when :tool_call
49
+ puts "\n[using #{event.tool_name}...]"
50
+ when :tool_result
51
+ puts "[#{event.error? ? "failed" : "done"}]"
52
+ when :done
53
+ puts "\n---"
54
+ end
55
+ end
56
+ ```
57
+
58
+ ## Provider Support
59
+
60
+ Streaming is implemented per-provider. The built-in `openai` provider streams via the `openai` gem's `client.chat.completions.stream` and emits `:text` events for content deltas; tool-call arguments are not streamed incrementally (a `:tool_call` event fires once the full round finishes and arguments are fully parsed — a tool can't be invoked from a partial JSON fragment). The `ollama` provider streams the same way, since it subclasses `openai` and only swaps the client's `base_url` and defaults. The `mock` provider also supports streaming, splitting its canned response into words, useful for testing UI code without a network call.
61
+
62
+ Retries via `with_retries` wrap stream *creation* only — a connection drop mid-stream is not retried.