Get started
Connect OpenAI Agents, Mastra, or a custom TypeScript agent and guard its tools and final output.
Featherlane AI protects two boundaries: local tools before they execute and the final answer before your application sends it to a user. The exact setup depends on how your framework runs an agent.
Choose your framework in Step 4. Every path uses the same package and runtime credentials:
npm install @featherlane-ai/sdk1. Create an Agent and Runtime Key
In the Featherlane AI dashboard:
- Open Agents, choose New agent, and copy the new agent ID.
- Open API Keys, choose Create key, and select the same environment as the agent.
- Copy the key when it is shown. The full secret is displayed only once.
You now need three values:
| Value | Where it is used |
|---|---|
| Agent ID | The agentId option in your code |
| Runtime URL | FEATHERLANE_AI_URL |
| Runtime key | FEATHERLANE_AI_API_KEY |
2. Install One Package
npm install @featherlane-ai/sdk3. Configure
Put the URL and runtime key in the server environment that runs your agent.
For local development, add them to the environment file your framework loads,
such as .env or .env.local:
FEATHERLANE_AI_URL=https://api.featherlane.ai
FEATHERLANE_AI_API_KEY=tl_live_...The SDK reads both variables automatically. Keep FEATHERLANE_AI_API_KEY server-side:
do not commit it or expose it through a browser-prefixed variable such as
NEXT_PUBLIC_*.
4. Connect Your Framework
Create the framework agent first, then pass that object to guardAgent(...).
Do not call guardAgent(agent, ...) before agent has been declared.
The agentId value is the agent ID from the Featherlane AI dashboard. It does
not need to match your JavaScript variable name or the display name used by
OpenAI or Mastra.
OpenAI Agents does not expose agent.reply(...); it uses the separate
run(agent, input) function. Use guardAgent(...) to protect locally
executed function tools, then wrap the function that returns
result.finalOutput to protect the answer:
npm install @featherlane-ai/sdk @openai/agents zodimport { Agent, run } from '@openai/agents';
import { guard, guardAgent } from '@featherlane-ai/sdk';
const baseAgent = new Agent({
name: 'Support agent',
instructions: 'You are a helpful support assistant.',
});
const agent = guardAgent(baseAgent, { agentId: 'support-agent' });
const reply = guard({ agentId: 'support-agent' }).wrap(
async (message: string): Promise<string> => {
const result = await run(agent, message);
if (typeof result.finalOutput !== 'string') {
throw new Error('The OpenAI agent did not return a text finalOutput.');
}
return result.finalOutput;
},
);
const safeReply = await reply('How do I reset my password?');
console.log(safeReply);Keep OPENAI_API_KEY configured exactly as OpenAI Agents expects. The
Featherlane AI variables are additional credentials; they do not replace
your model-provider key.
Do not stream tokens to the user before Featherlane AI checks them. For
streaming frameworks, buffer the complete result with the SDK's guard.stream
helper; see Guard Modes.
What The Integration Guards
guardAgent(...) discovers supported local tools from OpenAI Agents JS,
Mastra, LiveKit, and compatible tool registries. It authorizes each discovered
tool before its local execute() function runs. The reply(...) decorator
or guard(...).wrap(...) submits the final text to the Rust API at
POST /v1/events and applies the returned decision before the function
resolves.
The bearer key scopes each request to the workspace and environment. The same request creates the trace shown in the dashboard.
Only local tools whose execute() function is visible to the framework
adapter can be intercepted. Provider-hosted tools and remote MCP tools hidden
behind a framework require an adapter at the execution boundary you control.
The final output event does not send the raw user message text by default.
Automatic Run bookkeeping is best-effort: a Run storage failure does not
replace the guard result or the agent's own error. Set run: false only when
you intentionally want ungrouped traces.
For a framework-owned multi-turn session, keep the same wrap-once shape and bind the real lifecycle:
import { guardAgent, liveKitRun } from '@featherlane-ai/sdk';
const session = createLiveKitAgentSession();
const agent = guardAgent(createAgent(), {
agentId: 'support-agent',
run: liveKitRun(session, { externalId: roomSid }),
});
await session.start({ agent, room });The first guarded output or local tool starts one live_call Run. Every later guarded trace from that wrapped LiveKit session uses the same Run, which remains running until the session closes. agentId identifies the registered agent; roomSid identifies this execution. Never use agentId as a session key.
Generic agents keep the default one-Run-per-reply behavior because an agent object may be shared across users. Frameworks other than LiveKit can select session scope by supplying a stable externalId and a registerEnd callback.
5. Verify
Send one test message and open the agent's Run and trace in the dashboard. No repository clone, Doppler setup, local Rust server, or model-provider proxy is required.
Common setup errors
- Cannot find
@featherlane-ai/sdk: run the install command in the same application package as the agent file. In a monorepo, installing it only at a different workspace root may not make the import available. - Agent used before declaration: create
baseAgentfirst, then callguardAgent(baseAgent, ...). The decorator needs the real framework object. - No trace appears: confirm that
FEATHERLANE_AI_URLandFEATHERLANE_AI_API_KEYare loaded by the server process, and thatagentIdexactly matches an agent in the key's environment. - Structured or streamed output: select or buffer the final user-visible string before guarding it. Never send unguarded tokens to the browser first.
See Guard Modes for strict, rewrite, regeneration, and streaming behavior.
Write Your First Policy
Start with the policy authoring guide: