Lab: build a credential broker
Write both patterns side by side, dump each agent's context, and see the difference the broker makes.
Execution Intents is a small idea with a large consequence, and the fastest way to believe it is to implement both sides. Forty lines of Python, no network, no account. The question the lab answers is narrow and concrete: after the agent has done its job, is the credential in its context?
- 1
Write both agents. The first reads the credential the obvious way; the second sends an intent and lets a broker do the call.
bashcat > /tmp/broker.py <<'EOF' VAULT = {"stripe/key": "sk-live-9f2a"} ALLOWED = {("stripe", "GET", "/v1/balance")} class ReadingAgent: def __init__(self): self.context = [] def run(self): key = VAULT["stripe/key"] # the agent now holds it self.context.append(f"fetched credential: {key}") self.context.append(f"calling stripe with {key}") return "balance: 42.00" def brokered_call(binding, method, path): if (binding, method, path) not in ALLOWED: return f"DENIED: {binding} {method} {path} is not an allowed intent" _key = VAULT["stripe/key"] # resolved here, never returned return "balance: 42.00" class BrokeredAgent: def __init__(self): self.context = [] def run(self): self.context.append("intent: stripe GET /v1/balance") result = brokered_call("stripe", "GET", "/v1/balance") self.context.append(f"result: {result}") return result for Agent in (ReadingAgent, BrokeredAgent): a = Agent(); a.run() leaked = [line for line in a.context if "sk-live" in line] print(f"{Agent.__name__}:") for line in a.context: print(f" {line}") print(f" -> credential in context: {'YES' if leaked else 'no'}\n") print("an injected agent tries something else:") print(" ", brokered_call("stripe", "POST", "/v1/transfers")) EOF python3 /tmp/broker.py - 2
Read the two context dumps. They are the whole lesson.
textReadingAgent: fetched credential: sk-live-9f2a calling stripe with sk-live-9f2a -> credential in context: YES BrokeredAgent: intent: stripe GET /v1/balance result: balance: 42.00 -> credential in context: no an injected agent tries something else: DENIED: stripe POST /v1/transfers is not an allowed intent - 3
Clean up.
bashrm /tmp/broker.py
Both agents got the same answer. Only one of them is a problem if it is later asked to repeat everything it knows.
- The reading agent's context contains a live credential. A prompt injection at any later turn can ask for it, and any log, trace or provider request that captured the context has it too.
- The brokered agent's context contains an intent and a result. There is nothing to exfiltrate because there was never anything to hold.
- The last line is the second property, and it is easy to miss. The broker refused an intent it was not configured for, so a successfully injected agent still could not initiate a transfer.
Notice that the broker is trivial: an allowlist, a lookup, and a call. The security value is not in its sophistication, it is in where it sits. The check runs somewhere the agent's context cannot reach and cannot argue with.
This is the shape of Execution Intents. A binding is the allowlist plus the stored credential, credential_source with a vault_ref is the lookup resolved at execution time, and the agent sends the same kind of intent your BrokeredAgent sent. The production version adds host and path guardrails, credential rotation, per-binding limits and an audit trail, and the property it protects is the one you just demonstrated in forty lines.
The broker still holds the credential, so this shrinks the private-data circle rather than removing it. An attacker who compromises the broker gets everything, which is why the real implementation keeps that material in an HSM and constrains what a binding may call.
Check your understanding
3 questionsAfter both agents complete, what is the difference in their contexts?
What did the DENIED line demonstrate?
What does brokering not solve?