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.
📋 Prerequisites: Completed 07-python-sdk.md, familiar with SDK basics
1. What You'll Learn
- Append-only log design principles
- SessionEvent event stream types and structure
- Trajectory view usage
- Session fork and restore mechanisms
- Log persistence and export
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:
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
.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:
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:
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
{
"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
{
"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
{
"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
{
"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"
}
}
{
"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
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":
┌─ 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:
Top control bar → 📋 → Trajectory
(3) Trajectory Filtering and Search
▶ Example 5: Filter by Event Type
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
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:
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:
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
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
# 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.restoredevent.
6. Log Persistence
(1) Default Storage
DSH session logs are stored by default in the project directory's .dsh/sessions/:
.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
# 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
# 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
# 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
# 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:
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
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
rotation.max_size_mb and archive.after_days for automatic storage management.dsh session export; SDK uses the get_trajectory() method.📖 Summary
- DSH session logs use an append-only design: immutable, ordered, growing only
- SessionEvent includes 13 event types covering conversations, tools, approvals, errors, etc.
- Trajectory view visualizes the Agent's complete execution trajectory
- Fork creates branches from any node without affecting the main line; Restore rolls back to a specified node
- Log persistence supports snapshots, rotation, archiving, and export
- Trajectory naturally supports operation auditing and compliance requirements
- Both SDK and CLI can access and operate on Trajectory data
📝 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.