Skip to content

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.

.env
ORDERS_APP_URL=http://localhost:3000

kindgi dev says which files it read when it starts:

✓ env files: .env, .env.local — 4 name(s) for the pack

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:

tools/order-link/index.ts
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;

Call it from my-pack.link, a one-step flow:

Terminal window
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.

A value can use another name's, as ${NAME} or $NAME, with ${NAME:-fallback} for when it's unset or empty:

.env
ORDERS_HOST=localhost:3000
ORDERS_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 dev from.
  • A key that refers to itself, like ANTHROPIC_API_KEY above, takes the shell's value too. This keeps the key out of the file, as a docker-compose .env does.

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.

Terminal window
kindgi secrets set ACME_WEBHOOK_SECRET --env=local --scope=tenant --app

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:

Terminal window
kindgi secrets copy

It 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).

Terminal window
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.

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.ts
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 pack

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.example

The 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.