deploy/openai-agents/http template of the hoop repository. Start with the quick start on your laptop, then run it against your API.
Why the agent cannot skip the Sidecar
Two controls are OpenAI’s, and one is the Sidecar’s:- One reachable host. The environment template sets
network.access: restrictedand lists only the Sidecar’s host inallowed_domains. The sandbox reaches the network through OpenAI’s egress proxy, which refuses every other host, the API’s own included. curl printscurl: (56) CONNECT tunnel failed, response 403. - Placeholders in the sandbox. Both tokens the agent sends are vault credentials bound to the Sidecar’s host. Code in the sandbox sees a placeholder, such as
__OPENAI_SECRET__[HOOP_CALLER_TOKEN:<32 hex digits>]__, new in each session. The egress proxy writes the real token into requests to that host only. - A caller token on every request. The Sidecar refuses a request without the caller token before any byte of it reaches the API. The Sidecar’s URL is public, and a caller from anywhere else is refused the same way.
allowed_hosts, and another host gets the placeholder. The one-host limit holds in sessions that name the environment template. A session cannot broaden its network: the API refuses that request with 400. A session that names no environment template gets enabled networking, OpenAI’s default, so limit who holds API keys for your OpenAI project.
Tokens go in headers only
Get the template
Both setups below use$T, so run them in the shell that sets it:
Quick start
The Sidecar fronts hoop’s demo API, a set of invented users with emails, phones and cards. A Cloudflare quick tunnel gives it a public HTTPS host. The tunnel is for a trial: it has no uptime promise and gets a new host on each start. You need Docker,curl 7.76 or later, openssl, jq 1.6 or later, and an OpenAI API key with Agents API access. No cluster and no certificate.
1
Start the Sidecar
2
Ask the agent
Read your OpenAI API key without echoing it, then run the agent:It creates an environment template that can reach only the tunnel host, a vault with the caller token, and the demo agent. Then it starts a session, asks the agent to list the users and to try
DELETE /users/1, and prints each command the agent ran, its output and the answer.The answer lists the users with [REDACTED:EMAIL_ADDRESS], [REDACTED:PHONE_NUMBER] and [REDACTED:CREDIT_CARD], and reports 403 and this agent may only read from the API for the DELETE. The last line is turn: completed.3
Read the audit trail
openai-agents and the session id the caller sent: the OpenAI session, or run.sh for the checks in step 1. The DELETE rows name rule read-only. The request without a token is the anonymous row with rule identity, and carries no session id.4
Clean up
run.sh took under 2 minutes and agent.sh about a minute in our runs.
Run it against your API
Requirements
- A Kubernetes cluster that can publish a
LoadBalancerService, and a DNS name for it. Allkubectlandhelmcommands below use your current namespace. - A TLS certificate for that name from a public CA, as
tls.crt(full chain) andtls.key. The vault fills tokens only into HTTPS requests on port 443 or 8443, and the egress proxy inspects TLS. A private CA is untested. helm,kubectl,openssl,curl7.76 or later andjq1.6 or later.- An OpenAI API key with Agents API access.
hoophq/hoopsidecar1.263.0 or later.values.yamlpins 1.263.0. Older releases report the template’s install source asother.
--set laneServices.public.loadBalancerIP=<address> to the helm install below, or issue the certificate with a DNS-01 challenge, which needs no running server.
1
Create the tokens and the certificate
Keep them outside the clone. Set Save your API’s own token for the vault, without echoing it:Put
SIDECAR_HOST to your DNS name:tls.crt and tls.key in this directory. With certbot and DNS-01, for example:2
Run the Sidecar
In the Wait until
helm install line, replace https://api.example.com with your API:EXTERNAL-IP is not <pending>, then point SIDECAR_HOST at it: an A record for an IP address, a CNAME for a host name. These are the parts of values.yaml that decide what the agent can do:values.yaml
caller_tokenadmits a request only when itsX-Hoop-Tokenheader holds a token from the file. Audit rows name the principalopenai-agents. See Caller token.upstreamis a URL, so the hop to the API uses TLS and verifies the API’s certificate. The listener forwards theAuthorizationheader unchanged, so the API gets the token that the vault filled.guardrailsrefuses every method butGET,HEADandOPTIONS.otheris any method that HTTP does not define, such asMOVE.maskredacts emails, phone numbers and card numbers in responses, and no other value.http.headersrecords the agent’sX-OpenAI-Session-Idon each audit row.- The public Service maps port 443 to the listener’s 8443. The admin port stays on the chart’s
ClusterIPService. analytics.installSource, at the top of the file, names this template in the Sidecar’s usage analytics. Setanalytics.enabled: falseto send no analytics.
3
Check the Sidecar
request refused: the request carried no credential. The second prints x-hoop-denied: policy.4
Create the OpenAI side
Read your OpenAI API key without echoing it:The dry run prints the request bodies with your host, and sends nothing:Then create them:Each credential is bound to the Sidecar’s host, not the API’s, because the sandbox never reaches the API:
create makes the environment template, a vault with two credentials, and the agent. It writes their ids to ./.openai-ids, and refuses a second run until delete. The environment template is what keeps the sandbox on the Sidecar:openai/environment-template.json
openai/credentials/caller-token.json
setup.shwritesSIDECAR_HOSTin place ofsidecar.example.com, and sends each token from its file, without the trailing newline that the API refuses.CUSTOMER_API_TOKEN, your API’s token, is the second credential, in the same shape.- An environment template cannot hold a vault, so each session names the environment template, the vault and the agent.
- The agent’s instructions,
openai/agent.md, name the three headers to send. They use/v1/customersas an example, so edit them for your API.
5
Start a session
Verify
The Sidecar writes the audit trail to stdout and keeps no copy, so ship the pod’s logs to your log system. To read it:
anonymous with rule identity.
Kubernetes through the Sidecar
Thekubernetes template gives the agent kubectl against one cluster, through a Sidecar http listener in front of the cluster’s API server. The Sidecar refuses DELETE, masks Secret values in JSON responses and records each request with the session id.
It differs from the REST setup above:
- No caller token. kubectl cannot send
X-Hoop-Token, so the listener admits any caller, and only the API server checks the token. Rows name the principalanonymous. - kubectl arrives as an environment file. The sandbox has no route to a download host, so
setup.shuploads the kubectl client to the template, and a script in the sandbox unpacks it. It is the linux/amd64 build. - The session id travels as an impersonation extra. kubectl sends it in
Impersonate-Extra-Hoop-Session, which needs one ClusterRole foruserextras/hoop-session.
kubectl apply failing on the masked annotation.
Postgres through an MCP server
A hosted sandbox cannot reach a database over the Postgres protocol:psql fails with restricted and with enabled networking, and the egress proxy closes a tunnel to port 5432 on the first Postgres byte. The postgres-mcp template gives the agent Postgres as an MCP tool instead. dbhub, the MCP server, reaches the database only through a Sidecar postgres listener, which refuses writes, masks emails and records each statement.
Its guarantee is different from the REST setup above:
- OpenAI’s servers make the MCP call, not the sandbox. The agent’s MCP tool sets
connection_origin: service, and the sandbox’s network isdisabled. Astatic_bearercredential in the vault sends dbhub’s token from OpenAI’s servers, so the token never enters the sandbox. - Caddy checks the address, and dbhub checks the token. Caddy, the HTTPS front end, admits only the addresses in OpenAI’s
chatgpt-connectors.jsonand serves only/mcp. OpenAI’s IP address page names no list for MCP calls from the Agents API, and the test sessions called from addresses in this one. It holds every OpenAI customer, so the token is what admits your agent. - dbhub has one route to the database. It shares a network with Caddy and the Sidecar only, so the Sidecar is its only path to Postgres.
Behavior to know
OpenAI’s:- US data residency only, no Zero Data Retention. See the Agents API overview. The prompts, and the masked responses the agent reads, go to OpenAI.
- Exact hosts only.
allowed_domainstakes 1 to 100 exact host names, with no wildcard, scheme, port or path. A redirect target needs its own entry. See hosted environments. - An idle sandbox can be deleted after an hour. OpenAI’s timeout is not configurable.
- No cost cap per session. The API refuses the
spend_controlsession field. - Port 80 passes too. The egress proxy lets plain HTTP through to the Sidecar’s host, and the vault fills nothing there. Publish the listener on 443 only, as the template does.
- The listener’s own certificate is untested from a hosted session. The test sessions reached the Sidecar through a Cloudflare tunnel, so the egress proxy saw Cloudflare’s certificate, not the one in
hoop-sidecar-tls. Run one session against your host before agents rely on it.
- The session id is a label. The agent writes the header, so it can write any value. It correlates rows. It does not prove who asked.
- Two kinds of row carry no session id, although the request sent it: a refused caller, and a WebSocket frame.
- One token for every agent. Each row names
openai-agents, not a person. Per-user rows need a per-user token. - Only the caller token guards the public URL. The sandbox’s egress addresses are shared by OpenAI customers, so an address allowlist on the load balancer cannot narrow who reaches it. Rotate the token as Caller token describes.
- The rule reads the request method only. An API that accepts
X-HTTP-Method-Override,X-HTTP-MethodorX-Method-Overrideruns the method that the header names, even on aGET. Turn overrides off at the API. With a license, you can also add onehttp_headerrule per override header and list each header inhttp.headers. - A request is held until it is whole. The Sidecar judges a request after its body arrives, and forwards none of it before. A request over 8 MiB is refused. A client that sends
Expect: 100-continue(curl does this above 1 MiB) waits about 1 second before it sends the body. - Masking applies to responses. A JSON or text response under
brorzstdis refused, not forwarded unmasked. A response over 16 MiB passes unmasked. Audit rows hold the request line as sent. See Masking. - Scope. The template covers JSON REST APIs over HTTP/1.1 and HTTP/2. Server-sent events and MCP servers are out of its scope.
- No other database, and no SSH, from the sandbox yet. No template covers them, and Postgres bytes do not pass the egress proxy. For Postgres, see Postgres through an MCP server.
Clean up
delete removes the vault first, because it holds the tokens, then the sessions, the agent and the template. If it fails halfway, run it again: it retries only what is left. Then remove ~/hoop-openai and the DNS record for SIDECAR_HOST.
A network error during create or session can leave a resource that .openai-ids does not list. Find it by its name, hoop sidecar, customer-api or hoop-demo-api, in GET /v1/vaults, /v1/agents, /v1/agents/environments/templates or /v1/agents/sessions, and delete it.
Free and Enterprise
Free tier: one Data Masking rule and one Guardrail per Sidecar are free, forever. Running more than one rule per feature, or managing rules centrally across Sidecars, requires Enterprise.
-
The template fits the free tier. It uses one guardrail rule,
read-only, and one masking rule,pii, however many agents and sessions call it. One rule can carry more than one check:read-onlynames seven methods, andpiithree entities. -
A config over the cap does not start. In the quick start,
./run.shstops and shows the Sidecar’s log, which ends:To check a config before the next start, rundocker compose run --rm sidecar hoop start sidecar --validateinquickstart/after the first./run.sh. A running Sidecar refuses an edit over the cap with a warning, and keeps the old rules. -
More rules: an Enterprise license with the guardrails and data-masking features lifts the one-rule caps, with or without a Control Plane. See Licensing and the chart’s License setting. In the quick start, put the license document itself in
quickstart/.env, in single quotes:HOOP_LICENSE='{"payload": ...}'. - Approvals and central management: connect the Sidecar to a Control Plane, which also needs an Enterprise license. It adds approvals, shared rule sets and one view of many Sidecars.
Next
Claude Managed Agents
The same setup for an agent on Anthropic’s cloud sandbox.
HTTP
What the http listener inspects, masks and refuses.
Guardrail Rules
operation, http_resource, http_status and http_header rules.Kubernetes
Install and configure the Sidecar chart.