Featherlane AI
Get started

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/sdk

1. Create an Agent and Runtime Key

In the Featherlane AI dashboard:

  1. Open Agents, choose New agent, and copy the new agent ID.
  2. Open API Keys, choose Create key, and select the same environment as the agent.
  3. Copy the key when it is shown. The full secret is displayed only once.

You now need three values:

ValueWhere it is used
Agent IDThe agentId option in your code
Runtime URLFEATHERLANE_AI_URL
Runtime keyFEATHERLANE_AI_API_KEY

2. Install One Package

npm install @featherlane-ai/sdk

3. 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 zod
import { 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 baseAgent first, then call guardAgent(baseAgent, ...). The decorator needs the real framework object.
  • No trace appears: confirm that FEATHERLANE_AI_URL and FEATHERLANE_AI_API_KEY are loaded by the server process, and that agentId exactly 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:

On this page