Files
deepseek-harness/AGENTS.md
2026-07-14 16:21:41 +08:00

10 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/    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
  skill/       skill provider registry + local impl + catalog/loader tool
  web/         web seam + search/fetch providers + model-facing web tools
  compact/     compaction seam + basic backend
  subagent/    subagent seam + spawn/fork/ACP backends + delegation tool
  workflow/    workflow seam + worker-thread engine + the workflow tool
  todo/        the todo_write tool
  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
  ui/          ACP/stdio/JSON-RPC front doors; boot, approval, and interaction plugins
  support/     dev/test infrastructure packages
  util/        zero-dependency utilities
python/      Python SDK and bundled runtime (see python/README.md)
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 ^22.19 || >=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 goldens; filter: -t <name>
pnpm run test:snapshot:record  # re-record goldens (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 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:cordis    # self-referential demo: the agent modifies its own runtime (needs key)
pnpm run demo:acp       # ACP server agent (needs DEEPSEEK_API_KEY)

Run the CI gates locally before marking a PR ready

Run narrow checks during implementation and this CI-equivalent sequence before marking a PR ready. Fresh worktrees need pnpm run build before publint and NodeNext inspect lib/:

set -euo pipefail
pnpm run typecheck
pnpm run lint
pnpm run duplication
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'
test -n "$(find .sessions -path '.sessions/cwd-*/main-session-*.jsonl' -type f -print -quit)"
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 packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts

test:coverage, not test, is the gate (why); report only commands actually run.

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 by process.loadEnvFile(). cordis.yml uses !!js (never !js) for env vars. Never commit credentials. CI e2e self-skips without a key; docs/testing.md owns the with-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, never relative paths; in-package relative imports use explicit .ts extensions. Dev/test/demo run unbuilt via tsx + the root tsconfig paths map; builds are for outside consumers only.
  • Registrations are effects: every contribution goes through ctx.effect() / ctx.on(); a registry's register() returns the disposer.
  • Typed events use declaration merging; extensible unions use merge-extensible maps. Event JSDoc needs @mode and payload @param tags; public service methods document parameters and non-void returns. Catalog gates enforce this.
  • 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.
  • 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 choices are defaulted, 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.
  • 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.
  • Validate RFC premises against current code; friction may expose overreach, so amend proposals before moving them to implemented/.
  • Testing policydocs/testing.md. Transcript changes need snapshots or a PR note. Fixtures must replay on macOS/Linux; fix fixtures, not normalizers.
  • 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 (cookbook).
  • Plan unit, e2e, and snapshot coverage for new seams, lifecycle shapes, and transcript surfaces, and schedule any missing harness support before implementation.
  • Merge PRs with merge commits, never squash/rebase or rewrite pushed branches. Put a review fix on its introducing PR, then merge down the stack (guide).
  • TODO markers: FIXME/TODO/XXX by urgency (semantics).
  • Files end with exactly one trailing newline; git diff --check (pre-push) 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. Encode enforceable invariants in checks, using narrow justified exceptions rather than disabling a rule globally.

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

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.