Skip to content

Configuration Reference

~/.xacpx/config.json is the main configuration file for xacpx. To manage chat channels (WeChat, Feishu, Yuanbao) from the terminal, see Channel Management. To modify a subset of fields from within the chat interface, see the /config Command.

File locations

FileDefault pathPurpose
Config~/.xacpx/config.jsonMain configuration
State~/.xacpx/state.jsonSessions, chat contexts, daemon state
Plugins~/.xacpx/plugins/Plugin packages installed by xacpx plugin add
Runtime logs~/.xacpx/runtime/app.logApplication log
Performance log~/.xacpx/runtime/perf.logPerformance debug log (when enabled)

Environment variable overrides:

VariableDescription
WEACPX_CONFIGOverride the config file path (default: ~/.xacpx/config.json)
WEACPX_STATEOverride the state file path (default: ~/.xacpx/state.json)
WEACPX_WEIXIN_SDKForce a specific weixin-agent-sdk entry file path
WEACPX_ILINK_APP_IDiLink-App-Id header sent with WeChat channel outbound requests; omit to suppress the header (backward-compatible)

Top-level schema

jsonc
{
  "language": "en",
  "transport": { ... },
  "logging": { ... },
  "channel": { ... },
  "channels": [ ... ],
  "plugins": [ ... ],
  "agents": { ... },
  "workspaces": { ... },
  "orchestration": { ... },
  "later": { ... }
}

Minimal working config: the following is enough to start xacpx. transport.type defaults to "acpx-bridge"; agents and workspaces can be populated later via chat commands.

json
{
  "transport": {},
  "agents": {},
  "workspaces": {},
  "orchestration": {}
}

Language

FieldTypeRequiredDescription
language"en" | "zh"NoSelects the language for all xacpx runtime output (chat replies, CLI output, orchestration prompts, messages). When absent, the language is inferred from the system locale on first run ($LC_ALL/$LC_MESSAGES/$LANG: starts with zh → Chinese, otherwise English) and persisted to config. Changeable in chat with /config set language en. Takes full effect after xacpx restart.

Transport configuration

Controls how xacpx communicates with the acpx backend.

FieldTypeRequiredDescription
type"acpx-cli" | "acpx-bridge"NoCommunication mode (default: "acpx-bridge")
commandstringNoExplicit path to the acpx binary; overrides automatic resolution
sessionInitTimeoutMsnumberNoSession initialization timeout in milliseconds (default: 120000)
permissionMode"approve-all" | "approve-reads" | "deny-all"NoPermission mode; defaults to "approve-all" when omitted, so non-interactive prompt turns do not stop on permission requests unless a stricter policy is explicitly configured
nonInteractivePermissions"deny" | "fail"NoPolicy for non-interactive scenarios (default: "deny")
permissionPolicystringNoPath to an acpx permission policy file; passed as acpx --permission-policy <value>
queueOwnerTtlSecondsnumberNoIdle lifetime of the acpx queue owner process in seconds; passed as --ttl (default: 1800; 0 = keep alive indefinitely)

Transport types

"acpx-cli" — spawns a new acpx child process for each operation (prompt/cancel/ensureSession). Uses node-pty for PTY allocation. Suitable for local development and debugging.

"acpx-bridge" — starts a persistent bridge subprocess (bridge-main.ts). All operations are sent via stdin/stdout JSON RPC; the acpx process does not restart between commands. Suitable for production and long-running deployments.

acpx binary resolution order

When transport.command is not set, xacpx looks for acpx in this order:

  1. node_modules/.bin/acpx (bundled in the project)
  2. acpx on the shell PATH

Setting transport.command explicitly overrides this lookup entirely.

Reducing cold starts (queueOwnerTtlSeconds)

When acpx receives a prompt it starts a queue owner background process that holds the ACP agent and model context. Each subsequent message from xacpx connects to the same owner via Unix socket — skipping the adapter boot + session/new/load cold start (typically a few seconds to tens of seconds).

