Documentation

Quick start

Install

terminal
pip install farol-sdk

Run this in your terminal in the same environment where your agent runs. If you're using a virtual environment, make sure it's activated first.

Not sure what a virtual environment is? Skip this and just run pip install farol-sdk directly in your terminal.

πŸ’‘ Using a venv?

python -m venv venv
source venv/bin/activate  # Mac/Linux
venv\Scripts\activate     # Windows
pip install farol-sdk

Get your API key

Copy your API key from your Farol dashboard. You'll find it at the top of the page after signing in.

Get your API key β†’

Wrap your agent

Replace the API key placeholder with your real key from Step 2, and give your agent a name. Everything else can stay as-is.
from farol import trace
from anthropic import Anthropic

client = Anthropic()

@trace(agent_name="my-agent", farol_key="frl_your_key_here")
def my_agent(task, *, run):
    run["topic"] = task

    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": task}]
    )
    run["input_tokens"] = response.usage.input_tokens
    run["output_tokens"] = response.usage.output_tokens
    return response.content[0].text

Run your agent normally

your agent file
# Just call your function as usual β€” Farol tracks everything automatically
result = my_agent("your task here")

Open your Farol dashboard β€” you'll see the run appear within seconds, with cost, duration, token usage and any errors automatically tracked.

See it in your dashboard

Curious what it looks like? See a live example with real data.

View live demo β†’

Webhook integrations

On Builder plans and above (see pricing), you can add a webhook URL in Account settings. Farol sends a JSON POST when a cost anomaly is detected. Use the steps below to wire common destinations.

Farol sends this JSON payload to your webhook URL when a cost anomaly is detected:

webhook payload
{
  "event": "cost_anomaly",
  "agent": "research-agent",
  "topic": "Weekly data pipeline run",
  "reason": "Cost 3.2Γ— above median baseline (median: $0.000078, actual: $0.000248)",
  "cost_usd": 0.000248,
  "timestamp": "2026-04-12T10:00:00.000Z",
  "dashboard": "https://usefarol.dev/app"
}

Farol expects a 2xx response. Failed deliveries are logged in Account settings.

Discord
  • Open your Discord server and go to Server Settings (click the server name at the top left).
  • Navigate to Integrations β†’ Webhooks β†’ New Webhook.
  • Choose a channel, give it a name, then click Copy Webhook URL.
  • Paste the URL in Farol Settings β†’ Webhook section and click Save.
Google Chat
  • Open Google Chat and go to the Space where you want alerts (Spaces are group channels).
  • Click the Space name at the top β†’ Apps & Integrations β†’ Add webhooks.
  • Give it a name, click Save, then copy the webhook URL.
  • Paste the URL in Farol Settings β†’ Webhook section and click Save.
Telegram

Telegram requires a custom payload format that differs from Farol's standard JSON. The easiest way to connect is via Zapier or Make (see below) β€” create a Catch Hook, then add a step to send a Telegram message using the bot action.

You'll still need a Telegram bot β€” create one with @BotFather to get your token, then use it in Zapier/Make.

Zapier / Make

A Catch Hook is a trigger that gives you a unique URL. When Farol sends data to that URL, your Zap or scenario runs automatically.

  • Create a Catch Hook trigger in Zapier or Make.
  • Copy the webhook URL and paste it in Farol Settings.
  • For example: receive a Farol alert β†’ send a Telegram message, create a Notion entry, or page your team in PagerDuty.

SDK reference

Node.js SDK β€” Install with npm install @usefarol/sdk. Full TypeScript support. See npmjs.com/package/@usefarol/sdk.

Python’s trace decorator injects a run dict for tokens, timing, and optional steps. The Node SDK’s trace() wraps an async function and passes a Run object with startSpan (see below).

Python @trace parameters

ParameterDescription
agent_name Required. Display name for this agent in the dashboard.
farol_key Optional. API key from the dashboard; when set (and Supabase extras + env are configured), runs sync to your account.
model Optional. Model label stored on the run. Default "claude-haiku-4-5-20251001".
cost_per_1k_input_tokens Optional. USD per 1k input (prompt) tokens. Default 0.00025 (Claude Haiku).
cost_per_1k_output_tokens Optional. USD per 1k output (completion) tokens. Default 0.00125 (Claude Haiku).
prompt_version Optional. Tag this run with a prompt version label (e.g. "v2", "after_refactor"). Max 50 characters. Shown in the runs table and trace modal.
parent_trace_id Optional. Pass to @trace(..., parent_trace_id="…") to store a fixed parent link on every invocation. If omitted, the parent id is taken from the current trace context when a traced function calls another traced function and propagate is True.
propagate Optional. When True (default), inherit parent_trace_id from the current trace context (Python contextvars). Set False to disable.
sample_rate Optional. Fraction of successful runs to send (0.0–1.0). Errors always sent. Default 1.0.

