How to Use the set_output Function for Inter-Agent Communication in Codebuff

Use the set_output tool to store structured data in an agent's state, enabling parent agents to retrieve validated results after a child agent completes its execution cycle.

The set_output function is the primary mechanism for structured inter-agent communication in the Codebuff framework. When building multi-agent workflows in the CodebuffAI/codebuff repository, this built-in tool allows child agents to return complex data objects that parent agents can consume reliably, with full JSON schema validation ensuring type safety across agent boundaries.

Understanding the set_output Workflow

The set_output tool operates as a stateful bridge between agent execution layers. Unlike simple message passing, this function persists structured output directly to the agent's state object, making it available to any parent or orchestrator after the agent's turn completes.

The workflow follows four distinct phases:

  1. Declaration – The agent template registers set_output in its toolNames array and defines an outputSchema that specifies the expected JSON structure.
  2. Invocation – During execution, the LLM generates a call to set_output with a payload matching the defined schema.
  3. Validation – The runtime's handleSetOutput function validates the payload against outputSchema using Zod parsing.
  4. Retrieval – The parent agent accesses the validated data via agentState.output after the child finishes.

Configuring Your Agent for Structured Output

To enable inter-agent communication with set_output, you must configure the agent template with specific metadata that tells the runtime how to validate and store the output.

Defining the outputSchema

The outputSchema field uses Zod to enforce the structure of data passed between agents. This schema acts as a contract between parent and child agents.

// common/src/templates/initial-agents-dir/examples/03-advanced-file-explorer.ts
import { z } from 'zod'

export const fileAnalyzerAgent = {
  name: 'file-analyzer',
  toolNames: ['set_output', 'read_file', 'end_turn'],
  outputMode: 'structured_output',
  outputSchema: z.object({
    filePath: z.string().describe('Path to the analyzed file'),
    changedLines: z.array(z.number()).describe('Line numbers modified'),
    summary: z.string().describe('Brief description of changes')
  }),
  // ... other agent configuration
}

Registering the Tool

The set_output tool must be explicitly included in the agent's toolNames array. According to the source in common/src/types/dynamic-agent-template.ts, this tool is mandatory when outputMode is set to "structured_output" or "json".

// common/src/tools/list.ts
export const TOOL_DEFINITIONS = {
  set_output: {
    name: 'set_output',
    description: 'Store structured output for parent agent consumption',
    parameters: z.object({}) // Runtime validation uses outputSchema instead
  },
  // ... other tools
}

Implementing set_output in Agent Logic

When the LLM generates the agent's execution steps, it must invoke set_output with a payload that conforms to the defined schema. The tool call typically appears as the final action before end_turn.

// Conceptual LLM-generated step within handleSteps
async function analyzeFile(filePath: string) {
  // Perform analysis...
  const changedLines = [12, 13, 14]
  
  // Store structured result for parent
  await callTool('set_output', {
    data: {
      filePath: filePath,
      changedLines: changedLines,
      summary: 'Refactored utility functions'
    }
  })
  
  // Signal completion
  await callTool('end_turn')
}

The runtime wraps this call in a CodebuffToolCall object with toolName: 'set_output' and the provided input object.

How the Runtime Processes set_output Calls

The handleSetOutput function in packages/agent-runtime/src/tools/handlers/tool/set-output.ts manages the validation and storage logic. This handler ensures type safety before persisting data to the agent state.

// packages/agent-runtime/src/tools/handlers/tool/set-output.ts
export const handleSetOutput = (async ({
  previousToolCallFinished,
  toolCall,
  agentState,
  ...rest
}) => {
  const output = toolCall.input
  const { data } = output ?? {}

  await previousToolCallFinished

  // Retrieve agent template for schema validation
  const agentTemplate = agentState.agentType
    ? await getAgentTemplate({ ...rest, agentId: agentState.agentType })
    : null

  let finalOutput: unknown
  
  if (agentTemplate?.outputSchema) {
    // Strict validation against Zod schema
    try {
      agentTemplate.outputSchema.parse(output)
      finalOutput = output
    } catch {
      // Fallback to data field only
      agentTemplate.outputSchema.parse(data)
      finalOutput = data
    }
  } else {
    // No schema defined - extract data field if present
    const keys = Object.keys(output)
    const hasOnlyDataField = keys.length === 1 && keys[0] === 'data'
    finalOutput = hasOnlyDataField ? data : output
  }

  // Persist to agent state
  agentState.output = finalOutput as Record<string, unknown>

  return { output: jsonToolResult({ message: 'Output set' }) }
}) satisfies CodebuffToolHandlerFunction<'set_output'>

The handler performs schema validation using Zod's parse method. If the payload fails validation against the full output object, it attempts to validate just the data field. This provides flexibility while maintaining type safety.

Reading Output from Parent Agents

After a child agent completes its execution cycle, the parent retrieves the structured data through the child's agentState.output property. The runtime enforces that agents with set_output in their tool list must actually call it before ending their turn.

// packages/agent-runtime/src/run-agent-step.ts
if (currentAgentState.output === undefined && toolNames.includes('set_output')) {
  // Enforcement: structured output agents must call set_output
  throw new Error(
    "Agent finished without calling set_output while a structured output schema is required"
  )
}

// Parent accessing child output
const childState = await runAgentStep({
  agentId: 'file-analyzer',
  // ... other params
})

const result = childState.output
// result → { filePath: 'src/utils/helper.ts', changedLines: [12, 13, 14] }

This pattern enables complex multi-agent workflows where specialized child agents perform discrete tasks and return structured results that parent agents can programmatically process.

Summary

  • Declare the contract: Add set_output to toolNames and define an outputSchema using Zod to enforce the data structure between agents.
  • Store structured data: Child agents call set_output with a payload matching the schema before ending their turn.
  • Validate automatically: The handleSetOutput handler in packages/agent-runtime/src/tools/handlers/tool/set-output.ts validates payloads against the schema and stores valid data on agentState.output.
  • Retrieve safely: Parent agents access childState.output after the child completes, with the runtime enforcing that structured output agents must call set_output before finishing.

Frequently Asked Questions

What happens if an agent with set_output never calls the tool?

The runtime throws an error. According to the logic in packages/agent-runtime/src/run-agent-step.ts, if an agent's toolNames includes set_output but agentState.output remains undefined when the turn ends, the system raises: "Agent finished without calling set_output while a structured output schema is required". This ensures that parent agents never receive undefined data when expecting structured output.

Can set_output be used without an outputSchema?

Yes, but with reduced type safety. If no outputSchema is defined in the agent template, handleSetOutput stores the raw input or extracts the data field if it's the only property present. However, best practices recommend always defining an outputSchema when using set_output for inter-agent communication to ensure consistent data contracts between parent and child agents.

How does set_output differ from regular message passing?

Unlike add_message or other communication tools that append to conversation history, set_output performs structured state persistence. It validates data against Zod schemas, stores results in a dedicated agentState.output property, and enforces that the data must be present before the agent can complete its turn. This makes it suitable for programmatic data exchange rather than conversational context building.

What validation errors can occur when calling set_output?

Validation fails when the payload structure does not match the Zod schema defined in outputSchema. The handleSetOutput function first attempts to validate the full input object; if that fails, it tries validating just the data field. If both attempts fail, the handler returns an error message and does not update agentState.output, forcing the agent to retry with valid data before ending its turn.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →