docs/config-reference.md

Config reference

YAML configuration shape and supported fields.

⚙️ Configuration Reference

Randal is configured via a YAML file. Accepted filenames (checked in order):

  1. randal.config.yaml
  2. randal.config.yml
  3. randal.yaml

Or specify explicitly: randal --config path/to/config.yaml <command>

All string values support ${ENV_VAR} substitution from the process environment. The parsed config is deeply frozen (immutable at runtime).


📋 Top-Level

FieldTypeDefaultRequiredDescription
namestringYesInstance name. Used for memory index naming and identification.
versionstring"0.1"NoConfig schema version.
possestringNoTeam/group identifier for multi-agent coordination.
managed.bootstrapobjectNoOptional published-artifact bootstrap metadata. Used only for runtime startup, never as Config Studio state.
capabilitiesstring[][]NoCapabilities this agent has access to. Controls MCP server wiring and tool permissions in the generated opencode.json. See Capabilities below.

Published Artifact Bootstrap

managed.bootstrap is optional and does not change normal local startup defaults.

managed:
  bootstrap:
    controlPlaneUrl: https://control-plane.example
    workspaceId: workspace-123
    versionId: version-7
    artifactUrl: https://control-plane.example/api/workspaces/workspace-123/setup-versions/version-7/config-export

Rules:

  • only published artifacts may be referenced here
  • this field is runtime bootstrap metadata, not Studio draft state
  • the compiled runtime behavior must remain unchanged by default when managed.bootstrap is absent

🧩 Capabilities

The capabilities field declares what this agent can do. It drives the opencode.json generation performed by randal setup — each capability maps to MCP servers and tool permissions that are included in the generated config.

CapabilityMCP Server(s)Description
memorymemoryPersistent memory via Meilisearch. Also enabled automatically when memory.store is configured.
search or tavilytavilyWeb search via Tavily. Also enabled when TAVILY_API_KEY is in the environment.
videovideoVideo generation and composition tools. Also enabled when a video tool is in tools[].
image-genimage-genImage generation tools. Also enabled when an image-gen tool is in tools[].
schedulerschedulerHeartbeat and cron scheduling. Also enabled automatically when heartbeat.enabled or cron.jobs is non-empty.

Example:

# Explicitly declare all capabilities this agent uses
capabilities:
  - memory
  - search
  - video
  - image-gen
  - scheduler

If capabilities is omitted or empty, MCP servers are still wired based on other config sections (e.g., memory.store, heartbeat.enabled, tools[]). The capabilities field provides explicit control and is the recommended way to declare what your agent can do.

After changing capabilities, run randal setup to regenerate opencode.json.


🔄 Prompt Resolution

All prompt-bearing config fields (identity.persona, identity.systemPrompt, identity.rules, identity.knowledge, heartbeat.prompt, cron.jobs.*.prompt, tools.*.skill) support a unified three-layer resolution system. Values are resolved at prompt build time, not at config parse time.

Resolution Layers

Values are checked in this order:

LayerDetectionBehavior
Layer 3: Code ModuleEnds with .ts or .jsDynamic import, call default(ctx) export, return result string.
Layer 1: File ReferenceStarts with ./ or /, or ends with .md or .txtRead file, then apply {{var}} template interpolation.
Layer 0: Inline PassthroughEverything elseReturn as-is, no transformation.

Layer 3 (code module) is checked first, so ./foo.ts is treated as a code module, not a file reference.

Template Interpolation ({{var}})

File-loaded content (Layer 1) supports {{key}} template interpolation using variables from identity.vars plus auto-populated values:

VariableSourceDescription
nameconfig.nameAgent instance name
versionconfig.versionConfig schema version
dateAuto-generatedCurrent ISO date (e.g., 2026-03-15)
(user-defined)identity.varsCustom key-value pairs from config

User-defined vars take precedence over auto-populated vars with the same name. Unknown {{key}} placeholders are left as-is.

Important: {{var}} interpolation only applies to file-loaded content (Layer 1). It does not apply to code module output (Layer 3) or inline YAML values (Layer 0). Inline YAML values already have ${ENV_VAR} substitution at config parse time.

Code Module Contract

Code modules (.ts/.js) must export a default function:

import type { PromptContext } from "@randal/core";

// For string fields (persona, systemPrompt, heartbeat.prompt, etc.)
export default function(ctx: PromptContext): string | Promise<string>;

// For rules arrays, modules may also return string[]
export default function(ctx: PromptContext): string | string[] | Promise<string | string[]>;

PromptContext Interface

interface PromptContext {
  basePath: string;              // Directory containing randal.config.yaml
  vars?: Record<string, string>; // Template variables (identity.vars + auto-populated)
  configName?: string;           // From config.name
}

Examples

Inline string (Layer 0):

identity:
  persona: "You are a helpful AI assistant."

File reference with template vars (Layer 1):

identity:
  persona: ./IDENTITY.md
  vars:
    name: my-agent
    company: Acme Corp

Where IDENTITY.md contains:

# {{name}}
You are {{name}}, built by {{company}}.

Code module (Layer 3):

identity:
  systemPrompt: ./instructions.ts

Where instructions.ts contains:

import type { PromptContext } from "@randal/core";

export default function(ctx: PromptContext): string {
  const isWeekend = [0, 6].includes(new Date().getDay());
  return isWeekend
    ? "Focus on maintenance tasks today."
    : "Prioritize active development tasks.";
}

Mixed rules array:

identity:
  rules:
    - "NEVER delete data"
    - ./safety-rules.md
    - ./dynamic-rules.ts

File entries are split by newlines into individual rules. Code modules may return string (split by newlines) or string[].


🪪 identity

Agent identity and knowledge configuration. Defaults to {} if omitted.

The primary source-machine persona authoring flow is bun run scripts/persona-preseed.ts, which launches an OpenCode interview and writes both review artifacts and a Mac-mini-ready preseed bundle locally. randal init --persona remains available as a secondary local artifact writer.

The durable persona artifacts in that flow are:

  • .randal/identity/persona.identity.yaml: structured YAML identity bundle intended to be the source of truth
  • .randal/identity/PERSONA.md: readable persona document for human review
  • IDENTITY.md: rendered compatibility artifact for existing prompt-resolution flows
  • identity.ts: generated and manifest-tracked when the TypeScript identity compatibility path is selected

These generated files are tracked in .randal/managed-artifacts.yaml as Randal-managed outputs.

randal setup --preseed <file> accepts a trusted local wrapper bundle authored on a source machine and copied to the target Mac mini.

schemaVersion: "1"
kind: randal-preseed
trustedLocalOnly: true
authoring:
  sourceMachine: this-machine
  targetMachine: mac-mini
setup:
  configPath: randal.config.yaml
files:
  - scope: project
    path: randal.config.yaml
    kind: config
    content: |
      name: mac-mini-randal
      runner:
        workdir: .

Supported v1 keys:

  • schemaVersion: must be "1"
  • kind: must be randal-preseed
  • trustedLocalOnly: must be true
  • authoring.sourceMachine / authoring.targetMachine: optional handoff metadata for the source-machine -> target-machine workflow
  • setup.configPath: project-local config path to hand to the normal setup pipeline after materialization
  • setup.outputDir: optional OpenCode output override
  • files[]: explicit file payloads to materialize before setup runs

Each files[] entry supports:

  • scope: project or home
  • path: relative path inside that scope
  • kind: artifact label recorded in .randal/managed-artifacts.yaml
  • content: plaintext file contents

Trusted-local-only note: plaintext secrets are supported in content, including .env payloads, but this format is intentionally not encrypted or remotely fetched in v1.

