Downcity
Configuration

Environment Variable Strategy (Host Env, Project .env, Runtime Metadata)

Explain runtime env precedence for host-injected env, project .env, and runtime metadata

Canonical schema doc: Env and downcity.db Database Design

Environment Variable Strategy (Host Env, Project .env, Runtime Metadata)

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
Host-injected envnew Agent({ env })NoOne agent instanceshared API keys, embedded host values, city global env
Project env file<agent>/.envUser-managed fileOne agentproject-local runtime overlay values
Runtime metadata varsprocess memory (DC_CITY_*, DC_AGENT_*, DC_SESSION_ID)Nosingle request / processruntime endpoint, agent identity, session metadata
Bot credentials~/.downcity/downcity.db channel_accountsYes (encrypted fields)Reusable by bindingTelegram/Feishu/QQ bot secrets
Console env records~/.downcity/downcity.db env_entriesYes (encrypted)Console-managed storageglobal/shared env registry

Key points:

  1. @downcity/agent does not auto-read host process.env.
  2. new Agent({ env }) is the host injection entrypoint.
  3. When the host is studio, Console-global env is read from downcity.db and passed explicitly into Agent.
  4. downcity.json stores bindings (model.primary, channel.channelAccountId), not plaintext credentials.

2. Data Flow (diagram)

3. Load Priority (who wins)

3.1 Agent env merge

  1. Host-injected new Agent({ env }) loads first.
  2. <agent>/.env overlays it.

So the agent-level merge order is: project .env > host-injected env.

3.2 Runtime metadata injection

  1. Runtime helpers start from the resolved agent env snapshot.
  2. Request-scoped metadata such as DC_SESSION_ID, DC_CITY_*, and DC_AGENT_* is injected last.

So key precedence for runtime metadata is: DC_CITY_* / DC_AGENT_* / DC_SESSION_ID > project .env > host-injected env.

3.3 Channel credential resolution

  1. downcity.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. Agent runtime env assembly (new Agent({ env }) + .env).
  2. Local plugin command context assembly.
  3. Runtime helpers that append request metadata on top of the resolved agent env.

4.2 Does not read project .env

  1. Global model/provider pool (model_providers, models) from downcity.db.
  2. Plugin config from project downcity.json (plugins.*, including plugin-owned dependency 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. Console UI Global / Env writes env_entries for Console-side env management.
  2. Bot account CRUD (UI): writes channel_accounts.
  3. Model CRUD (CLI/UI): writes model_providers, models.
  4. Plugin config (studio plugin action ..., studio asr ..., or studio tts ...): writes project downcity.json.
  5. Channel configure action: writes downcity.json (enabled, channelAccountId).
  6. User manual .env edit: affects that agent only.

Note:

  1. env_entries is Console-managed storage.
  2. If a host wants Console-managed env to participate in Agent runtime, it should pass those values through new Agent({ env }).

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 host injected shared keys into Agent.
  3. The new project .env already contains fallback keys.
  1. Initialize console-global data once:
studio init
studio model create
studio plugin action asr configure --payload '{"modelId": "SenseVoiceSmall"}'
  1. Manage shared env in Console UI Global / Env, or pass them explicitly through new Agent({ env }).
  2. Create channel accounts in Console UI Global / Channel Accounts.
  3. In each agent downcity.json, bind channel to channelAccountId.
  4. Keep agent-private runtime keys in <agent>/.env only when needed.

8. Best-Practice Checklist

  1. Keep persisted secrets in downcity.db encrypted tables.
  2. Keep downcity.json as binding/config file, not secret storage.
  3. Use <agent>/.env as project-local runtime overlay only.
  4. Use new Agent({ env }) only for explicit host-side overrides.
  5. Validate channel status via chat status after changing bindings.
  6. If values look inherited, check channelAccountId, host-injected env, and project .env.

9. Homepage Agent Marketplace

The homepage community marketplace now uses PostgreSQL-compatible storage and works well with Supabase.

Required environment variables:

  1. DATABASE_URL: point this to your Supabase Postgres connection string so repository submissions and review states can be stored centrally.

Behavior:

  1. Public submissions are inserted with review_status = pending.
  2. Managers approve or reject records manually in Supabase.
  3. Only approved records are shown on the public marketplace page.

10. Console public address

When you use studio start -p or studio console start -p, the CLI tries to print Public URL in startup output. If you use studio public on, future plain studio start and studio console start runs also reuse the saved public bind mode.

These values are normally injected by studio start or the deployment environment. They are not endpoint parameters that users enter for contact link:

  1. DOWNCITY_PUBLIC_URL: full external URL, for example https://console.example.com
  2. DOWNCITY_PUBLIC_HOST: external host only; the CLI expands it to http://<host>:5315

studio 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:

  1. DOWNCITY_PUBLIC_URL
  2. DOWNCITY_PUBLIC_HOST
  3. the current bind host (when it is already directly reachable)
  4. a detected public IPv4 from local network interfaces