rcrewai 0.3.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7682076eeeb0d3c1bdc0de2185c83ad8c925ce19165786daa0910c1925fdf64
4
- data.tar.gz: 1db4ba5508d8aef52b6645be9cae3dcb7328564f8335a24fe844655196d2f53f
3
+ metadata.gz: 68a381d21e047e37972fda09fd7e9faa93da77e6fe01a040a4c77b9ee0066aca
4
+ data.tar.gz: 3818acc6b9b6eb71d27d4099cb1c1fc574adbdd0a3a8128389803c051c364774
5
5
  SHA512:
6
- metadata.gz: dfaf95b581c86cd36573400729a02e72e307721f51267fefafdb3245aae28bffc89201855dcd294f38ecfc54a6a1dbf2062d92fbd55310e4f2f9d968b49a1a6e
7
- data.tar.gz: 4c8ed1132d754e92ab154c192f28d8add3d80fdbedbaaa15fa6fd876002f18674d30a91f7ef9fb5dcba65a4240ca9f464d26fa15b64a8cdcdee4ad806cefe3bd
6
+ metadata.gz: 65d796bde314db55466f7f4f043db9eeb772dee3e72914d2bc27947eb6dfe84ce246b89a6181d473c3f7d175d353fbc9531914c8baa5b7b010e02229324bac27
7
+ data.tar.gz: b2fffaab35dc672b12d2a5aea5fd0b02e6519b2f4a5ca98daaf66bef740551e2e674f4cc39967f3d987ed9dfe1c13e7dec4c8a30e5b86e2140242e347dd96a11
data/.rubocop.yml CHANGED
@@ -1 +1,21 @@
1
1
  inherit_from: .rubocop_todo.yml
