跳转至

DeepSeek Harness 拆解的维护记录

这是 DeepSeek Harness 背后的事。正常阅读不用看,要对照上游更新、核对出处、重跑实验时再看。

版本和素材

  • 上游:deepseek-ai/deepseek-harness,MIT 许可。native/system/ 下的 Landlock 启动器子包是 BSD-3,两份许可都随存档放着
  • 读的版本:687f282(2026-10-17 提交,0.2.1-rc.2 发版合并),2026-10-20 读。各页不逐处标版本,上游更新了再对照这一个点
  • 源码存档:assets/dsh-src/ 只存细讲页 --8<-- 引到的文件,逐字未改,路径和原仓库一致。决策记录(原仓库 .agents/notes/)不存档:正文摘句,脚注钉这个 commit 的永链
  • 行号:模版标题里的 路径:起-止 是原仓库同版本的行号,存档逐字,两边一致
  • pi 的事实:pi 官网和文档经 Context7 读取(2026-10-20)。压缩对照组照抄了 @earendil-works/pi-coding-agent 1.3.0(MIT)里的两段提示词
  • 调研:先派五个子 agent 分读插件、会话日志、工具与沙箱、多 agent、全部 prompt 文本,再按第一轮盘问定下的六条线各派一个子 agent 实跑、写初稿,主会话统一术语、审稿、写入口篇

实验设置

材料在 assets/sim/<线>/:脚本在目录根,out/ 是原始记录,txt/ 是页面上逐块引用的效果。

  • 环境:在 /tmp 克隆上游,pnpm install --frozen-lockfile(跳过 Electron 二进制)再 pnpm run build,Node 24.16、pnpm 11.28.5。脚本按绝对路径 import 克隆里的源码或构建产物,重跑前要先把克隆和构建恢复到同一个 commit
  • 隔离:跑 dsh 代码的进程一律 env -i HOME PATH 起,不带任何密钥。模型请求由另一个进程发,持有 key 的只有它:
    • ds_call.py 直接发请求体(拼 prompt 线)
    • compaction/proxy.py 和 agents/relay.py 是监听 127.0.0.1 的转发进程:dsh 拿一个假 key、把 baseURL 指到本机,转发进程换上真 key 经本站中转发给 DeepSeek
  • 会话日志上传:dsh 默认在请求体里带 dsh_session_log,三个调用脚本转发前都剥掉了,日志实际没有上传。dsh 只看到转发进程回了 2xx,照样在日志里记了 session-log-deepseek/delivery-accepted
  • 模型:全部 deepseek-flash,思考开、effort high,都是 dsh 的默认值;走 DeepSeek 的 Anthropic 兼容接口(/anthropic/v1/messages)
  • 各线跑了什么:
    • 插件:Cordis、dsh-scope、工具注册表、agent 注册表直接从构建产物加载;--dump-config 在临时 DSH_HOME 里真跑。构造的:weather 插件、rm_cache 工具、三个拦截插件、DryRunBash、TinyLoop、三个 agent key、用户 patch。预设注册表本身没有整套跑
    • 会话日志:dsh 的 agent 循环加仓库测试用的 MockAdapter 跑四轮;项目自带的 request-reconstruction.spec.ts 30 条全部通过。模型回复、崩溃时刻、replace、v0 快照的占位符替换都是构造的。bash-tool-turn 快照的 manifest 标着人手写的场景,只用来看事件顺序
    • 拼 prompt:用项目的 Web 测试脚手架在进程内起真实组合,导出的 system prompt 和项目快照逐字节一致(5632 字节);用到的 10 个 headless 快照回放全部通过。Web 的 e2e 回放没跑完:本机没有 musl-gcc,构建不出发行版的启动器,bash 被沙箱拒掉。构造的:两轮提问、时间读数、read 的结果、三份 AGENTS.md
    • 压缩:dsh 自己的循环、压缩、裁剪、AGENTS.md 插件、read 工具和 DeepSeek 适配器真跑三遍。阈值从默认的 678,464 调到 15,000(第一遍 8,000),其余照 base 组合包;对话任务和三行 AGENTS.md 是写的。第二遍是试跑,原件没存档;页面用第三遍,「拒收」一段取自第一遍。超长后重试那一支没跑
    • 工具与沙箱:Landlock 启动器用本机 gcc 编上游 main.c,产物不进仓库(build-launcher.sh);沙箱、审批、工具注册表、观察策略、落盘都是真组合。构造的:审批回答方、delete_path 和 fetch_log 两个工具、「用户中途改文件」、落盘上限 300(默认 12500)、只认前缀的旧分类规则。三个放宽场景用上游快照测试重放。PTC、Auto review 没跑
    • 多 agent:dsh 的 headless 入口真跑 fork、spawn 两组父子会话,第 1 轮发 dsh 的 docs/architecture.md,两轮指令是写的。构造的:各种结局的结算通知、委派深度、目标折叠日志、定时提醒、重复挂起。后台任务完成通知(函数没导出)、续跑驱动、权限判断、Workflow 没跑
  • 用量(token):
实验 调用 未命中输入 命中缓存 输出
拼 prompt:两轮,第二轮按两种做法各发一次 4 28,910 34,176 821
压缩第一遍(阈值 8,000) 17 50,672 55,040 29,355
压缩第二遍(试跑) 13 45,886 89,984 13,660
压缩第三遍(正式) 15 52,610 97,024 20,492
压缩对照组:pi 格式 2 约 11,600 0 约 4,800
多 agent:fork、spawn 两组 15 13,540 132,992 1,515

实跑脚本

各线脚本清单:

  • plugin/:run.sh 一键重跑;reversible、seam、intercept、factory、scope、preset 六个 .mjs;resolve-hook.mjs 让仓库外的脚本按包名解析到克隆里;user.patch.yml 是构造的用户 patch
  • log/:loop.ts(四轮会话)、surface.ts、crash.ts、fork.ts、migrate.ts、snapshot-events.py
  • prompt/:dump/(Web 测试脚手架导出组装结果)、lab.ts(只调 dsh 的序列化、SSE 解析和 AGENTS.md 渲染)、run.py(编排和调模型)、extract.py
  • compaction/:run.ts、proxy.py、hook.mjs、control.py(pi 格式对照组)、lab.ts、lab-texts.ts、extract.py
  • tools/:build-launcher.sh、sandbox、profiles、escalation、pipeline、policy-context、fs-observe、spill、render 九个 .mts,lib.mts、replay.sh、events.py
  • agents/:run.sh、relay.py、lab.ts、analyze.py;重跑 lab.ts、analyze.py 前先把 out/sessions/*.zstd 解压
ds_call.py:把请求体发给 DeepSeek,剥掉 dsh_* 字段
"""Send a request body (JSON file) to DeepSeek's Anthropic-compatible Messages API via the wiki gateway.
Strips dsh_* top-level fields (session-log upload), never prints the key. Saves raw response."""
import json, os, sys, pathlib, urllib.request

ENV = pathlib.Path('/data2/work/lht/study/26.06/zensical-wiki/.env')
BASE = 'https://deepseek-proxy.liuhetian.work/anthropic/v1/messages'

def key():
    for line in ENV.read_text().splitlines():
        if line.startswith('MY_API_KEY='):
            return line.split('=', 1)[1].strip().strip('"').strip("'")
    raise SystemExit('MY_API_KEY missing')

def call(body, headers=None):
    body = {k: v for k, v in body.items() if not k.startswith('dsh_')}
    h = {'content-type': 'application/json', 'authorization': 'Bearer ' + key(), 'anthropic-version': '2023-06-01'}
    h.update(headers or {})
    req = urllib.request.Request(BASE, data=json.dumps(body).encode(), headers=h, method='POST')
    with urllib.request.urlopen(req, timeout=600) as r:
        return r.read().decode()

if __name__ == '__main__':
    src, dst = sys.argv[1], sys.argv[2]
    body = json.loads(pathlib.Path(src).read_text())
    hdrs = json.loads(sys.argv[3]) if len(sys.argv) > 3 else None
    pathlib.Path(dst).write_text(call(body, hdrs))
    print('saved', dst)
agents/relay.py:持有 key 的本机转发进程
"""Local relay for the agents-line experiment.

dsh runs in a key-less process with DEEPSEEK_BASE_URL=http://127.0.0.1:<port>/anthropic
and a dummy DEEPSEEK_API_KEY. This separate process holds the real key (read by
../ds_call.py from the wiki .env), strips the dsh_* top-level fields (session-log
upload), forwards the request to the DeepSeek Anthropic-compatible endpoint via the
wiki gateway, returns the raw response, and records every call:
  out/calls/NNN.request.json   the body dsh built (after stripping dsh_*)
  out/calls/NNN.response.txt   the raw response (SSE when dsh streams)
  out/calls/index.jsonl        one line per call: model, message count, usage
Usage: python3 relay.py <port> <out-dir>
"""
import json, pathlib, sys, threading, urllib.error, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import ds_call  # noqa: E402  (shared caller: key lookup + gateway base)

UPSTREAM = ds_call.BASE.rsplit('/v1/messages', 1)[0]  # .../anthropic
PORT, OUT = int(sys.argv[1]), pathlib.Path(sys.argv[2]) / 'calls'
OUT.mkdir(parents=True, exist_ok=True)
lock, counter = threading.Lock(), [0]
PASS_HEADERS = ('content-type', 'accept', 'anthropic-version', 'anthropic-beta')


def usage_of(raw: str) -> dict:
    usage = {}
    for line in raw.splitlines():
        if not line.startswith('data:'):
            continue
        try:
            ev = json.loads(line[5:])
        except ValueError:
            continue
        if ev.get('type') == 'message_start':
            usage.update(ev['message'].get('usage', {}))
        elif ev.get('type') == 'message_delta':
            usage.update(ev.get('usage', {}))
    if not usage:
        try:
            usage = json.loads(raw).get('usage', {})
        except ValueError:
            pass
    return usage


class Relay(BaseHTTPRequestHandler):
    def _forward(self, method: str) -> None:
        path = self.path.split('/anthropic', 1)[-1]
        body = None
        if method == 'POST':
            data = json.loads(self.rfile.read(int(self.headers['content-length'])))
            data = {k: v for k, v in data.items() if not k.startswith('dsh_')}
            body = json.dumps(data).encode()
        headers = {k: self.headers[k] for k in PASS_HEADERS if self.headers.get(k)}
        headers['authorization'] = 'Bearer ' + ds_call.key()
        req = urllib.request.Request(UPSTREAM + path, data=body, headers=headers, method=method)
        try:
            with urllib.request.urlopen(req, timeout=600) as r:
                status, ctype, raw = r.status, r.headers.get('content-type', ''), r.read()
        except urllib.error.HTTPError as e:
            status, ctype, raw = e.code, e.headers.get('content-type', ''), e.read()
        if body is not None:
            with lock:
                counter[0] += 1
                n = counter[0]
            text = raw.decode('utf-8', 'replace')
            (OUT / f'{n:03d}.request.json').write_text(json.dumps(data, ensure_ascii=False, indent=1))
            (OUT / f'{n:03d}.response.txt').write_text(text)
            with lock, open(OUT / 'index.jsonl', 'a') as f:
                f.write(json.dumps({'n': n, 'path': path, 'status': status, 'model': data.get('model'),
                                    'messages': len(data.get('messages', [])), 'usage': usage_of(text)}) + '\n')
        self.send_response(status)
        self.send_header('content-type', ctype)
        self.send_header('content-length', str(len(raw)))
        self.end_headers()
        self.wfile.write(raw)

    def do_POST(self):
        self._forward('POST')

    def do_GET(self):
        self._forward('GET')

    def log_message(self, *args):
        pass


ThreadingHTTPServer(('127.0.0.1', PORT), Relay).serve_forever()
compaction/proxy.py:持有 key 的本机转发进程,顺带记录用量
"""本机转发:dsh 的 DeepSeek 适配器把 baseURL 指到这里,这个进程换上 key 转给 DeepSeek 的
Anthropic 兼容接口(经本站中转),原样回传 SSE。跑 dsh 代码的进程里没有任何密钥。

每次请求存一对文件:out/proxy/NNN-request.json(剥掉 dsh_* 顶层字段后的请求体)、
NNN-response.sse(原始返回),并在 out/proxy/calls.jsonl 追加一行用量。

用法:python3 proxy.py <port> <out_dir>
"""
import json, pathlib, sys, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from ds_call import key  # noqa: E402  共享脚本里读 .env 的 MY_API_KEY,不打印

UPSTREAM = 'https://deepseek-proxy.liuhetian.work/anthropic'
PASS_HEADERS = ('content-type', 'anthropic-version', 'anthropic-beta', 'accept')

PORT, OUT = int(sys.argv[1]), pathlib.Path(sys.argv[2])
OUT.mkdir(parents=True, exist_ok=True)
count = 0


def usage_of(sse: str) -> dict:
    usage = {}
    for line in sse.splitlines():
        if not line.startswith('data: '):
            continue
        try:
            event = json.loads(line[6:])
        except ValueError:
            continue
        if event.get('type') == 'message_start':
            usage.update(event['message'].get('usage', {}))
        elif event.get('type') == 'message_delta':
            usage.update(event.get('usage', {}))
    return usage


class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        global count
        count += 1
        n = count
        raw = self.rfile.read(int(self.headers['content-length']))
        body = {k: v for k, v in json.loads(raw).items() if not k.startswith('dsh_')}
        (OUT / f'{n:03d}-request.json').write_text(json.dumps(body, ensure_ascii=False, indent=2))
        headers = {h: self.headers[h] for h in PASS_HEADERS if self.headers.get(h)}
        headers['authorization'] = 'Bearer ' + key()
        req = urllib.request.Request(UPSTREAM + self.path, data=json.dumps(body).encode(),
                                     headers=headers, method='POST')
        try:
            with urllib.request.urlopen(req, timeout=900) as r:
                status, ctype, data = r.status, r.headers.get('content-type'), r.read()
        except urllib.error.HTTPError as e:
            status, ctype, data = e.code, e.headers.get('content-type'), e.read()
        text = data.decode()
        (OUT / f'{n:03d}-response.sse').write_text(text)
        purpose = 'compaction' if self.headers.get('x-deepseek-harness-compact') == '1' else 'conversation'
        with open(OUT / 'calls.jsonl', 'a') as f:
            f.write(json.dumps({'n': n, 'status': status, 'purpose': purpose, 'model': body.get('model'),
                                'messages': len(body.get('messages', [])), 'usage': usage_of(text)}) + '\n')
        self.send_response(status)
        self.send_header('content-type', ctype or 'text/event-stream')
        self.send_header('content-length', str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def log_message(self, *args):
        pass


ThreadingHTTPServer(('127.0.0.1', PORT), Handler).serve_forever()
compaction/control.py:pi 格式的对照组
"""对照组:同一段被压的对话,按 baseline(pi 1.3.0)的格式另起一次摘要请求,看前缀缓存命中多少。

输入是 dsh 实跑里那次压缩请求(proxy 记下的 NNN-request.json):去掉末尾 dsh 的压缩指令,
其余消息按 pi 的 serializeConversation 格式转成一段文本([User]: / [Assistant]: /
[Assistant tool calls]: / [Tool result]:,工具结果截到 2000 字符),包进 <conversation>,
后接 pi 的 SUMMARIZATION_PROMPT;system 换成 pi 的 SUMMARIZATION_SYSTEM_PROMPT,不带 tools。
模型、max_tokens、thinking、effort 和 dsh 那次一致。pi 的两段提示词原文照抄自
@earendil-works/pi-coding-agent 1.3.0 的 dist/core/compaction/{utils,compaction}.js。

用法:python3 control.py <dsh-compaction-request.json> <out_dir>
"""
import json, pathlib, sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from ds_call import call  # noqa: E402

PI_SYSTEM = """You are a context summarization assistant. Your task is to read a conversation between a user and an AI assistant, then produce a structured summary following the exact format specified.

Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary."""

PI_PROMPT = """The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.

Use this EXACT format:

## Goal
[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]

## Constraints & Preferences
- [Any constraints, preferences, or requirements mentioned by user]
- [Or "(none)" if none were mentioned]

## Progress
### Done
- [x] [Completed tasks/changes]

### In Progress
- [ ] [Current work]

### Blocked
- [Issues preventing progress, if any]

## Key Decisions
- **[Decision]**: [Brief rationale]

## Next Steps
1. [Ordered list of what should happen next]

## Critical Context
- [Any data, examples, or references needed to continue]
- [Or "(none)" if not applicable]

Keep each section concise. Preserve exact file paths, function names, and error messages."""

TOOL_RESULT_MAX_CHARS = 2000


def text_of(blocks):
    if isinstance(blocks, str):
        return blocks
    return '\n'.join(b['text'] for b in blocks if b.get('type') == 'text')


def truncate(text, limit):
    return text if len(text) <= limit else f'{text[:limit]}\n\n[... {len(text) - limit} more characters truncated]'


def serialize(messages):
    parts = []
    for m in messages:
        blocks = m['content'] if isinstance(m['content'], list) else [{'type': 'text', 'text': m['content']}]
        if m['role'] == 'user':
            for b in blocks:
                if b['type'] == 'text' and b['text']:
                    parts.append(f"[User]: {b['text']}")
                elif b['type'] == 'tool_result':
                    content = text_of(b.get('content', ''))
                    if content:
                        parts.append(f'[Tool result]: {truncate(content, TOOL_RESULT_MAX_CHARS)}')
        else:
            texts = [b['text'] for b in blocks if b['type'] == 'text']
            calls = [f"{b['name']}(" + ', '.join(f'{k}={json.dumps(v, ensure_ascii=False)}' for k, v in b['input'].items()) + ')'
                     for b in blocks if b['type'] == 'tool_use']
            if texts:
                parts.append('[Assistant]: ' + '\n'.join(texts))
            if calls:
                parts.append('[Assistant tool calls]: ' + '; '.join(calls))
    return '\n\n'.join(parts)


src, out = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2])
dsh = json.loads(src.read_text())
messages = json.loads(json.dumps(dsh['messages']))
last = messages[-1]
# dsh 把压缩指令作为最后一条 user 的最后一个文本块;对照组把它拿掉,换成 pi 的提示词
assert last['role'] == 'user' and last['content'][-1]['type'] == 'text' and 'compaction engine' in last['content'][-1]['text']
last['content'] = last['content'][:-1]
if not last['content']:
    messages.pop()

body = {
    'model': dsh['model'],
    'max_tokens': dsh['max_tokens'],
    'thinking': dsh['thinking'],
    'output_config': dsh['output_config'],
    'system': PI_SYSTEM,
    'messages': [{'role': 'user', 'content': [{'type': 'text', 'text': f'<conversation>\n{serialize(messages)}\n</conversation>\n\n{PI_PROMPT}'}]}],
}
out.mkdir(parents=True, exist_ok=True)
(out / 'control-request.json').write_text(json.dumps(body, ensure_ascii=False, indent=2))
resp = call(body)
(out / 'control-response.json').write_text(resp)
print(json.dumps(json.loads(resp)['usage']))
plugin/run.sh:插件线一键重跑
#!/usr/bin/env bash
# 重跑插件线的全部实演。dsh 克隆需已 pnpm install && pnpm run build。
# 跑项目代码的进程一律 env -i,不带任何密钥。
set -euo pipefail
cd "$(dirname "$0")"
DSH_ROOT=${DSH_ROOT:-/tmp/dsh-src/deepseek-harness}
run() { env -i HOME="$HOME" PATH="$PATH" DSH_ROOT="$DSH_ROOT" node --import ./resolve-hook.mjs "$1" > "txt/${1%.mjs}.txt" 2>&1; }
for s in reversible seam intercept factory scope preset; do run "$s.mjs"; done

# dsh --dump-config:临时 DSH_HOME,先 dump 一次 web 和 sdk-minimal,再放入构造的用户 patch 重 dump
H=$(mktemp -d)
dsh() { (cd /tmp && env -i HOME="$HOME" PATH="$PATH" DSH_HOME="$H" node "$DSH_ROOT/apps/cli/lib/bin.js" "$@"); }
dsh --profile sdk-minimal --dump-config > out/dump-sdk-minimal.yml
dsh --profile web --dump-config > out/dump-web.yml
cp user.patch.yml "$H/profiles/web/cordis.patch.yml"
dsh --profile web --dump-config > out/dump-web-patched.yml 2> /tmp/dump-web-patched.err
{ sed "s#$H#\$DSH_HOME#g" /tmp/dump-web-patched.err; echo; diff out/dump-web.yml out/dump-web-patched.yml | sed "s#$H#\$DSH_HOME#g" || true; } > txt/user-patch-diff.txt
sed -i "s#$H#\$DSH_HOME#g" out/dump-web-patched.yml
sed -n 385,398p out/dump-web.yml > txt/dump-web-rows.txt