Secrets & environments
Secrets and environment variables are the entire credential story: there are no per-service connect flows to manage. Declare a name, fetch it in code, and the engine worries about where the value lives. When one workflow needs different values for staging and production, group those values into an environment and pick one per run.
Declare, then get
export const meta = {
slug: "morning-digest",
title: "Morning Digest",
triggers: [{ kind: "cron", expr: "0 9 * * 1-5" }],
permissions: { secrets: [{ name: "GITHUB_TOKEN" }] },
};
const token = await secrets.get("GITHUB_TOKEN");The declaration is an allowlist: secrets.get() on an undeclared name fails, and so does a run whose declared secret is missing, with an error that says exactly what to set. Fail-closed, both ways. A secret ref is just { name }; there is nothing else to wire.
Where secrets resolve from
boardwalk dev: your project's.envfile (override with--env). Nothing leaves your machine.- Boardwalk:the org's secrets vault, managed in the dashboard (Settings → Secrets) or via the API and CLI. Values are encrypted at rest and released to a run only for the names its manifest declares.
- Self-hosted:the server's environment. Your hardware, your values.
The program code is identical in all three.
The redaction guarantee
Secret values live only in your deterministic code; they are redacted from everything the model sees: prompts, tool arguments, tool results, the transcript. Fetch with the token, then hand the model the data, never the credential. Because the model can't see a secret, prompt injection can't exfiltrate one.
Secrets in env vars
env: {
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}",
},When a subprocess needs a credential (a CLI you shell out to, for instance), meta.env may reference a declared secret. The reference must be the whole value, exactly as above; partial interpolation inside a longer string is not supported. meta.env is declared inline in the workflow; for values that differ between staging and production, set a variable on an environment instead.
Environments
An environment is a named bag of org configuration, such as production or staging. Each holds its own secrets (in the vault) and its own non-secret variables. A run executes in exactly one environment and resolves both from it. Below every named environment sits the organization base: the values that apply when a run targets no environment, and the fallback for any name an environment doesn't override.
The workflow does not name an environment. Nothing in meta changes; the same deployed workflow runs against production or staging depending only on which environment you pick when you triggerit. Create and manage environments in the dashboard (Settings → Environments) or via the API.
Non-secret variables
A variable is non-secret configuration (a base URL, a feature flag, an account id) that the platform injects into the run as a process environment variable. Read it the ordinary way:
const region = process.env.AWS_REGION_NAME;
const apiBase = process.env.API_BASE_URL;Unlike secrets, variables need no manifest declaration and no allowlist: they are not sensitive, so the value is visible in logs and is not redacted from the model. Set them per environment (or on the org base) right next to that environment's secrets. Your program owns process.envoutright: any name is yours to set, and the platform's own credentials never appear there, so nothing can collide.
Per-environment overrides
The same name can resolve differently per environment. A DEPLOY_KEY secret or an API_BASE_URL variable defined on productionoverrides the organization base of the same name; a name an environment doesn't define falls through to the base. So a run in productionsees production's value, a run in stagingsees staging's, and a run with no environment sees the base, all from the same secrets.get("DEPLOY_KEY") or process.env.API_BASE_URL in your code.
The allowlist still applies regardless of environment: a secret a workflow never declares in permissions.secrets is never resolvable, in any environment.
Selecting an environment
Pick the environment when you start a run, by name. A manual or webhook trigger takes an environment alongside the input; a schedule carries the environment every fire runs in. Omit it and the run uses the organization base.
POST /v1/orgs/:slug/workflows/:id/runs
{
"environment": "production",
"input": { "ref": "refs/heads/main" }
}A child started with workflows.call() or workflows.run() inherits its parent's environment, so a call tree resolves the same secrets and variables end to end. Triggering from boardwalk dev or the CLI runs in the organization base; choose a specific environment from the dashboard, the API, or a schedule.