2
+
3
+ Naming/MethodParameterName:
4
+ # `k` (top-k retrieval) and short math vars for vector similarity are
5
+ # conventional and clearer than forced longer names. The rest are RuboCop's
6
+ # defaults, restated because AllowedNames replaces rather than extends.
7
+ AllowedNames:
8
+ - k
9
+ - a
10
+ - b
11
+ - io
12
+ - id
13
+ - to
14
+ - by
15
+ - 'on'
16
+ - in
17
+ - at
18
+ - ip
19
+ - db
20
+ - os
21
+ - pp
data/CHANGELOG.md CHANGED
@@ -7,6 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-07-03
11
+
12
+ Polish release completing the roadmap backlog: crew lifecycle hooks, batch
13
+ execution, rate limiting, per-agent reasoning, context-window management, and
14
+ multimodal image input. All additive — existing code runs unchanged.
15
+
16
+ ### Added
17
+ - Crew lifecycle hooks: `Crew#before_kickoff` and `Crew#after_kickoff` register callbacks that run before/after execution. A `before_kickoff` hook receives the inputs hash (passed via `crew.execute(inputs:)`) and may transform it; an `after_kickoff` hook receives the result and may transform it. Multiple hooks run in registration order. The (possibly transformed) inputs are exposed on `Crew#last_inputs`. (#15)
18
+ - `Crew#kickoff_for_each(inputs:)` runs the crew once per input set and returns one result per input, in order. Runs are isolated — each execution starts from only its own inputs. (#16)
19
+ - Rate limiting: `Agent.new(max_rpm:)` throttles the agent's LLM calls to the given requests-per-minute using a thread-safe rolling-window `RCrewAI::RateLimiter`. The agent's client is transparently wrapped (`RateLimiter::ThrottledClient`) so every `chat` acquires a slot first; `max_rpm` nil/0 means unlimited. (#17)
20
+ - Reasoning: `Agent.new(reasoning: true)` runs a reasoning/planning pass before answering — the LLM drafts a short plan for the task, which is injected into the answer prompt and surfaced on the result hash as `:reasoning` (without polluting `task.result`). Bounded by `max_reasoning_attempts` (default 3), retrying if the model returns empty output; if every attempt is empty, execution proceeds without a plan. Off by default. (#18)
21
+ - Context-window management: `Agent.new(respect_context_window: true)` trims the message history to fit the model's context window before each LLM call, dropping the oldest non-system messages while always keeping system messages and the latest message. The new `RCrewAI::ContextWindow` module provides the token estimate (chars/4 heuristic), a per-model window-size table, and the `fit` trimmer. Off by default. (#19)
22
+ - Multimodal input: `Task.new(attachments:)` accepts image attachments (`{ type: :image, url: '...' }` or `{ type: :image, path: '...' }`). When a task has attachments, the agent builds an OpenAI-style multimodal user message (text + `image_url` parts); local files are base64-encoded into data URLs with a mime type inferred from the extension. Supported on OpenAI/Azure; other providers raise a clear `Multimodal::UnsupportedProviderError`. The new `RCrewAI::Multimodal` module builds the content parts. (#20)
23
+
24
+ ## [0.4.0] - 2026-07-03
25
+
26
+ This release closes the feature-parity gap with the modern CrewAI framework,
27
+ adding its second pillar (**Flows**) alongside **Knowledge (RAG)**, structured
28
+ output, guardrails, planning, and training/testing. See `ROADMAP.md`.
29
+
30
+ ### Added
31
+
32
+ #### Flows (#11)
33
+ - `RCrewAI::Flow` — an event-driven workflow engine (CrewAI's second pillar). Subclass it and declare methods with a class-level DSL: `start`, `listen`, `router`, and the `and_` / `or_` trigger combinators. `kickoff` runs the graph to a fixed point; routers emit labels that listeners trigger on.
34
+ - Flow state (`Flow::State`) is a schemaless object with an automatic UUID, seedable via `kickoff(inputs:)`.
35
+ - Flow persistence: pluggable state stores (`Flow::MemoryStateStore`, `Flow::FileStateStore`, or any `#save`/`#load` object); `flow.restore(id)` resumes a persisted run.
36
+ - Flows can invoke a `Crew` as a step and pause for input via `#human_feedback`.
37
+
38
+ #### Knowledge / RAG (#9)
39
+ - `RCrewAI::Knowledge` module adds retrieval-augmented context. Sources (`StringSource`, `FileSource`, `PdfSource`, `CsvSource`, `UrlSource`) are chunked, embedded, and stored in an in-memory cosine-similarity vector store (no external DB required).
40
+ - Attach via `Agent.new(knowledge:)` / `knowledge_sources:` (role-specific) or `Crew.new(knowledge:)` / `knowledge_sources:` (shared with all agents); relevant chunks are injected into each task's prompt at execution.
41
+ - The embedder (`Knowledge::Embedder`, default OpenAI `text-embedding-3-small`) and vector store are pluggable.
42
+
43
+ #### Task output processing (#6, #7, #8)
44
+ - Structured output: `Task.new(output_schema:)` validates and coerces the agent's output against a JSON-schema subset, exposing the parsed object via `Task#structured_output` (and the raw string via `Task#raw_result`). JSON embedded in surrounding prose or a fenced code block is extracted automatically; output that doesn't conform re-runs the agent with the error fed back.
45
+ - Guardrails: `Task.new(guardrail:)` takes a callable returning `[ok, value_or_error]` to validate and transform output before it flows downstream, retrying up to `guardrail_max_retries` (default 3) with the rejection reason fed back to the agent.
46
+ - Output persistence & formatting: `Task.new(output_file:)` writes the result to disk (`create_directory:` controls parent-dir creation, default true), and `markdown: true` prepends a heading when the output isn't already a markdown document.
47
+ - `RCrewAI::OutputSchema` — a small JSON-schema-subset validator/coercer used by structured task output.
48
+
49
+ #### Per-agent LLM (#5)
50
+ - `Agent.new(llm:)` accepts a provider symbol (`:anthropic`), an options hash (`{ provider:, model:, api_key:, temperature: }`), or a pre-built client instance. Agents in the same crew can use different providers/models (e.g. a cheap worker model and a stronger manager model). Omitting `llm:` keeps the previous global-configuration behavior.
51
+ - `Configuration#with_overrides` returns a copy of the configuration with per-agent overrides applied, leaving global state untouched.
52
+
53
+ #### Planning (#10)
54
+ - `Crew.new(planning: true)` runs a single planner pass before execution that asks an LLM to draft a short plan for each task and folds it into the task's description. Optional `planning_llm:` selects the planner client (defaults to the global provider). Best-effort — a planner error or unparseable output leaves tasks unchanged and execution proceeds.
55
+ - `Task#enrich_description` appends supplementary guidance (used by the planner) without discarding the original instructions.
56
+
57
+ #### Training & testing (#12)
58
+ - `Crew#train(n_iterations:, filename:)` runs the crew repeatedly, collects feedback after each iteration (via a `feedback:` callable, defaulting to a human prompt), and persists it as JSON.
59
+ - `Crew#test(n_iterations:)` runs the crew repeatedly and reports per-run and average scores (via a `scorer:` callable, defaulting to the run's success rate).
60
+
10
61
  ## [0.3.0] - 2026-05-12
11
62
 
12
63
  ### Added
@@ -128,5 +179,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
128
179
  - CLI usage documentation
129
180
  - Real-world use cases and examples
130
181
 
131
- [Unreleased]: https://github.com/gkosmo/rcrewAI/compare/v0.1.0...HEAD
182
+ [Unreleased]: https://github.com/gkosmo/rcrewAI/compare/v0.5.0...HEAD
183
+ [0.5.0]: https://github.com/gkosmo/rcrewAI/compare/v0.4.0...v0.5.0
184
+ [0.4.0]: https://github.com/gkosmo/rcrewAI/compare/v0.3.0...v0.4.0
185
+ [0.3.0]: https://github.com/gkosmo/rcrewAI/compare/v0.1.0...v0.3.0
132
186
  [0.1.0]: https://github.com/gkosmo/rcrewAI/releases/tag/v0.1.0
data/README.md CHANGED
@@ -19,6 +19,8 @@ RCrewAI is a Ruby implementation of the CrewAI framework, allowing you to create
19
19
  - **🏗️ Hierarchical Teams**: Manager agents that coordinate and delegate tasks to specialist agents
20
20
  - **🔒 Production Ready**: Security controls, error handling, logging, monitoring, and sandboxing
21
21
  - **🎯 Flexible Orchestration**: Sequential, hierarchical, and concurrent execution modes
22
+ - **🌊 Flows**: Event-driven workflows with `start`/`listen`/`router`, branching, and persistent state
23
+ - **📚 Knowledge (RAG)**: Ground agents in your own documents with built-in retrieval
22
24
  - **💎 Ruby-First Design**: Built specifically for Ruby developers with idiomatic patterns
23
25
 
24
26
  ## 📦 Installation
@@ -169,6 +171,254 @@ RCrewAI.configure do |config|
169
171
  end
170
172
  ```
171
173
 
174
+ ### Per-agent LLM
175
+
176
+ The `RCrewAI.configure` block sets the crew-wide default. Any agent can override
177
+ it with the `llm:` option, so a single crew can mix providers and models — for
178
+ example a cheap model for workers and a stronger one for the manager:
179
+
180
+ ```ruby
181
+ # Provider only (uses that provider's configured model + key)
182
+ researcher = RCrewAI::Agent.new(name: 'researcher', role: '...', goal: '...',
183
+ llm: :anthropic)
184
+
185
+ # Provider + model (and optionally api_key / temperature)
186
+ manager = RCrewAI::Agent.new(name: 'manager', role: '...', goal: '...',
187
+ llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
188
+
189
+ worker = RCrewAI::Agent.new(name: 'worker', role: '...', goal: '...',
190
+ llm: { provider: :openai, model: 'gpt-4o-mini' })
191
+
192
+ # Or pass a pre-built client instance
193
+ worker = RCrewAI::Agent.new(name: 'worker', role: '...', goal: '...',
194
+ llm: my_client)
195
+ ```
196
+
197
+ Omit `llm:` to use the global `RCrewAI.configure` settings. Overrides never
198
+ mutate the global configuration.
199
+
200
+ ## 📤 Structured Output, Guardrails & File Output
201
+
202
+ Tasks can validate, transform, and persist their output:
203
+
204
+ ```ruby
205
+ task = RCrewAI::Task.new(
206
+ name: 'extract',
207
+ description: 'Extract the article title and word count as JSON',
208
+ agent: analyst,
209
+
210
+ # Structured output — validated & coerced against a JSON schema.
211
+ # Non-conforming output re-runs the agent with the error fed back.
212
+ output_schema: {
213
+ type: 'object',
214
+ properties: { title: { type: 'string' }, words: { type: 'integer' } },
215
+ required: ['title']
216
+ },
217
+
218
+ # Guardrail — ->(output) { [ok, value_or_error] }. On rejection the agent
219
+ # re-runs (up to guardrail_max_retries) with the reason appended.
220
+ guardrail: ->(out) { [out.length < 5000, 'must be under 5000 chars'] },
221
+ guardrail_max_retries: 3,
222
+
223
+ # Persist the result. Parent dirs are created unless create_directory: false.
224
+ output_file: 'out/report.md',
225
+ markdown: true
226
+ )
227
+
228
+ task.execute
229
+ task.structured_output # => { "title" => "...", "words" => 1234 }
230
+ task.raw_result # => the unprocessed string the agent produced
231
+ ```
232
+
233
+ ## 🗺️ Planning
234
+
235
+ Enable `planning:` on a crew to run a planner pass before execution. The planner
236
+ drafts a short plan for each task and folds it into the task description, giving
237
+ the executing agent a head start:
238
+
239
+ ```ruby
240
+ crew = RCrewAI::Crew.new('research_crew', planning: true)
241
+ # Optionally use a dedicated (e.g. stronger) planner model:
242
+ crew = RCrewAI::Crew.new('research_crew', planning: true,
243
+ planning_llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
244
+ ```
245
+
246
+ Planning is best-effort: if the planner errors or returns unparseable output,
247
+ the crew runs with the original tasks unchanged.
248
+
249
+ ## 🏋️ Training & Testing
250
+
251
+ Iterate on a crew by training it with feedback or scoring repeated runs:
252
+
253
+ ```ruby
254
+ # Train: run N times, collect feedback after each run, persist to JSON.
255
+ crew.train(n_iterations: 3, filename: 'training.json')
256
+
257
+ # Provide feedback programmatically instead of prompting a human:
258
+ crew.train(n_iterations: 3, filename: 'training.json',
259
+ feedback: ->(iteration, result) { "run #{iteration}: #{result[:success_rate]}%" })
260
+
261
+ # Test: run N times and score each run (defaults to success_rate).
262
+ crew.test(n_iterations: 5)
263
+ # => { iterations: 5, scores: [...], average_score: 92.0 }
264
+ ```
265
+
266
+ ## 🪝 Kickoff Hooks & Batch Runs
267
+
268
+ Run setup/teardown around a crew, and batch it over many inputs:
269
+
270
+ ```ruby
271
+ crew.before_kickoff { |inputs| inputs.merge(started_at: Time.now) } # may transform inputs
272
+ crew.after_kickoff { |result| notify(result); result } # may transform result
273
+
274
+ crew.execute(inputs: { topic: 'ruby' })
275
+ crew.last_inputs # => the (possibly transformed) inputs the run used
276
+
277
+ # Batch: run the crew once per input set, results returned in order.
278
+ results = crew.kickoff_for_each(inputs: [
279
+ { topic: 'ruby' },
280
+ { topic: 'python' }
281
+ ])
282
+ ```
283
+
284
+ ## ⏱️ Rate Limiting
285
+
286
+ Cap an agent's LLM calls to stay under provider limits. Calls beyond the cap
287
+ block until the rolling 60-second window frees up:
288
+
289
+ ```ruby
290
+ agent = RCrewAI::Agent.new(name: 'a', role: '...', goal: '...', max_rpm: 20)
291
+ ```
292
+
293
+ The limiter (`RCrewAI::RateLimiter`) is thread-safe, so it holds under async
294
+ execution. `max_rpm: nil` (the default) or `0` means unlimited.
295
+
296
+ ## 🧠 Reasoning
297
+
298
+ Have an agent think through a plan before answering. The reasoning trace is
299
+ surfaced on the result and does not pollute `task.result`:
300
+
301
+ ```ruby
302
+ agent = RCrewAI::Agent.new(name: 'a', role: '...', goal: '...',
303
+ reasoning: true, max_reasoning_attempts: 3)
304
+
305
+ result = agent.execute_task(task)
306
+ result[:reasoning] # => the plan the agent drafted before answering
307
+ result[:content] # => the final answer
308
+ ```
309
+
310
+ Off by default. If the reasoning pass keeps returning empty output past
311
+ `max_reasoning_attempts`, the agent proceeds without a plan.
312
+
313
+ ## 🪟 Context Window Management
314
+
315
+ Keep long tool-use loops or large injected context from overflowing the model's
316
+ context window. When enabled, the oldest non-system messages are dropped to fit
317
+ before each LLM call (system messages and the latest message are always kept):
318
+
319
+ ```ruby
320
+ agent = RCrewAI::Agent.new(name: 'a', role: '...', goal: '...',
321
+ respect_context_window: true)
322
+ ```
323
+
324
+ Window sizes come from `RCrewAI::ContextWindow` (with a conservative default for
325
+ unknown models); headroom for the response is reserved from `max_tokens`. Off by
326
+ default.
327
+
328
+ ## 🖼️ Multimodal Input
329
+
330
+ Pass images to a vision-capable model via task attachments. Local files are
331
+ base64-encoded automatically; URLs pass through:
332
+
333
+ ```ruby
334
+ RCrewAI.configure { |c| c.llm_provider = :openai; c.openai_model = 'gpt-4o' }
335
+
336
+ task = RCrewAI::Task.new(
337
+ name: 'describe', description: 'What is in this chart?', agent: agent,
338
+ attachments: [
339
+ { type: :image, path: 'chart.png' },
340
+ { type: :image, url: 'https://example.com/photo.jpg' }
341
+ ]
342
+ )
343
+ ```
344
+
345
+ Supported on OpenAI and Azure; other providers raise a clear error when
346
+ attachments are present.
347
+
348
+ ## 📚 Knowledge (RAG)
349
+
350
+ Ground agents in your own documents. Sources are chunked, embedded, and stored
351
+ in an in-memory vector store; the most relevant chunks are injected into each
352
+ task's prompt automatically.
353
+
354
+ ```ruby
355
+ kb = RCrewAI::Knowledge::Base.new(sources: [
356
+ RCrewAI::Knowledge::StringSource.new('Our refund window is 30 days.'),
357
+ RCrewAI::Knowledge::FileSource.new('docs/policy.txt'),
358
+ RCrewAI::Knowledge::PdfSource.new('handbook.pdf'),
359
+ RCrewAI::Knowledge::UrlSource.new('https://example.com/faq')
360
+ ])
361
+
362
+ # Agent-level (role-specific) knowledge:
363
+ support = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...', knowledge: kb)
364
+
365
+ # Or pass raw sources and let the agent build the base:
366
+ support = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...',
367
+ knowledge_sources: [RCrewAI::Knowledge::StringSource.new('...')])
368
+
369
+ # Crew-level knowledge is shared with every agent:
370
+ crew = RCrewAI::Crew.new('support_crew', knowledge: kb)
371
+ ```
372
+
373
+ Embeddings default to OpenAI's `text-embedding-3-small`; pass a custom
374
+ `embedder:` (anything responding to `embed(texts)`) or vector store to swap the
375
+ backend.
376
+
377
+ ## 🌊 Flows
378
+
379
+ Beyond crews, RCrewAI has **Flows** — an event-driven workflow engine for
380
+ orchestrating steps (and whole crews) with explicit branching and state:
381
+
382
+ ```ruby
383
+ class ArticleFlow < RCrewAI::Flow
384
+ start :outline
385
+ def outline
386
+ state.sections = %w[intro body conclusion]
387
+ state.sections.length
388
+ end
389
+
390
+ listen :outline
391
+ def draft(section_count)
392
+ state.words = section_count * 100
393
+ state.words
394
+ end
395
+
396
+ router :draft
397
+ def review(words)
398
+ words >= 250 ? :publish : :expand
399
+ end
400
+
401
+ listen :publish
402
+ def publish = state.status = 'published'
403
+
404
+ listen :expand
405
+ def expand = state.status = 'needs more work'
406
+ end
407
+
408
+ flow = ArticleFlow.new
409
+ flow.kickoff(inputs: { author: 'me' })
410
+ flow.state.status # => "published"
411
+ flow.state.id # => automatic UUID
412
+ ```
413
+
414
+ - `start` / `listen` / `router` wire methods into a graph; a listener receives
415
+ its trigger's return value.
416
+ - Combine triggers with `and_(:a, :b)` (all) and `or_(:a, :b)` (any).
417
+ - **State** is a schemaless object with a UUID, seedable via `kickoff(inputs:)`.
418
+ - **Persistence**: pass `state_store:` (`RCrewAI::Flow::FileStateStore.new(dir)`
419
+ or your own `#save`/`#load`) and call `flow.restore(id)` to resume.
420
+ - Invoke a `Crew` inside any step, or pause with `human_feedback('Approve?')`.
421
+
172
422
  ## 💡 Examples
173
423
 
174
424
  ### Hierarchical Team with Human Oversight
data/ROADMAP.md ADDED
@@ -0,0 +1,90 @@
1
+ # RCrewAI Roadmap
2
+
3
+ This roadmap tracks feature parity between **RCrewAI** (Ruby) and the upstream
4
+ [**crewai**](https://pypi.org/project/crewai/) Python framework.
5
+
6
+ ## Current status
7
+
8
+ - **RCrewAI:** `0.3.0` (2026-05-12)
9
+ - **Upstream crewai:** `1.15.x` (mid-2026)
10
+
11
+ RCrewAI is a faithful port of CrewAI's **"Crews"** mental model (Agents / Tasks /
12
+ Crew, sequential + hierarchical processes, tools, memory, human-in-the-loop). As
13
+ of `0.3.0` the LLM plumbing is modern: native function calling across all five
14
+ providers, a tool-schema DSL, typed streaming events, MCP client, and per-model
15
+ pricing.
16
+
17
+ Since CrewAI's `1.0`, the framework grew a second pillar (**Flows**) plus
18
+ **Knowledge (RAG)**, **Guardrails**, **structured output**, **Planning**, and
19
+ **Training/Testing**. RCrewAI now implements all of these — see the matrix below.
20
+
21
+ **Status: complete.** All milestone issues (#5–#12) shipped in v0.4.0, and all
22
+ backlog items (#15–#20) are done and merged to `main` (awaiting the next
23
+ release). There is no outstanding roadmap work.
24
+
25
+ ## Parity matrix
26
+
27
+ | Concept | crewai | RCrewAI 0.3.0 | Target |
28
+ |---|---|---|---|
29
+ | Agents / Tasks / Crew | ✅ | ✅ | — |
30
+ | Sequential / hierarchical process | ✅ | ✅ | — |
31
+ | Native function calling + tool DSL | ✅ | ✅ (0.3.0) | — |
32
+ | Streaming events | ✅ | ✅ (0.3.0) | — |
33
+ | MCP client | ✅ | ✅ (0.3.0) | — |
34
+ | Per-model pricing / cost | ✅ | ✅ (0.3.0) | — |
35
+ | Per-agent LLM override | ✅ | ✅ (#5) | ✅ done |
36
+ | Structured output (schema) | ✅ | ✅ (#6) | ✅ done |
37
+ | Task guardrails | ✅ | ✅ (#7) | ✅ done |
38
+ | `output_file` / markdown | ✅ | ✅ (#8) | ✅ done |
39
+ | Knowledge / RAG | ✅ | ✅ (#9) | ✅ done |
40
+ | Planning | ✅ | ✅ (#10) | ✅ done |
41
+ | Flows (`start`/`listen`/`router`) | ✅ | ✅ (#11) | ✅ done |
42
+ | Flow state + persistence | ✅ | ✅ (#11) | ✅ done |
43
+ | Training / Testing | ✅ | ✅ (#12) | ✅ done |
44
+ | Reasoning, rate-limiting, batch kickoff, hooks, context window, multimodal | ✅ | ✅ (#15–#20) | ✅ done |
45
+
46
+ ## Milestones (highest leverage first)
47
+
48
+ ### 0.3.1 — Per-agent LLM override
49
+ Let `Agent.new(llm:)` accept a provider/model, instead of only the global
50
+ `RCrewAI.configure`. Unblocks mixed-model crews (cheap model for workers, strong
51
+ model for the manager).
52
+
53
+ ### 0.4.0 — Structured output & guardrails
54
+ Builds directly on the 0.3.0 tool-schema/JSON-schema plumbing.
55
+ - `Task.new(output_schema:)` → validated, coerced structured result.
56
+ - `Task.new(guardrail:)` → proc/object that validates & transforms output, with
57
+ bounded retries (`guardrail_max_retries`).
58
+ - `output_file:` + `markdown:` output formatting.
59
+
60
+ ### 0.5.0 — Knowledge (RAG) & Planning
61
+ - Knowledge sources: string, `.txt`, PDF (have `pdf-reader`), CSV, JSON, URL
62
+ (have `nokogiri`). Embeddings client + a pluggable vector store (start with an
63
+ in-memory / SQLite cosine store; no hard Chroma dependency).
64
+ - Attach at agent **and** crew level.
65
+ - `Crew.new(planning: true)` → a planner pass that drafts a step plan before
66
+ execution.
67
+
68
+ ### 0.6.0 — Flows
69
+ The flagship. A Ruby DSL mirroring CrewAI Flows:
70
+ - `start`, `listen`, `router` decorators/class-methods.
71
+ - `and_` / `or_` trigger combinators.
72
+ - Structured flow **state** (a plain struct/`Data` or dry-struct) with a UUID.
73
+ - `@persist`-equivalent state persistence across restarts.
74
+ - `human_feedback` pause/resume point.
75
+
76
+ ### 0.7.0 — Training & Testing
77
+ - `crew.train(n_iterations:, filename:)` capturing human feedback.
78
+ - `crew.test(n_iterations:, model:)` scoring runs.
79
+
80
+ ### Backlog — ✅ all complete
81
+
82
+ Formerly polish items with no set version; all shipped in the `[Unreleased]`
83
+ changes (see CHANGELOG):
84
+
85
+ - [#15](https://github.com/gkosmo/rcrewAI/issues/15) — `before_kickoff` / `after_kickoff` lifecycle hooks ✅
86
+ - [#16](https://github.com/gkosmo/rcrewAI/issues/16) — `kickoff_for_each` batch execution ✅
87
+ - [#17](https://github.com/gkosmo/rcrewAI/issues/17) — `max_rpm` rate limiting ✅
88
+ - [#18](https://github.com/gkosmo/rcrewAI/issues/18) — per-agent reasoning (`reasoning:`, `max_reasoning_attempts:`) ✅
89
+ - [#19](https://github.com/gkosmo/rcrewAI/issues/19) — `respect_context_window` history trimming ✅
90
+ - [#20](https://github.com/gkosmo/rcrewAI/issues/20) — multimodal agents (image/file inputs) ✅
@@ -0,0 +1,191 @@
1
+ # Upgrading to RCrewAI 0.4
2
+
3
+ RCrewAI 0.4 closes the feature-parity gap with the modern CrewAI framework. It
4
+ adds CrewAI's second pillar — **Flows** — alongside **Knowledge (RAG)**,
5
+ structured task output, guardrails, planning, and training/testing.
6
+
7
+ **0.4 is fully backward compatible.** There are no breaking changes: existing
8
+ 0.3 code runs unchanged. Everything below is opt-in.
9
+
10
+ Each new capability has a runnable example under `examples/` — see the links
11
+ throughout.
12
+
13
+ ---
14
+
15
+ ## 1. What you must do
16
+
17
+ Nothing. Upgrade the gem and your existing code keeps working:
18
+
19
+ ```ruby
20
+ gem 'rcrewai', '~> 0.4'
21
+ ```
22
+
23
+ ---
24
+
25
+ ## 2. What you can do (new capabilities)
26
+
27
+ ### 2a. Per-agent LLM
28
+
29
+ Give each agent its own provider/model instead of only the global default.
30
+ Pass a provider symbol, an options hash, or a pre-built client:
31
+
32
+ ```ruby
33
+ worker = RCrewAI::Agent.new(name: 'worker', role: '...', goal: '...',
34
+ llm: { provider: :openai, model: 'gpt-4o-mini' })
35
+ manager = RCrewAI::Agent.new(name: 'manager', role: '...', goal: '...',
36
+ llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
37
+ ```
38
+
39
+ Omitting `llm:` keeps the global `RCrewAI.configure` behavior. Overrides never
40
+ mutate the global configuration.
41
+
42
+ ### 2b. Structured output, guardrails, and file output
43
+
44
+ Post-process a task's result after the agent produces it:
45
+
46
+ ```ruby
47
+ task = RCrewAI::Task.new(
48
+ name: 'extract', description: '...', agent: agent,
49
+
50
+ # Validate & coerce against a JSON schema. Non-conforming output re-runs the
51
+ # agent with the error fed back. Parsed object on task.structured_output.
52
+ output_schema: { type: 'object', properties: { title: { type: 'string' } },
53
+ required: ['title'] },
54
+
55
+ # Validate & transform. ->(output) { [ok, value_or_error] }. Retries up to
56
+ # guardrail_max_retries (default 3) with the reason fed back to the agent.
57
+ guardrail: ->(out) { [out.length < 5000, 'too long'] },
58
+
59
+ # Persist the result (parent dirs created unless create_directory: false).
60
+ output_file: 'out/report.md', markdown: true
61
+ )
62
+
63
+ task.execute
64
+ task.structured_output # => { "title" => "..." }
65
+ task.raw_result # => the unprocessed string the agent produced
66
+ ```
67
+
68
+ See `examples/structured_output_example.rb`.
69
+
70
+ ### 2c. Knowledge (RAG)
71
+
72
+ Ground agents in your own documents. Sources are chunked, embedded, and stored
73
+ in an in-memory cosine vector store; relevant chunks are injected into each
74
+ task's prompt automatically.
75
+
76
+ ```ruby
77
+ kb = RCrewAI::Knowledge::Base.new(sources: [
78
+ RCrewAI::Knowledge::StringSource.new('Refunds within 30 days.'),
79
+ RCrewAI::Knowledge::FileSource.new('docs/policy.txt'),
80
+ RCrewAI::Knowledge::PdfSource.new('handbook.pdf'),
81
+ RCrewAI::Knowledge::UrlSource.new('https://example.com/faq')
82
+ ])
83
+
84
+ # Agent-level (role-specific):
85
+ agent = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...', knowledge: kb)
86
+
87
+ # Or pass raw sources and let the agent build the base:
88
+ agent = RCrewAI::Agent.new(name: 'support', role: '...', goal: '...',
89
+ knowledge_sources: [RCrewAI::Knowledge::StringSource.new('...')])
90
+
91
+ # Crew-level knowledge is shared with every agent:
92
+ crew = RCrewAI::Crew.new('support', knowledge: kb)
93
+ ```
94
+
95
+ Embeddings default to OpenAI's `text-embedding-3-small`; pass a custom
96
+ `embedder:` (anything responding to `embed(texts)`) to swap the backend.
97
+ See `examples/knowledge_rag_example.rb`.
98
+
99
+ ### 2d. Planning
100
+
101
+ Have a planner pass draft a per-task plan before execution:
102
+
103
+ ```ruby
104
+ crew = RCrewAI::Crew.new('research_crew', planning: true)
105
+ # Optionally use a dedicated (stronger) planner model:
106
+ crew = RCrewAI::Crew.new('research_crew', planning: true,
107
+ planning_llm: { provider: :anthropic, model: 'claude-3-opus-20240229' })
108
+ ```
109
+
110
+ The plan is folded into each task's description. Planning is best-effort: a
111
+ planner error or unparseable output leaves tasks unchanged.
112
+
113
+ ### 2e. Training & testing
114
+
115
+ Iterate on a crew with feedback, or score repeated runs:
116
+
117
+ ```ruby
118
+ # Run N times, collect feedback after each run, persist to JSON.
119
+ crew.train(n_iterations: 3, filename: 'training.json')
120
+
121
+ # Provide feedback programmatically instead of prompting a human:
122
+ crew.train(n_iterations: 3, filename: 'training.json',
123
+ feedback: ->(iteration, result) { "run #{iteration}: #{result[:success_rate]}%" })
124
+
125
+ # Run N times and score each run (defaults to success_rate).
126
+ crew.test(n_iterations: 5)
127
+ # => { iterations: 5, scores: [...], average_score: 92.0 }
128
+ ```
129
+
130
+ See `examples/planning_and_training_example.rb`.
131
+
132
+ ### 2f. Flows
133
+
134
+ Flows are an event-driven workflow engine — the biggest addition in 0.4.
135
+ Subclass `RCrewAI::Flow` and wire methods with a class-level DSL:
136
+
137
+ ```ruby
138
+ class ArticleFlow < RCrewAI::Flow
139
+ start :outline
140
+ def outline
141
+ state.sections = %w[intro body conclusion]
142
+ state.sections.length
143
+ end
144
+
145
+ listen :outline
146
+ def draft(section_count)
147
+ state.words = section_count * 100
148
+ state.words
149
+ end
150
+
151
+ router :draft
152
+ def review(words)
153
+ words >= 250 ? :publish : :expand
154
+ end
155
+
156
+ listen :publish
157
+ def publish = state.status = 'published'
158
+
159
+ listen :expand
160
+ def expand = state.status = 'needs more work'
161
+ end
162
+
163
+ flow = ArticleFlow.new
164
+ flow.kickoff(inputs: { author: 'me' })
165
+ flow.state.status # => "published"
166
+ ```
167
+
168
+ - `start` / `listen` / `router` wire methods into a graph; a listener receives
169
+ its trigger's return value, and a router's return becomes a label listeners
170
+ fire on.
171
+ - Combine triggers with `and_(:a, :b)` (all) and `or_(:a, :b)` (any).
172
+ - **State** is a schemaless object with a UUID, seedable via `kickoff(inputs:)`.
173
+ - **Persistence**: pass `state_store:`
174
+ (`RCrewAI::Flow::FileStateStore.new(dir)` or your own `#save`/`#load`) and
175
+ call `flow.restore(id)` to resume.
176
+ - Invoke a `Crew` inside any step, or pause with `human_feedback('Approve?')`.
177
+
178
+ See `examples/flow_example.rb`.
179
+
180
+ ---
181
+
182
+ ## When to use Flows vs. Crews
183
+
184
+ - **Crew** — a team of agents collaborating on a set of tasks (sequential,
185
+ hierarchical, or async). Reach for a crew when the work is "have these agents
186
+ produce these outputs."
187
+ - **Flow** — explicit, branching orchestration with state. Reach for a flow
188
+ when you need conditional paths, joins, persistence/resumption, or you want to
189
+ coordinate multiple crews and plain Ruby steps.
190
+
191
+ They compose: a Flow step can kick off a Crew.