FieldTypeDefaultRequiredDescription
identity.personastringNoAgent persona injected into system prompt. Supports prompt resolution.
identity.systemPromptstringNoAdditional system instructions appended to prompt. Supports prompt resolution.
identity.knowledgestring[][]NoGlob patterns or file paths for knowledge files. Contents loaded and injected into system prompt. Supports prompt resolution.
identity.rulesstring[][]NoRules injected as a numbered list into system prompt. Each entry supports prompt resolution. File entries are split by newlines into individual rules.
identity.varsRecord<string, string>{}NoUser-defined template variables. Available as {{key}} in file-loaded prompts. Auto-populated vars: name, version, date.

🎯 runner

Agent execution configuration. The runner section is required.

Cutover note: runner.compaction.* was removed. The only supported execution model is a brain-managed session backed by plan files plus canonical .opencode/loop-state.json state.

FieldTypeDefaultRequiredDescription
runner.defaultAgent"opencode" | "mock""opencode"NoDefault agent adapter.
runner.defaultModelstring"anthropic/claude-sonnet-4"NoDefault model identifier passed to the agent CLI. For Railway GPT Pro / OpenCode OAuth deployments, set this to openai/gpt-5.4.
runner.defaultMaxIterationsnumber20NoMaximum iterations per job before marking as failed.
runner.workdirstringYesWorking directory for agent processes.
runner.allowedWorkdirsstring[]NoAllowed working directories. If set, job workdirs are validated against this list before the agent is spawned. A job whose workdir is not within one of these directories is rejected with an error. Path matching is prefix-based and paths are resolved to absolute before comparison. Recommended for container-based deployments to restrict agent filesystem access.
runner.completionPromisestring"DONE"NoCompletion marker tag. The runner looks for <promise>DONE</promise> in agent output.
runner.struggle.noChangeThresholdnumber3NoIterations with no file changes before the agent is considered stuck.
runner.struggle.maxRepeatedErrorsnumber3NoConsecutive non-zero exit codes before the agent is considered stuck.
runner.opencodeAuth.mode"disabled" | "openai-oauth""disabled"NoEnables OpenCode auth-file materialization before opencode run. Use openai-oauth for the Railway subscription-auth flow.
runner.opencodeAuth.authFileEnvstring"OPENCODE_AUTH_JSON"NoEnvironment variable name that contains the JSON payload written to the OpenCode auth file at runtime.
runner.opencodeAuth.authFilePathstringOpenCode default auth path under ~/.local/share/opencode/auth.jsonNoOptional override for where the runner writes the materialized auth file. Leave unset unless your runtime needs a different location.
runner.opencode.variantstringNoOptional OpenCode CLI variant flag. For GPT Pro Railway usage, set xhigh.

Loop-State Cutover

  • .opencode/loop-state.json is the canonical durable build-state file.
  • Build entries are keyed by plan slug, not by transient runner job ID.
  • Corrupted loop-state files now raise explicit errors instead of being silently reset.
  • Legacy runner-written statuses such as active, completed, and errored are no longer valid in canonical loop-state.

OpenCode OAuth Example

Use this when you want a headless Railway deploy to reuse a one-time local OpenCode login:

runner:
  defaultAgent: opencode
  defaultModel: openai/gpt-5.4
  workdir: /app/workspace
  opencodeAuth:
    mode: openai-oauth
    authFileEnv: OPENCODE_AUTH_JSON
  opencode:
    variant: xhigh

Bootstrap flow:

  1. Run opencode auth login locally.
  2. Copy ~/.local/share/opencode/auth.json into a secret named OPENCODE_AUTH_JSON.
  3. Start Railway normally; the runner materializes the auth file before spawning OpenCode.

Security handling:

  • Treat OPENCODE_AUTH_JSON like a credential.
  • Store it in secret management only.
  • Never commit it to the repo or paste it into issue threads.
  • Avoid logging the JSON payload or writing ad hoc debug copies of the file.

Alternatives remain supported: API-key flows via OPENROUTER_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY still work without runner.opencodeAuth.*.


🔐 credentials

