# Conflicts: # docs/architecture.i18n.yaml # docs/architecture.zh.md # docs/module-graph.md # packages/host/apiproxy/src/api-proxy.ts # scripts/verify-package-readme-model-experience.ts
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
api/ Remote BFF assembly and TypeRT RPC gateway
typert/ type graph generator, loader, and runtime registry
llm/ LLM capability: Service Definition/Consumer + DeepSeek providers
e2b/ E2B POC: sandbox + FS/subprocess adapters
bash/ bash capability: Service Definition + local/pwsh providers + shell Consumers
subprocess/ subprocess capability + local process-tree provider
pty/ persistent PTY capability
fs/ filesystem capability + policy
lsp/ language-server capability
skill/ skill provider registry + local impl + catalog/loader tool
web/ web capability: Service Definition + search/fetch providers + tool Consumer
compact/ compaction capability + basic provider
context/ request-context plugins
subagent/ subagent capability: Service Definition + providers + delegation Consumers
bundle/ installable dsh --profile patch-layer bundles
workflow/ workflow capability + worker-thread provider + tool Consumer
todo/ todo_write tool
plan/ plan mode as logged state
preset/ per-session agent composition from preset cordis.yml files
guard/ loop-hygiene + tool-timeout plugins
self-modification/ the agent inspects/mounts its own plugins
hooks/ Claude Code/Codex hook bridges + wire-protocol library
session/ durable session data: persistence, projection, titles, telemetry
settings/ user-settings capability + file provider
credentials/ credential-reference capability + env/.env provider
acp/ automation-only Agent Client Protocol server
interaction/ approval/interaction capabilities, permission, commands, ask-user
boot/ shared app-bin glue
scaffold/ project tooling: helper, launcher, initializer, SDK protocol
examples/ demo bundles (agent-spine + CLI/ACP/JSON-RPC bins)
support/ dev/test infrastructure
util/ zero-dependency utilities
python/ Python SDK and bundled runtime (see python/README.md)
native/ @deepseek-ai/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-syncfor 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, nottest, 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 areprivate: true.cordisis 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 builtlib/under plain Node; source regressions use their declared launcher (testing policy). ThedshCLI 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/Webcordis.ymlbare plugins must appear in their resolver manifest'sdependencies;verify-cordis-configenforces it. - Registrations are effects: every contribution goes through
ctx.effect()/ctx.on(); a registry'sregister()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
@modeand 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 short-circuits the chain (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 documented extension points; changing
agent-looprequires updating docs/architecture.md. - A capability seam comprises Service Definition / Service provider / Consumer roles. It is complete, never one role; split only when roles evolve independently (glossary).
- Prefer maintained dependencies over hand-rolling when they genuinely delete owned code and tests (policy).
- Explicit > implicit at package boundaries: defaulting is an explicit
resolve(request): Specstep in the owning implementation, never a hidden?? defaultinsiderun()(thedsh-bashrequest/spec split is the template). - No hardcoded tunables in plugins: deployment-varying choices are validated
Configfields changeable from cordis.yml; aDEFAULT_*constant or test hook 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>fromdsh-brand), never barestring. - Trust TypeScript at typed same-process boundaries. 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
pathstosrcand pass on a clean tree; gates consuming builtlib/declare that dependency (layout). - Keep compiler faces explicit. Each package uses one aggregate except
api/remotes; repo-wide programs seed a face config, never the root solution (layout). - An empty
catchnames what it swallows and why nothing else can reach it; keep thetryto 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 policy — docs/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 ofargs(cookbook). - Plan unit, e2e, and snapshot coverage for new capability seams, lifecycle shapes, and transcript surfaces; add missing snapshot-harness support in the same change.
- 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). - Labels: one PR
kind/*, all materialarea/*, and native Issue Type (taxonomy). - TODO markers:
FIXME/TODO/XXXby 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 narrowing 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 Service Definition, 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.