Files
Cameron Pfiffer d5bbce6dec feat: add multi-agent demo examples
Three demo examples showcasing multi-agent orchestration:

- **economics-seminar**: Hostile faculty panel debates AI economist presenter
- **research-team**: Coordinator, Researcher, Analyst, Writer collaboration
- **dungeon-master**: Persistent DM that creates its own game system

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-27 15:13:19 -08:00

254 lines
7.4 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bun
/**
* Research Team CLI
*
* A multi-agent academic research system that improves over time.
* Demonstrates Letta's persistent memory capabilities.
*
* Usage:
* bun examples/research-team/cli.ts "your research query" [options]
*
* Options:
* --depth=quick|standard|comprehensive Set research depth (default: standard)
* --status Show team status
* --reset Reset team (start fresh)
* --feedback=taskId Provide feedback for a task
*/
import { parseArgs } from 'node:util';
import { createInterface } from 'node:readline';
import type { Depth, UserFeedback } from './types.js';
import { DEPTH_CONFIGS, formatDuration } from './types.js';
import {
runResearchWorkflow,
processFeedback,
getTeamStatus,
resetTeam
} from './agents/coordinator.js';
import { readOutput, getOutputPath, outputExists, ARTIFACTS } from './tools/file-store.js';
// LETTA_CLI_PATH is optional if `letta` is in PATH
async function main() {
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: {
depth: { type: 'string', default: 'standard' },
status: { type: 'boolean', default: false },
reset: { type: 'boolean', default: false },
feedback: { type: 'string' },
help: { type: 'boolean', short: 'h', default: false },
},
allowPositionals: true,
});
// Help
if (values.help) {
printHelp();
return;
}
// Status command
if (values.status) {
await showStatus();
return;
}
// Reset command
if (values.reset) {
await resetTeam();
console.log('✅ Team reset. All agents will be created fresh on next run.');
return;
}
// Feedback command
if (values.feedback) {
await collectFeedback(values.feedback);
return;
}
// Research query
const query = positionals.join(' ');
if (!query) {
console.error('❌ Error: No research query provided');
console.error(' Usage: bun cli.ts "your research query" [--depth=quick|standard|comprehensive]');
process.exit(1);
}
// Validate depth
const depth = values.depth as Depth;
if (!['quick', 'standard', 'comprehensive'].includes(depth)) {
console.error(`❌ Error: Invalid depth "${depth}"`);
console.error(' Valid options: quick, standard, comprehensive');
process.exit(1);
}
// Run research
await runResearch(query, depth);
}
function printHelp() {
console.log(`
🔬 Research Team CLI
A multi-agent academic research system with persistent memory.
Each agent learns and improves over time based on experience and feedback.
USAGE:
bun examples/research-team/cli.ts "your research query" [options]
OPTIONS:
--depth=LEVEL Set research depth (default: standard)
- quick: ~5 min, 3 sources, brief report
- standard: ~15 min, 6 sources, thorough report
- comprehensive: ~30 min, 10 sources, extensive report
--status Show team status (agent IDs, completed tasks)
--reset Reset team state (creates fresh agents next run)
--feedback=ID Provide feedback for a completed task
-h, --help Show this help message
EXAMPLES:
# Quick research on quantum computing
bun cli.ts "quantum error correction techniques" --depth=quick
# Standard research on AI
bun cli.ts "chain of thought prompting in large language models"
# Comprehensive climate research
bun cli.ts "carbon capture and storage technologies" --depth=comprehensive
# Check team status
bun cli.ts --status
# Provide feedback after a task
bun cli.ts --feedback=task-1234567890-abc123
THE TEAM:
📚 Researcher - Finds and evaluates academic sources
🔍 Analyst - Synthesizes findings, identifies patterns
✍️ Writer - Produces polished research reports
Each agent has persistent memory that improves with use!
`);
}
async function showStatus() {
const status = await getTeamStatus();
console.log('\n📊 Research Team Status\n');
console.log(`Initialized: ${status.initialized ? '✅ Yes' : '❌ No'}`);
console.log(`Completed Tasks: ${status.completedTasks}`);
console.log('\nAgent IDs:');
for (const [role, id] of Object.entries(status.agentIds)) {
const emoji = id ? '✅' : '⬜';
console.log(` ${emoji} ${role}: ${id || '(not created yet)'}`);
}
if (status.completedTasks > 0) {
console.log('\n💡 Tip: Agents have learned from past tasks!');
console.log(' Run another query to see improved results.');
}
console.log('');
}
async function runResearch(query: string, depth: Depth) {
const config = DEPTH_CONFIGS[depth];
console.log('\n' + '═'.repeat(60));
console.log('🔬 RESEARCH TEAM');
console.log('═'.repeat(60));
console.log(`\n📋 Query: "${query}"`);
console.log(`📊 Depth: ${depth} (est. ${config.estimatedMinutes} min)`);
console.log(`📚 Target Sources: ${config.sourcesCount}`);
console.log('\n' + '─'.repeat(60) + '\n');
const startTime = Date.now();
try {
const result = await runResearchWorkflow(query, depth, (phase, message) => {
// Progress is already logged by coordinator
});
if (result.success) {
console.log('\n' + '═'.repeat(60));
console.log('✅ RESEARCH COMPLETE');
console.log('═'.repeat(60));
console.log(`\n⏱ Duration: ${formatDuration(result.durationMs)}`);
console.log(`📄 Report: ${getOutputPath(result.reportPath)}`);
console.log(`🔖 Task ID: ${result.taskId}`);
// Show report preview
if (outputExists(result.reportPath)) {
const report = await readOutput(result.reportPath);
const preview = report.slice(0, 500);
console.log('\n📝 Report Preview:');
console.log('─'.repeat(40));
console.log(preview + (report.length > 500 ? '...' : ''));
console.log('─'.repeat(40));
}
console.log('\n💬 To provide feedback and help the team learn:');
console.log(` bun cli.ts --feedback=${result.taskId}`);
console.log('');
} else {
console.error('\n❌ Research failed. Check the logs above for details.');
}
} catch (error) {
console.error('\n❌ Error during research:', error);
process.exit(1);
}
}
async function collectFeedback(taskId: string) {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
const question = (prompt: string): Promise<string> => {
return new Promise(resolve => {
rl.question(prompt, resolve);
});
};
console.log('\n📝 Feedback for Task: ' + taskId);
console.log('─'.repeat(40));
// Rating
let rating = 0;
while (rating < 1 || rating > 5) {
const input = await question('Rate the research (1-5 stars): ');
rating = parseInt(input, 10);
if (isNaN(rating) || rating < 1 || rating > 5) {
console.log('Please enter a number between 1 and 5.');
rating = 0;
}
}
// Comment
const comment = await question('What could be better? (optional, press Enter to skip): ');
rl.close();
const feedback: UserFeedback = {
taskId,
rating,
comment: comment || undefined,
timestamp: new Date(),
};
console.log('\n📤 Submitting feedback to team...\n');
await processFeedback(taskId, feedback);
console.log('\n✅ Feedback recorded! The team will apply these lessons to future tasks.');
console.log('');
}
// Run main
main().catch(console.error);