# Claude Agent SDK

The [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview) is
Claude Code as a library: built-in `Read`, `Edit`, `Bash`, `Grep`. Two facts
about it decide how you should hand it credentials.

First, it reads `ANTHROPIC_API_KEY` from **the environment of the process that
runs your agent, and does not load `.env` files** — so process wrapping isn't a
workaround here, it's the documented path.

Second, this agent reads files and runs commands. A `.env` sitting in the working
directory is not inert configuration; it is inside the agent's reach, along with
`git log -p` and `docker compose config`. The safest place for a credential in
this framework is an environment variable in the child process and nowhere else.

> **Note:** **Start here if seekrit is new:** [three commands](/docs/guides/frameworks#get-running-in-five-minutes) put the keys in an environment, mint a token bound to it, and export `SEEKRIT_TOKEN`. A token reads [everything in its environment](/docs/guides/frameworks#which-secrets-does-the-agent-get), so there is no per-key or per-framework setup to do before any of the below.

## 1. Wrap the process

```bash
seekrit run -- python agent.py
seekrit run -- npx tsx agent.ts
```

```python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage

async def main():
    async for message in query(
        prompt="Review utils.py for bugs that would cause crashes. Fix any issues you find.",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Edit", "Glob"],
            permission_mode="acceptEdits",
        ),
    ):
        if isinstance(message, AssistantMessage):
            print(message.content)

asyncio.run(main())
```

Install is `pip install claude-agent-sdk` or
`npm install @anthropic-ai/claude-agent-sdk`.

## 2. Resolve in code

```python
import os
import seekrit

seekrit.Client().into_env()          # before the first query()
assert "ANTHROPIC_API_KEY" in os.environ
```

Useful when the agent runs inside a server that has no wrapper — a queue worker
or a web handler. Resolve once at boot, not per request.

## 3. Never hold the key

The Agent SDK is built on the same foundation as Claude Code and loads the same
settings, so it takes the same gateway variables and the proxy needs no code
change:

```bash
export ANTHROPIC_BASE_URL=http://127.0.0.1:8080/anthropic
export ANTHROPIC_AUTH_TOKEN='{{seekrit:ANTHROPIC_API_KEY}}'
```

```toml
# seekrit-proxy.toml
listen = "127.0.0.1:8080"

[[route]]
prefix = "/anthropic"
upstream = "https://api.anthropic.com"
allow = ["ANTHROPIC_API_KEY"]
methods = ["POST"]
paths = ["/v1/messages"]
```

`ANTHROPIC_AUTH_TOKEN` sends the value as `Authorization: Bearer …`;
`ANTHROPIC_API_KEY` sends it as `x-api-key`. The proxy substitutes placeholders
in either header, so pick whichever your setup already uses — but use exactly
one, since a credential in the variable your gateway doesn't read fails with a
`401` that looks like a seekrit problem and isn't.

### Why there is no in-process option here

The other pages in this section offer a
[`fetch`/`httpx` shim](/docs/guides/agent-proxy/in-process) as a middle rung
between an environment variable and the proxy. It does not apply here: the
Claude Agent SDK drives a subprocess rather than calling the API from your
process, so there is no client of yours to hand a transport to. Environment or
proxy are the two options.

## Gotchas

- **The agent can reach whatever the process can.** With `Bash` allowed, an
  agent that wants a credential can go looking for one. That is the argument for
  keeping plaintext out of the filesystem entirely rather than for tightening
  `allowed_tools` — though tightening them is also worth doing.
- **Multi-tenant needs more than a credential boundary.** Anthropic's own docs
  warn against relying on default `query()` options for tenant isolation: managed
  policy settings, `~/.claude.json`, and per-directory auto memory are read
  regardless of `settingSources`. For a multi-tenant agent, give each tenant its
  own filesystem, pass `settingSources: []` and
  `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`, and resolve that tenant's secrets per run
  rather than injecting every tenant's into one process.
- **A wrapped process is not a sandboxed one.** `seekrit run` keeps values off
  disk; it does not stop the agent reading the environment it was given. If the
  agent must not be able to read the key at all, that is shape 3.
- **Authenticate with `seekrit login`, not an exported `SEEKRIT_TOKEN`.**
  `seekrit run` injects the resolved values *on top of* the environment it
  inherits, so a token exported in your shell is visible to the child too — and
  a token is a decryption credential, so an agent holding it can resolve
  everything that token grants, not just what you injected. `seekrit login`
  stores it in `~/.config/seekrit/config.json` instead, leaving nothing in the
  environment for the agent to find.
