docs: refresh top-level README + add dsh-arch-diagram skill
Replaces the 20-line stub with a bilingual README pair covering:
- getting started (`pnpm run demo:repl` + Node ^22.19 || ≥24 + pnpm 11.7)
- editor integration (Zed / VS Code / Cursor over ACP)
- programmatic embedding via `@deepseek-ai/dsh-app-boot` (with the
two failure paths of `boot()` vs `installFailLoud` spelled out)
- writing a plugin (`echo-tool` grounded in `examples/echo-agent`, with
the runnable command `pnpm run demo:echo`)
- Packages table grouped by family, including `session-query`,
`context`, and Support (with `invariants` called out as a runtime
diagnostic mounted by `dsh-agent-core`, not test-only)
- deep-dive links, community, license
Bilingual pairing: English and Chinese sides share byte-identical code
blocks and mirroring link ordinals; language-asymmetric community
channels (Discord/X in EN, WeCom in ZH) sit inside HTML `<a href>`
tags that the pairing gate's structural signature excludes by design.
ZH side follows `docs/i18n/terminology.md` — first `agent` occurrence
annotated as `agent(智能体)`, plain `agent` thereafter.
Ships an in-repo skill at `.agents/skills/dsh-arch-diagram/` that owns
the two architecture PNGs (`assets/arch-{en,zh}.png`). HTML templates
+ shared CSS + a `render.sh` that renders via Chrome headless. The
renderer is portable (auto-detects Chrome/Chromium on macOS + Linux,
uses `--no-sandbox` under root for container envs, discovers the local
http.server port from Python's own startup line so `lsof` isn't
required). The 12-card capability row uses `flex: 0 0 auto` so
adjacent card backgrounds cannot clip descriptions; layout tuned to
1560px page / 96% row-width / 1820×580 render viewport.
Consolidates the four review-response commits from the initial round
of ds-review-bot feedback; each review round is documented in a
top-level PR comment for traceability.
This commit is contained in:
12 files changed
+1069
-22
No files matched your search
@@ -0,0 +1,158 @@
|
||||
---
|
||||
name: dsh-arch-diagram
|
||||
description: Regenerate the DeepSeek Harness "System overview" architecture PNGs (English + Chinese) that the top-level README embeds. Reflects the current state of packages/* and docs/architecture.md. Use when the codebase adds/removes/renames services and the README diagram must be refreshed.
|
||||
---
|
||||
|
||||
# dsh-arch-diagram
|
||||
|
||||
Produces `assets/arch-en.png` and `assets/arch-zh.png` in this repo — the two hero images embedded at the top of `README.md` and `README.zh.md`.
|
||||
|
||||
The pipeline is HTML + Chrome headless, so text quality, layout, and connector geometry are **deterministic** — every rerun produces the same output regardless of model.
|
||||
|
||||
Aesthetic is locked to the DeepSeek brand: light blue gradient background, brand-blue Cordis bar, sans-serif everywhere, no serif / no italic / no hand-drawn feel.
|
||||
|
||||
---
|
||||
|
||||
## When to use
|
||||
|
||||
- User asks to "regenerate" or "refresh" the Harness architecture diagram
|
||||
- User points out `packages/` has changed (new services, renames) and the README diagram is stale
|
||||
- User just added a new `packages/<family>/` or a new `ctx.*` service and wants the diagram to reflect it
|
||||
|
||||
## When NOT to use
|
||||
|
||||
- User wants a different diagram (Code Mode, Workflow fanout, agent lifecycle) — those belong in `docs/` and need their own skill
|
||||
- User wants a different visual aesthetic (dark mode, hand-drawn, editorial-serif) — this skill is locked to DeepSeek brand
|
||||
|
||||
---
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Enumerate current services from the repo
|
||||
|
||||
The diagram is grounded in two lists that live in the docs:
|
||||
|
||||
- **Default Services** — from `packages/core/*` and `docs/architecture.md`'s "Default Services" table. As of last refresh: `ctx.sessions`, `ctx.systemPrompt`, `ctx.tools`, `ctx.agents`, `ctx.agentLoop`.
|
||||
- **Capability Services** — from the non-core capability packages and `docs/architecture.md`'s "Capability Services" table. As of last refresh: `ctx.llm`, `ctx.bash`, `ctx.sandbox`, `ctx.codeRuntime`, `ctx.fs`, `ctx.skills`, `ctx.web`, `ctx.compact`, `ctx.subagents`, `ctx.workflows`, `ctx.sessionPersistence`, `ctx.sessionQuery`.
|
||||
|
||||
**Do this**:
|
||||
|
||||
1. Read `docs/architecture.md`
|
||||
2. Extract the two `ctx.*` tables verbatim
|
||||
3. Cross-check against `packages/`: services live under `packages/core/*` (defaults) or under `packages/<family>/<package>/` (capabilities). The inner package dir mirrors the npm name without the `@deepseek-ai/dsh-` prefix — e.g. `packages/session-query/session-query/` publishes `@deepseek-ai/dsh-session-query`. Never assume the inner dir carries a `dsh-` prefix.
|
||||
|
||||
### 2. Diff against the current HTML templates
|
||||
|
||||
Read `harness-arch-en.html` and `harness-arch-zh.html` in this skill directory. Pull out the currently-rendered `ctx.*` names. Report the diff:
|
||||
|
||||
```
|
||||
Default services: no change
|
||||
Capability services: + ctx.<new>, − ctx.<removed>, ↻ ctx.<renamed>
|
||||
```
|
||||
|
||||
### 3. Update the templates
|
||||
|
||||
If there are changes, edit both HTML files in place. Each card is one line:
|
||||
|
||||
```html
|
||||
<div class="card"><span class="name">ctx.something</span><span class="desc">short description</span></div>
|
||||
```
|
||||
|
||||
For a **new** service:
|
||||
- Description is **short** — 2–4 words (EN), 3–6 chars (ZH)
|
||||
- Ground the description in what the package's README or its `ctx.<name>.register()` **actually** does. Do NOT paraphrase from an abstract or invent capabilities the code doesn't have.
|
||||
- Add the card in **both** languages, in docs order.
|
||||
|
||||
For a **removed** service: delete the card in both files.
|
||||
|
||||
For a **renamed** service: update the `.name` span in both files.
|
||||
|
||||
**Do not touch the layout**. Rows are:
|
||||
- Top row (`row top`): exactly 5 default-service cards
|
||||
- Bottom row (`row bottom`): 12 capability-service cards
|
||||
|
||||
If the capability count grows past ~13, the row gets visually tight. Stop and ask the user before shrinking fonts or wrapping to two rows.
|
||||
|
||||
### 4. Render to PNG
|
||||
|
||||
```bash
|
||||
bash .agents/skills/dsh-arch-diagram/render.sh
|
||||
```
|
||||
|
||||
Default output: `<repo>/assets/arch-en.png` and `<repo>/assets/arch-zh.png` — the paths that `README.md` and `README.zh.md` already reference.
|
||||
|
||||
Pass an alternate directory as `$1` to write elsewhere. `CHROME` and `PORT` env vars override the browser path and http.server port.
|
||||
|
||||
The script:
|
||||
- Spins up a temporary `python3 -m http.server` on `127.0.0.1` (needed so Google Fonts CDN loads reliably in Chrome headless)
|
||||
- Runs Chrome headless twice at `--force-device-scale-factor=2 --window-size=1536,580`
|
||||
- Writes two 3072×1160 PNGs (~600 KB each)
|
||||
- Tears down the server
|
||||
|
||||
### 5. Verify by eye — mandatory, do not skip
|
||||
|
||||
After rendering, **open each PNG and visually check every item** in this list. Do not report the diagram as done without going through it.
|
||||
|
||||
- [ ] Top-row card count matches the Default Services list you extracted in §1, every name rendered in full (no truncation on the longest, e.g. `ctx.systemPrompt` / `ctx.agentLoop`)
|
||||
- [ ] Bottom-row card count matches the Capability Services list you extracted in §1, every name in full (especially the longest, e.g. `ctx.sessionPersistence`)
|
||||
- [ ] Every card has a description underneath, and the description matches what the code does (not made up)
|
||||
- [ ] Bar reads `Cordis · microkernel (vendored)` (EN) / `Cordis · 微内核 (vendored)` (ZH)
|
||||
- [ ] Number of vertical connector lines equals (top-row cards + bottom-row cards) from §1. All straight vertical, all parallel, each reaching the horizontal center of its card
|
||||
- [ ] `cordis.yml` sidebar visible on the right; dashed arrow points left into the bar
|
||||
- [ ] `deployment leaf` / `部署清单` and `picks which plugins load` / `决定哪些插件加载` are readable and NOT crossed through by any connector line
|
||||
- [ ] Chinese descriptions render as CJK glyphs, not `□` tofu boxes (means fonts loaded)
|
||||
- [ ] No large empty gradient area at the bottom of the image — if there is, tune `--window-size` in `render.sh`
|
||||
|
||||
If any check fails: fix the CSS or template, re-run `render.sh`, re-check. Do not report "done" on first render without visually confirming.
|
||||
|
||||
---
|
||||
|
||||
## Design system (locked — do not modify without explicit user request)
|
||||
|
||||
Colors:
|
||||
- Background: `linear-gradient(135deg, #ffffff 0%, #e8effc 100%)`
|
||||
- Brand blue (Cordis bar + connectors): `#4a6ef5`
|
||||
- Ink text: `#1a1a1a`
|
||||
- Muted text: `#8a8f9c`
|
||||
- Card border: `#dde3ef`
|
||||
- Card fill: `#ffffff`
|
||||
|
||||
Typography (**sans-serif only** — never serif, never italic):
|
||||
- English title & body: **Inter** (Google Fonts)
|
||||
- Chinese title & body: **Noto Sans SC** (Google Fonts) with `PingFang SC` local fallback
|
||||
- Code labels (`ctx.*`, `cordis.yml`, `vendored`): **JetBrains Mono** (Google Fonts)
|
||||
|
||||
Layout:
|
||||
- `.page` max-width 1560 px, centered
|
||||
- `.bus-row`, `.row.top`, `.row.bottom` all 96% width, centered
|
||||
- Top row: `flex: 1` on each card (equal widths)
|
||||
- Bottom row: `justify-content: space-between` + `flex: 0 0 auto` on each card — cards keep their intrinsic (nowrap) width and adjacent card backgrounds cannot clip a description
|
||||
- Bus: `#4a6ef5` filled, 60 px tall, 8 px radius
|
||||
- `cordis.yml` sidebar `position: absolute` at `left: calc(100% + 20px)` of `.bus-row`, width 130 px
|
||||
- All connectors: CSS `::before` / `::after` pseudo-elements, 2 px wide, 40 px tall, `#4a6ef5`, centered via `left: 50%; transform: translateX(-50%)` — this is what guarantees pixel-perfect vertical parallel lines regardless of card content
|
||||
|
||||
Rendering:
|
||||
- Chrome headless `--force-device-scale-factor=2`
|
||||
- `--window-size=1820,580` → output is 3640×1160 (aspect ~3.14:1, wider than square-ish so a 12-card capability row fits without truncation)
|
||||
- Local http.server on `127.0.0.1` so CDN fonts load cleanly
|
||||
|
||||
---
|
||||
|
||||
## Files in this skill
|
||||
|
||||
- `SKILL.md` — this file
|
||||
- `harness-arch.css` — shared design system (colors, fonts, layout, connectors)
|
||||
- `harness-arch-en.html` — English template
|
||||
- `harness-arch-zh.html` — Chinese template
|
||||
- `render.sh` — Chrome headless renderer
|
||||
|
||||
Templates are self-contained. Fonts load from Google Fonts CDN; no local font files.
|
||||
|
||||
---
|
||||
|
||||
## Rules I keep breaking (and shouldn't)
|
||||
|
||||
1. **Verify the output by looking at it.** Don't report "done" and move on — actually inspect each PNG and check every item in §5.
|
||||
2. **Ground service descriptions in the code, not the docs' abstract, not the marketing copy.** If unsure what a package does, read its `src/` or the `ctx.<name>.register()` block.
|
||||
3. **Don't touch layout unless asked.** 96% row/bar width, 40 px connector height, and 60 px bar height are all tuned. Card counts grow one at a time; once the bottom row starts to visibly overflow again (adjacent card backgrounds clipping a description, or the `cordis.yml` sidebar falling outside the render viewport), stop and ask before shrinking fonts, widening the viewport further, or wrapping to two rows.
|
||||
4. **Never introduce a serif font or italic.** Not for titles, not for annotations, not "to make it feel editorial." This diagram is DeepSeek brand: sans-serif, upright, only.
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1536, initial-scale=1.0">
|
||||
<title>DeepSeek Harness · Architecture</title>
|
||||
<link rel="stylesheet" href="harness-arch.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<p class="eyebrow">ARCHITECTURE</p>
|
||||
<h1>System overview</h1>
|
||||
|
||||
<div class="diagram">
|
||||
<p class="section-label top">DEFAULT SERVICES</p>
|
||||
|
||||
<div class="row top">
|
||||
<div class="card"><span class="name">ctx.sessions</span><span class="desc">event-sourced log</span></div>
|
||||
<div class="card"><span class="name">ctx.systemPrompt</span><span class="desc">prompt assembly</span></div>
|
||||
<div class="card"><span class="name">ctx.tools</span><span class="desc">tool registry & pipeline</span></div>
|
||||
<div class="card"><span class="name">ctx.agents</span><span class="desc">agent handles & events</span></div>
|
||||
<div class="card"><span class="name">ctx.agentLoop</span><span class="desc">ReAct control loop</span></div>
|
||||
</div>
|
||||
|
||||
<div class="bus-row">
|
||||
<div class="bus">
|
||||
<span class="core">Cordis</span>
|
||||
<span class="dot">·</span>
|
||||
<span class="core">microkernel</span>
|
||||
<span class="vendored">(vendored)</span>
|
||||
</div>
|
||||
<div class="yml-side">
|
||||
<div class="yml-arrow">
|
||||
<svg viewBox="0 0 30 12" width="30" height="12" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="30" y1="6" x2="8" y2="6" stroke="#8a8f9c" stroke-width="1.2" stroke-dasharray="3 3"/>
|
||||
<polyline points="8,3 2,6 8,9" fill="none" stroke="#8a8f9c" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="yml-box">
|
||||
<div class="file">cordis.yml</div>
|
||||
<div class="sub">deployment leaf</div>
|
||||
</div>
|
||||
<div class="yml-note">picks which plugins load</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom">
|
||||
<div class="card"><span class="name">ctx.llm</span><span class="desc">LLM provider adapter</span></div>
|
||||
<div class="card"><span class="name">ctx.bash</span><span class="desc">shell execution</span></div>
|
||||
<div class="card"><span class="name">ctx.sandbox</span><span class="desc">process confinement</span></div>
|
||||
<div class="card"><span class="name">ctx.codeRuntime</span><span class="desc">JS worker runtime</span></div>
|
||||
<div class="card"><span class="name">ctx.fs</span><span class="desc">filesystem + policy</span></div>
|
||||
<div class="card"><span class="name">ctx.skills</span><span class="desc">reusable skills</span></div>
|
||||
<div class="card"><span class="name">ctx.web</span><span class="desc">search & fetch</span></div>
|
||||
<div class="card"><span class="name">ctx.compact</span><span class="desc">context compression</span></div>
|
||||
<div class="card"><span class="name">ctx.subagents</span><span class="desc">spawn / fork / ACP</span></div>
|
||||
<div class="card"><span class="name">ctx.workflows</span><span class="desc">dynamic orchestration</span></div>
|
||||
<div class="card"><span class="name">ctx.sessionPersistence</span><span class="desc">JSONL / SQLite</span></div>
|
||||
<div class="card"><span class="name">ctx.sessionQuery</span><span class="desc">unified log reads</span></div>
|
||||
</div>
|
||||
|
||||
<p class="section-label bottom">CAPABILITY SERVICES</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1536, initial-scale=1.0">
|
||||
<title>DeepSeek Harness · 架构</title>
|
||||
<link rel="stylesheet" href="harness-arch.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<p class="eyebrow cn">架 构</p>
|
||||
<h1 class="cn">系统一览</h1>
|
||||
|
||||
<div class="diagram">
|
||||
<p class="section-label top cn">默 认 服 务</p>
|
||||
|
||||
<div class="row top">
|
||||
<div class="card"><span class="name">ctx.sessions</span><span class="desc cn">事件溯源日志</span></div>
|
||||
<div class="card"><span class="name">ctx.systemPrompt</span><span class="desc cn">系统提示词组装</span></div>
|
||||
<div class="card"><span class="name">ctx.tools</span><span class="desc cn">工具注册与执行</span></div>
|
||||
<div class="card"><span class="name">ctx.agents</span><span class="desc cn">Agent 实例与事件</span></div>
|
||||
<div class="card"><span class="name">ctx.agentLoop</span><span class="desc cn">ReAct 控制循环</span></div>
|
||||
</div>
|
||||
|
||||
<div class="bus-row">
|
||||
<div class="bus cn">
|
||||
<span class="core">Cordis</span>
|
||||
<span class="dot">·</span>
|
||||
<span class="core">微内核</span>
|
||||
<span class="vendored">(vendored)</span>
|
||||
</div>
|
||||
<div class="yml-side">
|
||||
<div class="yml-arrow">
|
||||
<svg viewBox="0 0 30 12" width="30" height="12" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="30" y1="6" x2="8" y2="6" stroke="#8a8f9c" stroke-width="1.2" stroke-dasharray="3 3"/>
|
||||
<polyline points="8,3 2,6 8,9" fill="none" stroke="#8a8f9c" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="yml-box">
|
||||
<div class="file">cordis.yml</div>
|
||||
<div class="sub cn">部署清单</div>
|
||||
</div>
|
||||
<div class="yml-note cn">决定哪些插件加载</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row bottom">
|
||||
<div class="card"><span class="name">ctx.llm</span><span class="desc cn">模型适配层</span></div>
|
||||
<div class="card"><span class="name">ctx.bash</span><span class="desc cn">命令行执行</span></div>
|
||||
<div class="card"><span class="name">ctx.sandbox</span><span class="desc cn">进程隔离</span></div>
|
||||
<div class="card"><span class="name">ctx.codeRuntime</span><span class="desc cn">JS 运行时</span></div>
|
||||
<div class="card"><span class="name">ctx.fs</span><span class="desc cn">文件读写</span></div>
|
||||
<div class="card"><span class="name">ctx.skills</span><span class="desc cn">可复用技能</span></div>
|
||||
<div class="card"><span class="name">ctx.web</span><span class="desc cn">网页搜索抓取</span></div>
|
||||
<div class="card"><span class="name">ctx.compact</span><span class="desc cn">上下文压缩</span></div>
|
||||
<div class="card"><span class="name">ctx.subagents</span><span class="desc cn">子智能体</span></div>
|
||||
<div class="card"><span class="name">ctx.workflows</span><span class="desc cn">动态编排</span></div>
|
||||
<div class="card"><span class="name">ctx.sessionPersistence</span><span class="desc cn">会话持久化</span></div>
|
||||
<div class="card"><span class="name">ctx.sessionQuery</span><span class="desc cn">会话日志查询</span></div>
|
||||
</div>
|
||||
|
||||
<p class="section-label bottom cn">能 力 服 务</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,220 @@
|
||||
/* DeepSeek brand — bone-white + brand-blue, all sans-serif, no italic, no serif. */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||
|
||||
:root {
|
||||
--bg-top: #ffffff;
|
||||
--bg-bot: #e8effc;
|
||||
--ink: #1a1a1a;
|
||||
--muted: #8a8f9c;
|
||||
--card-bd: #dde3ef;
|
||||
--card-bg: #ffffff;
|
||||
--brand: #4a6ef5;
|
||||
--brand-soft: #dbe4fe;
|
||||
|
||||
--sans: 'Inter', 'Noto Sans SC', 'PingFang SC', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
--cn: 'Noto Sans SC', 'PingFang SC', 'Inter', -apple-system, sans-serif;
|
||||
--mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
color: var(--ink);
|
||||
background: linear-gradient(135deg, var(--bg-top) 0%, var(--bg-bot) 100%);
|
||||
padding: 40px 64px 32px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-feature-settings: "kern" 1, "liga" 1, "cv02" 1, "cv11" 1;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 1560px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ── header ─────────────────────────────────────────── */
|
||||
.eyebrow {
|
||||
font-family: var(--sans);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.22em;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.eyebrow.cn { font-family: var(--cn); letter-spacing: 0.4em; text-transform: none; }
|
||||
|
||||
h1 {
|
||||
font-family: var(--sans);
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
color: var(--ink);
|
||||
margin-bottom: 88px;
|
||||
}
|
||||
h1.cn { font-family: var(--cn); letter-spacing: 0.02em; font-weight: 600; }
|
||||
|
||||
/* ── diagram frame ──────────────────────────────────── */
|
||||
.diagram { position: relative; }
|
||||
|
||||
.section-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.22em;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.section-label.cn { font-family: var(--cn); letter-spacing: 0.4em; text-transform: none; font-size: 12px; }
|
||||
.section-label.bottom { margin: 20px 0 0; }
|
||||
|
||||
/* ── card rows ──────────────────────────────────────── */
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
width: 96%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
.row.top .card { flex: 1; }
|
||||
.row.bottom {
|
||||
gap: 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
/* flex: 0 0 auto so cards keep their intrinsic (nowrap) width and
|
||||
neighbouring card backgrounds cannot clip a description. */
|
||||
.row.bottom .card { flex: 0 0 auto; }
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-bd);
|
||||
border-radius: 6px;
|
||||
padding: 14px 10px;
|
||||
text-align: center;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
.card .name {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--ink);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
.card .desc {
|
||||
font-family: var(--sans);
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
color: var(--muted);
|
||||
margin-top: 4px;
|
||||
line-height: 1.3;
|
||||
min-height: 28px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.card .desc.cn { font-family: var(--cn); }
|
||||
.row.bottom .card { padding: 10px 6px; }
|
||||
.row.bottom .card .name { font-size: 10px; }
|
||||
.row.bottom .card .desc { font-size: 9px; min-height: 22px; }
|
||||
.row.bottom .card .desc.cn { font-size: 10px; }
|
||||
|
||||
/* connectors — pure CSS, guaranteed vertical & parallel */
|
||||
.row.top .card::after,
|
||||
.row.bottom .card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 2px;
|
||||
background: var(--brand);
|
||||
}
|
||||
.row.top .card::after { top: 100%; height: 40px; }
|
||||
.row.bottom .card::before { bottom: 100%; height: 40px; }
|
||||
|
||||
/* ── bus row (Cordis) ───────────────────────────────── */
|
||||
.bus-row {
|
||||
position: relative;
|
||||
width: 96%;
|
||||
margin: 40px auto;
|
||||
}
|
||||
.bus {
|
||||
height: 60px;
|
||||
background: var(--brand);
|
||||
color: #ffffff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--sans);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.bus .dot { padding: 0 12px; opacity: 0.75; }
|
||||
.bus .vendored {
|
||||
font-family: var(--mono);
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
opacity: 0.8;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.bus.cn .core { font-family: var(--cn); }
|
||||
|
||||
/* ── cordis.yml side box (positioned outside bus width) ─ */
|
||||
.yml-side {
|
||||
position: absolute;
|
||||
left: calc(100% + 20px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 130px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.yml-arrow {
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 30px;
|
||||
height: 12px;
|
||||
}
|
||||
.yml-arrow svg { display: block; }
|
||||
.yml-box {
|
||||
width: 100%;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-bd);
|
||||
border-radius: 6px;
|
||||
padding: 12px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.yml-box .file {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--ink);
|
||||
}
|
||||
.yml-box .sub {
|
||||
font-family: var(--sans);
|
||||
font-size: 10px;
|
||||
color: var(--muted);
|
||||
margin-top: 4px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.yml-box .sub.cn { font-family: var(--cn); letter-spacing: 0.05em; }
|
||||
.yml-note {
|
||||
font-family: var(--sans);
|
||||
font-size: 10px;
|
||||
color: var(--muted);
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.yml-note.cn { font-family: var(--cn); letter-spacing: 0.05em; }
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
# render.sh — Chrome headless renderer for the DeepSeek Harness architecture diagram.
|
||||
#
|
||||
# Writes arch-en.png and arch-zh.png (3072×1160, ~600 KB each) to the given output
|
||||
# directory. Default is <repo>/assets/, which is where the README image references
|
||||
# resolve.
|
||||
#
|
||||
# Usage:
|
||||
# bash render.sh # writes to <repo>/assets/
|
||||
# bash render.sh /some/other/dir # writes there instead
|
||||
#
|
||||
# Env overrides:
|
||||
# CHROME=/path/to/Chrome # non-standard Chrome location
|
||||
# PORT=<n> # override the local http.server port
|
||||
|
||||
set -e
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SKILL_DIR/../../.." && pwd)"
|
||||
OUT_DIR="${1:-$REPO_ROOT/assets}"
|
||||
BIND_PORT="${PORT:-0}" # 0 → kernel picks a free port
|
||||
SERVER_LOG="$(mktemp -t dsh-arch-diagram-server.XXXXXX.log)"
|
||||
|
||||
# Locate a Chrome / Chromium binary. Honour $CHROME if set; otherwise probe
|
||||
# common per-platform paths so `bash render.sh` works on macOS + Linux out
|
||||
# of the box.
|
||||
if [ -z "$CHROME" ]; then
|
||||
for candidate in \
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium" \
|
||||
"$(command -v google-chrome 2>/dev/null || true)" \
|
||||
"$(command -v google-chrome-stable 2>/dev/null || true)" \
|
||||
"$(command -v chromium 2>/dev/null || true)" \
|
||||
"$(command -v chromium-browser 2>/dev/null || true)"; do
|
||||
if [ -n "$candidate" ] && [ -x "$candidate" ]; then
|
||||
CHROME="$candidate"; break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$CHROME" ] || [ ! -x "$CHROME" ]; then
|
||||
echo "No Chrome / Chromium binary found." >&2
|
||||
echo "Tried: macOS default, google-chrome, google-chrome-stable, chromium, chromium-browser." >&2
|
||||
echo "Set CHROME=/path/to/binary and rerun." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
python3 -u -m http.server "$BIND_PORT" --bind 127.0.0.1 --directory "$SKILL_DIR" \
|
||||
>"$SERVER_LOG" 2>&1 &
|
||||
SERVER_PID=$!
|
||||
cleanup() { kill "$SERVER_PID" 2>/dev/null; rm -f "$SERVER_LOG"; }
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# Discover the actual bound port from Python's own startup line
|
||||
# ("Serving HTTP on 127.0.0.1 port <N> ..."). Works whether the kernel
|
||||
# picked it or PORT was set explicitly; no external `lsof` needed.
|
||||
PORT=""
|
||||
for _ in $(seq 1 40); do
|
||||
PORT=$(awk '/Serving HTTP on/ {print $6; exit}' "$SERVER_LOG" 2>/dev/null || true)
|
||||
[ -n "$PORT" ] && break
|
||||
# Bail early if the server died (e.g. explicit PORT already in use).
|
||||
kill -0 "$SERVER_PID" 2>/dev/null || break
|
||||
sleep 0.05
|
||||
done
|
||||
if [ -z "$PORT" ]; then
|
||||
echo "Failed to detect local server port. Server log:" >&2
|
||||
cat "$SERVER_LOG" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Chrome/Chromium refuses to run as root without --no-sandbox. Rendering
|
||||
# a local static HTML doc under a headless browser doesn't need Chrome's
|
||||
# own sandbox, so passing the flag when EUID is 0 makes the script work
|
||||
# in Codex/CI container environments that default to root.
|
||||
CHROME_EXTRA_ARGS=()
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
CHROME_EXTRA_ARGS+=(--no-sandbox)
|
||||
fi
|
||||
|
||||
for lang in en zh; do
|
||||
out="$OUT_DIR/arch-$lang.png"
|
||||
"$CHROME" \
|
||||
"${CHROME_EXTRA_ARGS[@]}" \
|
||||
--headless=new \
|
||||
--disable-gpu \
|
||||
--hide-scrollbars \
|
||||
--force-device-scale-factor=2 \
|
||||
--window-size=1820,580 \
|
||||
--virtual-time-budget=8000 \
|
||||
--screenshot="$out" \
|
||||
"http://127.0.0.1:$PORT/harness-arch-$lang.html" \
|
||||
2>/dev/null
|
||||
size=$(du -h "$out" | awk '{print $1}')
|
||||
echo "[$lang] wrote $out ($size)"
|
||||
done
|
||||
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
README.md: 53dd3896eb15800125673e7c44f7de02daca9376
|
||||
README.zh.md: ab826f62658248249ec18c57b35c0065c0f909d1
|
||||
README.md: 3380558e6c956cdc7f1b748b9ea8d3d455f781db
|
||||
README.zh.md: 9f415af0a6582c455fdb8e8bb5400dd28b42a161
|
||||
@@ -1,20 +1,240 @@
|
||||
# DeepSeek Harness
|
||||
<!--
|
||||
README.md — English side of the pair.
|
||||
If you edit this, update README.zh.md in the same commit, then run:
|
||||
pnpm run verify-translation-pairing --write
|
||||
to refresh README.i18n.yaml.
|
||||
-->
|
||||
|
||||
<div align="center">
|
||||
|
||||
<!-- TODO: replace with wordmark / logo asset once designed -->
|
||||
<h1>DeepSeek Harness</h1>
|
||||
|
||||
**The plugin-first agent SDK. Every capability — including the loop — is a plugin.**
|
||||
|
||||
<p>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-BSD--3--Clause-blue" alt="license"></a>
|
||||
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A522.19-3c873a" alt="node"></a>
|
||||
<a href="https://pnpm.io"><img src="https://img.shields.io/badge/pnpm-workspace-f69220" alt="pnpm"></a>
|
||||
<a href="https://www.typescriptlang.org"><img src="https://img.shields.io/badge/TypeScript-strict-3178c6" alt="typescript"></a>
|
||||
<a href="https://agentclientprotocol.com"><img src="https://img.shields.io/badge/protocol-ACP-4a6ef5" alt="ACP"></a>
|
||||
<a href="https://discord.gg/4nyuPgFzdE"><img src="https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
|
||||
<a href="https://x.com/Deepseekharness"><img src="https://img.shields.io/badge/X-@Deepseekharness-000000?logo=x&logoColor=white" alt="X"></a>
|
||||
</p>
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
The **DeepSeek Harness SDK** is a plugin-based SDK for building agent harnesses.
|
||||
<!-- TODO: deepseek.com/harness-sdk placeholder URLs — swap for the final canonical URLs when ready. -->
|
||||
[Docs](https://deepseek.com/harness-sdk/docs) · [Landing page](https://deepseek.com/harness-sdk) · [Community](#community)
|
||||
|
||||
## Development
|
||||
</div>
|
||||
|
||||
This monorepo is built on the [Cordis](https://github.com/cordiverse/cordis) framework (vendored as source under `vendor/`), microkernel-style: everything is a plugin.
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/arch-en.png" alt="DeepSeek Harness — System overview" width="100%">
|
||||
</p>
|
||||
|
||||
## What is this?
|
||||
|
||||
DeepSeek Harness is a TypeScript SDK for building AI agents on top of the [Cordis](https://github.com/cordiverse/cordis) microkernel. **Every service, including the ReAct loop, is a plugin registered through `ctx.*`.** A batteries-included service registry ships in the box — LLM adapters, sandboxed execution, filesystem with policy, web search, sub-agents, dynamic workflows, session persistence, and more — and a `cordis.yml` at your project root chooses which get loaded. You can replace any of them, add your own, or leave the shipped defaults alone.
|
||||
|
||||
## Getting started
|
||||
|
||||
**New project** (one-command scaffold):
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm run test # vitest
|
||||
pnpm run demo:repl # REPL agent demo (needs DEEPSEEK_API_KEY)
|
||||
pnpm run demo:acp # ACP server agent demo (needs DEEPSEEK_API_KEY)
|
||||
npm create @deepseek-ai/harness # coming soon, not yet on npm
|
||||
```
|
||||
|
||||
For humans, start with the [development guide](docs/development.md) for local setup, hooks, environment variables, and quality gates, then read the [architecture design](docs/architecture.md) and [documentation graph index](docs/graph-atlas.md) before package work. Local context lives in [packages/](packages/) and [vendor/](vendor/).
|
||||
**From source** (read the code / run demos / contribute):
|
||||
|
||||
For agents, follow [AGENTS.md](AGENTS.md).
|
||||
```sh
|
||||
git clone https://github.com/deepseek-harness/deepseek-harness.git
|
||||
cd deepseek-harness
|
||||
pnpm install
|
||||
export DEEPSEEK_API_KEY=sk-... # optional — omit and use pnpm run demo:echo (mock, no key)
|
||||
pnpm run demo:repl
|
||||
```
|
||||
|
||||
Requires **Node `^22.19 || ≥24`** and **pnpm ≥ 11.7** (Node engine and pnpm pinned in `package.json`; `corepack enable` picks up the exact pnpm version). Node 23 is not on the support matrix.
|
||||
|
||||
**Heads up:** `demo:repl` runs real `read` / `write` / `edit` file tools and `bash` in your current working directory — best run from a scratch dir or a git-clean project so you can review the changes.
|
||||
|
||||
## Use it in your editor
|
||||
|
||||
Harness ships an [Agent Client Protocol (ACP)](https://agentclientprotocol.com) server. ACP lets an editor drive an agent from its sidebar; [Zed](https://zed.dev) supports it natively.
|
||||
|
||||
The ACP server command (from your local clone):
|
||||
|
||||
```sh
|
||||
pnpm run demo:acp
|
||||
```
|
||||
|
||||
Zed side — Zed's `settings.json` (Cmd-Shift-P → "zed: open settings") takes an `agent_servers` entry:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"DeepSeek Harness": {
|
||||
"command": "pnpm",
|
||||
"args": ["--dir", "/path/to/deepseek-harness", "run", "demo:acp"],
|
||||
"env": { "DEEPSEEK_API_KEY": "sk-..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`--dir` points at your local clone. Zed launches the agent as a subprocess; each Zed session maps to its own agent instance, with chat in the sidebar and tool calls (arguments, results, file diffs) rendered inline in the editor. Configuration details in [`examples/acp-agent`](./examples/acp-agent) (including the snapshot-tested surface).
|
||||
|
||||
**VS Code / Cursor** — install an ACP client extension for either editor, such as [ACP Client](https://marketplace.visualstudio.com/items?itemName=formulahendry.acp-client) (`formulahendry.acp-client`) or [ACP Pro](https://marketplace.visualstudio.com/items?itemName=duclvz.acp-pro) (`duclvz.acp-pro`), and point a custom agent at `pnpm run demo:acp`.
|
||||
|
||||
**Other ACP clients** — same launch. Feature-by-feature support matrix in [`packages/ui/acp/acp-feature-support.md`](./packages/ui/acp/acp-feature-support.md).
|
||||
|
||||
## Embed it in your own app
|
||||
|
||||
Harness bootstraps from a `cordis.yml` via [`@deepseek-ai/dsh-app-boot`](./packages/ui/app-boot). For library-style integration into your own Node.js service, the same boot helpers apply:
|
||||
|
||||
```ts
|
||||
// my-app.ts
|
||||
import {
|
||||
boot,
|
||||
installFailLoud,
|
||||
loadEnv,
|
||||
resolveConfigPath,
|
||||
} from '@deepseek-ai/dsh-app-boot'
|
||||
|
||||
installFailLoud('my-app')
|
||||
loadEnv('my-app')
|
||||
|
||||
const ctx = await boot('my-app', resolveConfigPath('./cordis.yml', undefined))
|
||||
// ctx is the Cordis root Context; every service you mount in cordis.yml
|
||||
// is reachable via ctx.* (ctx.agents, ctx.sessions, ctx.tools, …).
|
||||
// The app plugins loaded from cordis.yml keep the process alive on their own
|
||||
// (stdio agents hold stdin; the ACP agent holds an RPC connection).
|
||||
// To shut down programmatically, call `await ctx.fiber.dispose()`.
|
||||
```
|
||||
|
||||
`boot()` returns once the whole plugin tree has settled. Two separate failure paths: a module-import failure rejects the `boot()` Promise directly, so the caller's `await` throws — handle it with `try/catch`. `installFailLoud` covers a different case — a late plugin-init rejection surfacing *after* `boot()` has already resolved, which would otherwise become an unhandled rejection and die silently. In `cordis.yml`, the entry-point app plugin — `dsh-stdio-agent` for a REPL, `dsh-acp-agent` for an ACP server, or a custom one — sits alongside whichever services should load. Full helper surface: [`packages/ui/app-boot`](./packages/ui/app-boot).
|
||||
|
||||
For end-to-end examples, see [`examples/`](./examples):
|
||||
|
||||
- [`echo-agent`](./examples/echo-agent) — a minimal setup with a mock LLM and an echo tool
|
||||
- [`coding-agent`](./examples/coding-agent) — a full coding agent wired to the real DeepSeek LLM
|
||||
- [`acp-agent`](./examples/acp-agent) — ACP server, with a sandbox composition variant
|
||||
|
||||
## Demo
|
||||
|
||||
Harness driving Zed as an ACP agent — chat in the sidebar, tool calls (bash, file edits, diffs) rendered inline in the editor:
|
||||
|
||||
<p align="center">
|
||||
<video src="https://github.com/user-attachments/assets/a2bee95d-684f-41f4-b55f-1c14db0f24fa" controls width="800">
|
||||
Your browser does not support inline video; download it from <a href="./assets/demo-acp.mp4">assets/demo-acp.mp4</a>.
|
||||
</video>
|
||||
</p>
|
||||
|
||||
<!-- TODO: additional feature-showcase clips go here (Code Mode / Dynamic Workflows / Self-installing plugins). -->
|
||||
|
||||
## Write a plugin
|
||||
|
||||
A Harness function/namespace plugin exports `name`, `inject`, and `apply` — cordis's Loader reads those separately. **`export default` breaks this shape** ([why](./docs/postmortem/0001-acp-default-export-drops-inject.md)): the Loader keeps only the `apply` function and silently drops `inject` / `name`, so the plugin fails to load with `cannot get property … without inject`. Inside `apply(ctx)`, tools / LLM adapters / services register through `ctx.*`.
|
||||
|
||||
The minimal echo tool from [`examples/echo-agent`](./examples/echo-agent):
|
||||
|
||||
```ts
|
||||
// echo-tool.ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'echo-tool'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'echo',
|
||||
description: 'Echo the given text back, uppercased.',
|
||||
parameters: {
|
||||
text: { type: 'string', required: true },
|
||||
},
|
||||
async execute(args) {
|
||||
// args is typed: { text: string }
|
||||
return [{ type: 'text', text: `ECHO: ${args.text.toUpperCase()}` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
`parameters` uses the [schemastery](./vendor/schemastery) JSON-Schema-shaped DSL — one field per property, with `required: true` for mandatory ones. A leaf `cordis.yml` is a flat `EntryOptions[]` the Loader iterates; this tool's entry looks like:
|
||||
|
||||
```yaml
|
||||
- id: echo-tool
|
||||
name: './echo-tool.ts' # your tool
|
||||
```
|
||||
|
||||
Alongside it, a bootable config also needs an LLM adapter and a `stdio-agent` app entry whose `config.model` points at an id that adapter registers. The minimal runnable version — mock LLM + this echo tool + a `stdio-agent` entry wired to `mock-echo` — is [`examples/echo-agent`](./examples/echo-agent), launched via:
|
||||
|
||||
```sh
|
||||
pnpm run demo:echo
|
||||
```
|
||||
|
||||
LLM-adapter and UI-plugin shapes: [`docs/cookbook/extension-cookbook.md`](./docs/cookbook/extension-cookbook.md).
|
||||
|
||||
## Packages
|
||||
|
||||
All packages ship under the `@deepseek-ai/dsh-*` scope. Grouped by family:
|
||||
|
||||
| Family | What lives here |
|
||||
|---|---|
|
||||
| **Core** (`packages/core/`) | `dsh-scope` · `dsh-session` · `dsh-tools` · `dsh-agent` · `dsh-agent-loop` · `dsh-system-prompt` |
|
||||
| **LLM** (`packages/llm/`) | `dsh-llm` (the seam) + `dsh-llm-deepseek` (hand-rolled) and `dsh-llm-pi-ai` (library-backed twin — same DeepSeek endpoint, different internals, kept for design verification) |
|
||||
| **Bash** (`packages/bash/`) | Shell execution: local + sandboxed backends, model-facing `bash` tool |
|
||||
| **Filesystem** (`packages/fs/`) | Filesystem service with a policy layer, `read` / `write` / `edit` tools |
|
||||
| **Web** (`packages/web/`) | Web search (Perplexity, Exa, DeepSeek) + fetch, model-facing tool |
|
||||
| **Sandbox** (`packages/sandbox/`) | Process-confinement seam (bwrap / Landlock / Seatbelt) — wraps a caller's argv under a per-call policy; execution itself lives in `ctx.bash` |
|
||||
| **Code runtime** (`packages/code-runtime/`) | JS worker runtime that Code Mode dispatches into |
|
||||
| **Sub-agents** (`packages/subagent/`) | `spawn`, `fork`, plus in-process / subprocess / ACP-backed backends |
|
||||
| **Workflows** (`packages/workflow/`) | Dynamic workflow orchestration (worker-thread execution) |
|
||||
| **Skills** (`packages/skill/`) | Skill-provider registry (`ctx.skills`) + a local-filesystem provider |
|
||||
| **Session persistence** (`packages/session-persistence/`) | Event-log persistence: JSONL and SQLite backends |
|
||||
| **Session query** (`packages/session-query/`) | `ctx.sessionQuery` — unified logical-corpus reads over live sessions + persistence |
|
||||
| **Compact** (`packages/compact/`) | Context compression / summarization |
|
||||
| **Context** (`packages/context/`) | Opt-in request-context enrichment (e.g. `dsh-time-context` — dynamic time-in-prompt) |
|
||||
| **Cordis toolset** (`packages/cordis/`) | Model-facing tools that inspect / mount / unmount cordis plugins at runtime |
|
||||
| **UI apps** (`packages/ui/`) | `dsh-stdio-agent` (REPL) · `dsh-acp-agent` (ACP server) · `dsh-app-boot` · approval + ask-user primitives |
|
||||
| **Hooks** (`packages/hooks/`) | Hook protocol + Claude Code / OpenAI Codex hook-config bridges |
|
||||
| **Guards** (`packages/guard/`) | Advisory loop-hygiene plugins (e.g. `repeat-tool-guard` for repeated-call escalation) |
|
||||
| **Timeouts** (`packages/timeout/`) | `timeout-policy` — a zero-config `tools/execute` wrapper enforcing per-tool `timeoutMs` |
|
||||
| **Todo** (`packages/todo/`) | The model-facing `todo_write` tool (whole-list task tracker) |
|
||||
| **Support** (`packages/support/`) | `invariants` — runtime diagnostic plugin mounted unconditionally by the shipped `dsh-agent-spine-demo` bundle; plus test/dev-only helpers (`llm-replay`, `acp-snapshot`, `subagent-mock`) |
|
||||
| **Example bundles** (`packages/examples/`) | Ready-to-run demo compositions the top-level `demo:*` scripts launch: `dsh-agent-spine-demo` (default spine + capabilities), `dsh-stdio-demo` (REPL), `dsh-acp-demo` (ACP server), `dsh-jsonrpc-demo` |
|
||||
| **Utils** (`packages/util/`) | Internal utility packages (`brand`, `timeout`) |
|
||||
|
||||
For the full module dependency graph, see [`docs/module-graph.md`](./docs/module-graph.md).
|
||||
|
||||
## Deep dives
|
||||
|
||||
To understand what makes DeepSeek Harness different, start here:
|
||||
|
||||
- [Architecture](./docs/architecture.md) — the service taxonomy and the microkernel structure
|
||||
- [Agent lifecycle](./docs/agent-lifecycle.md) — how a turn flows through the loop, with sequence diagrams
|
||||
- [Cordis primer](./docs/cordis-primer.md) — a working introduction to the underlying plugin framework
|
||||
- [Tool execution pipeline](./docs/tool-execution-pipeline.md) — how a tool call passes through permission gates, hooks, and logging
|
||||
- [Capability seams](./docs/capability-seams.md) — the extension points each service exposes
|
||||
- [Code Mode](./docs/rfc/implemented/feature/2026-06-15-code-mode.md) — the model writes one JavaScript program per turn that chains many bash / tool calls, executed in a single runtime pass. **One model round-trip per multi-step operation**, not one per call.
|
||||
- [Dynamic Workflows](./docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md) — the model writes a plain-JS orchestrator that fans out sub-agents in parallel, joins their results, and returns to the parent — instead of a chain of sub-agent tool calls.
|
||||
- [Self-referential Cordis toolset](./docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md) — the SDK's own plumbing (`cordis_inspect`, `cordis_mount`, `cordis_unmount`) is exposed as tools, so the model can inspect its own runtime and load new plugins on the fly.
|
||||
|
||||
Docs site: **[deepseek.com/harness-sdk/docs](https://deepseek.com/harness-sdk/docs)**.
|
||||
|
||||
## Community
|
||||
|
||||
<a name="community"></a>
|
||||
|
||||
- **[GitHub Issues](https://github.com/deepseek-harness/deepseek-harness/issues)** — bug reports
|
||||
- **[GitHub Discussions](https://github.com/deepseek-harness/deepseek-harness/discussions)** — questions, ideas, RFCs
|
||||
|
||||
Real-time chat on <a href="https://discord.gg/4nyuPgFzdE"><b>Discord</b></a>. Release announcements on <a href="https://x.com/Deepseekharness"><b>X / Twitter</b></a>.
|
||||
|
||||
## License
|
||||
|
||||
[BSD 3-Clause](./LICENSE) © DeepSeek
|
||||
+230
-10
@@ -1,20 +1,240 @@
|
||||
# DeepSeek Harness
|
||||
<!--
|
||||
README.zh.md — Chinese pair.
|
||||
If you edit this, update README.md in the same commit, then run:
|
||||
pnpm run verify-translation-pairing --write
|
||||
to refresh README.i18n.yaml.
|
||||
-->
|
||||
|
||||
<div align="center">
|
||||
|
||||
<!-- TODO: 待设计后替换为 logo / wordmark 资源 -->
|
||||
<h1>DeepSeek Harness</h1>
|
||||
|
||||
**插件优先的 agent(智能体) SDK。每一项能力 — 包括 loop 本身 — 都是插件。**
|
||||
|
||||
<p>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-BSD--3--Clause-blue" alt="license"></a>
|
||||
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A522.19-3c873a" alt="node"></a>
|
||||
<a href="https://pnpm.io"><img src="https://img.shields.io/badge/pnpm-workspace-f69220" alt="pnpm"></a>
|
||||
<a href="https://www.typescriptlang.org"><img src="https://img.shields.io/badge/TypeScript-strict-3178c6" alt="typescript"></a>
|
||||
<a href="https://agentclientprotocol.com"><img src="https://img.shields.io/badge/protocol-ACP-4a6ef5" alt="ACP"></a>
|
||||
</p>
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
**DeepSeek Harness SDK** 是用于构建 agent harness(智能体框架)的 SDK,采取基于插件的设计。
|
||||
<!-- TODO: deepseek.com/harness-sdk 是占位 URL,最终域名就绪后替换。 -->
|
||||
[文档](https://deepseek.com/harness-sdk/docs) · [Landing page](https://deepseek.com/harness-sdk) · [社群](#community)
|
||||
|
||||
## 开发
|
||||
</div>
|
||||
|
||||
本 monorepo 基于 [Cordis](https://github.com/cordiverse/cordis) 框架构建(以源码形式收录在 `vendor/` 下),采用微内核风格:所有功能都以插件形式提供。
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/arch-zh.png" alt="DeepSeek Harness · 系统一览" width="100%">
|
||||
</p>
|
||||
|
||||
## 这是什么?
|
||||
|
||||
DeepSeek Harness 是一个用 TypeScript 写的 AI agent SDK,构建在 [Cordis](https://github.com/cordiverse/cordis) 微内核之上。**所有服务,包括默认的 ReAct loop,都是通过 `ctx.*` 注册的插件。** 仓库里带了一整套开箱即用的服务 —— LLM 适配、沙盒执行、带策略的文件读写、网页搜索、子 agent、动态工作流、会话持久化等 —— 由项目根目录下的 `cordis.yml` 决定加载哪些。你可以替换任何一项、加装自己的,或者保留默认。
|
||||
|
||||
## 上手
|
||||
|
||||
**新起一个项目**(一条命令生成脚手架):
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm run test # vitest
|
||||
pnpm run demo:repl # REPL agent demo (needs DEEPSEEK_API_KEY)
|
||||
pnpm run demo:acp # ACP server agent demo (needs DEEPSEEK_API_KEY)
|
||||
npm create @deepseek-ai/harness # coming soon, not yet on npm
|
||||
```
|
||||
|
||||
面向开发者:先读[开发指南](docs/development.md),了解本地环境搭建、钩子、环境变量与质量门禁,动手改 package 之前再读[架构设计](docs/architecture.md)和[文档关系图索引](docs/graph-atlas.md)。局部上下文见 [packages/](packages/) 与 [vendor/](vendor/)。
|
||||
**克隆仓库**(读代码 / 跑 demo / 贡献代码):
|
||||
|
||||
面向 agent:遵循 [AGENTS.md](AGENTS.md)。
|
||||
```sh
|
||||
git clone https://github.com/deepseek-harness/deepseek-harness.git
|
||||
cd deepseek-harness
|
||||
pnpm install
|
||||
export DEEPSEEK_API_KEY=sk-... # optional — omit and use pnpm run demo:echo (mock, no key)
|
||||
pnpm run demo:repl
|
||||
```
|
||||
|
||||
需要 **Node `^22.19 || ≥24`** 和 **pnpm ≥ 11.7**(Node 引擎与 pnpm 版本由 `package.json` 的 `engines` / `packageManager` 字段锁定;`corepack enable` 会自动装对 pnpm 版本)。Node 23 不在支持范围内。
|
||||
|
||||
**注意:** `demo:repl` 会用真实的 `read` / `write` / `edit` 文件工具和 `bash` 操作**当前工作目录**,最好在空目录或干净的 git 项目里运行,方便随时 review 改动。
|
||||
|
||||
## 接入编辑器
|
||||
|
||||
Harness 内置了一个 [Agent Client Protocol (ACP)](https://agentclientprotocol.com) server。ACP 是一个把 agent 挂到编辑器边栏作为后端的协议;[Zed](https://zed.dev) 目前原生支持。
|
||||
|
||||
ACP server 的启动命令(在 clone 的仓库根目录下):
|
||||
|
||||
```sh
|
||||
pnpm run demo:acp
|
||||
```
|
||||
|
||||
Zed 端 —— 在 Zed 的 `settings.json`(Cmd-Shift-P → "zed: open settings")里加上 `agent_servers` 一节:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"DeepSeek Harness": {
|
||||
"command": "pnpm",
|
||||
"args": ["--dir", "/path/to/deepseek-harness", "run", "demo:acp"],
|
||||
"env": { "DEEPSEEK_API_KEY": "sk-..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`--dir` 指向本地仓库路径。Zed 把 agent 作为子进程拉起,每个 Zed session 对应一个独立的 agent 实例;编辑器边栏可以直接对话,tool 调用(参数、结果、文件 diff)内联渲染到编辑区。完整配置说明见 [`examples/acp-agent`](./examples/acp-agent)。
|
||||
|
||||
**VS Code / Cursor** —— 两个编辑器都可以装 ACP 客户端插件,比如 [ACP Client](https://marketplace.visualstudio.com/items?itemName=formulahendry.acp-client)(`formulahendry.acp-client`)或 [ACP Pro](https://marketplace.visualstudio.com/items?itemName=duclvz.acp-pro)(`duclvz.acp-pro`),把 `pnpm run demo:acp` 配成自定义 agent 即可接入。
|
||||
|
||||
**其他 ACP 客户端** —— 接入方式相同。支持哪些 ACP 特性见 [`packages/ui/acp/acp-feature-support.md`](./packages/ui/acp/acp-feature-support.md)。
|
||||
|
||||
## 嵌入自己的应用
|
||||
|
||||
Harness 是通过 [`@deepseek-ai/dsh-app-boot`](./packages/ui/app-boot) 从一份 `cordis.yml` 引导起来的。如果希望在个人 Node.js 服务里以库的形式集成,可以用同样的引导方式:
|
||||
|
||||
```ts
|
||||
// my-app.ts
|
||||
import {
|
||||
boot,
|
||||
installFailLoud,
|
||||
loadEnv,
|
||||
resolveConfigPath,
|
||||
} from '@deepseek-ai/dsh-app-boot'
|
||||
|
||||
installFailLoud('my-app')
|
||||
loadEnv('my-app')
|
||||
|
||||
const ctx = await boot('my-app', resolveConfigPath('./cordis.yml', undefined))
|
||||
// ctx is the Cordis root Context; every service you mount in cordis.yml
|
||||
// is reachable via ctx.* (ctx.agents, ctx.sessions, ctx.tools, …).
|
||||
// The app plugins loaded from cordis.yml keep the process alive on their own
|
||||
// (stdio agents hold stdin; the ACP agent holds an RPC connection).
|
||||
// To shut down programmatically, call `await ctx.fiber.dispose()`.
|
||||
```
|
||||
|
||||
`boot()` 会在整棵插件树 settle 完之后返回。两条失败路径要分开处理:模块导入失败会直接 reject `boot()` 的 Promise,`await` 处会抛出,调用方用 `try/catch` 接住即可。`installFailLoud` 负责的是另一件事 —— `boot()` 返回之后才浮出来的 late plugin-init rejection,如果不接就会变成无人处理的 unhandled rejection 静默死掉。`cordis.yml` 里的 app 插件入口 —— `dsh-stdio-agent`(REPL)、`dsh-acp-agent`(ACP server)或自定义 —— 搭配想加载的服务。完整的 helper 表见 [`packages/ui/app-boot`](./packages/ui/app-boot)。
|
||||
|
||||
完整的组合示例见 [`examples/`](./examples):
|
||||
|
||||
- [`echo-agent`](./examples/echo-agent) —— mock LLM + echo tool 的最小示例
|
||||
- [`coding-agent`](./examples/coding-agent) —— 接真实 DeepSeek LLM 的完整 coding agent
|
||||
- [`acp-agent`](./examples/acp-agent) —— ACP server,含一个沙盒 composition variant
|
||||
|
||||
## 演示
|
||||
|
||||
Harness 作为 ACP agent 挂在 Zed 里 —— 边栏对话,工具调用(bash、文件编辑、diff)内联渲染在编辑区:
|
||||
|
||||
<p align="center">
|
||||
<video src="https://github.com/user-attachments/assets/a2bee95d-684f-41f4-b55f-1c14db0f24fa" controls width="800">
|
||||
浏览器不支持内联视频,可下载 <a href="./assets/demo-acp.mp4">assets/demo-acp.mp4</a> 查看。
|
||||
</video>
|
||||
</p>
|
||||
|
||||
<!-- TODO: 后续加各功能特色演示视频(Code Mode / 动态工作流 / 自安装插件) -->
|
||||
|
||||
## 编写插件
|
||||
|
||||
Harness 的 function/namespace 插件通过分开的 `name` / `inject` / `apply` 命名导出注册,cordis Loader 读的是这些字段。**`export default` 不适用于这种形态** —— Loader 只会拿到 `apply` 函数,`inject` / `name` 被静默丢掉,加载时报 `cannot get property … without inject`(详见 [postmortem 0001](./docs/postmortem/0001-acp-default-export-drops-inject.md))。`apply(ctx)` 内通过 `ctx.*` 注册 tool、挂载 LLM adapter 或暴露 service。
|
||||
|
||||
下面这个是 [`examples/echo-agent`](./examples/echo-agent) 里的真实 echo tool 插件:
|
||||
|
||||
```ts
|
||||
// echo-tool.ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'echo-tool'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'echo',
|
||||
description: 'Echo the given text back, uppercased.',
|
||||
parameters: {
|
||||
text: { type: 'string', required: true },
|
||||
},
|
||||
async execute(args) {
|
||||
// args is typed: { text: string }
|
||||
return [{ type: 'text', text: `ECHO: ${args.text.toUpperCase()}` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
`parameters` 是 [schemastery](./vendor/schemastery) 的 JSON-Schema 风格 DSL —— 每个字段一项定义,`required: true` 标记必填。leaf `cordis.yml` 是 Loader 迭代的一个 flat `EntryOptions[]`,这个工具的条目长这样:
|
||||
|
||||
```yaml
|
||||
- id: echo-tool
|
||||
name: './echo-tool.ts' # your tool
|
||||
```
|
||||
|
||||
同一份配置里还要有 LLM adapter 和一个 `stdio-agent` app 条目,`config.model` 指向 adapter 里注册的某个 model id。最小可跑组合 —— mock LLM + 这个 echo tool + 接到 `mock-echo` 的 `stdio-agent` —— 见 [`examples/echo-agent`](./examples/echo-agent),运行命令:
|
||||
|
||||
```sh
|
||||
pnpm run demo:echo
|
||||
```
|
||||
|
||||
LLM adapter 与 UI 插件的写法见 [`docs/cookbook/extension-cookbook.md`](./docs/cookbook/extension-cookbook.md)。
|
||||
|
||||
## Packages
|
||||
|
||||
所有包都在 `@deepseek-ai/dsh-*` scope 下,按目录分组:
|
||||
|
||||
| 分组 | 包含 |
|
||||
|---|---|
|
||||
| **Core**(`packages/core/`)| `dsh-scope` · `dsh-session` · `dsh-tools` · `dsh-agent` · `dsh-agent-loop` · `dsh-system-prompt` |
|
||||
| **LLM**(`packages/llm/`)| `dsh-llm`(seam)+ `dsh-llm-deepseek`(手写实现)与 `dsh-llm-pi-ai`(第三方库实现的孪生 —— 打同一个 DeepSeek endpoint,内部走不同代码路径,用于设计验证)|
|
||||
| **Bash**(`packages/bash/`)| 命令行执行:本地 + 沙盒后端,模型可调用的 `bash` tool |
|
||||
| **Filesystem**(`packages/fs/`)| 带策略层的文件服务,`read` / `write` / `edit` tools |
|
||||
| **Web**(`packages/web/`)| 网页搜索(Perplexity、Exa、DeepSeek)+ fetch,模型可调用的 tool |
|
||||
| **Sandbox**(`packages/sandbox/`)| 进程隔离接缝(bwrap / Landlock / Seatbelt)—— 按每次调用的策略包一层 argv,真正的执行由 `ctx.bash` 负责 |
|
||||
| **Code runtime**(`packages/code-runtime/`)| Code Mode 分发进入的 JS worker 运行时 |
|
||||
| **Sub-agents**(`packages/subagent/`)| `spawn` / `fork`,以及进程内 / 子进程 / ACP 后端 |
|
||||
| **Workflows**(`packages/workflow/`)| 动态工作流编排(worker 线程执行)|
|
||||
| **Skills**(`packages/skill/`)| Skill provider 注册中心(`ctx.skills`)+ 本地文件系统 provider |
|
||||
| **Session persistence**(`packages/session-persistence/`)| 事件日志持久化:JSONL 与 SQLite 后端 |
|
||||
| **Session query**(`packages/session-query/`)| `ctx.sessionQuery` —— 把 live sessions 和持久化层合成同一份逻辑语料的统一查询 |
|
||||
| **Compact**(`packages/compact/`)| 上下文压缩 / 摘要 |
|
||||
| **Context**(`packages/context/`)| 可选的请求上下文增强(如 `dsh-time-context` —— 系统提示词里注入动态时间)|
|
||||
| **Cordis toolset**(`packages/cordis/`)| 模型可调用的、在运行时查看 / 挂载 / 卸载 cordis 插件的 tools |
|
||||
| **UI apps**(`packages/ui/`)| `dsh-stdio-agent`(REPL)· `dsh-acp-agent`(ACP server)· `dsh-app-boot` · approval / ask-user 基础件 |
|
||||
| **Hooks**(`packages/hooks/`)| Hook 协议 + Claude Code / OpenAI Codex 的 hook 配置桥 |
|
||||
| **Guards**(`packages/guard/`)| 建议性的 loop 健康插件(如 `repeat-tool-guard` —— 检测同一 tool 重复调用并升级 advisory)|
|
||||
| **Timeouts**(`packages/timeout/`)| `timeout-policy` —— 零配置的 `tools/execute` 包装,按 tool 声明的 `timeoutMs` 强制超时 |
|
||||
| **Todo**(`packages/todo/`)| 模型可调用的 `todo_write` tool(整表任务追踪)|
|
||||
| **Support**(`packages/support/`)| `invariants` —— 由默认组合 `dsh-agent-spine-demo` 无条件挂载的运行时诊断插件;此外是仅测试/开发用的辅助包(`llm-replay`、`acp-snapshot`、`subagent-mock`)|
|
||||
| **Example bundles**(`packages/examples/`)| 顶层 `demo:*` 脚本直接跑的组合示例包:`dsh-agent-spine-demo`(默认 spine + 能力)、`dsh-stdio-demo`(REPL)、`dsh-acp-demo`(ACP server)、`dsh-jsonrpc-demo` |
|
||||
| **Utils**(`packages/util/`)| 内部工具包(`brand`、`timeout`)|
|
||||
|
||||
完整的模块依赖图见 [`docs/module-graph.md`](./docs/module-graph.md)。
|
||||
|
||||
## 深入阅读
|
||||
|
||||
想理解 DeepSeek Harness 为什么与众不同,从这里入手:
|
||||
|
||||
- [架构](./docs/architecture.md) —— 服务分类和微内核结构
|
||||
- [agent 生命周期](./docs/agent-lifecycle.md) —— 一次 turn 在 loop 里的流转(含时序图)
|
||||
- [Cordis 入门](./docs/cordis-primer.md) —— 底层插件框架的实用入门
|
||||
- [工具执行流水线](./docs/tool-execution-pipeline.md) —— 一次 tool 调用如何经过权限校验、hooks 和日志
|
||||
- [能力接缝](./docs/capability-seams.md) —— 每个服务暴露的替换点
|
||||
- [Code Mode](./docs/rfc/implemented/feature/2026-06-15-code-mode.md) —— 模型每个 turn 写一段 JS 程序,在一次运行里串起多次 bash / tool 调用。**多步操作 → 一次模型往返**,不是每次调用一次往返
|
||||
- [动态工作流](./docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md) —— 模型写一段 JS orchestrator,把多个 sub-agent 并行 fan out、合并结果、再回到父 agent —— 而不是链式地调 subagent tool
|
||||
- [自引用的 Cordis 工具集](./docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md) —— SDK 自身的插件管理机制(`cordis_inspect` / `cordis_mount` / `cordis_unmount`)以 tool 的形式暴露给模型,让模型能在运行时查看当前运行时并按需挂载新插件
|
||||
|
||||
文档站:**[deepseek.com/harness-sdk/docs](https://deepseek.com/harness-sdk/docs)**。
|
||||
|
||||
## 社群
|
||||
|
||||
<a name="community"></a>
|
||||
|
||||
- **[GitHub Issues](https://github.com/deepseek-harness/deepseek-harness/issues)** —— Bug 反馈
|
||||
- **[GitHub Discussions](https://github.com/deepseek-harness/deepseek-harness/discussions)** —— 功能建议、设计讨论、Q&A
|
||||
|
||||
企业微信讨论群通过腾讯问卷申请入群,专人筛选后邀请:
|
||||
|
||||
<img src="./assets/community-wecom-survey.png" alt="腾讯问卷 · 企业微信社群入群申请" width="220">
|
||||
|
||||
## License
|
||||
|
||||
[BSD 3-Clause](./LICENSE) © DeepSeek
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 666 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 662 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
Reference in New Issue
Block a user