Skip to content

Config schema

How to read this page

This page is the source of truth for every field in agentx.json. Most operators never edit it directly — the dashboard's Settings page covers the common changes. Use this when (a) the dashboard doesn't expose what you need yet, (b) you're version-controlling agentx.json and want to know what each field means, or (c) you're debugging a config validation error. Selected fields are annotated with a "When to change" line where the default isn't obvious.

Every field in agentx.json. Source of truth: src/daemon/config.ts and src/business/config.ts.

Environment variables are expanded inline: ${MY_TOKEN}process.env.MY_TOKEN. A .env file in the working directory is auto-loaded.

Top-level shape

json
{
  "node":          { "id": "...", "name": "...", "bind": "0.0.0.0:19900", "defaultAgent": "..." },
  "providers":     { "<provider>": { "apiKey": "", "defaultModel": "", "baseUrl": "" } },
  "agents":        { "<agentId>":  { ... } },
  "channels":      { "telegram": {...}, "whatsapp": {...}, "discord": {...}, "slack": {...}, "gitlab": {...}, "github": {...}, "webrtc": {...} },
  "crons":         { "<cronId>":   { ... } },
  "services":      { "<serviceId>":{ ... } },
  "session":       { "staleMinutes": 45, "maxTurnsPerSession": 15, "tierTwoThresholdTokens": 195000, "contextStrategy": "layered" },
  "notifications": { ... },
  "mesh":          { "enabled": false, "peers": [], ... },
  "boards":        [ { ... } ],
  "dashboard":     { "enabled": false, "port": 4202, ... },
  "graph":         { "enabled": false, ... },
  "workflows":     { "enabled": false, "dir": ".agentx/workflows", "editor": "edit" },
  "webhooks":      [ { "id": "...", "source": "gitlab", "agentId": "...", ... } ],
  "plugins":       [ "<npm-package-name>", ... ],
  "business":      { ... }
}

node

FieldTypeDefaultPurpose
idstringShort slug identifying this node (used in mesh agent cards)
namestringHuman-readable node name
bindstring127.0.0.1:18800HTTP bind address. Use 0.0.0.0:… for mesh or multi-interface
defaultAgentstringAgent for endpoints that don't specify one (e.g. POST /ask)

providers

Keyed by provider name (claude, openai, ollama, …). Each entry:

FieldTypePurpose
apiKeystringUsed by API-backed tiers (sdk, orchestrator)
defaultModelstringModel id if the agent doesn't specify one
baseUrlstringOverride endpoint (Ollama, proxies)

agents.<id>

