Skip to content

Chapter 18 · Multi-Crew Orchestration and Reuse ​

Chapter goals: Master the composition pattern of串 multiple Crews in a Flow, learn to pass artifacts between Crews via shared state, split crews by business domain, and design a reusable enterprise-grade project structure.

18.1 When You Need Multiple Crews ​

Agents within a single Crew share the same task chain and context. Split when your business shows these characteristics:

  • Different phase nature: research (exploratory, many tools) and writing (pure generation) mixed in one Crew makes it impossible to tune max_iter, model tier, and temperature separately;
  • Reuse demand: the same "research Crew" needs to be shared by weekly reports, monthly reports, and competitive analysis pipelines;
  • Failure isolation: when one step fails, you only want to re-run that phase, not the entire chain.

Splitting principle: one Crew = one cohesive business capability (research capability, writing capability, review capability). The Flow is responsible for串 them into an end-to-end process. This is exactly the design intent of the official crewai create flow project — a crews/ directory is naturally present in flow projects.

18.2 Composition Pattern 1: Flow Methods Calling Multiple Crews Directly ​

The most direct orchestration: each Flow step instantiates a Crew and kickoffs, writing the artifact into state. Following the scaffold shape from Chapter 17:

python
# src/my_pipeline/main.py — serial串 of research Crew + writing Crew
import os
from pydantic import BaseModel
from crewai.flow.flow import Flow, listen, start
from my_pipeline.crews.research_crew.research_crew import ResearchCrew
from my_pipeline.crews.writing_crew.writing_crew import WritingCrew

class PipelineState(BaseModel):
    topic: str = ""
    research: str = ""      # Research artifact
    article: str = ""       # Writing artifact

class ContentPipeline(Flow[PipelineState]):

    @start()
    def receive_topic(self):
        self.state.topic = "AI Agent Framework Comparison"

    @listen(receive_topic)
    def run_research(self):
        # Phase 1: Research Crew (exploratory: many tools, higher iteration cap)
        out = ResearchCrew().crew().kickoff(
            inputs={"topic": self.state.topic}
        )
        self.state.research = out.raw       # Artifact enters state for downstream
        return out.raw

    @listen(run_research)
    def run_writing(self):
        # Phase 2: Writing Crew (generative: no search tools, slightly higher temperature)
        out = WritingCrew().crew().kickoff(
            inputs={"research": self.state.research}
        )
        self.state.article = out.raw
        return out.raw

if __name__ == "__main__":
    print(ContentPipeline().kickoff())

Three engineering points:

  • Each Crew's inputs keys must match its own YAML placeholders — explicit interface contracts are the key to maintainability;
  • Only put cross-phase "formal artifacts" in state; intermediate drafts stay in each Crew's task outputs;
  • If phase 2 fails and only needs re-running, combine with Chapter 16's persistence to recover from a snapshot before run_writing, without redoing research.

18.3 Composition Pattern 2: Light Steps + Heavy Crew Hybrid Pipeline ​

Not every step deserves a full Crew. Mature projects typically follow "lightweight LLM/single-agent step up front, heavy Crew handles core work":

python
# hybrid_flow.py — light-heavy hybrid的典型布局
from pydantic import BaseModel
from crewai.flow.flow import Flow, listen, router, start

class TriageState(BaseModel):
    request: str = ""
    category: str = ""
    answer: str = ""

class SupportPipeline(Flow[TriageState]):

    @start()
    def triage(self):
        """Triage: a cheap light step decides which pipeline to take"""
        text = self.state.request.lower()
        self.state.category = (
            "billing" if "invoice" in text or "bill" in text else "tech"
        )

    @router(triage)
    def route(self):
        return self.state.category          # "billing" or "tech"

    @listen("billing")
    def billing_flow(self):
        from my_pipeline.crews.billing_crew.billing_crew import BillingCrew
        out = BillingCrew().crew().kickoff(
            inputs={"question": self.state.request})
        self.state.answer = out.raw
        return out.raw

    @listen("tech")
    def tech_flow(self):
        from my_pipeline.crews.tech_crew.tech_crew import TechCrew
        out = TechCrew().crew().kickoff(
            inputs={"question": self.state.request})
        self.state.answer = out.raw
        return out.raw

This "triage → route → specialized Crew" structure is the standard solution for customer-support ticket systems: triage uses rules or a single LLM call (fast and cheap), heavy lifting is delegated to domain Crews.

18.4 Organizing crews Packages by Business Domain ​

The scaffold-generated flow project directory naturally supports flat multi-Crew layout:

text
my_pipeline/
├── pyproject.toml
└── src/my_pipeline/
    ├── main.py                  # Flow orchestration layer
    ├── crews/
    │   ├── research_crew/
    │   │   ├── config/
    │   │   │   ├── agents.yaml
    │   │   │   └── tasks.yaml
    │   │   └── research_crew.py
    │   ├── writing_crew/
    │   │   ├── config/*.yaml
    │   │   └── writing_crew.py
    │   └── review_crew/
    │       └── ...
    └── tools/
        └── custom_tool.py       # Custom tool shared across Crews

Four structural discipline rules for enterprise projects:

  1. Each crew is self-contained: config YAML + definition file complete, independently testable (ResearchCrew().crew().kickoff(...) runs standalone);
  2. Tools sink to bottom: tools shared across multiple Crews go in the top-level tools/包; prohibit cross-importing each other's private modules;
  3. LLM config centralized: base_url/api_key/model tier converged into one llm_config.py; avoid scattered hardcoding;
  4. Contracts are documentation: each crew's README写明 the inputs keys it needs and the output structure it produces.

The new scaffold also supports JSON-first inline Crews (crews/research_crew/crew.jsonc + agents/researcher.jsonc), loaded in Flow steps using the official loader:

python
from pathlib import Path
from crewai.project import load_crew

# Standard loading for JSON-first inline Crews
crew, default_inputs = load_crew(
    Path(__file__).parent / "crews" / "research_crew" / "crew.jsonc"
)
result = crew.kickoff(inputs={**default_inputs, "topic": "AI Agents"})

18.5 Reuse and Config Sharing ​

With multiple Crews, duplicated config becomes an immediate problem. Two practical techniques:

python
# llm_config.py — centralized model tier management
import os
from crewai import LLM

def fast_llm():     # Light tasks: triage, summarization
    return LLM(model="openai/deepseek-chat",
               base_url="https://api.deepseek.com/v1",
               api_key=os.getenv("DEEPSEEK_API_KEY"),
               temperature=0.2)

def deep_llm():     # Heavy tasks: research, analysis
    return LLM(model="openai/deepseek-chat",
               base_url="https://api.deepseek.com/v1",
               api_key=os.getenv("DEEPSEEK_API_KEY"),
               temperature=0.7,
               max_tokens=4096)
python
# Referenced uniformly in each crew.py; tier changes in one place
from my_pipeline.llm_config import deep_llm

@agent
def researcher(self) -> Agent:
    return Agent(config=self.agents_config["researcher"], llm=deep_llm())

Another dimension is artifact schema reuse: define data structures passed between Crews as a shared Pydantic model (e.g., ResearchReport); the upstream uses output_pydantic to produce, the downstream declares consumption per this structure in its description — giving the interface compile-time check capability.

18.6 End-to-End Example: Three-Crew Content Factory ​

Gathering all patterns from this chapter into one complete skeleton (YAML details omitted, focus on orchestration):

python
# factory.py — 三段式 content factory: research → write → review
from pydantic import BaseModel
from crewai.flow.flow import Flow, listen, router, start
from my_pipeline.llm_config import fast_llm
from my_pipeline.crews.research_crew.research_crew import ResearchCrew
from my_pipeline.crews.writing_crew.writing_crew import WritingCrew
from my_pipeline.crews.review_crew.review_crew import ReviewCrew

class FactoryState(BaseModel):
    topic: str = ""
    draft: str = ""
    verdict: str = ""
    final: str = ""

class ContentFactory(Flow[FactoryState]):

    @start()
    def intake(self):
        self.state.topic = "Vector Database Selection Guide"

    @listen(intake)
    def stage_research(self):
        out = ResearchCrew().crew().kickoff(inputs={"topic": self.state.topic})
        self.state.draft = out.raw

    @listen(stage_research)
    def stage_write(self):
        out = WritingCrew().crew().kickoff(inputs={"research": self.state.draft})
        self.state.draft = out.raw

    @listen(stage_write)
    def stage_review(self):
        out = ReviewCrew().crew().kickoff(inputs={"draft": self.state.draft})
        self.state.verdict = out.raw      # Convention: output "PASS" or revision notes

    @router(stage_review)
    def gate(self):
        return "publish" if "PASS" in self.state.verdict else "revise"

    @listen("publish")
    def done(self):
        self.state.final = self.state.draft
        return f"Published: '{self.state.topic}'"

    @listen("revise")
    def back_to_writer(self):
        # Review failed: carry feedback back to writing phase (simplified retry loop)
        out = WritingCrew().crew().kickoff(inputs={
            "research": self.state.draft,
            "feedback": self.state.verdict,
        })
        self.state.final = out.raw
        return "Revise and publish"

This example covers all elements of multi-Crew orchestration: phase splitting, state relay, quality-gate routing, and feedback-driven retry loops. Chapter 23's capstone will extend this into a runnable project.

18.7 Chapter Summary ​

  • Split signals: different phase nature, reuse needs, failure isolation; one Crew = one cohesive business capability;
  • Composition patterns: Flow steps串-multiply Crews sequentially; light-heavy hybrid uses lightweight triage + @router to funnel into specialized Crews;
  • Directory discipline: self-contained under crews/, tools sink to tools/, LLM config centralized, inputs/outputs contracts documented;
  • JSON-first inline Crews loaded via load_crew(); classic YAML Crews instantiated and called directly;
  • Quality gates (review → router → rewrite with feedback) are the standard fault-tolerance mechanism for multi-Crew pipelines.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Which is NOT a core reason to split "research" and "writing" into two Crews?

2. When串 two Crews in a Flow, what is the standard way to pass upstream artifacts to downstream?

3. Regarding crews package organization discipline, which is correct?

4. For "review Crew outputs PASS or revision notes, retry with feedback on failure", what is the correct skeleton?

🛠️ Hands-On Exercises ​

  1. Complete the content factory from this chapter into a runnable project: scaffold three Crews with --classic, write their agents.yaml/tasks.yaml, and a shared llm_config.py.
  2. Set different model parameters for the research Crew and writing Crew (low-temp low-iteration vs high-temp high-output); use usage_metrics to record token distribution per phase.
  3. Add a third route to the content factory: if review fails twice consecutively, route to a "manual handling" branch (hint: add retry_count to state, check in gate).

Multi-Crew orchestration is now systematic. Next, integrate external capability ecosystems: Chapter 19 · MCP Integration.