Skip to main content

git diff for AI agent runs — record golden trajectories, find the first divergent step, gate CI on real regressions

Project description

offtrack

git diff for AI agent runs.

Record golden trajectories. Re-run after any change. See the first step where your agent went off track — and gate CI on it.

CI gated by offtrack Python 3.10+ License: MIT


offtrack demo: record golden trajectories, then catch the model that skips the refund policy check

You bumped a model. Or tweaked a prompt. Or upgraded your agent framework.

Every eval score still looks fine. But your agent now refunds $842 without checking the refund policy — cheaper, faster, and catastrophically wrong:

$ offtrack check

  ✗ refund/over-limit  0/5 aligned
      =  step 0  llm_call     (both runs identical)
      =  step 1  tool_call    lookup_order({"order_id": "TEST-1"})
      ▲ first divergence — expected check_refund_policy, got issue_refund
        baseline  check_refund_policy({"amount_usd": 842.0})
        this run  issue_refund({"order_id": "TEST-1", "amount_usd": 842.0})
      divergence rate rose 0% → 100% (Fisher exact p=0.004, effect +100%)
      Δtokens -46%  Δlatency -30%

  FAIL — exit code 1

Eval scores tell you whether behavior changed. offtrack shows you where: the first divergent step, with cost and latency deltas, from traces stored locally in SQLite. Statistical verdicts (PASS / FAIL / INCONCLUSIVE) gate CI so stochastic LLM variance doesn't cause false alarms — and honest uncertainty doesn't hide behind a green check.

Quickstart

pip install offtrack
offtrack init                 # scaffold offtrack.yaml + baselines/
offtrack record               # capture golden trajectories (N runs per task)
# ...change your model / prompt / framework...
offtrack check                # first divergent step + verdict + exit code

Capturing traces — three ways

Zero-code (OpenAI / Anthropic SDKs) — two lines at your agent's entrypoint:

import offtrack.capture
offtrack.capture.install()   # patches whichever SDKs are importable

Every LLM call is recorded, and tool calls are reconstructed by message delta — real names, args, and results, no changes to your agent loop.

LangGraph / LangChain — pass the callback:

from offtrack.integrations.langgraph import OfftrackCallbackHandler

handler = OfftrackCallbackHandler()
graph.invoke(inputs, config={"callbacks": [handler]})
handler.finish()

Anything else — write capture events (JSONL) to $OFFTRACK_TRACE_DIR, or export OpenTelemetry GenAI traces there: offtrack ingests both semconv generations and the OpenInference flavor. Claude Code sessions import directly with offtrack ingest claude-code <session.jsonl>.

