Exploring Llm Traces logo

Exploring Llm Traces

Organization
PostHog
exploring-llm-traces

Debug and inspect LLM/AI agent traces using PostHog's MCP tools. Use when the user pastes a trace or session URL (e.g. /ai-observability/traces/<id> or /ai-observability/sessions/<id>), asks to debug a trace, figure out what went wrong, check if an agent used a tool correctly, verify context/files were surfaced, inspect subagent behavior, investigate LLM decisions, or analyze token usage and costs. Also use when raw SQL/HogQL against `events.properties.$ai_input` / `$ai_output_choices` returns empty — message content lives only on the dedicated `posthog.ai_events` table.

Overview

PublisherPostHog
Repositoryskills
Skill nameexploring-llm-traces
Stars
77
Forks
8
Bundled files
9
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 9 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by PostHog on GitHub. Read the source before you install it.

Installation

Install the Exploring Llm Traces AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/PostHog/skills/tree/main/skills/omnibus/exploring-llm-traces
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/PostHog/skills.git /tmp/skills
mkdir -p .claude/skills
cp -r /tmp/skills/skills/omnibus/exploring-llm-traces .claude/skills/exploring-llm-traces
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Exploring Llm Traces in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Exploring Llm Traces on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Exploring Llm Traces is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Exploring LLM traces with MCP tools

PostHog captures LLM/AI agent activity as traces. Each trace is a tree of events representing a single AI interaction — from the top-level agent invocation down to individual LLM API calls.

Available tools

ToolPurpose
posthog:query-llm-traces-listSearch and list traces; can return large multi-trace payloads
posthog:query-llm-traceGet a single trace by ID with full event tree
posthog:read-data-schemaDiscover custom event/person properties before filtering
posthog:execute-sqlAd-hoc SQL for complex trace analysis

Event hierarchy

See the event reference for the full schema.

text
$ai_trace (top-level container)
  └── $ai_span (logical groupings, e.g. "RAG retrieval", "tool execution")
        ├── $ai_generation (individual LLM API call)
        └── $ai_embedding (embedding creation)

Events are linked via $ai_parent_id → parent's $ai_span_id or $ai_trace_id.

Workflow: debug a trace or session from a URL

Step 1 — Classify the URL

First inspect the path. Do not treat every UUID-looking value as a trace ID.

  • /ai-observability/traces/<trace_id> or legacy /llm-analytics/traces/<trace_id> / /llm-observability/traces/<trace_id> is a single trace. Fetch it with posthog:query-llm-trace.
  • /ai-observability/sessions/<session_id> or legacy /llm-analytics/sessions/<session_id> is an AI session, not a trace. Fetch traces with posthog:query-llm-traces-list filtered by event property $ai_session_id.

Preserve date_from / date_to query parameters from the URL when present. If none are present but the URL has a timestamp query parameter, use that timestamp as the anchor and query an absolute window around it, for example timestamp - 36h to timestamp + 36h. This handles exact session links whose UI timestamp may be offset from the stored event timestamps while keeping the query bounded. If the URL has neither explicit dates nor timestamp, use a safe default like {"date_from": "-7d"}.

For exact trace and session URLs, skip schema discovery for the standard $ai_* fields used below. These are AI observability built-ins, not project-specific custom properties.

Step 2 — Browse trace summaries

Explicitly set detail: "summary" when browsing traces. This returns event metadata and leaves the conversation content out, so browsing does not spend context on prompts and outputs. Omitting detail still returns full detail for compatibility with existing callers.

For a trace URL, call posthog:query-llm-trace with:

json
{
  "traceId": "<trace_id>",
  "detail": "summary",
  "dateRange": { "date_from": "-7d" }
}

For a session URL, call posthog:query-llm-traces-list with:

json
{
  "detail": "summary",
  "dateRange": { "date_from": "<timestamp_minus_36h>", "date_to": "<timestamp_plus_36h>" },
  "filterTestAccounts": false,
  "limit": 20,
  "properties": [{ "type": "event", "key": "$ai_session_id", "value": ["<session_id>"], "operator": "exact" }]
}

Use the URL's date_from / date_to values in the session query if present. If the URL only has timestamp, calculate the absolute date range from that timestamp instead of using a relative range like -1h. Set filterTestAccounts: false for an exact URL so the requested trace is not hidden by account filters.

The result contains trace and event metadata only. Prompts, outputs, span states, $ai_error and $ai_feedback_text are left out, and their names are listed in _summaryOmittedKeys beside the property bag. A trace with _detail: { "mode": "summary" } carries no conversation content at all. Read the content with detail: "full".

From the result you get:

  • Every event with its type ($ai_span, $ai_generation, etc.)
  • Span names ($ai_span_name) — these are the tool/step names
  • Latency, error flags, models used
  • Parent-child relationships via $ai_parent_id
  • _posthogUrl — always include this in your response so the user can click through to the UI

Step 3 — Read the content needed for the investigation

Once you have selected a trace, request posthog:query-llm-trace with detail: "full" before inspecting exact tool arguments, checking which context the model received, or searching conversation content:

json
{
  "traceId": "<trace_id>",
  "detail": "full",
  "dateRange": { "date_from": "-7d" }
}

Preserve the date range from the original URL or discovery query instead of copying the example range. Keep relevant property filters to narrow the read. If the user already identified the trace and needs exact content, you can request full detail directly.

Both modes enforce response size limits. Check the omission and truncation markers before drawing conclusions. A dropped event, a name listed in _summaryOmittedKeys or _redactedKeys, or a keyword missing from a summary is not evidence that it was absent from the trace. If full detail is still truncated, narrow the query to the relevant events or open _posthogUrl for the complete data. Both detail modes also withhold the properties that are not AI payload, described in Withheld properties below.

Step 4 — Parse large full-detail results with scripts

When the result is persisted to a file (large traces with full $ai_input/$ai_output_choices), use the parsing scripts to explore it.

Start with the summary to get the full picture, then drill into specifics:

bash
# 1. Overview: metadata, tool calls, final output, errors
python3 scripts/print_summary.py /path/to/persisted-file.json

# 2. Timeline: chronological event list with truncated I/O
python3 scripts/print_timeline.py /path/to/persisted-file.json

# 3. Drill into a specific span's full input/output
SPAN="tool_name" python3 scripts/extract_span.py /path/to/persisted-file.json

# 4. Full conversation with thinking blocks and tool calls
python3 scripts/extract_conversation.py /path/to/persisted-file.json

# 5. Search for a keyword across the returned properties
SEARCH="keyword" python3 scripts/search_traces.py /path/to/persisted-file.json

All scripts support MAX_LEN=N env var to control truncation (0 = unlimited).

Withheld properties

The two trace tools return the AI payload only. Only the $ai_* properties PostHog's taxonomy defines reach you, plus $ai_generation_id, $ai_cache_read_cost_usd, $ai_cache_creation_cost_usd, $ai_effort, $session_id, $lib and $lib_version. Every other event property, and every person property, is withheld in both detail modes, and its name is listed in _redactedKeys beside the property bag. $ai_base_url and $ai_request_url arrive without their query string.

This covers a project's own custom properties, such as project_id or conversation_id. A withheld property is unchanged in PostHog. It still works as a filter here, and you can read its value in the PostHog UI or with execute-sql. A name in _redactedKeys means the property is on the event and only its value is withheld. search_traces.py cannot find a keyword that appears only in a withheld property.

Investigation patterns

"Did the agent use the tool correctly?"

  1. Find the $ai_span for the tool call (look at $ai_span_name)
  2. Check $ai_input_state — what arguments were passed to the tool?
  3. Check $ai_output_state — what did the tool return?
  4. Check $ai_is_error — did the tool call fail?

"Was the context correct?" / "Were the right files surfaced?"

  1. Find the $ai_generation event where the LLM made the decision
  2. Check $ai_input — this is the full message history the LLM saw
  3. Look at preceding $ai_span events for retrieval/search steps
  4. Check their $ai_output_state — what content was retrieved and fed to the LLM?

