DeepSeek Harness: Session Logs and Trajectory

Last updated: 2026-08-31

Every Agent conversation is a non-reproducible journey — models have randomness, tools have side effects, context accumulates. DSH's Trajectory system uses "append-only logs" to completely record every step, enabling you to trace back, audit, fork, and restore the session state at any point in time.

💡 Tip: Trajectory is not just a log viewer — it's the core of session management. You can fork a branch to experiment without affecting the main line, or restore from any checkpoint to choose a different path.

📋 Prerequisites: Completed 07-python-sdk.md, familiar with SDK basics

1. What You'll Learn


Session Turn

2. Append-Only Log Design

(1) Why Append-Only?

Traditional log systems allow modification and deletion, but Agent session logs must be immutable — like a flight data recorder (black box), once a record is written it cannot be changed:

100%
graph LR
    E1[Event 1] --> E2[Event 2] --> E3[Event 3] --> E4[Event 4] --> E5[Event 5]
    E5 -.->|Append only| NEW[Event 6]
    
    style E1 fill:#e8f5e9
    style E2 fill:#e8f5e9
    style E3 fill:#e8f5e9
    style E4 fill:#e8f5e9
    style E5 fill:#e8f5e9
    style NEW fill:#fff3e0

Three principles of append-only design:

Principle Description Benefit
Immutable Once written, logs cannot be modified or deleted Complete audit trail
Ordered Events are strictly ordered by timestamp Replayable reproduction
Append-only Only new events can be added, no deletion No concurrency conflicts

(2) Comparison with Traditional Logs

Dimension Traditional Logs DSH Append-Only Logs
Modifiable ✅ Can modify/delete ❌ Cannot modify
Concurrency safe Requires locking Naturally safe (append only)
Rollback capability Depends on backups Restore from any point
Audit capability May be tampered with Tamper-proof
Storage efficiency Compressible Grows continuously (requires periodic archiving)

(3) Log Storage Structure

TEXT 📖 Display only
.dsh/
└── sessions/
    └── sess_abc123/
        ├── events.log          # Event log (append-only)
        ├── snapshots/          # State snapshots
        │   ├── snap_001.json
        │   ├── snap_002.json
        │   └── snap_003.json
        └── metadata.json       # Session metadata

3. SessionEvent Event Stream

(1) Event Types

Every operation in a DSH session is recorded as a SessionEvent:

TYPESCRIPT
type SessionEventType =
  | 'session.created'
  | 'session.config_changed'
  | 'user.message'
  | 'agent.message'
  | 'agent.thinking'
  | 'tool.call'
  | 'tool.result'
  | 'tool.approval.requested'
  | 'tool.approval.resolved'
  | 'session.forked'
  | 'session.restored'
  | 'error.occurred';

(2) Event Structure

Each SessionEvent contains standard fields:

TYPESCRIPT
interface SessionEvent {
  id: string;                  // Unique event ID
  type: SessionEventType;      // Event type
  timestamp: number;           // Unix timestamp (milliseconds)
  sessionId: string;           // Parent session ID
  data: Record<string, unknown>;  // Event payload data
  parentId?: string;           // Parent event ID (used for forks)
}

(3) Detailed Event Descriptions

User Message Event:

▶ Example 1: user.message Event

JSON
{
  "id": "evt_001",
  "type": "user.message",
  "timestamp": 1724486400000,
  "sessionId": "sess_abc123",
  "data": {
    "content": "Help me refactor the utils directory",
    "attachments": []
  }
}

Tool Call Event:

▶ Example 2: tool.call Event

JSON
{
  "id": "evt_002",
  "type": "tool.call",
  "timestamp": 1724486401500,
  "sessionId": "sess_abc123",
  "data": {
    "tool": "search",
    "params": {
      "pattern": "utils/*",
      "type": "file"
    },
    "mode": "standard"
  }
}

Tool Result Event:

▶ Example 3: tool.result Event

