AI frameworks
Every agent framework draws the same line: orchestration in, credential management out. LangGraph, Mastra, CrewAI, Pydantic AI, both Agents SDKs — none of them wants to be where your provider keys live, and all of them hand the question back to you.
This page answers it twice: the short version, which is three commands and no code, and then what to add as the agent grows teeth. Start at the top. Nothing below the first section is required to have a working agent, and plenty of projects never need any of it.
Get running in five minutes
You need the CLI and an account:
npm install -g @seekrit/cli
seekrit login
None of the three steps below is framework-specific. If you already keep an app's config in seekrit, skip to step 2.
1. Put the keys in an environment
An environment is the thing that holds secrets — one per app, per stage, like
storefront/development. Everything in it travels together, so put the agent's
provider keys in the environment its process should read. If they're in a .env
file today, that's one command:
seekrit secrets import .env --app storefront --env development
Or set them one at a time, or paste them into the dashboard:
seekrit secrets set OPENAI_API_KEY sk-… --app storefront --env development
seekrit secrets set TAVILY_API_KEY tvly-… --app storefront --env development
No application yet? seekrit app create --name Storefront --slug storefront, or
create one in the dashboard and its environments come with it. The
quickstart walks through a first-time account (keys,
passphrase, first secret) in more detail.
2. Mint a token for the agent
A service token is the credential a machine uses to read that environment. It is bound to one environment when you create it:
seekrit token create --name agent-dev --app storefront --env development
# prints once: skt_XXXXXXXX_…
Copy it now — the value is shown once and cannot be retrieved later. In the dashboard the same thing is Organization → Service tokens → Mint token, or the no token badge on an environment column, which pre-fills the binding.
The token carries its own private key, so it decrypts on its own: no passphrase, no config file, nothing else to set up. See Service tokens.
3. Run the agent through the CLI
export SEEKRIT_TOKEN=skt_XXXXXXXX_…
seekrit run -- python agent.py # or: mastra dev, langgraph dev, pnpm dev…
seekrit run resolves the token's environment, decrypts locally, and injects
every value as an environment variable in the child process only — nothing on
disk, nothing in shell history, nothing for a coding agent to find in a file
later. Your framework picks the keys up exactly where it already looks:
OPENAI_API_KEY, ANTHROPIC_API_KEY, TAVILY_API_KEY, your database URL.
To see what the agent will get, without printing any values:
seekrit export --format dotenv | cut -d= -f1
That is the whole integration. No SDK, no import, no code change — you are
not integrating with seekrit so much as declining to write a .env file. Delete
the old one once the import worked: seekrit run still overlays a .env if one
is there, and a file that exists is a file an agent can still read.
For containers and CI, seekrit-run is the same behaviour as
a static binary with no Node dependency.
If it didn't work
| Symptom | Cause |
|---|---|
| The framework says no API key was found | The key isn't in the environment the token is bound to. seekrit export --format dotenv | cut -d= -f1 lists what the token actually resolves; seekrit whoami prints the org/app/env it is bound to |
| The command runs but reads an old value | Something above the managed layer is winning. Precedence is group < app-env < .env file < process env, so a stale .env or an exported variable overrides seekrit. seekrit run --explain names the source layer of every variable, without printing values |
seekrit run warns and the process starts anyway | Resolving is best-effort by design — a missing token, an unreachable API, or a decryption failure logs to stderr and still launches the command with whatever .env and process.env provide. seekrit export is the strict version: it errors instead |
| A value is right in the dashboard, empty in the process | The name has a typo, or the secret exists in a different environment of the same app. seekrit secrets list --app <app> --env <env> shows names per environment |
Which secrets does the agent get?
All of them, from the one environment its token is bound to — plus the slices of any group composed into that environment. There is no per-key selection step on the seekrit side, and nothing to attach per framework or per tool. The token's binding is the selection.
Two consequences worth knowing up front:
- A secret you add later just shows up. The next
seekrit runresolves the environment as it stands now. You don't re-mint or re-grant anything to give an agent a new key — put it in the environment and restart the process. - A token is a decryption credential for that whole environment. If it
shouldn't hold everything in
storefront/development, the fix is a narrower binding, not a narrower list of keys.
So the coarse control — the one the server enforces — is which environment:
| You want | Do this |
|---|---|
| The agent to read only some of an app's keys | Give the agent its own environment (seekrit env create --app storefront --name "Agent dev" --slug agent-dev) and bind its token there |
| Two agents with different keys | A token each, bound to different environments |
| Shared keys plus per-agent ones | Keep the shared ones in a group and compose it into each agent's environment |
| A key that works for one API and nowhere else | That's not a visibility question — see hold a placeholder |
| One tool to spend a key the others can't | Same — see scope a key to one tool |
The last two rows are the important distinction. Everything in this section is about what a token can read. The levels further down are about where a value is allowed to go once something can read it, which is a separate gate, lives in your own config, and is optional.
Where the seekrit token lives
One credential in, all the others out — so the remaining question is where it goes. That depends only on where the agent runs, not on the framework:
| The agent runs… | Authenticate with |
|---|---|
| On your own machine | seekrit login. The session lives in ~/.config/seekrit/config.json, so there's no token in your environment at all. A session isn't bound to an environment, so pass --app and --env to run/export, and expect a passphrase prompt (SEEKRIT_PASSPHRASE to skip it) |
| On a deployed server or container | SEEKRIT_TOKEN as a platform environment variable — the one variable your platform holds. Or hold nothing: third-party sync pushes the values into the platform's own environment instead |
| In CI | SEEKRIT_TOKEN as a repository or organization secret — see CI/CD & containers |
| In an agent sandbox | Usually neither: resolve outside the sandbox and inject at create, or give the sandbox placeholders and a proxy endpoint. See Agent sandboxes |
| Unattended, provisioning its own structure | SEEKRIT_CLIENT_ID / SEEKRIT_CLIENT_SECRET — machine credentials that mint and cache an admin token automatically. See the MCP server |
seekrit run passes its own environment through to the child. A
SEEKRIT_TOKEN you exported in your shell is therefore visible to the agent
too — and a token decrypts everything its environment holds, not just what you
injected. Fine while you're trying this out; on any machine where the agent can
run shell commands, use seekrit login instead so there's nothing in the
environment for it to find.
Then tighten it, when you need to
Four levels, in order of how little your agent's process ends up holding. Each one costs a bit more setup than the last, so take them when a specific thing worries you — not up front.
Wrap the process — where you already are
seekrit run -- <your dev command>. Zero code, every framework, every language.
The values are real environment variables inside the child, which means anything
the process can run can also read them.
Good enough when the process runs only code you wrote.
Resolve in code
import seekrit
seekrit.Client().into_env() # or: secrets = seekrit.Client().resolve()
Three lines at startup with the SDKs — Python, JS/TS, Go, Ruby.
Reach for it when there's no wrapper to hang seekrit run off (a serverless
handler, a Worker, a notebook), or when different requests need different
credentials — a multi-tenant agent resolving per tenant rather than per process.
Hold a placeholder, not a key
ChatOpenAI(base_url="http://127.0.0.1:8080/openai/v1", api_key="{{seekrit:OPENAI_API_KEY}}")
The egress proxy holds the credential; your process
holds {{seekrit:OPENAI_API_KEY}}. On the way out the proxy swaps in the real
value — and only toward the hosts, methods, and paths you allowlisted,
default-deny. Anything in an environment variable can be printed, logged, or
POSTed somewhere else; a placeholder cannot.
Reach for it when the code holding the credential is not code you trust: a sandbox, model-generated code, an agent whose next action you cannot predict.
Often still zero-code. OPENAI_BASE_URL is a de facto standard — Pydantic
AI, CrewAI, and the OpenAI Agents SDK all read it, and Claude Code and the Claude
Agent SDK take ANTHROPIC_BASE_URL. Where a framework honours it, this level is
two environment variables and no source change:
export OPENAI_BASE_URL=http://127.0.0.1:8080/openai/v1
export OPENAI_API_KEY='{{seekrit:OPENAI_API_KEY}}'
Neither of those is a secret, so they belong in your compose file or Procfile, not in seekrit.
And often no proxy either. The SDKs ship the same substitution engine as a
fetch wrapper and an httpx transport, so your code can hold the placeholder
without a sidecar — see
in-process injection. Weaker than the
proxy, because it runs in the same process as the code holding the placeholder;
much stronger than an environment variable.
Scope a key to one tool
A provider key buys tokens. A tool's Stripe or GitHub key does something
irreversible. The framework adapters narrow an allowlist to a single tool
call, so a prompt-injected search cannot reach a payment credential:
- LangChain / LangGraph —
seekrit.langchainmiddleware - Mastra —
seekritToolFetch - Pydantic AI — a toolset wrapper
Reach for it when one tool in an agent can spend money or change state and the others have no business touching that credential.
What to add, and when
| If… | Add |
|---|---|
You just don't want a .env file | Nothing — wrapping the process is the answer |
| It's a Worker, Lambda, or notebook with no wrapper | Resolve in code |
| Each request serves a different tenant | Resolve per run, not per process |
| The process runs code you didn't write | A placeholder, via the proxy |
| A key must work for one operation but not others | A placeholder, with methods and paths on the route |
| You want a placeholder but not a sidecar | In-process injection |
| One tool may spend a key and the others may not | Per-tool scoping |
| The agent must not hold the seekrit token either | Run the proxy beside it, or resolve outside its process entirely |
| You want several of these | Nothing conflicts; the proxy just sees a placeholder where a key used to be |
Per framework
Each page shows the same levels in that framework's own idiom — the actual constructor arguments, the actual dev command.
| Framework | Start with | Worth knowing | |
|---|---|---|---|
| LangGraph / LangChain | Python | Wrap | Per-tool scoping via LangChain 1.x middleware |
| Mastra | TypeScript | Wrap | model takes a function of the request — per-tenant keys are first-class |
| Pydantic AI | Python | Resolve in code | deps is a real per-run seam; a toolset scopes one tool |
| AI SDK | TypeScript | Wrap locally, resolve on serverless | fetch is a first-class provider option |
| OpenAI Agents SDK | Python | Wrap | Tracing is a second credential and a second egress host |
| Claude Agent SDK | Python / TS | Wrap | It deliberately ignores .env, so wrapping is the only zero-code path |
| CrewAI | Python | Wrap | The crew's tool keys are the ones to move to a placeholder |
| LlamaIndex | Python | Wrap | Ingestion pipelines hold their credential for a long run |
Not listed doesn't mean not supported: wrapping the process needs nothing from
the framework at all. If a framework starts a process, seekrit run goes in
front of it.
Agents that manage their own secrets
The pages above are for you wiring up an agent. If the agent itself should store and inject credentials — a coding agent setting up a project, say — install the agent plugin instead. It carries both MCP servers and the skills that keep values out of files.