Node.js trace() options

OptionDescription
agentName Required. Display name for this agent in the dashboard.
farolKey Required. API key from the dashboard; ties runs to your account.
model Optional. Model label for display. Default "unknown".
costPer1kInputTokens Optional. USD per 1k input tokens. Default 0.00025 (Claude Haiku).
costPer1kOutputTokens Optional. USD per 1k output tokens. Default 0.00125 (Claude Haiku).
farolEndpoint Optional. Custom ingest URL. Only change if self-hosting. Never point to an untrusted URL.
captureIo Optional. Store prompt inputs and outputs. Default false. Handle with care.
sampleRate Optional. Fraction of successful runs to send (0.0–1.0). Errors always sent. Default 1.0.
promptVersion Optional. Tag this run with a prompt version label. Max 50 characters.
parentTraceId Optional. Link this run to a parent agent run. Usually omitted β€” parent ID is set automatically when a traced function awaits another traced function. Set explicitly to force a parent; that value overrides automatic propagation.
propagate Optional. When true (default), inherit parent ID from AsyncLocalStorage context. Set false to disable.
example β€” custom pricing
# Prices vary by model β€” check your provider's pricing page
@trace(
    agent_name="my-agent",
    farol_key="frl_your_key_here",
    cost_per_1k_input_tokens=0.00025,   # Claude Haiku input price
    cost_per_1k_output_tokens=0.00125,  # Claude Haiku output price
)

Spans β€” Node.js (run.startSpan)

Use run.startSpan(name, { type, metadata }) for tool calls, LLM calls, and other steps. Call span.end() (or span.end(err)) when finished.

example β€” tool and LLM spans (Node)
const search = run.startSpan('web_search', { type: 'tool', metadata: { url } });
const result = await searchWeb(url);
search.end();

const llm = run.startSpan('llm_call', { type: 'llm' });
try {
  const response = await llmClient.call(prompt);
  llm.inputTokens = response.usage.input_tokens;
  llm.outputTokens = response.usage.output_tokens;
  llm.input = prompt;       // only sent when captureIo: true
  llm.output = response.text;
  llm.end();
} catch (e) {
  llm.end(e instanceof Error ? e : new Error(String(e)));
  throw e;
}
FieldDescription
name Required (first argument to startSpan). Label for this span in the trace timeline.
type Optional. "tool" or "llm". Default "tool".
metadata Optional. Object of extra info shown in the trace panel.
span.inputTokens / outputTokens Token counts (LLM spans).
span.input Prompt text β€” only included in the payload when captureIo: true.
span.output Response text β€” only included when captureIo: true.
span.error Set when you call span.end(error).

Note: Farol currently supports sequential spans best. Parallel or concurrent spans may not display in the correct order in the trace timeline. Full parallel span support is on the roadmap.

Python β€” run dict (no run.span)

The Python package passes run as a plain dict. There is no run.span() helper and no capture_io / ingest-url options on @trace. Set run["input_tokens"], run["output_tokens"], run["topic"], etc., and optionally append step summaries to run["steps"] (list of objects, e.g. {"step": "llm_call"}). Use the Node SDK or OpenTelemetry if you need per-span rows in the dashboard.

example β€” tokens + steps (Python)
@trace(agent_name="my-agent", farol_key="frl_your_key_here")
def my_agent(task, *, run):
    run["topic"] = task
    response = llm.call(task)
    run["input_tokens"] = response.usage.input_tokens
    run["output_tokens"] = response.usage.output_tokens
    run["steps"].append({"step": "llm_call"})
    return response.text

Multi-agent tracing

Child agent runs are linked to their parent automatically. When a traced function calls another traced function on the same thread (Python) or within the same async chain (Node), the child run’s parent_trace_id is set from the parent run’s ID so you can reconstruct the full pipeline in Farol. You can still pass parent_trace_id on the decorator to force a specific parent when needed.

