Skip to content

Configuration Reference

Agents are defined by a single character.json file. This page documents every field.

Top-Level Fields

Field Type Required Description
name string Yes Agent display name (used in logs and dashboard)
bio string Yes One-line description of the agent's purpose
personality string Yes System prompt injected into every Claude request
lore string[] No Background facts appended to the system prompt
style object No Output style preferences
messageExamples object[] No Few-shot examples for consistent behavior
tools object[] No HTTP APIs the agent can call
mcpServers object No MCP servers the agent can use as tools
messaging object No Multi-platform messaging config (see Messaging). Overrides discord.
discord object No Discord connection settings (legacy, use messaging for new agents)
memory object No Memory retention policies
llm object Yes LLM provider and model settings
heartbeat object No Periodic status ping to an endpoint

personality

The core system prompt. This is the most important field -- it defines who the agent is and how it behaves.

{
  "personality": "You are a helpful assistant.\nYou answer questions and help users with tasks.\nYou are concise and professional."
}

Tip

Use \n for line breaks. Keep it under 2000 characters. Put detailed knowledge in lore instead.

lore

An array of facts the agent should know. Each entry is appended to the system prompt as context.

{
  "lore": [
    "The API returns JSON responses",
    "Users can ask questions in any language",
    "All actions are logged for audit purposes"
  ]
}

style

Controls the agent's output format.

{
  "style": {
    "language": "English",
    "tone": "professional but friendly",
    "format": "concise"
  }
}
Field Type Default Description
language string "English" Preferred response language
tone string "neutral" Communication tone
format string "plain" Output formatting rules

messageExamples

Few-shot examples that guide Claude's response style. Each example has a user message and an agent response.

{
  "messageExamples": [
    {
      "user": "What's the status of order #123?",
      "agent": "Order #123 is currently in transit. Expected delivery: tomorrow."
    }
  ]
}

tools

HTTP APIs the agent can call via Claude's tool use. Each tool group has a base URL and a list of endpoints.

{
  "tools": [
    {
      "url": "http://my-api.default.svc.cluster.local",
      "endpoints": [
        {
          "method": "GET",
          "path": "/orders",
          "description": "List orders by status"
        },
        {
          "method": "GET",
          "path": "/orders/:id",
          "description": "Get order details by ID"
        }
      ]
    }
  ]
}

Tool Endpoint Fields

Field Type Required Description
method string Yes HTTP method: GET, POST, PUT, DELETE
path string Yes URL path appended to the group's url
description string Yes Shown to Claude as the tool description

The runtime converts each endpoint into a Claude tool. The tool name is derived from method + path (e.g., get_folio_balance). Claude decides when to call tools based on the description.

CLI providers need the HTTP-tools bridge

The in-process tool loop only runs on the anthropic and openai-api providers. On claude-cli (and codex-cli) this block is inert unless you also declare the built-in bridge, which serves these endpoints as MCP tools derived by the same code:

"mcpServers": { "httptools": { "command": "node", "args": ["src/mcp-servers/http-tools.js"] } },
"llm": { "passMcpToCli": true }

See HTTP-tools MCP bridge.

mcpServers

Model Context Protocol servers the agent can use as tools. Each server is spawned as a child process via stdio.

{
  "mcpServers": {
    "context7": {
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp@latest"]
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
    }
  }
}

MCP Server Fields

Field Type Required Description
(key) string Yes Server name (used as tool name prefix)
command string Yes Command to spawn the server process
args string[] No Arguments passed to the command
env object No Additional environment variables for the server process

MCP tools are prefixed with mcp_{serverName}_ to avoid name collisions with HTTP tools. For example, a tool named resolve-library-id from the context7 server becomes mcp_context7_resolve-library-id.

MCP servers are connected on agent startup and disconnected on shutdown. If a server fails to connect, the agent continues without it.

Declared servers are the complete MCP grant

For claude-cli, the servers declared here are the whole MCP grant. The CLI is invoked with --strict-mcp-config, so host or image MCP configuration (~/.claude.json, a project .mcp.json, plugins) is never inherited. Declaring no servers means the agent gets no MCP tools — not the host's. Before this guarantee (#651), an agent declaring nothing silently inherited every server on the host while running with permissions bypassed, so the character file was not a capability boundary.

All coding agents (Dev-E variants, Review-E, iBuild-E) should include context7 to fetch current library and framework documentation. Without it, agents rely on training-cutoff knowledge and may use stale API patterns, burning iterations on outdated usage.

Server Package Purpose
context7 @upstash/context7-mcp@latest Current library/framework docs — prevents stale API usage
github @modelcontextprotocol/server-github GitHub API (PRs, issues, code search)
memory @dashecorp/rig-memory-mcp Shared rig memory (learnings, decisions, gotchas)
advisor src/mcp-servers/advisor.js Rig-specific coding guidance (built-in, Dev-E/Review-E only)

Minimal mcpServers block for a coding agent:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"]
    },
    "memory": {
      "command": "node",
      "args": ["/usr/local/lib/node_modules/@dashecorp/rig-memory-mcp/index.js"]
    },
    "context7": {
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp@latest"]
    }
  }
}

Note

Set llm.passMcpToCli: true so CLI providers (claude-cli, codex-cli) receive MCP tools. Per-agent configs live in rig-gitops HelmRelease files, not in rig-agent-runtime directly.

messaging

Multi-platform messaging configuration. See Messaging for full setup guides.

{
  "messaging": {
    "platform": "slack",
    "config": {
      "channels": {
        "general": "my-channel"
      }
    }
  }
}
Field Type Required Description
platform string Yes "discord" or "slack"
config.channels object or array Yes Channels the agent listens on

Note

Requires index-v2.js entry point. Set command: ["node", "src/index-v2.js"] in Helm values.

discord

Discord connection and routing settings. Used by the legacy index.js entry point, or when messaging.platform is "discord".

{
  "discord": {
    "channels": ["#support"],
    "threadMode": "per-user",
    "allowBots": []
  }
}
Field Type Default Description
channels string[] [] Channel names the agent listens on. Empty = all channels.
threadMode string "none" "none", "per-document", or "per-user"
allowBots string[] [] Bot user IDs whose messages the agent should process

Thread Modes

Mode Behavior
none Reply directly in the channel
per-document Create a new thread for each document/attachment
per-user Create one thread per user (reuse existing)

memory

Retention policies for the memory system. See Memory for details.

{
  "memory": {
    "conversationRetention": "30d",
    "patternRetention": "indefinite",
    "historyRetention": "5y"
  }
}
Field Type Default Description
conversationRetention string "30d" How long to keep conversation messages
patternRetention string "indefinite" How long to keep learned patterns
historyRetention string "indefinite" How long to keep audit history

Duration format: "30d" (days), "1y" (years), "indefinite".

llm

LLM provider configuration.

{
  "llm": {
    "provider": "anthropic",
    "model": "claude-haiku-4-5-20251001",
    "temperature": 0.3,
    "maxTokens": 4096,
    "baseUrl": "https://api.openai.com/v1"
  }
}

baseUrl (openai-api provider only, #668) — the OpenAI-compatible server to call. Defaults to OpenAI. Point it at any server that speaks the same protocol (vLLM, Ollama) to run a character on a self-hosted model: "baseUrl": "https://<host>/v1". https:// is required — the API key is sent as a bearer on every request — and the URL is resolved at agent creation, so a bad value fails at boot rather than on the first message. Requests carry User-Agent: rig-agent-runtime/<version>.

baseUrlFrom (openai-api provider only, #674) — resolve the base URL from rig-conductor instead of writing a literal pod URL: "baseUrlFrom": { "conductorPod": "qwen", "conductorUrl": "http://rig-conductor-api.rig-conductor.svc.cluster.local:8080" }. The provider calls GET {conductorUrl}/api/pods/{conductorPod} and uses proxyUrl + "/v1" while the pod isRunning; the result is cached and re-resolved once per chat turn after a network error or an HTTP 404/502/503/504 from the model server, so a RunPod migration no longer needs a config change. conductorUrl is optional when CONDUCTOR_BASE_URL is set; with neither, boot fails. If both baseUrl and baseUrlFrom are set, baseUrlFrom wins and a warning is logged. A pod that is not running does not stop the agent from booting — the turn fails with a providerNotReady error instead (the wake-on-chat hook). The conductor endpoint is unauthenticated in-cluster and its answer decides where the bearer key is POSTed, so the proxyUrl is only used when it is a bare https origin (no credentials, path, query or fragment) on a host matching allowedHostSuffixes (default [".proxy.runpod.net"]); anything else is logged as proxyUrl rejected: … and treated as not ready. See Endpoint from the conductor.

Wake-on-chat (baseUrlFrom only, #679) — when a POST /api/chat turn finds the pod not running, the runtime asks the conductor to start it (POST /api/pods/{conductorPod}/start, fire-and-forget) and answers as a normal assistant message (HTTP 200, the usual { reply, sessionId } shape — no client change): "Waking the GPU brain — that takes about 9 minutes…". Turns inside the next 15 minutes get "Still waking — N min elapsed…" without a second start request; after 15 minutes the wake is requested again. When the conductor itself is unreachable the reply says so and nothing is woken. Bringing vLLM up takes ~9 minutes (volume-first wake, measured 2026-09-09); the user asks again and the turn resolves through the conductor as usual. A completion POST that hangs (the resolver still holds the pre-stop proxy URL) is cut at timeoutMs (default 2 min) and takes the same path. Once the pod is RUNNING but vLLM is still loading the weights, the proxy answers 502/503/504 (or the connection is refused) twice in a row while the conductor — asked again, and reachable — still reports the pod up: that is reported as "The GPU is up and the model is still loading — that takes a few more minutes…" and never triggers a second wake (#685). After 20 minutes of that the reply stops reassuring and says the model server may be stuck, logged at error level. A stale cached pod status (the conductor unreachable), a timed-out request, and a response whose body fails mid-read are all excluded — they keep the generic 502, because they are faults, not a boot. See Wake-on-chat.

Field Type Default Description
provider string "anthropic" LLM provider: "anthropic", "claude-cli", "codex-cli", or "openai-api". ("claude-tmux" runs the interactive Claude Code TUI in a tmux pane for a live, attachable session — see the note below.)
fallbackProviders string[] [] Additional providers in the fallback chain. llm.provider is the default active provider; fallbackProviders entries are tried afterward in list order. agent.process() tries the active provider first and walks this chain on fallback-eligible errors (rar#420). buildProviderChain merges this list (in order, first) with the keys of providers and de-dupes — either source adds a provider to the chain, so fallbackProviders works even when the provider is not also in the providers map.
providers object {} Provider-specific overrides keyed by provider name
model string "claude-haiku-4-5-20251001" Model identifier passed to the selected provider
baseUrl string "https://api.openai.com/v1" For openai-api: the OpenAI-compatible server (https required). Ignored when baseUrlFrom is set.
baseUrlFrom object null For openai-api: { "conductorPod": "<name>", "conductorUrl"?: "<http(s) url>", "allowedHostSuffixes"?: string[] } — resolve the base URL from GET {conductorUrl}/api/pods/{conductorPod} (proxyUrl origin + /v1), cached and re-resolved on failure (#674). conductorUrl falls back to CONDUCTOR_BASE_URL. allowedHostSuffixes (default [".proxy.runpod.net"]; an empty list means the default) is the host allow-list for proxyUrl — set it when the model server moves off RunPod.
temperature number 0.3 Response randomness (0.0 = deterministic, 1.0 = creative)
maxTokens number 4096 Maximum tokens per Anthropic SDK response.
maxTurns number 10 Maximum turns for CLI providers (claude-cli, codex-cli).
timeoutMs number 300000 (CLI), 120000 (openai-api) CLI timeout in milliseconds for CLI providers. For openai-api: the per-request cap on each chat-completion POST (AbortSignal.timeout), so a model server or proxy that holds the connection cannot pin the chat slot — an aborted request re-resolves the endpoint once and, with baseUrlFrom, reaches the wake-on-chat path.
idleStallMs number 360000 For claude-tmux: fail-fast (→ fallback) if the interactive pane shows no change for this long without finishing.
search boolean false Enables Codex web search when provider is codex-cli.
authMode string "auto" For codex-cli: "auto" or "device-auth"
passMcpToCli boolean false Passes mcpServers through to CLI providers that support inline MCP config. For claude-cli, false (or an empty mcpServers) produces a strict empty grant — the agent gets no MCP tools at all, never the host's discovered config (#651).
dangerouslyBypassApprovalsAndSandbox boolean false For codex-cli, runs outside Codex's normal approvals and sandbox flow
readOnly boolean false For claude-cli: makes the agent genuinely read-only. Drops --dangerously-skip-permissions, runs under --permission-mode dontAsk (tools not pre-approved are auto-denied when invoked), pre-approves only Read/Grep/Glob/TodoWrite, and denies every write-capable built-in — Bash, Write, Edit, Monitor (executes shell scripts), Skill, ToolSearch, WebFetch/WebSearch (exfiltration) and the rest. Deny wins: a character can narrow this but never widen it. See Tool policy. Not yet enforced on codex-cli/claude-tmux — do not deploy a readOnly character on those providers.
allowedTools string[] For claude-cli: passed to --allowedTools. NOTE: this is permission pre-approval, not a bound — tools outside the list still exist unless denied or running under readOnly.
disallowedTools string[] For claude-cli: passed to --disallowedTools; these tools are removed. Under readOnly this list is merged into the built-in deny list.

Notes:

  • anthropic uses the Anthropic SDK and ANTHROPIC_API_KEY.
  • claude-cli uses the local claude command. OAuth subscription tokens (sk-ant-oat...) automatically route to this mode.
  • claude-tmux — runs claude interactively in a tmux pane (send-keys/paste-buffer + capture-pane) instead of headless claude -p. Its purpose is live observability: the session is attachable, so a read-only web terminal in the rig cockpit can stream the real Claude Code TUI as the agent works (rar#587). Billing is unchanged — it uses the pod's credential as-is, the same CLAUDE_CODE_OAUTH_TOKEN every rig agent runs on (the chart maps the anthropic-api-key secret to it; there is no metered ANTHROPIC_API_KEY in-cluster). The credential decides billing, not the mode, so tmux changes nothing about the rig's posture — it's the same subscription token the whole fleet already uses (if a metered key is ever provisioned it's picked up automatically). claude-cli remains the automatic fallback for any fallback-eligible failure. See the rig-docs decision 2026-06-16-review-e-cost-reduction. Before each launch the provider seeds ~/.claude.json (hasCompletedOnboarding: true + pre-trusts the task's workDir) so the interactive TUI skips its first-run Select login method / trust screens and authenticates straight from CLAUDE_CODE_OAUTH_TOKEN; if either screen still appears it fails fast to claude-cli rather than idle-stalling for idleStallMs (see docs/2026-06-17-claude-tmux-onboarding-skip.md, #600).
  • codex-cli uses the local codex command.
  • openai-api uses OPENAI_API_KEY directly against OpenAI's HTTP API, with the same tool set available to the SDK providers.
  • llm.authMode: device-auth tells the runtime to require codex login --device-auth and surface the login URL/code through progress messages before running a turn.
  • Without llm.authMode: device-auth, codex-cli uses the existing environment, which can be either a stored Codex login or OPENAI_API_KEY.
  • llm.passMcpToCli: true passes mcpServers through to codex exec so the CLI can use the same MCP servers as the SDK providers.
  • llm.dangerouslyBypassApprovalsAndSandbox: true enables Codex execution outside its normal approvals / sandbox flow. When this is enabled, the runtime must not also pass --full-auto.
  • fallbackProviders defines the chain of additional providers used for automatic failover on fallback-eligible errors. agent.process() tries the active provider first; on errors where AgentProviderError.fallbackEligible !== false (the default for every existing provider error), it walks the rest of the chain before failing. Per-invocation switching only — the next assignment still starts from the active provider, which fail-fasts in <10ms when codex has captured a usage-limit (rar#468). Session pinning is a future option. See docs/2026-05-18-provider-chain-iteration.md for the design.
  • When llm.provider changes (e.g. after a HelmRelease update), any previously stored manual-switch state is discarded in favour of the new configured primary. This prevents stale persisted state from overriding the operator's intent when switching from one primary provider to another. See #266.
  • providers.{name} can override provider-specific settings such as model, timeoutMs, maxTurns, or authMode without duplicating the entire llm block.

Example with Codex as the default provider and Claude Code / OpenAI API available for manual switching:

{
  "llm": {
    "provider": "codex-cli",
    "fallbackProviders": ["claude-cli", "openai-api"],
    "providers": {
      "codex-cli": {
        "model": "gpt-5.4",
        "authMode": "device-auth"
      },
      "claude-cli": {
        "model": "claude-sonnet-4-5",
        "maxTurns": 10
      },
      "openai-api": {
        "model": "gpt-5.4"
      }
    }
  }
}

cron

Configuration for one-shot cron mode. When set, the agent can run on a schedule via node src/run-once.js, executing the prompt and posting results to a Discord webhook.

{
  "cron": {
    "prompt": "Check all open PRs and report any that need attention."
  }
}
Field Type Required Description
prompt string Yes The prompt to execute on each cron run

The cron schedule itself is configured in the Helm chart (cron.schedule), not in character.json. Results are posted to the DISCORD_WEBHOOK_URL environment variable if set.

When codex-cli is used in cron / one-shot mode, the runtime can also send operator progress updates to that webhook, including:

  • login required
  • login already in progress
  • login cooling down / rate-limited
  • login complete
  • selected provider failed
  • operator must switch provider manually before retrying

webhooks

Receive real-time HTTP events from external systems (e.g., GitHub webhooks). Each source gets a POST /webhook/{source} endpoint on the dashboard HTTP server.

{
  "webhooks": {
    "github": {
      "secret": "env:GITHUB_WEBHOOK_SECRET"
    }
  }
}
Field Type Required Description
(key) string Yes Source name (used in URL path)
secret string No HMAC-SHA256 secret. Prefix with env: to read from environment variable.

Events are verified via X-Hub-Signature-256 header (GitHub HMAC). If no secret is configured, verification is skipped.

Supported GitHub events: pull_request, pull_request_review, check_suite, check_run, issues, issue_comment, push

The webhook event is formatted into a concise prompt and processed through the agent loop. In split mode, events are queued via Redis Stream (same as Discord messages). Responses are posted to DISCORD_WEBHOOK_URL.

heartbeat

Periodically POSTs the agent's status to a configured endpoint. On startup the runtime logs:

[Heartbeat] agent-name → url every Ns

where agent-name is read from the top-level name field of character.json.

{
  "heartbeat": {
    "url": "http://rig-conductor-api:8080/api/events",
    "intervalSeconds": 60,
    "agentId": "dev-e"
  }
}
Field Type Default Description
url string Endpoint to POST heartbeat events
intervalSeconds number 60 Seconds between heartbeats
agentId string Agent identifier included in the heartbeat payload; used to derive stable instance IDs in multi-replica StatefulSets

Heartbeat Payload Fields

Field Type Description
type string Always "HEARTBEAT"
agentId string Stable instance identifier
hostname string|null Pod hostname from process.env.HOSTNAME, or null if unset
podName string|null Pod name from process.env.POD_NAME, falling back to process.env.HOSTNAME, or null if neither is set
status string Current agent status (e.g. "idle", "working")
currentIssue string|null Active issue number, if any
currentRepo string|null Active repo, if any
uptimeSeconds number Process uptime in seconds
memoryMB number RSS memory usage in MB (Math.round(rss / 1024 / 1024))
activeProvider string|null Currently active LLM provider
availableProviders string[] Providers available for switching
errorCount number Cumulative unhandled errors since last successful work completion
lastWorkAt string|null ISO 8601 UTC timestamp of the last successful assignment completion, or null if no work has been completed since startup
buildSha string|null Git commit SHA from BUILD_SHA env var, or null if not set
startup string Present on first heartbeat only — startup elapsed time

Environment Variables

These are set on the container, not in character.json.

Variable Required Description
CHARACTER_FILE Yes Path to character.json (e.g., /config/character.json)
VALKEY_URL Yes Valkey/Redis connection URL for the stream consumer (e.g., redis://valkey:6379)
DISCORD_BOT_TOKEN Discord agents Discord bot token. May be omitted for a chat-only agent (CHAT_API_TOKEN set): Discord is skipped entirely — see Chat-only mode. Missing it on any other agent stays fail-fast.
SLACK_BOT_TOKEN Slack agents Slack Bot User OAuth Token (xoxb-...)
SLACK_APP_TOKEN Slack agents Slack App-Level Token for Socket Mode (xapp-...)
ANTHROPIC_API_KEY Yes Anthropic API key
DATABASE_URL No Postgres connection string. Omit for in-memory mode.
DISCORD_WEBHOOK_URL No Discord webhook URL for cron mode output.
GITHUB_WEBHOOK_SECRET No HMAC secret for GitHub webhook verification.
POD_NAME No Kubernetes pod name injected via the Downward API. Included as podName in heartbeat payloads; falls back to HOSTNAME if unset.
BUILD_SHA No Git commit SHA injected at build time. Included as buildSha in heartbeat payloads.
DASHBOARD_PORT No Dashboard HTTP port (default: 3000). Set to 0 for an OS-picked ephemeral port.
DASHBOARD_ENABLED No Set to false / 0 / off / no to skip the dashboard listener entirely. The agent still posts logs and tool calls to the in-memory state; nothing is exposed over HTTP. Heartbeat and message handling are unaffected.
CHAT_API_TOKEN No Shared secret enabling POST /api/chat, the request/reply chat surface Rig Cockpit uses. Unset disables the route entirely (503). Callers present it as X-Chat-Token. Every call drives an LLM, so the route is cost-incurring and fails closed. See HTTP chat endpoint.

Full Example

{
  "name": "My Agent",
  "bio": "A helpful support assistant",
  "personality": "You are a helpful assistant that answers questions...",
  "lore": [
    "The API uses REST conventions",
    "All timestamps are in UTC"
  ],
  "style": {
    "language": "English",
    "tone": "professional but friendly",
    "format": "concise"
  },
  "messageExamples": [
    {
      "user": "What's the status of order #123?",
      "agent": "Order #123 is in transit. Expected delivery: tomorrow."
    }
  ],
  "tools": [
    {
      "url": "http://my-api.default.svc.cluster.local",
      "endpoints": [
        { "method": "GET", "path": "/orders", "description": "List orders by status" }
      ]
    }
  ],
  "discord": {
    "channels": ["#support"],
    "threadMode": "per-user"
  },
  "memory": {
    "conversationRetention": "30d",
    "patternRetention": "indefinite",
    "historyRetention": "indefinite"
  },
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"]
    },
    "context7": {
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp@latest"]
    }
  },
  "llm": {
    "provider": "anthropic",
    "model": "claude-haiku-4-5-20251001",
    "temperature": 0.3,
    "maxTokens": 4096
  },
  "cron": {
    "prompt": "Check all open PRs and report any that need attention."
  }
}