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:
- 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 |
|---|---|---|---|---|
| Host-injected env | new Agent({ env }) | No | One agent instance | shared API keys, embedded host values, city global env |
| Project env file | <agent>/.env | User-managed file | One agent | project-local runtime overlay values |
| Runtime metadata vars | process memory (DC_CITY_*, DC_AGENT_*, DC_SESSION_ID) | No | single request / process | runtime endpoint, agent identity, session metadata |
| Bot credentials | ~/.downcity/downcity.db channel_accounts | Yes (encrypted fields) | Reusable by binding | Telegram/Feishu/QQ bot secrets |
| Console env records | ~/.downcity/downcity.db env_entries | Yes (encrypted) | Console-managed storage | global/shared env registry |
Key points:
@downcity/agentdoes not auto-read hostprocess.env.new Agent({ env })is the host injection entrypoint.- When the host is
studio, Console-global env is read fromdowncity.dband passed explicitly intoAgent. downcity.jsonstores bindings (model.primary,channel.channelAccountId), not plaintext credentials.
2. Data Flow (diagram)
3. Load Priority (who wins)
3.1 Agent env merge
- Host-injected
new Agent({ env })loads first. <agent>/.envoverlays it.
So the agent-level merge order is: project .env > host-injected env.
3.2 Runtime metadata injection
- Runtime helpers start from the resolved agent env snapshot.
- Request-scoped metadata such as
DC_SESSION_ID,DC_CITY_*, andDC_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
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
- Agent runtime env assembly (
new Agent({ env })+.env). - Local plugin command context assembly.
- Runtime helpers that append request metadata on top of the resolved agent env.
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_entriesfor Console-side env management. - Bot account CRUD (UI): writes
channel_accounts. - Model CRUD (CLI/UI): writes
model_providers,models. - Plugin config (
studio plugin action ...,studio asr ..., orstudio tts ...): writes projectdowncity.json. - Channel configure action: writes
downcity.json(enabled,channelAccountId). - User manual
.envedit: affects that agent only.
Note:
env_entriesis Console-managed storage.- 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:
- The channel is bound to an existing
channelAccountId. - The host injected shared keys into
Agent. - The new project
.envalready contains fallback keys.
7. Recommended Setup
- Initialize console-global data once:
studio init
studio model create
studio plugin action asr configure --payload '{"modelId": "SenseVoiceSmall"}'- Manage shared env in Console UI
Global / Env, or pass them explicitly throughnew Agent({ env }). - 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 project-local runtime overlay only. - Use
new Agent({ env })only for explicit host-side overrides. - Validate channel status via
chat statusafter changing bindings. - If values look inherited, check
channelAccountId, host-injectedenv, and 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 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:
DOWNCITY_PUBLIC_URL: full external URL, for examplehttps://console.example.comDOWNCITY_PUBLIC_HOST: external host only; the CLI expands it tohttp://<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:
DOWNCITY_PUBLIC_URLDOWNCITY_PUBLIC_HOST- the current bind host (when it is already directly reachable)
- a detected public IPv4 from local network interfaces