Testing Agents Behind Authentication
Many production agents sit behind an API that requires authentication — most commonly an OAuth2 client-credentials flow (Auth0 “machine-to-machine” is the canonical example). LangWatch’s HTTP agent type carries a static credential (bearer / api_key / basic), which is not enough when a token has to be exchanged first.
A custom code agent closes that gap: a few lines of Python that fetch the token, call your API with it, and keep the credentials in your project’s encrypted secret store — never in the agent’s stored source.
How it fits together
- Project secrets hold the client ID and client secret, encrypted at rest. They are exposed to your code agent’s Python as the injected
secretsnamespace. - The code agent exchanges the credentials for an access token and calls your protected API with it.
- A scenario drives the agent like a real user and judges the answers — the agent only passes if it actually got through the auth wall.
1. Store the credentials as project secrets
In Settings → Secrets, create the credentials and the endpoint coordinates — keeping the endpoints in secrets too means the whole agent is configurable without touching its code or any API. CreateAUTH0_CLIENT_SECRET through the Settings → Secrets UI, not on a command line: a secret passed as a CLI argument lands in your shell history and the process listing, and can end up in CI logs. The non-sensitive coordinates are fine to create via the CLI:
UPPER_SNAKE_CASE (^[A-Z][A-Z0-9_]*$). Values are encrypted and never returned by any API.
2. Write the code agent
Create a code agent with a single inputmessage and a single declared output output: the declared output key must match the key the Python returns, or the run fails with missing_output. Use this Python, the reference implementation, exercised continuously against the real code-agent runtime:
- Entry point:
class Codewith__call__(no constructor arguments). secretsis injected into the module globals — it is not the Python stdlibsecretsmodule. Do notimport secrets; that would shadow the injected namespace.os.environis not populated in the sandbox.- Return every declared output key, or the run fails with
missing_output. - Available packages:
requests,httpx,pydantic,langwatch. - Budget: the token fetch plus your API call must finish inside the runner’s wall-clock limit (60s by default) — keep explicit timeouts on both requests.
- Failure behavior:
raise_for_status()puts the URL and status code in the error — never the request body — so a rejected credential fails loudly without exposing the secret.
3. Map the agent’s input for scenarios
Because everything else rides the secrets namespace, the agent has a single input — the conversation message — and needs exactly one mapping:message from the scenario’s input. That mapping is creatable in the agent editor today.
Keeping the endpoint coordinates in secrets is deliberate: the alternative — static
value mappings on extra inputs — currently cannot be created from the editor UI, only via the agents API. UI support for static value mappings is tracked in #6371.