Environment Variable Strategy (Console Shared vs Agent Private)
Clear scope and agent-env merge logic for env_entries, channel_accounts, and <agent>/.env
Canonical schema doc: Env and downcity.db Database Design
Environment Variable Strategy (Console Shared vs Agent Private)
This page answers 4 practical questions:
- How many types of env-related variables exist?
- Where is each type stored?
- How are they loaded and merged at startup?
- What reads project
.env, and what does not?
1. Scope Matrix
| Layer | Source | Persisted | Scope | Typical content |
|---|---|---|---|---|
| Console shared env | ~/.downcity/downcity.db env_entries (scope=global) | Yes (encrypted) | All agents | shared API keys and shared placeholders |
| Agent private env (DB) | ~/.downcity/downcity.db env_entries (scope=agent, agent_id) | Yes (encrypted) | One agent (agent_id) | project-level secret keys |
| Agent private env (file) | <agent>/.env | User-managed file | One agent | runtime overlay values |
| Bot credentials | ~/.downcity/downcity.db channel_accounts | Yes (encrypted fields) | Reusable by binding | Telegram/Feishu/QQ bot secrets |
| Runtime context vars | process memory (DC_CTX_*) | No | single request | channel/chat/user context |
Key points:
- All persisted env-like secrets are in
downcity.dbencrypted tables. <agent>/.envis runtime-only overlay for that agent.downcity.jsonstores bindings (model.primary,channel.channelAccountId), not plaintext credentials.
2. Data Flow (diagram)
3. Load Priority (who wins)
3.1 Agent env merge
- Agent-scope DB env loads first.
<agent>/.envoverlays it..envwins on key conflicts.
3.2 shell session injection
- A shell subprocess starts from the host process
process.env. - Console shared env (
scope=global) overlays that base. - Current agent env (
agent-scope DB env + <agent>/.env) overlays shared env. - Request-scoped context vars such as
DC_CTX_*are injected last.
So key precedence inside shell sessions is: DC_CTX_* > agent env > global env > host process env.
3.3 Channel credential resolution
downcity.jsonchannel config only bindschannelAccountId.- Runtime resolves real credentials from
channel_accounts. - Missing binding or missing required secrets =>
config_missing.
4. What Reads .env and What Does Not
4.1 Reads project .env
- Project-layer
${ENV_KEY}resolution. - Agent process env injection (
agent env in env_entries + .env). shellsubprocess injection (global env + current agent env +DC_CTX_*).- Optional fallback paths in some service auth helpers.
4.2 Does not read project .env
- Global model/provider pool (
model_providers,models) fromdowncity.db. - Plugin config from project
downcity.json(plugins.*, including plugin-owned dependency config). - Bot account credential source of truth (
channel_accounts). - Runtime context vars (
DC_CTX_*) are generated at request time.
5. Save Paths
- Console UI
Global / Envwritesenv_entrieswithscope=globalorscope=agent. - Bot account CRUD (UI): writes
channel_accounts. - Model CRUD (CLI/UI): writes
model_providers,models. - Plugin config (
city plugin action ...,city asr ..., orcity tts ...): writes projectdowncity.json. - Channel configure action: writes
downcity.json(enabled,channelAccountId). - User manual
.envedit: affects that agent only.
6. FAQ and Troubleshooting
6.1 Why does a new agent show existing channel credentials?
Most common reasons:
- The channel is bound to an existing
channelAccountId. - The new project
.envalready contains fallback keys.
7. Recommended Setup
- Initialize console-global data once:
city init
city model create
city plugin action asr configure --payload '{"modelId": "SenseVoiceSmall"}'- Manage shared env in Console UI
Global / Env(writesenv_entries). - Create channel accounts in Console UI
Global / Channel Accounts. - In each agent
downcity.json, bind channel tochannelAccountId. - Keep agent-private runtime keys in
<agent>/.envonly when needed.
8. Best-Practice Checklist
- Keep persisted secrets in
downcity.dbencrypted tables. - Keep
downcity.jsonas binding/config file, not secret storage. - Use
<agent>/.envas agent-local runtime overlay only. - Validate channel status via
chat statusafter changing bindings. - If values look inherited, check both
channelAccountIdand project.env.
9. Homepage Agent Marketplace
The homepage community marketplace now uses PostgreSQL-compatible storage and works well with Supabase.
Required environment variables:
DATABASE_URL: point this to your Supabase Postgres connection string so repository submissions and review states can be stored centrally.
Behavior:
- Public submissions are inserted with
review_status = pending. - Managers approve or reject records manually in Supabase.
- Only
approvedrecords are shown on the public marketplace page.
10. Console public address
When you use city start -p or city console start -p, the CLI tries to print Public URL in startup output.
These values are normally injected by city start or the deployment environment. They are not endpoint parameters that users enter for contact link:
DOWNCITY_PUBLIC_URL: full external URL, for examplehttps://console.example.comDOWNCITY_PUBLIC_HOST: external host only; the CLI expands it tohttp://<host>:5315
city start automatically detects the public IP and stores it in global Console Env as DOWNCITY_PUBLIC_HOST. Agent contact link generation also uses this value to create transferable contact codes.
Priority:
DOWNCITY_PUBLIC_URLDOWNCITY_PUBLIC_HOST- the current bind host (when it is already directly reachable)
- a detected public IPv4 from local network interfaces