This release is a pre-release and may not be stable for production use.
lockstepd
Status: experimental · v0.2.0-beta. Kernfunktionen sind durch 187 Tests abgedeckt (185 in der Standard-Suite, grün ohne Keys und Netz; 2 key-gated Live-Smoke-Tests), das Format ist stabil (v1) — die API darf sich vor 1.0 aber noch ändern.
Deterministischer Replay-Debugger für Agent-Läufe.
Zeichne jeden LLM-Aufruf, jedes Tool-Ergebnis, jede Uhr- und Zufalls-Lesung in eine
manipulationssichere Datei auf. Spiele den Lauf offline bit-exakt wieder — prüfbar per
Hash-Kette. Verzweige an beliebiger Stelle und lass lockstepd bisect automatisch den Schritt
finden, an dem alles gekippt ist.
Änderungen: CHANGELOG.md.
Das Problem
Agenten sind nicht reproduzierbar: erneutes Laufen lässt das Modell anders samplen, Tools liefern andere Daten, und der Bug ist weg, bevor du ihn siehst. Observability-Tools zeigen dir Traces — aber ein Trace ist Protokoll, kein Debugger: du kannst nicht anhalten, einen Schritt ändern und von dort neu laufen lassen. Genau diese Lücke füllt lockstepd: Aufzeichnung wird zur ausführbaren Wahrheit, gegen die du Code ändern, verzweigen und Ursachen eingrenzen kannst.
Demo-Session
$ make demo
──── 1 · RECORD ────
recorded examples/data/demo.lockstep.gz events=214 steps=26 trace=c2a74eb72004be11…
──── 2 · REPLAY (offline, kein Netz) ────
replay identical trace=c2a74eb72004be11…
──── 3 · VERIFY ×20 (Determinismus-Nachweis) ────
identical replays: 20/20
──── 4 · SCRUB ────
scrubbed 96 events -> clean.lockstep.gz (remaining suspicious: 0)
clean-check: clean rc=0
──── 6 · BISECT (eingebauter Fehler bei tools:10 finden) ────
┌─────────── bisect result ───────────┐
│ field │ value │
│ culprit step │ 22 (= tools:10) │
│ first bad fork │ 23 │
│ probes / budget │ 7 / 7 │
│ total steps │ 52 │
└─────────────────────────────────────┘
Der Bisect-Lauf oben ist echt: ein 25-Schritt-Demo-Agent mit einem Gift-Wert im Tool-Ergebnis von Schritt 10 — gefunden in 7 Proben (Budget ⌈log₂(52)⌉+2).
Installation
uv add lockstepd # Kern (msgspec) + CLI (typer/rich)
uv add "lockstepd[httpx]" # Transport-Instrumentierung (empfohlen)
pip install "lockstepd[anthropic]" # oder [openai] – optional, SDK-seitige Helfer
Python ≥ 3.12. CI-fähig ohne API-Schlüssel und ohne Netzwerk (Fake-Backend inklusive).
Schnellstart
Variante A — CLI (Subprozess, empfohlen):
lockstepd record run.lockstep.gz -- examples/demo_agent.py --steps 12
lockstepd replay run.lockstep.gz -- examples/demo_agent.py --steps 12 # gleiche Syntax
lockstepd verify run.lockstep.gz --times 100 -- examples/demo_agent.py --steps 12
lockstepd inspect run.lockstep.gz
Der ---Fallback über LOCKSTEP_COMMAND (JSON) funktioniert weiterhin, ist aber nicht mehr nötig.
Dein Agent braucht dafür genau zwei Zeilen:
from lockstepd.runtime import auto_session
session = auto_session() # liest LOCKSTEP_RECORD / LOCKSTEP_REPLAY / LOCKSTEP_FORK_*
with session:
... # normaler Agentencode; httpx-Clients werden automatisch instrumentiert
Variante B — In-Process-API:
import httpx
from lockstepd.session import Session
from lockstepd.integrations.httpx_transport import LockstepTransport
from lockstepd.patchers.concurrency import OrderedExecutor
with Session.record("run.lockstep.gz") as s:
client = httpx.Client(base_url="https://api.provider.test/v1",
transport=LockstepTransport(s)) # zeichnet auf
# ... LLM-Aufrufe über client, Tools über OrderedExecutor ...
s.final_state.update({"answer": answer})
print(s.trace_hash_hex)
Replay desselben Codes:
with Session.replay("run.lockstep.gz") as s:
client = httpx.Client(..., transport=LockstepTransport(s)) # serviert vom Band
...
assert s.trace_hash_hex == recorded_hash # bit-exakt
Fork & Bisect siehe lockstepd fork --help / lockstepd bisect --help sowie
examples/check_failed.py.
Was aufgezeichnet wird
LLM-Anfragen/-Antworten inkl. Streaming-Chunks mit relativen Ankunftsabständen, Tool-Aufrufe
mit Abschlussreihenfolge (completion_index), Uhren (time, datetime), Zufall (random,
uuid4, os.urandom), gelesene Umgebungsvariablen (redaktiert), optionale FS-Zugriffe,
Retries und Fehler — alles in einer gzip-JJSONL-Datei mit BLAKE2b-Hash-Kette.
Details: FORMAT.md.
CLI
| Befehl | Zweck |
|---|---|
lockstepd record OUT -- script.py args… |
Lauf aufzeichnen |
lockstepd replay TAPE -- script.py args… |
offline wiederabspielen; Divergenz ⇒ Fehler mit Diff |
lockstepd verify TAPE --times N -- script.py … |
Integrität + N-facher Identitätsnachweis |
lockstepd inspect TAPE |
interaktiver Trace-Browser (rich) |
lockstepd diff A B |
erste Divergenz + Unified-Diff + Kind-Statistik |
lockstepd fork TAPE --at N --out F -- script.py … |
Prefix abspielen, Suffix live |
lockstepd bisect BAD --predicate check.py -- script.py … |
Ursachen-Schritt per Binärsuche |
lockstepd scrub TAPE -o CLEAN / --check |
Secrets entfernen / nachweisen |
lockstepd export TAPE -o otlp.json |
OTLP/JSON-Spans |
Echte SDKs ohne API-Key
Die echten anthropic-/openai-Clients laufen keyless gegen das Fake-Backend
(lockstepd.testing.SdkFakeBackend, echte Wire-Formate inkl. SSE). Integrationstests
beweisen Record → Replay bit-exakt durch den genuine SDK-Pfad — Sync und Async.
Instrumentiert werden beide HTTP-Stapel: httpx und der httpx2-Fork aktueller SDKs.
Live-Smoke (opt-in, nie in CI)
Ein minimaler Record→Replay-Zyklus gegen die echten APIs liegt unter
tests/test_live_smoke.py (Marker live). Er läuft nur, wenn LOCKSTEP_LIVE_SMOKE=1
und der jeweilige Provider-Key gesetzt ist — sonst Skip. Die Standard-Suite
und CI schließen ihn explizit aus:
LOCKSTEP_LIVE_SMOKE=1 pytest -m live # kostet echte Tokens; bewusst manuell
Garantien — und wo sie enden
verify --times N beweist Wiederholbarkeit empirisch; die Hash-Kette macht jede Manipulation
und jede Divergenz sofort sichtbar (kryptografisch, nicht heuristisch).
Hook-Striktheit: Zeit-, Zufalls- und Env-Lesungen laufen über eigene sequentielle Queues,
getrennt vom strukturellen Ereignis-Cursor. Im Standard (strict_hooks=True, Subprozess-
Betrieb) ist jede Abweichung in diesen Strömen ein harter Fehler. Mit strict_hooks=False
fällt lockstepd stattdessen auf reale Werte zurück und notiert die Abweichung transparent in
session.soft_divergences — nützlich für In-Process-Replays, bei denen Bibliotheksräuschen
(pytest-interne UUIDs, Cookie-Jar-Uhren) keine harte Divergenz wert sein soll. Die Liste
macht jeden weichen Fallback sichtbar statt ihn zu verschweigen.
Was nicht reproduzierbar ist — von Set-Iteration bis C-Level-getenv — steht schonungslos in
LIMITS.md. Ehrlicher Vergleich zu bestehenden Werkzeugen: PRIOR_ART.md.
Aufzeichnungsformat: FORMAT.md. Entscheidungen: DECISIONS.md,
Status: PROGRESS.md.
Entwicklung
make check # ruff + ruff format --check + mypy --strict + pytest (185 Tests, ohne Keys/Netz)
make bench # 10k-Ereignis-Benchmark
make demo # obige Session
Lizenz: MIT.
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 lockstepd-0.2.0b0.tar.gz.
File metadata
- Download URL: lockstepd-0.2.0b0.tar.gz
- Upload date:
- Size: 163.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f9cb9e3671c07bef3e62bdeb004e828d0cba6dd0c6776532caf2e58cbab82c7
|
|
| MD5 |
b1e94124f0c2d772354624bd9d72f2bf
|
|
| BLAKE2b-256 |
41d2f17930f4fd2110be72ed2782c7d0b333aec95b0e992a17f633efc0d53ac0
|
Provenance
The following attestation bundles were made for lockstepd-0.2.0b0.tar.gz:
Publisher:
release.yml on dato-bitar/lockstepd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lockstepd-0.2.0b0.tar.gz -
Subject digest:
0f9cb9e3671c07bef3e62bdeb004e828d0cba6dd0c6776532caf2e58cbab82c7 - Sigstore transparency entry: 2604581462
- Sigstore integration time:
-
Permalink:
dato-bitar/lockstepd@841781a3ea82b106e2858cfd8668f8c73fcb7c9e -
Branch / Tag:
refs/tags/v0.2.0b0 - Owner: https://github.com/dato-bitar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@841781a3ea82b106e2858cfd8668f8c73fcb7c9e -
Trigger Event:
push
-
Statement type:
File details
Details for the file lockstepd-0.2.0b0-py3-none-any.whl.
File metadata
- Download URL: lockstepd-0.2.0b0-py3-none-any.whl
- Upload date:
- Size: 53.0 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 |
0cc72562ab81f841200495e85a9e306494c3d051f4f5c1354b46abe9a5b7709a
|
|
| MD5 |
2ff73bf6ac9bd1bcc446f24932a51d14
|
|
| BLAKE2b-256 |
f53255412fdf4480654ada614729da11e15fa8d590db06deda19dd2b720ec0fa
|
Provenance
The following attestation bundles were made for lockstepd-0.2.0b0-py3-none-any.whl:
Publisher:
release.yml on dato-bitar/lockstepd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lockstepd-0.2.0b0-py3-none-any.whl -
Subject digest:
0cc72562ab81f841200495e85a9e306494c3d051f4f5c1354b46abe9a5b7709a - Sigstore transparency entry: 2604581552
- Sigstore integration time:
-
Permalink:
dato-bitar/lockstepd@841781a3ea82b106e2858cfd8668f8c73fcb7c9e -
Branch / Tag:
refs/tags/v0.2.0b0 - Owner: https://github.com/dato-bitar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@841781a3ea82b106e2858cfd8668f8c73fcb7c9e -
Trigger Event:
push
-
Statement type: