Git for agent trajectories - branch, diff, and bisect LLM agent runs, backed by real re-execution.
Project description
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
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.
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:
$ 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:
$ retrial fork a1b2c3d --agent examples.booking_agent:run_agent --edit-file edit.json
Forked into session s_3f9c02ab1e
$ 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 asunpriced, 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
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
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file retrial-0.1.3.tar.gz.
File metadata
- Download URL: retrial-0.1.3.tar.gz
- Upload date:
- Size: 121.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1788bc98f2fc510a5cf1a078fa67f9daa7080b31b8032e4b2dc7197f119c385e
|
|
| MD5 |
2b119cf825d87d4a677973c9274b4305
|
|
| BLAKE2b-256 |
e6f5839a1f8a3620b1fa285816256cf06852047faba501b123a54f59d152be3b
|
Provenance
The following attestation bundles were made for retrial-0.1.3.tar.gz:
Publisher:
publish.yml on ArcKansupada/retrial
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
retrial-0.1.3.tar.gz -
Subject digest:
1788bc98f2fc510a5cf1a078fa67f9daa7080b31b8032e4b2dc7197f119c385e - Sigstore transparency entry: 2310187980
- Sigstore integration time:
-
Permalink:
ArcKansupada/retrial@cd182838bb097a5379ad32271a1501ae081b8724 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/ArcKansupada
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cd182838bb097a5379ad32271a1501ae081b8724 -
Trigger Event:
push
-
Statement type:
File details
Details for the file retrial-0.1.3-py3-none-any.whl.
File metadata
- Download URL: retrial-0.1.3-py3-none-any.whl
- Upload date:
- Size: 68.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7028f8667a6c031ba6f25049e8213db64be87c362710c91da0ec52188f8fcb6b
|
|
| MD5 |
55714ce4d4440d54bf4835667a5b6bb3
|
|
| BLAKE2b-256 |
eb807f7bea3af72b1bbc67def17df83a9208e17bf9be31a64a7c72be5ab5bf0a
|
Provenance
The following attestation bundles were made for retrial-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on ArcKansupada/retrial
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
retrial-0.1.3-py3-none-any.whl -
Subject digest:
7028f8667a6c031ba6f25049e8213db64be87c362710c91da0ec52188f8fcb6b - Sigstore transparency entry: 2310187996
- Sigstore integration time:
-
Permalink:
ArcKansupada/retrial@cd182838bb097a5379ad32271a1501ae081b8724 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/ArcKansupada
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cd182838bb097a5379ad32271a1501ae081b8724 -
Trigger Event:
push
-
Statement type: