Skip to main content

Event-sourced recording + deterministic replay + time‑travel debugger for LLM agents.

Project description

Timewarp — Deterministic Replay & Time‑Travel Debugger for LLM Agent Workflows

PyPI Python Versions License CI Publish

Record every step. Rewind any step. Reproduce any run.

Timewarp adds event‑sourced logging and deterministic replay to agent frameworks (LangGraph first, LangChain optional), plus an interactive REPL debugger for step‑through, diffs, and what‑if edits. It fills a well‑documented gap: mainstream tools visualize traces but don’t let you replay them exactly.

What’s Included (v0.1 core)

  • Core models and helpers
    • timewarp.events: Pydantic v2 models (Run, Event, BlobRef), hashing, redaction
    • timewarp.codec: Canonical JSON (orjson), Zstandard compression
    • timewarp.determinism: RNG snapshot/restore
  • Local store
    • timewarp.store.LocalStore: SQLite (WAL) for runs/events + filesystem blobs
    • Deterministic blob layout: runs/<run_id>/events/<step>/<kind>.bin (zstd)
    • Connection PRAGMAs applied per-connection: journal_mode=WAL, synchronous=NORMAL, configurable busy timeout
    • Monotonic steps: per-run event step must strictly increase; single-writer-per-run is recommended for correctness
  • LangGraph recording
    • timewarp.langgraph.LangGraphRecorder: streams updates|values|messages, records LLM|TOOL|DECISION|HITL|SNAPSHOT events
    • Labels include thread_id, namespace, node, checkpoint_id, anchor_id
    • Privacy redaction via privacy_marks
  • Diff engine
    • Anchor‑aware alignment + windowed realignment; DeepDiff/text diffs; first divergence
    • Delta debugging: minimal failing window via diff --bisect
  • Replay scaffolding
    • PlaybackLLM/PlaybackTool inject recorded outputs with prompt/args validation
    • LangGraphReplayer.resume() re‑executes from nearest checkpoint using recorded outputs
    • What‑if overrides supported (one‑shot per step)
  • CLI
    • timewarp list|events|tools|diff|debug, plus resume, inject, and fsck (see below)
    • timewarp-repl interactive debugger for browsing timelines, inspecting prompts/tools/memory, deterministic resume, and recording what‑if forks
    • export langsmith <run_id> to serialize runs/events for external tooling
  • Telemetry (optional)
    • OpenTelemetry spans per event; replay spans link to originals via Span Links
    • Attributes use tw.* keys: tw.run_id, tw.step, tw.action_type, tw.actor, tw.replay, tw.namespace, tw.thread_id, tw.checkpoint_id, tw.anchor_id, tw.branch_of, tw.hash.output|state|prompt

Install & Dev

Requires Python 3.11+.

Install from PyPI:

pip install timewarp-llm
# optional extras
pip install langgraph langchain-core  # optional runtime dependencies for recording/replay
pip install 'timewarp-llm[otel]'
pip install 'timewarp-llm[dspy]'   # optional DSPy optimizers

# CLI entry points
timewarp --help
timewarp-repl --help
uv venv && uv pip install -e .[dev]
ruff format && ruff check --fix
mypy --strict
pytest -q

Developer notes: see `docs/DEV.md` for CLI helper modules, canonical JSON path, store insert/observability details, and provenance consistency.

Optional dependencies

  • LangGraph/LC core: uv pip install langgraph langchain-core
  • Telemetry: uv pip install -e .[otel] (installs opentelemetry-*)
  • DSPy: uv pip install -e .[dspy] (installs dspy) for optional prompt optimization

DSPy Dataset & Optimization (optional)

Timewarp can export per-agent datasets from recorded runs and optionally run DSPy optimizers to produce improved prompt specifications.

Build a dataset from a run:

timewarp ./timewarp.sqlite3 ./blobs dspy build-dataset <run_id> --out ds.json

Run an optimizer (requires installing the dspy extra):

timewarp ./timewarp.sqlite3 ./blobs dspy optimize ds.json --optimizer bootstrap --out prompts.json
# or
timewarp ./timewarp.sqlite3 ./blobs dspy optimize ds.json --optimizer mipro --out prompts.json
# emit overrides JSON directly consumable by `dspy fork`
timewarp ./timewarp.sqlite3 ./blobs dspy optimize ds.json --emit-overrides --out overrides.json

Notes

  • Dataset groups examples by agent (LangGraph node). Each example includes inputs (messages when available), the agent's memory snapshot at step-1, the recorded output, and step/thread metadata.
  • Optimizers are optional. If DSPy is not installed or compilation fails, the CLI emits a heuristic prompt template per agent with basic metrics.
  • This is pre‑release functionality and may evolve without backward compatibility guarantees.

Recording a Run (LangGraph)

Quickstart via facade:

from timewarp import wrap, messages_pruner
from examples.langgraph_demo.app import make_graph

graph = make_graph()

rec = wrap(
    graph,
    project="demo",
    name="my-run",
    stream_modes=("updates", "messages", "values"),
    snapshot_every=20,
    snapshot_on=("terminal", "decision"),
    state_pruner=messages_pruner(max_len=2000, max_items=200),
    enable_record_taps=True,  # robust prompt/tool args hashing
    event_batch_size=20,      # batch appends to reduce SQLite overhead
)
result = rec.invoke({"text": "hi"}, config={"configurable": {"thread_id": "t-1"}})
print("run_id=", rec.last_run_id)

Manual recorder usage:

from pathlib import Path
from timewarp.events import Run
from timewarp.store import LocalStore
from timewarp.langgraph import LangGraphRecorder
from timewarp import messages_pruner

store = LocalStore(db_path=Path("./timewarp.db"), blobs_root=Path("./blobs"))
run = Run(project="demo", name="my-run", framework="langgraph")
rec = LangGraphRecorder(
    graph=my_compiled_graph,
    store=store,
    run=run,
    stream_modes=("updates", "values"),  # also supports "messages"
    stream_subgraphs=True,
    snapshot_on={"terminal", "decision"},
    state_pruner=messages_pruner(max_len=2000, max_items=200),
)
result = rec.invoke({"text": "hi"}, config={"configurable": {"thread_id": "t-1"}})

Debugging & Diffs

timewarp ./timewarp.db ./blobs list
timewarp ./timewarp.db ./blobs debug <run_id>              # basic inspector (legacy)
timewarp ./timewarp.db ./blobs diff <run_a> <run_b>       # first divergence / bisect
timewarp ./timewarp.db ./blobs events <run_id> --type LLM --node compose --thread t-1 --json

# NEW: interactive debugger (recommended)
timewarp-repl ./timewarp.sqlite3 ./blobs <run_id> \
  --app examples.langgraph_demo.app:make_graph \
  --thread t-1 --freeze-time

Interactive Debugger (REPL)

Timewarp ships a richer interactive REPL that unifies timeline browsing, prompt/tools/memory inspection, deterministic replay, what‑if injections, prompt overrides, and diffs.

  • Binary: timewarp-repl (installed by the package)
  • Programmatic: timewarp.interactive_debug.launch_debugger(...)

CLI usage

timewarp-repl <db> <blobs> <run_id> [--app module:function] [--thread ID] \
  [--freeze-time] [--strict-meta] [--allow-diff] [--overrides overrides.json]

Inside the REPL

Commands
  app module:function         Load a compiled LangGraph via factory (enables resume/inject)
  thread T                    Set thread_id to use during resume/inject
  freeze | unfreeze           Toggle freeze-time replay
  strict | nonstrict          Toggle strict meta checks (provider/model/tools invariants)
  allowdiff | disallowdiff    Toggle allowing prompt diffs during replay (for overrides)
  overrides [file.json]       Load per-agent prompt overrides; empty to clear

Views
  list [type=.. node=.. thread=.. ns=..]   Timeline (filterable)
  event STEP                  Show a single event + blob size hints
  llm                         Show the last LLM before current position
  prompt [STEP]               Messages/tools head + estimated tokens for an LLM step
  tools [STEP]                Tools summary across run or details for one LLM step
  memory                      Memory summary by space
  memory_show STEP            Full memory snapshot up to step
  memory_diff A B [key=path]  Structural diff between two snapshots (optional dotted key)

Execution
  resume [FROM_STEP]          Deterministically resume from a checkpoint using recorded outputs
  inject STEP output.json [-r|--record]
                              One-shot override at STEP; optionally record fork immediately
  fork_prompts overrides.json [-r|--record]
                              Prepare/record a fork that applies prompt overrides
  diff OTHER_RUN_ID [--bisect] [--window N]
                              Show first divergence or minimal failing window

Programmatic launch

from timewarp.interactive_debug import launch_debugger
from examples.langgraph_demo.app import make_graph

launch_debugger(
    db="./timewarp.sqlite3",
    blobs="./blobs",
    run_id="<UUID>",
    graph=make_graph(),          # or pass --app module:function via CLI
    thread_id="t-1",
    freeze_time=True,
    strict_meta=False,
    allow_diff=False,
)

Integrity Check (fsck)

Verify that all blobs referenced by a run exist on disk; optionally repair and garbage‑collect orphans. Emits JSON for easy automation.

# Basic verification (JSON output)
timewarp ./timewarp.db ./blobs fsck <run_id>
# Attempt repair by promoting any matching .tmp files to final .bin
timewarp ./timewarp.db ./blobs fsck <run_id> --repair
# Remove blob files on disk that are not referenced by the run (dangerous);
# use a grace period to avoid racing with in-flight writes
timewarp ./timewarp.db ./blobs fsck <run_id> --gc-orphans --grace 5

Output shape:

{"missing": ["runs/<id>/events/12/output.bin", ...],
 "repaired": ["runs/<id>/events/12/output.bin", ...],
"orphans_gc": ["runs/<id>/events/99/output.bin.tmp", ...]}

SQLite & Concurrency

  • Single-writer-per-run: Timewarp enforces strictly increasing step per run_id. The events table uses (run_id, step) as its primary key and LocalStore guards with a monotonic check against MAX(step). Running multiple writers for the same run_id can cause UNIQUE violations or out-of-order errors. Recommended: one process per run ID.
  • PRAGMAs: Each connection applies journal_mode=WAL, synchronous=NORMAL, a configurable busy_timeout, and best-effort foreign_keys=ON, temp_store=MEMORY, and a journal_size_limit. These trade-offs aim for durable, fast appends.
  • JSON1 indexes (optional): Additional indexes rely on SQLite JSON1 (json_extract). When JSON1 isn’t available, index creation is skipped and a one-time warning is printed; queries still work but may fall back to table scans. Most modern Python builds ship SQLite with JSON1 enabled.
  • Blob finalization: Blobs are written to *.bin.tmp and promoted to final *.bin on event append. Reading a blob may also finalize the file if a matching .tmp exists.
  • Orphan GC: fsck --gc-orphans applies a grace window (--grace) to avoid deleting files created moments ago by in-flight writers.

Delta Debugging (Minimal Failing Delta)

Find the smallest contiguous mismatching window between two runs, accounting for anchor‑aware realignment to skip benign reorders:

# Text output
timewarp ./timewarp.db ./blobs diff <run_a> <run_b> --bisect

# JSON output (machine‑readable)
timewarp ./timewarp.db ./blobs diff <run_a> <run_b> --bisect --json
# => {"start_a": <int>, "end_a": <int>, "start_b": <int>, "end_b": <int>, "cause": <str>} | {"result": null}

# Tune anchor lookahead window (default 5)
timewarp ./timewarp.db ./blobs diff <run_a> <run_b> --bisect --window 3

Notes

  • Causes: "output hash mismatch" | "anchor mismatch" | "adapter/schema mismatch".
  • Benign reorders (by matching anchors) are excluded from the window.
  • If all aligned pairs match but lengths differ, the trailing unmatched step is reported with cause "anchor mismatch".
  • Exit codes: add --fail-on-divergence to return a non‑zero exit code when a divergence is found (applies to text and --json modes).

Deterministic Replay & What‑ifs (CLI)

Provide an app factory that returns a compiled LangGraph (example shipped):

--app examples.langgraph_demo.app:make_graph

Resume deterministically from a prior checkpoint:

timewarp ./timewarp.db ./blobs resume <run_id> --from 42 --thread t-1 --app examples.langgraph_demo.app:make_graph

Inject an alternative output at step N and fork:

timewarp ./timewarp.db ./blobs inject <run_id> 23 \
  --output alt_23.json \
  --thread t-1 \
  --app examples.langgraph_demo.app:make_graph \
  --record-fork   # execute and persist new branch immediately

Notes

  • The CLI binds playback wrappers via lightweight installers that intercept LangChain ChatModel/Tool calls during replay. Your graph runs without network/tool side‑effects in replay mode.
  • For forks, you can either prepare and record later, or pass --record-fork to execute and persist the new branch immediately. The new run is labeled with branch_of and override_step for lineage.
  • Snapshot knobs: snapshot_every controls cadence; snapshot_on can include "terminal" and/or "decision" to emit snapshots at run end and after routing decisions. You can also pass a state_pruner callable to trim large fields from state snapshots before persistence.
  • REPL filters: inside debug, run list type=LLM node=compose thread=t-1 to view a subset.
  • Pretty state: state --pretty prints truncated previews with size hints.
  • Save patch: savepatch STEP file.json writes the event’s output JSON for reuse with inject.
  • Event batching: event_batch_size batches DB writes for throughput. For heavy runs, try 50 or 100.

Tools, Prompt, and Memory Views

Inspect tools available to the model, prompts, and memory/retrieval state reconstructed per step and agent:

# Tools summary across LLM steps
timewarp ./timewarp.sqlite3 ./blobs tools <run_id>

# Tools detail for a specific LLM step
timewarp ./timewarp.sqlite3 ./blobs tools <run_id> --step 42 --json

# Memory snapshots (per agent/space)
timewarp ./timewarp.sqlite3 ./blobs memory summary <run_id> --step 120
timewarp ./timewarp.sqlite3 ./blobs memory show <run_id> --step 120 --space planner --json
timewarp ./timewarp.sqlite3 ./blobs memory diff <run_id> 100 140 --space planner --scope working --key messages.0
Tips in the interactive REPL
> tools            # summary across LLM steps
> tools 42         # detail for step 42
> prompt 42        # prompt parts (messages + tools), hashes, token estimate
> memory           # summary by agent at current step
> memory_show 120  # snapshot at step 120
> memory_diff 120 140 key=messages.0  # structural diff; optional dot path

Details

  • Available tools: extracted from recorded prompt parts when present; tools_digest shows a stable hash when details aren’t recorded.
  • Called tools: correlated by thread_id and node, scanning forward until the next LLM event on the same thread.
  • Token estimate: provider-agnostic heuristic (≈ chars/4) to quickly gauge prompt size; for precise tokens/costs integrate a tokenizer.
  • Privacy: printed payloads respect privacy_marks redaction.

Capturing LangGraph memory from values

To synthesize memory from LangGraph values stream, configure the recorder with memory_paths:

from timewarp.langgraph import LangGraphRecorder

rec = LangGraphRecorder(
    graph=graph,
    store=store,
    run=run,
    stream_modes=("updates", "values"),
    memory_paths=("messages", "history", "scratch", "artifacts", "memory"),
)

Each new/changed key under these paths emits a MEMORY event (mem_provider="LangGraphState") with stable hashes.item, labels.anchor_id, and inferred mem_scope from the path name. The CLI memory command reconstructs per-agent snapshots from these events.

Record‑time taps (determinism)

For stronger determinism checks, Timewarp can compute and store hashes.prompt and hashes.args at call sites (LangChain core). When using installers directly, start a recording session to scope staged hashes to the current run:

from timewarp.bindings import begin_recording_session, bind_langgraph_record

# Assuming you are using LangGraphRecorder with a concrete Run object
end_session = begin_recording_session(run.run_id)
teardown = bind_langgraph_record()
try:
    # run your graph under the recorder
    ...
finally:
    # Ensure both session and patches are cleaned up
    end_session()
    teardown()
  • The wrap(...) facade auto‑enables record taps with enable_record_taps=True and管理 the session lifecycle for you.
  • When not using wrap(...), prefer begin_recording_session(...) to avoid any cross‑run leakage; global fallbacks are removed in dev.

Telemetry

Enable OpenTelemetry by installing the extras and configuring an exporter in your app. Timewarp emits spans per event; replay spans use Span Links pointing to original spans. Attributes use the tw.* namespace.

Examples

  • Example LangGraph factory: examples/langgraph_demo/app.py provides make_graph() for quick --app usage in CLI.
  • CLI implementation: the console entrypoint is timewarp.cli:main, which dispatches to a decomposed CLI package under timewarp/cli/ (commands/helpers). Commands remain stable across versions.
  • Freeze-time example: examples/langgraph_demo/time_freeze_app.py provides make_graph_time() that writes timewarp.determinism.now() into state so you can verify identical timestamps on replay with --freeze-time.
  • Parallel branches example: examples/langgraph_demo/parallel_app.py demonstrates fan-out and join with DECISION anchors.

Multi‑Agent Demo

A realistic multi‑agent LangGraph with mock tools, a fake LLM, human‑in‑the‑loop (HITL), subgraphs, and dynamic routing:

  • File: examples/langgraph_demo/multi_agent_full.py
  • Features:
    • LLM events with prompt hashing via record‑time taps (FakeListChatModel)
    • TOOL events with MCP‑style metadata (tool_name, mcp_server, mcp_transport) and args hashing
    • Human‑in‑the‑loop using langgraph.types.interrupt + Command(goto=...)
    • Subgraph for review (draft_writerlight_edit) and dynamic routing to skip review
    • Snapshots on terminal + decisions; message‑rich state for pruning/pretty printing
  • Run it to record a run and demonstrate resume + what‑if:
uv run python -m examples.langgraph_demo.multi_agent_full

# Inspect with CLI (defaults: ./timewarp.sqlite3 ./blobs)
uv run timewarp ./timewarp.sqlite3 ./blobs list
uv run timewarp ./timewarp.sqlite3 ./blobs debug <run_id>

End‑to‑End Script (Record → Resume → Fork → Diff)

For a single, repeatable flow that exercises most features, use:

  • File: examples/langgraph_demo/run_all.py
  • What it does:
    • Builds the multi‑agent graph and records a baseline run
    • Resumes deterministically from the nearest checkpoint (no side‑effects)
    • Forks the run by overriding the first TOOL/LLM output (what‑if), records the branch
    • Computes first divergence and minimal failing window between base and fork
  • Uses a dedicated store by default to avoid local schema drift:
    • DB: tw_runs/demo.sqlite3
    • Blobs: tw_runs/blobs/
  • Run:
uv run python -m examples.langgraph_demo.run_all
# => prints JSON with run IDs, first divergence, and minimal window

Notes

  • Ensure optional deps installed: uv pip install langgraph langchain-core.
  • For CLI resume/inject, pass your app factory (e.g., examples.langgraph_demo.app:make_graph or examples.langgraph_demo.multi_agent_full:make_graph_multi).
  • Full multi‑agent example: examples/langgraph_demo/multi_agent_full.py exercises LLM, TOOL, DECISION, HITL, SNAPSHOT, subgraphs, parallel fan‑out with reducers, and async paths.
  • Tests exercise recorder, diff alignment, replay state reconstruction, and playback installers.

Full Multi‑Agent Demo

Record a representative multi‑agent workflow and exercise the debugger end‑to‑end:

python -m examples.langgraph_demo.multi_agent_full
# prints: Recorded run_id: <UUID>
# also records a what‑if fork and, if supported, an async run

The script builds a graph with:

  • LLM nodes (planner, review:draft_writer) and staged prompt hashes.
  • TOOL node (tooling) with MCP‑like metadata and privacy redaction on kwargs.
  • Parallel branches (planner, tooling, optional tooling_async) merged via a reducer on artifacts to avoid concurrent update conflicts.
  • A human HITL interrupt, DECISION events on routing, and periodic/terminal snapshots.
  • A review subgraph that streams when stream_subgraphs=True.

After recording, explore via CLI (defaults write to ./timewarp.sqlite3 and ./blobs):

timewarp ./timewarp.sqlite3 ./blobs list
timewarp ./timewarp.sqlite3 ./blobs debug <run_id>

Resume deterministically and run a what‑if injection (using this demo’s factory):

timewarp ./timewarp.sqlite3 ./blobs resume <run_id> \
  --app examples.langgraph_demo.multi_agent_full:make_graph_multi \
  --thread t-demo --freeze-time

timewarp ./timewarp.sqlite3 ./blobs inject <run_id> <step> \
  --output alt.json \
  --app examples.langgraph_demo.multi_agent_full:make_graph_multi \
  --thread t-demo --record-fork --freeze-time

Prompt overrides (DSPy-style)

