Skip to content

Agent Configuration

Each agent is defined by a YAML file that specifies its identity, behavior, schedule, permissions, and integrations. This page documents the complete agent configuration schema.

name: my-coder
description: "Implements features from GitHub issues"
workspace: my-project
repo: myorg/my-project
identity:
name: "Code Bot"
role: "Software Engineer"
personality: "Methodical and thorough"
system_prompt: |
You are a senior software engineer. Write clean, tested code.
work_source:
type: github
labels:
ready: "ready"
in_progress: "in-progress"
schedules:
check-issues:
type: interval
interval: 5m
prompt: "Check for ready issues and work on the oldest one."
session:
max_turns: 50
timeout: 2h
model: claude-sonnet-4-20250514
permission_mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- Bash
mcp_servers:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
model: claude-sonnet-4-20250514
max_turns: 100
permission_mode: acceptEdits
FieldTypeRequiredDescription
namestringYesUnique identifier for the agent
descriptionstringNoHuman-readable description
workspacestring | objectNoDirectory name or workspace config
repostringNoGitHub repository (e.g., owner/repo)
identityobjectNoAgent identity configuration
system_promptstringNoCustom system instructions for Claude
default_promptstringNoDefault prompt when triggered without --prompt
work_sourceobjectNoWhere the agent gets tasks
schedulesobjectNoMap of named schedule configurations
hooksobjectNoPost-job actions (notifications, webhooks)
instancesobjectNoConcurrency and instance settings
sessionobjectNoSession runtime settings
permission_modestringNoPermission mode setting
allowed_toolsstring[]NoTools the agent can use
denied_toolsstring[]NoTools explicitly denied
mcp_serversobjectNoMCP server configurations
chatobjectNoChat integration settings
dockerobjectNoDocker execution settings
runtimestringNoRuntime type: "sdk" (default) or "cli"
modelstringNoClaude model to use
max_turnsintegerNoMaximum conversation turns
metadata_filestringNoPath to agent metadata file for hooks

The unique identifier for this agent. Used in CLI commands and logging.

name: bragdoc-coder

Human-readable description of what this agent does.

description: "Implements features and fixes bugs for the bragdoc project"

Where the agent operates. Can be a simple string (directory name) or a full configuration object.

Simple form:

workspace: my-project

Full form:

workspace:
root: /path/to/workspace
auto_clone: true
clone_depth: 1
default_branch: main
FieldTypeDefaultDescription
rootstringAbsolute path to workspace root
auto_clonebooleantrueAuto-clone repo if missing
clone_depthinteger1Git clone depth
default_branchstring"main"Default branch to use

GitHub repository in owner/repo format. Used for cloning and work source integration.

repo: edspencer/bragdoc-ai

Defines the agent’s persona for Claude interactions.

identity:
name: "Senior Developer"
role: "Backend Engineer"
personality: "Detail-oriented, writes comprehensive tests"
FieldTypeDescription
namestringDisplay name for the agent
rolestringJob title or function
personalitystringPersonality traits and working style

Custom instructions prepended to Claude’s system prompt.

system_prompt: |
You are a senior software engineer specializing in TypeScript.
Always write tests for new code.
Follow the existing code style.

Default prompt used when the agent is triggered without an explicit --prompt argument.

default_prompt: "Check for new issues and process the oldest one."

This enables simple triggering:

Terminal window
# Without default_prompt, you must specify --prompt
herdctl trigger my-agent --prompt "Do something"
# With default_prompt configured, just run
herdctl trigger my-agent

Defines where the agent gets tasks to work on.

work_source:
type: github
labels:
ready: "ready-for-dev"
in_progress: "in-progress"
cleanup_in_progress: true
FieldTypeDescription
typestringWork source type. Currently: "github"
labelsobjectLabel configuration for GitHub issues
labels.readystringLabel indicating an issue is ready for work
labels.in_progressstringLabel to apply when work begins
cleanup_in_progressbooleanRemove in_progress label when complete