FieldTypeDefaultPurpose
namestringDisplay name
workspacestringDirectory with agent instructions, skills, MCP config
tierclaude-code | codex-cli | sdk | orchestratorclaude-codeExecution strategy
providerstringFor sdk/orchestrator: which providers[] entry
modelstringModel id
systemPromptstringInline override (normally lives in CLAUDE.md)
mentionsstring[][]@handles that route to this agent
intentsstring[][]Phase 5 — typed capabilities. Free-form list of intent strings this agent is allowed to handle. When set, the org-chart canHandle check rejects dispatches with intents not in this list. Empty/unset = permissive (handles any intent). Examples: ["issue.opened", "merge_request.opened"], ["cron.fired", "message.received"]. When to change: set this to a comma-separated list when the agent should ONLY handle specific intents (e.g. ["issue.opened", "merge_request.opened"] for a code-review agent that shouldn't pick up Telegram chitchat). Leave blank for permissive (default)
maxDelegationDepthnumber (0–50)5Phase 8 — capability-bounded security. Max distinct upstream agents in the delegation chain on the same (project, subject) before a dispatch to this agent is refused. The ledger walker counts distinct agents across recent decisions on the subject. Set to 0 to disable for an agent that's always called as the bottom of a chain. When to change: lower this (e.g. 2) for agents at the bottom of a chain — prevents cascade loops where A → B → A. Default 5 is fine for most teams
mcpRecord<string, McpServer>Per-agent MCP servers. Synced to <workspace>/.mcp.json at boot. Operator edits to .mcp.json are respected — see agent-mcp.ts
contextStrategy"layered" | "planner"inherited from session.contextStrategyPer-agent override of the global context-assembly strategy
contextReferencesboolfalseWhen true, the registry resolves references-recipes for this agent's workspace and renders a deterministic [Verified References] block at priority 4.7. Off by default — flip on per agent (e.g. pm-ksi, devops-agent) once a references/ registry exists. When to change: turn on for agents that need stable, cited facts (PMs, devops) — surfaces a [Verified References] block in the prompt. Off by default because not every agent has a references/ registry
maxConcurrentnumber1Parallel turns allowed
maxExecutionMinutesnumber (1–240)20Hard wall-clock cap on a single Claude Code invocation. Exceeding sends SIGTERM (exit 143). Bump for devops/coder agents that run long investigations or multi-file refactors
permissionModestringdefaultClaude Code permission mode (default, acceptEdits, plan, bypassPermissions)
queueModecollect | followup | dropcollectBehavior when messages arrive during a turn
accessprivate | publicprivateReachability via the public API. public lets external apps POST /api/public/agents/<id>/messages with a scoped token (agent:<id> or agent:*)
heartbeat.enabledboolfalseEnable periodic in-session check-ins
heartbeat.intervalMinutesnumber30
heartbeat.promptstringstock prompt
heartbeat.channelstringheartbeatVirtual channel tag

Execution tiers

TierBackendContinuityNotes
claude-codeClaude Code CLIclaudeSessionId, optional persistent processBest-supported tool-using path. Uses workspace .claude/, MCP, skills/hooks where present
codex-cliCodex CLIcodexSessionId from Codex thread_idUses codex exec for fresh runs and codex exec resume for warm runs. AgentX sends a compact context budget to avoid prompt bloat
sdkAnthropic Agent SDKAgentX-rendered bounded historyAPI-key path for Anthropic Agent SDK; less native-session/usage metadata than CLI tiers today
orchestratorAgentX generate() loopAgentX-rendered bounded historyProvider-agnostic path for supported non-CLI providers

Every tier goes through the same AgentX registry: routing, queueing, context assembly, freshSession, task history, traces, and final channel delivery are tier-independent. See Agent execution tiers for the detailed behavior contract and current implementation plan.

channels.telegram

FieldTypeDefault
enabledboolfalse
accounts.<name>.tokenstringBot token (env expansion OK)
accounts.<name>.agentBindingstringAgent id this bot speaks for
accounts.<name>.allowFromstring[]Per-account sender allowlist (overrides global)
accounts.<name>.pollInboundbooltrueWhen false, daemon keeps the token registered for outbound but skips long-polling. Use on remote nodes where the bound agent lives elsewhere — otherwise two daemons race on the same getUpdates cursor
policy.dmpair | blockpairDM policy
policy.groupmention-required | allmention-requiredGroup policy
policy.allowFromstring[]Global sender allowlist (user id, chat id, or @username). Closed by default — when neither global nor per-account is set, every inbound is dropped

channels.whatsapp

FieldTypeDefault
enabledboolfalse
sessionDirstring.agentx/whatsapp-sessions
defaultAgentstring
allowFromstring[]Contact allowlist
routes[].contact / .groupstringMatch
routes[].agentstringTarget agent

channels.whatsapp.ingest

Data-source ingestion — turns WhatsApp from a messaging-only channel into a source that seeds the wiki with contact/group metadata (and optionally bounded message windows) per an explicit allowlist. See WhatsApp as a data source for the full walkthrough.

FieldTypeDefaultPurpose
enabledboolfalseMaster switch. Default-deny
mode"metadata-only" | "messages""metadata-only"metadata-only pulls contact/group info; messages additionally pulls the last messageCap messages per allowlisted chat
allowContactsstring[][]Phone numbers or JIDs, substring match (same semantics as allowFrom)
allowGroupsstring[][]Group JIDs, substring match
denyContactsstring[][]Wins over allow
denyGroupsstring[][]Wins over allow
messageCapnumber (1–500)50Per-chat message cap when mode = "messages"
historyDaysnumber (1–365)30Max age of messages to include in a window
contactRefreshDaysnumber (1–90)7Skip re-writing a contact entry unless this many days have passed and the profile hash differs
throttle.minMsBetweenCallsnumber (≥100)1500Minimum spacing between live Baileys reads (ban-safety)
throttle.maxCallsPerMinutenumber (≥1)20Per-minute cap on live reads
throttle.maxChatsPerSweepnumber (≥1)25Per-sweep cap on targets (protects personal accounts)
retentionDaysnumber (≥0)0Purge absorbed raw entries older than this; 0 = never (phase 2)

channels.discord / channels.slack

FieldType
enabledbool
token (Discord) / botToken + appToken (Slack)stringSlack: xoxb-… bot token + xapp-… app-level token (Socket Mode)
agentBindingstring

channels.gitlab

FieldTypeDefault
enabledboolfalse
hoststringhttps://gitlab.com
webhookPortnumber18810
webhookSecretstring
tokenstringFallback API token
routes[].project / .agentstring
agentMappings[].agentIdstring
agentMappings[].gitlabUsernamesstring[][]
agentMappings[].keywordsstring[][]
agentMappings[].tokenstringPer-agent GitLab PAT
agentMappings[].nodestringMesh peer for a remote agent — forces username→agent resolution to this mapping

channels.github

FieldTypeDefault
enabledboolfalse
tokenstringPAT (use ${GITHUB_TOKEN}); tokenFile is an alternative — read first line at startup
appId / clientId / privateKeyFilevariousGitHub App auth (JWT issuer); clientId is preferred per GitHub's updated docs
webhookSecretstringValidates X-Hub-Signature-256
routes[].repo / .agentstring"owner/repo" or "*" for default
agentMappings[].agentId / .githubUsernames / .token / .tokenFile / .nodevariousPer-agent identity, mesh routing

channels.webrtc

WebRTC voice/video calls between mesh peers. v1 ships transcribe-only bot participation.

FieldTypeDefault
enabledboolfalse
stunServersstring[][stun:stun.l.google.com:19302]ICE STUN servers
turnServers[]object[]TURN relays (required when both peers behind symmetric NAT). Fields: urls, username, credential
allowedCallersstring[][] (allow all peers)Peer names permitted to initiate calls into this daemon
ringNotify[]object[]Where to send "someone is calling" notifications. Same shape as notifications.destination
callUrlBasestringhttp://<node.bind>Base URL for tap-to-join links
bot.enabledboolfalseAI bot joins via ?bot=<id> query param
bot.defaultAgentId / .whisperBackend (auto/mlx/openai) / .whisperModel / .whisperLanguage / .mlxBinary / .transcriptChannel / .maxCallMinutesvariousvariousWhisper transcription config

crons.<id>

FieldTypeDefault
enabledbooltrue
schedulestring5-field cron
timezonestringUTC
agentstring
promptstring
timeoutnumber600seconds
modelstring
maxOutputTokensnumber (50–8000)Soft cap appended to the prompt — Claude Code CLI has no hard flag for this. 1500 for briefs, 500 for status pings, 300 for classifiers
onErrorstring | string[]logAny of log, notify, disable. See Journey 2
notify.channel / .chatId / .accountIdstringWhere failures get pushed

services.<id>

Deterministic handlers that intercept messages before agent routing — no LLM call for the match.

FieldType
namestringHuman label
triggers[].patternstring (regex)
triggers[].channelstringRestrict to a channel
allowedContactsstring[]Whitelist
agentstringDispatch target
promptstringKnown prompt (not user text)
schedulestringOptional cron — run on timer too
timezonestring
notify.channel / .chatId / .accountIdstringPush result

mesh

FieldTypeDefault
enabledboolfalse
peers[].urlstringe.g. http://100.x.x.x:19900
peers[].namestring
peers[].tokenstringShared secret (${MESH_TOKEN})
discoverystatic | mdnsstatic
healthCheck.intervalnumber60seconds
healthCheck.timeoutnumber10

session

Controls Claude CLI --resume session reuse and the context-assembly strategy. See Context strategies for the full picture + bench results.

FieldTypeDefaultPurpose
staleMinutesnumber (1–1440)45Idle timeout for --resume. After this many minutes of no activity the session is dropped and the next turn starts fresh. Longer = better prompt-cache hit; shorter = less --resume replay bloat
maxTurnsPerSessionnumber (2–200)15Hard cap on turns per Claude CLI session. On hit, the next turn rotates. Prevents --resume replay growing unbounded across a long chat. When to change: lower (e.g. 8) if your agents accumulate context bloat across long Telegram threads; raise (e.g. 30) if rotations are losing too much context mid-task
tierTwoThresholdTokensnumber (50 000–200 000)195000If the prior turn's total input (input + cacheRead + cacheCreate) crosses this, rotate before the next turn. Claude bills tier-2 at 1.5× above 200K; default leaves a 5K headroom. When to change: lower it (e.g. 150_000) if you're on the Anthropic Max plan and tier-2 surcharges are eating budget. Default 200_000 matches Anthropic's official threshold
contextStrategy"layered" | "planner""layered"Context assembly strategy. Per-task override via the contextStrategy field in POST /task or AgentTask
maxClaudeCodeDispatchesPerHournumber80Soft dispatch budget for claude-code-tier agents (shared OAuth pools count across the fleet). Warns at 80% of the cap; short-circuits cold dispatches when exceeded — warm sessions always allowed. Sized for Max 5×; raise for Max 20×
maxClaudeCodeDispatchesPer5hnumber1805-hour rolling counterpart to the hourly budget
json
"session": {
  "staleMinutes": 45,
  "maxTurnsPerSession": 15,
  "tierTwoThresholdTokens": 195000,
  "contextStrategy": "layered"
}

notifications

Daemon-wide defaults for long-task notifications.

FieldTypeDefault
longTaskThresholdnumber30seconds; 0 disables
destination.channel / .chatId / .accountIdstring
on.taskComplete / on.taskError / on.taskQueuedbooltrue/true/false

boards

Array of Kanban boards backed by a WorkSource. See Boards & Kanban for the column model and label conventions.

FieldTypeDefault
boards[].idstring (slug)
boards[].namestringHuman label
boards[].source.typegitlab (more later)Discriminated union on source
boards[].source.projectsstring[]One or more GitLab project paths
boards[].primaryToolLabelstringANDed into every list query — shown as a baseline chip
boards[].labels[]objectManual label palette (name, color #RRGGBB, description)
boards[].columns[]object[]6 default GitLab-style columnsSee below
boards[].timeRangeDaysnumber (1–365)30Open-window time range
boards[].closedWindowDaysnumber (1–365)30Closed-column window
boards[].reconciliation.enabledbooltrueStale-Doing reconciler
boards[].reconciliation.staleDoingMinutesnumber45
boards[].reconciliation.respectLunchBreak / .respectSchedulebooltrue
boards[].reconciliation.actionbadge | notifybadge

Default columns (when columns[] is unset): Open / To Do / Doing / On Hold / Review / Closed, driving the Status::* scoped-label taxonomy.

Column kinds:

  • open-backlog — opened issues with no label matching scopedPrefix. Dragging in strips scoped labels.
  • scoped-label — opened issues carrying scopedLabel (e.g. Status::Doing). Mutually exclusive by prefix.
  • closed — all closed issues. Dragging in closes; dragging away reopens.
  • label (legacy) — opened issues with mapsToLabel; flat add/remove.

dashboard

The Kanban dashboard / board UI HTTP server. Off by default; turn on per machine.

FieldTypeDefault
enabledboolfalse
portnumber4202
bindstring127.0.0.1Use 0.0.0.0 to expose to other machines (or bind to a Tailscale interface)
tokenstringOptional bearer for writes. If unset, writes are unauthenticated (local-only acceptable)
daemonUrlstringhttp://localhost:18800Primary daemon for live view + A2A
daemons[]object[]Additional daemons to poll. Each: name, url, optional dashboardUrl (auto-derived from url with port 18800/19900 → 4202), token, dashboardToken
draftAgentstringDefault agent for AI-assisted drafting in the create-issue flow

graph (intent knowledge)

Per-message intent classification into a fixed-axis taxonomy. See Intent graph.

FieldTypeDefault
enabledboolfalse
baseDirstring.agentx/graphWhere schema/nodes/classifications live
draftAgentstringAgent that proposes classifications
reviewAgentstringAgent used by agentx graph review. Should have the wiki skill so it can call wiki query for context
autoApproveStructurestrict | extend-leaves | anyextend-leavesStructural auto-approval policy. When to change: leave at default (extend-leaves) until you trust the classifier. Set to any only if you want zero review (not recommended); set to strict if you want to manually approve every label
autoApproveConfidencenumber (0–1)1.0Min classifier confidence to auto-approve. OR'd with autoApproveStructure
retrievalWeights.graph / .bm25number0.6 / 0.4Hybrid wiki-retrieval score weights (sum need not be 1)

workflows

Declarative state machines. See Workflows.

FieldTypeDefault
enabledboolfalse
dirstring.agentx/workflowsWhere definitions live (one JSON or YAML file per workflow)
editordisabled | readonly | editeditWhether the dashboard exposes the visual editor (and at what level)
matching.enabledboolfalseEnables the workflow matcher seam before free-form agent execution
matching.modesuggest | autosuggestsuggest logs candidate workflows; auto is reserved for dispatcher-backed auto-run and currently falls back
matching.autoRunThresholdnumber (0–1)0.85Confidence target for future auto-run
matching.suggestThresholdnumber (0–1)0.65Minimum confidence to log a workflow suggestion

Workflow definitions also accept generated-workflow metadata: status (draft, review, active, deprecated), tags, entity, intentPath, generatedFrom, sourceTaskIds, confidence, workflowVersion, ownerAgent, lastMatchedAt, and matchCount. Generated drafts are written with status: draft and state: disabled.

webhooks[]

Inbound webhook inventory the dashboard manages. The actual URL is always POST /webhook/<agentId>/<source>.

FieldTypeDefault
idstring (slug)
sourcegitlab | github | sentry | stripe | discord | slack | custom
agentIdstring
secretEnvstringEnv var holding the signing secret
descriptionstring
enabledbooltrue
nodestringRoute to a mesh peer instead of local execution
triggersRecord<event-type, workflowId>Per-event-type workflow routing. E.g. "issues.opened": "triage-bug" (GitHub), "Note Hook": "review-comment" (GitLab)
defaultWorkflowstringWorkflow used when no triggers entry matches (backward-compatible fallback)

plugins[]

Array of installed npm package names. The loader does dynamic import(name) at boot, validates the manifest, and calls plugin.setup(ctx). See docs/architecture/plugins.md (when surfaced) for the contract.

json
"plugins": ["agentx-plugin-mattermost", "@noqta/plugin-mattermost"]

Plugins can register channel adapters via ctx.addChannel() and subscribe to bus events via ctx.on().

Process-pool eviction

When any agent has persistentProcess: true, the daemon keeps a warm Claude subprocess per (agent, channel, chatId). The pool is bounded by these knobs:

jsonc
"processPool": {
  "maxIdleSeconds": 30,         // eligibility for cap-pressured eviction (LRU)
  "maxAgeSeconds": 2700,        // unconditional kill on next sweep (45min default)
  "sweepIntervalSeconds": 5     // how often the sweeper checks
}

Increase maxAgeSeconds for chat workloads where the same conversation legitimately spans hours; decrease it (e.g. 1800) to eject any pool slot that's been idle longer than 30 minutes — useful when a triage→worker pattern would otherwise inherit stale visitor context. See Persistent processes for the full operator recipe.

Actions registry (separate files)

Actions do not live in agentx.json. Each action is its own file at .agentx/actions/<id>.json. Schema (Zod, src/actions/types.ts):

jsonc
{
  "id": "hubspot-create-contact",          // ^[a-z][a-z0-9_-]*$
  "title": "Create HubSpot contact",
  "description": "Pushes a contact into HubSpot CRM",
  "kind": "http",                          // "shell" | "http"
  "url": "https://api.hubapi.com/crm/v3/objects/contacts",
  "method": "POST",                        // GET | POST | PUT | PATCH | DELETE
  "headers": { "Authorization": "Bearer ${HUBSPOT_TOKEN}" },
  "body": "{\"properties\":{\"email\":\"{{email}}\"}}",
  "inputs": [
    { "name": "email", "type": "string", "required": true, "description": "Contact's primary email" }
  ],
  "timeoutMs": 30000                       // 100..600000
}

kind: shell swaps the URL/method/headers/body fields for command, cwd, and an optional env: { KEY: "value" } map. Templates (, ${ENV_VAR}) work everywhere. See the Actions reference for the integration cookbook.

business (optional)

See Journey 7 — Business layer for worked examples.

FieldTypeDefault
enabledboolfalse
timezonestringUTC
mainChannel.channel / .chatId / .accountIdstringDaily summary destination
workSourcediscriminated unionSee below
roles.<role>.titlestring
roles.<role>.responsibilitiesstring[][]
roles.<role>.sopPathstring
roles.<role>.kpisstring[][]
orgChart.<agentId>.rolestring
orgChart.<agentId>.reportsTostring
orgChart.<agentId>.schedule.daysday[]mon–fri
orgChart.<agentId>.schedule.start / .endHH:MM
orgChart.<agentId>.schedule.lunch.start / .endHH:MMOptional
orgChart.<agentId>.utilizationTargetnumber (0–1)0.8
projects[]object[]Phase 3. Per-project metadata. projects[].id ("owner/repo" for source-linked, free string otherwise) and projects[].pm (agentId responsible for gating dispatches on this project)
workTickMinutesnumber15
idleQueueThresholdnumber0Skip work-tick if queue depth exceeds this

business.workSource

Discriminated union on type:

typeRequired fieldsNotes
backlogpath (default .agentx/backlog.md)Reads from .agentx/backlog.json (canonical) when present, else parses the legacy GFM checklist at path. Items can be imported from gitlab/github via agentx backlog import and mutations sync upstream. See agentx backlog
gitlabprojects: string[] (default [] = all configured channels.gitlab.routes projects)The work-pool ticks GitLab issues assigned to each agent's mapped username. Closed/done items leave the pool
wikipath, glob (default **/*.md)Scans markdown files for - [ ] @agent task checkboxes

Governance flags (Phase 3 / 8)

The architectural rescue introduces governance hooks driven by env vars at startup. These are read once when the daemon boots; flipping a flag requires a restart.

FlagEffect
INTENT_LEDGER_MODEoff (default before activation) — no ledger writes. shadow — ledger records every dispatch decision in parallel with the legacy router; divergences are tracked, not enforced. authoritative — ledger is the source of truth, legacy router becomes the divergence reporter
INTENT_PM_GATE_ENABLEDWhen true AND business.enabled = true, project-scoped events flow through the org-chart PM (business.projects[].pm) before reaching agents. Combined with agents[].intents (Phase 5) and agents[].maxDelegationDepth (Phase 8) for layered admission control

The Phase 7 reproducibility check (agentx ledger replay) can be run any time the ledger has events — it spins up a fresh tmp ledger, replays the source events, and reports any divergences in dispatch decisions.

Environment variables

See the CLI reference env-var table for the full list (~14 entries: provider creds, runtime overrides, governance flags, channel-token conventions).

Anonymized example

A full production-shaped config with tokens scrubbed lives at /examples/agentx.example.json. Use it as a starting point for multi-agent, multi-channel, cron + business deployments.

Released under the MIT License.