Files
deepseek-harness/AGENTS.md
T
Yichen Jiang 52607cab69 docs(agent-presets): bring the note and the architecture map up to what shipped
The Agent Note was written when only the seam existed and never caught up.
Rewritten in place, per the implemented-note contract, with the four facts the
later work established:

- a preset file is an INPUT: `EntryTree.write()` persists a tree whenever the
  Loader thinks the config changed, and a self-disposing plugin is enough, so
  the inherited behaviour truncates a shipped preset to `[]` the first time a
  session ends
- a plugin that looks itself up in the global registry breaks inside a preset,
  because `register()` files into the calling context's scope — the general
  rule behind the `dsh-tool-skill` fix
- an entry-local `isolate` realm is invisible to the agent's own scope too, not
  only to the host, which is what makes a preset's registry that agent's own
  and also why a consumer left outside the group silently contributes nothing
- switching is blank-only, and why it swaps the subtree rather than the session

`docs/architecture.md` gains an Agent Presets section: the map has to carry a
new architectural concept or it is wrong, and the root layout gains the group.

Both budget ceilings are raised rather than the content cut. `AGENTS.md` sat at
1774/1775 — one word of room, already far under the 5% headroom the standard
asks for — so no group line could be added at all; `architecture.md` was in the
same shape. Raising restores headroom instead of encoding "the map may not grow".
2026-08-07 00:40:43 +08:00

14 KiB

AGENTS.md

DeepSeek Harness SDK is a plugin-based agent harness on vendored Cordis: everything is a plugin. Read docs/architecture.md before changing packages/; follow docs/AGENTS.md for documentation.

Pre-release stance: foundation over blast radius

Remove this section at the first tagged release. With no external consumers, prefer the correct foundation over compatibility shims: rename or repackage freely and update every reference together. Backends reject old on-disk formats. SQLite uses monotonic SCHEMA_VERSION; dsh-session keeps SESSION_FORMAT_VERSION at 0 with no compatibility promise.

Repository layout

vendor/      Vendored Cordis source — manifest + sync procedure in vendor/README.md
packages/    @deepseek-ai/dsh-<pkg> workspaces at packages/<group>/<pkg>/
  core/        product API spine: session, system-prompt, tools, agent, agent-loop
  typert/      type graph generator, loader, and runtime registry
  llm/         LLM seam + DeepSeek adapters (direct-fetch + pi-ai design twin)
  bash/        bash executor seam + local/pwsh impls + model-facing shell tools
  subprocess/  subprocess seam + local process-tree impl
  pty/         persistent PTY seam/backend/tools
  fs/          filesystem seam + local impl + policy gate + read/write/edit tools
  lsp/         language-server seam + local stdio provider + model-facing lsp tool
  skill/       skill provider registry + local impl + catalog/loader tool
  web/         web seam + search/fetch providers + model-facing web tools
  compact/     compaction seam + basic backend
  context/     request-context plugins
  subagent/    subagent seam + spawn/fork/ACP backends + delegation tool
  bundle/      profile plugin bundles: installable patch layers for dsh --profile
  workflow/    workflow seam + worker-thread engine + workflow tool
  todo/        todo_write tool
  plan/        plan mode as logged per-agent collaboration state
  preset/      per-session agent composition from preset cordis.yml files
  guard/       loop-hygiene plugins
  cordis/      self-referential toolset: the agent inspects/mounts plugins in its own runtime
  hooks/       Claude Code/Codex hook bridges + shared wire-protocol library
  session-persistence/  persistence seam + JSONL/SQLite backends
  settings/    user-settings seam + file-backed provider
  credentials/ credential-reference seam + env-over-.env provider
  acp/         automation-only Agent Client Protocol server
  ui/          JSON-RPC bridge; boot, approval, interaction plugins
  examples/    demo bundles (agent-spine + CLI/ACP/JSON-RPC bins) leaves load
  support/     dev/test infrastructure
  util/        zero-dependency utilities
python/      Python SDK and bundled runtime (see python/README.md)
native/      node-addon-landlock-run source of record (see native/README.md)
examples/    Runnable cordis.yml leaves over packages/examples bundles (see examples/AGENTS.md)
.agents/     Agent workflows and Agent Notes (`notes/`)
docs/        architecture, generated catalogs, postmortems, cookbook (see docs/AGENTS.md)
scripts/     repo gates and generators
website/     VitePress projection of selected bilingual docs/ sources

Package groups: packages/README.md.

Commands

pnpm install            # pnpm workspaces, node ^22.19 || >=24
pnpm run clean           # remove build outputs and safe residue from deleted packages
pnpm run test           # vitest unit tests
pnpm run test:coverage  # CI coverage gate: per-file 100% on packages/*/*/src
pnpm run test:e2e       # real-API tests; self-skip without DEEPSEEK_API_KEY
pnpm run test:snapshot  # keyless ACP/headless replay vs expected outputs; filter: -t <name>
pnpm run test:snapshot:record  # re-record expected outputs (needs key)
pnpm run typecheck
pnpm run lint
pnpm run duplication    # cross-file TypeScript clone detection
pnpm run build          # tsc emits lib/types, tsdown bundles runtime
pnpm run check:windows-wine  # ONLY when diagnosing a known Windows failure (needs wine); CI owns this signal
pnpm run hygiene        # knip + publint + workspace constraints + NodeNext consumer check
pnpm run doc-sync       # all documentation gates; leaf list in scripts/run-gates.ts
pnpm run website:build  # VitePress build (doubles as dead-link check)
pnpm run demo:headless "task" # one-shot agent (needs DEEPSEEK_API_KEY)
pnpm run demo:cordis    # the agent modifies its own runtime (needs key)
pnpm run demo:acp       # ACP automation server (needs DEEPSEEK_API_KEY)

Host sandbox failures

When required gh, pnpm, build, test, or generator commands fail because the agent sandbox blocks credentials, network, IPC, file watching, or nested sandbox-exec, retry unchanged with the narrowest host escalation before diagnosing authentication or project failure. Require sandbox evidence; never bypass genuine test failures or the product sandbox under test.

Run relevant checks locally

Run checks before pushes via dsh-pre-push-checks; report only commands run. After gh stack sync, validate immediately; do not merge before checks pass.

  • Match evidence to the surface: focused tests for behavior, snapshots for model or user output, doc-sync for docs, build/hygiene and built smokes for published paths, and real-API e2e for provider behavior.
  • Never default to the full suite or repeat a passing check for commit or push. CI owns exhaustive coverage and the platform matrix; rehearse all locally only by explicit request, for CI diagnosis, or for an irreducibly repository-wide change.
  • test:coverage, not test, is the CI coverage gate (why).

Secrets / .env

Real-API tests and demos read DEEPSEEK_API_KEY, optional DEEPSEEK_BASE_URL, and root .env. cordis.yml allows !!js (never !js) only under plugin config; Loader metadata is static, so conditional composition uses overlays (primer). Never commit credentials. CI e2e skips without a key; testing.md owns key policy.

