Keep local values in env files
On your machine, a pack's settings live in env files at the pack's root:
your app's own .env, then .env.local. The secrets you store with
kindgi secrets set live in Kindgi's own file, .kindgi/secrets.env, which
your app doesn't load. kindgi dev reads them in that order, and a name in
more than one takes its value from the last: .kindgi/secrets.env wins.
ORDERS_APP_URL=http://localhost:3000kindgi dev says which files it read when it starts:
✓ env files: .env, .env.local — 4 name(s) for the packWhat reads them
Section titled “What reads them”Under kindgi dev, the env files are two things at once.
The pack service's environment. Your tools' code runs in the pack service, and the names in your app's env files are in its environment:
import { defineTool } from '@kindgi/sdk/define';import type { ToolId } from '@kindgi/sdk/types';import { z } from 'zod';
const defined = defineTool({ id: 'my-pack.order-link' as ToolId, description: 'Returns the link to an order in the back office.', version: '0.1.0', input: z.object({ orderId: z.string() }), output: z.object({ url: z.string() }), effects: [], mutating: false, handler: async ({ orderId }) => { const base = process.env.ORDERS_APP_URL; if (base === undefined) throw new Error('ORDERS_APP_URL is not set'); return { url: `${base}/orders/${orderId}` }; },});
if (defined.kind === 'err') throw new Error(defined.error.message);export default defined.value;import os
from pydantic import BaseModel, Field
from kindgi import tool
class OrderRef(BaseModel): order_id: str = Field(alias="orderId")
class Link(BaseModel): url: str
@tool(id="my-pack.order-link", mutating=False)def order_link(ref: OrderRef) -> Link: """Returns the link to an order in the back office.""" return Link(url=f"{os.environ['ORDERS_APP_URL']}/orders/{ref.order_id}")Call it from my-pack.link, a one-step flow:
kindgi runs start --flow=my-pack.link --input='{"orderId":"ord_1001"}' "flowId": "my-pack.link", "flowVersion": "0.1.0", "status": "completed", … "output": { "url": "http://localhost:3000/orders/ord_1001" },Nothing else reaches it: a variable exported in the shell you start
kindgi dev from isn't in the pack service's environment unless an env file
refers to it (below), and a KINDGI_* name never
is, from anywhere. Neither is a secret: no name from .kindgi/secrets.env,
and no model provider's key, whichever file holds it. Save an env file and
the pack service restarts with the new values.
Refer to another value
Section titled “Refer to another value”A value can use another name's, as ${NAME} or $NAME, with
${NAME:-fallback} for when it's unset or empty:
ORDERS_HOST=localhost:3000ORDERS_APP_URL=http://${ORDERS_HOST}ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}- A name the env files define takes the files' value, wherever it's defined.
- Any other name takes the value from the shell you start
kindgi devfrom. - A key that refers to itself, like
ANTHROPIC_API_KEYabove, takes the shell's value too. This keeps the key out of the file, as a docker-compose.envdoes.
If the shell doesn't have it, the value is empty. kindgi dev warns as it
starts, and so does kindgi env list:
⚠ 1 warning(s): .env: ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} takes the environment's ANTHROPIC_API_KEY, and the environment doesn't have it — expanded to ""With the key exported in the shell, kindgi env list shows it set:
ANTHROPIC_API_KEY sk****************al [.env]The local secrets store. Tools' declared secrets, HTTP tools'
credentials, model providers' keys and MCP endpoints' credentials resolve in
an environment; under kindgi dev it's local, and local is these files,
Kindgi's own last. kindgi secrets set … --env=local writes to
.kindgi/secrets.env (Store a secret): owner-only,
under .kindgi/ (which kindgi init keeps out of git), and in no file your
app loads. A framework like Next.js or Vite loads .env.local into every
route of your app; your app has no use for a model key.
For a value your app reads too, such as a webhook signing secret, add
--app: it writes your app's own env file instead (the last of the files,
.env.local by default), where both sides read it.
kindgi secrets set ACME_WEBHOOK_SECRET --env=local --scope=tenant --appA key your app's env files already hold
Section titled “A key your app's env files already hold”A model provider's key can sit in .env or .env.local already: put there
before Kindgi kept its own file, or because your app calls the provider
itself. Kindgi still uses it from there. kindgi dev says so once as it
starts, naming the key and the file, never the value.
To give Kindgi its own copy:
kindgi secrets copyIt copies the keys Kindgi's providers use from your app's env files into
.kindgi/secrets.env, a line exactly as written, and merges: a name Kindgi's
file already holds keeps Kindgi's value. Name keys to copy others
(kindgi secrets copy ANTHROPIC_API_KEY).
It never edits or deletes anything in your app's files. Whether the key
stays there is your call: it's your app's file, and your app may use the key
itself. A provider's SDK reads ANTHROPIC_API_KEY or OPENAI_API_KEY from
the environment without your code naming it. copy says, for each key:
- that it's still in your app's file, and removing it is up to you;
- if your app uses the key itself, to keep it there;
- if the file is tracked by git, that the key is in your repository's history: rotate it with its provider.
When your app calls the provider too, give each its own key. Keep your
app's key in its env file, and give Kindgi its own key under the same name in
.kindgi/secrets.env, which takes precedence. Each spends on its own key.
One shared key in your app's file works as well, but it stays readable by
your app and by a coding agent working in it, so keep your agent out of
those files (Your coding agent).
See what's set
Section titled “See what's set”kindgi env list --env=local Environment: local Env file: .env Env file: .env.local Reveal: NO (redacted; --reveal to show)
4 key(s): HTTPBIN_TOKEN de**********23 [.env.local] INVENTORY_TOKEN in**************56 [.env.local] ORDERS_APP_URL ht*****************00 [.env] RECEIPT_SIGNING_KEY a6************************************************************83 [.env.local]Values are redacted; --reveal prints them to a terminal, never to a pipe or
a file. kindgi env list shows your app's env files; the names in Kindgi's
own file are listed by kindgi secrets list --env=local --scope=tenant, never
their values.
Read other files
Section titled “Read other files”To read more of your app's files, list them in the pack's config, lowest
precedence first. Kindgi's own .kindgi/secrets.env is always read last:
// in kindgi.config.tsdev: { envFiles: ['.env', '.env.development', '.env.local'] },# in pyproject.toml[tool.kindgi.dev]envFiles = [".env", ".env.development", ".env.local"]kindgi dev reads the list when it starts: restart it after a change.
✓ env files: .env, .env.development, .env.local — 6 name(s) for the packKeep them out of git
Section titled “Keep them out of git”The files hold secrets. kindgi init adds .kindgi/ and .env.local to
your .gitignore. Make sure it covers every env file:
.kindgi/.env.env.*!.env.exampleThe Python template's .gitignore has these lines. A TypeScript pack made
with kindgi init 0.1.0 has no .gitignore (its lines landed in
.npmignore): add one.