Files
deepseek-harness/AGENTS.md
Tianyi Cui 7702a33531 docs(AGENTS): rewrite the root standing orders to the 1,500-word budget
Applies the documentation standard to its biggest offender. Every rule
survives as one to three lines plus a link to its durable home; the
stories, duplicate statements, and re-narrations go:

- Situational clusters evict to new homes: docs/testing.md (tiers,
  with-key policy, real-over-mock, world-verification, real-entry-path
  guards), docs/defensive-patterns.md (the bug-class rules), and
  docs/cookbook/responding-to-pr-review-on-a-stack.md (the stacked-PR
  review procedure).
- Doc-authoring rules consolidate into docs/AGENTS.md § Writing rules
  (current-state-never-history, md-wrap, ts-block compilation, @mode,
  catalog same-change, pair same-change).
- packages/README.md drops to the group table + the extension-vs-bundle
  dependency rule; the hand ASCII graph yields to the generated
  module-graph.md; group READMEs are the canonical per-package map.
- packages/AGENTS.md keeps only its packages-specific rules (export
  shape, ctx.get, real-Loader coverage); examples/AGENTS.md repoints
  its with-key-policy link; rfc/README.md loses a narrated-history
  aside; dsh-code-review / dsh-find-simplifications / verify-md-wrap
  references follow the moved content.
- Budget manifest ratchets: AGENTS.md 8200 -> 1500 (now 1,495 words),
  packages/README.md 1900 -> 600, packages/AGENTS.md 600 -> 450; the
  two new eviction docs join the budget set (testing 800, defensive
  550); docs/AGENTS.md raises 1000 -> 1250 for the absorbed writing
  rules (the one justified increase). The doc-tiers RFC's deferred list
  prunes the two items this change ships.
2026-07-04 14:22:47 +08:00

11 KiB

AGENTS.md

This is the monorepo of the DeepSeek Harness group; it hosts DeepSeek Code, DeepSeek's coding agent product. The codebase is built on the vendored Cordis framework, microkernel-style: everything is a plugin. Read docs/architecture.md before changing anything under packages/ — the service map, event taxonomy, loop lifecycle, and extension seams. The documentation standard is docs/AGENTS.md. Design context: Coding Harness MVP 需求分析, 微内核Harness实现思路.

Pre-release stance: foundation over blast radius

This applies only while the harness is unreleased — remove this section at the first tagged release. There are no external consumers, so optimize for the correct foundation, not a small diff: move files, rename public symbols, repackage plugins, and update every reference in the same change. No backward-compat shims, deprecation aliases, or re-export stubs. On-disk formats need no migrations — a backend REJECTS anything not at the current version. Two sanctioned version stances: monotonic bump-and-reject (the SQLite backend's SCHEMA_VERSION), and a pinned 0 that absorbs all shape churn (SESSION_FORMAT_VERSION in dsh-session, documented "no compatibility implied") so the instability stays explicit. Real version policy begins at the first release.

Repository layout

vendor/      Vendored Cordis source — manifest + sync procedure in vendor/README.md
packages/    Harness packages at packages/<group>/<pkg>/, all named @deepseek-ai/dsh-<pkg>
  core/        product API spine: session, system-prompt, tools, agent, agent-loop, agent-core (the bundle)
  llm/         LLM seam + the DeepSeek adapters (hand-rolled + pi-ai design twin)
  bash/        bash executor seam + local impl + model-facing bash tools
  fs/          filesystem seam + local impl + policy gate + read/write/edit tools
  web/         web seam + search/fetch providers + model-facing web tools
  compact/     compaction seam + basic backend
  subagent/    subagent seam + spawn/fork/ACP backends + delegation tool
  todo/        the todo_write tool
  hooks/       Claude Code / Codex hook bridges + shared wire-protocol library
  session-persistence/  persistence seam + JSONL/SQLite backends
  ui/          ACP bridge + the stdio/ACP app packages (each with a bin)
  support/     dev/test infrastructure: invariants, ui-stdio, llm-replay, subagent-mock
  util/        zero-dependency utilities (Branded<B>)
examples/    Runnable demos: thin cordis.yml leaves over the app packages (see examples/AGENTS.md)
docs/        architecture, generated catalogs, RFCs, postmortems, cookbook (see docs/AGENTS.md)
scripts/     repo gates and generators

Per-package map: the group READMEs, indexed from packages/README.md.

Commands

pnpm install            # pnpm workspaces, node >= 24
pnpm run test           # vitest unit tests
pnpm run test:coverage  # THE gating test run: per-file 100% coverage on packages/*/*/src
pnpm run test:e2e       # real-API tests; self-skip without DEEPSEEK_API_KEY
pnpm run test:snapshot  # keyless ACP replay vs committed goldens; filter one: pnpm run test:snapshot -t <name>
pnpm run test:snapshot:record  # re-record goldens against the real API (needs key)
pnpm run typecheck
pnpm run lint
pnpm run build          # tsc emits lib/types, tsdown bundles runtime
pnpm run hygiene        # knip + publint + workspace constraints + NodeNext consumer check
pnpm run doc-sync       # all documentation gates; see the doc-sync script in package.json
pnpm run demo:echo      # mock-model REPL, no key needed
pnpm run demo:repl      # real REPL coding agent (needs DEEPSEEK_API_KEY)
pnpm run demo:acp       # ACP server agent over JSON-RPC stdio (needs DEEPSEEK_API_KEY)

Run the CI gates locally before marking a PR ready

CI is the backstop, not the first place a gate runs. From a fresh clone or worktree, run pnpm run build once first — publint and the NodeNext check validate built lib/. The CI-equivalent run:

set -euo pipefail
pnpm run typecheck
pnpm run lint
pnpm run test:coverage
pnpm run test:snapshot
pnpm run doc-sync
pnpm run verify-module-graph
pnpm run build
pnpm run hygiene
out=$(printf 'echo ci smoke\n' | pnpm run demo:echo 2>&1)
printf '%s\n' "$out" | grep -q '\[tool call\] echo({"text":"ci smoke"})'
printf '%s\n' "$out" | grep -q '\[tool result\] ECHO: CI SMOKE'
ls .sessions/_no-cwd/main-session-*.jsonl >/dev/null
rm -rf .sessions
pnpm exec vitest run --config vitest.e2e.config.ts packages/ui/stdio-agent/tests/built-bin.e2e.ts packages/ui/acp-agent/tests/built-bin.e2e.ts

test:coverage, not test, is the gating run (why); a review sign-off counts only for the commands it actually ran.

Secrets / .env

Real-API tests and demos read DEEPSEEK_API_KEY (and optional DEEPSEEK_BASE_URL) from the environment or a gitignored root .env loaded via process.loadEnvFile(). cordis.yml references env vars with the !!js tag (never !js). Never commit credentials. CI has no secrets, so e2e suites self-skip without a key — a CI accommodation, not a cost signal; the with-key policy is in docs/testing.md.

Conventions

  • Every npm package is @deepseek-ai/dsh-<name>; vendored packages keep upstream names and are private: true. cordis is a peerDependency (+ devDependency) of every harness package.
  • ESM everywhere ("type": "module"). Cross-package imports use package names, never relative paths; in-package relative imports use explicit .ts extensions. Dev/test/demo run unbuilt via tsx + the root tsconfig paths map; building is only for consumers outside the repo.
  • Registrations are effects: every contribution goes through ctx.effect() / ctx.on(); a registry's register() returns the disposer. Every registry gets an HMR-safety test.
  • Typed events via declaration merging; extensible unions use the merge-extensible-map pattern (ContentBlockMap, SessionEventMap, …). Every new event's JSDoc carries an @mode tag — the catalog generator hard-errors without it; mode semantics are in the generated catalog header and the catalog RFC.
  • Discriminated unions: switch on the tag, not if-chains. Closed unions end with default: assertNever(...); merge-extensible unions must NOT — handle known cases and fall through default with a comment.
  • Waterfall listeners MUST call next() to delegate; returning without it is the veto (semantics).
  • Plugins, not loop changes: new behavior goes on the documented extension seams; changing agent-loop requires updating docs/architecture.md.
  • Capability seams are three packages — interface / implementation / consumer (capability seams); don't split preemptively.
  • Explicit > implicit at package seams: no optional field silently filled by a hidden ?? default inside run(); defaulting is an explicit resolve(request): Spec step in the owning implementation (the dsh-bash request/spec split is the template).
  • Opaque cross-boundary ids are branded (Branded<B> from dsh-brand), never bare string (branded IDs).
  • An empty catch names what it swallows and why nothing else can reach it; keep the try to one statement.
  • Symmetry is usually more correct: parallel values get parallel form; asymmetry is a smell for a missed extraction.
  • Tests document behavior, not golden truth: a green test pins what the code DOES, not what it SHOULD do. Before preserving a behavior solely for its test, ask whether it is load-bearing; an artifact changes together with its test, with the why in the PR (worked example).
  • RFCs are proposals, not golden truth: validate its premise against current code before implementing; friction is evidence of over-reach — amend on the way to implemented/ (worked example).
  • Testing policy — tiers, with-key generosity, real-over-mock, world-verification, real-load-path and published-bin guards: docs/testing.md. A transcript/UX-affecting change needs a snapshot test, or a PR note why none applies.
  • A tool's ACP render intent is part of its design, decided up front (generic/terminal/diff, locations); presentation methods are pure functions of args (render-intent RFC, cookbook).
  • A new capability seam, lifecycle shape, or transcript surface names its coverage at every tier (unit, e2e, snapshot) at plan time and verifies the harness can express it — a gap is scheduled work, not a mid-build surprise.
  • Merge PRs with merge commits (gh pr merge --merge), never squash/rebase. Never rewrite a pushed branch; update a child by merging its parent down. A review fix lands on the PR that introduced the issue, as a separate commit, then merges down (stacked-review guide).
  • TODO markers by urgency: FIXME / TODO / XXX (semantics).
  • Files end with exactly one trailing newline; git diff --check (pre-push) gates it.

Defensive patterns

docs/defensive-patterns.md carries the hard-won bug-class rules: report orthogonal outcomes independently; honor cross-seam contracts on both sides; async state is not synchronous state; dispose must reach quiescence; contain callback exceptions; never hand untrusted output the ambient environment or predictable paths. Read it before writing lifecycle, concurrency, subprocess, or teardown code.

Type safety and documentation

Everything compiles under strict: true with noImplicitAny; every remaining any carries a comment saying why a narrower type is infeasible. Lean toward the stricter lint rule and the extra mechanical gate: encode invariants in checks (verify-* scripts), preferring a narrow justified escape hatch over a rule left off globally. Type gymnastics are acceptable inside core packages when they buy plugin-author DX (the defineTool schema DSL is the canonical example).

Docs are part of every change: code changes update their README and JSDoc in the SAME change; a bilingual-pair edit updates the counterpart and re-records (i18n contract). The writing rules — document the current state never the history, one physical line per paragraph, one home per fact — and the word-budget gate live in docs/AGENTS.md.

Editing these instructions

AGENTS.md is the real file; CLAUDE.md is a symlink to it (root, packages/, examples/). Edit AGENTS.md, never the symlink. This file is budget-gated (verify-doc-budgets): additions displace something or justify a ceiling raise in the PR.

Vendoring policy

vendor/ packages are pinned source copies (manifest with upstream SHAs in vendor/README.md). Update via the sync procedure there; re-apply or retire the logged local modifications; rerun pnpm run test && pnpm run build.