A map of named schedules that trigger agent execution. Each schedule has a unique key.

schedules:
morning-standup:
type: cron
expression: "0 9 * * 1-5"
prompt: "Review open PRs and summarize status."
issue-check:
type: interval
interval: 10m
prompt: "Check for ready issues and work on the oldest one."
work_source:
type: github
labels:
ready: "ready"
on-demand:
type: webhook
prompt: "Process the incoming webhook payload."
support-chat:
type: chat
prompt: "You are a helpful support agent."
TypeDescriptionKey Fields
intervalRun at fixed intervalsinterval (e.g., “5m”, “1h”)
cronRun on cron scheduleexpression (cron syntax)
webhookTriggered by HTTP webhook
chatTriggered by chat messages

Use cron expressions for precise time-based scheduling. Cron format: minute hour day month weekday

Common cron expressions:

ExpressionDescription
0 9 * * *Daily at 9:00 AM
30 9 * * 1-5Weekdays at 9:30 AM
0 */2 * * *Every 2 hours
0 0 1 * *First day of each month at midnight
0 9 * * 1Every Monday at 9:00 AM
0 0 * * 0Weekly on Sunday at midnight

Supported shorthands:

ShorthandEquivalentDescription
@hourly0 * * * *Every hour at minute 0
@daily0 0 * * *Every day at midnight
@weekly0 0 * * 0Every Sunday at midnight
@monthly0 0 1 * *First of each month at midnight
@yearly0 0 1 1 *January 1st at midnight

Example using shorthands:

schedules:
daily-report:
type: cron
expression: "@daily"
prompt: "Generate daily status report."
weekly-review:
type: cron
expression: "@weekly"
prompt: "Conduct weekly code review summary."
  • Use cron when specific times matter:

    • Daily reports at 9 AM (0 9 * * *)
    • Business hours processing (0 9-17 * * 1-5)
    • End-of-week summaries (0 17 * * 5)
    • Monthly maintenance (0 2 1 * *)
  • Use interval when regular frequency matters:

    • Health checks every 5 minutes (5m)
    • Polling for new work (10m)
    • Continuous monitoring (1m)
    • Cache refresh (1h)
# Cron: Reports need to arrive at specific times
schedules:
morning-report:
type: cron
expression: "0 9 * * 1-5"
prompt: "Generate morning status report for the team."
# Interval: Just need regular checks, timing doesn't matter
schedules:
issue-check:
type: interval
interval: 5m
prompt: "Check for new issues to process."
FieldTypeDescription
typestringRequired. One of: interval, cron, webhook, chat
intervalstringInterval duration (e.g., “5m”, “1h”, ”30s”)
expressionstringCron expression (e.g., “0 9 * * 1-5”)
promptstringTask prompt for this schedule
work_sourceobjectOverride work source for this schedule

For more details on scheduling, see Schedules and Triggers.

Configure actions that run after job completion. Use hooks for notifications, logging, triggering downstream systems, or any post-job processing.

hooks:
after_run:
- type: shell
name: "Log output"
command: "jq -r '.result.output'"
- type: discord
name: "Notify team"
channel_id: "${DISCORD_CHANNEL_ID}"
bot_token_env: DISCORD_BOT_TOKEN
when: "metadata.shouldNotify"
on_error:
- type: webhook
url: "https://alerts.example.com/errors"
EventDescription
after_runRuns after every job (success or failure)
on_errorRuns only when a job fails
TypeDescription
shellExecute a shell command with HookContext on stdin
webhookPOST/PUT HookContext JSON to a URL
discordSend formatted notification to Discord channel
slackSend formatted notification to Slack channel
FieldTypeDefaultDescription
typestringRequired. shell, webhook, discord, or slack
namestringHuman-readable name for logs
continue_on_errorbooleantrueContinue if hook fails
on_eventsarrayallFilter to specific events: completed, failed, timeout, cancelled
whenstringConditional execution (dot-notation path, e.g., metadata.shouldNotify)

