# handleSteps Generator Function in Codebuff: A Complete Guide to Programmatic Agent Control

> Master the handleSteps generator function in Codebuff for programmatic agent control. Script tool calls, commands, and directives for precise LLM execution.

- Repository: [Codebuff/codebuff](https://github.com/CodebuffAI/codebuff)
- Tags: deep-dive
- Published: 2026-03-08

---

**The `handleSteps` generator function in Codebuff lets you script agent execution by yielding tool calls, step commands, and special directives, giving you precise control over when the LLM runs and when tools are invoked.**

The `handleSteps` generator function is an optional but powerful feature in the Codebuff repository that transforms how agents execute tasks. By adding this function to your agent definition, you replace the default LLM-driven loop with a programmatic script that yields specific instructions to the runtime. This approach is implemented in [`packages/agent-runtime/src/run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/run-programmatic-step.ts) and defined in [`agents/types/agent-definition.ts`](https://github.com/CodebuffAI/codebuff/blob/main/agents/types/agent-definition.ts), giving developers fine-grained control over tool execution, LLM stepping, and output generation.

## What is the handleSteps Generator Function?

`handleSteps` is a **generator function** (using the `function*` syntax) that you attach to an `AgentDefinition` object. When Codebuff spawns your agent, the programmatic step executor (`runProgrammaticStep`) creates a generator instance from this function and drives it step-by-step. Each time the generator yields a value, the runtime interprets that value as a command, executes it, and sends the result back to the generator.

The generator receives a **context object** containing:

- `agentState`: Read-only access to the current agent state
- `prompt`: The original prompt passed to the agent
- `params`: Any parameters supplied via tool calls
- `logger`: A streaming logger that sends output to the UI via `handleStepsLogChunk`

This context allows you to inspect the current execution state and emit logs that appear in real-time in the Codebuff interface.

## Yield Values and Execution Control

The runtime validates every yielded value against `HandleStepsYieldValueSchema` (defined at line 42-44 in [`run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/run-programmatic-step.ts)). Invalid yields trigger immediate errors with clear messages. Here are the valid yield patterns:

### Tool Calls

Yield a `ToolCall` object to execute any built-in tool immediately:

```typescript
const { toolResult } = yield {
  toolName: 'read_files',
  input: { paths: ['src/index.ts'] }
}

```

Available tools include `read_files`, `spawn_agents`, `set_output`, and any custom tools defined in your project.

### Step Control (STEP and STEP_ALL)

Control when the LLM generates responses:

- **`'STEP'`**: Runs the agent's LLM once, producing a single assistant message. Use this to get the LLM's input before deciding the next tool call.
- **`'STEP_ALL'`**: Enters continuous mode where the LLM keeps generating messages until it calls the `end_turn` tool or stops producing tool calls. This is useful for letting the LLM work autonomously through a complex task.

```typescript
// Single step
yield 'STEP'

// Continuous execution
yield 'STEP_ALL'

```

### Text Parsing and Generation

- **`{ type: 'STEP_TEXT', text }`**: Parses the provided text for embedded tool calls, then executes those calls in order. This allows dynamic tool invocation based on generated content.
- **`{ type: 'GENERATE_N', n }`**: Requests **n** separate LLM responses. The runtime sets `generateN = n` and returns multiple outputs, useful for "best-of-N" scenarios where you want to pick the highest quality response.

### Ending Execution

When the generator returns or finishes, the current agent's turn ends automatically. You can also explicitly `return` to signal completion.

## Implementation Details and Source Locations

Understanding where `handleSteps` is defined and executed helps when debugging or extending functionality:

- **[`agents/types/agent-definition.ts`](https://github.com/CodebuffAI/codebuff/blob/main/agents/types/agent-definition.ts)** (lines 58-70): Defines the `AgentDefinition` interface including the optional `handleSteps` property with full TypeScript typing.

- **[`packages/agent-runtime/src/run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/run-programmatic-step.ts)** (lines 26-62): Contains `runProgrammaticStep`, the core executor that instantiates the generator, validates yields against `HandleStepsYieldValueSchema` (lines 42-44), and routes each yield to the appropriate handler.

- **[`sdk/src/agents/load-agents.ts`](https://github.com/CodebuffAI/codebuff/blob/main/sdk/src/agents/load-agents.ts)** (lines 240-245): Handles serialization of `handleSteps` functions to strings for storage and re-evaluation at runtime. This allows agent definitions to be saved and loaded while preserving their programmatic logic.

- **[`sdk/src/run.ts`](https://github.com/CodebuffAI/codebuff/blob/main/sdk/src/run.ts)**: Wires up `handleStepsLogChunk` to stream generator logs to the UI in real-time.

- **[`packages/agent-runtime/src/tools/handlers/tool/spawn-agent-utils.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agent-utils.ts)**: Reference implementation showing how `spawn_agents` tool works, commonly invoked from within `handleSteps`.

## Practical Examples

### Basic File Reader

This agent reads a file and returns its contents using programmatic control:

```typescript
import type { AgentDefinition } from './types/agent-definition'

export const definition: AgentDefinition = {
  id: 'file-reader',
  displayName: 'File Reader',
  model: 'anthropic/claude-sonnet-4.6',
  instructionsPrompt: 'Read the requested file and output its contents.',
  handleSteps: function* ({ agentState, prompt, logger }) {
    logger.info('Starting file read operation')
    
    // Get LLM acknowledgment (optional)
    yield 'STEP'
    
    // Execute read_files tool
    const { toolResult } = yield {
      toolName: 'read_files',
      input: { paths: [prompt ?? 'README.md'] },
    }
    
    // Return result via set_output
    yield {
      toolName: 'set_output',
      input: { 
        output: toolResult?.[0]?.content ?? 'No content found' 
      },
    }
    
    return
  },
}

export default definition

```

### Continuous Execution with STEP_ALL

Use this pattern when you want the LLM to work autonomously until it decides to stop:

```typescript
handleSteps: function* ({ logger }) {
  logger.info('Entering autonomous mode')
  
  // LLM keeps generating until it calls end_turn
  yield 'STEP_ALL'
  
  logger.info('Agent finished autonomous execution')
}

```

### Spawning Sub-agents

Orchestrate complex workflows by spawning specialized sub-agents:

```typescript
handleSteps: function* ({ logger, prompt }) {
  logger.debug('Delegating to specialist sub-agent')
  
  // Spawn a "thinker" agent to analyze the prompt
  const { toolResult } = yield {
    toolName: 'spawn_agents',
    input: {
      agents: [
        {
          agent_type: 'thinker',
          prompt: `Analyze this request deeply: ${prompt}`,
        },
      ],
    },
  }
  
  // Wait for sub-agent completion with a single step
  const { stepsComplete } = yield 'STEP'
  
  if (stepsComplete && toolResult?.[0]) {
    yield {
      toolName: 'set_output',
      input: { output: toolResult[0].content },
    }
  }
}

```

### Best-of-N Generation

Generate multiple responses and select the best one:

```typescript
handleSteps: function* ({ logger }) {
  logger.info('Generating 3 candidate responses')
  
  // Request 3 separate LLM generations
  yield { type: 'GENERATE_N', n: 3 }
  
  // The runtime will provide all 3 responses for comparison
  yield 'STEP'
  
  // Logic to select best response would go here...
}

```

## Summary

- **`handleSteps`** is an optional generator function in Codebuff that enables programmatic control over agent execution by yielding specific commands to the runtime.
- **Yield values** include `ToolCall` objects for immediate tool execution, `'STEP'` for single LLM turns, `'STEP_ALL'` for autonomous operation, and special commands like `GENERATE_N` for multiple responses.
- **Context access** provides read-only `agentState`, the original `prompt`, tool `params`, and a streaming `logger` for real-time UI feedback.
- **Source locations** include [`agents/types/agent-definition.ts`](https://github.com/CodebuffAI/codebuff/blob/main/agents/types/agent-definition.ts) (definition), [`packages/agent-runtime/src/run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/run-programmatic-step.ts) (execution), and [`sdk/src/agents/load-agents.ts`](https://github.com/CodebuffAI/codebuff/blob/main/sdk/src/agents/load-agents.ts) (serialization).
- **Validation** ensures every yield matches `HandleStepsYieldValueSchema`, throwing clear errors for invalid values before execution proceeds.

## Frequently Asked Questions

### How do I add handleSteps to my existing agent definition?

Add a `handleSteps` property to your `AgentDefinition` object in your agent file (typically located in a `.agents/` folder). The value must be a generator function (`function*`) that accepts a context object. According to the source code in [`agents/types/agent-definition.ts`](https://github.com/CodebuffAI/codebuff/blob/main/agents/types/agent-definition.ts) (lines 58-70), this property is optional and fully typed, so TypeScript will validate your generator's signature automatically.

### What happens if I yield an invalid value from handleSteps?

The runtime validates every yielded value against `HandleStepsYieldValueSchema` defined in [`packages/agent-runtime/src/run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/run-programmatic-step.ts) (lines 42-44). If you yield an unrecognized object or malformed structure, the executor throws a validation error immediately and halts the agent's execution. This strict typing prevents silent failures and ensures that only supported operations (ToolCalls, STEP commands, or special directives) reach the execution engine.

### Can I use async operations inside handleSteps?

No, `handleSteps` must be a **synchronous generator function** (`function*`, not `async function*`). The runtime in `runProgrammaticStep` expects to drive the generator step-by-step using `.next()`, and async generators would complicate the execution model. However, you can yield ToolCalls that perform asynchronous operations (like `read_files` or `spawn_agents`), and the runtime will pause your generator until the tool completes, effectively giving you async capabilities through the yield mechanism.

### How does logging work inside the handleSteps generator?

The generator receives a `logger` object in its context parameter that streams logs directly to the Codebuff UI. When you call `logger.info()`, `logger.debug()`, or similar methods, the runtime in [`sdk/src/run.ts`](https://github.com/CodebuffAI/codebuff/blob/main/sdk/src/run.ts) routes these through `handleStepsLogChunk` to provide real-time feedback during execution. This is particularly useful for debugging complex generator flows or showing progress when spawning sub-agents or running multiple steps, as the logs appear in the UI without waiting for the generator to complete.