example β€” parent and child agents
@trace(agent_name="research-agent", farol_key="frl_...")
def research_agent(topic, *, run):
    run["topic"] = topic
    # calling market_agent inside here automatically links it
    result = market_agent(topic)
    return result


@trace(agent_name="market-agent", farol_key="frl_...")
def market_agent(topic, *, run):
    run["topic"] = topic
    # parent_trace_id is set automatically
    # do market analysis...

Auto-propagation uses Python’s contextvars (async-safe). Set propagate=False on @trace to disable. Manual parent_trace_id always takes precedence.

In the dashboard, parent runs show their spawned children and total pipeline cost. Child runs show a link back to the parent.

Framework integrations

Farol works with any Python or Node.js framework β€” wrap your agent’s entrypoint function with @trace and Farol tracks everything automatically. No special integration required.

LangChain observability

Wrap the function that invokes your LangChain chain or agent:

python β€” langchain
from farol import trace
from langchain.chains import LLMChain

@trace(agent_name="langchain-agent", farol_key="frl_your_key_here")
def run_chain(query, *, run):
    run["topic"] = query

    result = chain.invoke({"query": query})
    run["steps"].append({"step": "chain_invoke"})
    return result

result = run_chain("summarise last quarter earnings")

CrewAI monitoring

Wrap the crew kickoff to track the entire crew run as one traced agent:

python β€” crewai
from farol import trace
from crewai import Crew

@trace(agent_name="crewai-agent", farol_key="frl_your_key_here")
def run_crew(topic, *, run):
    run["topic"] = topic

    result = crew.kickoff(inputs={"topic": topic})
    run["steps"].append({"step": "crew_kickoff"})
    return result

result = run_crew("market analysis for Q2 2026")

Vercel AI SDK (Node.js)

Wrap the function that calls generateText or streamText:

typescript β€” vercel ai sdk
import { trace } from '@usefarol/sdk';
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const runAgent = trace(
  async (run, prompt: string) => {
    run.topic = prompt;

    const span = run.startSpan('generate', { type: 'llm' });
    try {
      const { text, usage } = await generateText({
        model: anthropic('claude-haiku-4-5'),
        prompt,
      });
      span.inputTokens = usage.promptTokens;
      span.outputTokens = usage.completionTokens;
      span.end();
      return text;
    } catch (e) {
      span.end(e instanceof Error ? e : new Error(String(e)));
      throw e;
    }
  },
  { agentName: 'vercel-ai-agent', farolKey: 'frl_your_key_here' }
);

Cursor SDK (Node.js)

Wrap Cursor agent runs to track cost, duration, and errors automatically:

typescript β€” cursor sdk
import { trace } from '@usefarol/sdk';
import { CursorClient } from '@cursor/sdk';

const client = new CursorClient({ apiKey: process.env.CURSOR_API_KEY });

const runAgent = trace(
  async (run, prompt: string) => {
    run.topic = prompt;

    const span = run.startSpan('cursor_agent', { type: 'tool' });
    try {
      const result = await client.agent.run({ prompt });
      span.end();
      return result;
    } catch (e) {
      span.end(e instanceof Error ? e : new Error(String(e)));
      throw e;
    }
  },
  { agentName: 'cursor-agent', farolKey: 'frl_your_key_here' }
);

await runAgent('scaffold a new Express API with TypeScript');
Note: Cursor SDK is in early access. Check the Cursor SDK docs for the latest API.

Works with any framework

If your agent is a Python or Node.js function, wrap it with @trace (Python) or trace() (Node). This includes AutoGen, Haystack, LlamaIndex, smolagents, and custom loops. In Python, update the run dict with tokens and optional steps; in Node, use run.startSpan() for a per-span timeline.

OpenTelemetry integration Beta

If you already use OpenTelemetry, point your OTLP/HTTP exporter at Farol β€” no SDK changes required.

Python β€” opentelemetry-sdk

from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor

exporter = OTLPSpanExporter(
    endpoint="https://usefarol.dev/otel",
    headers={"x-farol-key": "frl_your_key_here"}
)
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

Node.js β€” @opentelemetry/sdk-node

import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