For complete hook documentation, see Hooks.

Path to a JSON file that the agent writes during execution. This metadata is included in hook context and can be used for conditional hook execution.

metadata_file: metadata.json # Path relative to workspace

Example workflow:

  1. Configure metadata_file in agent config
  2. Agent writes metadata during execution:
    {
    "shouldNotify": true,
    "lowestPrice": 159.99,
    "retailer": "Staples"
    }
  3. Hooks use when to conditionally execute:
    hooks:
    after_run:
    - type: discord
    when: "metadata.shouldNotify"
    channel_id: "..."
    bot_token_env: DISCORD_BOT_TOKEN

The metadata is also displayed in Discord notification embeds and included in webhook payloads.

Configure concurrent execution limits for the agent.

instances:
max_concurrent: 1
FieldTypeDefaultDescription
max_concurrentinteger1Maximum simultaneous jobs for this agent

The max_concurrent setting controls how many schedule-triggered jobs can run simultaneously. When an agent reaches its limit, additional schedule triggers are skipped until a slot becomes available.

Example: Allow parallel processing

name: data-processor
description: "Processes work items from a queue"
instances:
max_concurrent: 3 # Process up to 3 items at once
schedules:
process-queue:
type: interval
interval: 1m
prompt: "Process the next available item."
work_source:
type: github
labels:
ready: "ready"
in_progress: "processing"

When to increase max_concurrent:

  • Processing independent work items (e.g., separate GitHub issues)
  • Running short, non-conflicting tasks
  • When throughput is more important than ordering

When to keep max_concurrent: 1 (default):

  • Work items may conflict or depend on each other
  • Agent modifies shared resources
  • Order of execution matters

For more details on concurrency, see Schedules - Concurrency Control.

Runtime settings for agent sessions.

session:
max_turns: 50
timeout: 2h
model: claude-sonnet-4-20250514
FieldTypeDescription
max_turnsintegerMaximum conversation turns per session
timeoutstringSession timeout (e.g., “30m”, “2h”)
modelstringClaude model for this session

permission_mode, allowed_tools, denied_tools

Section titled “permission_mode, allowed_tools, denied_tools”

Control what the agent can do. See Permissions for full details.

permission_mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- Glob
- Grep
- "Bash(git *)"
- "Bash(npm *)"
- "Bash(pnpm *)"
denied_tools:
- WebFetch
- "Bash(rm -rf *)"
- "Bash(sudo *)"
FieldTypeDescription
permission_modestringPermission mode (see below)
allowed_toolsstring[]Tools the agent can use (use Bash(pattern) for bash commands)
denied_toolsstring[]Tools explicitly denied (use Bash(pattern) for bash commands)
ModeDescription
defaultDefault Claude Code behavior
acceptEditsAuto-accept file edits (default)
bypassPermissionsSkip permission prompts
planPlan-only mode, no execution

Configure MCP (Model Context Protocol) servers. See MCP Servers for full details.

mcp_servers:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
custom:
url: http://localhost:8080/mcp

Each MCP server is a named entry with these fields:

FieldTypeDescription
commandstringExecutable command
argsstring[]Command arguments
envobjectEnvironment variables
urlstringURL for HTTP-based MCP servers

Configure chat integrations for the agent. Discord uses one bot per agent, while Slack uses a shared bot with channel-to-agent routing.

