I wired Mozilla's any-agent and the OpenAI Agents SDK into 1Claw so an agent can call a real API with your credentials, and the key never reaches the model's context window. The whole thing is one decorator.
The honest version
Same confession as Kevin: I paste API keys into agents. I work on AI at Mozilla, I know better, and I do it anyway. It's 1 a.m., the agent needs a Resend key to send a test email, and the fastest path is the dumb one. Copy the key, drop it in the prompt, move on.
1Claw's local daemon already solves this at the network layer. The daemon makes the call, so the model never touches the key. But I live inside the OpenAI Agents SDK, writing tools in Python, and I wanted the same guarantee in the process itself: right inside the tool function, with no daemon running alongside it. So I built it. It came out to one decorator and a small HTTP client.
What I built
A small Python package, oneclaw_agent_tools, with three pieces:
- A thin 1Claw client.At the time of writing, I rolled a minimal client from 1Claw's open-source OpenAPI spec. Since then, the official Python SDK (
pip install oneclaw) has shipped and covers the full API surface. You can swap the built-in client foroneclaw.create_client()if you prefer the official package. Either way, it authenticates as a 1Claw agent, refreshes its short-lived token, and fetches a secret value just in time over httpx. - A
@with_secretdecorator. You write a normal tool function whose first parameters are secrets. The decorator hands the agent a version with those parameters stripped from the signature, so the model is never told the key exists. - A no-leak proof harness.
assert_no_leak(trace, key)serializes a whole agent run and asserts the secret isn't in it. A security claim you can't check is just a vibe.
It all runs on Mozilla's any-agent (which we build, and which hands me one clean AgentTrace no matter which backend I pick) driving the OpenAI Agents SDK.
The five-minute demo
You need a 1Claw account, an OpenAI key, and a Resend key. The Resend key goes into 1Claw, not into your environment.
# Python 3.11-3.13 (any-agent doesn't support 3.14 yet)
git clone https://github.com/marcusrein/1claw-openai-agents-demo
cd 1claw-openai-agents-demo
python3.13 -m venv .venv && source .venv/bin/activate
pip install -e ".[agent,dev]"Store your Resend key in 1Claw, create an agent, and write a policy granting that agent read on that one secret. Two things that will save you the hour of debugging I spent: the policy has to live on the same vault as the secret (and the vault your agent is bound to), and you can check that the agent actually sees it:
python main.py --list # prints the exact secret path the agent can readPut that path in .env as ONECLAW_RESEND_SECRET (mine was database/api-key), then fill in the rest:
cp .env.example .env
# ONECLAW_AGENT_ID, ONECLAW_AGENT_API_KEY, ONECLAW_VAULT_ID,
# ONECLAW_RESEND_SECRET, OPENAI_API_KEY, DEMO_RECIPIENTNotice what's not in that file: the Resend key. It lives in 1Claw. Now run it:
python main.py "Email me a one-line hello from a leak-proof agent."The agent picks the send_emailtool, sends a real email, and tells you it's done. The key was in your environment in exactly zero places.
The pattern is one decorator
Here's the tool. The Resend key is the first parameter, so @with_secret strips it from what the agent sees:
@with_secret("RESEND_API_KEY")
def send_email(resend_api_key: str, to: str, subject: str, body: str) -> dict:
"""Send a plain-text email to one recipient.
Args:
to: The recipient's email address.
subject: The subject line.
body: The plain-text body of the email.
"""
resp = httpx.post(
"https://api.resend.com/emails",
headers={"Authorization": f"Bearer {resend_api_key}"},
json={"from": "1claw demo <onboarding@resend.dev>",
"to": [to], "subject": subject, "text": body},
timeout=30.0,
)
resp.raise_for_status()
return {"status": "sent", "id": resp.json().get("id")}The docstring documents to, subject, and body, but not the key. That's on purpose. The OpenAI Agents SDK builds each tool's JSON schema from the signature and docstring, so a parameter that's in neither never shows up. Wire the client up once:
set_default_client(OneClawClient(
agent_id=os.environ["ONECLAW_AGENT_ID"],
api_key=os.environ["ONECLAW_AGENT_API_KEY"],
vault_id=os.environ["ONECLAW_VAULT_ID"],
))
agent = AnyAgent.create("openai", AgentConfig(
model_id="openai:gpt-4.1-mini",
instructions="Send emails with send_email. Never ask for API keys.",
tools=[send_email],
))The part that matters: the model never sees the key
Two places to check, both mechanical.
First, what the model is even offered. The OpenAI Agents SDK turns send_email into a tool schema. Here's the actual output of function_tool(send_email).params_json_schema:
{
"properties": {
"to": { "type": "string", "description": "The recipient's email address." },
"subject": { "type": "string", "description": "The subject line." },
"body": { "type": "string", "description": "The plain-text body of the email." }
},
"required": ["to", "subject", "body"],
"additionalProperties": false
}No resend_api_key. The model can't pass a key, ask for one, or invent one, because as far as it knows the parameter isn't there. The leak is closed at the schema, before the model generates a single token.
Second, the whole run. I ran a real gpt-4.1-mini agent through the tool, with Resend stubbed so no mail actually went out, and dumped the trace. The span that matters:
EXECUTE_TOOL: send_email
Input: { "to": "marcus@example.com", "subject": "Hello",
"body": "hi from a leak-proof agent" }
Output: { "status": "sent", "id": "msg_real_smoke" }Then I searched the entire serialized trace for the secret:
trace size: 4739 chars
sentinel key present in trace? False
'Bearer' present in trace? FalseEvery message, the tool input, the tool output, all 4,739 characters of it, and the credential is nowhere. The tool got the key inside its own function body, called Resend, and dropped it. The model's view never contained it.
This ships as a runnable test. The fast ones need no keys:
$ pytest -q
...............s.....
20 passed, 1 skippedOne test deliberately leaks a sentinel into a fake trace, so I know the detector actually fails when it's supposed to. The skipped one is the opt-in live run: set ONECLAW_LIVE=1 and it drives a real model and checks the key never reaches the trace.
Fail-closed by default
1Claw is deny-by-default. Valid agent credentials aren't enough. A human has to write a policy giving this agent read on this secret. To show it, I stored a second secret, billing/stripe-key, and gave the agent no policy for it. The agent's policy covers the Resend path and nothing else, so it can't even see the Stripe key in a listing. Reach for it directly and:
Refused as expected: Agent is not permitted to read secret 'billing/stripe-key'.
Grant access with a 1claw policy.One credential, least privilege. If the agent is ever compromised, the blast radius is exactly the secrets a human chose to grant, and you revoke them in one click.
Why in-process instead of the daemon?
Use whichever fits. The 1Claw daemon is the right call when you want the secret to never enter the agent process at all; the daemon is the last hop. But when I'm building with the OpenAI Agents SDK, my tools are already Python functions in my process. Doing the injection there, inside the tool body, means nothing extra to run and the same guarantee: the secret lives in the function and never in the context window. It fits SDK-native code.
Architecture
AI Model (OpenAI Agents SDK, via Mozilla any-agent)
|
| send_email(to, subject, body) <- no key in the call
v
@with_secret tool body (your process)
|
| 1. key = OneClawClient.get_secret("RESEND_API_KEY") --> 1Claw (HSM)
| 2. POST https://api.resend.com/emails (Bearer key) --> Resend
| 3. return {"status": "sent"}
v
Back to model (key never in context, never in the trace)Open and auditable
For a tool whose whole job is holding your credentials, "trust us" isn't good enough, and 1Claw doesn't ask you to. The agent-facing path is open source: the 1claw-mcp server, the 1claw-sdk, and the full OpenAPI spec that I wrote the Python client from in the first place. any-agent is open source too. The only sealed box in the stack is the HSM, which is the box you want sealed.
One honest caveat: this doesn't make secrets vanish. The agent still holds one credential, its 1Claw agent key, in the environment. That's the trade. You swap a pile of plaintext third-party keys, scattered across .env files and prompts, for a single scoped, revocable, auditable credential that can't read anything a human didn't allow.
What's next
Adding a credentialed tool is a ten-line job now, and the guarantee comes free:
@with_secret("STRIPE_SECRET_KEY")
def refund(stripe_key: str, charge_id: str, amount_cents: int) -> dict:
"""Refund a charge.
Args:
charge_id: The Stripe charge to refund.
amount_cents: Amount to refund, in cents.
"""
... # use stripe_key; the model only ever calls refund(charge_id, amount_cents)Write the policy, hand the tool to AgentConfig, done. Same shape for Slack, GitHub, or a database URL: any secret you don't want your agent able to leak.
If you've been pasting keys into agents, and I know you have because I was too, clone the repo, set a few env vars, and watch an agent send an email with a key it can't see.
Stack: any-agent · OpenAI Agents SDK · 1Claw · Resend