Agenthog setup
You (the AI coding agent reading this skill) are being asked to install and configure AgentHog tracing in the user's project. Follow this skill end-to-end. Do NOT skip the verification step.
1. Detect the runtime
Look at the project root to determine the language and primary framework:
- Python if you see
pyproject.toml,requirements.txt,setup.py, or*.pyfiles. - TypeScript / Node if you see
package.jsonwith"type": "module"or TypeScript files, ortsconfig.json.
If both, default to whichever the primary entrypoint uses. Ask the user if genuinely ambiguous.
Identify the primary framework: FastAPI, Flask, Django, LangGraph, LangChain,
Express, Vercel AI SDK, Next.js, Hono, etc. The framework determines where
init belongs and where the per-request loop is.
2. Gather configuration
Two values are required from the user. The endpoint is auto-defaulted — do NOT ask the user about it unless one of the override conditions below fires.
Required: AGENTOS_API_KEY and AGENTOS_WORKSPACE_ID
These are secrets, resolved at runtime from the environment — the SDK reads
AGENTOS_API_KEY / AGENTOS_WORKSPACE_ID from env vars (see the init
examples below). Your job is to wire the code to those env vars and confirm the
values are present — never to obtain, see, or store the secret yourself.
Already in the environment? (
AGENTOS_API_KEY,AGENTOS_WORKSPACE_IDset) — good, nothing to gather. Wire the code to read them and move on.Not set? The user provides them — you never handle the secret. Offer two ways and prefer the first. A bare
exportis per-shell and dies when the terminal closes — a common cause of "it traced once, then stopped," and it's invisible to any other shell (including your own tool shell):Preferred —
agenthog init(agenthog >= 0.5.0): prompts for both values, writes a gitignored.env(0600), and persists across restarts and across shells. The user runs it and types the key; you do not. Because it writes.env, the values survive new terminals — no two-shells mismatch.Fallback — shell
export(ephemeral, fine for a quick one-off): the user sets the values in the terminal where they'll run the app:export AGENTOS_API_KEY=<their key> export AGENTOS_WORKSPACE_ID=<their workspace id>The quickstart page gives a ready-to-paste
exportblock. This lasts only for that shell session — recommendagenthog initfor anything persistent.
Get values from https://app.theagentos.space (API key: Settings → API Keys; workspace ID: sidebar chip / top-right). Once the user has done one of these, confirm presence by name (below) and continue.
Confirm presence without ever reading the secret:
Check that the vars are set, never their value — a boolean presence test,
never cat .env or echoing the key into your context:
# prints only yes/no; resolves env first, then a gitignored .env the app loads
python3 -c "import os; print('AGENTOS_API_KEY set:', bool(os.getenv('AGENTOS_API_KEY')))"
If it prints False, the user hasn't completed agenthog init / export yet —
ask them to finish, don't go hunting for the value.
Credential safety (do not deviate):
- Never accept the API key pasted into the chat. If a user offers to paste
it, decline and tell them to run
agenthog init(orexportit) instead — you referenceAGENTOS_API_KEYby name, never the value. - Never search the filesystem or read files hunting for a key, and never
open
.envto read the secret. Confirm presence by env-var name only (above). - Never hardcode the key in source, and never print or echo it. The
code references
AGENTOS_API_KEYvia the environment, never a literal. - The agent never writes the plaintext secret to disk.
agenthog init(driven by the user typing their key) or the user does that. You may create or update.env.examplewith a placeholder (Step 6); the real value is never yours to handle.
Endpoint: default silently
AGENTOS_ENDPOINT resolves in this order without prompting the user:
- If
AGENTOS_ENDPOINTis already set in the environment, use that. - Otherwise, use the default:
https://api.theagentos.space.
Only prompt the user about the endpoint if you have positive evidence they want to override it — e.g. they said something like "I'm self-hosting" or "we run agenthog on our own infra" in their request.
Otherwise: pick the default and move on. Asking for the endpoint on every quickstart is annoying friction for the 95% who use the hosted cloud.
Never commit the plaintext API key, and don't write it to disk yourself —
the user places the real value (Step 6 wires the code + the .env.example
placeholder).
3. Install the SDK (a new-enough version is mandatory)
Minimum supported version: Python
agenthog >= 0.9.0, TypeScriptagenthog >= 0.3.0. The ingestion API rejects events from older SDKs (HTTP 426). The Python floor is 0.9.0 because this skill depends on its features end-to-end:agenthog initcredentials (0.5.0), raw-HTTP LLM capture for OpenAI-compatible endpoints + automatic flush-on-exit (0.6.0), the@agenthog.tool/@traceabledecorator for tracing the agent's tools (0.7.0), and one-call tool captureinstrument_module(Step 5b, 0.9.0) — which guarantees no tool is silently missed. Below the floor, setup produces incomplete traces (missing tool steps, or none at all from a short-lived process).
Install (or upgrade) agenthog as part of setup, using the project's existing
package manager — the same one already managing its dependencies. Tell the user
what you're adding, and pin the floor so an existing older install is bumped
rather than left as-is. If you can't determine the package manager, ask before
installing.
Python
Detect the package manager (uv, poetry, pip, pipenv) and install with the
version floor + upgrade:
# uv (uv add always resolves to the newest allowed; the floor guarantees ≥0.9.0)
uv add 'agenthog>=0.9.0'
# poetry
poetry add 'agenthog@>=0.9.0'
# pip (-U upgrades an already-installed older copy)
pip install -U 'agenthog>=0.9.0'
For frameworks with first-class auto-instrumentation, install the extras:
# OpenAI calls
uv add 'agenthog[openai]>=0.9.0'
# Anthropic
uv add 'agenthog[anthropic]>=0.9.0'
# LangChain / LangGraph
uv add 'agenthog[langchain]>=0.9.0'
# OpenTelemetry passthrough
uv add 'agenthog[otel]>=0.9.0'
TypeScript
# pnpm (preferred)
pnpm add agenthog@latest
# npm
npm install agenthog@latest
# yarn
yarn add agenthog@latest
# bun
bun add agenthog@latest
Auto-instrumentation in TS is wired via the integrations import — no extras
required at install time. See reference/typescript.md.
Verify the installed version satisfies the floor (do not skip)
After installing, confirm the version (Python >= 0.9.0, TypeScript >= 0.3.0).
If it isn't, upgrade and re-check before continuing — the rest of the setup (and
the ingestion API) assumes it.
# Python — prints the installed version; exits non-zero if below the floor.
python -c "import agenthog, sys; from importlib.metadata import version; \
v=version('agenthog'); print('agenthog', v); \
sys.exit(0 if tuple(map(int, v.split('.')[:2])) >= (0,9) else 1)"
# TypeScript — same idea.
node -e "const v=require('agenthog/package.json').version; const [a,b]=v.split('.').map(Number); \
console.log('agenthog', v); process.exit((a>0||b>=3)?0:1)"
If the check reports a version below the floor (Python 0.9.0, TypeScript
0.3.0), upgrade it the same way you installed it (the package manager's
upgrade for agenthog) and re-run the check. If the floor still can't be met
(e.g. the registry hasn't published it yet), stop and tell the user rather
than proceeding on an unsupported version — their telemetry would be rejected at
ingest, or the trace would be missing tool steps.
4. Wire init at app startup
The init call must run once, before any LLM/tool call you want traced.
Place it in the app's primary entrypoint:
| Framework | Where to call init |
|---|---|
| FastAPI | main.py, before app = FastAPI() |
| Flask | app.py, before app = Flask(__name__) |
| Django | settings.py bottom, or an apps.py ready() hook |
| LangGraph script | top of the script, before graph construction |
| Express | index.ts/server.ts, before app.listen |
| Next.js | instrumentation.ts (Next.js's official OTel/tracing hook) |
| Vercel AI SDK | wherever the AI client is constructed |
Python init pattern
import os
import agenthog
agenthog.init(
api_key=os.environ["AGENTOS_API_KEY"],
endpoint=os.environ.get("AGENTOS_ENDPOINT", "https://api.theagentos.space"),
workspace_id=os.environ["AGENTOS_WORKSPACE_ID"],
agent_id="<descriptive-agent-name>", # pick based on what this service is
)
# Auto-instrument supported LLM / framework libraries. Detects installed
# packages (openai, anthropic, langchain) and patches them. As of
# agenthog >= 0.6.0 it ALSO captures raw-HTTP calls to OpenAI-compatible
# /chat/completions endpoints made via `requests` or `httpx` — so agents that
# POST directly to NVIDIA, Together, Groq, OpenRouter, a local vLLM/Ollama,
# etc. (instead of through a vendor SDK) get traced with no code change.
agenthog.autoinstrument()
Provider coverage.
autoinstrument()(>= 0.6.0) covers the vendor SDKs and raw-HTTP OpenAI-compatible calls. If the agent talks to a custom or non-OpenAI-shaped endpoint that neither path recognizes, the LLM call won't be auto-captured — log it explicitly withagenthog.emit("agent.llm_call", {...})(orlog_tool_callfor tools, Step 5b). When verifying (Step 7), confirm the trace actually shows the LLM step, not just an emptytask_run.
Flushing on exit is automatic in agenthog >= 0.6.0:
init()registers anatexit+SIGTERMdrain, so short-lived CLIs / scripts deliver their buffer without a manualflush(). Only a hard kill (SIGKILL,os._exit) bypasses it — callagenthog.shutdown()before such an exit if you have one.
TypeScript init pattern
import { init } from "agenthog";
init({
apiKey: process.env.AGENTOS_API_KEY!,
endpoint: process.env.AGENTOS_ENDPOINT ?? "https://api.theagentos.space",
workspaceId: process.env.AGENTOS_WORKSPACE_ID!,
agentId: "<descriptive-agent-name>",
});
// Auto-instrumentation is per-integration in TS. Import the wrapper for the
// LLM client you use. Examples:
// import "agenthog/integrations/openai";
// import "agenthog/integrations/anthropic";
// import "agenthog/integrations/vercelAI";
// import "agenthog/integrations/langchain";
Pick a agentId that describes what the service does, not just the repo
name. Examples: customer-support-bot, code-review-agent, etl-pipeline.
5. Wrap the primary agent loop in start_task_run
One task_run = one end-to-end execution of the agent (one user message → one
final response, or one cron trigger → one job complete). Spans nest inside it.
Python — context manager
with agenthog.start_task_run(user_id=user_id) as ctx:
# Auto-instrumented LLM calls in here are traced under one task_run_id.
# Tool calls are NOT auto-traced unless they go through a framework
# (LangChain/LangGraph) — see Step 5b for hand-written tool loops.
result = my_agent.run(user_input)
return result
Available kwargs: agent_id, task_run_id, trace_id, user_id,
session_id. There is no metadata= kwarg — attach attribution via
the explicit kwargs above instead.
For an HTTP handler (FastAPI/Flask), wrap the handler body:
@app.post("/chat")
async def chat(req: ChatRequest):
with agenthog.start_task_run(user_id=req.user_id):
return await my_agent.run(req.message)
TypeScript — callback
import { startTaskRun } from "agenthog";
app.post("/chat", async (req, res) => {
await startTaskRun({ userId: req.body.userId }, async () => {
const out = await myAgent.run(req.body.message);
res.json(out);
});
});
Available StartTaskRunArgs fields: agentId, taskRunId, traceId,
userId, sessionId. No metadata field.
If the user's framework has a middleware concept (Express, Hono, FastAPI
middleware), prefer wiring start_task_run as a middleware so every request
gets a task_run automatically.
5b. Instrument the agent's tools (the full execution tree)
autoinstrument() captures LLM calls (vendor SDKs + raw-HTTP
OpenAI-compatible calls) — but not the agent's tools: ordinary functions
that hit an API, query a DB, geocode, run a computation. Left alone, the trace
shows only the LLM step, and evals that look for tool usage wrongly report "the
agent never used a tool / hallucinated." This is the same boundary in every
tracer — Arize/LangSmith auto-trace the full tree only on instrumented
frameworks; for hand-written tools they require an annotation too.
This step is what produces the full workflow tree (tool calls + LLM call), not just the model step. Do it whenever the agent has tool/helper functions.
Preferred — one call over the tools module (Python, agenthog >= 0.9.0)
Point instrument_module at the module holding the agent's tool functions. It
wraps every public function defined there as an agent.tool_call step in a
single call — so no tool can be silently missed the way per-function decorating
can. It returns the list of wrapped names; keep that list for the Step 7
count check.
import agenthog
import weather # the module with the agent's tool functions
agenthog.init(...)
wrapped = agenthog.instrument_module(weather)
# wrapped == ['get_weather', 'get_air_quality', 'geocode']
It skips private helpers (leading _), imported names, classes, and constants.
Tune the selection when the defaults don't fit:
- A public function that isn't a tool →
instrument_module(weather, exclude=["build_prompt"]). - A tool named with a leading
_, or one that lives among imports →instrument_module(weather, include=["_geocode"]).
Functions you already decorated with @agenthog.tool are detected and not
double-wrapped. Enumerate the agent's tool module(s) and pass each through
one instrument_module call — this is what makes "all tools captured"
structural rather than a per-function judgment you can under-apply.
When tools are spread across many files, or you want per-function control — decorate each
Add @agenthog.tool to each function the agent uses as a tool. One line each;
captures args → tool.input, return → tool.output, plus duration / status /
error, as an agent.tool_call step under the active task_run. Works on sync
and async functions; safe to leave in place before init(). Decorate every
tool — a missed one is silently absent from the trace (exactly what
instrument_module prevents).
import agenthog
@agenthog.tool
def get_weather(city: str) -> dict | None:
...
@agenthog.tool(name="geocode") # custom step name
def _geocode(city: str) -> tuple[float, float] | None:
...
TypeScript has no
@tooldecorator orinstrument_moduleyet (JS SDK is pre-0.6.0; bulk capture, the decorator, raw-HTTP capture, and auto-flush-on-exit are Python-only for now). For Node/TS agents, instrument tools with the explicitlogToolCallpath in the Alternative section below, and be aware the rest of the auto-capture story is thinner than Python until the JS SDK catches up.
Identify the tool functions — typically the helpers the entrypoint calls
before/around the LLM call (data fetches, lookups, actions). Either path should
produce a multi-step trace like
task_run → get_weather → geocode → get_air_quality → llm_call.
Alternative — log explicitly (older SDKs, or a raw tool-calling loop)
If you can't decorate (e.g. tools dispatched dynamically in a model
tool-calling loop, or the TS SDK which has neither instrument_module nor
@tool), log each call:
result = run_tool(name, **args)
agenthog.get_default_client().log_tool_call(name=name, input=args, output=result)
const result = await runTool(name, args);
await getDefaultClient()!.logToolCall({ name, input: args, output: result });
log_tool_call / logToolCall take name plus optional input, output,
status, duration_ms, error. Skip tool instrumentation only when every tool
already runs through an auto-instrumented framework.
6. Document the config (placeholders only — never the real key)
Add three placeholder lines to .env.example (or create it). This file is
committed, so it must never contain a real key:
AGENTOS_API_KEY=agops_replace_me
AGENTOS_WORKSPACE_ID=ws_replace_me
AGENTOS_ENDPOINT=https://api.theagentos.space
For the real values:
- Python (used
agenthog initin Step 2): the gitignored.envis already written and ignored — nothing more to do here except the committed.env.exampleplaceholders above (for teammates). - Otherwise: tell the user to set the real values themselves — in their
shell environment, secret manager, or a gitignored
.env. Do not write the plaintext key to any file on their behalf. If they use a.env, confirm it's listed in.gitignore(add it if missing) so the secret can't be committed.
Make the .env actually load (do not skip)
A .env file does not populate os.environ / process.env on its own.
The SDK reads the keys from the environment at runtime (Step 4), so if the app
doesn't load .env the user will set everything correctly and still see no
trace with an empty/missing key. Wire the load step to match the runtime:
| Runtime | How .env reaches the environment |
|---|---|
| Next.js | Automatic — Next loads .env / .env.local. Nothing to add. |
| Node (non-Next) | Run with node --env-file=.env … (Node 20+), or add import "dotenv/config"; as the first import. |
| Python — FastAPI / Flask / script | Add from dotenv import load_dotenv; load_dotenv() before agenthog.init(...) (add the python-dotenv dep), or use pydantic-settings, or run via uv run --env-file .env …. |
| Any runtime | Or just export in the shell before launching: export AGENTOS_API_KEY=… AGENTOS_WORKSPACE_ID=…. |
Pick the one matching the project. If you add load_dotenv(), place it at the
top of the entrypoint, before init runs. Confirm the var is actually populated
(Step 7's "Confirm AGENTOS_API_KEY is loaded" check) before declaring done.
7. Verify (hard gate — do not skip, do not assume)
This step is mandatory and blocking. Setup is not done until you have
seen a real trace from a real run. Wiring init + start_task_run does not by
itself produce a trace: if no LLM/tool call is captured, the trace is an empty
task_run and nothing useful appears. Never report success because the code
"looks wired up" — only because you observed the trace.
Check the trace is complete, not just present. It should contain the LLM
call and a tool_call step for each tool the agent used (Step 5b).
Count them: the number of distinct tool_call steps must equal the number
of tools instrumented — the length of the list instrument_module returned, or
the number of functions you decorated with @agenthog.tool. Fewer means a tool
was missed (or an execution path didn't run it); a trace with only the
llm_call and no tool steps means the tools weren't instrumented at all — go
back to Step 5b. Do not pass this gate on a count mismatch. The goal is the full
execution tree, the way Arize/LangSmith show it.
Run the app. Exercise one request that hits the wrapped handler. Then verify via one of these paths, in order of simplicity:
Path A — Dashboard (always works)
Open in a browser:
https://app.theagentos.space/traces
Sign in if needed. A trace from the request you just exercised should appear within a few seconds. Use this path by default.
Path B — CLI verifier (offline, cryptographic)
The agenthog CLI ships as an installed entrypoint, NOT as python -m agenthog. There is no __main__.py. Invoke the entrypoint directly:
# If agenthog[audit] is installed in the active env:
agenthog audit verify --workspace-id $AGENTOS_WORKSPACE_ID
# Or, ephemerally without modifying the project's deps:
uvx --with 'agenthog[audit]' agenthog audit verify --workspace-id $AGENTOS_WORKSPACE_ID
# Or, with uv run from outside any project (mirrors `uv run --no-project`):
uv run --no-project --with 'agenthog[audit]' agenthog audit verify \
--workspace-id $AGENTOS_WORKSPACE_ID
Do not run python -m agenthog audit verify — that fails with
'agenthog' is a package and cannot be directly executed. The CLI is
invoked via the agenthog console-script entrypoint, not the module
runner.
Note: agenthog audit verify requires the [audit] extra (pulls in
cryptography for Ed25519 verification). If you only installed
agenthog[openai] for tracing, add [audit] for verification or use
Path A.
If no trace appears after 30 seconds
- Check the app's stdout for
[agenthog.sdk]warning lines. - Confirm
AGENTOS_API_KEYis loaded (not empty, not the placeholder). - Confirm the endpoint is reachable:
curl -I $AGENTOS_ENDPOINT. - Confirm the app actually called
initbefore the handler runs (sometimesinitis wired after the handler import — fix the import order soinitruns first at module load). - Trace appears but is empty (no LLM step)? The model call wasn't
captured.
autoinstrument()covers the vendor SDKs and — on agenthog >= 0.6.0 — raw-HTTP OpenAI-compatible/chat/completionscalls. A custom/non-OpenAI-shaped endpoint won't be auto-traced: log it withagenthog.emit("agent.llm_call", {...}). (On older SDKs, rawrequests/httpxcalls are never captured — upgrade to >= 0.6.0 or switch to the OpenAI SDK with a custombase_url.) - Short-lived script and nothing arrives? On agenthog >= 0.6.0 the buffer
flushes on exit automatically; on older versions add
agenthog.flush()before the process exits, or upgrade.
8. Report back
Once a trace is verified visible, tell the user:
- What you installed (which extras)
- Where you put the
initcall (file + line) - Where you wrapped the loop (file + line)
- The trace_id of your verification trace (so they can open it directly)
Rules
- Never throw. The user's agent must keep running even if AgentHog is misconfigured. The SDK guarantees its own calls don't raise; your wiring should match that contract.
- Credentials stay in the environment. The API key is read from an env var at runtime. Never hardcode it, never print or echo it, never write the plaintext value to a file, and never scan the filesystem for it. Wire the code to the env var and let the user place the secret (Step 2).
- Don't auto-instrument secrets. If the user has prompts containing PII
or API keys, mention the privacy controls (
capture_content=False) in the report — don't change the default silently. - Don't refactor unrelated code. Only touch what's necessary to wire tracing. If the user wants broader observability work, let them ask explicitly.
- Use uv if it's already in the project. Otherwise use whatever package manager the project already uses. Don't introduce a new one.