Building a Crew
This is the end-to-end process for going from an idea to a running crew.
1. Define the goal
Write one sentence: "This crew takes X and produces Y."
Examples:
- "This crew takes a GovWin search URL and produces a shortlist of relevant contracts with draft proposals."
- "This crew takes a feature description and produces working, tested code committed to a repo."
- "This crew takes a company name and produces a competitive analysis report in Obsidian."
If you can't write that sentence, the goal is too vague. Break it down first.
2. List the steps a human expert would take
Ignore AI for a moment. If you hired three smart people to do this, what would each person do? Write the steps:
- Search GovWin for relevant contracts
- Filter to ones that match Stilo's capabilities
- Read each contract and extract key requirements
- Write a draft proposal for each
- Review proposals against the requirements
These steps become your tasks. The person doing each step becomes your agent.
3. Create the project
bash /srv/shared/projects/new-project.sh research-crew my-crew-name
cd ~/projects/my-crew-name
source .venv/bin/activate4. Define your agents
Edit src/research_crew/agents.py. Add one function per agent:
def contract_scout() -> Agent:
return Agent(
role="Government Contract Scout",
goal="Find federal contracts on GovWin that match Stilo's capabilities in IT and engineering services.",
backstory="You are an expert at reading government procurement databases. You know how to identify high-probability bids from the noise.",
tools=[WebScrapeTool(), DuckDuckGoSearchTool(), ObsidianWriteTool()],
llm=_llm("SCOUT_MODEL"),
verbose=True,
)
def proposal_writer() -> Agent:
return Agent(
role="Proposal Writer",
goal="Write compelling, compliant government contract proposals that win bids.",
backstory="You have written dozens of winning government proposals. You understand FAR compliance, evaluation criteria, and how to tell a compelling technical story.",
tools=[ObsidianReadTool(), ObsidianWriteTool()],
llm=_llm("WRITER_MODEL"),
verbose=True,
)Add the model env vars to .env:
SCOUT_MODEL=qwen3-general
WRITER_MODEL=claude-sonnet5. Define your tasks
Edit src/research_crew/tasks.py. One function per task:
def scout_task(search_url: str) -> Task:
return Task(
description=f"""
Go to {search_url} and find government contracts relevant to Stilo Solutions.
Stilo provides IT services, software development, and engineering solutions.
For each relevant contract extract:
- Contract title and number
- Agency
- Due date
- Value estimate
- Key requirements summary
Save your findings to: contracts/scouted.md in the Obsidian vault.
""",
expected_output="A structured list of 3-10 relevant contracts saved to Obsidian.",
agent=contract_scout(),
)
def proposal_task() -> Task:
return Task(
description="""
Read the scouted contracts from contracts/scouted.md in the Obsidian vault.
For the top 3 contracts by fit and value, write a draft proposal for each.
Each proposal should include:
- Executive Summary
- Technical Approach
- Past Performance references
- Staffing Plan
Save each proposal as contracts/proposal-<contract-number>.md in Obsidian.
""",
expected_output="Three draft proposals saved to Obsidian, one per contract.",
agent=proposal_writer(),
context=[scout_task], # receives scout output automatically
)6. Assemble the crew
Edit src/research_crew/crew.py:
def build_crew(search_url: str) -> Crew:
scout = agents.contract_scout()
writer = agents.proposal_writer()
return Crew(
agents=[scout, writer],
tasks=[
tasks.scout_task(search_url),
tasks.proposal_task(),
],
process=Process.sequential,
verbose=True,
)7. Wire up the entry point
Edit src/research_crew/main.py:
def run():
search_url = sys.argv[1] if len(sys.argv) > 1 else "https://govwin.com/..."
crew = build_crew(search_url)
crew.kickoff()8. Run it
PYTHONPATH=src python -m research_crew.main "https://govwin.com/search?q=IT+services"Watch the agents work in the terminal. Results appear in your Obsidian vault and sync to your desktop within seconds.
9. Iterate
Crews rarely work perfectly on the first run. Common improvements:
- Agent keeps hallucinating → add more detail to the backstory or task description
- Agent ignores a tool → add a sentence like "Use the WebScrapeTool to fetch the page" in the task description
- Output is too short → be explicit in
expected_output: "a minimum of 500 words" - Wrong model for the job → swap to
claude-sonnetfor tasks requiring judgment - Agent gets stuck in a loop → set
max_iter=5on the Agent
Writing a good task description
The task description is the most important thing you write. Rules:
- Be specific about inputs — tell the agent exactly where to look
- Be specific about outputs — tell the agent exactly what to produce and where to save it
- Break it down — if a task has 4 steps, number them explicitly
- Don't explain how to think — describe the goal, not the reasoning process
❌ Bad:
Research the government contracts market and write something about it.✅ Good:
Go to {url} and extract all contracts tagged with NAICS code 541512.
For each contract, record: title, agency, due date, estimated value, and a
one-paragraph summary of the statement of work.
Save results as a markdown table to contracts/raw-{today}.md in Obsidian.