Skip to content

Commit 915abb0

Browse files
authored
Merge pull request #3 from jetthoughts/vcr-failure-integration
✨ feat: collaboration substrate for RubyLLM multi-agent runs
2 parents f3ab0f6 + 9a4dcf2 commit 915abb0

48 files changed

Lines changed: 6462 additions & 863 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.commandcode/taste/taste.md

Whitespace-only changes.

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ jobs:
2828
- name: Run specs
2929
run: bundle exec rake spec
3030

31+
- name: Replay recorded workflow cassettes
32+
env:
33+
VCR_RECORD_MODE: none
34+
run: bundle exec rspec --tag live
35+
3136
- name: Run RuboCop
3237
run: bundle exec rubocop
3338

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,35 @@
1010
.bundle
1111
vendor/bundle
1212
Gemfile.lock
13+
14+
# Local agent-tooling state
15+
/.claude/
16+
/.claude-flow/
17+
/.junie/
18+
19+
# Local secrets
20+
.env
21+
.env.local
22+
.env.*.local
23+
24+
# Codex local configuration
25+
.codex/
26+
27+
# Local agent tooling (kept on disk, never packaged)
28+
/.agents/
29+
/.swarm/
30+
/.mcp.json
31+
/AGENTS.md
32+
/CLAUDE.md
33+
examples/blog/pipeline_trace.md
34+
.commandcode/
35+
36+
# Generated by the examples on every run; regenerate rather than track
37+
examples/*/trace.md
38+
examples/*/pipeline_trace.md
39+
examples/*/choice_trace.md
40+
examples/blog/output.md
41+
examples/blog/eval.json
42+
examples/code_review/review.md
43+
examples/decision_panel/decision.md
44+
examples/topic_analyst/plan.md

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Changelog
2+
3+
## 0.1.0 — experimental
4+
5+
First public release. The API is deliberately small but not yet stable: artifact ordering,
6+
error types, and trace serialization all changed shortly before this release. Pin an exact
7+
version, and read [docs/DECISIONS.md](docs/DECISIONS.md) for what the gem refuses to do and
8+
why — those refusals are the stable part.
9+
10+
### Added
11+
12+
- Named immutable artifact versions with `as:`/`from:` handoffs and the thin `Team#run` API.
13+
- Machine-readable traces: `Session#to_h`/`#to_json` with per-call and run-total best-known
14+
token usage; prompts and results export only with `include_content: true`.
15+
- Typed `BudgetExceededError < CollaborationError` for budget exhaustion.
16+
- `examples/code_review/` — parallel fan-out/fan-in with a VCR-replayed spec and a
17+
line comparison against the upstream plain-Ruby pattern.
18+
19+
### Fixed
20+
21+
- Artifact versions are reserved in submission order, so `artifact(name)` is deterministic
22+
when parallel work completes out of order.
23+
- Non-`StandardError` crashes finalize their call as `:failed` and re-raise instead of
24+
leaving it `:running` with a burned budget slot.
25+
- Fiber siblings settle before a crash propagates; thread joins no longer mask the first crash.
26+
- A coworker instance delegating back into its own call fails with a clear error instead of
27+
`deadlock; recursive locking`.
28+
- Duplicate coworkers in one `parallel` batch are rejected before reserving budget instead of
29+
silently dropping results.
30+
- `share_context: false` sessions no longer record handoff inputs the coworker never received.
31+
32+
### Changed
33+
34+
- `Run#step` omitted `from:` now hands over every completed artifact, matching `Session#ask`.
35+
- The published gem contains only `lib/`, README, CHANGELOG, and LICENSE.
36+
37+
### Foundation
38+
39+
- Coworker registry, `delegate_work`/`ask_question` tools, session call budgets,
40+
thread/fiber `parallel`, selected handoffs, and the Markdown trace.

Gemfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ source 'https://rubygems.org'
55
gemspec
66

77
group :development, :test do
8+
gem 'async', require: false
89
gem 'rake'
910
gem 'rspec', '~> 3.0'
1011
gem 'rubocop', require: false
12+
gem 'ruby_llm-mcp', '~> 1.0', require: false
13+
gem 'vcr'
14+
gem 'webmock', '~> 3.18'
15+
16+
gem 'ruby_llm-tribunal', '~> 0.1', require: false if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2')
1117
end

PITCH.md

Lines changed: 73 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,98 @@
1-
# PITCH: `ruby_llm-team` — the delegation primitive RubyLLM won't ship, as a gem
1+
# PITCH: `ruby_llm-team`
22

3-
One sentence: **a `RubyLLM::Team` is the one multi-agent primitive that plain Ruby cannot
4-
express on its own — letting the *model* choose which named coworker to route to at
5-
runtime — and it belongs in a small extension gem, not in RubyLLM core and not inside a
6-
CrewAI-style orchestration framework.**
3+
`ruby_llm-team` packages the repeated plumbing around named RubyLLM coworkers:
4+
delegation tools, exact result handoffs, bounded calls, concurrent review, and an
5+
inspectable collaboration record. It is a small extension to RubyLLM, not a process
6+
engine.
77

8-
This document grounds that claim in the rejection of PR #891, the maintainer's stated
9-
philosophy, the CrewAI model and its community pain, and RubyLLM's own issue history.
8+
## Why a separate gem
109

11-
---
10+
RubyLLM's Agentic Workflows guidance intentionally uses ordinary Ruby for sequencing,
11+
routing, parallel work, fan-in, and evaluator/reviser loops. The maintainer closed the
12+
original Team contribution to RubyLLM core for that reason: the demonstrated workflows
13+
did not justify another core abstraction.
1214

13-
## 1. The seed: why this gem exists
15+
That decision sets the boundary for this gem. Team must remove repeated integration code
16+
without taking workflow policy away from the application.
1417

15-
PR [#891](https://github.com/crmne/ruby_llm/pull/891) ("Add RubyLLM::Team for multi-agent
16-
collaboration") was closed by the maintainer. The rejection is the single most important
17-
fact about this gem, because it defines the *correct* scope:
18+
## What the CrewAI review actually shows
1819

19-
> "This does not belong in the library. The existing Agentic Workflows documentation
20-
> already shows multi-agent sequencing, routing, handoffs, parallel work, and fan-in
21-
> using plain Ruby and the existing Agent and Tool APIs. Those examples are clearer, use
22-
> less code, and do not require adding another abstraction or public API to RubyLLM."
23-
> [crmne, PR #891 comment](https://github.com/crmne/ruby_llm/pull/891#issuecomment-5435551502)
20+
CrewAI offers two related layers:
2421

25-
Read carefully. The maintainer did **not** say the idea is bad. He said:
22+
- **Crews** organize autonomous agents into sequential or hierarchical processes.
23+
- **Flows** give the application explicit, event-driven control over state, branches, and
24+
execution paths.
2625

27-
1. The **workflow patterns** (sequencing, routing, handoffs, parallel, fan-in) are already
28-
well served by plain Ruby + Agent + Tool. Adding a process engine would be worse.
29-
2. A new abstraction does not belong in **core**.
30-
3. If a concrete capability can't be expressed with existing APIs, open an issue first.
26+
CrewAI tasks can name an agent, expected output, prior task context, guardrails, and
27+
asynchronous execution. A later task that depends on asynchronous tasks forms a clear
28+
fan-out/fan-in boundary. These are useful collaboration mechanics, independent of
29+
CrewAI's larger framework.
3130

32-
`ruby_llm-team` answers all three. It adds **no process, no scheduler, no graph**. It is a
33-
thin tool-layer. And it is a **gem**, not a core addition — precisely the escape hatch the
34-
maintainer's "does not belong in the library" leaves open.
31+
The review does **not** support describing CrewAI as simply rigid or claiming that its
32+
framework decides every execution path. CrewAI itself recommends Crews for autonomous
33+
work, Flows for deterministic work, and a hybrid for applications needing both.
3534

36-
---
35+
## What Team adopts
3736

38-
## 2. The two ways, compared
37+
- Named specialists with explicit responsibilities.
38+
- Exact outputs from completed work as inputs to dependent work.
39+
- Concurrent execution for independent tasks, followed by a synchronization barrier.
40+
- Artifact-preserving reviewer handoffs with application-owned revision limits.
41+
- Visible call limits, errors, inputs, and results.
42+
- A choice between application-directed calls and model-directed delegation tools.
3943

40-
### The RubyLLM way (maintainer's stance, current docs)
44+
These mechanics map naturally to Ruby agents, tools, threads, and fibers. They do not
45+
require a second workflow language.
4146

42-
Orchestration is **ordinary Ruby**. `Agent` is a configured chat; `Tool` is a capability;
43-
the docs show sequential, routing, handoff, parallel, fan-in, and evaluator-optimizer as
44-
small plain-Ruby classes. Applications own task order, dependencies, persistence, and
45-
resume. `RubyLLM.workflow` only adds instrumentation correlation — it does not take over
46-
execution.
47+
## What Team deliberately leaves out
4748

48-
Strengths: total flexibility, nothing hidden, debuggable, idiomatic, durable (the loop is
49-
interruptible and resumable). Weakness: every team re-writes the same delegation boundary
50-
by hand.
49+
- `Crew` / `Task` / `Process` or graph DSLs.
50+
- YAML workflow definitions and generated project structure.
51+
- A built-in hierarchical manager or automatic planner.
52+
- Framework-owned state persistence, scheduling, deployment, or remote transport.
53+
- Built-in memory, knowledge stores, RAG, or MCP clients.
54+
- Hidden retry, model-selection, or concurrency policy.
55+
- Runtime quality claims based only on an LLM judge.
5156

52-
### The CrewAI way
57+
Those capabilities can be valuable, but RubyLLM, ordinary Ruby, and focused ecosystem
58+
gems already provide composition points for them. Adding them to Team would turn a small
59+
collaboration primitive into a competing agent platform.
5360

54-
`Crew` / `Agent` / `Task` / `Process` abstractions. Agents declare role/goal/backstory;
55-
tasks declare expected output; the crew runs a `sequential` or `hierarchical` process
56-
(hierarchical needs a manager LLM). Simple to explain, fast to prototype.
61+
## The product boundary
5762

58-
Strengths: approachable, opinionated, quick demos; CrewAI reports enterprise adoption and
59-
"14x less code" vs graph frameworks. Weakness: a fixed process model that is **not very
60-
flexible** — the exact tradeoff the user named.
63+
Team owns:
6164

62-
### The decisive evidence
65+
- a named coworker registry;
66+
- `delegate_work` and `ask_question` tools;
67+
- per-run collaboration state and exact handoffs;
68+
- immutable named artifacts, revision lineage, and a thin Run API;
69+
- atomic call budgets;
70+
- thread or fiber fan-out/fan-in;
71+
- normalized errors, results, and collaboration traces.
6372

64-
CrewAI's own engineering blog, after "2 billion agentic workflows", lands on the **RubyLLM
65-
position**, not the Crew abstraction:
73+
The application owns task dependencies, conditional policy, quality gates, revision and
74+
escalation limits, persistence, authorization, cancellation, and approvals. A model may
75+
choose coworkers through `session.tools`; explicit workflows may call `session.ask` and
76+
`session.parallel` directly.
6677

67-
> "Architecture choices compound fast... separating the predictable from the
68-
> unpredictable. Having deterministic workflows handling the structure, and agents
69-
> deployed strategically where judgment actually matters."
70-
>
71-
> "Many engineers regret graph-based architectures... too many abstraction layers stacked
72-
> on top of each other... when something breaks, the engineers dig through multiple
73-
> indirections just to try finding which prompt or tool caused it."
74-
> [Lessons From 2 Billion Agentic Workflows](https://blog.crewai.com/lessons-from-2-billion-agentic-workflows/)
78+
## Evaluation boundary
7579

76-
The community reports the same friction:
80+
Runtime validators protect production invariants such as Ruby syntax and real APIs.
81+
Tribunal is an optional test-time grader for relevance, faithfulness, hallucination, and
82+
regression evaluation. Its report is evidence about a saved artifact; it is not a Team
83+
coworker, runtime gate, or telemetry system.
7784

78-
- r/crewai: "Overwhelmed with limitations... outdated dependencies, slow performance."
79-
- r/AI_Agents: "it gets fragile and you lose fine-grained control" in production.
80-
- r/LangChain: "LangGraph and CrewAI are overcomplicating agents... So I abandoned these
81-
libraries, as a bonus dropped the necessity to use Python in production."
82-
- r/AI_Agents "Who's using CrewAI really?": few teams report production use.
85+
## Evidence standard
8386

84-
**Conclusion: do not ship a CrewAI-style `Crew`/`Task`/`Process` abstraction.** It would
85-
contradict the maintainer's philosophy, the docs, and CrewAI's own hard-won lessons. The
86-
gem's job is the *opposite*: give Ruby developers the one missing low-level primitive and
87-
let them keep orchestration in plain Ruby.
87+
Product decisions should rely on current primary documentation, source code, and observed
88+
behavior. Anonymous community complaints may suggest questions to investigate, but they
89+
are not sufficient evidence for permanent scope decisions.
8890

89-
---
91+
## Sources
9092

91-
## 3. Shared requests and pains (grounding)
92-
93-
These are the signals that a delegation primitive is genuinely wanted, from RubyLLM's own
94-
issue tracker and the broader community:
95-
96-
| Signal | Source | What it says |
97-
|---|---|---|
98-
| Multi-agent is wanted, but not as transport | [#670 A2A protocol](https://github.com/crmne/ruby_llm/issues/670) (declined, `not_planned`) | People want multi-agent; maintainer drew the line at external transport. **Local delegation is the acceptable scope.** |
99-
| Team idea itself | [#891](https://github.com/crmne/ruby_llm/pull/891) | Rejected on process/scope, not on value. The code was correct and fully tested (17 specs, 97.74% coverage). |
100-
| Long-running, resumable work | [#635 "Interrupting the agentic loop"](https://github.com/crmne/ruby_llm/issues/635) (completed) | Real pain: multi-step loops that must pause/resume across deploys. A team that composes with durable agents fits this. |
101-
| The "one more abstraction" fatigue | Maintainer's rejection; CrewAI blog; community threads | Nobody wants another rigid framework. The gem must stay a primitive, not a platform. |
102-
| Ruby landscape gap | langchain.rb (huge/complex), FlowNodes (minimalist) | No Ruby-idiomatic, provider-agnostic multi-agent delegation primitive exists. RubyLLM is the natural host ecosystem. |
103-
104-
The through-line: **Ruby developers want multi-agent capability without sacrificing
105-
control.** CrewAI-style frameworks sell the former and tax the latter. A small gem that
106-
sells the primitive and leaves control alone is the gap.
107-
108-
---
109-
110-
## 4. What the gem is, and is not
111-
112-
### Is
113-
114-
- `RubyLLM::Team` — a named coworker registry.
115-
- Two ordinary `RubyLLM::Tool`s the model calls at runtime: `delegate_work` and
116-
`ask_question`. The model, not the developer, picks the coworker.
117-
- Recoverable error contract (`{ error: ... }`), shared `context:`, attachment
118-
round-tripping, class-vs-instance lifecycle, snapshot concurrency safety.
119-
- Composes with the existing RubyLLM way: plain-Ruby workflow classes, durable agents,
120-
`RubyLLM.workflow` instrumentation.
121-
122-
### Is not
123-
124-
- Not a `Crew`/`Task`/`Process` engine. No sequential/hierarchical process, no scheduler,
125-
no executor, no graph.
126-
- Not an A2A transport. No host-boundary communication.
127-
- Not a replacement for the Agentic Workflows patterns — those stay in plain Ruby, per the
128-
maintainer.
129-
130-
The differentiator in one line: **Team is where the model decides; the workflow is where
131-
the developer decides.** CrewAI lets the framework decide both; plain Ruby leaves both to
132-
you; this gem takes only the part the model must own.
133-
134-
---
135-
136-
## 5. The pitch
137-
138-
> RubyLLM gives you one beautiful API for every provider, and says "orchestrate with
139-
> plain Ruby." That's right — until you want the *model* to route work to a named
140-
> specialist at runtime. Today every team hand-rolls that delegation boundary: a registry,
141-
> two tool classes, an error contract, attachment handling. `ruby_llm-team` is that
142-
> boundary, extracted, tested, and composable — so your orchestration stays plain Ruby and
143-
> the model's delegation stays first-class.
144-
>
145-
> - **For RubyLLM users:** one line of setup, two tools, no framework.
146-
> - **For the maintainer's philosophy:** no new abstraction in core, no process engine,
147-
> no transport — just a Tool boundary that plain Ruby couldn't express.
148-
> - **For the market:** the Ruby gap between "single agent" and "crew" — without the crew's
149-
> rigidity.
150-
151-
---
152-
153-
## 6. Sources
154-
155-
- Maintainer rejection: https://github.com/crmne/ruby_llm/pull/891#issuecomment-5435551502
156-
- RubyLLM Agentic Workflows docs: `docs/_advanced/agentic-workflows.md` (working tree)
157-
- CrewAI lessons: https://blog.crewai.com/lessons-from-2-billion-agentic-workflows/
158-
- RubyLLM issues: #670 (A2A, declined), #635 (interrupt loop), #891 (Team), #889 (JSON format)
159-
- Community: r/crewai, r/AI_Agents, r/LangChain threads (linked in section 2)
93+
- [RubyLLM Agentic Workflows](https://rubyllm.com/agentic-workflows/)
94+
- [RubyLLM Team PR discussion](https://github.com/crmne/ruby_llm/pull/891)
95+
- [CrewAI introduction: Crews and Flows](https://docs.crewai.com/core-concepts/Agents)
96+
- [CrewAI tasks and asynchronous context](https://docs.crewai.com/en/concepts/tasks)
97+
- [CrewAI source](https://github.com/crewAIInc/crewAI)
98+
- [RubyLLM ecosystem](https://rubyllm.com/ecosystem/)

0 commit comments

Comments
 (0)