Metadata-Version: 2.5
Name: lockstepd
Version: 0.2.0b0
Summary: lockstepd - deterministic record/replay debugger for LLM agent runs: record, replay bit-exact offline, fork at step N, bisect failures.
Project-URL: Homepage, https://github.com/dato-bitar/lockstepd
Project-URL: Repository, https://github.com/dato-bitar/lockstepd
Project-URL: Issues, https://github.com/dato-bitar/lockstepd/issues
Project-URL: Changelog, https://github.com/dato-bitar/lockstepd/blob/main/CHANGELOG.md
Author: lockstepd contributors
License-Expression: MIT
License-File: LICENSE
Keywords: agents,debugging,determinism,llm,record-replay,replay
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Debuggers
Requires-Python: >=3.12
Requires-Dist: msgspec>=0.19.0
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.42; extra == 'anthropic'
Provides-Extra: cli
Requires-Dist: rich>=13.7; extra == 'cli'
Requires-Dist: typer>=0.12; extra == 'cli'
Provides-Extra: dev
Requires-Dist: anthropic>=0.42; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: hypothesis>=6.108; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: openai>=1.50; extra == 'dev'
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
Requires-Dist: pytest>=8.2; extra == 'dev'
Requires-Dist: rich>=13.7; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: typer>=0.12; extra == 'dev'
Provides-Extra: httpx
Requires-Dist: httpx>=0.27; extra == 'httpx'
Provides-Extra: openai
Requires-Dist: openai>=1.50; extra == 'openai'
Description-Content-Type: text/markdown

# lockstepd

[![CI](https://github.com/example/lockstepd/actions/workflows/ci.yml/badge.svg)](https://github.com/example/lockstepd/actions/workflows/ci.yml)

**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](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

```bash
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):**

```bash
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:

```python
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:**

```python
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:

```python
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](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:

```bash
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](LIMITS.md). Ehrlicher Vergleich zu bestehenden Werkzeugen: [PRIOR_ART.md](PRIOR_ART.md).
Aufzeichnungsformat: [FORMAT.md](FORMAT.md). Entscheidungen: [DECISIONS.md](DECISIONS.md),
Status: [PROGRESS.md](PROGRESS.md).

## Entwicklung

```bash
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.