The owner's idle lifetime is set by --ttl. xacpx defaults to 1800 seconds (30 minutes), which covers most conversational pauses. After genuine idleness the agent reclaims automatically within 30 minutes.

  • Larger values (e.g. 3600) keep agents warmer but extend the residual window after the daemon stops.
  • 0 = keep alive indefinitely; every session maintains a persistent agent process; highest resource use.
  • On xacpx stop, xacpx terminates the queue owner processes for its known sessions (best-effort; failures do not block shutdown). Sessions with ttl=0 that survive an unclean shutdown require manual cleanup.
  • Changing this value requires restarting the daemon.

Orchestration MCP auto-injection

Before sending a prompt to an acpx session, xacpx starts the acpx queue owner and injects an MCP stdio server named xacpx via ACPX_QUEUE_OWNER_PAYLOAD. The MCP tool prefix is therefore mcp__xacpx__* (for example mcp__xacpx__delegate_request). This gives the managed agent access to orchestration tools (delegate_request, etc.) and scheduled-task tools.

This injection does not modify .acpxrc.json, ~/.acpx/config.json, or replace the acpx home directory, so existing sessions, stream logs, and index mappings are unaffected.

Injection command resolution order:

  1. WEACPX_CLI_COMMAND environment variable
  2. WEACPX_DAEMON_ARG0 + current Node executable
  3. process.argv[1] + current Node executable
  4. xacpx

If xacpx is not launched via the standard CLI or daemon, set WEACPX_CLI_COMMAND explicitly:

bash
WEACPX_CLI_COMMAND="node /path/to/xacpx/dist/cli.js" xacpx run

Agents

Registered agent map. Keys are agent names used by /agent add, /session new --agent, etc.

FieldTypeRequiredDescription
driverstringYesAgent driver type; passed as the first positional argument to acpx
commandstringNoRaw command for a custom agent; not recommended for built-in drivers

Built-in templates (use driver only; let acpx resolve the alias):

TemplateDriver
codex"codex"
claude"claude"
pi"pi"
openclaw"openclaw"
gemini"gemini"
cursor"cursor"
copilot"copilot"
droid"droid"
factory-droid"factory-droid"
factorydroid"factorydroid"
iflow"iflow"
kilocode"kilocode"
kimi"kimi"
kiro"kiro"
opencode"opencode"
qoder"qoder"
qwen"qwen"
trae"trae"

Add templates via chat with /agent add <name> or from the terminal with xacpx agent add <name>. Adding an agent that already has the same configuration is idempotent; a name conflict with different settings prompts you to delete first.

json
{
  "agents": {
    "codex": { "driver": "codex" },
    "claude": { "driver": "claude" },
    "kimi": { "driver": "kimi" },
    "my-agent": { "driver": "custom", "command": "/usr/local/bin/my-agent" }
  }
}

Workspaces

Registered workspace map. Keys are workspace names used by /workspace new, /session new --ws, etc.

A home workspace (cwd: "~") is seeded automatically on first run; remove it with xacpx workspace rm home.

FieldTypeRequiredDescription
cwdstringYesWorkspace path; passed as acpx --cwd; supports ~ expansion
descriptionstringNoDescription shown in /workspaces output
json
{
  "workspaces": {
    "backend": {
      "cwd": "/Users/name/Projects/backend",
      "description": "backend repo"
    },
    "frontend": {
      "cwd": "/Users/name/Projects/frontend"
    }
  }
}

Channels

The channels array lists which message channel runtimes to start.

Manage channels from the terminal:

bash
xacpx channel list
xacpx channel add feishu
xacpx channel disable weixin
xacpx restart

ChannelRuntimeConfig fields

FieldTypeRequiredDescription
idstringYesUnique channel identifier; must match type (built-in: "weixin"; plugins: "feishu", "yuanbao", etc.)
typestringYesChannel type. Built-in: "weixin". "feishu" from @ganglion/xacpx-channel-feishu; "yuanbao" from @ganglion/xacpx-channel-yuanbao
enabledbooleanNoWhether the channel is active (default: true)
optionsobjectVariesChannel-specific configuration (see below)

Feishu channel options