How it works

  1. Recordofftrack record runs each task N times (default 5) and stores the trajectories: every LLM call, tool call, argument, token count, cost, latency. Baselines auto-export to baselines/*.jsoncommitted to git, so changing golden behavior is a reviewed act in PRs.
  2. Align — new runs are aligned against baselines with Needleman-Wunsch sequence alignment over steps (tool-name gating + structural argument similarity). Reordered parallel calls, retries, and volatile fields (UUIDs, timestamps — masked by default) don't cause false alarms.
  3. Localize — the report points at the first divergent step: missing, extra, or changed — with the argument-level diff and whether the trajectories resynced afterward.
  4. Verdict — baselines are recorded N times so run-to-run variance is measured, not assumed. FAIL requires an exact Fisher test plus a minimum effect size; PASS requires an exact upper confidence bound. Anything else is INCONCLUSIVE with a prescription: "run 2 more repetitions."

CI

- uses: abhi13-tech/offtrack@v0
  with:
    suite: offtrack.yaml
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

FAIL fails the check and posts a PR comment with the divergence table. INCONCLUSIVE warns without blocking (strictness is opt-in via inconclusive-as: fail). Exit codes: 0 pass, 1 fail, 3 inconclusive, 4 setup error — infra problems never masquerade as regressions.

This repo gates itself: every PR runs offtrack against its own demo agent (dogfood.yml), and the gate's failure path is itself under test.

pytest

@pytest.mark.offtrack(task="refund/over-limit")
def test_refund_flow(offtrack):
    with offtrack.record():
        run_my_agent("refund order TEST-1")
    offtrack.assert_matches_baseline()
    offtrack.assert_cost_under(usd=0.05)
    offtrack.assert_max_steps(30)

pytest -n auto (xdist) is fully supported — workers spill trajectories to append-only files; the controller merges at session end.

What offtrack is not

These are complements, not competitors — offtrack is the trajectory-diff layer:

offtrack promptfoo LangSmith EvalView
Unit of comparison step-level trajectory prompt/output pairs hosted traces + evals eval-run snapshots
First-divergence localization
Semantic alignment (retries, reordering, masks) exact-match only
Statistical verdict (variance-aware) pass@k
Local-first, no account SQLite hosted
Vendor-neutral ingest (OTel GenAI, both semconv generations)

offtrack doesn't score answer quality — pair it with an eval framework for that. It catches the thing eval scores structurally miss: procedural regressions in how the agent got there.

Demo

examples/refund-agent/ is a complete offline demo — a small support agent with a written refund policy and two scripted personas: fake-careful (follows policy) and fake-sloppy (a stand-in for a cheaper model that skips the policy check). No API key, no network, $0:

cd examples/refund-agent
AGENT_MODEL=fake-careful offtrack record    # golden: lookup → policy check → escalate
AGENT_MODEL=fake-sloppy  offtrack check     # FAIL: skips policy, refunds $842 directly

The fake mode is a scripted LLM, not a mocked test — the entire real pipeline (capture, ingest, alignment, stats) executes identically in fake and real mode.

Roadmap

  • LangGraph callback + OpenAI/Anthropic SDK capture shims — shipped in 0.2.0 (offtrack.capture.install())
  • Claude Code session ingest — shipped in 0.2.0 (offtrack ingest claude-code)
  • Semantic matchers — shipped in 0.3.0 (align.final_answer: lexical | embedding; LLM-judge matcher still to come)
  • offtrack bisect — shipped in 0.4.0 (offtrack bisect --good <ref>)
  • CrewAI / Pydantic-AI adapters · HTML report viewer

Development

git clone https://github.com/abhi13-tech/offtrack && cd offtrack
uv sync --all-extras --group dev
uv run pytest          # 200 tests, all offline
uv run mypy            # strict
just dogfood           # run the gate against the demo locally

MIT licensed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

offtrack-0.4.0.tar.gz (605.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

offtrack-0.4.0-py3-none-any.whl (73.7 kB view details)

Uploaded Python 3

File details

Details for the file offtrack-0.4.0.tar.gz.

File metadata

  • Download URL: offtrack-0.4.0.tar.gz
  • Upload date:
  • Size: 605.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for offtrack-0.4.0.tar.gz
Algorithm Hash digest
SHA256 0ea779ae71545fbe1930009c9bf670b460f5515847cb222521784ce2ceb272fd
MD5 5cfd127f69c923eba1d823a6ef22598a
BLAKE2b-256 868a462d9798081be829b0b0262cd36cca445ccc57a4040294659b1392aada6c

See more details on using hashes here.

Provenance

The following attestation bundles were made for offtrack-0.4.0.tar.gz:

Publisher: release.yml on abhi13-tech/offtrack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file offtrack-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: offtrack-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 73.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for offtrack-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8dc5937d7631abc8bcab3fa5cda40cc3c975a6af424f238f9ca8ef5a9504478a
MD5 d78306a9c155534c559e6403d298b9d3
BLAKE2b-256 507f85e64353d0569809b453c1868dc4c21ef9c62d1a2be17ca3cae5941e4f57

See more details on using hashes here.

Provenance

The following attestation bundles were made for offtrack-0.4.0-py3-none-any.whl:

Publisher: release.yml on abhi13-tech/offtrack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page