Skip to content

Configuration Reference

At startup, zhin runtime start checks these project-root candidates in order. If more than one exists, startup fails instead of guessing:

  1. config.yml
  2. config.yaml
  3. config.json
  4. zhin.config.yml
  5. zhin.config.yaml
  6. zhin.config.json

It is recommended to use zhin.config.yml consistently. All examples in this document use it.

For field-by-field types, required fields, defaults, and plugin schemas, use the generated configuration field reference. It is generated from Runtime source and plugin schema.json files, with drift blocked in CI.

How to use this reference

Run npx zhin setup to generate a bootable configuration, then change fields with this page. Run npx zhin doctor before committing. After startup, trust the generation shown in Console rather than treating a disk file as applied state.

What you need to changeWhere it belongs
Installed plugins and Featuresdependencies and package.json#zhin
Host, plugin-instance, and AI valueszhin.config.yml
Secrets and environment differences.env / .env.<environment>, referenced with ${VAR}
Workrooms, members, and chat or repository bindingspersistent Workroom Catalog, managed in Console
What the runtime actually publishedEndpoint, capability catalog, and generation state in Console

Configuration supplies values; it does not mount code. Install the dependency and update package.json#zhin before adding configuration for a new adapter, Feature, or plugin.

Loading and Validation Flow

Configuration passes JSON Schema validation, and only the keys listed below are allowed at the top level. A misspelled key produces Invalid Plugin config in zhin.config.yml at startup.

Top-level keys other than plugin and plugins are consumed by the CLI Host assembly layer. They are not passed to plugins.

Environment Variable Expansion

${VAR}, ${VAR:-default}, and ${VAR:=default} within string values are expanded:

yaml
http:
  token: ${HTTP_TOKEN:-dev-token}   # Falls back to dev-token when HTTP_TOKEN is not set

An unset variable without a default expands to an empty string. For fields such as apiKey, this triggers AI-provider soft pruning.

Dotenv loads .env then .env.<environment>. --environment selects the name and defaults to development. Keep secrets in environment variables, never in the configuration file.

Top-Level Keys

KeyTypeDescription
log_levelstring | numberLog level (e.g., info, debug); ZHIN_LOG_LEVEL / LOG_LEVEL environment variables can temporarily override
httpobjectHTTP Host (Console / API entry point)
databaseobjectDatabase
speechobjectSpeech STT/TTS (requires @zhin.js/speech installed)
assistantobjectAssistant Runtime (scheduled tasks / event API)
aiobjectAI stack (requires @zhin.js/agent etc., see below)
mcpobjectMCP Host (exposes bot tools as an MCP Server)
a2aobjectA2A Host (Agent Card / remote Agent interop)
htmlRendererobjectShotium HTML rendering (e.g., width / viewport / scale)
pluginobjectRoot Plugin (the application itself) configuration
pluginsobjectChild plugin configuration, keyed by instanceKey

http

yaml
http:
  port: 8086                 # Runtime fallback when no config exists
  host: 127.0.0.1            # Default 127.0.0.1
  token: ${HTTP_TOKEN}       # API Bearer token
  corsOrigins:               # Allowed cross-origin sources
    - "https://console.zhin.dev"
  base: /api                 # API mount path

With no configuration, Runtime falls back to 8086; the current project scaffold writes 8068. Trust the project configuration and startup log.

Local development can access without token; production must set one. tokens supports multiple scoped tokens.

database

yaml
database:
  dialect: sqlite            # Default sqlite
  filename: ./data/bot.db
  • dialect options: sqlite, mysql, pg, mongodb, redis, memory.
  • Fields other than dialect are passed as-is to the corresponding dialect (e.g., pg/mysql connection parameters).
  • When database is omitted entirely, it defaults to <project-root>/.zhin/data.sqlite.

speech

yaml
speech:
  stt: { provider: ollama, model: whisper, host: http://localhost:11434 }
  tts: { provider: edge, voice: zh-CN-XiaoxiaoNeural }

When configured, this enables the speech pipeline and exposes voice_stt / voice_tts tools to the Agent. @zhin.js/speech is an optional peer dependency and must be installed separately.

assistant

yaml
assistant:
  enabled: true                    # Enable unified JobStore, default false
  profile:
    enabled: true
    file: assistant.profile.yml    # Profile file, drives scheduled tasks
  events:
    enabled: true                  # Open POST /api/assistant/events
    rateLimitPerMinute: 60         # Default 60
  defaults:
    notifyOnFailure: false         # Whether to notify on Job failure, default false

The remaining sub-key is queue (Schedule execution concurrency/retry/timeout). Schedule facts are always persisted to schedule-jobs.json; manage them with zhin schedule.

mcp / a2a

yaml
mcp:
  enabled: true
  path: /mcp
  token: ${HTTP_TOKEN}                  # Falls back to http.token if unset
  allowUnauthenticatedLocalhost: false  # Recommend false for production

a2a:
  enabled: true
  path: /a2a

Both sections are mounted on the HTTP Host and enabled as needed; when not configured, the corresponding SDK is not loaded. Remote Workroom execution uses fixed-generation a2a.workroomCallbacks and a2a.workroomRemoteExecutors transport bindings. Project membership and conversation bindings remain in the persistent Workroom Catalog, not in process configuration. See the complete @zhin.js/a2a example on GitHub.

ai

The ai section is consumed by the Agent Host. Whenever ai / assistant is configured in any section, the startup graph loads the Agent stack; providers missing credentials are soft-pruned and do not block startup.

providers: Named Model Providers

yaml
ai:
  providers:
    deepseek:
      sdk: deepseek                  # Required
      apiKey: ${DEEP_SEEK_API_KEY}
    openrouter:
      sdk: openai-compatible
      baseUrl: ${OPENROUTER_BASE_URL}
      apiKey: ${OPENROUTER_API_KEY}
      contextWindow: 32768
      models:                        # Allowed model whitelist
        - "openrouter/free"
    ollama-local:
      sdk: ollama
      host: http://127.0.0.1:11434   # ollama uses host instead of baseUrl
    cloudflare:
      sdk: openai-compatible
      apiKey: ${CLOUDFLARE_API_TOKEN}
      accountId: ${CLOUDFLARE_ACCOUNT_ID}
FieldDescription
sdkRequired, one of six: openai, anthropic, google, deepseek, ollama, openai-compatible
apiKey / baseUrl / hostCredentials and endpoint; ollama uses host, Cloudflare also requires accountId
modelsExplicit model list
defaultModel, contextWindow, timeout, maxRetries, headersBehavior tuning
authSchemeAuthorization header prefix, default 'Bearer '
imageGenerationText-to-image defaults (for drivers that support this capability, e.g., defaultModel, defaultSize, watermarkEnabled, promptSuffix)

Provider aliases can be named arbitrarily; when sdk is not explicitly specified, it is inferred from the alias prefix (e.g., deepseek-main -> deepseek); if inference fails, it falls back to openai-compatible. The corresponding @ai-sdk/* packages are optional peers and should be installed based on which SDK is used.

agents: Bind Models and Roles

yaml
ai:
  agents:
    zhin:                            # Main Agent, default entry point
      provider: openrouter           # References the provider alias
      model: openrouter/free
      mcpServers: [icqq]             # References names in ai.mcpServers
    researcher:
      provider: openrouter
      model: openrouter/free
      nickname: 'Researcher'         # Agent self-reference and UI display name
FieldDescription
provider / modelRequired, the bound provider alias and model
nicknameAgent self-reference and UI display name
mcpServersList of MCP Server names visible to this Agent
priority / matchInbound routing: match can match by adapter / endpoint / scene / sceneId / hasMedia / contentContains, single or array
permission.taskspawn_task visible child Agent types (glob -> allow / deny)

mcpServers: External MCP Servers

yaml
ai:
  mcpServers:
    - name: icqq
      transport: streamable-http     # stdio | streamable-http | sse
      url: ${ICQQ_MCP_URL}
      headers:
        Authorization: Bearer ${ICQQ_MCP_TOKEN}
    - name: local-tools
      transport: stdio
      command: npx
      args: ["-y", "some-mcp-server"]
      env: { FOO: bar }

Once connected successfully, these Servers' tools enter the Agent tool pool (assigned by agents.<name>.mcpServers).

Other ai Sub-sections

yaml
ai:
  sessions:                # Session storage
    maxHistory: 200        # Default 200 in database mode, 100 in memory mode
    expireMs: 604800000    # Default 7 days in database mode, 24h in memory mode
    useDatabase: true      # Default true; uses Database Root Host (.zhin/data.sqlite when database is omitted)
  context:                 # Group chat context recording
    enabled: true
    maxRecentMessages: 100
    summaryThreshold: 50
    keepAfterSummary: 10
    maxContextTokens: 4000
  memory:                  # Three-layer Markdown file memory, enabled by default
    semantic:
      enabled: true        # Generation-owned memory_search/memory_upsert; requires the Database Host
      autoConsolidate: false
    # memory_entries must be ready during candidate activation or that generation fails closed
    # memoryMcp is deprecated; use the native memory_search / memory_upsert tools above
  access:                  # AI access control
    mode: open             # open | closed | whitelist, default open
    users: []              # Allowed user IDs in whitelist mode
    groups: []
    denyMessage: ...
  agent:                   # Tool execution security
    inboundQueue:
      groupMode: supersede # supersede (default) | fifo
    execSecurity: allowlist    # deny | allowlist | full
    execPreset: readonly       # readonly | network | development | custom
    execAllowlist: ["^ls ", "^cat "]
    maxIterations: 15          # Maximum tool iterations per turn, default 15
    thinkingPreview: false     # Show LLM actual thinking content (truncated) in activity feedback instead of static "Thinking...", default false
    thinkingPreviewMaxLength: 200  # Max chars for thinkingPreview, default 200
  trigger:                 # AI trigger rules
    prefixes: ["ai:"]          # Trigger prefixes, default ['#', 'AI:', 'ai:']
    respondToAt: true          # Respond to @bot, default true
    respondToPrivate: true     # Private chats bypass prefix requirement, default true
    ignorePrefixes: ['/', '!', '!']  # Avoid conflicting with commands
    timeout: 60000

ai.multimodal governs multimodal input and output. ai.knowledge.baseDir selects the local knowledge directory and defaults to knowledge.

Remote Agents no longer attach through ai.remoteAgents. An optional A2A Executor enters through the persistent Workroom Catalog and generation-owned authority, under Assignment lease/fence and Journal contracts.

ai.workrooms has been removed. Projects, members, and collaboration spaces are managed by the persistent Workroom Catalog in Console; revision-checked saves take effect without restarting the runtime.

plugin and plugins

yaml
# Root Plugin (the application itself) configuration; keys are determined by the application's config schema
plugin:
  terminal:
    interactive: true

# Child plugin configuration, keyed by instanceKey
plugins:
  sandbox:
    endpoints:
      - context: sandbox
        name: sandbox-bot
        owner: sandbox-user

instanceKey defaults to the last package-name segment without the adapter-, plugin-, or service- prefix. For example, @zhin.js/adapter-icqq becomes icqq.

zhin install writes plugins.<instanceKey> and mounts the package under package.json#zhin.plugins.

Adapter Instances: master / trusted / commandPrefix

Platform adapter plugin instance configurations support three common keys:

KeyLocationDescription
masterInstance top-levelStable Endpoint Owner ID used for authorization and explicit /approve management commands
trustedInstance top-level / per-endpointTrusted user ID list; array or whitespace/comma-separated string
commandPrefixInstance top-level / per-endpointCommand prefix, default ''; per-endpoint values override top-level

endpoints Array: One Instance with Multiple Accounts

Multi-account adapters use the endpoints array to declare multiple Endpoints:

yaml
plugins:
  icqq:
    master: "${ICQQ_MASTER}"     # Top-level shared: applies to all endpoints
    endpoints:                   # Per-entry: name is required, other fields merge with top-level (per-entry takes priority)
      - name: "${ICQQ_ACCOUNT_1}"
      - name: "${ICQQ_ACCOUNT_2}"

Expansion rules (@zhin.js/adapter's expandEndpointConfigs):

  • Each endpoint's final configuration = instance configuration (without the endpoints key) shallow-merged with the entry's fields, per-entry fields override top-level.
  • name is required and must be a non-empty string; must not contain ~; duplicates keep only the first with a warning.
  • When endpoints is empty or all entries are invalid, it falls back to a single Endpoint (named after the instanceKey).

Real-world example (QQ official bot, main account through proxy gateway + sandbox account coexisting):

yaml
plugins:
  qq:
    endpoints:
      - name: zhin
        appid: "${QQ_APPID}"
        secret: "${QQ_SECRET}"
        mode: websocket
        sandbox: false
        gatewayUrl: "https://bots.example.com/gateway/102005927"
        intents: [GUILDS, GROUP_AND_C2C_EVENT, PUBLIC_GUILD_MESSAGES]
      - name: "102069707"
        appid: "${QQ_SANDBOX_APPID}"
        secret: "${QQ_SANDBOX_SECRET}"
        mode: websocket
        sandbox: true
        intents: [GUILDS, GROUP_AND_C2C_EVENT]

  slack:
    endpoints:
      - name: zhin
        token: "${SLACK_TOKEN}"
        appToken: "${SLACK_APP_TOKEN}"
        signingSecret: "${SLACK_SIGNING_SECRET}"
        socketMode: true

For adapter-specific fields (appid, intents, socketMode, url, access_token, etc.), see the README of the corresponding adapter package.

Minimal Configuration

You can run without any Host sections -- this is the entire configuration for examples/minimal-bot:

yaml
plugin:
  terminal:
    interactive: true

plugins: {}

After starting, use the zhin setup wizard to progressively add database / adapters / AI (see CLI Reference); for how to run, see zhin runtime start in Detail.