How Freebuff Manages Public Agent Definitions: Bundled and Custom Agents Explained

Freebuff manages public agent definitions through a dual-layer system where bundled TypeScript modules are compiled into the CLI binary via a pre-build script, while user-provided definitions are dynamically loaded from .agents directories at runtime, with the SDK's loader merging both sources and giving precedence to user definitions.

Freebuff's architecture for public agent definitions enables both immediate out-of-the-box functionality and deep customization. All agents in the CodebuffAI/freebuff repository conform to a standardized AgentDefinition interface, allowing the system to treat built-in and user-created agents identically while maintaining clear separation between immutable bundled resources and mutable local configurations.

The AgentDefinition Schema

Every public agent in Freebuff is structured as a TypeScript module exporting an AgentDefinition object. This strict typing ensures consistency across bundled and user-provided agents.

The complete schema lives in agents/types/agent-definition.ts and defines every configurable aspect of an agent:

  • id – Unique identifier used to invoke the agent
  • model – Target LLM (e.g., openai/gpt-5-mini)
  • toolNames – Array of available tools the agent can call
  • spawnableAgents – Sub-agents this agent can instantiate
  • handleSteps – Generator function defining the agent's execution flow
  • displayName – Human-readable label for UI presentation

Both bundled and user-provided agents share this identical type definition, enabling seamless interoperability within the runtime environment.

Bundled Public Agents

Freebuff embeds its built-in public agents directly into the CLI binary through a sophisticated pre-build process. This ensures critical agents remain available even when users have not configured a local .agents directory.

The Pre-Build Pipeline

The script at cli/scripts/prebuild-agents.ts scans the repository's .agents directory tree (including paths like agents/reviewer/code-reviewer.ts and agents/general-agent/gpt-5-agent.ts). For each valid definition found:

  1. Imports the TypeScript module
  2. Stringifies any handleSteps generator functions
  3. Generates a self-contained module at cli/src/agents/bundled-agents.generated.ts

Runtime Registry

At startup, cli/src/utils/local-agent-registry.ts imports this generated bundle and exposes it through getBundledAgentsAsLocalInfo(). These agents carry the flag isBundled: true, distinguishing them from user overrides.

import { getBundledAgentsAsLocalInfo } from '../agents/bundled-agents.generated'

const bundled = getBundledAgentsAsLocalInfo()
bundled.forEach(agent => {
  console.log(`${agent.id} (${agent.displayName}) – bundled: ${agent.isBundled}`)
})

Because these definitions are compiled into the binary, the code-reviewer and other public agents function immediately after installation without additional configuration.

Loading User-Provided Agents

When the SDK initializes, it supplements bundled agents with custom definitions through the dynamic loader in sdk/src/agents/load-agents.ts.

Discovery and Validation

The loader searches three default locations recursively:

  • {cwd}/.agents
  • {cwd}/../.agents
  • ${HOME}/.agents

Alternatively, users can specify a custom path via the agentsPath parameter. The loader collects all files matching .ts, .tsx, .js, .mjs, or .cjs extensions, validates them against the AgentDefinition schema, and returns a map keyed by the agent's id.

import { loadLocalAgents } from '@codebuff/sdk/src/agents/load-agents'

const { agents, validationErrors } = await loadLocalAgents({
  validate: true,    // Enforce schema validation
  verbose: true,     // Log loading diagnostics
})

if (validationErrors.length) {
  console.error('Invalid agent definitions:', validationErrors)
}

Environment Resolution and Debugging

The loader processes $ENV_VAR references within MCP server configurations, substituting actual environment values at runtime. Each loaded definition retains its original _sourceFilePath property, enabling precise debugging when agent validation fails.

Runtime Precedence and Usage

Freebuff merges bundled and user-provided definitions at runtime, with user agents taking precedence over bundled equivalents when IDs collide. This allows seamless overrides of built-in behavior without modifying the core repository.

To use loaded agents with the SDK client:

import { CodebuffClient } from '@codebuff/sdk/src/client'

const client = new CodebuffClient()
const result = await client.run({
  agent: 'code-reviewer',                     // References bundled or custom agent
  agentDefinitions: Object.values(agents),    // Merged registry
  prompt: 'Please review this pull request',
})

Creating Custom Public Agents

Users can extend or override functionality by placing TypeScript files in any searched .agents directory:

// .agents/my-agent.ts
import { AgentDefinition } from '@codebuff/common/templates/initial-agents-dir/types/agent-definition'

const myAgent: AgentDefinition = {
  id: 'my-agent',
  displayName: 'My Custom Agent',
  model: 'openai/gpt-5-mini',
  toolNames: ['read_files', 'write_file'],
  handleSteps: function* ({ logger }) {
    logger.info('Running custom step')
    const { toolResult } = yield {
      toolName: 'read_files',
      input: { paths: ['example.txt'] },
    }
    // Custom logic here
  },
}

export default myAgent

The next invocation of loadLocalAgents() automatically detects this file, overriding any bundled agent with the same id while preserving access to all other public definitions.

Summary

  • Standardized Schema: All public agent definitions conform to the AgentDefinition interface in agents/types/agent-definition.ts, ensuring type safety across bundled and custom implementations.

  • Dual Loading Strategy: Freebuff combines pre-bundled agents (generated via cli/scripts/prebuild-agents.ts and exposed through cli/src/utils/local-agent-registry.ts) with dynamically loaded user agents from .agents directories.

  • Environment Awareness: The SDK loader (sdk/src/agents/load-agents.ts) resolves environment variables, validates schemas, and preserves source file paths for debugging.

  • Precedence Rules: User-provided definitions automatically override bundled agents with matching IDs, enabling customization without forking the repository.

  • Binary Independence: Bundled agents are embedded in the CLI binary, ensuring core functionality works immediately while still supporting extensibility through the .agents directory convention.

Frequently Asked Questions

Where are bundled public agent definitions stored in the Freebuff repository?

Bundled agent source files reside under the agents/ directory tree in the repository root (e.g., agents/reviewer/code-reviewer.ts). During the build process, cli/scripts/prebuild-agents.ts scans these files and generates cli/src/agents/bundled-agents.generated.ts, which is then compiled into the CLI binary. At runtime, cli/src/utils/local-agent-registry.ts imports this generated module to expose bundled agents with the isBundled: true flag.

How do I override a bundled public agent with my own definition?

Create a TypeScript file in any recognized .agents directory (such as {cwd}/.agents or ${HOME}/.agents) that exports an AgentDefinition with the same id as the bundled agent you wish to override. When sdk/src/agents/load-agents.ts runs, it automatically gives precedence to user-provided definitions over bundled ones. The next SDK client initialization will use your custom implementation instead of the built-in version.

What file formats does Freebuff support for agent definitions?

The loader in sdk/src/agents/load-agents.ts recursively searches for files with the following extensions: .ts, .tsx, .js, .mjs, and .cjs. Both CommonJS and ES Module formats are supported, provided they export a default AgentDefinition object or an object conforming to the expected schema.

How does Freebuff handle environment variables in agent configurations?

When loading agent definitions, Freebuff's SDK loader automatically detects and resolves $ENV_VAR syntax within MCP server configuration objects. This occurs during the validation phase in sdk/src/agents/load-agents.ts, substituting the literal string with the corresponding environment variable value at runtime. This allows sensitive credentials or host-specific settings to remain outside of version-controlled agent definition files.

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 →