Conventions

  • Every npm package is @deepseek-ai/dsh-<name>; vendored packages keep upstream names and are private: true. cordis is a peerDependency (+ dev) of every harness package.
  • ESM everywhere ("type": "module"). Cross-package imports use package names; in-package relative imports include .ts. Config subprocesses run built lib/ under plain Node; source regressions use their declared launcher (testing policy). The dsh CLI source launch runs through tsx's ESM-only hook (node --import tsx/esm); modules it reaches must stay ESM (no CJS-only shapes) — Node's native TypeScript modes are unavailable across the engines range (source-launch contract). Raw/Web cordis.yml bare plugins must appear in their resolver manifest's dependencies; verify-cordis-config enforces it.
  • Registrations are effects: every contribution goes through ctx.effect() / ctx.on(); a registry's register() returns the disposer.
  • Runtime invariants assert owned relationships. Check authoritative event streams or mutable data, not service or method presence, plugin metadata or effects, or fixed pure examples. If a package has no plausible relationship, an explained empty companion is correct (package contract).
  • Typed events use declaration merging and merge-extensible maps. Event JSDoc needs @mode and payload @param; scoped keys absent from payloads need @dshScopeScan unsupported. Public service methods document parameters and non-void returns.
  • Switch on discriminant tags. Closed unions end in assertNever; merge-extensible unions fall through a documented default.
  • Waterfall listeners MUST call next() to delegate; returning without it is the veto (semantics).
  • Model-visible ⟺ logged: anything that reaches a model request must be reconstructable from the session log; a new model-visible input requires a session event.
  • 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; don't split preemptively.
  • Prefer maintained dependencies over hand-rolling when they genuinely delete owned code and tests (policy).
  • Explicit > implicit at package seams: defaulting is an explicit resolve(request): Spec step in the owning implementation, never a hidden ?? default inside run() (the dsh-bash request/spec split is the template).
  • No hardcoded tunables in plugins: deployment-varying choices are validated Config fields changeable from cordis.yml; a DEFAULT_* constant or test seam is not configurability. Protocol constants, external specs, and security invariants stay fixed.
  • Misconfiguration fails loud at load when self-contained, otherwise at the earliest resolvable point; never silently skip a missing referent.
  • Opaque cross-boundary ids are branded (Branded<B> from dsh-brand), never bare string.
  • Trust TypeScript at typed same-process seams. Do not add runtime validation, fallback behavior, or hostile-input tests solely for values the static interface requires; validate at parser/config, queued, model/tool JSON, durable/file, worker, process, and wire boundaries.
  • Source plane vs artifact plane, never mixed. Static gates and tests resolve workspace imports through tsconfig paths to src and pass on a clean tree; gates consuming built lib/ declare that dependency (layout).
  • ts.Program consumers seed tsconfig.host.json or tsconfig.client.json, never the root solution — one program holding both sides collides the cordis Context merges (layout).
  • An empty catch names what it swallows and why nothing else can reach it; keep the try to one statement.
  • Prefer symmetry for parallel values; unexplained asymmetry usually signals a missed extraction.
  • Tests describe behavior, not correctness. Change obsolete behavior with its tests; explain why in the PR.
  • Non-trivial changes MUST include an Agent Note in the same PR; only mechanical/local edits are exempt (scope). Archived notes are frozen: never edit or treat them as current authority (archive policy).
  • Testing policydocs/testing.md. Every non-trivial model- or product-user-visible behavior change adds or updates a keyless snapshot through a real runnable example in the same PR; package tests, e2e-only assertions, and mock-only fixtures do not substitute for the assembled application transcript. Fixtures must replay on macOS/Linux; fix fixtures, not normalizers.
  • A tool's UI render intent is part of its design, decided up front (generic/terminal/diff, locations); presentation methods are pure functions of args (cookbook).
  • Plan unit, e2e, and snapshot coverage for new seams, lifecycle shapes, and transcript surfaces; missing snapshot-harness support is part of the implementation, not deferred follow-up.
  • Choose PR history deliberately. Split independent changes; fix the introducing PR before propagation. Standalone PRs and official stacks may merge-forward or rebase after review. Rewrites use --force-with-lease, abort on remote movement, never raw --force; an in-progress merge-forward preserves its checkpoint before taking a newer base (rationale).
  • Label PRs: one kind (feature/bug-fix/doc/testing/cleanup), each matching area; the taxonomy is extensible.
  • TODO markers: FIXME/TODO/XXX by urgency (semantics).
  • Files end with exactly one trailing newline; git diff --cached --check (pre-commit) gates it.

Defensive patterns

Read docs/defensive-patterns.md before lifecycle, concurrency, subprocess, or teardown work.

Type safety and documentation

Everything compiles under strict: true with noImplicitAny; every remaining any explains why a narrower type is infeasible. Every module and export has concise JSDoc for its non-obvious contract; function-like exports include @param/@returns, as enforced by verify-export-jsdoc. Heritage-declared members, plugin-protocol slots, and constructors keep their docs at the declaring seam, protocol, or class.

Comments and docs preserve complete contracts and non-obvious orientation, not reasoning transcripts. Do not narrate control flow or tests, preserve review history, or restate code. Keep factual clauses affecting behavior, failure, timing, ownership, or safe use; link aggressively to owning rationale. Use dsh-prose-standard for prose decisions. Wire mechanically checkable invariants into an executed top-level gate and prove each new or changed acceptance path rejects an invalid case. Use narrow justified exceptions instead of disabling a rule globally.

Docs accompany every code change: update affected README/JSDoc contracts together; update both sides of a bilingual pair and re-record it (i18n contract). Current-state prose, one physical line per paragraph, one home per fact, and word budgets live in docs/AGENTS.md.

Editing these instructions

CLAUDE.md symlinks AGENTS.md at root, packages/, and examples/; edit the real file. Keep each rule self-contained while linking high-level docs. Condense when clarity survives; raise a verify-doc-budgets ceiling when the contract genuinely needs more space.

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.