FieldTypeRequiredDescription
options.appIdstringRequired in single-bot modeFeishu App ID
options.appSecretstringRequired in single-bot modeFeishu App Secret
options.domain"feishu" | "lark"NoAPI domain (default: "feishu")
options.requireMentionbooleanNoRequire @mention in group chats (default: true)
options.textMessageFormat"text"NoMessage send format; currently only "text"
options.dedupTtlMsnumberNoMessage deduplication TTL in milliseconds (default: 43200000 — 12 hours)
options.dedupMaxEntriesnumberNoMaximum deduplication cache entries (default: 5000)
options.defaultAccountstringNoDefault account ID for multi-bot mode; falls back to "default" then the first entry
options.accountsobjectNoPer-account overrides indexed by accountId; each entry can override appId, appSecret, domain, requireMention, dmPolicy, groupPolicy, allowFrom, enabled, name
options.dmPolicy"open" | "allowlist" | "disabled"NoDirect-message admission policy (default: "open")
options.groupPolicy"open" | "allowlist" | "disabled"NoGroup-chat admission policy (default: "open"); requireMention still applies independently
options.allowFromstring[]NoSender open_id allowlist; active when either policy is "allowlist"
options.replyMode"static" | "streaming" | "auto"NoReply rendering mode; default "auto" (DM → streaming, group → static). "streaming" uses a CardKit v2 interactive card that updates in place (thinking → streaming → complete/aborted/error, with realtime footer timer, reasoning folding, collapsible tool-call panels in verbose mode, markdown image URL → image_key resolution, character-level streaming). Requires cardkit:card:write + im:message:send_as_bot bot permissions; falls back to static on first-card failure and logs feishu.streaming.fallback. Can be overridden per account with options.accounts.<id>.replyMode

Yuanbao channel options

Provided by @ganglion/xacpx-channel-yuanbao. Install with xacpx plugin add @ganglion/xacpx-channel-yuanbao, then add the channel.

FieldTypeRequiredDescription
options.appKeystringYes (with appSecret)Yuanbao bot App Key
options.appSecretstringYes (with appKey)Yuanbao bot App Secret
options.tokenstringNoStatic token; also accepts appKey:appSecret form (split on load). True static auth token requires botId too
options.botIdstringNoBot account ID for @-mention recognition and self-message filtering; usually returned by sign-token and filled automatically
options.apiDomainstringNoYuanbao API domain (default: "bot.yuanbao.tencent.com")
options.wsUrlstringNoYuanbao WebSocket URL (default: "wss://bot-wss.yuanbao.tencent.com/wss/connection")
options.requireMentionbooleanNoRequire @mention in group chats (default: true)
options.replyToMode"off" | "first" | "all"NoQuote-reply strategy (default: "first")
options.overflowPolicy"stop" | "split"NoOverflow strategy for long output (default: "split")
options.maxCharsnumberNoCharacter count threshold for splitting outbound text (default: 3000)
options.mediaMaxMbnumberNoMaximum media size in MB (default: 20)
options.fallbackReplystringNoText sent when the agent returns no text output
options.accountsobjectNoPer-account overrides; entries inherit top-level config

WeChat extended configuration (openclaw.json)

The built-in weixin channel options is an empty object. Additional WeChat fields are read from ~/.xacpx/state/openclaw.json (override path via OPENCLAW_CONFIG). This is separate from ~/.xacpx/config.json.

json
{
  "channels": {
    "openclaw-weixin": {
      "routeTag": "...",
      "botAgent": "MyApp/1.0",
      "accounts": {
        "<accountId>": {
          "routeTag": "...",
          "botAgent": "MyApp/1.0 (account-A)"
        }
      }
    }
  }
}
FieldTypeRequiredDescription
routeTagstring | numberNoWritten to the SKRouteTag request header for backend routing or A/B testing; account-level takes precedence
botAgentstringNoClient identifier written to base_info.bot_agent; syntax name/version[ (comment)]; truncated at 256 bytes; falls back to xacpx when empty; account-level takes precedence
accounts.<id>objectNoPer-account overrides for routeTag and botAgent

Legacy compatibility

  • channel.type (old single-channel config) is still loaded and mapped to a single-entry channels[] automatically.
  • The old feishu top-level object is still recognized as a legacy alias; new configs should use options.
  • wechat.replyMode is still mapped to channel.replyMode on load.

Permissions

The /pm (or /permission) command exposes these configuration values in chat:

/pm commandConfig valueEffect
/pm set allowtransport.permissionMode: "approve-all"More operations auto-approved
/pm set readtransport.permissionMode: "approve-reads"Reads auto-approved; writes more cautious
/pm set denytransport.permissionMode: "deny-all"Deny operations requiring approval
/pm auto denytransport.nonInteractivePermissions: "deny"Auto-deny in non-interactive contexts
/pm auto failtransport.nonInteractivePermissions: "fail"Fail immediately in non-interactive contexts

Defaults

Logging

FieldDefault
logging.level"info"
logging.maxSizeBytes2097152 (2 MB)
logging.maxFiles5
logging.retentionDays7
logging.perf.enabledfalse
logging.perf.maxSizeBytes5242880 (5 MB)
logging.perf.maxFiles3
logging.perf.retentionDays7

The logging.perf tracer is bound at buildApp() time; changing it requires a daemon restart. Only the built-in WeChat channel writes perf traces; other channels do not emit turns even when the option is enabled.

Reply mode

channel.replyMode defaults to "verbose". Set a per-session override with /replymode; clear it with /replymode reset.

Scheduled tasks

later.defaultMode defaults to "temp" (execute in a temporary session). Set to "bind" to execute in the session that created the task.

Orchestration

FieldDefault
orchestration.maxPendingAgentRequestsPerCoordinator3
orchestration.allowWorkerChainedRequestsfalse
orchestration.allowedAgentRequestTargets[] (no restriction)
orchestration.allowedAgentRequestRoles[] (no restriction)
orchestration.progressHeartbeatSeconds300 (falls back to 300 when omitted or non-finite)
orchestration.maxParallelTasksPerAgent3

Examples

Full example

json
{
  "language": "en",
  "transport": {
    "type": "acpx-bridge",
    "command": "acpx",
    "sessionInitTimeoutMs": 120000,
    "permissionMode": "approve-all",
    "nonInteractivePermissions": "deny",
    "queueOwnerTtlSeconds": 1800
  },
  "logging": {
    "level": "info",
    "maxSizeBytes": 2097152,
    "maxFiles": 5,
    "retentionDays": 7,
    "perf": {
      "enabled": false,
      "maxSizeBytes": 5242880,
      "maxFiles": 3,
      "retentionDays": 7
    }
  },
  "channel": {
    "replyMode": "verbose"
  },
  "channels": [
    { "id": "weixin", "type": "weixin", "enabled": true }
  ],
  "plugins": [],
  "agents": {
    "codex": { "driver": "codex" },
    "claude": { "driver": "claude" }
  },
  "workspaces": {
    "backend": {
      "cwd": "/absolute/path/to/backend",
      "description": "backend repo"
    }
  },
  "orchestration": {
    "maxPendingAgentRequestsPerCoordinator": 3,
    "allowWorkerChainedRequests": false,
    "allowedAgentRequestTargets": [],
    "allowedAgentRequestRoles": [],
    "maxParallelTasksPerAgent": 3
  },
  "later": {
    "defaultMode": "temp"
  }
}

WeChat only

json
{
  "channels": [
    { "id": "weixin", "type": "weixin", "enabled": true }
  ]
}

WeChat + Feishu

json
{
  "channels": [
    { "id": "weixin", "type": "weixin", "enabled": true },
    {
      "id": "feishu",
      "type": "feishu",
      "enabled": true,
      "options": {
        "appId": "cli_xxx",
        "appSecret": "...",
        "domain": "feishu"
      }
    }
  ]
}

Yuanbao (requires @ganglion/xacpx-channel-yuanbao)

json
{
  "plugins": [
    { "name": "@ganglion/xacpx-channel-yuanbao", "enabled": true }
  ],
  "channels": [
    {
      "id": "yuanbao",
      "type": "yuanbao",
      "enabled": true,
      "options": {
        "appKey": "yb_xxx",
        "appSecret": "...",
        "requireMention": true
      }
    }
  ]
}

Tighter orchestration limits

json
{
  "orchestration": {
    "maxPendingAgentRequestsPerCoordinator": 5,
    "allowWorkerChainedRequests": false,
    "allowedAgentRequestTargets": ["claude", "codex"],
    "allowedAgentRequestRoles": ["reviewer", "planner"],
    "maxParallelTasksPerAgent": 5
  }
}

Released under the MIT License.