Creating New Agents
Create specialized agents from scratch using TypeScript files in the .agents/ directory.
Types:
- LLM-based - Use prompts and language models
- Programmatic - Use TypeScript generator functions with
handleSteps
Control Flow:
yield 'STEP'- Run one LLM generation stepyield 'STEP_ALL'- Run until completionreturn- End the agent's turn
Accessing Context:
agentState- Current agent state and message historyprompt- User's prompt to the agentparams- Additional parameters passed to the agent
Basic Structure
Create a new TypeScript file in .agents/ directory. This example uses the researcher as a spawnable subagent:
.agents/my-custom-agent.ts
import { AgentDefinition } from './types/agent-definition'const definition: AgentDefinition = {id: 'my-custom-agent',version: '1.0.0',displayName: 'My Custom Agent',spawnerPrompt:'Spawn this agent for specialized workflow tasks requiring custom logic',model: 'anthropic/claude-sonnet-4.5',outputMode: 'last_message',includeMessageHistory: true,toolNames: ['read_files', 'write_file', 'end_turn'],spawnableAgents: ['codebuff/researcher@0.0.1'], // Use full name for built-in agentsinputSchema: {prompt: {type: 'string',description: 'What documentation to create or update',},},systemPrompt: `You are a documentation specialist.`,instructionsPrompt:"Write documentation based on the user's request. Research existing code and patterns first.",stepPrompt:'Continue working on the documentation. Use end_turn when complete.',}export default definition
Domain-Specific Examples
API Documentation Agent
Writes REST and GraphQL docs with examples. Uses the researcher for documentation lookup:
.agents/api-documenter.ts
import { AgentDefinition } from './types/agent-definition'const definition: AgentDefinition = {id: 'api-documenter',version: '1.0.0',displayName: 'API Documentation Specialist',spawnerPrompt:'Spawn this agent to write API documentation with examples, schemas, and error codes',model: 'anthropic/claude-sonnet-4.5',outputMode: 'last_message',includeMessageHistory: true,toolNames: ['read_files','code_search','write_file','spawn_agents','end_turn',],spawnableAgents: ['codebuff/researcher@0.0.1'], // Use full name for built-in agentsinputSchema: {prompt: {type: 'string',description: 'What API endpoints or schemas to document',},},systemPrompt:'You are an API documentation specialist. Write clear documentation for REST APIs and GraphQL schemas with examples, request/response formats, and error codes.',instructionsPrompt:'Analyze the specified API endpoints and create documentation including examples, parameters, and response schemas.',stepPrompt:'Continue documenting the API. Include practical examples and edge cases. Use end_turn when complete.',}export default definition
Database Migration Agent
Builds and reviews database migrations. Uses the reviewer for checking migrations:
.agents/migration-specialist.ts
import { AgentDefinition } from './types/agent-definition'const definition: AgentDefinition = {id: 'migration-specialist',version: '1.0.0',displayName: 'Database Migration Specialist',spawnerPrompt:'Spawn this agent to create reversible database migrations with proper indexing and rollback procedures',model: 'anthropic/claude-sonnet-4.5',outputMode: 'last_message',includeMessageHistory: true,toolNames: ['read_files','write_file','code_search','run_terminal_command','end_turn',],spawnableAgents: ['codebuff/reviewer@0.0.1'],systemPrompt:'You are a database migration specialist. Create reversible database migrations with proper indexing and rollback procedures.',instructionsPrompt:"Create a database migration for the requested schema changes. Ensure it's reversible and includes proper indexing.",stepPrompt:'Continue working on the migration. Test it if possible and spawn a reviewer to check for issues.',}export default definition
Programmatic Agents (Advanced)
LLM-based agents cover many tasks. Programmatic agents add precise control over complex workflows while still letting you tap into LLMs when you want them.
Why Use Programmatic Agents?
- Deterministic workflows - Guarantee specific steps happen in order
- Dynamic decision making - Branch based on your own logic
- Complex orchestration - Coordinate multiple agents and tools with logic
- State management - Maintain state across multiple agent steps
How It Works
Use TypeScript generator functions with the handleSteps field to control execution. This example spawns thinker and reviewer subagents:
.agents/code-analyzer.ts
import { AgentDefinition } from './types/agent-definition'const definition: AgentDefinition = {id: 'code-analyzer',displayName: 'Code Analysis Expert',spawnerPrompt: 'Spawn for deep code analysis and refactoring suggestions',model: 'anthropic/claude-sonnet-4.5',toolNames: ['read_files', 'code_search', 'spawn_agents', 'write_file'],spawnableAgents: ['codebuff/thinker@0.0.1', 'codebuff/reviewer@0.0.1'],handleSteps: function* ({ agentState, prompt, params }) {// First, find relevant filesconst { toolResult: files } = yield {toolName: 'find_files',input: { query: prompt },}// Read the most important filesif (files) {const filePaths = JSON.parse(files).slice(0, 5)yield {toolName: 'read_files',input: { paths: filePaths },}}// Spawn a thinker for deep analysisyield {toolName: 'spawn_agents',input: {agents: [{agent_type: 'thinker',prompt: `Analyze the code structure and suggest improvements for: ${prompt}`,},],},}// Let the agent generate its responseyield 'STEP_ALL'},}export default definition
Key Concepts for Programmatic Agents
1. Generator Function Basics
Your handleSteps function receives context and yields actions:
handleSteps: function* ({ agentState, prompt, params }) {// agentState: Current conversation and agent state// prompt: What the user asked this agent to do// params: Additional parameters passed to the agent// Your logic here...}
2. Yielding Tool Calls
Execute tools and get their results:
const { toolResult, toolError } = yield {toolName: 'read_files',input: { paths: ['file1.ts', 'file2.ts'] }}if (toolError) {// Handle error caseconsole.error('Failed to read files:', toolError)} else {// Use the resultconst fileContent = JSON.parse(toolResult)}
3. Control Flow Options
Control Flow:
yield 'STEP'- Run one LLM generation stepyield 'STEP_ALL'- Run until completionreturn- End the agent's turn
4. Advanced Example: Conditional Workflow
handleSteps: function* ({ agentState, prompt, params }) {// Step 1: Analyze the codebaseconst { toolResult: analysis } = yield {toolName: 'spawn_agents',input: {agents: [{agent_type: 'thinker',prompt: `Analyze: ${prompt}`}]}}// Step 2: Based on analysis, choose actionif (analysis?.includes('refactor')) {// Get all files that need refactoringconst { toolResult: files } = yield {toolName: 'find_files',input: { query: 'needs refactoring' }}// Step 3: Refactor each filefor (const file of JSON.parse(files || '[]')) {yield {toolName: 'write_file',input: {path: file,instructions: 'Refactor for better performance',content: '// ... refactored code ...'}}}}// Step 4: Final reviewyield {toolName: 'spawn_agents',input: {agents: [{agent_type: 'reviewer',prompt: 'Review all changes'}]}}// Let the agent summarizeyield 'STEP_ALL'}
When to Choose Programmatic vs LLM-based
Use Programmatic (handleSteps) when:
- You need guaranteed execution order
- Decisions depend on specific file contents
- Complex multi-step workflows with branching
- Integration with external systems
- Error recovery is critical
Use LLM-based (prompts only) when:
- Task is straightforward
- Agent needs creative freedom
- Natural language understanding is key
- Workflow is simple and linear