JSON
{
  "id": "evt_003",
  "type": "tool.result",
  "timestamp": 1724486402300,
  "sessionId": "sess_abc123",
  "data": {
    "toolCallId": "evt_002",
    "status": "success",
    "result": {
      "files": ["utils/format.ts", "utils/validate.ts", "utils/helpers.ts"]
    },
    "duration_ms": 800
  }
}

Approval Event:

▶ Example 4: tool.approval Event

JSON
{
  "id": "evt_004",
  "type": "tool.approval.requested",
  "timestamp": 1724486403000,
  "sessionId": "sess_abc123",
  "data": {
    "tool": "file_edit",
    "params": {
      "action": "edit",
      "path": "utils/format.ts"
    },
    "riskLevel": "high"
  }
}
JSON
{
  "id": "evt_005",
  "type": "tool.approval.resolved",
  "timestamp": 1724486405000,
  "sessionId": "sess_abc123",
  "data": {
    "approvalId": "evt_004",
    "decision": "allowed",
    "decidedBy": "user"
  }
}

(4) Complete Event Stream Example

TEXT 📖 Display only
Timeline                  Event Type
─────────────────────────────────────────
10:00:00.000   session.created
10:00:05.120   user.message          "Help me refactor the utils directory"
10:00:06.300   tool.call             search → utils/*
10:00:07.100   tool.result           Found 3 files
10:00:08.200   tool.call             file_edit → read utils/format.ts
10:00:08.500   tool.result           File content returned
10:00:10.800   agent.thinking        Analyzing refactoring plan...
10:00:12.000   tool.approval.requested   file_edit → edit
10:00:15.000   tool.approval.resolved    → allowed
10:00:15.200   tool.call             file_edit → edit utils/format.ts
10:00:15.600   tool.result           Edit complete
10:00:17.000   agent.message         "Refactoring complete!"

4. Trajectory View

(1) What Is Trajectory?

Trajectory is the visual interface for session logs, showing the Agent's complete "trajectory":

TEXT 📖 Display only
┌─ Trajectory View ──────────────────────────────────────┐
│                                                         │
│ 10:00  👤 Help me refactor the utils directory          │
│ 10:00    🔍 search(utils/*) → 3 files            0.8s  │
│ 10:00    📄 file_edit(read) → utils/format.ts    0.3s  │
│ 10:00    📄 file_edit(read) → utils/validate.ts  0.2s  │
│ 10:00    🤔 Thinking... Analyzing refactoring plan      │
│ 10:00    ⚠️ Approval: edit utils/format.ts              │
│ 10:00      → ✅ Allowed                                  │
│ 10:00    📝 file_edit(edit) → utils/format.ts    0.4s  │
│ 10:00    ⚠️ Approval: edit utils/validate.ts            │
│ 10:00      → ✅ Allowed                                  │
│ 10:00    📝 file_edit(edit) → utils/validate.ts  0.3s  │
│ 10:00    🤖 Refactoring complete! Extracted shared type definitions... │
│                                                         │
│ [Fork from here] [Restore to here] [Export]             │
└─────────────────────────────────────────────────────────┘

(2) Accessing Trajectory in Web UI

In the Web UI, click the log icon in the top control bar to open the Trajectory view:

TEXT 📖 Display only
Top control bar → 📋 → Trajectory

▶ Example 5: Filter by Event Type

TEXT 📖 Display only
Trajectory View Filters:
┌──────────────────────────────────────────┐
│ Filters:                                 │
│ ☑ user.message    ☑ agent.message        │
│ ☑ tool.call       ☑ tool.result          │
│ ☐ agent.thinking  ☐ approval events      │
│                                          │
│ Search: [Enter keywords...]              │
└──────────────────────────────────────────┘

(4) Accessing Trajectory via SDK

▶ Example 6: Get Event Stream via SDK

PYTHON
from dsh import DSHClient

client = DSHClient(base_url="http://127.0.0.1:3080")
session = client.get_session("sess_abc123")

# Get all events
events = session.get_trajectory()

for event in events:
    print(f"[{event.timestamp}] {event.type}: {event.data}")

# Filter by type
tool_events = session.get_trajectory(event_type="tool.call")

for event in tool_events:
    print(f"Tool: {event.data['tool']}")
    print(f"Params: {event.data['params']}")

5. Session Fork and Restore

(1) Fork Concept

Fork creates a session branch from a specific point in time — the main line continues forward while the branch develops independently:

100%
graph LR
    E1[Event 1] --> E2[Event 2] --> E3[Event 3] --> E4[Event 4]
    E3 -->|fork| F1[Fork Event 1] --> F2[Fork Event 2]
    
    E4 --> E5[Event 5]
    
    style E1 fill:#e8f5e9
    style E2 fill:#e8f5e9
    style E3 fill:#e8f5e9
    style E4 fill:#e8f5e9
    style E5 fill:#e8f5e9
    style F1 fill:#e3f2fd
    style F2 fill:#e3f2fd

(2) Fork Use Cases

Scenario Description
Solution exploration Try different approaches from the same node, compare results
Safe fallback Fork before destructive operations; fall back to main line if it fails
A/B testing Compare the same task using different models/modes
Experimental changes When unsure of results, try in a branch first

(3) Forking in Web UI

In the Trajectory view, click the "Fork from here" button next to any event:

TEXT 📖 Display only
10:00  📝 file_edit(edit) → utils/format.ts  [Fork from here]
10:00  ⚠️ Approval: edit utils/validate.ts    [Fork from here]
10:00  🤖 Refactoring complete!               [Fork from here]

Forking creates a new session starting from the selected event point, copying all context before that point.

(4) Forking via SDK

▶ Example 7: SDK Fork Operation

PYTHON
client = DSHClient(base_url="http://127.0.0.1:3080")
session = client.get_session("sess_abc123")

# Fork from the 5th event
forked = session.fork(after_event="evt_005")

print(f"Forked session: {forked.id}")
print(f"Parent: {forked.parent_id}")
print(f"Fork point: evt_005")

# Continue conversation in the forked branch
response = forked.send("Try a different refactoring approach, split by function")

(5) Restore

Restore is different from Fork — it rolls the current session back to a specified event point, discarding subsequent events:

▶ Example 8: SDK Restore Operation

PYTHON
# Restore to the 3rd event point
session.restore(to_event="evt_003")

# Events after evt_004, evt_005, etc. are marked as "restored-away"
# New events continue appending after evt_003

Note: Restore doesn't delete events (append-only principle), but marks subsequent events as invalid and adds a session.restored event.


6. Log Persistence

(1) Default Storage

DSH session logs are stored by default in the project directory's .dsh/sessions/:

TEXT 📖 Display only
.dsh/
├── sessions/
│   ├── sess_abc123/
│   │   ├── events.log        # Event log
│   │   ├── snapshots/        # State snapshots
│   │   └── metadata.json     # Metadata
│   └── sess_def456/
│       ├── events.log
│       └── ...
├── config.yaml               # DSH configuration
└── plugins/                  # Plugin directory

(2) Persistence Configuration

YAML
# dsh.config.yaml
storage:
  # Storage path
  base_path: ".dsh/sessions"
  
  # Snapshot strategy
  snapshots:
    enabled: true
    interval: 10              # Save a snapshot every 10 events
    max_snapshots: 5          # Keep at most 5 snapshots
  
  # Log rotation
  rotation:
    max_size_mb: 100          # Max 100MB per log file
    max_files: 50             # Keep at most 50 sessions
  
  # Archiving
  archive:
    enabled: true
    path: ".dsh/archive/"
    after_days: 30            # Auto-archive after 30 days

(3) Exporting Session Logs

▶ Example 9: Export as JSON

PYTHON
# Export complete session log
session = client.get_session("sess_abc123")
events = session.get_trajectory()

import json
with open("session_export.json", "w") as f:
    json.dump([e.to_dict() for e in events], f, indent=2)

▶ Example 10: Export as Markdown

BASH
# CLI export
dsh session export sess_abc123 --format markdown --output session.md

# Output format
# # Session: sess_abc123
# ## 10:00 - User
# Help me refactor the utils directory
# ## 10:00 - Tool: search
# Pattern: utils/* → 3 files found
# ...

(4) Log Cleanup

BASH
# List all sessions (sorted by size)
dsh session list --sort size

# Archive old sessions
dsh session archive --older-than 30d

# Delete archived sessions (irreversible)
dsh session clean --archived-only

7. Trajectory and Auditing

(1) Operation Auditing

Trajectory records every Agent operation, making it naturally suitable for auditing:

100%
graph TB
    AUDIT[Audit Need] --> T1[Who executed it?]
    AUDIT --> T2[When?]
    AUDIT --> T3[What was done?]
    AUDIT --> T4[What was the result?]
    
    T1 --> TRAJ[Trajectory Event Stream]
    T2 --> TRAJ
    T3 --> TRAJ
    T4 --> TRAJ

(2) Compliance Scenarios

Compliance Requirement How Trajectory Meets It
Operation traceability Every event has ID, timestamp, operator
Tamper-proof changes Append-only log, cannot modify history
Approval records approval.requested + approval.resolved complete record
Rollback capability Restore from any checkpoint

(3) Generating Audit Reports

▶ Example 11: Generating an Audit Report

PYTHON
session = client.get_session("sess_abc123")
events = session.get_trajectory()

report = {
    "session_id": session.id,
    "duration": events[-1].timestamp - events[0].timestamp,
    "user_messages": len([e for e in events if e.type == "user.message"]),
    "tool_calls": len([e for e in events if e.type == "tool.call"]),
    "approvals_requested": len([e for e in events if e.type == "tool.approval.requested"]),
    "approvals_denied": len([e for e in events if e.type == "tool.approval.resolved" and e.data.get("decision") == "denied"]),
    "files_modified": list(set([
        e.data.get("path") for e in events
        if e.type == "tool.call" and e.data.get("tool") == "file_edit"
    ])),
    "errors": len([e for e in events if e.type == "error.occurred"])
}

import json
print(json.dumps(report, indent=2))

❓ FAQ

Q Will append-only logs grow infinitely?
A Yes, but DSH provides archiving and rotation mechanisms. Configure rotation.max_size_mb and archive.after_days for automatic storage management.
Q Do forked sessions share data with the original?
A Fork copies a context snapshot at the time of forking; after that, they're completely independent. Changes don't affect each other.
Q Does Restore actually delete historical events?
A No. The append-only principle guarantees events are never deleted. Restore only marks subsequent events as invalid and starts appending new events from the restore point.
Q Can Trajectory data be exported?
A Yes. Supports JSON, Markdown, and CSV format export. CLI uses dsh session export; SDK uses the get_trajectory() method.
Q Can multiple users see the same session's Trajectory?
A DSH defaults to single-user mode, so there's no multi-user sharing issue. If using shared storage (e.g., NFS), multiple DSH instances can read the same logs.
Q How can I use Trajectory in CI/CD?
A Use the SDK to export the event stream, analyze tool call counts, approval denial rates, error rates, etc., as quality gates.

📖 Summary


📝 Exercises

1. ⭐ Basic: Complete an Agent conversation (with at least 2 tool calls), open the Trajectory view, and list all events with their types and timestamps.

2. ⭐⭐ Intermediate: Fork a branch from the 3rd event of a session, try a different approach in the branch. Compare the final results of the main line and the branch.

3. ⭐⭐⭐ Challenge: Use the Python SDK to write a Trajectory analysis tool — input a session ID, automatically generate an audit report (including operation statistics, approval records, modified file list, error summary), output as JSON format.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