Skip to content

Time-Travel Debugging for AI Agents 🎬

Published: January 15, 2025

What if you could record an AI agent's session and replay it later? DevDuck's session recording feature enables exactly that - a time-travel debugger for AI agents.

The Vision

sequenceDiagram
    participant User
    participant DevDuck
    participant Recorder
    participant ZIP as session.zip

    User->>DevDuck: session_recorder(action="start")
    Recorder->>Recorder: Start capturing

    User->>DevDuck: "analyze this code"
    DevDuck->>Recorder: Record tool calls
    DevDuck->>Recorder: Record agent messages
    DevDuck->>Recorder: Record sys events

    User->>DevDuck: session_recorder(action="snapshot")
    Recorder->>Recorder: Capture state

    User->>DevDuck: session_recorder(action="stop")
    Recorder->>ZIP: Export all events

    Note over ZIP: Complete session<br/>ready for replay

Three Layers of Recording

flowchart TB
    subgraph "Agent Layer"
        AM[Agent Messages]
        AD[Agent Decisions]
        AS[Agent State]
    end

    subgraph "Tool Layer"
        TC[Tool Calls]
        TR[Tool Results]
        TD[Tool Duration]
    end

    subgraph "System Layer"
        FO[File Operations]
        NO[Network Ops]
        EV[Environment]
    end

    AM & AD & AS --> Buffer
    TC & TR & TD --> Buffer
    FO & NO & EV --> Buffer

    Buffer --> Export[session.zip]

    style Buffer fill:#f9f,stroke:#333,stroke-width:4px

Event Types

@dataclass
class RecordedEvent:
    timestamp_ns: int    # Nanosecond precision
    layer: str           # "sys", "tool", "agent"
    event_type: str      # "file.open", "tool.call", "message"
    data: Dict[str, Any] # Event-specific data
    trace_id: str        # For correlation

Recording a Session

Start Recording

🦆 session_recorder(action="start")

# Output:
🎬 Recording started: session-20260203-143052
Events will be captured at sys/tool/agent layers.

Create Snapshots

# After important milestones
🦆 session_recorder(action="snapshot", description="after API integration")

# Output:
🎬 Snapshot #1 created: after API integration

Stop and Export

🦆 session_recorder(action="stop")

# Output:
🎬 Recording stopped and exported!
Session: session-20260203-143052
Events: 847
Snapshots: 3
Export: /tmp/devduck/recordings/session-20260203-143052.zip

The Session ZIP Format

session-20260203-143052.zip
├── events.jsonl      # All events in JSON Lines format
├── snapshots.json    # State snapshots with conversation history
├── metadata.json     # Session info
└── session.pkl       # Serialized state (for true replay)

events.jsonl

{"timestamp_ns": 1706973052000000000, "layer": "agent", "event_type": "session.start", "data": {"session_id": "session-20260203-143052"}}
{"timestamp_ns": 1706973053000000000, "layer": "tool", "event_type": "tool.call", "data": {"name": "shell", "args": {"command": "ls -la"}}}
{"timestamp_ns": 1706973054000000000, "layer": "sys", "event_type": "file.open", "data": {"path": "/tmp/test.txt", "mode": "r"}}

snapshots.json

[
  {
    "snapshot_id": 1,
    "timestamp": 1706973100.5,
    "agent_messages_count": 5,
    "agent_messages": [
      {"role": "user", "content": "analyze this code"},
      {"role": "assistant", "content": "I'll examine..."}
    ],
    "system_prompt": "You are DevDuck...",
    "last_query": "analyze this code",
    "last_result": "Found 3 issues..."
  }
]

Loading and Analyzing Sessions

from devduck import load_session

# Load a recorded session
session = load_session("~/Desktop/session-20260203-143052.zip")

print(session)
# LoadedSession(id=session-20260203-143052, events=847, snapshots=3, duration=245.3s)

# Filter events by layer
tool_events = session.get_events_by_layer("tool")
print(f"Tool calls: {len(tool_events)}")

# Filter by event type
file_opens = session.get_events_by_type("file.open")
print(f"Files opened: {len(file_opens)}")

# Get events up to a snapshot
events_before_snapshot = session.get_events_until_snapshot(2)

Resuming from Snapshots

sequenceDiagram
    participant User
    participant Session as LoadedSession
    participant Agent as DevDuck Agent

    User->>Session: resume_from_snapshot(2)
    Session->>Session: Load snapshot #2
    Session->>Agent: Restore messages
    Session->>Agent: Restore state
    Session->>User: Resume result

    User->>Agent: Continue working...

    Note over Agent: Conversation<br/>history intact!

Resume API

from devduck import load_session

session = load_session("session.zip")

# Resume from snapshot (restores agent.messages!)
result = session.resume_from_snapshot(2, agent=my_agent)
print(f"Restored {result['messages_restored']} messages")

# Resume and continue with new query
result = session.resume_and_continue(
    snapshot_id=2,
    new_query="what was I working on?",
    agent=my_agent
)
print(result['agent_result'])

CLI Resume

# Resume from latest snapshot
$ devduck --resume ~/Desktop/session-20260203-143052.zip

# Resume from specific snapshot
$ devduck --resume session.zip --snapshot 2

# Resume and run query
$ devduck --resume session.zip "continue where we left off"

Use Cases

1. Debugging Agent Behavior

flowchart LR
    Bug[Bug Report] --> Load[Load Session]
    Load --> Analyze[Analyze Events]
    Analyze --> Find[Find Root Cause]
    Find --> Fix[Fix Agent]
session = load_session("problematic-session.zip")

# Find the failing tool call
for event in session.get_events_by_layer("tool"):
    if "error" in str(event.data).lower():
        print(f"Error at {event.timestamp_ns}: {event.data}")

2. Training Data Generation

# Extract all successful interactions
successful_sessions = []
for session_path in Path(RECORDING_DIR).glob("*.zip"):
    session = load_session(str(session_path))
    if session.metadata.get("status") == "success":
        successful_sessions.append(session.to_dict())

# Export for training
with open("training_data.json", "w") as f:
    json.dump(successful_sessions, f)

3. Long-Running Task Continuation

# Start a long analysis session
🦆 session_recorder(action="start")
🦆 analyze all Python files in this project

# ... computer crashes or you close terminal ...

# Later, resume where you left off
$ devduck --resume /tmp/devduck/recordings/session-*.zip
✅ Resumed from snapshot #3
   Messages restored: 12
   Working directory: /Users/dev/project

4. Collaboration & Sharing

# Share your agent session with a colleague
$ cp /tmp/devduck/recordings/session-*.zip ~/Desktop/agent-session.zip
$ # Send to colleague...

# Colleague loads and continues
$ devduck --resume agent-session.zip "what did you find?"

Recording Architecture

flowchart TB
    subgraph "Recording System"
        Recorder[SessionRecorder]
        Buffer[EventBuffer]
        Hooks[System Hooks]
    end

    subgraph "Hooks"
        OpenHook[builtins.open]
        RequestsHook[requests.get]
    end

    subgraph "Events"
        ToolCall[Tool Calls]
        AgentMsg[Agent Messages]
        SysEvents[System Events]
    end

    OpenHook --> SysEvents
    RequestsHook --> SysEvents
    ToolCall --> Buffer
    AgentMsg --> Buffer
    SysEvents --> Buffer
    Buffer --> Recorder
    Recorder --> Export[ZIP Export]

    style Recorder fill:#f9f,stroke:#333,stroke-width:4px

Event Buffer

class EventBuffer:
    """Ring buffer for recording events across all layers."""

    def __init__(self, max_events: int = 10000):
        self.events = deque(maxlen=max_events)

    def record(self, layer: str, event_type: str, data: dict):
        event = RecordedEvent(
            timestamp_ns=time.time_ns(),
            layer=layer,
            event_type=event_type,
            data=data,
        )
        self.events.append(event)

System Hooks

def _install_hooks(self):
    """Install hooks to capture OS-level events."""

    # Hook: builtins.open
    self._original_open = builtins.open
    def traced_open(file, mode="r", *args, **kwargs):
        self.record_sys_event("file.open", {"path": str(file), "mode": mode})
        return self._original_open(file, mode, *args, **kwargs)
    builtins.open = traced_open

    # Hook: requests.get
    import requests
    self._original_requests_get = requests.get
    def traced_get(url, *args, **kwargs):
        self.record_sys_event("http.get", {"url": str(url)[:200]})
        return self._original_requests_get(url, *args, **kwargs)
    requests.get = traced_get

Best Practices

Recording Tips

  1. Start early: Begin recording at session start
  2. Snapshot often: After major milestones
  3. Describe snapshots: Use meaningful descriptions
  4. Clean up: List and delete old sessions periodically
# List all recorded sessions
🦆 session_recorder(action="list")

# Shows:
🎬 Recorded Sessions:
- session-20260203-143052.zip (2.1 KB)
- session-20260202-091523.zip (15.3 KB)

Storage Considerations

  • Events are truncated to prevent huge files
  • Ring buffer limits to 10,000 events
  • Sensitive data (API keys) is redacted
  • ZIP compression reduces file size

Future: Replay Mode

sequenceDiagram
    participant User
    participant Replayer
    participant Events
    participant Agent

    User->>Replayer: replay_events(callback=handler)

    loop For each event
        Replayer->>Events: Get next event
        Events->>Replayer: Event data
        Replayer->>Agent: Apply event
        Agent->>User: Show result
        User->>Replayer: Continue/Pause
    end

Coming soon: Step-by-step replay with pause/resume, allowing you to "debug" an agent session like debugging code.


Record. Analyze. Resume. Repeat. 🎬