Integration Guide

This guide walks you through connecting your existing AI agent to TameFlare. TameFlare supports two integration modes:

  • Proxy enforcement (v2) — recommended. Zero code changes. The proxy intercepts all HTTP traffic.
  • SDK advisory (v1) — the agent voluntarily checks with TameFlare before acting.

Proxy Integration (v2, recommended)

The proxy model requires no code changes to your agent. It works with any language or framework.

1. Initialize and add connectors

npx tf init
npx tf connector add github --token ghp_xxx
npx tf connector add openai --token sk-xxx

Or use a platform template:

npx tf init --platform langchain    # Pre-configured for LLM agents
npx tf init --platform n8n          # Pre-configured for workflow automation
npx tf init --platform claude-code  # Pre-configured for coding assistants

2. Set permissions

npx tf permissions set --gateway "Bot" --connector github \
    --action "github.pr.*" --decision allow
 
npx tf permissions set --gateway "Bot" --connector github \
    --action "github.pr.merge" --decision require_approval
 
npx tf permissions set --gateway "Bot" --connector github \
    --action "github.repo.delete" --decision deny

3. Run your agent

npx tf run --name "Bot" python my_agent.py
npx tf run --name "Bot" node my_agent.js
npx tf run --name "Bot" ./my_agent

That's it. All outbound HTTP traffic is now proxied. The agent never sees real API keys.

4. Monitor

npx tf status                  # Gateway + agent stats
npx tf logs                    # Live traffic log
npx tf approvals list          # Pending approvals
npx tf approvals approve <id>  # Approve a held request

Or use the dashboard at http://localhost:3000/dashboard.

Available connectors

| Connector | Domains | Actions | |---|---|---| | github | api.github.com | 20+ (PRs, issues, branches, releases, files, repos) | | openai | api.openai.com | 24+ (chat, embeddings, images, audio, files, fine-tuning) | | anthropic | api.anthropic.com | 2+ (messages, models) | | stripe | api.stripe.com | 40+ (charges, refunds, payments, subscriptions, invoices) | | slack | slack.com, api.slack.com | 35+ (messages, channels, files, users, admin) | | generic | Any domain | Method-based (GET, POST, PUT, DELETE) |

Why proxy mode is better

  • Zero code changes — works with any agent, any language
  • Deny-all default — no connector = no access
  • Credential isolation — agent never sees real API keys
  • Hold-connection approvals — proxy holds HTTP request until human approves
  • Cannot be bypassed — agent's traffic is physically routed through the proxy
Tip
The proxy works with Python, Node.js, Go, shell scripts, LangChain, CrewAI, OpenClaw, n8n, and any tool that makes HTTP calls.

SDK Integration (v1, advisory mode)

The SDK mode is useful when you want the agent to voluntarily check with TameFlare before acting. The agent keeps its own credentials.

Warning
In SDK mode, enforcement depends on the agent cooperating. A buggy or compromised agent could skip the TameFlare check. Proxy mode is architecturally enforced.

Before you start

You need a running TameFlare instance and at least one registered agent:

  1. Follow the Quick Start to get tf running
  2. Go to Dashboard > Agents and click Register agent to get an API key
  3. Install at least one policy (the GitHub Safe Defaults pack is a good start)

You'll need two values:

| Value | Where to find it | |---|---| | AAF_API_KEY | Shown once when you register an agent (e.g. aaf_test_abc123...) | | AAF_BASE_URL | Your TameFlare instance URL + /api (e.g. http://localhost:3000/api) |


Node.js agent

1. Install the SDK

# Install from source (npm package coming soon)
npm install ./packages/sdk-node

2. Initialize the client

import { AgentFirewall } from "@agentfirewall/sdk";
 
const TameFlare = new AgentFirewall({
  apiKey: process.env.AAF_API_KEY,
  baseUrl: process.env.AAF_BASE_URL ?? "http://localhost:3000/api",
});

3. Wrap your agent's actions

Before TameFlare — your agent calls GitHub directly:

// OLD: Agent calls GitHub directly
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
await octokit.pulls.merge({ owner: "acme", repo: "api", pull_number: 42 });

After TameFlare (Mode A) — agent requests permission, Gateway executes:

// NEW: Agent requests permission, Gateway executes
const result = await TameFlare.requestAction({
  type: "github.pr.merge",
  resource: {
    provider: "github",
    account: "acme",
    target: "acme/api",
    environment: "production",
  },
  parameters: { pull_number: 42 },
});
 
switch (result.status) {
  case "allowed":
    await TameFlare.execute(result.action_request_id, result.decision_token);
    break;
  case "pending_approval":
    const approved = await TameFlare.waitForApproval(result.action_request_id);
    if (approved) await TameFlare.execute(result.action_request_id, approved.decision_token);
    break;
  case "denied":
    console.log("Blocked:", result.decision.reason);
    break;
}

After TameFlare (Mode B) — agent checks policy, then executes itself:

// NEW: Agent checks policy, then executes itself
const result = await TameFlare.requestAction({
  type: "github.pr.merge",
  resource: {
    provider: "github",
    account: "acme",
    target: "acme/api",
    environment: "production",
  },
  parameters: { pull_number: 42 },
});
 
if (result.status === "allowed") {
  // Agent still holds credentials and executes directly
  const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
  await octokit.pulls.merge({ owner: "acme", repo: "api", pull_number: 42 });
} else if (result.status === "denied") {
  console.log("Policy blocked this action:", result.decision.reason);
}

Python agent

1. Install the SDK

# Install from source (PyPI package coming soon)
pip install ./packages/sdk-python

2. Initialize the client

from agentfirewall import AgentFirewall
import os
 
TameFlare = AgentFirewall(
    api_key=os.environ["AAF_API_KEY"],
    base_url=os.environ.get("AAF_BASE_URL", "http://localhost:3000/api"),
)

3. Wrap your agent's actions

Before TameFlare:

# OLD: Agent calls Stripe directly
import stripe
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
stripe.Transfer.create(amount=25000, currency="usd", destination="acct_xyz")

After TameFlare (Mode A):

# NEW: Agent requests permission, Gateway executes
result = TameFlare.request_action(
    action_type="payment.transfer.initiate",
    provider="stripe",
    account="acct_main",
    target="transfers",
    environment="production",
    parameters={"amount": 25000, "currency": "usd", "destination": "acct_xyz"},
    risk_hints={"financial_impact": True, "irreversible": True},
)
 
if result.status == "allowed":
    TameFlare.execute(result.action_request_id, result.decision_token)
elif result.status == "pending_approval":
    approved = TameFlare.wait_for_approval(result.action_request_id, timeout_seconds=300)
    if approved:
        TameFlare.execute(result.action_request_id, approved.decision_token)
elif result.status == "denied":
    print(f"Blocked: {result.decision.reason}")

After TameFlare (Mode B):

# NEW: Agent checks policy, then executes itself
result = TameFlare.request_action(
    action_type="payment.transfer.initiate",
    provider="stripe",
    account="acct_main",
    target="transfers",
    environment="production",
    parameters={"amount": 25000, "currency": "usd", "destination": "acct_xyz"},
)
 
if result.status == "allowed":
    # Agent still holds credentials
    stripe.Transfer.create(amount=25000, currency="usd", destination="acct_xyz")
elif result.status == "denied":
    print(f"Blocked: {result.decision.reason}")

LangChain

LangChain agents use tools to interact with external services. Wrap each tool with an TameFlare check.

Wrapping a LangChain tool

from langchain.tools import tool
from agentfirewall import AgentFirewall
 
TameFlare = AgentFirewall(api_key=os.environ["AAF_API_KEY"])
 
@tool
def create_github_issue(repo: str, title: str, body: str) -> str:
    """Create a GitHub issue. Checked by TameFlare policy engine."""
    result = TameFlare.request_action(
        action_type="github.issue.create",
        provider="github",
        account=repo.split("/")[0],
        target=repo,
        environment="production",
        parameters={"title": title, "body": body},
    )
 
    if result.status == "allowed":
        exec_result = TameFlare.execute(result.action_request_id, result.decision_token)
        return f"Issue created: {exec_result}"
    elif result.status == "denied":
        return f"Action blocked by policy: {result.decision.reason}"
    elif result.status == "pending_approval":
        return "Action requires human approval. Waiting..."

Using with an agent

from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
 
llm = ChatOpenAI(model="gpt-4o")
tools = [create_github_issue]  # TameFlare-wrapped tools
agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
 
result = executor.invoke({"input": "Create an issue about the login bug"})
Tip
The key insight: wrap the tool function, not the agent. The agent calls tools normally — the TameFlare check happens inside the tool before any external API call is made.

CrewAI

CrewAI agents use tools similarly to LangChain. Wrap each tool with an TameFlare check.

from crewai import Agent, Task, Crew
from crewai.tools import tool
from agentfirewall import AgentFirewall
 
TameFlare = AgentFirewall(api_key=os.environ["AAF_API_KEY"])
 
@tool("Send Email")
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email. Checked by TameFlare policy engine."""
    result = TameFlare.request_action(
        action_type="email.send",
        provider="sendgrid",
        account="main",
        target=to,
        environment="production",
        parameters={"to": to, "subject": subject, "body": body},
        risk_hints={"external_recipient": not to.endswith("@yourcompany.com")},
    )
 
    if result.status == "allowed":
        exec_result = TameFlare.execute(result.action_request_id, result.decision_token)
        return f"Email sent: {exec_result}"
    elif result.status == "denied":
        return f"Blocked by policy: {result.decision.reason}"
    elif result.status == "pending_approval":
        approved = TameFlare.wait_for_approval(result.action_request_id, timeout_seconds=120)
        if approved:
            TameFlare.execute(result.action_request_id, approved.decision_token)
            return "Email sent after approval"
        return "Email blocked — approval denied or timed out"
 
agent = Agent(
    role="Communications Manager",
    goal="Send project updates to stakeholders",
    tools=[send_email],
)

Raw HTTP

If you're not using an SDK, you can call the TameFlare API directly.

Request an action

curl -X POST http://localhost:3000/api/v1/actions \
  -H "Authorization: Bearer $AAF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action_spec": {
      "type": "github.pr.merge",
      "resource": {
        "provider": "github",
        "account": "acme",
        "target": "acme/api",
        "environment": "production"
      },
      "parameters": { "pull_number": 42 }
    }
  }'

Check the response

{
  "action_request_id": "act_abc123",
  "status": "allowed",
  "decision": {
    "outcome": "allow",
    "reason": "No deny rules matched",
    "risk_score": 0.3,
    "matched_policies": ["github-safe-defaults"]
  },
  "decision_token": "eyJhbGciOiJFUzI1NiIs..."
}

Execute (Mode A only)

curl -X POST http://localhost:3000/api/v1/actions/act_abc123/execute \
  -H "Authorization: Bearer $AAF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "decision_token": "eyJhbGciOiJFUzI1NiIs..." }'

Migrating credentials to the Gateway

To move from Mode B (policy-only) to Mode A (full enforcement):

Step 1: Start the Gateway

cd apps/gateway && go run ./cmd/gateway

Or via Docker:

docker compose up gateway

Step 2: Move credentials

Remove external API credentials from your agent's environment and add them to the Gateway:

| Credential | Move from | Move to | |---|---|---| | GitHub PAT | Agent's GITHUB_TOKEN | Dashboard > Settings > Integrations > GitHub PAT | | Slack token | Agent's SLACK_TOKEN | Dashboard > Settings > Integrations > Slack Bot Token | | Any HTTP API key | Agent's env vars | Gateway's env or use the webhook connector |

Note
Credentials stored in the dashboard are encrypted at rest with AES-256-GCM when SETTINGS_ENCRYPTION_KEY is configured. Always set this in production.

Step 3: Configure the Gateway URL

In the dashboard, go to Settings > Integrations and set the Gateway URL (default: http://localhost:8443).

Step 4: Switch your agent to Mode A

Replace direct API calls with TameFlare.execute() calls. The Gateway will use the credentials you moved in Step 2.

Step 5: Verify

  1. Run a test action from the dashboard sandbox
  2. Check the Connectors page — it should show "Gateway online" with your connectors listed
  3. Check the Audit Log for action.executed events

Using the webhook connector for any API

If the Gateway doesn't have a built-in connector for your tool, use the webhook connector. It sends HTTP requests to any URL:

{
  "action_spec": {
    "type": "webhook.post",
    "resource": {
      "provider": "webhook",
      "target": "https://api.example.com/v1/action"
    },
    "parameters": {
      "method": "POST",
      "headers": { "Authorization": "Bearer secret" },
      "body": { "key": "value" }
    }
  }
}

Recommended rollout

Proxy mode (v2)

| Step | Action | Enforcement | |---|---|---| | 1 | tf init, add connectors, set permissions | — | | 2 | tf run with monitor enforcement | All traffic forwarded, decisions logged | | 3 | Review traffic log for 1-2 weeks, tune permissions | Still monitor | | 4 | Switch to soft_enforce | Denies logged as would_deny, still forwarded | | 5 | Switch to full_enforce | Denied requests return 403 |

SDK mode (v1)

| Step | Action | Enforcement | |---|---|---| | 1 | Install TameFlare, register agent, install policy packs | — | | 2 | Integrate SDK, set L0 (Monitor) | Agent checks TameFlare but all actions allowed | | 3 | Review audit log, tune policies | Still L0 | | 4 | Switch to L1 (Soft enforce) | Denies logged as warnings | | 5 | Switch to L2 (Full enforce) | All decisions enforced |

Tip
Start with monitor mode to observe real agent behavior before enforcing. This prevents false positives that block legitimate work.

How framework interception works

TameFlare intercepts HTTP traffic at the transport layer, not the framework layer. This means it works with any agent framework without plugins or adapters.

Why this matters

| Approach | How it works | Limitation | |---|---|---| | Framework plugin | Hook into LangChain/CrewAI tool system | Only works with that framework. Requires code changes. | | SDK wrapper | Wrap each tool call with an TameFlare check | Requires code changes per tool. Agent can skip the check. | | Transport interception (TameFlare proxy) | Set HTTP_PROXY env var. All HTTP calls go through the proxy. | Works with everything. Cannot be bypassed by the agent. |

When you run tf run --name "Bot" python my_agent.py, TameFlare sets HTTP_PROXY and HTTPS_PROXY for the child process. Every HTTP library (Python requests, httpx, urllib3, Node.js fetch, axios, Go net/http) respects these variables automatically.

The agent's code doesn't change. The framework doesn't need to know about TameFlare. The proxy sees the raw HTTP request, matches it to a connector, parses the action type, checks permissions, and either forwards (with injected credentials) or blocks.

Framework-specific notes

| Framework | Proxy support | Notes | |---|---|---| | LangChain | Automatic | Uses requests/httpx which respect HTTP_PROXY | | CrewAI | Automatic | Uses requests under the hood | | n8n | Automatic | Node.js fetch/axios respect proxy env vars | | OpenClaw | Automatic | Standard HTTP client | | Claude Code | Automatic | Uses Node.js HTTP client | | Custom Python | Automatic | requests, httpx, urllib3 all respect proxy | | Custom Node.js | Automatic | fetch, axios, node-fetch all respect proxy | | Custom Go | Automatic | net/http respects HTTP_PROXY by default | | Shell scripts (curl) | Automatic | curl respects proxy env vars |


Multi-agent setup

Run multiple agents through the same gateway with different permissions.

Example: DevOps Bot + Finance Bot

# Add connectors (shared across agents)
npx tf connector add github --token-env GITHUB_TOKEN
npx tf connector add generic --domains api.stripe.com --token-env STRIPE_KEY
 
# DevOps Bot: full GitHub access, no Stripe
npx tf permissions set --gateway "DevOps Bot" --connector github \
    --action "github.*" --decision allow
npx tf permissions set --gateway "DevOps Bot" --connector github \
    --action "github.repo.delete" --decision deny
 
# Finance Bot: Stripe read-only, no GitHub
npx tf permissions set --gateway "Finance Bot" --connector generic \
    --action "generic.get" --decision allow
npx tf permissions set --gateway "Finance Bot" --connector generic \
    --action "generic.post" --decision require_approval
 
# Run each agent (each gets a dedicated proxy port)
npx tf run --name "DevOps Bot" python devops_agent.py &
npx tf run --name "Finance Bot" python finance_agent.py &

How agent isolation works

  • Each agent gets a dedicated proxy port allocated by the gateway
  • An agent can only see its own traffic logs and permissions
  • Agents cannot access each other's credentials or traffic
  • The dashboard shows all agents' activity (scoped by RBAC role)
  • The kill switch can target a specific agent: tf kill-switch --activate --scope "Finance Bot"

Naming conventions

Agent names are free-form strings. Recommendations:

  • Use descriptive names: "DevOps Bot", "PR Review Agent", "Invoice Processor"
  • Names are case-sensitive: "Bot" and "bot" are different agents
  • Names appear in audit logs, traffic logs, and approval requests

What to do when things go wrong

| Problem | Solution | |---|---| | Agent traffic not proxied | Ensure you used tf run — it sets HTTP_PROXY/HTTPS_PROXY env vars | | Agent gets 403 from proxy | Check tf logs for the denied action. Add a permission rule or switch to monitor mode | | Approval never resolves | Check tf approvals list. Approvals expire after 5 minutes | | Kill switch blocking everything | Run tf kill-switch deactivate or check dashboard Settings | | Agent gets 401 Unauthorized (SDK) | Check AAF_API_KEY is correct and the agent is active | | Agent gets 429 Too Many Requests (SDK) | Rate limit hit (60 req/min). The SDK auto-retries | | Gateway shows "offline" | Ensure gateway is running (tf status) or check the Gateway URL in Settings | | Approval never arrives in Slack | Check Slack integration in Settings (bot token, signing secret, channel ID) |

Next steps