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 (embedding / LLM-judge) via the Matcher protocol — v1 is deterministic-only by design
  • offtrack bisect — find the commit that introduced a divergence
  • 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.2.0.tar.gz (596.7 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.2.0-py3-none-any.whl (66.2 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for offtrack-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f787c4e1da8dd65c4359d7dd29b4a54d5c527be7e40f5dca9f9ecbe7f6f41f95
MD5 6e6d215e8e58b07a61763f33b68dfda3
BLAKE2b-256 2d69ff0d084759345a9f9b098de59e1f71ea6ac2723481aef39fa0e1d0151384

See more details on using hashes here.

Provenance

The following attestation bundles were made for offtrack-0.2.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: offtrack-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 66.2 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 38d393f680974ed058a3a0f8c654df129f2e29d04664e1d7c1056a36f0c9b991
MD5 d8f83d477d6d0e3c641d856a20036992
BLAKE2b-256 4da3653480482b3b9812e4732624b3807a18bc1d4f7c3db17845592bc06919f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for offtrack-0.2.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