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>
204 lines
4.4 KiB
TypeScript
204 lines
4.4 KiB
TypeScript
/**
|
|
* File Store Helper
|
|
*
|
|
* Utilities for reading/writing shared files between agents.
|
|
*/
|
|
|
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
import { existsSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
// Output directory for research artifacts
|
|
const OUTPUT_DIR = join(__dirname, '..', 'output');
|
|
|
|
/**
|
|
* Ensure output directory exists
|
|
*/
|
|
async function ensureOutputDir(): Promise<void> {
|
|
if (!existsSync(OUTPUT_DIR)) {
|
|
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write content to a file in the output directory
|
|
*/
|
|
export async function writeOutput(filename: string, content: string): Promise<string> {
|
|
await ensureOutputDir();
|
|
const filepath = join(OUTPUT_DIR, filename);
|
|
await writeFile(filepath, content, 'utf-8');
|
|
return filepath;
|
|
}
|
|
|
|
/**
|
|
* Read content from a file in the output directory
|
|
*/
|
|
export async function readOutput(filename: string): Promise<string> {
|
|
const filepath = join(OUTPUT_DIR, filename);
|
|
return await readFile(filepath, 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Check if a file exists in the output directory
|
|
*/
|
|
export function outputExists(filename: string): boolean {
|
|
return existsSync(join(OUTPUT_DIR, filename));
|
|
}
|
|
|
|
/**
|
|
* Get the full path for an output file
|
|
*/
|
|
export function getOutputPath(filename: string): string {
|
|
return join(OUTPUT_DIR, filename);
|
|
}
|
|
|
|
/**
|
|
* Standard filenames for workflow artifacts
|
|
*/
|
|
export const ARTIFACTS = {
|
|
// Research phase
|
|
findings: (taskId: string) => `${taskId}-findings.md`,
|
|
sourceEvaluations: (taskId: string) => `${taskId}-source-evals.json`,
|
|
|
|
// Analysis phase
|
|
analysis: (taskId: string) => `${taskId}-analysis.md`,
|
|
|
|
// Writing phase
|
|
report: (taskId: string) => `${taskId}-report.md`,
|
|
|
|
// Meta files
|
|
feedback: (taskId: string) => `${taskId}-feedback.json`,
|
|
reflections: (taskId: string) => `${taskId}-reflections.json`,
|
|
|
|
// Team state
|
|
teamState: 'team-state.json',
|
|
};
|
|
|
|
/**
|
|
* Load team state from disk (or return default)
|
|
*/
|
|
export async function loadTeamState(): Promise<{
|
|
agentIds: Record<string, string | null>;
|
|
sharedBlockIds: Record<string, string | null>;
|
|
completedTasks: number;
|
|
}> {
|
|
const filepath = join(OUTPUT_DIR, ARTIFACTS.teamState);
|
|
|
|
if (existsSync(filepath)) {
|
|
const content = await readFile(filepath, 'utf-8');
|
|
return JSON.parse(content);
|
|
}
|
|
|
|
return {
|
|
agentIds: {
|
|
coordinator: null,
|
|
researcher: null,
|
|
analyst: null,
|
|
writer: null,
|
|
},
|
|
sharedBlockIds: {
|
|
sources: null,
|
|
terminology: null,
|
|
pitfalls: null,
|
|
},
|
|
completedTasks: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Save team state to disk
|
|
*/
|
|
export async function saveTeamState(state: {
|
|
agentIds: Record<string, string | null>;
|
|
sharedBlockIds: Record<string, string | null>;
|
|
completedTasks: number;
|
|
}): Promise<void> {
|
|
await ensureOutputDir();
|
|
const filepath = join(OUTPUT_DIR, ARTIFACTS.teamState);
|
|
await writeFile(filepath, JSON.stringify(state, null, 2), 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Generate a findings template for the researcher
|
|
*/
|
|
export function generateFindingsTemplate(query: string, sourcesCount: number): string {
|
|
return `# Research Findings
|
|
|
|
## Query
|
|
${query}
|
|
|
|
## Sources Found
|
|
Target: ${sourcesCount} sources
|
|
|
|
---
|
|
|
|
<!-- Researcher: Fill in the sources below -->
|
|
|
|
## Source 1
|
|
**Title**:
|
|
**Authors**:
|
|
**Venue**:
|
|
**Year**:
|
|
**Citations**:
|
|
**Relevance Score**: /10
|
|
**Quality Score**: /10
|
|
|
|
**Summary**:
|
|
|
|
**Key Findings**:
|
|
|
|
---
|
|
|
|
<!-- Continue for additional sources -->
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Generate an analysis template
|
|
*/
|
|
export function generateAnalysisTemplate(query: string): string {
|
|
return `# Analysis
|
|
|
|
## Research Question
|
|
${query}
|
|
|
|
## Summary of Findings
|
|
<!-- Analyst: Synthesize the key findings here -->
|
|
|
|
## Key Themes
|
|
<!-- Identify 3-5 major themes across the sources -->
|
|
|
|
1.
|
|
2.
|
|
3.
|
|
|
|
## Patterns and Connections
|
|
<!-- What patterns emerge? How do findings relate? -->
|
|
|
|
## Gaps and Limitations
|
|
<!-- What questions remain unanswered? -->
|
|
|
|
## Recommendations
|
|
<!-- Based on the analysis, what are the key takeaways? -->
|
|
|
|
---
|
|
|
|
*Analysis generated by Research Team*
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Clear all outputs for a fresh start
|
|
*/
|
|
export async function clearOutputs(): Promise<void> {
|
|
const { rm } = await import('node:fs/promises');
|
|
if (existsSync(OUTPUT_DIR)) {
|
|
await rm(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
}
|