Environment and secret management. Defaults to {} if omitted.

FieldTypeDefaultRequiredDescription
credentials.envFilestring"./.env"NoPath to .env file (relative to config file).
credentials.allowstring[][]NoAllowlist of variable names to load from the .env file. Only listed vars are passed to the agent.
credentials.inheritstring[]["PATH", "HOME", "SHELL", "TERM"]NoEnvironment variables inherited from the parent process.

🔗 services

Named external service bindings with declarative credential delivery. Defaults to {} if omitted.

Each service is a named entry with a credential delivery mechanism and optional audit logging.

FieldTypeDefaultRequiredDescription
services.<name>.descriptionstringNoHuman-readable purpose of this service.
services.<name>.credentials.type"env" | "file" | "ambient" | "script" | "none"YesCredential delivery mechanism.
services.<name>.auditbooleanfalseNoLog when agent spawns with this service's credentials.

Credential Types

type: env — Inject environment variables directly.

FieldTypeDescription
varsRecord<string, string>Key-value map of env vars to inject. Supports ${ENV_VAR} substitution.

type: file — Copy a credential file and set env vars pointing to it.

FieldTypeDescription
filestringSource file path (relative to config).
mountAsstringDestination path where the file is copied.
varsRecord<string, string>Env vars to inject (typically pointing to the mounted file).

type: ambient — Keep existing host binaries and config dirs available.

FieldTypeDescription
binariesstring[]Binary names to ensure stay in PATH.
pathsstring[]Config directories the agent needs access to.

type: script — Run a script before each job and capture output as credentials.

FieldTypeDescription
commandstringScript to execute (relative to config).
varsRecord<string, string>Map of var names. Use "stdout" as the value to capture script output.
ttlnumberRe-run interval in seconds. Cached until expired.

type: none — Explicitly block access to a service.

FieldTypeDescription
binariesstring[]Binary names to strip from PATH.
varsstring[]Env var names to remove from the child process environment.

Example:

services:
  github:
    description: "GitHub via provisioned PAT"
    credentials:
      type: env
      vars:
        GH_TOKEN: ${GITHUB_PAT}
        GITHUB_TOKEN: ${GITHUB_PAT}
    audit: true

  gcloud:
    description: "Google Cloud via service account"
    credentials:
      type: file
      file: ./secrets/gcp-sa-key.json
      mountAs: /tmp/gcp-creds.json
      vars:
        GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-creds.json

  aws:
    description: "AWS explicitly blocked"
    credentials:
      type: none
      binaries: [aws, aws-vault]
      vars: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN]

  internal-api:
    description: "Internal API via rotating token"
    credentials:
      type: script
      command: ./scripts/get-api-token.sh
      vars:
        INTERNAL_API_TOKEN: stdout
      ttl: 3600

🔒 sandbox

Process isolation configuration. Controls how aggressively Randal restricts the agent child process environment. Defaults to { enforcement: "none" } (current behavior preserved).

FieldTypeDefaultDescription
sandbox.enforcement"none" | "env-scrub""none"Enforcement level. none = no restrictions. env-scrub = apply PATH filtering, home access restrictions, and env scrubbing.
sandbox.pathFilter.mode"inherit" | "allowlist" | "blocklist""inherit"How to filter the PATH variable.
sandbox.pathFilter.allowstring[][]PATH prefixes to keep (when mode is allowlist). Supports ~ expansion.
sandbox.pathFilter.blockstring[][]Binary names whose containing dirs are removed (when mode is blocklist).
sandbox.homeAccess.sshbooleantrueAllow ~/.ssh access. When false: sets GIT_SSH_COMMAND=/bin/false, unsets SSH_AUTH_SOCK.
sandbox.homeAccess.gitconfigbooleantrueAllow ~/.gitconfig credential helpers. When false: sets GIT_CONFIG_GLOBAL=/dev/null.
sandbox.homeAccess.dockerbooleantrueAllow ~/.docker/config.json. When false: sets DOCKER_CONFIG=/dev/null.
sandbox.homeAccess.awsbooleantrueAllow ~/.aws. When false: unsets all AWS_* vars, sets null config paths.

