Documentation
Quick start
Install
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
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
# 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:
{
"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
| Parameter | Description |
|---|---|
| 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
| Option | Description |
|---|---|
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. |
# 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.
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;
}
| Field | Description |
|---|---|
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.
@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.
@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:
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:
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:
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:
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');
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' },
});
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.