"Did the subagent work?"

  1. In the structural overview, find spans that are children of other spans (via $ai_parent_id)
  2. The parent span is the orchestrator; child spans are subagent steps
  3. Check each child's $ai_output_state and $ai_is_error
  4. If a child span contains $ai_generation events, those are the subagent's LLM calls

"Why did the LLM say X?"

  1. Use search_traces.py to find where the text appears: SEARCH="the text" python3 scripts/search_traces.py FILE
  2. This shows which event and property path contains it
  3. Check the $ai_input of that generation to see what the LLM was told before it said X

Constructing UI links

The trace tools return _posthogUrl — always surface this to the user.

You can also construct links manually:

  • Trace detail: https://app.posthog.com/ai-observability/traces/<trace_id>?timestamp=<url_encoded_timestamp>&event=<optional_event_id>
  • Traces list with filters: returned in _posthogUrl from query-llm-traces-list

The timestamp query param is required — use the createdAt of the earliest event in the trace, URL-encoded (e.g. timestamp=2026-04-01T19%3A39%3A20Z).

When presenting findings, always include the relevant PostHog URL so the user can verify.

Finding traces

Use posthog:query-llm-traces-list with detail: "summary" to search and filter traces.

CRITICAL: Never assume event names, property names, or property values from training data. Every project instruments different custom properties. For open-ended searches and custom filters, call posthog:read-data-schema first to discover what properties and values actually exist in the project's data before constructing filters.

The exception is exact AI observability trace/session URLs: use the built-in $ai_trace_id / $ai_session_id fields directly and skip schema discovery.

Discovering the schema first

Before filtering traces, discover what's available:

  1. Confirm AI events exist — call posthog:read-data-schema with kind: "events" and look for $ai_* events
  2. Find filterable properties — call posthog:read-data-schema with kind: "event_properties" and event_name: "$ai_generation" (or another AI event) to see what properties are captured
  3. Get actual values — call posthog:read-data-schema with kind: "event_property_values", event_name: "$ai_generation", and property_name: "$ai_model" to see real model names in use

Only then construct the query-llm-traces-list call with property filters.

This is especially important for custom properties like project_id, conversation_id, user_tier, etc. — these vary per project and cannot be guessed. A custom property works as a filter even though its value is withheld from the response.

Do not confirm $ai_* properties, but confirm any other like email of a person.

By filters

json
posthog:query-llm-traces-list
{
  "detail": "summary",
  "dateRange": {"date_from": "-1h"},
  "filterTestAccounts": true,
  "limit": 20,
  "properties": [
    {"type": "event", "key": "$ai_model", "value": "gpt-4o", "operator": "exact"}
  ]
}

Multiple filters are AND-ed together:

json
posthog:query-llm-traces-list
{
  "detail": "summary",
  "dateRange": {"date_from": "-1h"},
  "filterTestAccounts": true,
  "properties": [
    {"type": "event", "key": "$ai_provider", "value": "anthropic", "operator": "exact"},
    {"type": "event", "key": "$ai_is_error", "value": ["true"], "operator": "exact"}
  ]
}

You can also filter by person properties (discover them via read-data-schema with kind: "entity_properties" and entity: "person"):

json
posthog:query-llm-traces-list
{
  "detail": "summary",
  "dateRange": {"date_from": "-1h"},
  "filterTestAccounts": true,
  "properties": [
    {"type": "person", "key": "email", "value": "@company.com", "operator": "icontains"}
  ]
}

By external identifiers

Customers often store their own IDs as event or person properties. Use posthog:read-data-schema to discover what custom properties exist, then filter:

  1. Call posthog:read-data-schema with kind: "event_properties" and event_name: "$ai_trace" to find custom properties
  2. Review the returned properties and their sample values
  3. Construct the filter using the discovered property key and a known value
