Importing Langfuse traces#

Langfuse tells you what happened; Maida tells you whether it changed. The Langfuse importer turns traces that already exist in Langfuse into local Maida runs, so they can be inspected, baselined, and gated without adding a second instrumentation path.

The integration is API-only and read-only. Maida sends authenticated GET /api/public/v2/observations requests, normalizes the observations, validates the result against Maida’s current trace contract, and writes only to local Maida storage. It does not modify Langfuse data or upload the imported run to a hosted Maida service.

Until the importer is included in the next PyPI release, install the current main revision:

uv tool install "maida-ai>=0.5"

Configure access#

No optional package is required. Set the credentials used by the Langfuse SDK:

export LANGFUSE_PUBLIC_KEY=pk-lf-...
export LANGFUSE_SECRET_KEY=sk-lf-...

Langfuse Cloud is the default. For a regional or self-hosted deployment, set the base URL and, optionally, the request timeout:

export LANGFUSE_BASE_URL=https://us.cloud.langfuse.com
export LANGFUSE_TIMEOUT=15

Credentials are read from the environment. The CLI has no credential flags and never stores credentials in a run. The v2 observations endpoint requires Langfuse Cloud or self-hosted Langfuse v4+.

Import a trace#

Import one complete source trace by its Langfuse trace ID:

maida import langfuse --trace-id 7f0d4a2c...

Or discover traces in a bounded, timezone-aware interval. --from is inclusive and --to is exclusive:

maida import langfuse \
  --from 2026-08-01T00:00:00Z \
  --to 2026-08-02T00:00:00Z \
  --trace-name support-agent \
  --session-id session-42 \
  --environment production

--trace-name, --session-id, and repeatable --environment options narrow range discovery. They cannot be combined with --trace-id. Range discovery uses server-side filters and then fetches every observation for each selected trace. All cursor pages are followed.

Use --json for a machine-readable summary. Exit code 0 means every selected complete trace was imported or already existed; 2 means invalid selection, no matches, or only incomplete traces; 10 means an API, normalization, or storage failure.

Re-importing the same Langfuse project and trace is idempotent. Maida derives a stable destination trace ID and skips an identical existing import. It refuses to overwrite a conflicting run.

Mapping contract#

One Langfuse trace becomes one Maida run. Its traceName becomes the recurring Maida run_name. Session IDs remain source metadata. Maida creates a synthetic root span so source traces with multiple roots, subagents, or absent ancestors still form one valid tree.

Langfuse observation

Maida representation

Trace

One run plus a synthetic root span

GENERATION

LLM span / LLM_CALL, with model, input, output, and token usage

TOOL

Tool span / TOOL_CALL, with name, arguments, result, and error state

SPAN, AGENT, CHAIN, RETRIEVER, EVALUATOR, EMBEDDING, GUARDRAIL

Preserved structural span

EVENT

Zero-duration structural span when no end time is present

Unknown type

Preserved structural span and reported in the import summary

Session

maida.meta.langfuse.session_ids on the run root

Parent-child links are retained when the parent is present. Missing parents attach to the synthetic root. Inputs, outputs, metadata, costs, and usage details pass through Maida’s active redaction and truncation before persistence. Completed source errors remain errors; incomplete non-event observations are skipped rather than given a fabricated completion.

Legacy local single-trace compatibility#

The current CI path is the Action trace-command workflow below. For local migration workflows that already import one completed trace at a time, the legacy single-run interface remains available:

maida view
maida baseline --out baselines/support-agent.json

# Import the next completed run, then gate the latest local run.
maida import langfuse --trace-id NEXT_TRACE_ID
maida assert \
  --baseline baselines/support-agent.json \
  --policy .maida/policy.yaml

The compatibility path uses the same structural evaluator for changed tool paths, new tools, repeated work, loops, status changes, and configured latency or token envelopes.

Run the importer in GitHub Actions#

The Action accepts a trusted trace-command when the run comes from an importer rather than a Python agent entrypoint. Keep credentials in GitHub secrets and select exactly one completed source trace:

- uses: maida-ai/maida-assert@v5
  env:
    LANGFUSE_PUBLIC_KEY: ${{ secrets.LANGFUSE_PUBLIC_KEY }}
    LANGFUSE_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }}
    LANGFUSE_TRACE_ID: ${{ vars.LANGFUSE_TRACE_ID }}
  with:
    trace-command: maida import langfuse --trace-id "$LANGFUSE_TRACE_ID"
    baseline: baselines/support-agent.json
    policy: .maida/policy.yaml

Imported traces use a fixed one-trial gate because a source trace is already a completed observation, not a script Maida can rerun statistically. Do not pass --trials in extra-args, select a range that creates several runs, or build trace-command from pull-request-controlled text. Policies that need repeated independent executions should use agent-script instead.

Try it without an account#

The offline Langfuse import demo runs the real importer against a loopback fake API. Its fully synthetic fixture proves idempotent import, a baseline pass, and a deterministic structural regression failure without a Langfuse account, API key, LLM, or external network request.

Privacy and failure behavior#

  • Requests are read-only and go only to the configured Langfuse origin.

  • Imported data stays under the local MAIDA_DATA_DIR (by default ~/.maida/); there is no default upload or telemetry path.

  • Credentials remain environment-only and are not copied into trace metadata.

  • Invalid selections and incomplete-only results exit 2 with an actionable message. Authentication, API, normalization, or storage failures exit 10.

  • A future Langfuse observation type is retained as structural signal and named in the summary instead of being silently dropped.