Run a recursive multi-agent research pipeline that searches the web, evaluates sources, extracts learnings, and generates a structured markdown report.
- Bun installed
- Agentuity CLI installed and logged in (
agentuity login)
You need an Exa API key to run web searches.
bun install
EXA_API_KEY=your_key_here bun run devOpen localhost:3500 for the frontend, or localhost:3500/workbench to call the orchestrator directly.
Four agents collaborate to answer a research query:
orchestrator: entry point. Validatesdepth,breadth, andmaxResultsparameters, then delegates toresearcherandauthorin sequence.researcher: drives the research loop. Generates search queries with Claude, callsweb-searchfor each query, extracts learnings, and recurses with reduced depth until the result limit is reached.web-search: executes a single Exa search, deduplicates URLs against accumulated sources, and uses Claude to evaluate each result asrelevantorirrelevant.author: takes the full research object and generates a markdown report withgenerateText.
The recursive loop inside researcher halves the breadth at each depth level, using follow-up questions extracted from previous learnings to refine the prompt:
async function deepResearch(
prompt: string,
currentDepth: number,
currentBreadth: number,
): Promise<void> {
if (currentDepth === 0 || research.searchResults.length >= inputs.maxResults) {
return;
}
const queries = await generateSearchQueries(prompt, currentBreadth);
for (const query of queries) {
const webSearchResult = await webSearchAgent.run({
query,
accumulatedSources: research.searchResults,
});
research.searchResults.push(...webSearchResult.searchResults);
research.completedQueries.push(query);
for (const result of webSearchResult.searchResults) {
const learning = await extractLearning(query, result);
research.learnings.push(learning);
}
if (research.searchResults.length >= inputs.maxResults) return;
}
// Recurse: reduce depth by 1, halve breadth, refine with follow-up questions
const newBreadth = Math.ceil(currentBreadth / 2);
if (currentDepth > 1 && newBreadth > 0) {
const refinedPrompt =
research.learnings.length > 0
? `${prompt}. Follow-up: ${research.learnings.at(-1)!.followUpQuestions.join(', ')}`
: prompt;
await deepResearch(refinedPrompt, currentDepth - 1, newBreadth);
}
}Default parameters: depth=2, breadth=3, maxResults=20. Both depth and breadth are capped at 5.
src/
├── agent/
│ ├── orchestrator/
│ │ ├── agent.ts # Parameter validation, sequences researcher → author
│ │ └── index.ts
│ ├── researcher/
│ │ ├── agent.ts # Recursive research loop, query generation, learning extraction
│ │ └── index.ts
│ ├── web-search/
│ │ ├── agent.ts # Exa search, duplicate detection, LLM relevance evaluation
│ │ └── index.ts
│ └── author/
│ ├── agent.ts # Markdown report generation from research data
│ └── index.ts
├── api/index.ts # POST routes for each agent
├── lib/
│ ├── exa.ts # Exa client wrapper (searchAndContents with livecrawl)
│ ├── prompts.ts # System, evaluation, and author prompt templates
│ └── types.ts # Shared types: Research, SearchResult, Learning
└── web/
├── App.tsx # React frontend
└── ...