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:
Cameron Pfiffer
2026-01-27 15:51:27 -08:00
parent d5bbce6dec
commit 3b4811f035
9 changed files with 1137 additions and 0 deletions

107
examples/bug-fixer/cli.ts Executable file
View 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
View 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');
}

View 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',
};