chat:
discord:
bot_token_env: SUPPORT_DISCORD_TOKEN
output:
tool_results: true
tool_result_max_length: 900
system_status: true
result_summary: false
errors: true
guilds:
- id: "123456789012345678"
channels:
- id: "987654321098765432"
name: "#support"
mode: mention
dm:
enabled: true
mode: auto
FieldTypeDefaultDescription
bot_token_envstringRequired. Environment variable name containing this agent’s Discord bot token
outputobjectControl which SDK messages appear as Discord embeds (tool results, system status, errors, result summary). See Discord Output Settings
guildsarrayDiscord servers (guilds) this bot participates in
guilds[].idstringDiscord guild ID
guilds[].channelsarrayChannels to monitor in this guild
guilds[].channels[].idstringDiscord channel ID
guilds[].channels[].namestringHuman-readable name (for documentation)
guilds[].channels[].modestringmentionmention (respond when @mentioned) or auto (respond to all)
guilds[].dm.enabledbooleantrueAllow direct messages
guilds[].dm.modestringautoDM response mode
chat:
slack:
bot_token_env: SLACK_BOT_TOKEN
app_token_env: SLACK_APP_TOKEN
session_expiry_hours: 24
log_level: standard
output:
tool_results: true
tool_result_max_length: 900
system_status: true
errors: true
channels:
- id: "C0123456789"
mode: mention
dm:
enabled: true
mode: auto
FieldTypeDefaultDescription
bot_token_envstringRequired. Environment variable containing the Bot User OAuth Token (xoxb-)
app_token_envstringRequired. Environment variable containing the App-Level Token (xapp-) for Socket Mode
session_expiry_hoursnumber24Hours before conversation context expires
log_levelstringstandardLogging level: minimal, standard, or verbose
outputobjectControl which SDK messages appear in Slack (tool results, system status, errors). See Slack Output Settings
channelsarraySlack channels this agent monitors
channels[].idstringSlack channel ID (starts with C)
channels[].modestringmentionmention (respond when @mentioned) or auto (respond to all)
dm.enabledbooleantrueAccept direct messages
dm.modestringautoDM response mode: mention or auto
dm.allowliststring[]Only accept DMs from these user IDs
dm.blockliststring[]Block DMs from these user IDs

Run the agent in a Docker container for security isolation.

docker:
enabled: true
memory: "2g"
cpu_shares: 1024
workspace_mode: rw
ephemeral: true
max_containers: 5
tmpfs:
- "/tmp"
pids_limit: 100
labels:
team: backend
FieldTypeDefaultDescription
enabledbooleanfalseEnable Docker execution
ephemeralbooleantrueFresh container per job
memorystring2gMemory limit
cpu_sharesintegerCPU relative weight (soft limit)
cpu_periodintegerCPU CFS period in microseconds
cpu_quotaintegerCPU CFS quota in microseconds
max_containersinteger5Container pool limit
workspace_modestringrwWorkspace mount: rw or ro
tmpfsstring[]Tmpfs mounts for in-memory temp storage
pids_limitintegerMaximum processes (prevents fork bombs)
labelsobjectContainer labels for organization

Fleet-level only options (use per-agent overrides): image, network, volumes, user, ports, env, host_config

See Docker Configuration for security model and detailed options.

Select the runtime backend for this agent.

runtime: cli # Use CLI runtime (Max plan pricing)
ValueDescription
sdkClaude Agent SDK (default, standard pricing)
cliClaude CLI (Max plan pricing, requires CLI installed)

See Runtime Configuration for detailed guidance on choosing a runtime.

Override the Claude model for this agent.

model: claude-sonnet-4-20250514

Common models:

  • claude-sonnet-4-20250514 — Fast, capable (recommended)
  • claude-opus-4-20250514 — Most capable

Maximum number of conversation turns per session.

max_turns: 100

Sets the permission mode for the agent. See permission_mode, allowed_tools, denied_tools for the full permissions reference.

permission_mode: acceptEdits

A development agent that processes GitHub issues:

