feat: add basic utility examples with agent persistence
Three practical examples showing core SDK capabilities: - **bug-fixer**: Find and fix bugs, remembers codebase patterns - **release-notes**: Generate release notes from git commits - **file-organizer**: Organize directories, learns preferences Each example uses agent persistence (getOrCreateAgent pattern) so the agent remembers context across sessions - the key Letta differentiator. 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com>
This commit is contained in:
107
examples/bug-fixer/cli.ts
Executable file
107
examples/bug-fixer/cli.ts
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Bug Fixer CLI
|
||||
*
|
||||
* A persistent agent that finds and fixes bugs in code.
|
||||
*
|
||||
* Usage:
|
||||
* bun cli.ts "description of the bug" # Fix a specific bug
|
||||
* bun cli.ts # Interactive mode
|
||||
* bun cli.ts --status # Show agent status
|
||||
* bun cli.ts --reset # Reset agent
|
||||
*/
|
||||
|
||||
import { parseArgs } from 'node:util';
|
||||
import {
|
||||
loadState,
|
||||
saveState,
|
||||
getOrCreateAgent,
|
||||
fixBug,
|
||||
interactiveMode,
|
||||
showStatus,
|
||||
reset,
|
||||
} from './fixer.js';
|
||||
|
||||
async function main() {
|
||||
const { values, positionals } = parseArgs({
|
||||
args: process.argv.slice(2),
|
||||
options: {
|
||||
status: { type: 'boolean', default: false },
|
||||
reset: { type: 'boolean', default: false },
|
||||
help: { type: 'boolean', short: 'h', default: false },
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (values.help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (values.reset) {
|
||||
await reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const state = await loadState();
|
||||
|
||||
if (values.status) {
|
||||
await showStatus(state);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create the agent
|
||||
const agent = await getOrCreateAgent(state);
|
||||
|
||||
// Save agent ID if new
|
||||
if (!state.agentId && agent.agentId) {
|
||||
state.agentId = agent.agentId;
|
||||
await saveState(state);
|
||||
console.log(`\x1b[90m[Agent: ${agent.agentId}]\x1b[0m`);
|
||||
console.log(`\x1b[90m[→ https://app.letta.com/agents/${agent.agentId}]\x1b[0m\n`);
|
||||
}
|
||||
|
||||
if (positionals.length > 0) {
|
||||
// Fix the specified bug
|
||||
const bugDescription = positionals.join(' ');
|
||||
await fixBug(agent, state, bugDescription);
|
||||
} else {
|
||||
// Interactive mode
|
||||
await interactiveMode(agent, state);
|
||||
}
|
||||
|
||||
agent.close();
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`
|
||||
🐛 Bug Fixer
|
||||
|
||||
A persistent agent that finds and fixes bugs. Remembers your codebase.
|
||||
|
||||
USAGE:
|
||||
bun cli.ts [bug description] Fix a specific bug
|
||||
bun cli.ts Interactive mode
|
||||
bun cli.ts --status Show agent status
|
||||
bun cli.ts --reset Reset agent (forget everything)
|
||||
bun cli.ts -h, --help Show this help
|
||||
|
||||
EXAMPLES:
|
||||
bun cli.ts "the tests in auth.test.ts are failing"
|
||||
bun cli.ts "TypeError on line 42 of utils.ts"
|
||||
bun cli.ts "npm run build shows an error about missing module"
|
||||
|
||||
HOW IT WORKS:
|
||||
1. Describe the bug (error message, failing test, unexpected behavior)
|
||||
2. The agent explores your codebase to understand the context
|
||||
3. It identifies the root cause and makes a fix
|
||||
4. It runs tests or commands to verify
|
||||
|
||||
PERSISTENCE:
|
||||
The agent remembers your codebase across sessions. The more you use it,
|
||||
the better it knows where things are and what patterns to look for.
|
||||
`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
255
examples/bug-fixer/fixer.ts
Normal file
255
examples/bug-fixer/fixer.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Bug Fixer Agent
|
||||
*
|
||||
* A persistent agent that finds and fixes bugs in code.
|
||||
* Remembers the codebase and past fixes across sessions.
|
||||
*/
|
||||
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { createSession, resumeSession, type Session } from '../../src/index.js';
|
||||
import { BugFixerState, DEFAULT_CONFIG } from './types.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const STATE_FILE = join(__dirname, 'state.json');
|
||||
|
||||
// ANSI colors
|
||||
const COLORS = {
|
||||
agent: '\x1b[36m', // Cyan
|
||||
system: '\x1b[90m', // Gray
|
||||
success: '\x1b[32m', // Green
|
||||
error: '\x1b[31m', // Red
|
||||
reset: '\x1b[0m',
|
||||
};
|
||||
|
||||
const SYSTEM_PROMPT = `You are a bug-fixing agent. Your job is to find and fix bugs in code.
|
||||
|
||||
## Your Capabilities
|
||||
You have access to tools for exploring and modifying code:
|
||||
- Read files to understand the codebase
|
||||
- Search for patterns with Grep
|
||||
- Find files with Glob
|
||||
- Edit files to fix bugs
|
||||
- Run commands with Bash (tests, linters, etc.)
|
||||
|
||||
## Your Approach
|
||||
1. **Understand the bug** - Read the error message or failing test carefully
|
||||
2. **Explore the codebase** - Find relevant files and understand the context
|
||||
3. **Identify the root cause** - Don't just fix symptoms, find the actual issue
|
||||
4. **Make minimal changes** - Fix the bug without unnecessary refactoring
|
||||
5. **Verify the fix** - Run tests or the command that was failing
|
||||
|
||||
## Memory
|
||||
You remember past sessions. Use this to:
|
||||
- Recall where things are in the codebase
|
||||
- Remember patterns that caused bugs before
|
||||
- Avoid repeating failed approaches
|
||||
|
||||
## Style
|
||||
- Be concise but thorough
|
||||
- Explain what you're doing and why
|
||||
- If you're unsure, say so and explain your reasoning`;
|
||||
|
||||
/**
|
||||
* Load state from disk
|
||||
*/
|
||||
export async function loadState(): Promise<BugFixerState> {
|
||||
if (existsSync(STATE_FILE)) {
|
||||
const data = await readFile(STATE_FILE, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
}
|
||||
return {
|
||||
agentId: null,
|
||||
fixCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save state to disk
|
||||
*/
|
||||
export async function saveState(state: BugFixerState): Promise<void> {
|
||||
await writeFile(STATE_FILE, JSON.stringify(state, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the bug fixer agent
|
||||
*/
|
||||
export async function getOrCreateAgent(state: BugFixerState): Promise<Session> {
|
||||
if (state.agentId) {
|
||||
console.log(`${COLORS.system}Resuming bug fixer agent...${COLORS.reset}`);
|
||||
return resumeSession(state.agentId, {
|
||||
model: DEFAULT_CONFIG.model,
|
||||
allowedTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`${COLORS.system}Creating new bug fixer agent...${COLORS.reset}`);
|
||||
const session = await createSession({
|
||||
model: DEFAULT_CONFIG.model,
|
||||
systemPrompt: SYSTEM_PROMPT,
|
||||
memory: [
|
||||
{
|
||||
label: 'codebase-knowledge',
|
||||
value: `# Codebase Knowledge
|
||||
|
||||
## Project Structure
|
||||
(Will be populated as I explore)
|
||||
|
||||
## Key Files
|
||||
(Important files I've discovered)
|
||||
|
||||
## Patterns
|
||||
(Common patterns in this codebase)`,
|
||||
description: 'What I know about this codebase',
|
||||
},
|
||||
{
|
||||
label: 'fix-history',
|
||||
value: `# Fix History
|
||||
|
||||
## Past Bugs
|
||||
(Bugs I've fixed before)
|
||||
|
||||
## Lessons Learned
|
||||
(What I've learned from past fixes)`,
|
||||
description: 'History of bugs fixed and lessons learned',
|
||||
},
|
||||
],
|
||||
allowedTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'],
|
||||
permissionMode: 'bypassPermissions',
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream output with color
|
||||
*/
|
||||
function createStreamPrinter(): (text: string) => void {
|
||||
return (text: string) => {
|
||||
process.stdout.write(`${COLORS.agent}${text}${COLORS.reset}`);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message and get response
|
||||
*/
|
||||
export async function chat(
|
||||
session: Session,
|
||||
message: string,
|
||||
onOutput?: (text: string) => void
|
||||
): Promise<string> {
|
||||
await session.send(message);
|
||||
|
||||
let response = '';
|
||||
const printer = onOutput || createStreamPrinter();
|
||||
|
||||
for await (const msg of session.receive()) {
|
||||
if (msg.type === 'assistant') {
|
||||
response += msg.content;
|
||||
printer(msg.content);
|
||||
} else if (msg.type === 'tool_call') {
|
||||
console.log(`\n${COLORS.system}[${msg.name}]${COLORS.reset}`);
|
||||
} else if (msg.type === 'tool_result') {
|
||||
console.log(`${COLORS.system}[done]${COLORS.reset}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix a bug
|
||||
*/
|
||||
export async function fixBug(
|
||||
session: Session,
|
||||
state: BugFixerState,
|
||||
description: string
|
||||
): Promise<void> {
|
||||
console.log(`\n${COLORS.system}Starting bug fix...${COLORS.reset}\n`);
|
||||
|
||||
const prompt = `Please fix this bug:
|
||||
|
||||
${description}
|
||||
|
||||
Steps:
|
||||
1. First, explore the codebase to understand the context
|
||||
2. Find the root cause of the bug
|
||||
3. Make the minimal fix needed
|
||||
4. Run any relevant tests to verify
|
||||
|
||||
Go ahead and fix it.`;
|
||||
|
||||
await chat(session, prompt, createStreamPrinter());
|
||||
|
||||
// Update fix count
|
||||
state.fixCount++;
|
||||
await saveState(state);
|
||||
|
||||
console.log(`\n\n${COLORS.success}Bug fix attempt complete.${COLORS.reset}`);
|
||||
console.log(`${COLORS.system}Total fixes attempted: ${state.fixCount}${COLORS.reset}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive mode - keep fixing bugs
|
||||
*/
|
||||
export async function interactiveMode(session: Session, state: BugFixerState): Promise<void> {
|
||||
const readline = await import('node:readline');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const askQuestion = (prompt: string): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(prompt, (answer) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
console.log(`${COLORS.system}Interactive mode. Describe bugs to fix, or type 'quit' to exit.${COLORS.reset}\n`);
|
||||
|
||||
while (true) {
|
||||
const input = await askQuestion(`${COLORS.agent}Describe the bug > ${COLORS.reset}`);
|
||||
|
||||
if (input.toLowerCase() === 'quit' || input.toLowerCase() === 'exit') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!input.trim()) continue;
|
||||
|
||||
await fixBug(session, state, input);
|
||||
}
|
||||
|
||||
rl.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show status
|
||||
*/
|
||||
export async function showStatus(state: BugFixerState): Promise<void> {
|
||||
console.log('\n🐛 Bug Fixer Status\n');
|
||||
console.log(`Agent: ${state.agentId || '(not created yet)'}`);
|
||||
if (state.agentId) {
|
||||
console.log(` → https://app.letta.com/agents/${state.agentId}`);
|
||||
}
|
||||
console.log(`Fixes attempted: ${state.fixCount}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset state
|
||||
*/
|
||||
export async function reset(): Promise<void> {
|
||||
if (existsSync(STATE_FILE)) {
|
||||
const fs = await import('node:fs/promises');
|
||||
await fs.unlink(STATE_FILE);
|
||||
}
|
||||
console.log('\n🗑️ Bug fixer reset. Agent forgotten.\n');
|
||||
}
|
||||
16
examples/bug-fixer/types.ts
Normal file
16
examples/bug-fixer/types.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Bug Fixer Types
|
||||
*/
|
||||
|
||||
export interface BugFixerState {
|
||||
agentId: string | null;
|
||||
fixCount: number;
|
||||
}
|
||||
|
||||
export interface BugFixerConfig {
|
||||
model: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG: BugFixerConfig = {
|
||||
model: 'sonnet',
|
||||
};
|
||||
Reference in New Issue
Block a user