# How Agent State and Message History Management Works in Codebuff

> Understand how Codebuff manages agent state and message history with its immutable AgentState object and serializable messageHistory array for seamless conversation tracking.

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

---

**Codebuff manages agent state and message history through an immutable `AgentState` object that tracks every conversation turn, tool result, and sub-agent interaction in a serializable `messageHistory` array.**

The Codebuff runtime treats every LLM interaction as a stateful session centered on the `AgentState` interface. This architecture enables deterministic replay, hierarchical agent spawning, and tool-driven conversation flow while maintaining strict ordering guarantees across asynchronous boundaries.

## Core Architecture of Agent State

### The AgentState Interface

The foundation of agent state and message history management in Codebuff lives in **[`.agents/types/agent-definition.ts`](https://github.com/CodebuffAI/codebuff/blob/main/.agents/types/agent-definition.ts)**. This file defines the `AgentState` interface that encapsulates everything needed to persist and resume a conversation:

```ts
export interface AgentState {
  agentId: string
  runId: string
  parentId: string | undefined

  /** The agent's conversation history: messages from the user and the assistant. */
  messageHistory: Message[]

  /** The last value set by the set_output tool. */
  output: Record<string, any> | undefined

  /** The system prompt for this agent. */
  systemPrompt: string

  /** The tool definitions for this agent. */
  toolDefinitions: Record<string, { description: string | undefined; inputSchema: {} }>

  /** Token count from the Anthropic API (updated each step). */
  contextTokenCount: number
}

```

The `messageHistory` field is an ordered array of `Message` objects (defined in **[`common/types/messages/codebuff-message.ts`](https://github.com/CodebuffAI/codebuff/blob/main/common/types/messages/codebuff-message.ts)**) that records every user query, assistant response, and tool execution result.

## Message History Lifecycle

Codebuff's message history follows a strict lifecycle from initialization through sub-agent merging:

### Initialization and System Messages

When an agent session begins, `messageHistory` starts as an empty array (`[]`). The runtime immediately injects a system message containing the `systemPrompt` defined in the agent configuration.

### Appending User and Assistant Messages

The **`add_message`** tool handler in **[`packages/agent-runtime/src/tools/handlers/tool/add-message.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/add-message.ts)** appends new messages to the history:

```ts
// Tool payload
{
  toolName: 'add_message',
  input: {
    role: 'user',
    content: 'Explain the differences between GPT‑4 and Claude.'
  }
}

// Handler (simplified)
agentState.messageHistory.push(userMessage(toolCall.input.content));

```

### Handling Tool Results

After the assistant generates a tool call, the runtime 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 476-478) creates a `tool`-role message and pushes it immediately after the originating assistant turn:

```ts
agentState.messageHistory = [...agentState.messageHistory]
agentState.messageHistory.push(assistantMessage(toolCallPart))

```

### Explicit History Replacement

The **`set_messages`** tool in **[`packages/agent-runtime/src/tools/handlers/tool/set-messages.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/set-messages.ts)** allows wholesale replacement of the conversation history:

```ts
// Tool payload
{
  toolName: 'set_messages',
  input: {
    messages: [
      { role: 'system', content: 'You are a helpful assistant.' },
      { role: 'user', content: 'What is the weather?' }
    ]
  }
}

// Handler
agentState.messageHistory = toolCall.input.messages;

```

### Sub-Agent Spawning and History Merging

When spawning sub-agents via **[`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)**, Codebuff handles history inheritance and merging:

1. **Filtering**: The parent filters out unfinished tool calls before passing history to the child
2. **Inheritance**: The child receives a copy of the parent's filtered history
3. **Merging**: When the child completes, its final `messageHistory` merges back into the parent

```ts
// Parent prepares filtered history
const parentHistory = filterUnfinishedToolCalls(parentAgentState.messageHistory);

// Child runs...
const childResult = await runSubAgent(...);

// Merge back
parentAgentState.messageHistory = result.agentState.messageHistory;
parentAgentState.childRunIds.push(result.agentState.runId);

```

## Output Extraction from Message History

The **`getAgentOutput`** helper in **[`packages/agent-runtime/src/util/agent-output.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/util/agent-output.ts)** extracts the final output from `messageHistory` based on the agent template configuration. For `last_message` output mode, it uses `getLastAssistantTurnMessages`:

```ts
function getLastAssistantTurnMessages(messageHistory: Message[]): Message[] {
  // Find the index of the last assistant message
  let lastAssistantIndex = -1
  for (let i = messageHistory.length - 1; i >= 0; i--) {
    if (messageHistory[i].role === 'assistant') {
      lastAssistantIndex = i
      break
    }
  }
  // Collect that assistant message and all subsequent tool messages
  const result: Message[] = []
  for (let i = lastAssistantIndex; i < messageHistory.length; i++) {
    const message = messageHistory[i]
    if (message.role === 'assistant' || message.role === 'tool') {
      result.push(message)
    } else {
      break // stop on user/system
    }
  }
  return result
}

```

This function walks the history backwards to locate the most recent `assistant` message, then collects that message plus any following `tool` messages until encountering a `user` or `system` entry.

## Token Accounting and Immutability

### Context Token Counting

Every step execution updates `AgentState.contextTokenCount` via the `/api/v1/token-count` endpoint. This field tracks credit usage and enables cost budgeting across the agent hierarchy.

### Handling Read-Only Constraints

The runtime in **[`packages/agent-runtime/src/run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/run-programmatic-step.ts)** notes that `agentState.messageHistory` becomes read-only after certain async boundaries. The workaround involves cloning the array before mutation:

```ts
// NOTE: agentState.messageHistory is readonly for some reason (?!). Recreating the array is a workaround.
agentState.messageHistory = [...agentState.messageHistory]
agentState.messageHistory.push(assistantMessage(toolCallPart))

```

## Practical Code Examples

### Adding Messages with add_message

```ts
// Tool invocation payload
{
  toolName: 'add_message',
  input: {
    role: 'user',
    content: 'Refactor this function to use async/await'
  }
}

// Implementation pushes to history
agentState.messageHistory.push(userMessage(toolCall.input.content));

```

*Source: **[add-message.ts](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/add-message.ts)***

### Replacing History with set_messages

```ts
// Tool invocation payload
{
  toolName: 'set_messages',
  input: {
    messages: [
      { role: 'system', content: 'You are a code reviewer.' },
      { role: 'user', content: 'Review this PR' }
    ]
  }
}

// Implementation replaces array
agentState.messageHistory = toolCall.input.messages;

```

*Source: **[set-messages.ts](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/set-messages.ts)***

### Extracting the Last Assistant Turn

```ts
const lastTurn = getLastAssistantTurnMessages(agentState.messageHistory);
// Returns:
// [
//   { role: 'assistant', content: 'Here is the solution...' },
//   { role: 'tool', content: '{"status":"ok"}' }
// ]

```

*Source: **[agent-output.ts](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/util/agent-output.ts)***

### Spawning Sub-Agents with History Inheritance

```ts
// Filter parent history to remove unfinished calls
const parentHistory = filterUnfinishedToolCalls(parentAgentState.messageHistory);

// After child execution, merge histories
parentAgentState.messageHistory = childResult.agentState.messageHistory;
parentAgentState.childRunIds.push(childResult.agentState.runId);

```

*Source: **[spawn-agent-utils.ts](https://github.com/CodebuffAI/codebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agent-utils.ts)***

## Summary

- **AgentState** serves as the single source of truth for conversation state, defined in **[`.agents/types/agent-definition.ts`](https://github.com/CodebuffAI/codebuff/blob/main/.agents/types/agent-definition.ts)** with fields for `messageHistory`, `output`, `systemPrompt`, and token accounting.
- **Message history** follows a strict lifecycle: initialization → user/assistant appends via **`add_message`** → tool result injection → optional wholesale replacement via **`set_messages`** → sub-agent inheritance and merging.
- **Output extraction** uses **`getLastAssistantTurnMessages`** in **[`agent-output.ts`](https://github.com/CodebuffAI/codebuff/blob/main/agent-output.ts)** to isolate the final assistant response and associated tool results based on template configuration.
- **Immutability constraints** require array cloning before mutation in **[`run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/run-programmatic-step.ts)** to work around read-only boundaries after async operations.
- **Hierarchical agents** share history through **[`spawn-agent-utils.ts`](https://github.com/CodebuffAI/codebuff/blob/main/spawn-agent-utils.ts)**, filtering unfinished tool calls for children and merging final states back to parents.

## Frequently Asked Questions

### How does Codebuff handle message history when spawning sub-agents?

When spawning sub-agents, Codebuff filters the parent’s `messageHistory` to remove unfinished tool calls via `filterUnfinishedToolCalls` in **[`spawn-agent-utils.ts`](https://github.com/CodebuffAI/codebuff/blob/main/spawn-agent-utils.ts)**. The child receives this filtered copy and operates independently. Upon completion, the child’s final `messageHistory` merges back into the parent, and the child’s `runId` is tracked in the parent’s `childRunIds` array.

### What is the difference between add_message and set_messages tools?

The **`add_message`** tool appends a single message to the existing `messageHistory` array, preserving all previous conversation context. In contrast, the **`set_messages`** tool performs a wholesale replacement of the entire history, allowing agents to reset context, trim old messages, or load predefined conversation states. Both tools are implemented in **[`add-message.ts`](https://github.com/CodebuffAI/codebuff/blob/main/add-message.ts)** and **[`set-messages.ts`](https://github.com/CodebuffAI/codebuff/blob/main/set-messages.ts)** respectively.

### How does Codebuff extract the final output from message history?

Codebuff uses the **`getAgentOutput`** utility in **[`agent-output.ts`](https://github.com/CodebuffAI/codebuff/blob/main/agent-output.ts)** to extract results based on the agent template configuration. For `last_message` output mode, it calls `getLastAssistantTurnMessages`, which walks backward through `messageHistory` to find the most recent `assistant` message, then collects that message plus any subsequent `tool` messages until encountering a `user` or `system` entry.

### Why does Codebuff clone the messageHistory array during execution?

According to the source code in **[`run-programmatic-step.ts`](https://github.com/CodebuffAI/codebuff/blob/main/run-programmatic-step.ts)**, the `agentState.messageHistory` array becomes read-only after certain async boundaries. To work around this immutability constraint, the runtime clones the array using the spread operator (`[...agentState.messageHistory]`) before pushing new assistant or tool messages, ensuring mutations succeed without violating TypeScript readonly protections.