Echo Agent Configuration Reference¶
security¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
security.profile |
security.profile |
Literal | personal_cli |
personal_cli/daemon/public_gateway | Overall security profile preset |
channels¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
channels.telegram.enabled |
channels.telegram.enabled |
bool | false |
— | Enable the Telegram channel |
channels.telegram.token |
channels.telegram.token |
str | "" |
— | Telegram bot API token |
channels.telegram.allowFrom |
channels.telegram.allow_from |
list | [] |
— | Allowlist of user IDs permitted to interact (empty = all) |
channels.telegram.proxy |
channels.telegram.proxy |
str | None | null |
— |
channels.telegram.groupPolicy |
channels.telegram.group_policy |
Literal | mention |
open/mention | Group reply policy: open = all messages, mention = only when @-mentioned |
channels.telegram.reactionsEnabled |
channels.telegram.reactions_enabled |
bool | true |
— | Whether to add emoji reactions to messages |
channels.telegram.dataDir |
channels.telegram.data_dir |
str | "" |
— | Directory persisting Telegram state (long-poll offset, prevents re-fetch after restart); defaults to ~/.echo-agent/data/telegram |
channels.discord.enabled |
channels.discord.enabled |
bool | false |
— | Enable the Discord channel |
channels.discord.token |
channels.discord.token |
str | "" |
— | Discord bot token |
channels.discord.allowFrom |
channels.discord.allow_from |
list | [] |
— | Allowlist of user IDs permitted to interact (empty = all) |
channels.discord.groupPolicy |
channels.discord.group_policy |
Literal | mention |
open/mention | Group reply policy: open = all messages, mention = only when @-mentioned |
channels.discord.reactionsEnabled |
channels.discord.reactions_enabled |
bool | true |
— | Whether to add emoji reactions to messages |
channels.webhook.enabled |
channels.webhook.enabled |
bool | false |
— | Enable the webhook channel |
channels.webhook.host |
channels.webhook.host |
str | 0.0.0.0 |
— | Webhook server bind address |
channels.webhook.port |
channels.webhook.port |
int | 8080 |
— | Webhook server listen port |
channels.webhook.secret |
channels.webhook.secret |
str | "" |
— | Secret used to verify inbound request signatures |
channels.webhook.path |
channels.webhook.path |
str | /webhook |
— | HTTP path on which webhooks are received |
channels.webhook.maxPending |
channels.webhook.max_pending |
int | 1000 |
— | Maximum number of pending webhook requests queued |
channels.cli.enabled |
channels.cli.enabled |
bool | true |
— | Enable the CLI channel |
channels.cron.enabled |
channels.cron.enabled |
bool | false |
— | Enable the cron channel |
channels.slack.enabled |
channels.slack.enabled |
bool | false |
— | Enable the Slack channel |
channels.slack.botToken |
channels.slack.bot_token |
str | "" |
— | Slack bot token (xoxb-) |
channels.slack.appToken |
channels.slack.app_token |
str | "" |
— | Slack app-level token (xapp-) for Socket Mode |
channels.slack.allowFrom |
channels.slack.allow_from |
list | [] |
— | Allowlist of user IDs permitted to interact (empty = all) |
channels.slack.reactionsEnabled |
channels.slack.reactions_enabled |
bool | true |
— | Whether to add emoji reactions to messages |
channels.whatsapp.enabled |
channels.whatsapp.enabled |
bool | false |
— | Enable the WhatsApp channel |
channels.whatsapp.verifyToken |
channels.whatsapp.verify_token |
str | "" |
— | WhatsApp webhook verification token |
channels.whatsapp.accessToken |
channels.whatsapp.access_token |
str | "" |
— | WhatsApp Cloud API access token |
channels.whatsapp.phoneNumberId |
channels.whatsapp.phone_number_id |
str | "" |
— | WhatsApp phone number ID used for sending |
channels.whatsapp.webhookPath |
channels.whatsapp.webhook_path |
str | /whatsapp |
— | HTTP path on which WhatsApp webhooks are received |
channels.whatsapp.host |
channels.whatsapp.host |
str | 0.0.0.0 |
— | WhatsApp server bind address |
channels.whatsapp.port |
channels.whatsapp.port |
int | 8081 |
— | WhatsApp server listen port |
channels.whatsapp.appSecret |
channels.whatsapp.app_secret |
str | "" |
— | WhatsApp App Secret for webhook HMAC signature verification |
channels.whatsapp.allowFrom |
channels.whatsapp.allow_from |
list | [] |
— | Allowlist of user IDs permitted to interact (empty = all) |
channels.whatsapp.groupPolicy |
channels.whatsapp.group_policy |
str | mention |
— | Group response policy: all=respond to all, mention=only respond when mentioned |
channels.weixin.enabled |
channels.weixin.enabled |
bool | false |
— | Enable the Weixin channel |
channels.weixin.accountId |
channels.weixin.account_id |
str | "" |
— | Weixin customer-service account ID |
channels.weixin.token |
channels.weixin.token |
str | "" |
— | Weixin access authentication token |
channels.weixin.baseUrl |
channels.weixin.base_url |
str | https://ilinkai.weixin.qq.com |
— | Weixin API base URL |
channels.weixin.cdnBaseUrl |
channels.weixin.cdn_base_url |
str | https://novac2c.cdn.weixin.qq.com/c2c |
— | Weixin media CDN base URL |
channels.weixin.allowFrom |
channels.weixin.allow_from |
list | [] |
— | Allowlist of user IDs permitted to interact (empty = all) |
channels.weixin.dmPolicy |
channels.weixin.dm_policy |
str | open |
— | Direct-message reply policy |
channels.weixin.dataDir |
channels.weixin.data_dir |
str | "" |
— | Local data directory for the Weixin channel |
channels.weixin.typingIndicator |
channels.weixin.typing_indicator |
bool | true |
— | Send a typing indicator to the user while a message is being processed |
channels.qqbot.enabled |
channels.qqbot.enabled |
bool | false |
— | Enable the QQ bot channel |
channels.qqbot.appId |
channels.qqbot.app_id |
str | "" |
— | QQ bot AppID |
channels.qqbot.appSecret |
channels.qqbot.app_secret |
str | "" |
— | QQ bot AppSecret |
channels.qqbot.allowFrom |
channels.qqbot.allow_from |
list | [] |
— | Allowlist of user IDs permitted to interact (empty = all) |
channels.qqbot.sandbox |
channels.qqbot.sandbox |
bool | false |
— | Use the QQ sandbox environment |
channels.qqbot.markdownSupport |
channels.qqbot.markdown_support |
bool | true |
— | Send as QQ native Markdown (msg_type=2) and keep inline markers like bold/code. On by default; if the bot lacks native Markdown permission, the first message is rejected and auto-retried as plain text, and later messages to that target skip markdown (re-probed after 24h). Tables/headings/HR are downgraded to readable plain text regardless. When off, always sends plain text |
channels.qqbot.mediaEnabled |
channels.qqbot.media_enabled |
bool | true |
— | Enable media (image/file) sending and receiving |
channels.qqbot.mediaMaxFileSizeMb |
channels.qqbot.media_max_file_size_mb |
int | 20 |
— | Maximum size per uploaded media file (MB) |
channels.qqbot.mediaUploadCacheSize |
channels.qqbot.media_upload_cache_size |
int | 500 |
— | Maximum number of cached media upload results |
channels.qqbot.mediaParseTags |
channels.qqbot.media_parse_tags |
bool | true |
— | Parse media tags embedded in messages |
channels.feishu.enabled |
channels.feishu.enabled |
bool | false |
— | Enable the Feishu channel |
channels.feishu.appId |
channels.feishu.app_id |
str | "" |
— | Feishu app ID |
channels.feishu.appSecret |
channels.feishu.app_secret |
str | "" |
— | Feishu app secret |
channels.feishu.verificationToken |
channels.feishu.verification_token |
str | "" |
— | Feishu event callback verification token |
channels.feishu.encryptionKey |
channels.feishu.encryption_key |
str | "" |
— | Feishu event encryption key |
channels.feishu.webhookPath |
channels.feishu.webhook_path |
str | /feishu |
— | HTTP path on which Feishu events are received |
channels.feishu.host |
channels.feishu.host |
str | 0.0.0.0 |
— | Feishu server bind address |
channels.feishu.port |
channels.feishu.port |
int | 8083 |
— | Feishu server listen port |
channels.feishu.groupPolicy |
channels.feishu.group_policy |
str | mention |
— | Group trigger policy: mention=respond only when @mentioned, all=respond to every message |
channels.feishu.botOpenId |
channels.feishu.bot_open_id |
str | "" |
— | Bot's own open_id for group chat mention filtering |
channels.dingtalk.enabled |
channels.dingtalk.enabled |
bool | false |
— | Enable the DingTalk channel |
channels.dingtalk.appKey |
channels.dingtalk.app_key |
str | "" |
— | DingTalk app key |
channels.dingtalk.appSecret |
channels.dingtalk.app_secret |
str | "" |
— | DingTalk app secret |
channels.dingtalk.robotCode |
channels.dingtalk.robot_code |
str | "" |
— | DingTalk robot code |
channels.dingtalk.allowFrom |
channels.dingtalk.allow_from |
list | [] |
— | Allowlist of user IDs permitted to interact (empty = all) |
channels.email.enabled |
channels.email.enabled |
bool | false |
— | Enable the email channel |
channels.email.imapHost |
channels.email.imap_host |
str | "" |
— | IMAP server host for receiving mail |
channels.email.imapPort |
channels.email.imap_port |
int | 993 |
— | IMAP server port for receiving mail |
channels.email.smtpHost |
channels.email.smtp_host |
str | "" |
— | SMTP server host for sending mail |
channels.email.smtpPort |
channels.email.smtp_port |
int | 465 |
— | SMTP server port for sending mail |
channels.email.username |
channels.email.username |
str | "" |
— | Mailbox login username |
channels.email.password |
channels.email.password |
str | "" |
— | Mailbox login password or app token |
channels.email.useSsl |
channels.email.use_ssl |
bool | true |
— | Use SSL when connecting to mail servers |
channels.email.pollIntervalSeconds |
channels.email.poll_interval_seconds |
int | 30 |
— | Interval between new-mail polls (seconds) |
channels.email.allowFrom |
channels.email.allow_from |
list | [] |
— | Allowlist of sender addresses permitted to interact (empty = all) |
channels.wecom.enabled |
channels.wecom.enabled |
bool | false |
— | Enable the WeCom channel |
channels.wecom.corpId |
channels.wecom.corp_id |
str | "" |
— | WeCom corporation ID |
channels.wecom.agentId |
channels.wecom.agent_id |
str | "" |
— | WeCom application AgentId |
channels.wecom.secret |
channels.wecom.secret |
str | "" |
— | WeCom application secret |
channels.wecom.token |
channels.wecom.token |
str | "" |
— | WeCom callback verification token |
channels.wecom.encodingAesKey |
channels.wecom.encoding_aes_key |
str | "" |
— | EncodingAESKey for WeCom encrypted callbacks; empty means plaintext mode |
channels.wecom.webhookPath |
channels.wecom.webhook_path |
str | /wecom |
— | HTTP path on which WeCom events are received |
channels.wecom.host |
channels.wecom.host |
str | 0.0.0.0 |
— | WeCom server bind address |
channels.wecom.port |
channels.wecom.port |
int | 8084 |
— | WeCom server listen port |
channels.matrix.enabled |
channels.matrix.enabled |
bool | false |
— | Enable the Matrix channel |
channels.matrix.homeserver |
channels.matrix.homeserver |
str | "" |
— | Matrix homeserver URL |
channels.matrix.userId |
channels.matrix.user_id |
str | "" |
— | Matrix bot user ID |
channels.matrix.accessToken |
channels.matrix.access_token |
str | "" |
— | Matrix access token |
channels.matrix.allowRooms |
channels.matrix.allow_rooms |
list | [] |
— | Allowlist of room IDs the bot responds in (empty = all) |
channels.matrix.reactionsEnabled |
channels.matrix.reactions_enabled |
bool | true |
— | Whether to add emoji reactions to messages |
channels.sendProgress |
channels.send_progress |
bool | true |
— | Send progress updates to the user |
channels.sendToolHints |
channels.send_tool_hints |
bool | true |
— | Send tool-invocation hints to the user |
channels.streamChannels |
channels.stream_channels |
list | ['cli', 'telegram', 'discord', 'slack', 'gateway:*'] |
— | Channels for which streaming incremental replies are enabled |
channels.streamFlushChars |
channels.stream_flush_chars |
int | 180 |
— | Character count that triggers a streaming flush |
channels.streamFlushIntervalMs |
channels.stream_flush_interval_ms |
int | 1500 |
— | Maximum interval between streaming flushes (ms) |
channels.streamParagraphMode |
channels.stream_paragraph_mode |
bool | true |
— | Flush streaming output on paragraph boundaries |
channels.streamLocalFlushChars |
channels.stream_local_flush_chars |
int | 24 |
— | Flush threshold for local channels (cli/gateway); 0 reuses the shared value |
channels.streamLocalFlushIntervalMs |
channels.stream_local_flush_interval_ms |
int | 100 |
— | Maximum interval between flushes for local channels (cli/gateway), in ms |
channels.streamLocalChannels |
channels.stream_local_channels |
list | ['cli', 'gateway:*'] |
— | Channels that use the local low-latency streaming tier (supports prefix:*) |
channels.streamOptimisticChannels |
channels.stream_optimistic_channels |
list | ['gateway:cli'] |
— | Channels allowed to stream optimistically (pre-tool draft sent then retracted); only channels that can redraw in place |
channels.transcriptionApiKey |
channels.transcription_api_key |
str | "" |
— | API key for the voice transcription service |
models¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
models.defaultModel |
models.default_model |
str | "" |
— | Default model used when no route matches |
models.providers |
models.providers |
list | [] |
— | List of model provider configurations |
models.providers[].name |
models.providers[].name |
str | "" |
— | Provider name referenced by routes |
models.providers[].apiKey |
models.providers[].api_key |
str | "" |
— | Provider API key |
models.providers[].apiKeyEnv |
models.providers[].api_key_env |
str | "" |
— | Read the API key from this environment variable so a host can inject an ephemeral secret without persisting it |
models.providers[].apiBase |
models.providers[].api_base |
str | "" |
— | Provider API base URL |
models.providers[].models |
models.providers[].models |
list | [] |
— | Models served by this provider |
models.providers[].extraHeaders |
models.providers[].extra_headers |
dict | {} |
— | Extra HTTP headers attached to requests |
models.providers[].maxRetries |
models.providers[].max_retries |
int | 3 |
— | Max retries on transient errors (exponential backoff) |
models.providers[].timeoutSeconds |
models.providers[].timeout_seconds |
int | 120 |
— | Per-request timeout (seconds) |
models.providers[].streamIncludeUsage |
models.providers[].stream_include_usage |
bool | true |
— | Send stream_options.include_usage on streaming requests (for token/cost accounting); set false for OpenAI-compatible endpoints that reject the field |
models.providers[].rateLimitRpm |
models.providers[].rate_limit_rpm |
int | 0 |
— | Provider request-per-minute cap (0 = unlimited) |
models.providers[].credentialPool |
models.providers[].credential_pool |
list | [] |
— | Pool of API keys rotated for this provider |
models.routes |
models.routes |
list | [] |
— | List of task-to-model routing rules |
models.routes[].model |
models.routes[].model |
str | "" |
— | Model name used by this route |
models.routes[].provider |
models.routes[].provider |
str | "" |
— | Provider name bound to this route |
models.routes[].taskTypes |
models.routes[].task_types |
list | [] |
— | Task types that match this route |
models.routes[].fallbackModels |
models.routes[].fallback_models |
list | [] |
— | Fallback models when the primary fails |
models.routes[].maxTokens |
models.routes[].max_tokens |
int | 8192 |
— | Maximum tokens generated for this route |
models.routes[].temperature |
models.routes[].temperature |
float | 0.7 |
— | Sampling temperature for this route |
models.routes[].contextWindow |
models.routes[].context_window |
int | 0 |
— | Explicit context-window override for this route's model (0 = unset, resolved automatically); takes precedence over the built-in registry and global fallback |
models.fallbackModel |
models.fallback_model |
str | "" |
— | Global fallback model |
models.modelWindows |
models.model_windows |
dict | {} |
— | Map of model id to context-window tokens (auto-captured by setup from provider metadata, or hand-set); ranks below a route override and above the built-in registry |
tools¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
tools.profile |
tools.profile |
Literal | full |
minimal/messaging/coding/full | Preset tool profile |
tools.allow |
tools.allow |
list | [] |
— | Explicit allowlist of tools overriding the profile |
tools.alsoAllow |
tools.also_allow |
list | [] |
— | Tools additionally allowed on top of the profile |
tools.deny |
tools.deny |
list | [] |
— | Explicit blocklist of tools |
tools.exec.enabled |
tools.exec.enabled |
bool | true |
— | Enable the shell/process execution tool |
tools.exec.maxOutputChars |
tools.exec.max_output_chars |
int | 2000000 |
— | Acquisition character cap for command output (not the model-facing cap, which is spill.maxInlineChars) |
tools.exec.host |
tools.exec.host |
Literal | sandbox |
auto/local/sandbox/container/remote | Host environment in which commands execute |
tools.exec.security |
tools.exec.security |
Literal | allowlist |
deny/allowlist/full | Command execution security mode |
tools.exec.ask |
tools.exec.ask |
Literal | on_miss |
off/on_miss/always | When to ask for approval before running a command |
tools.exec.safeBins |
tools.exec.safe_bins |
list | ['awk', 'cat', 'date', 'echo', 'find', 'grep', 'head', 'ls', 'pwd', 'rg', 'sed', 'sort', 'tail', 'tr', 'uniq', 'wc'] |
— | Commands allowed without approval under allowlist mode |
tools.exec.allowedCommands |
tools.exec.allowed_commands |
list | [] |
— | Additional allowlist of commands permitted to run |
tools.exec.blockedCommands |
tools.exec.blocked_commands |
list | [] |
— | Blocklist of commands forbidden from running |
tools.web.enabled |
tools.web.enabled |
bool | false |
— | Enable the web access tool |
tools.web.proxy |
tools.web.proxy |
str | None | null |
— |
tools.web.timeoutSeconds |
tools.web.timeout_seconds |
int | 30 |
— | Web request timeout (seconds) |
tools.web.searchApiKey |
tools.web.search_api_key |
str | "" |
— | Search service API key |
tools.web.searchProvider |
tools.web.search_provider |
Literal | brave |
brave/tavily/serpapi/searxng/serply | Web search service provider (serply uses the Serply SERP API: https://serply.io, docs https://serply.io/docs) |
tools.web.searchApiBase |
tools.web.search_api_base |
str | "" |
— | Search service API base URL |
tools.web.allowPrivateAddresses |
tools.web.allow_private_addresses |
bool | false |
— | Allow web_fetch to reach private/loopback addresses (SSRF risk) |
tools.browser.enabled |
tools.browser.enabled |
bool | true |
— | Enable browser automation tool (default on; auto-degrades if playwright/chromium missing) |
tools.browser.maxSessions |
tools.browser.max_sessions |
int | 3 |
— | Max concurrent browser sessions per owner |
tools.browser.maxTotalSessions |
tools.browser.max_total_sessions |
int | 10 |
— | Global cap on concurrent browser sessions across all owners (each is a Chromium context); <=0 disables |
tools.browser.sessionIdleTimeoutSec |
tools.browser.session_idle_timeout_sec |
int | 300 |
— | Idle seconds before a browser session is reaped |
tools.browser.maxSnapshotChars |
tools.browser.max_snapshot_chars |
int | 8000 |
— | Accessibility snapshot text truncation limit (chars) |
tools.browser.headless |
tools.browser.headless |
bool | true |
— | Headless mode (required on servers) |
tools.browser.navTimeoutSec |
tools.browser.nav_timeout_sec |
int | 30 |
— | Per-navigation timeout (seconds) |
tools.browser.allowPrivateAddresses |
tools.browser.allow_private_addresses |
bool | false |
— | Allow navigating to private addresses (default blocked, reuses SSRF policy) |
tools.browser.dialogPolicy |
tools.browser.dialog_policy |
str | dismiss |
— | Native dialog auto-handling policy: dismiss or accept (unhandled dialogs block the page) |
tools.browser.allowEvaluate |
tools.browser.allow_evaluate |
bool | true |
— | Allow the evaluate action to run JS in the page. The expression blacklist stops careless use, not deliberate obfuscation; turn this off where in-page code execution is unacceptable |
tools.browser.allowUnsafeEvaluate |
tools.browser.allow_unsafe_evaluate |
bool | false |
— | Skip evaluate's sensitive-expression checks (cookie/storage reads, script navigation). Default denied |
tools.browser.persistLoginState |
tools.browser.persist_login_state |
bool | false |
— | Persist browser login state (cookies/localStorage) into the workspace for reuse |
tools.browser.viewportWidth |
tools.browser.viewport_width |
int | 1280 |
— | Browser viewport width (px) |
tools.browser.viewportHeight |
tools.browser.viewport_height |
int | 800 |
— | Browser viewport height (px) |
tools.browser.userAgent |
tools.browser.user_agent |
str | "" |
— | Custom User-Agent; empty uses the Chromium default |
tools.restrictToWorkspace |
tools.restrict_to_workspace |
bool | false |
— | Restrict file operations to the workspace |
tools.safeWriteRoot |
tools.safe_write_root |
str | "" |
— | Root directory under which writes are permitted |
tools.inboundDocumentEnabled |
tools.inbound_document_enabled |
bool | true |
— | Auto download, decrypt and parse inbound document attachments |
tools.inboundDocumentMaxChars |
tools.inbound_document_max_chars |
int | 8000 |
— | Char cap for auto-injecting inbound document text; beyond it, inject a summary and hint read_document |
tools.mcpServers |
tools.mcp_servers |
dict | {} |
— | MCP server configurations keyed by name (the key feeds tool names and credential filenames; letters, digits, dot, dash, underscore only) |
tools.mcpServers{}.command |
tools.mcp_servers{}.command |
str | "" |
— | Command launching the MCP server over stdio (mutually exclusive with url) |
tools.mcpServers{}.args |
tools.mcp_servers{}.args |
list | [] |
— | Arguments for the MCP server launch command |
tools.mcpServers{}.env |
tools.mcp_servers{}.env |
dict | {} |
— | Environment variables for the MCP server process; ${VAR}/$VAR expanded |
tools.mcpServers{}.url |
tools.mcp_servers{}.url |
str | "" |
— | MCP server URL for Streamable HTTP transport (mutually exclusive with command) |
tools.mcpServers{}.headers |
tools.mcp_servers{}.headers |
dict | {} |
— | Custom headers for the MCP HTTP connection; ${VAR} expanded |
tools.mcpServers{}.auth |
tools.mcp_servers{}.auth |
Literal | "" |
/oauth | Auth mode: empty for none (or preset headers), 'oauth' for OAuth 2.1 PKCE |
tools.mcpServers{}.trustLevel |
tools.mcp_servers{}.trust_level |
Literal | untrusted |
untrusted/trusted | Trust level for this server. untrusted (default): tools are gated at exec or above and server-supplied readOnlyHint cannot lower it; trusted: annotations are honoured. Only set trusted for servers you control. |
tools.mcpServers{}.enabled |
tools.mcp_servers{}.enabled |
bool | true |
— | Enable this MCP server |
tools.mcpServers{}.timeout |
tools.mcp_servers{}.timeout |
int | 120 |
— | MCP tool call timeout (seconds) |
tools.mcpServers{}.connectTimeout |
tools.mcp_servers{}.connect_timeout |
int | 60 |
— | Connection and initialize handshake timeout (seconds) |
tools.mcpServers{}.toolsInclude |
tools.mcp_servers{}.tools_include |
list | [] |
— | Allowlist of MCP tools to expose (empty = all) |
tools.mcpServers{}.toolsExclude |
tools.mcp_servers{}.tools_exclude |
list | [] |
— | Blocklist of MCP tools to exclude |
tools.mcpSecurityPolicy |
tools.mcp_security_policy |
Literal | block |
warn/block | MCP injection-scan policy: block rejects suspicious tools, warn only logs. Covers tool name, description and inputSchema descriptions/titles |
tools.imageGen.enabled |
tools.image_gen.enabled |
bool | true |
— | Enable the image generation tool (unset skips registration even if a key is present) |
tools.imageGen.backend |
tools.image_gen.backend |
str | openai |
— | Image generation backend |
tools.imageGen.apiKey |
tools.image_gen.api_key |
str | "" |
— | OpenAI-compatible backend API key |
tools.imageGen.apiBase |
tools.image_gen.api_base |
str | "" |
— | OpenAI-compatible backend API base URL |
tools.imageGen.model |
tools.image_gen.model |
str | "" |
— | Image generation model name |
tools.imageGen.falKey |
tools.image_gen.fal_key |
str | "" |
— | FAL.ai backend access key |
tools.imageGen.falModel |
tools.image_gen.fal_model |
str | "" |
— | FAL.ai image generation model name |
tools.tts.enabled |
tools.tts.enabled |
bool | true |
— | Enable the TTS tool (unset skips registration; stored credentials are kept) |
tools.tts.openaiApiKey |
tools.tts.openai_api_key |
str | "" |
— | OpenAI TTS API key |
tools.tts.openaiApiBase |
tools.tts.openai_api_base |
str | "" |
— | OpenAI TTS API base URL |
tools.tts.model |
tools.tts.model |
str | "" |
— | TTS model name |
tools.tts.defaultBackend |
tools.tts.default_backend |
str | edge |
— | Default text-to-speech backend |
tools.tts.defaultVoice |
tools.tts.default_voice |
str | "" |
— | Default synthesis voice |
tools.codeExec.enabled |
tools.code_exec.enabled |
bool | true |
— | Enable the code execution tool |
tools.codeExec.timeoutSeconds |
tools.code_exec.timeout_seconds |
int | 30 |
— | Code execution timeout (seconds) |
tools.codeExec.allowedLanguages |
tools.code_exec.allowed_languages |
list | ['python', 'javascript', 'bash'] |
— | Languages permitted for code execution |
tools.mcp.enabled |
tools.mcp.enabled |
bool | true |
— | Enable MCP tool integration (false skips every configured MCP server) |
execution¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
execution.defaultExecutor |
execution.default_executor |
Literal | sandbox |
local/sandbox/container/remote | Default command executor type |
execution.sandboxRoot |
execution.sandbox_root |
str | /tmp/echo-agent-sandbox |
— | Root directory for the sandbox executor |
execution.containerImage |
execution.container_image |
str | "" |
— | Image used by the container executor |
execution.remoteHost |
execution.remote_host |
str | "" |
— | Target host for the remote executor |
execution.remoteUser |
execution.remote_user |
str | root |
— | Login user for the remote executor |
execution.remoteKeyPath |
execution.remote_key_path |
str | "" |
— | SSH private key path for the remote executor |
execution.remoteStrictHostKey |
execution.remote_strict_host_key |
Literal | accept-new |
no/accept-new/yes | SSH strict host key checking policy |
execution.remoteConnectTimeout |
execution.remote_connect_timeout |
int | 10 |
— | Remote executor connection timeout (seconds) |
execution.networkPolicy |
execution.network_policy |
Literal | deny |
allow/deny/restricted | Network access policy for the execution environment |
execution.maxBackgroundTasks |
execution.max_background_tasks |
int | 64 |
— | Max concurrent background tasks; over limit discardable dropped, durable queued |
permissions¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
permissions.adminUsers |
permissions.admin_users |
list | [] |
— | Global administrator users |
permissions.approval.requireApproval |
permissions.approval.require_approval |
list | ['cronjob', 'delegate_task', 'dep_install', 'exec', 'execute_code', 'process', 'skill_install', 'skill_manage', 'spawn_task'] |
— | Tools/actions that require approval before running. The risk tier (EXEC/DANGEROUS) already requires approval on its own; this list only adds tools and can never exempt one. delegate_task/spawn_task are listed because a worker they dispatch can call exec, and the dispatch is the last point where the caller's own authority is still known |
permissions.approval.autoApprove |
permissions.approval.auto_approve |
list | [] |
— | Tools/actions auto-approved without prompting |
permissions.approval.autoDeny |
permissions.approval.auto_deny |
list | [] |
— | Tools/actions auto-denied |
permissions.approval.defaultPolicy |
permissions.approval.default_policy |
Literal | approve |
approve/deny/ask | Default approval policy when no rule matches |
permissions.approval.waitTimeoutSeconds |
permissions.approval.wait_timeout_seconds |
int | 300 |
— | Timeout while waiting for human approval (seconds) |
permissions.approval.cliAutoApprove |
permissions.approval.cli_auto_approve |
bool | true |
— | Auto-approve actions on the CLI channel |
permissions.approval.trustedChannels |
permissions.approval.trusted_channels |
list | [] |
— | Trusted channels exempt from approval |
permissions.approval.mode |
permissions.approval.mode |
Literal | smart |
manual/smart/off | Approval mode: manual, smart, or off |
permissions.approval.smartModel |
permissions.approval.smart_model |
str | "" |
— | Model used to judge approvals in smart mode |
permissions.approval.unattendedPolicy |
permissions.approval.unattended_policy |
Literal | deny |
deny/allow_safe | Approval policy when running unattended |
permissions.elevated.enabled |
permissions.elevated.enabled |
bool | false |
— | Enable the elevated-permission mechanism |
permissions.elevated.allowFrom |
permissions.elevated.allow_from |
dict | {} |
— | Per-channel mapping of users allowed to elevate |
credentials¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
credentials.encryptionKeyEnv |
credentials.encryption_key_env |
str | ECHO_AGENT_CREDENTIAL_KEY |
— | Environment variable holding the credential encryption key |
credentials.requireEncryption |
credentials.require_encryption |
bool | true |
— | Require credential encryption |
session¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
session.maxHistoryMessages |
session.max_history_messages |
int | 500 |
— | Maximum history messages retained per session |
session.expiryHours |
session.expiry_hours |
int | 72 |
— | Session expiry time (hours) |
session.contextWindowTokens |
session.context_window_tokens |
int | 0 |
— | Global fallback context-window budget (0 = unset, so an unknown model lands on the 256K modern baseline; only an explicit positive value takes effect, for private/local models that cannot be resolved dynamically, and it ranks below models.dev and the built-in registry) |
session.compressionWindowCap |
session.compression_window_cap |
int | 200000 |
— | Compression-budget cap (0 = uncapped): the model's real window drives the display, but compression triggers against min(real_window, cap) so a large-window model does not let context balloon before compressing, which would raise per-request cost and latency |
session.introductionEnabled |
session.introduction_enabled |
bool | true |
— | Send a self-introduction on new sessions |
session.imClarifyPendingTtlSeconds |
session.im_clarify_pending_ttl_seconds |
int | 300 |
— | TTL (seconds) for an IM follow-up question; after this, the next message is treated as new rather than an answer to the pending question |
session.introductionTemplate |
session.introduction_template |
str | "" |
— | Self-introduction template |
session.historyImageTtlMinutes |
session.history_image_ttl_minutes |
int | 30 |
— | Time-to-live for images in history (minutes) |
session.historyImageLimit |
session.history_image_limit |
int | 4 |
— | Maximum images retained in history |
session.historyImageSkipIfCurrent |
session.history_image_skip_if_current |
bool | true |
— | Skip history images when the current turn already has one |
session.groupSessionScope |
session.group_session_scope |
Literal | per_user |
per_user/shared | Group session scope: per_user = isolate per sender (default), shared = whole group shares one session |
memory¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
memory.enabled |
memory.enabled |
bool | true |
— | Enable cognitive memory |
memory.scopePolicy |
memory.scope_policy |
Literal | session |
legacy/session | Memory scope policy |
memory.crossChannelOwner |
memory.cross_channel_owner |
bool | true |
— | Cross-channel owner memory: when on, senders listed in principal_bindings share owner memory across 1:1 DMs on any channel; unlisted senders and groups stay per-session. Off = all per-session. |
memory.ownerKey |
memory.owner_key |
str | owner |
— | Owner memory scope key (single-subject default owner; rarely needs changing) |
memory.allowModelEnvironmentWrites |
memory.allow_model_environment_writes |
bool | false |
— | Allow the model memory tool to write ENVIRONMENT memory or global-tagged entries (these bypass scope, globally visible). Off by default; the model may only write its own scope's USER memory. |
memory.principalBindings |
memory.principal_bindings |
list | [] |
— | Owner identity bindings: each "channel:sender_id"; listed senders' 1:1 DMs map to the owner memory scope for cross-channel sharing; others stay per-session. Security assumption: only for channels whose sender_id is platform-guaranteed unforgeable (e.g. Telegram/Slack); otherwise anyone spoofing that id reads owner memory. Effective only when cross_channel_owner is on. |
memory.retrievalOnMiss |
memory.retrieval_on_miss |
Literal | degrade |
degrade/sync | Behavior on retrieval cache miss: degrade=bounded sync retrieval with keyword fallback, sync=full synchronous retrieval |
memory.retrievalMissTimeoutSeconds |
memory.retrieval_miss_timeout_seconds |
float | 0.8 |
— | Time budget (s) for bounded sync retrieval on cache miss in degrade mode; falls back to local keyword search on timeout; 0=skip entirely (legacy) |
memory.cacheTtlSeconds |
memory.cache_ttl_seconds |
float | 60.0 |
— | Retrieval prefetch cache freshness TTL in seconds |
memory.cacheJaccardMin |
memory.cache_jaccard_min |
float | 0.3 |
— | Min Jaccard similarity between current and cached query; below is a miss |
memory.consolidationThreshold |
memory.consolidation_threshold |
int | 20 |
— | Entry threshold that triggers memory consolidation |
memory.narrativeEpisodeCount |
memory.narrative_episode_count |
int | 3 |
— | Number of recent episode summaries injected as the snapshot narrative layer (carries cross-entry temporal/causal narrative) |
memory.vectorEnabled |
memory.vector_enabled |
bool | true |
— | Enable vector-based memory retrieval |
memory.vectorDimensions |
memory.vector_dimensions |
int | 0 |
— | Memory embedding vector dimensions; 0 = follow the active embedding model |
memory.maxUserMemories |
memory.max_user_memories |
int | 1000 |
— | Maximum stored memories per user |
memory.maxEnvMemories |
memory.max_env_memories |
int | 500 |
— | Maximum stored environment memories |
memory.memoryNudgeInterval |
memory.memory_nudge_interval |
int | 10 |
— | Turn interval for nudging the model to store memories |
memory.importanceDecayDays |
memory.importance_decay_days |
float | 30.0 |
— | Memory importance decay period (days) |
memory.snapshotEnabled |
memory.snapshot_enabled |
bool | true |
— | Inject memory snapshots into context |
memory.snapshotLayering |
memory.snapshot_layering |
bool | true |
— | Snapshot layering: the always-on core keeps only top-K (by effective importance) plus explicitly pinned entries; the long tail is no longer injected into the system prompt every turn but surfaces via query-driven recall. Disable to revert to the legacy full snapshot (USER≤50/ENV≤30). Addresses the query-independent 'always-on profile looks unrelated' injection path. |
memory.snapshotUserCoreMax |
memory.snapshot_user_core_max |
int | 12 |
— | Max USER entries in the always-on core when layering is on (top-K + pinned). The long tail surfaces via recall. |
memory.snapshotEnvCoreMax |
memory.snapshot_env_core_max |
int | 8 |
— | Max ENVIRONMENT entries in the always-on core when layering is on (top-K + pinned). |
memory.contradictionDetection |
memory.contradiction_detection |
bool | true |
— | Enable memory contradiction detection |
memory.sleepConsolidation |
memory.sleep_consolidation |
bool | true |
— | Enable idle-time (sleep) memory consolidation |
memory.archivalThreshold |
memory.archival_threshold |
float | 0.05 |
— | Archival score threshold; entries below it move to the archival tier |
memory.forgetThreshold |
memory.forget_threshold |
float | 0.01 |
— | Forget score threshold; entries below it are forgotten |
memory.lineageMaxVersions |
memory.lineage_max_versions |
int | 3 |
— | Max superseded versions kept per key lineage; older ones move to archival for forgetting |
memory.lineageRetentionDays |
memory.lineage_retention_days |
int | 90 |
— | Retention days for superseded versions; stale ones move to archival even under the version cap |
memory.maxWorkingMemory |
memory.max_working_memory |
int | 20 |
— | Maximum working-memory entries |
memory.embeddingBackend |
memory.embedding_backend |
Literal | auto |
auto/local/provider | Embedding backend: auto=probe provider at startup, fall back to fastembed on failure; local=use local fastembed directly; provider=force provider, error out if probe fails |
memory.embeddingModel |
memory.embedding_model |
str | "" |
— | Embedding model used for memory vectorization |
memory.localEmbeddingModel |
memory.local_embedding_model |
str | BAAI/bge-small-zh-v1.5 |
— | Local fastembed fallback model when no embed-capable provider exists; empty string disables the fallback |
memory.hfEmbeddingEndpoint |
memory.hf_embedding_endpoint |
str | https://hf-mirror.com |
— | HuggingFace download endpoint for the local fastembed model; defaults to the hf-mirror.com mirror for CN networks. Set to https://huggingface.co for the official source, or empty to leave any existing HF_ENDPOINT env var untouched |
memory.embedTimeoutSeconds |
memory.embed_timeout_seconds |
float | 1.5 |
— | Query-embedding timeout (seconds); falls back to keyword search on timeout |
memory.rrfMinSimilarity |
memory.rrf_min_similarity |
float | 0.3 |
— | RRF vector-recall similarity floor (tunable). Vector hits below this cosine occupy no rank slot, contribute no RRF term, and do not count as a vector-admission path — keeping low-similarity hits from polluting real candidates. For normalized sentence embeddings 0.25 is 'barely related'; 0.30 is a safer floor. The BM25 side instead uses a discriminative-token gate (a single common CJK char never admits) rather than a score floor (different scale). |
memory.rerankEnabled |
memory.rerank_enabled |
bool | true |
— | Enable cross-encoder reranking. RRF only fuses rank order; a cross-encoder scores (query,doc) jointly — the relevance gold standard. When on, the fused top-K is reranked (top-K only, cheap); timeout/failure falls back to the RRF order. Default on: the reranker model (~941MB, sha256-verified) is prefetched at install time from the self-hosted mirrors (Gitee split volumes first, then the whole file from GitHub) and then served offline; if the prefetch was missed the runtime downloads it with backoff. Until it is ready each turn degrades to the RRF order without blocking the reply. Disable to drop the model and the per-turn rerank latency entirely. |
memory.rerankModel |
memory.rerank_model |
str | BAAI/bge-reranker-base |
— | Cross-encoder rerank model (a fastembed TextCrossEncoder model name). For CN/multilingual use BAAI/bge-reranker-base or jinaai/jina-reranker-v2-base-multilingual. |
memory.rerankTopK |
memory.rerank_top_k |
int | 10 |
— | Number of fused top-K candidates the reranker rescores; the rest keep RRF order. Bounds rerank cost. Default 10: the cross-encoder is CPU-only and a base-size model spends tens of ms per (query,doc) pair, so K=20 routinely blows the inference budget and wastes the whole pass; the recall quota is only 5 memories + 3 episodes, so K=10 already covers twice the quota and going higher buys latency, not ranking changes. |
memory.rerankMinScore |
memory.rerank_min_score |
float | 0.0 |
— | Rerank absolute relevance floor (0 = reorder only, drop nothing). When >0, reranked candidates below it are dropped (within the top-K only; if the floor drops everything it falls back to unfiltered, so a miscalibrated threshold can't empty recall). |
memory.rerankTimeoutSeconds |
memory.rerank_timeout_seconds |
float | 5.0 |
— | Per-call wait budget (seconds) for reranker INFERENCE; on timeout this turn keeps the RRF order. Inference only — model load/download uses rerank_load_timeout_seconds. (When both shared one value, 2s was neither enough to load a ~1GB model nor enough for a base-size model to score the top-K on CPU, so every turn degraded.) |
memory.rerankLoadTimeoutSeconds |
memory.rerank_load_timeout_seconds |
float | 60.0 |
— | Per-wait budget (seconds) for reranker model load/download, symmetric with embed_load_timeout_seconds. A timeout is not a failure: the background load continues, this turn keeps the RRF order, and the model is picked up transparently once ready. Setting it as low as the inference budget (2s) makes every wait time out and wastes the startup warmup. |
memory.embedLoadTimeoutSeconds |
memory.embed_load_timeout_seconds |
float | 60.0 |
— | Local embedding model first-load/download timeout (seconds); on timeout the embedder is marked failed and degrades to keyword search, preventing a hung download from starving the process |
memory.localEmbeddingCacheDir |
memory.local_embedding_cache_dir |
str | ~/.echo-agent/models/fastembed |
— | fastembed cache directory for the local embedding model; install-time prefetch and runtime share this path for offline cache hits. Defaults to a stable location under echo-home (not the volatile tempdir fastembed uses by default). Empty leaves fastembed's default (FASTEMBED_CACHE_PATH or tempdir) untouched |
memory.localEmbeddingMaxLoadAttempts |
memory.local_embedding_max_load_attempts |
int | 5 |
— | Max load attempts for the local embedding model before staying keyword-only until restart; prevents one network blip from permanently degrading the process |
memory.localEmbeddingRetryBackoffSeconds |
memory.local_embedding_retry_backoff_seconds |
float | 30.0 |
— | Backoff (seconds) before re-attempting a failed local embedding model load, so a failure does not re-trigger a load on every message |
memory.contradictionScanOnStore |
memory.contradiction_scan_on_store |
bool | false |
— | Scan for contradictions at memory store time |
memory.autoResolveContradictions |
memory.auto_resolve_contradictions |
bool | false |
— | Auto-resolve same-key contradictions (newest-wins) during sleep consolidation; off by default |
memory.reflectionEnabled |
memory.reflection_enabled |
bool | true |
— | Enable sleep-time reflection (distillation + LLM conflict adjudication), piggybacking on sleep consolidation |
knowledge¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
knowledge.enabled |
knowledge.enabled |
bool | true |
— | Enable knowledge-base retrieval |
knowledge.docsDir |
knowledge.docs_dir |
str | data/knowledge |
— | Knowledge base documents directory |
knowledge.indexPath |
knowledge.index_path |
str | data/knowledge_index.json |
— | Knowledge base index file path |
knowledge.autoIndex |
knowledge.auto_index |
bool | true |
— | Automatically index the documents directory |
knowledge.chunkSize |
knowledge.chunk_size |
int | 1200 |
— | Document chunk size (characters) |
knowledge.chunkOverlap |
knowledge.chunk_overlap |
int | 120 |
— | Overlap between adjacent chunks (characters) |
knowledge.maxResults |
knowledge.max_results |
int | 5 |
— | Maximum knowledge retrieval results returned |
knowledge.allowedExtensions |
knowledge.allowed_extensions |
list | ['.md', '.txt', '.rst', '.json', '.yaml', '.yml', '.py', '.pdf', '.docx', '.xlsx', '.pptx'] |
— | Document extensions eligible for indexing |
multiAgent¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
multiAgent.enabled |
multi_agent.enabled |
bool | true |
— | Enable multi-agent delegation |
multiAgent.maxDepth |
multi_agent.max_depth |
int | 3 |
— | Maximum delegation nesting depth |
multiAgent.maxParallelWorkers |
multi_agent.max_parallel_workers |
int | 4 |
— | Maximum parallel workers |
multiAgent.maxIterations |
multi_agent.max_iterations |
int | 12 |
— | Default maximum iterations per worker |
multiAgent.auditPath |
multi_agent.audit_path |
str | data/delegation_audit.jsonl |
— | Delegation audit log path |
multiAgent.workerProfiles |
multi_agent.worker_profiles |
list | [] |
— | List of worker profile configurations |
multiAgent.workerProfiles[].id |
multi_agent.worker_profiles[].id |
str | "" |
— | Worker profile ID |
multiAgent.workerProfiles[].name |
multi_agent.worker_profiles[].name |
str | "" |
— | Worker profile name |
multiAgent.workerProfiles[].description |
multi_agent.worker_profiles[].description |
str | "" |
— | Worker profile description |
multiAgent.workerProfiles[].instructions |
multi_agent.worker_profiles[].instructions |
str | "" |
— | Worker profile system instructions |
multiAgent.workerProfiles[].defaultTools |
multi_agent.worker_profiles[].default_tools |
list | [] |
— | Default tools available to the worker |
multiAgent.workerProfiles[].model |
multi_agent.worker_profiles[].model |
str | "" |
— | Model used by the worker |
multiAgent.workerProfiles[].maxIterations |
multi_agent.worker_profiles[].max_iterations |
int | 12 |
— | Maximum iterations per worker task |
multiAgent.workerProfiles[].maxTokens |
multi_agent.worker_profiles[].max_tokens |
int | 8192 |
— | Maximum tokens generated by the worker |
multiAgent.workerProfiles[].temperature |
multi_agent.worker_profiles[].temperature |
float | 0.4 |
— | Worker sampling temperature |
scheduler¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
scheduler.enabled |
scheduler.enabled |
bool | true |
— | Enable the task scheduler |
scheduler.maxConcurrentJobs |
scheduler.max_concurrent_jobs |
int | 10 |
— | Maximum concurrent scheduled jobs |
checkpoint¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
checkpoint.enabled |
checkpoint.enabled |
bool | true |
— | Enable pre-edit shadow-git checkpoint safety net (auto-degrades if git missing) |
checkpoint.storePath |
checkpoint.store_path |
str | ~/.echo-agent/checkpoints/store |
— | Path to the shadow git store |
checkpoint.maxSnapshotsPerWorkspace |
checkpoint.max_snapshots_per_workspace |
int | 20 |
— | Max snapshots retained per workspace |
checkpoint.maxTotalSizeMb |
checkpoint.max_total_size_mb |
int | 500 |
— | Total store size cap in MB; exceeding triggers gc |
checkpoint.maxFileSizeMb |
checkpoint.max_file_size_mb |
int | 10 |
— | Files larger than this (MB) are excluded from snapshots |
validation¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
validation.enabled |
validation.enabled |
bool | true |
— | Enable post-write incremental validation feedback (auto-degrades if checkers missing) |
validation.timeoutSec |
validation.timeout_sec |
float | 5.0 |
— | Per-file validation timeout in seconds; times out silently |
validation.maxDiagnostics |
validation.max_diagnostics |
int | 10 |
— | Max diagnostics appended to the tool result |
validation.maxFileSizeKb |
validation.max_file_size_kb |
int | 512 |
— | Files larger than this (KB) skip validation |
mediaUnderstanding¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
mediaUnderstanding.audioEnabled |
media_understanding.audio_enabled |
bool | true |
— | Enable inbound audio/voice transcription (auto-degrades if no provider) |
mediaUnderstanding.audioProvider |
media_understanding.audio_provider |
str | auto |
— | Transcribe backend: auto (probe) / cloud / local (faster-whisper) |
mediaUnderstanding.minAudioSizeKb |
media_understanding.min_audio_size_kb |
float | 1.0 |
— | Audio smaller than this (KB) skips transcription |
mediaUnderstanding.maxAudioSizeKb |
media_understanding.max_audio_size_kb |
int | 25000 |
— | Audio larger than this (KB) skips transcription |
mediaUnderstanding.localModelSize |
media_understanding.local_model_size |
str | base |
— | Local faster-whisper model size (tiny/base/small/...) |
mediaUnderstanding.videoEnabled |
media_understanding.video_enabled |
bool | true |
— | Enable inbound video understanding (frames + audio; auto-degrades) |
mediaUnderstanding.videoFrameCount |
media_understanding.video_frame_count |
int | 4 |
— | Number of frames uniformly sampled from a video |
mediaUnderstanding.videoVisionModel |
media_understanding.video_vision_model |
str | "" |
— | Vision model override for video captioning (empty = provider default) |
mediaUnderstanding.videoVisionPrompt |
media_understanding.video_vision_prompt |
str | 简要描述这段视频的画面内容。 |
— | Prompt for video frame captioning |
mediaUnderstanding.minVideoSizeKb |
media_understanding.min_video_size_kb |
float | 1.0 |
— | Video smaller than this (KB) skips understanding |
mediaUnderstanding.maxVideoSizeKb |
media_understanding.max_video_size_kb |
int | 204800 |
— | Video larger than this (KB) skips understanding (~200MB cost guard) |
mediaUnderstanding.videoFfmpegConcurrency |
media_understanding.video_ffmpeg_concurrency |
int | 2 |
— | Max concurrent ffmpeg processes for video frame/audio extraction |
mediaUnderstanding.transcriptionBaseUrl |
media_understanding.transcription_base_url |
str | https://api.groq.com/openai/v1 |
— | Cloud transcription endpoint base_url (OpenAI-compatible) |
mediaUnderstanding.transcriptionModel |
media_understanding.transcription_model |
str | whisper-large-v3 |
— | Cloud transcription model name |
runtime¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
runtime.singleInstance |
runtime.single_instance |
bool | true |
— | Allow only one channel-consuming instance per workspace (prevents duplicate consumption/replies when a background service and a foreground run coexist); --force overrides it |
storage¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
storage.databasePath |
storage.database_path |
str | data/echo_agent.db |
— | SQLite database file path |
storage.sessionsDir |
storage.sessions_dir |
str | data/sessions |
— | Directory storing session data |
storage.memoryDir |
storage.memory_dir |
str | data/memory |
— | Directory storing memory data |
storage.logsDir |
storage.logs_dir |
str | data/logs |
— | Directory storing log files |
storage.spillDir |
storage.spill_dir |
str | data/spill |
— | Directory storing spilled tool-output artifacts (must be a dedicated workspace-relative subdirectory) |
spill¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
spill.enabled |
spill.enabled |
bool | true |
— | Spill oversized tool output to disk and show the model a preview only (off falls back to the old behaviour: output is bluntly truncated downstream at 16000 chars, losing the trailing conclusion with no way to retrieve it) |
spill.maxInlineChars |
spill.max_inline_chars |
int | 6000 |
— | Model-facing character cap for tool output; larger results are spilled and replaced with a head/tail preview |
spill.retentionDays |
spill.retention_days |
int | 7 |
— | Days to retain spill artifacts before deletion |
spill.maxTotalMb |
spill.max_total_mb |
int | 512 |
— | Total size cap (MB) for spill artifacts; oldest are deleted first when exceeded |
spill.sweepIntervalHours |
spill.sweep_interval_hours |
int | 6 |
— | Interval (hours) between spill artifact sweeps |
artifacts¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
artifacts.enabled |
artifacts.enabled |
bool | true |
— | Enable session-scoped user artifact tools |
artifacts.rootDir |
artifacts.root_dir |
str | data/artifacts |
— | User artifact directory (a dedicated workspace-relative directory) |
artifacts.maxChunkChars |
artifacts.max_chunk_chars |
int | 3000 |
— | Maximum characters accepted by one artifact append |
artifacts.maxArtifactMb |
artifacts.max_artifact_mb |
int | 50 |
— | Maximum size of one artifact in MB |
artifacts.textFallbackMaxChars |
artifacts.text_fallback_max_chars |
int | 100000 |
— | Largest artifact eligible for segmented text fallback when attachments are unsupported |
artifacts.textFallbackChunkChars |
artifacts.text_fallback_chunk_chars |
int | 1700 |
— | Chunk size for text fallback delivery (default fits Discord's 2000-character limit) |
artifacts.retentionDays |
artifacts.retention_days |
int | 30 |
— | Recommended retention period for finalized artifacts |
artifacts.maxTotalMb |
artifacts.max_total_mb |
int | 1024 |
— | Total user artifact size cap in MB (oldest artifacts are removed first) |
artifacts.sweepIntervalHours |
artifacts.sweep_interval_hours |
int | 24 |
— | User artifact cleanup interval in hours |
artifacts.allowedExtensions |
artifacts.allowed_extensions |
list | ['.md', '.txt', '.json', '.csv'] |
— | Text artifact extensions the model may create |
observability¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
observability.logLevel |
observability.log_level |
str | INFO |
— | Logging level |
observability.traceEnabled |
observability.trace_enabled |
bool | true |
— | Whether to record execution traces (off disables trace files) |
observability.maxTraceFiles |
observability.max_trace_files |
int | 500 |
— | Max retained trace files; oldest are rotated out when exceeded; <=0 disables rotation |
observability.healthCheckIntervalSeconds |
observability.health_check_interval_seconds |
int | 60 |
— | Health check interval (seconds) |
observability.otelEnabled |
observability.otel_enabled |
bool | true |
— | Enable OpenTelemetry metrics export |
observability.otelEndpoint |
observability.otel_endpoint |
str | "" |
— | OpenTelemetry export endpoint |
observability.otelServiceName |
observability.otel_service_name |
str | echo-agent |
— | OpenTelemetry service name |
observability.otelExportIntervalMs |
observability.otel_export_interval_ms |
int | 5000 |
— | OpenTelemetry metrics export interval (ms) |
observability.loopWatchdogEnabled |
observability.loop_watchdog_enabled |
bool | true |
— | Enable the event-loop watchdog (detects a frozen loop and self-exits for respawn) |
observability.loopWatchdogWarnSeconds |
observability.loop_watchdog_warn_seconds |
float | 5.0 |
— | Seconds of loop stall before warning and dumping thread stacks |
observability.loopWatchdogKillSeconds |
observability.loop_watchdog_kill_seconds |
float | 30.0 |
— | Seconds of loop freeze before self-exiting for supervisor respawn |
observability.loopWatchdogCheckIntervalSeconds |
observability.loop_watchdog_check_interval_seconds |
float | 5.0 |
— | Interval (s) at which the watchdog thread checks the heartbeat |
observability.loopWatchdogMaxRestartsPerHour |
observability.loop_watchdog_max_restarts_per_hour |
int | 5 |
— | Max watchdog self-exits per hour before the circuit breaker suspends restarts |
skills¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
skills.enabled |
skills.enabled |
bool | true |
— | Enable the skills system |
skills.skillsDir |
skills.skills_dir |
str | skills |
— | Skills directory |
skills.creationNudgeInterval |
skills.creation_nudge_interval |
int | 10 |
— | Turn interval for nudging skill creation |
skills.disabled |
skills.disabled |
list | [] |
— | List of disabled skills |
skills.externalDirs |
skills.external_dirs |
list | [] |
— | External directories from which to load skills |
skills.allowLazyInstalls |
skills.allow_lazy_installs |
bool | true |
— | Allow lazy on-demand dependency installs for skills |
skills.admissionPolicy |
skills.admission_policy |
Literal | stage_for_review |
auto_write/stage_for_review/manual_only | Skill auto-distillation admission policy |
skills.autoWriteRisk |
skills.auto_write_risk |
Literal | low |
low/high | Highest risk level auto-written under the auto_write policy |
compression¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
compression.enabled |
compression.enabled |
bool | true |
— | Enable context compression |
compression.triggerRatio |
compression.trigger_ratio |
float | 0.7 |
— | Context usage ratio that triggers compression |
compression.tailBudgetRatio |
compression.tail_budget_ratio |
float | 0.4 |
— | Budget ratio reserved for tail messages after compression |
compression.headProtectCount |
compression.head_protect_count |
int | 3 |
— | Number of head messages protected from compression |
compression.summaryTargetRatio |
compression.summary_target_ratio |
float | 0.2 |
— | Target summary length relative to source |
compression.summaryMinTokens |
compression.summary_min_tokens |
int | 2000 |
— | Minimum summary tokens |
compression.summaryMaxTokens |
compression.summary_max_tokens |
int | 12000 |
— | Maximum summary tokens |
compression.summaryModel |
compression.summary_model |
str | "" |
— | Model used to generate summaries |
compression.summaryCooldownSeconds |
compression.summary_cooldown_seconds |
int | 600 |
— | Cooldown between compressions (seconds) |
compression.toolPruningEnabled |
compression.tool_pruning_enabled |
bool | true |
— | Enable pruning of tool results |
compression.toolPruningTailBudgetRatio |
compression.tool_pruning_tail_budget_ratio |
float | 0.3 |
— | Tail budget ratio retained when pruning tool results |
compression.maxCompressionCount |
compression.max_compression_count |
int | 10 |
— | Maximum compressions per session |
gateway¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
gateway.enabled |
gateway.enabled |
bool | false |
— | Enable the gateway service |
gateway.host |
gateway.host |
str | 127.0.0.1 |
— | Gateway bind address. Defaults to 127.0.0.1 (this machine only). To serve the network, set 0.0.0.0 AND configure auth.apiTokens — binding non-loopback without a token is refused at startup — and list your proxy domain in auth.allowedHosts if behind a reverse proxy. Note that leaving this empty does NOT mean local: an empty string, like ::, is a wildcard bind equivalent to 0.0.0.0 and is subject to the same rules |
gateway.port |
gateway.port |
int | 58123 |
— | Gateway listen port (0 = dynamically assigned; the real port is written to workspace/.echo-agent/gateway.json) |
gateway.apiPrefix |
gateway.api_prefix |
str | /api/v1 |
— | Gateway API path prefix |
gateway.wsPath |
gateway.ws_path |
str | /ws |
— | Gateway WebSocket path |
gateway.wsHeartbeatSeconds |
gateway.ws_heartbeat_seconds |
float | 30.0 |
— | Server-side WebSocket heartbeat interval in seconds (0 = off): the server pings clients and expects a pong, so a connection cannot silently die during a long turn and cause the reply to miss the CLI |
gateway.sessionPolicy.mode |
gateway.session_policy.mode |
Literal | idle |
daily/idle/both/none | Gateway session reset policy |
gateway.sessionPolicy.dailyResetHour |
gateway.session_policy.daily_reset_hour |
int | 4 |
— | Hour of day to reset sessions (0-23) |
gateway.sessionPolicy.idleTimeoutMinutes |
gateway.session_policy.idle_timeout_minutes |
int | 1440 |
— | Session idle timeout (minutes) |
gateway.auth.mode |
gateway.auth.mode |
Literal | allowlist |
open/allowlist/pairing | Gateway authentication mode |
gateway.auth.allowedUsers |
gateway.auth.allowed_users |
list | [] |
— | Allowlist of users permitted to access the gateway |
gateway.auth.adminUsers |
gateway.auth.admin_users |
list | [] |
— | Gateway administrator users |
gateway.auth.apiTokens |
gateway.auth.api_tokens |
list | [] |
— | Gateway API access tokens |
gateway.auth.adminTokens |
gateway.auth.admin_tokens |
list | [] |
— | Tokens required for high-risk admin endpoints (skills import/install/delete and knowledge upload/delete); falls back to api_tokens when empty |
gateway.auth.allowedOrigins |
gateway.auth.allowed_origins |
list | [] |
— | Allowlisted browser Origins (cross-site escape hatch). Empty does NOT disable CSRF protection: the WS handshake, POST /message and the admin endpoints reject explicit cross-site browser requests by default. Use this to additionally permit specific Origins (webviews, a dev frontend port); non-browser clients are always unaffected |
gateway.auth.tokenHeader |
gateway.auth.token_header |
str | X-Echo-Agent-Token |
— | Request header carrying the API token |
gateway.auth.pairingTtlSeconds |
gateway.auth.pairing_ttl_seconds |
int | 300 |
— | Pairing-mode token time-to-live (seconds) |
gateway.auth.allowedHosts |
gateway.auth.allowed_hosts |
list | [] |
— | Accepted Host header values. DNS rebinding makes Origin and Host both attacker-controlled strings — comparing them is useless. The only authoritative signal is whether the Host matches a name this gateway was intended to be reached on: loopback addresses when bound to loopback, the proxy domain when behind one. Empty defers to the bind-address default (loopback addresses when bound to loopback; when bound to 0.0.0.0/:: it warns at startup and the admin endpoints — sessions, config, memory writes, tasks, cron, knowledge — reject every browser request). Entries are compared normalized (case-insensitive, port stripped, IPv6 brackets folded); a wildcard such as 0.0.0.0 or :: is not a usable entry and is dropped. Set explicitly for reverse-proxy deployments |
gateway.knownPlatforms |
gateway.known_platforms |
list | ['cli', 'ws', 'api', 'cron', 'dingtalk', 'discord', 'email', 'feishu', 'matrix', 'qqbot', 'slack', 'telegram', 'webhook', 'wechat', 'wecom', 'weixin', 'whatsapp'] |
— | Platform values the gateway recognises. A client-reported platform is interpolated into the channel name gateway:{platform}, and channel names carry capability decisions elsewhere (e.g. channels.stream_optimistic_channels asserts a channel can redraw in place), so a value outside this list is folded to "ws" rather than rejected — that keeps existing third-party callers working. Empty list disables folding (legacy fully self-reported behaviour) |
gateway.platforms |
gateway.platforms |
dict | {} |
— | Per-platform gateway configurations keyed by platform |
gateway.platforms{}.rateLimitRpm |
gateway.platforms{}.rate_limit_rpm |
int | 30 |
— | Per-minute request cap for this platform |
gateway.mediaCacheDir |
gateway.media_cache_dir |
str | data/media_cache |
— | Gateway media cache directory |
gateway.mediaCacheMaxMb |
gateway.media_cache_max_mb |
int | 500 |
— | Media cache size limit (MB) |
gateway.mediaMaxFileMb |
gateway.media_max_file_mb |
int | 25 |
— | Per-file download ceiling (MB). Enforced on both Content-Length and the real byte stream; an over-size download is aborted and its partial file removed |
gateway.mediaMaxUrlsPerMessage |
gateway.media_max_urls_per_message |
int | 10 |
— | Maximum media URLs accepted on one message; extras are rejected |
gateway.mediaDownloadConcurrency |
gateway.media_download_concurrency |
int | 4 |
— | Maximum parallel media downloads, bounding outbound connections and memory |
gateway.mediaAllowPrivateAddresses |
gateway.media_allow_private_addresses |
bool | false |
— | Allow media downloads to reach private/loopback addresses (SSRF risk). Same policy as tools.web.allowPrivateAddresses but a separate switch, since an internal CDN may legitimately need it. Blocked by default; the http/https scheme restriction applies either way |
gateway.hooksDir |
gateway.hooks_dir |
str | "" |
— | Gateway hook scripts directory |
planning¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
planning.enabled |
planning.enabled |
bool | true |
— | Enable task planning |
planning.defaultStrategy |
planning.default_strategy |
str | auto |
— | Default planning strategy |
planning.maxBranches |
planning.max_branches |
int | 3 |
— | Number of candidate branches the Tree-of-Thought strategy explores |
planning.reflectionEnabled |
planning.reflection_enabled |
bool | true |
— | Enable planning reflection |
a2A¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
a2A.enabled |
a2a.enabled |
bool | true |
— | Enable the A2A (agent-to-agent) interface |
a2A.agentName |
a2a.agent_name |
str | echo-agent |
— | Agent name exposed over A2A |
a2A.agentDescription |
a2a.agent_description |
str | A modular AI agent framework |
— | Agent description exposed over A2A |
a2A.capabilities |
a2a.capabilities |
list | ['chat', 'tool_use'] |
— | Capability tags advertised in the A2A AgentCard |
a2A.taskTtlSeconds |
a2a.task_ttl_seconds |
float | 3600.0 |
— | How long terminal A2A tasks are retained (seconds) before reclamation |
a2A.maxTasks |
a2a.max_tasks |
int | 1000 |
— | Capacity of the A2A task store; oldest terminal tasks are evicted past it |
a2A.activeTaskTtlSeconds |
a2a.active_task_ttl_seconds |
float | 86400.0 |
— | Backstop retention for non-terminal A2A tasks (seconds); a leak guard, not a task deadline |
evaluation¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
evaluation.datasetPath |
evaluation.dataset_path |
str | data/eval |
— | Evaluation dataset path |
evaluation.timeoutPerCase |
evaluation.timeout_per_case |
int | 120 |
— | Timeout per evaluation case (seconds) |
bus¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
bus.maxQueueSize |
bus.max_queue_size |
int | 1000 |
— | Event bus queue capacity |
bus.maxConcurrency |
bus.max_concurrency |
int | 50 |
— | Event bus max concurrent handlers |
rateLimit¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
rateLimit.sessionRpm |
rate_limit.session_rpm |
int | 20 |
— | Per-session requests-per-minute cap |
rateLimit.sessionBurst |
rate_limit.session_burst |
int | 5 |
— | Per-session burst allowance |
circuitBreaker¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
circuitBreaker.failureThreshold |
circuit_breaker.failure_threshold |
int | 5 |
— | Consecutive failures that trip the breaker |
circuitBreaker.recoverySeconds |
circuit_breaker.recovery_seconds |
float | 60.0 |
— | Wait before attempting recovery after tripping (seconds) |
circuitBreaker.halfOpenMax |
circuit_breaker.half_open_max |
int | 2 |
— | Probe requests allowed in half-open state |
plugins¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
plugins.enabled |
plugins.enabled |
bool | true |
— | Enable the plugin system |
plugins.allow |
plugins.allow |
list | [] |
— | Allowlist of plugins permitted to load |
plugins.deny |
plugins.deny |
list | [] |
— | Blocklist of plugins forbidden from loading |
plugins.extraDirs |
plugins.extra_dirs |
list | [] |
— | Additional plugin search directories |
plugins.config |
plugins.config |
dict | {} |
— | Per-plugin custom configuration keyed by plugin |
plugins.trustedPlugins |
plugins.trusted_plugins |
list | [] |
— | Trusted plugins exempt from permission checks |
plugins.permissionMode |
plugins.permission_mode |
Literal | compat |
compat/strict | Plugin permission mode |
ui¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
ui.locale |
ui.locale |
Literal | auto |
en/zh/auto | Interface language |
agent¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
agent.maxIterations |
agent.max_iterations |
int | 40 |
— | Maximum iterations of the agent main loop |
agent.maxOutputContinuations |
agent.max_output_continuations |
int | 3 |
— | Maximum automatic continuations after a model output-length stop |
agent.continuationOverlapChars |
agent.continuation_overlap_chars |
int | 2000 |
— | Character window used to remove overlap between continuation chunks |
agent.toolConcurrency.enabled |
agent.tool_concurrency.enabled |
bool | true |
— | Run read-only, non-overlapping tool calls concurrently |
agent.toolConcurrency.maxConcurrent |
agent.tool_concurrency.max_concurrent |
int | 4 |
— | Max concurrent tools (1 disables concurrency = serial) |
agent.heartbeat.enabled |
agent.heartbeat.enabled |
bool | true |
— | Emit periodic progress heartbeat during long-running turns |
agent.heartbeat.firstDelaySec |
agent.heartbeat.first_delay_sec |
int | 30 |
— | Silence threshold (sec) before the first heartbeat |
agent.heartbeat.minIntervalSec |
agent.heartbeat.min_interval_sec |
int | 60 |
— | Minimum interval (sec) between visible feedback |
agent.heartbeat.verbosity |
agent.heartbeat.verbosity |
Literal | key_milestones |
key_milestones/every_tool/silent | Heartbeat verbosity tier |
agent.heartbeat.template |
agent.heartbeat.template |
str | ⏳ {activity}(已用时 {elapsed}) |
— | Heartbeat text template with {elapsed}/{activity} |
agent.inspection.enabled |
agent.inspection.enabled |
bool | false |
— | Enable proactive inspection (default off; declare items in INSPECT.md) |
agent.inspection.tickIntervalSec |
agent.inspection.tick_interval_sec |
int | 300 |
— | Inspection tick interval (seconds) for scanning due items |
agent.inspection.inspectFile |
agent.inspection.inspect_file |
str | INSPECT.md |
— | Inspection checklist filename (workspace-relative) |
agent.inspection.maxItemsPerTick |
agent.inspection.max_items_per_tick |
int | 5 |
— | Max due items dispatched to the agent per tick |
agent.inspection.deliverChannel |
agent.inspection.deliver_channel |
str | "" |
— | Inspection alert delivery channel (empty falls back to registering session) |
agent.inspection.deliverChatId |
agent.inspection.deliver_chat_id |
str | "" |
— | Inspection alert delivery chat id (empty falls back to registering session) |
evolution¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
evolution.enabled |
evolution.enabled |
bool | false |
— | Enable the self-evolving skill engine |
evolution.triggerMode |
evolution.trigger_mode |
Literal | manual |
manual/threshold/scheduled | Evolution trigger mode |
evolution.thresholdTrajectories |
evolution.threshold_trajectories |
int | 50 |
— | Trajectory count triggering threshold mode |
evolution.cronExpression |
evolution.cron_expression |
str | 0 4 * * * |
— | Cron expression for scheduled mode |
evolution.maxCandidatesPerRun |
evolution.max_candidates_per_run |
int | 3 |
— | Maximum candidates generated per run |
evolution.maxTrajectoriesPerRun |
evolution.max_trajectories_per_run |
int | 200 |
— | Maximum trajectories processed per run |
evolution.evalDatasetPath |
evolution.eval_dataset_path |
str | data/eval/baseline.yaml |
— | Evolution evaluation baseline dataset path |
evolution.regressionThreshold |
evolution.regression_threshold |
float | 0.05 |
— | Score-drop threshold that flags a regression |
evolution.requireStrictImprovement |
evolution.require_strict_improvement |
bool | true |
— | Require strict improvement before promotion |
evolution.minEvalCases |
evolution.min_eval_cases |
int | 3 |
— | Minimum eval cases required to promote; fewer is inconclusive |
evolution.recordTrajectories |
evolution.record_trajectories |
bool | true |
— | Record execution trajectories for evolution |
evolution.trajectoryRetentionDays |
evolution.trajectory_retention_days |
int | 30 |
— | Trajectory retention period (days) |
evolution.evolverModel |
evolution.evolver_model |
str | "" |
— | Model used to perform evolution |
evolution.skillSizeLimitBytes |
evolution.skill_size_limit_bytes |
int | 50000 |
— | Size limit for evolved skills (bytes) |
evolution.redactArgs |
evolution.redact_args |
bool | true |
— | Redact tool arguments when recording trajectories |
evolution.evalParallel |
evolution.eval_parallel |
int | 2 |
— | Evolution evaluation parallelism |
evolution.evalTimeoutSeconds |
evolution.eval_timeout_seconds |
int | 60 |
— | Evolution evaluation per-case timeout (seconds) |
evolution.cooldownSecondsAfterPromote |
evolution.cooldown_seconds_after_promote |
int | 86400 |
— | Cooldown after a promotion before evolving again (seconds) |
evolution.autoPromote |
evolution.auto_promote |
bool | true |
— | Auto-promote candidates that pass evaluation |
evolution.candidateReviewRequired |
evolution.candidate_review_required |
bool | false |
— | Require human review of candidates before promotion |
cost¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
cost.enabled |
cost.enabled |
bool | false |
— | Enable cost tracking and budget control |
cost.dailyBudgetUsd |
cost.daily_budget_usd |
float | 0.0 |
— | Daily cost budget in USD (0 = unlimited) |
cost.softThresholdRatio |
cost.soft_threshold_ratio |
float | 0.8 |
— | Budget ratio at which a soft warning is raised |
cost.pricingOverrides |
cost.pricing_overrides |
dict | {} |
— | Model pricing override table |
workspace¶
| Field | snake | type | default | choices | description |
|---|---|---|---|---|---|
workspace |
workspace |
str | ~/.echo-agent |
— | Agent workspace root directory |