Metadata-Version: 2.4
Name: retrial
Version: 0.1.3
Summary: Git for agent trajectories - branch, diff, and bisect LLM agent runs, backed by real re-execution.
Project-URL: Homepage, https://github.com/ArcKansupada/retrial
Project-URL: Repository, https://github.com/ArcKansupada/retrial
Project-URL: Issues, https://github.com/ArcKansupada/retrial/issues
Project-URL: Changelog, https://github.com/ArcKansupada/retrial/blob/main/CHANGELOG.md
Author-email: Aarav Kansupada <aaravk@utexas.edu>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,anthropic,bisect,debugging,llm,observability,replay,trajectory
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: click>=8.0
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Provides-Extra: live
Requires-Dist: anthropic>=0.117; extra == 'live'
Description-Content-Type: text/markdown

# retrial

**Git for agent trajectories.** Branch, diff, and bisect LLM agent runs — backed by real re-execution, not static logs.

When an agent does something wrong, most debugging is scrolling through logs. retrial lets you go back to step N, change one fact — a tool result, a retrieved doc, an intermediate decision — and re-run your real agent from there to see what it *actually* would have done differently. With the real model, not a guess. Validated end to end against live `claude-opus-4-8`.

## Why it's different

- **Real re-execution, not log branching.** A fork re-enters your live agent loop and makes real model calls from the fork point onward. It does not relabel stored JSON and call it a branch.
- **Zero-export integration.** A decorator on your loop, not a JSON schema to hand-build.
- **Narrow and deep.** One thing — branch/diff/bisect by real execution — done well.
- **Python-native, local-first.** No account, no telemetry, no dashboard. One SQLite file in `.retrial/`.

## Install

```bash
pip install retrial
```

## Integrate

retrial needs two things from your loop: the function that calls the model, and the function that runs tool calls. You pass both in — no monkey-patching of your SDK, so every recorded step traces back to a line you wrote.

```python
from retrial import record

@record(session_name="booking-agent")
def run_agent(messages, tools=TOOLS, call_model=call_model, execute_tools=execute_tools):
    while True:
        response = call_model(messages, tools)
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return response
        messages.append({"role": "user", "content": execute_tools(response)})
```

**The one rule: `messages` must be a parameter**, not a list created inside the function. That's what lets a fork seed your loop with edited history and get genuine re-execution. Give the other parameters defaults to fork from the CLI. The loop must be synchronous — `@record` refuses an `async def` rather than record something untrue. Then just run it; steps log as the loop runs, with no export step.

A bundled example under `examples/` forks, diffs, and bisects with no API key.

## Example: fork, then diff

Every step gets a content-hash SHA, addressable by a short prefix like git:

```bash
$ retrial log s_a8d4f64945
  4f0c1e2  step 0  model_call  (312ms, 450 tok)
  a1b2c3d  step 1  tool_call   ran search_flight
  9e77b10  step 2  model_call  (288ms, 544 tok)
```

Fork step 1 with one fact changed — the edit is a JSON patch — and the real model decides again from there:

```bash
$ retrial fork a1b2c3d --agent examples.booking_agent:run_agent --edit-file edit.json
Forked into session s_3f9c02ab1e
```

```bash
$ retrial diff s_a8d4f64945 s_3f9c02ab1e
diverged at d66697c
  cause: replace /output/0/content = flight_price 1200

  - A  book_flight    model_call
  + B  check_budget   model_call

final answer
  A  Confirmed: AUS-SFO booked for $450.
  B  That's over the $600 limit. I need approval before booking.
```

The fork called `check_budget`, a tool the original never touched — the kind of divergence only real re-execution produces. The original is never mutated; a fork is a new session, so you can branch the same step as many times as you like.

## Also

Same machinery — a fork plus a check — pointed at different questions:

- **`bisect`** — which step doomed a *failed* run? Binary search over resume points, about log2(steps) re-executions.
- **`ablate`** — which recorded facts did a *good* run actually need? Perturb each and see if the outcome flips. Causal, not heuristic.
- **`sweep`** — fork one step across many values to find a decision threshold in the model's behavior.
- **`rerun`** — re-execute every recorded run against your current code. Your traces are the regression suite; it exits non-zero on a regression, so CI fails without extra plumbing.
- **`cost`** — token and dollar breakdown per step. An unknown model prices as `unpriced`, never as a guess.

Bisect and ablate are duals and each refuses the other's job: bisect wants a run that failed, ablate a run that worked.

## What retrial refuses to do

The whole product rests on the replay being exactly what happened, so retrial raises rather than guesses when it can't verify that:

- Your loop **transforms a tool result** before appending it — the patch would land on a value you never saw.
- An edit **invents or drops** a tool result the run never produced.
- A run **crashed mid-loop** — the message state after its trailing tool call was never observed.
- Your agent or its model call is **async** — the session would be marked complete before the loop ran a step.
- The database was written by a **newer retrial**, or an imported step's **content doesn't match its SHA**.

A wrong replay would be worse than no replay. Merge was cut for the same reason: two forks are competing hypotheses, and answers don't merge.

## CLI

```
retrial init                    create .retrial/ + sqlite db
retrial list                    sessions, tree view
retrial log <session>           step-by-step history, SHA per step
retrial show <sha>              full detail on one step
retrial fork <sha> --agent M:F --edit-file e.json
retrial diff <a> <b>            --full to expand shared steps
retrial bisect <session> --check EXPR --agent M:F
retrial ablate <session> --check EXPR --agent M:F
retrial sweep <sha> --values-file v.json --agent M:F
retrial rerun --check EXPR --agent M:F
retrial cost <session>
retrial export <session> > trace.jsonl
retrial import trace.jsonl
```

SHA prefix matching applies throughout. The store is found by searching upward for `.retrial/`, the way git finds `.git/`; override with `--db` or `RETRIAL_DB`.

## Python API

```python
from retrial import record, fork, diff, bisect, ablate, sweep, rerun, trajectory, Store
from retrial import export, import_
```

Every function returns a plain dict, so results stay JSON-shaped and printable. The shapes are declared in `retrial/types.py` and shipped with `py.typed`, so a typo in a key is a type error, not a `KeyError` at 3am.

## License

MIT