name: project-coder
description: "Senior developer that implements features and fixes bugs"
workspace: my-project
repo: myorg/my-project
identity:
name: "Dev Bot"
role: "Senior Software Engineer"
personality: "Methodical, writes clean code with tests"
system_prompt: |
You are a senior software engineer working on this project.
Guidelines:
- Write TypeScript with strict types
- Include unit tests for new code
- Follow existing code patterns
- Create atomic commits with clear messages
work_source:
type: github
labels:
ready: "ready-for-dev"
in_progress: "in-progress"
cleanup_in_progress: true
schedules:
continuous:
type: interval
interval: 5m
prompt: |
Check for GitHub issues labeled "ready-for-dev".
If found, implement the oldest issue:
1. Create a feature branch
2. Implement the solution
3. Write tests
4. Create a PR
session:
max_turns: 100
timeout: 4h
model: claude-sonnet-4-20250514
permission_mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- Glob
- Grep
- Task
- "Bash(git *)"
- "Bash(npm *)"
- "Bash(pnpm *)"
- "Bash(node *)"
- "Bash(npx *)"
denied_tools:
- "Bash(rm -rf /)"
- "Bash(sudo *)"
mcp_servers:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
docker:
enabled: false

A content creation agent for marketing tasks:

name: content-marketer
description: "Creates blog posts, social content, and marketing copy"
identity:
name: "Marketing Assistant"
role: "Content Strategist"
personality: "Creative, engaging, brand-aware"
system_prompt: |
You are a content marketing specialist.
Your responsibilities:
- Write engaging blog posts
- Create social media content
- Maintain consistent brand voice
- Optimize content for SEO
work_source:
type: github
labels:
ready: "content-ready"
in_progress: "writing"
schedules:
content-check:
type: cron
expression: "0 8 * * 1-5"
prompt: |
Check for content requests labeled "content-ready".
For each request:
1. Research the topic
2. Create an outline
3. Write the content
4. Submit for review
social-daily:
type: cron
expression: "0 10 * * *"
prompt: |
Review recent blog posts and create social media
snippets for Twitter and LinkedIn.
session:
max_turns: 50
timeout: 2h
permission_mode: acceptEdits
allowed_tools:
- Read
- Write
- Edit
- Glob
- Grep
- WebFetch
- WebSearch
chat:
discord:
bot_token_env: MARKETER_DISCORD_TOKEN
guilds:
- id: "123456789012345678"
channels:
- id: "marketing-channel-id"
name: "#marketing"
mode: mention

A customer support agent that handles inquiries:

name: support-agent
description: "Handles customer support inquiries via chat"
identity:
name: "Support Bot"
role: "Customer Support Specialist"
personality: "Friendly, patient, solution-oriented"
system_prompt: |
You are a customer support specialist.
Guidelines:
- Be helpful and empathetic
- Provide accurate information from the knowledge base
- Escalate complex issues to humans
- Never make up information
schedules:
support-chat:
type: chat
prompt: |
You are monitoring support channels.
Help users with their questions about our product.
If you cannot help, escalate to a human.
ticket-review:
type: cron
expression: "0 9 * * 1-5"
prompt: |
Review open support tickets from yesterday.
Summarize common issues and trends.
session:
max_turns: 200
timeout: 8h
model: claude-sonnet-4-20250514
permission_mode: default
allowed_tools:
- Read
- Glob
- Grep
- WebFetch
denied_tools:
- Write
- Edit
- Bash
mcp_servers:
knowledge-base:
command: node
args: ["./mcp-servers/knowledge-base.js"]
env:
KB_API_KEY: ${KB_API_KEY}
chat:
discord:
bot_token_env: SUPPORT_DISCORD_TOKEN
guilds:
- id: "123456789012345678"
channels:
- id: "support-general-id"
name: "#support-general"
mode: mention
- id: "support-billing-id"
name: "#support-billing"
mode: mention
dm:
enabled: true
mode: auto

Agents inherit default settings from the fleet configuration (herdctl.yaml). Agent-specific settings override fleet defaults.

herdctl.yaml
defaults:
model: claude-sonnet-4-20250514
permission_mode: acceptEdits
session:
timeout: 2h
agents/special-agent.yaml
name: special-agent
model: claude-opus-4-20250514 # Override default model
session:
timeout: 4h # Override default timeout

Validate agent configuration before running:

Terminal window
herdctl validate agents/my-agent.yaml