deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
PROJECT README
English | 中文
Git-snapshot checkpoint/rollback capability for DeepSeek Harness (dsh).
Roll a session back to a previous completed turn: in a git worktree it restores both the files and the conversation; in a non-git worktree it rolls back only the conversation and the tool result says so.
Status: spike resolved, implementing. The Task 0 spike is complete with outcome B; the interface contract below is the post-spike design. This README is the authoritative hand-off document.
turn/end boundaries. Per-step capture (opencode-style) is a later enhancement, explicitly out of MVP.<dataDir>/checkpoint/<sessionId>/git/ (git init with GIT_DIR/GIT_WORK_TREE), holding tree hashes only — no commits, branches, or refs in the user's repo. When the workspace is already a git repo, seed the object DB from it via objects/info/alternates plus a copied index, so already-hashed blobs are reused (near-zero cost on large repos). Ignored files are skipped; untracked files above a size cap (default 2 MiB) are skipped.<dataDir>/checkpoint/<sessionId>/index.json (one record per turn: turn, tree hash, changed paths, timestamp). See §Task 0 for why the index does not live in the session log.git rev-parse --is-inside-work-tree). Git repo → file+conversation rollback. Not a git repo (or no git binary) → conversation-only rollback; checkpoint_list reports isGitRepo: false and checkpoint_restore refuses file restore.ctx.sessions.fork(source, boundarySeq, childId) cuts the log at the target turn's inclusive turn/end seq; the git layer restores files to the same boundary. There is no in-place truncation.Question: can this plugin's live events carry ignorable: true? Answer: no.
Session.append (packages/core/session/src/index.ts) builds the envelope from { type, seq, time, data, surfaceOp?, sourceEventSeqs? } only; SurfaceIntent has no further fields. The live path has no channel for the marker.session-persistence/src/coordinator.ts, the unknown-type guard) refuses any event type outside KNOWN_SESSION_EVENT_TYPES unless the persisted envelope carries ignorable: true.ignorable: true; the seed/restore envelope validator accepts the key, but live production cannot reach it.Consequence: the plugin appends no custom session events. The snapshot index is a plugin-owned file; model visibility rides on the two tools, whose tool/call/tool/result are known event types (reload-safe). If the harness later opens an ignorable path for live appends, the plugin can migrate back to logged checkpoint/captured events plus a projection — do not re-add events before that.
<dataDir>/checkpoint/<sessionId>/index.json)interface CheckpointRecord {
turn: number
treeHash: string | null // null when the workspace is not a git repo
changed: string[] // paths changed during this turn
capturedAt: number // epoch ms
}
checkpoint_list — args {}; result: { isGitRepo: boolean, points: { turn, changedCount, capturedAt }[] }.checkpoint_restore — args { turn: number }; performs fork+reseed + file restore + pre-revert self-snapshot; result: { restored: string[], childSessionId?: string, contextOnly: boolean, undoPoint?: number }. In a non-git worktree, restored is empty and contextOnly: true./checkpoint <turn> — registered on the harness commands service when present (user-invoked from the command palette; no model turn involved). Rolls the active session back to the turn like checkpoint_restore, rendering the outcome directly in the UI. A trailing number names the turn; empty input replies with usage and the available turns.interface Config {
dataDir?: string // default: <dsh home>/checkpoint
untrackedFileMaxBytes?: number // default 2 MiB
}
Plugin shape: function plugin, named exports name / inject / Config / apply, no default export (harness convention). Injects subprocess, sessions, tools.
agent/pre-step and the session/event feed are the capture triggers — the plugin only READS them; it appends nothing. Listen for turn/start (capture pre-tree) and turn/end (capture post-tree + patch). The session's workspace is session.header.cwd (durable).ctx.sessions.fork(source, boundary?, childId?) — inclusive boundary seq; the selected prefix must end outside an open turn (else OPEN_TURN); error codes SESSION_NOT_FOUND | SESSION_NOT_LIVE | SESSION_ALREADY_EXISTS | INVALID_BOUNDARY | OPEN_TURN. Child inherits cwd, stamps parentSession, sets seedLength.ctx.subprocess.spawn({ argv, cwd, ... }); resolve the binary with ctx.subprocess.resolveExecutable('git') and degrade gracefully when it resolves to nothing. No process.platform branching anywhere.sessions, subprocess (subprocess-local), tools; a full-session smoke additionally needs the subagent stack, shell/shellEnv, fs, web, userQuestions, jobs, skills — mirroring any preset-driven session.src/git-snapshot.ts — the isolated git object DB (init/track/restore/patch), alternates seeding, untracked size cap. Unit-tested against a real git binary in a tmpdir.src/index.ts — the function plugin: Config, git-worktree detection per session, capture hooks, index.json management, the two tools, fork+reseed + staged restore.checkpoint_restore to the previous turn — files restored, conversation cut at that turn, and the restore itself undoable.checkpoint_list reports isGitRepo: false; checkpoint_restore rolls back conversation only and reports contextOnly: true; files untouched.bin entry; ESM-only; @deepseek-ai/cordis + every imported @deepseek-ai/dsh-* in peerDependencies.The harness packages are not yet on a stable public registry cadence, so develop with a local checkout:
deepseek-harness next to this repo, pnpm install && pnpm run build (the build generates lib/typert.* artifacts the launch needs).devDependencies via file: paths (e.g. file:../deepseek-harness/packages/core/session).ctx.baseUrl pointed at apps/cli, mounts the plugin under test from this repo, and drives it — then delete the spec. A real-composition boot test in this repo is the long-term home.MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。