const exporter = new OTLPTraceExporter({
  url: 'https://usefarol.dev/otel',
  headers: { 'x-farol-key': 'frl_your_key_here' },
});
Beta: OTLP/HTTP JSON only. gRPC and Protobuf encoding are not yet supported. Farol maps service.name β†’ agent name, gen_ai.* attributes β†’ token counts. Use farol.* attributes for full control. See roadmap β†’

Dashboard

The dashboard has four sections:

  • Overview β€” KPI cards (total runs, success rate, avg duration with p95, total cost) and 4 charts. Filter by period (7/30/90 days) and agent.
  • Runs β€” full runs table. Columns: Agent, Topic, Model, Status, Quality, Anomaly, Steps, Duration, Tokens, Cost, Error, Timestamp. Click any row to open the trace timeline showing all spans. Search by topic, filter by agent, export to CSV (Starter+). Quality ratings act as lightweight evals β€” rate outputs directly from the trace modal and track trends over time.
  • Agents β€” one card per agent showing total runs, success rate, avg cost, avg duration, last run. Click a card to open a detailed modal with charts and runs filtered to that agent.
  • Alerts β€” seven alert types: cost anomaly, duration anomaly, p95 latency, regression, quality trend, budget, and proactive trend alerts. Grouped alerts show when multiple agents are affected simultaneously. Clicking an alert opens the trace or filters runs to the affected agent.

FAQ

Does Farol store my prompts?

By default only run metadata is stored (timing, tokens, cost, errors). The Node SDK can optionally send span prompt/response text when you set captureIo: true on trace() and assign span.input / span.output; values are truncated at 50,000 characters. The Python @trace decorator does not collect prompt bodies.

Is Farol secure?

Yes. All data is encrypted in transit (TLS 1.3) and at rest (AES-256). Prompts are never stored by default β€” only run metadata is tracked. Data is hosted in EU infrastructure (AWS Paris). See our Security page for full details.

How do I track multiple steps in my agent?

For Node, use run.startSpan() so each span appears in the trace timeline (see Spans). For Python, append entries to run["steps"] for coarse step labels on the run, or use OpenTelemetry / the Node SDK for full span rows.

Which frameworks does Farol support?

Any Python code path: the SDK is a decorator. LangChain, CrewAI, custom loopsβ€”wrap the entrypoint and update run with token counts (and optional steps) however your stack exposes them.

Does Farol support OpenTelemetry?

Yes β€” in beta. Point an OTLP/HTTP JSON exporter at https://usefarol.dev/otel with your Farol API key in the x-farol-key header. gRPC and Protobuf encodings are not supported yet. See OpenTelemetry integration and the roadmap.

Does Farol have an evals system?

Farol includes lightweight evals via quality scoring β€” rate any agent output thumbs up or down directly from the trace modal. Farol tracks quality trends over time and alerts you when output quality degrades week over week. A structured evals system with custom rubrics and automated scoring is on the roadmap. View the roadmap β†’

What counts as an event for billing?

Each span inside a run counts as one event. A run with 3 spans (e.g. 2 tool calls + 1 LLM call) counts as 3 events. A run with no spans counts as 1 event.

Which Python versions does Farol support?

Farol SDK supports Python 3.9+ (requires-python in the package metadata).

How does cost anomaly detection work?

After enough successful runs for the same agent, Farol compares the current cost to a rolling baseline (median and spread). Large jumpsβ€”relative to that baselineβ€”flag as anomalies so you catch bill spikes early.

Why am I not seeing alerts yet?

Farol needs enough run history to establish a reliable baseline before firing alerts. Minimum requirements per alert type:

  • Cost anomaly β€” 10+ successful runs for the same agent
  • Duration anomaly β€” 10+ successful runs for the same agent
  • Regression alerts β€” 10+ runs in both the current and previous 7-day window
  • Quality trend alerts β€” 5+ rated runs in both the current and previous 7-day window
  • p95 latency alerts β€” 20+ successful runs in both windows
  • Proactive trend alerts β€” 20+ runs in both windows

Budget alerts fire immediately when the monthly limit is crossed β€” no minimum runs needed.

Can I use Farol without a Farol key?

The decorator runs without a key, but runs are not tied to your dashboard and are not synced under your account. Pass farol_key from the app to attribute runs to you.

How long does Farol store my data?

Run data, traces, and spans are stored for as long as your account is active. There are no automatic expiry windows β€” your data stays available for historical analysis. Deleting your account permanently removes all runs, traces, and associated data. You can delete your account at any time from Account settings.