Downcity
Configuration

Environment Variable Strategy (Console Shared vs Agent Private)

Clear scope and runtime merge logic for global_env, agent_env, channel_accounts, and <agent>/.env

Canonical schema doc: Env and ship.db Database Design

Environment Variable Strategy (Console Shared vs Agent Private)

This page answers 4 practical questions:

  1. How many types of env-related variables exist?
  2. Where is each type stored?
  3. How are they loaded and merged at startup?
  4. What reads project .env, and what does not?

1. Scope Matrix

LayerSourcePersistedScopeTypical content
Console shared env~/.ship/ship.db global_envYes (encrypted)All agentsshared API keys and shared placeholders
Agent private env (DB)~/.ship/ship.db agent_envYes (encrypted)One agent (agent_id)project-level secret keys
Agent private env (file)<agent>/.envUser-managed fileOne agentruntime overlay values
Bot credentials~/.ship/ship.db channel_accountsYes (encrypted fields)Reusable by bindingTelegram/Feishu/QQ bot secrets
Runtime context varsprocess memory (DC_CTX_*)Nosingle requestchannel/chat/user context

Key points:

  1. All persisted env-like secrets are in ship.db encrypted tables.
  2. <agent>/.env is runtime-only overlay for that agent.
  3. ship.json stores bindings (model.primary, channel.channelAccountId), not plaintext credentials.

2. Data Flow (diagram)

3. Load Priority (who wins)

3.1 Runtime env merge

  1. agent_env (DB) loads first.
  2. <agent>/.env overlays it.
  3. .env wins on key conflicts.

3.2 Channel credential resolution

  1. ship.json channel config only binds channelAccountId.
  2. Runtime resolves real credentials from channel_accounts.
  3. Missing binding or missing required secrets => config_missing.

4. What Reads .env and What Does Not

4.1 Reads project .env

  1. Project-layer ${ENV_KEY} resolution.
  2. Agent runtime process env injection (agent_env + .env).
  3. Optional fallback paths in some service auth helpers.

4.2 Does not read project .env

  1. Global model/provider pool (model_providers, models) from ship.db.
  2. Extension global config from console_secure_settings.extensions_config.
  3. Bot account credential source of truth (channel_accounts).
  4. Runtime context vars (DC_CTX_*) are generated at request time.

5. Save Paths

  1. Bot account CRUD (UI): writes channel_accounts.
  2. Model CRUD (CLI/UI): writes model_providers, models.
  3. Extension config (city console config set extensions.*): writes extensions_config.
  4. Channel configure action: writes ship.json (enabled, channelAccountId).
  5. User manual .env edit: affects runtime only for that agent.

6. FAQ and Troubleshooting

6.1 Why does a new agent show existing channel credentials?

Most common reasons:

  1. The channel is bound to an existing channelAccountId.
  2. The new project .env already contains fallback keys.
  1. Initialize console-global data once:
city console init
city console model create
city console config set extensions.voice.enabled true
  1. Create channel accounts in Console UI Global / Channel Accounts.
  2. In each agent ship.json, bind channel to channelAccountId.
  3. Keep agent-private runtime keys in <agent>/.env only when needed.

8. Best-Practice Checklist

  1. Keep persisted secrets in ship.db encrypted tables.
  2. Keep ship.json as binding/config file, not secret storage.
  3. Use <agent>/.env as agent-local runtime overlay only.
  4. Validate channel status via chat status after changing bindings.
  5. If values look inherited, check both channelAccountId and project .env.

Table of Contents