Provide a JSON mapping of agent/node name to an override spec. A spec can be a string (treated as a system message or appended to a raw prompt) or an object with a mode and text.

Example overrides.json

{
  "planner": { "mode": "prepend_system", "text": "Be concise and accurate." },
  "review": "Prefer bullet points"
}
``

Apply overrides during a non-recorded resume (tolerate prompt-hash diffs with --allow-diff):

timewarp ./timewarp.sqlite3 ./blobs resume <run_id>
--app examples.langgraph_demo.multi_agent_full:make_graph_multi
--thread t-demo
--prompt-overrides ./overrides.json
--allow-diff


Fork and record a branch with overrides using inject:

timewarp ./timewarp.sqlite3 ./blobs inject <run_id> 0
--prompt-overrides ./overrides.json
--app examples.langgraph_demo.multi_agent_full:make_graph_multi
--thread t-demo
--allow-diff
--record-fork


Or use the dedicated helper:

timewarp ./timewarp.sqlite3 ./blobs dspy fork <run_id>
--app examples.langgraph_demo.multi_agent_full:make_graph_multi
--overrides ./overrides.json
--thread t-demo
--allow-diff
--record-fork


Each forked run is labeled with `branch_of=<baseline>` and `override_step=prompt_overrides`, so you can diff the branch against the baseline:

timewarp ./timewarp.sqlite3 ./blobs diff <baseline_run_id> <fork_run_id> --json


Event Filters Cheatsheet
------------------------

Focus on specific slices of the run quickly:

Only TOOL events (MCP) from the tooling node

timewarp ./timewarp.sqlite3 ./blobs events <run_id>
--type TOOL --tool-kind MCP --node tooling --json

Only LLM events from the planner node

timewarp ./timewarp.sqlite3 ./blobs events <run_id> --type LLM --node planner --json

HITL interrupts from the human node

timewarp ./timewarp.sqlite3 ./blobs events <run_id> --type HITL --node human --json

LLM events emitted inside the review subgraph (match by namespace)

timewarp ./timewarp.sqlite3 ./blobs events <run_id> --type LLM --namespace review --json

All DECISION anchors, useful to understand routing and joins

timewarp ./timewarp.sqlite3 ./blobs events <run_id> --type DECISION --json


Notes
-----

- The demo records one sync run (no async nodes) to keep `.invoke()` compatible,
  then tries an async run with `make_graph_multi(include_async=True)` using `.ainvoke()`.
- If your environment does not provide `graph.astream`, the async run is skipped.
- When using `wrap(...)` without an explicit `LocalStore`, the default DB path is
  `./timewarp.sqlite3` (examples above use that). Earlier examples may reference
  `./timewarp.db`; both are supported if you pass matching paths on the CLI.

MCP Example (optional)
----------------------

When `langgraph` and `langchain-mcp-adapters` are available, you can run the MCP demo app:

Record a run using the MCP example app

python - <<'PY' from pathlib import Path from timewarp.store import LocalStore from timewarp.events import Run from timewarp.langgraph import LangGraphRecorder from examples.langgraph_demo.mcp_app import make_graph_mcp

store = LocalStore(db_path=Path('./timewarp.db'), blobs_root=Path('./blobs')) graph = make_graph_mcp() run = Run(project='demo', name='mcp', framework='langgraph') rec = LangGraphRecorder(graph=graph, store=store, run=run, stream_modes=("messages","updates"), stream_subgraphs=True) _ = rec.invoke({"text":"hi"}, config={"configurable": {"thread_id": "t-1"}}) print('run_id=', run.run_id) PY

View TOOL events with MCP metadata

timewarp ./timewarp.db ./blobs events <run_id> --type TOOL --tool-kind MCP --json


Note: MCP metadata is best-effort and dependent on adapter/provider behavior. In environments
where the stream does not emit tool metadata, you may not observe TOOL events for MCP calls.

HITL & Privacy Docs
-------------------

- HITL patterns with LangGraph (DECISION anchors, snapshots, CLI tips): see `docs/hitl.md`.
- Privacy marks and redaction strategies (`redact`, `mask4`) with examples: see `docs/privacy.md`.

Time Provider & Freeze-Time
---------------------------

Use `timewarp.determinism.now()` in your graphs to obtain a deterministic clock.
Recording uses `now()` for `Run.started_at` and all `Event.ts`. During replay, you can
freeze time to the recorded event timestamps.

Programmatic replay:

from timewarp.replay import LangGraphReplayer

replayer = LangGraphReplayer(graph=my_graph, store=store) from timewarp.bindings import bind_langgraph_playback

Define an installer with the standard 3‑arg signature

def installer(llm, tool, memory) -> None: bind_langgraph_playback(my_graph, llm, tool, memory)

session = replayer.resume( run_id, from_step=None, thread_id="t-1", install_wrappers=installer, freeze_time=True )


CLI replay with frozen time:

timewarp ./timewarp.db ./blobs resume <run_id> --app examples.langgraph_demo.time_freeze_app:make_graph_time --thread t-1 --freeze-time

timewarp ./timewarp.db ./blobs inject <run_id> --output alt.json
--app examples.langgraph_demo.time_freeze_app:make_graph_time --thread t-1 --freeze-time --record-fork


Example graph writes the ISO timestamp to state (key `now_iso`). With `--freeze-time`,
replay preserves the exact value that was recorded.

Replay Convenience Facade
-------------------------

You can also resume deterministically via a one-call facade:

from timewarp import Replay

session = Replay.resume( store, app_factory="examples.langgraph_demo.app:make_graph", run_id=, from_step=42, thread_id="t-1", strict_meta=True, freeze_time=True, ) print(session.result)


Exporters
---------

Use the CLI to export a run in a LangSmith-friendly JSON bundle:

timewarp ./timewarp.db ./blobs export langsmith <run_id> --include-blobs


The module `timewarp.exporters.langsmith` also exposes `serialize_run(...)` and `export_run(...)` for programmatic use.

OpenTelemetry Quickstart
------------------------

See `docs/otel-quickstart.md` for a minimal setup to emit spans per event and link replay spans to recorded ones.

CLI Internals (Contributors)
----------------------------

- Entry point: `timewarp.cli:main` dispatches to a decomposed CLI under `timewarp/cli/`.
- Commands: `timewarp/cli/commands/*` implement subcommands (list, events, tools, diff, resume, inject, export, fsck, debug).
- Helpers: `timewarp/cli/helpers/*` contains small utilities used by the CLI only:
  - `jsonio`: `print_json`, `dumps_text`, `loads_file` (orjson‑backed).
  - `state`: `format_state_pretty`, `dump_event_output_to_file`.
  - `events`: `filter_events`.
  - `filters`: `parse_list_filters`.
- Stability: these helper modules are implementation details for the CLI and are not part of the public API; they may change between versions.
- Programmatic use: prefer the core modules and top‑level exports (`timewarp.events`, `timewarp.store`, `timewarp.diff`, `timewarp.replay`, `timewarp.langgraph`, etc.).

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

timewarp_llm-0.2.1.tar.gz (149.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

timewarp_llm-0.2.1-py3-none-any.whl (127.9 kB view details)

Uploaded Python 3

File details

Details for the file timewarp_llm-0.2.1.tar.gz.

File metadata

  • Download URL: timewarp_llm-0.2.1.tar.gz
  • Upload date:
  • Size: 149.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for timewarp_llm-0.2.1.tar.gz
Algorithm Hash digest
SHA256 015c9e7aa287a6a23a825d268891e5df6d347075b3faaec1a759ff8098989548
MD5 487938dbd70b5ab15e09404c2316b708
BLAKE2b-256 3cdf3f14cdf0d26adf9ed13fd186514900c90f790d6cff7c9757b01efc125257

See more details on using hashes here.

Provenance

The following attestation bundles were made for timewarp_llm-0.2.1.tar.gz:

Publisher: release.yml on aleks-apostle/timewarp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file timewarp_llm-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: timewarp_llm-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 127.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for timewarp_llm-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 788d681df56fe2e46f7b324d5ebdfb7e9639ec5ccfee5a0e39bfc8651a2f835d
MD5 96f934f75ef974df61849317f8bc1241
BLAKE2b-256 9b4c6da897a03d7399556f7056189187a2348e9c72f1c01e29dee777d4272d8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for timewarp_llm-0.2.1-py3-none-any.whl:

Publisher: release.yml on aleks-apostle/timewarp

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