When any homeAccess flag is false, a temporary HOME directory is created with only the allowed config dirs symlinked in. The temp dir is cleaned up after the job completes.

Example:

sandbox:
  enforcement: env-scrub
  pathFilter:
    mode: allowlist
    allow: [/usr/bin, /usr/local/bin, ~/.bun/bin]
  homeAccess:
    ssh: false
    gitconfig: false
    aws: false

⬆️ updates

Self-update configuration. Defaults to { autoCheck: false, channel: "stable" }.

FieldTypeDefaultDescription
updates.autoCheckbooleanfalseCheck for updates on randal serve startup.
updates.channel"stable" | "latest""stable"stable = follow semver tags. latest = follow main HEAD.

Example:

updates:
  autoCheck: true
  channel: stable

📡 gateway

Server and channel configuration. Defaults to {} if omitted.

FieldTypeDefaultRequiredDescription
gateway.channelsChannel[][]NoCommunication channels. At least one HTTP channel is needed for randal serve.

🌐 HTTP Channel

FieldTypeDefaultRequiredDescription
type"http"YesChannel type discriminator.
portnumber7600NoHTTP server port.
authstringYesBearer token for API authentication. Use ${RANDAL_API_TOKEN} to read from env.

💬 Discord Channel

FieldTypeDefaultRequiredDescription
type"discord"YesChannel type discriminator.
tokenstringYesDiscord bot token.
allowFromstring[]NoDiscord user ID allowlist.
guildIdstringNoGuild ID for instant slash command registration. If omitted, uses global registration (~1 hour propagation).
serversDiscordServer[][]NoPer-server configuration. See Discord Guide.

Setup: See the Discord Integration Guide for full setup instructions.

🎙️ Voice Channel

FieldTypeDefaultRequiredDescription
type"voice"YesChannel type discriminator.
allowFromstring[][]NoLegacy trusted admin caller list. Preserved as an admin-trust input for PSTN callers.
access.trustedCallersstring[][]NoAdditional trusted admin caller E.164 numbers.
access.unknownInbound"deny" | "external""deny"NoUnknown inbound caller handling. deny is the secure default.
access.defaultExternalGrantsstring[][]NoDefault explicit grants for external voice sessions when no per-session override is supplied.

Other Channels

Additional channel types (iMessage, Telegram, Slack, Email, WhatsApp, Signal) are community-maintained. See the Channel Adapters Guide for details and packages/core/src/config.ts for their config schemas.


🧠 memory

Memory system configuration. Defaults to {} if omitted.

FieldTypeDefaultRequiredDescription
memory.store"meilisearch""meilisearch"NoStorage backend. Meilisearch is the only supported backend.
memory.urlstring"http://localhost:7701"NoMeilisearch server URL.
memory.apiKeystringNoMeilisearch API key.
memory.indexstring"memory-{name}"NoMeilisearch index name. Falls back to memory- + config name.
memory.autoInject.enabledbooleantrueNoAuto-inject relevant memory into agent system prompt.
memory.autoInject.maxResultsnumber5NoMaximum memory results to inject per iteration.
memory.sharing.publishTostringNoShared Meilisearch index name to publish learnings to.
memory.sharing.readFromstring[][]NoShared Meilisearch index names to read from.

🔌 Embedder Configuration

Nested under memory.embedder. Defaults to { type: "builtin" }.

Builtin (default, no configuration needed):

memory:
  embedder:
    type: builtin

OpenAI:

FieldTypeDefaultRequired
type"openai"Yes
modelstring"text-embedding-3-large"No
apiKeystringYes

OpenRouter:

FieldTypeDefaultRequired
type"openrouter"Yes
modelstringYes
apiKeystringYes

Ollama:

FieldTypeDefaultRequired
type"ollama"Yes
modelstringYes
urlstring"http://localhost:11434"No

🛠️ tools

Array of external tool definitions. Defaults to [].

FieldTypeDefaultRequiredDescription
tools[].namestringYesTool name.
tools[].binarystringYesExecutable binary name (must be on PATH).
tools[].skillstringNoPath to a skill documentation markdown file.
tools[].platforms("darwin" | "linux" | "win32")[]["darwin", "linux"]NoPlatforms where this tool is available.

💰 tracking

Cost tracking configuration. Defaults to {}.

FieldTypeDefaultRequiredDescription
tracking.tokenPricingRecord<string, { input: number, output: number }>{}NoPer-model token pricing in dollars per million tokens. Keys are model identifiers. Example: Claude Sonnet at $3/M input, $15/M output → input: 3.00, output: 15.00.

💓 heartbeat

The heartbeat is a periodic "check in and use your judgment" primitive. The agent wakes at a configured interval, reads a prompt/checklist, and decides whether anything needs attention.

FieldTypeDefaultDescription
heartbeat.enabledbooleanfalseEnable periodic heartbeat.
heartbeat.everystring"30m"Interval between ticks. Supports: "15m", "1h", "2h30m".
heartbeat.promptstring"./HEARTBEAT.md"Path to heartbeat prompt file, or inline prompt string.
heartbeat.activeHours.startstringStart of active window (HH:MM format).
heartbeat.activeHours.endstringEnd of active window (HH:MM format).
heartbeat.activeHours.timezonestring"UTC"Timezone for active hours.
heartbeat.targetstring"none"Where to send heartbeat results.
heartbeat.modelstringOverride model for heartbeat (use a cheap model like claude-haiku-4).

Example:

heartbeat:
  enabled: true
  every: 30m
  prompt: ./HEARTBEAT.md
  activeHours:
    start: "08:00"
    end: "22:00"
    timezone: "America/Denver"
  model: anthropic/claude-haiku-4

📅 cron

Precise scheduled tasks with three schedule formats.

FieldTypeDefaultDescription
cron.jobs.<name>.schedulestring | {every} | {at}Cron expression, interval, or one-shot time.
cron.jobs.<name>.promptstringThe prompt to execute.
cron.jobs.<name>.execution"main" | "isolated""isolated"main: queue for next heartbeat. isolated: run as standalone job.
cron.jobs.<name>.modelstringOverride model for this job.
cron.jobs.<name>.announcebooleanfalseWhether to announce results to channels.

Schedule formats:

  • Cron expression: "0 7 * * *" (5-field: minute hour day-of-month month day-of-week)
  • 🔄 Interval: { every: "30m" } (repeating interval)
  • 🎯 One-shot: { at: "2026-03-15T14:00:00Z" } (fires once, then marked completed)

Example:

cron:
  jobs:
    morning-briefing:
      schedule: "0 8 * * *"
      prompt: "Review pending tasks. Compile a morning status."
      execution: isolated
      announce: true
    periodic-check:
      schedule: { every: "1h" }
      prompt: "Check system health."
      execution: main
    deploy-reminder:
      schedule: { at: "2026-03-15T14:00:00Z" }
      prompt: "Remind the team about the deployment."
      execution: isolated

🪝 hooks

External event triggers via HTTP webhooks.

FieldTypeDefaultDescription
hooks.enabledbooleanfalseEnable webhook endpoints.
hooks.tokenstringAuth token for webhook requests. If not set, hooks are disabled.
hooks.pathstring"/hooks"URL path prefix for hook endpoints.

When enabled, two endpoints are mounted:

  • POST /hooks/wake — Wake the agent with a message. Modes: "now" (immediate heartbeat) or "next-heartbeat" (queued).
  • POST /hooks/agent — Submit a job or queue a message. Modes: "now" (isolated job) or "next-heartbeat" (queued).

All requests require Authorization: Bearer <token> or x-randal-token: <token>.

Example:

hooks:
  enabled: true
  token: "${RANDAL_HOOK_TOKEN}"
# Trigger immediate heartbeat with context
curl -X POST http://localhost:7600/hooks/wake \
  -H "Authorization: Bearer $RANDAL_HOOK_TOKEN" \
  -d '{"text": "New VIP email received", "mode": "now"}'

# Queue for next heartbeat
curl -X POST http://localhost:7600/hooks/wake \
  -H "Authorization: Bearer $RANDAL_HOOK_TOKEN" \
  -d '{"text": "Low priority notification", "mode": "next-heartbeat"}'

📁 Example Configs

🏁 Minimal (local one-shot)

name: my-agent
runner:
  workdir: ~/dev/my-project

💻 Personal Dev Agent

name: dev-agent
identity:
  persona: "Senior TypeScript engineer"
  rules:
    - "Write tests for all new functions"
    - "Use Biome for formatting"
runner:
  defaultAgent: opencode
  defaultModel: anthropic/claude-sonnet-4
  defaultMaxIterations: 30
  workdir: ~/dev/my-project
credentials:
  envFile: ./.env
  allow: [ANTHROPIC_API_KEY]
gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"
memory:
  store: meilisearch

🏭 Production Agent with Meilisearch

name: support-agent
posse: production
identity:
  persona: "Customer support specialist"
  knowledge:
    - ./knowledge/help-center/*.md
    - ./knowledge/faq.md
  rules:
    - "Never delete production data"
    - "Never expose PII"
    - "Always escalate payment issues"
runner:
  defaultAgent: opencode
  defaultModel: claude-sonnet-4
  workdir: /home/node/workspace
credentials:
  envFile: ./.env
  allow: [ANTHROPIC_API_KEY, SUPABASE_URL, SUPABASE_AGENT_KEY]
gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"
memory:
  store: meilisearch
  url: http://meilisearch.internal:7700
  apiKey: "${MEILI_MASTER_KEY}"
  index: memory-support
  sharing:
    publishTo: shared
    readFrom: [shared]

🛠️ Multi-Tool Agent

name: ops-agent
runner:
  defaultAgent: opencode
  workdir: ~/dev/infra
credentials:
  envFile: ./.env
  allow: [ANTHROPIC_API_KEY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY]
  inherit: [PATH, HOME, SHELL, TERM, SSH_AUTH_SOCK]
gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"
tools:
  - name: terraform
    binary: terraform
    skill: ./skills/terraform.md
    platforms: [darwin, linux]
  - name: kubectl
    binary: kubectl
    platforms: [darwin, linux]
tracking:
  tokenPricing:
    "anthropic/claude-sonnet-4":
      input: 0.000003
      output: 0.000015

💬 Personal Assistant with Discord

name: assistant
identity:
  persona: "Personal dev assistant reachable via Discord."
runner:
  defaultAgent: opencode
  defaultModel: claude-sonnet-4
  workdir: ~/dev
credentials:
  envFile: ./.env
  allow: [ANTHROPIC_API_KEY]
  inherit: [PATH, HOME, SHELL, TERM]
gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"
    - type: discord
      token: "${DISCORD_BOT_TOKEN}"
      allowFrom: ["123456789012345678"]
memory:
  store: meilisearch
  url: http://localhost:7701
  apiKey: "${MEILI_MASTER_KEY}"
  index: memory-assistant
  autoInject:
    enabled: true
    maxResults: 5

🎙️ Voice & Video

FieldTypeDefaultDescription
voice.enabledbooleanfalseEnable voice/video features
voice.livekit.urlstring""LiveKit server URL
voice.livekit.apiKeystring""LiveKit API key
voice.livekit.apiSecretstring""LiveKit API secret
voice.twilio.accountSidstring""Twilio Account SID
voice.twilio.authTokenstring""Twilio Auth Token
voice.twilio.phoneNumberstring""Twilio phone number
voice.stt.provider"deepgram" | "whisper" | "assemblyai""deepgram"Speech-to-text provider
voice.stt.modelstringSTT model name
voice.stt.apiKeystring""STT provider API key
voice.tts.provider"elevenlabs" | "cartesia" | "openai" | "edge""elevenlabs"Text-to-speech provider
voice.tts.voicestringTTS voice ID
voice.tts.apiKeystring""TTS provider API key
voice.turnDetection.mode"auto" | "manual""auto"Turn detection mode
voice.video.enabledbooleanfalseEnable video features
voice.video.visionModelstring"gpt-4o"Vision model for processing screen shares
voice.video.publishScreenbooleanfalsePublish agent screen as video track
voice.video.recordSessionsbooleanfalseRecord voice/video sessions
voice.video.recordPathstring"./recordings"Path to save recordings

Browser/media voice requires LiveKit + STT + TTS configuration. PSTN calling requires Twilio in addition to that browser/media baseline.


🌐 Multi-Instance Mesh

FieldTypeDefaultDescription
mesh.enabledbooleanfalseEnable mesh orchestration
mesh.rolestring (enum)Agent's broad domain. One of: product-engineering, platform-infrastructure, security-compliance, data-intelligence, design-experience, content-communications, revenue-growth, customer-operations, strategy-finance, legal-governance. Used for routing pre-filter and analytics.
mesh.expertisestring | {file, additional?}Rich natural language expertise description. Supports inline string, file reference ({ file: "./profile.md" }), or combined ({ file: "./profile.md", additional: "Also knows X" }). Used for semantic routing when embeddings are available.
mesh.endpointstringThis instance's HTTP endpoint for peer communication
mesh.routingWeights.expertisenumber0.4Weight for semantic expertise matching in routing (2-tier: vector cosine → role match)
mesh.routingWeights.reliabilitynumber0.3Weight for reliability score in routing
mesh.routingWeights.loadnumber0.2Weight for current load in routing
mesh.routingWeights.modelMatchnumber0.1Weight for model availability in routing

📊 Analytics & Self-Learning

FieldTypeDefaultDescription
analytics.enabledbooleanfalseEnable analytics engine
analytics.autoAnnotationPromptbooleantruePrompt for annotations after job completion
analytics.feedbackInjectionbooleantrueInject empirical guidance into system prompts
analytics.recommendationFrequency"daily" | "weekly" | "on-demand""on-demand"How often to generate recommendations
analytics.domainKeywordsRecord<string, string[]>(see defaults)Custom keyword-to-domain mapping. Default domains: product-engineering, platform-infrastructure, security-compliance, data-intelligence, design-experience, content-communications, revenue-growth, customer-operations, strategy-finance, legal-governance. Override or extend per domain.
analytics.agingHalfLifenumber30Half-life in days for annotation aging

🌍 Browser Automation

FieldTypeDefaultDescription
browser.enabledbooleanfalseEnable browser automation
browser.headlessbooleantrueRun browser in headless mode
browser.profileDirstringDirectory for browser profile persistence
browser.sandboxbooleanfalseRun browser in sandbox container
browser.viewport.widthnumber1280Browser viewport width
browser.viewport.heightnumber720Browser viewport height
browser.timeoutnumber30000Default timeout in milliseconds

🔄 Runner Extensions

MCP Server

FieldTypeDefaultDescription
runner.mcpServer.enabledbooleanfalseEnable MCP server for bidirectional agent communication
runner.mcpServer.portnumber7601MCP server port
runner.mcpServer.toolsstring[]["memory_search", "context", "status", "skills", "annotate"]Tools to expose

💬 Additional Channel Types

The gateway supports additional channel types beyond HTTP and Discord. These channels have adapter implementations in the codebase but are community-maintained and not wired into the gateway by default. See the Channel Adapters Guide for details.

Available community channel types: telegram, slack, email, whatsapp, signal, voice. Config schemas remain in packages/core/src/config.ts for reference.