json
posthog:query-llm-traces-list
{
  "detail": "summary",
  "dateRange": {"date_from": "-7d"},
  "properties": [
    {"type": "event", "key": "project_id", "value": "proj_abc123", "operator": "exact"}
  ]
}

For more complex SQL patterns, read these references:

Parsing large trace results

Trace tool results are JSON. When too large to read inline, Claude Code persists them to a file. Use a full-detail response for content extraction and keyword searches. The scripts cannot recover content omitted from a summary, withheld from a property bag, or dropped by truncation.

Persisted file format

json
[{ "type": "text", "text": "{\"results\": [...], \"_posthogUrl\": \"...\"}" }]

Trace JSON structure

text
results (array for list, object for single trace)
  ├── id, traceName, createdAt, totalLatency, totalCost
  ├── inputState, outputState (trace-level state)
  └── events[]
        ├── event ($ai_span | $ai_generation | $ai_embedding | $ai_metric | $ai_feedback)
        ├── id, createdAt
        └── properties
              ├── $ai_span_name, $ai_latency, $ai_is_error
              ├── $ai_input_state, $ai_output_state (span tool I/O)
              ├── $ai_input, $ai_output_choices (generation messages)
              ├── $ai_model, $ai_provider
              └── $ai_input_tokens, $ai_output_tokens, $ai_total_cost_usd

Available scripts

ScriptPurposeUsage
print_summary.pyAggregate list/session totals, trace metadata, tool calls, errors, and final LLM outputpython3 scripts/print_summary.py FILE
print_timeline.pyChronological event timeline with I/O summariespython3 scripts/print_timeline.py FILE
extract_span.pyFull input/output of a specific span by nameSPAN="name" python3 scripts/extract_span.py FILE
extract_conversation.pyLLM messages with thinking blocks and tool callspython3 scripts/extract_conversation.py FILE
search_traces.pyFind a keyword across all event propertiesSEARCH="keyword" python3 scripts/search_traces.py FILE
show_structure.pyShow JSON keys and types without valuescat blob.json | python3 scripts/show_structure.py

Tips

  • Always set dateRange — queries without a time range are slow. Use narrow windows (-30m, -1h) for broad listing queries; wider windows (-7d, -30d) are fine for narrow queries filtered by trace ID or specific property values
  • Always include the _posthogUrl in your response so the user can click through
  • $ai_input_state / $ai_output_state on spans contain tool call inputs and outputs
  • $ai_input / $ai_output_choices on generations contain the full LLM conversation — can be megabytes; when the result is persisted to a file, use the parsing scripts
  • In raw SQL, heavy content ($ai_input / $ai_output / $ai_output_choices / $ai_input_state / $ai_output_state / $ai_tools) lives only on the posthog.ai_events table, not events.properties — see the event reference for the column mapping and trace-id-anchored query patterns
  • Use filterTestAccounts: true to exclude internal/test traffic when searching
  • $ai_trace events are NOT in the events array — their data is surfaced via trace-level inputState, outputState, and traceName

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Exploring Llm Traces AI skill do?

Debug and inspect LLM/AI agent traces using PostHog's MCP tools. Use when the user pastes a trace or session URL (e.g. /ai-observability/traces/<id> or /ai-observability/sessions/<id>), asks to debug a trace, figure out what went wrong, check if an agent used a tool correctly, verify context/files were surfaced, inspect subagent behavior, investigate LLM decisions, or analyze token usage and costs. Also use when raw SQL/HogQL against `events.properties.$ai_input` / `$ai_output_choices` returns empty — message content lives only on the dedicated `posthog.ai_events` table.

Why use Exploring Llm Traces on TypingMind?

Because you install it once and use it with any model. Exploring Llm Traces is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Exploring Llm Traces in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/PostHog/skills/tree/main/skills/omnibus/exploring-llm-traces. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Exploring Llm Traces?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Exploring Llm Traces?

As many as you like. As long as a model supports skills, you can use Exploring Llm Traces with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Exploring Llm Traces AI skill free?

Yes. It is published on GitHub by PostHog under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