16 KiB
AGENTS.md
DeepSeek Harness is an all-plugin agent harness on vendored Cordis. Read docs/architecture.md before changing packages/; follow docs/AGENTS.md for documentation.
Pre-release stance: foundation over blast radius
Remove at the first tagged release. Until then, prefer correct foundations over compatibility shims and update every reference together. Backends reject old disk formats; SQLite increments SCHEMA_VERSION, while dsh-session holds SESSION_FORMAT_VERSION at 0 without a compatibility promise.
Application launch
Node apps launch only through dsh profiles; application-package bins, demos, and SDK argv escape hatches are forbidden. The private Python runtime is the sole temporary exception. Architecture owns scope and deferred artifact rename; pnpm run verify-application-entrypoints enforces it.
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
shell/ bash capability: Service Definition + local/pwsh providers + shell Consumers
subprocess/ subprocess capability + local process-tree provider + shared Win32 library
terminal/ persistent sessions
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
compaction/ 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
webhook/ webhook ingress
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
identity/ anonymous identity
settings/ user-settings capability + file provider
credentials/ credential/authorization capabilities + env/.env provider
acp/ automation-only Agent Client Protocol server
interaction/ approval/interaction capabilities, permission, commands, ask-user
boot/ shared profile/application boot glue
sdk/ JSON-RPC protocol, server, TypeScript client, and private Python carrier
examples/ reusable demo bundles (agent-spine)
experimental/ private prototypes excluded from official releases
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 hygiene # knip + publint + workspace constraints + NodeNext consumer check
pnpm run check:windows-wine # ONLY when diagnosing a known Windows failure (needs wine); CI owns this signal
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 dsh --profile headless "task" # run one task from source (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
If a required gh, pnpm, build, test, or generator command fails because the sandbox blocks credentials, network, IPC, watching, or nested sandbox-exec, retry unchanged with the narrowest host escalation. Require sandbox evidence; never bypass test failures or the product sandbox.
Run relevant checks locally
Before pushes, use dsh-pre-push-checks to choose the smallest diff-covering checks; after gh stack sync, validate immediately and never merge before they pass. Report commands only. Match evidence to its surface: focused behavior tests, model/user snapshots, doc-sync, build/hygiene plus built smokes for published paths, and real-API e2e for provider behavior. CI owns exhaustive coverage and the platform matrix; run them locally only by 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) under plugin config and entry disabled; other metadata stays literal, so conditional composition also 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 are rescoped (mapping) andprivate: true.@deepseek-ai/cordisis a peerDependency (+ dev) of every harness package. - ESM everywhere (
"type": "module"). Use package names across packages and.tsin local relative imports. 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 exports) — 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. Without a plausible relationship, an explained empty companion is correct (package invariant rules).
- 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. ASessionEventMapmember is required-on-read by default — builds that do not know its type refuse the log unless the event carries the envelope'signorable: true; only structural format changes bumpSESSION_FORMAT_VERSION(mechanism). - 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-shellrequest/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. - Keep comments local. Do not restate code, explain distant behavior unless locally required, or expand unrelated comments (rationale).
- 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).
- Client UI copy is locale-owned. Route product text through typed dictionaries and
tor localized primitive props;verify-client-ui-i18nrejects hardcoded copy (decision). - Testing policy — docs/testing.md. Every non-trivial model- or product-user-visible change updates a keyless runnable-example snapshot; package, e2e-only, and mock-only tests do not substitute. Fixtures replay on macOS/Linux; fix fixtures, not normalizers.
- Design each tool's UI presentation up front. Host presenters stay pure; Web cards derive from raw events and persisted result metadata (cookbook).
- Plan unit, e2e, and snapshot coverage for capability seams, lifecycle paths, and transcript output; include missing snapshot-harness support in the same change.
- Both SDKs project the loop. Agent-loop, session-lifecycle, and
SessionEventMapchanges update the TypeScript and Python SDK expected outputs in the same PR;pnpm run testcovers neither (surfaces). - Choose PR history deliberately. Split independent changes and fix the introducing PR before propagation. Standalone/stack branches may merge-forward or rebase. Rewrites use
--force-with-lease, abort on remote movement, never raw--force; preserve an in-progress merge-forward 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 state complete contracts and context, not reasoning transcripts. Use direct, concrete terms. Do not use metaphors. Before writing contract, boundary, or shape, ask whether a more exact term names the subject: write response fields, JSON validation, or ESM exports instead of response shape, validation boundary, or module shape. Keep contract for preconditions, postconditions, invariants, compatibility promises, and other obligations that callers, callees, implementers, providers, producers, or consumers rely on. Keep a literal process, wire, security, transaction, or lifecycle boundary. Do not narrate control flow or tests, preserve review history, or restate code. Keep behavior, failure, timing, ownership, and safe-use facts; link the rationale. Use dsh-prose-standard for decisions. Wire mechanically checkable invariants into an executed top-level gate and prove each 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 and JSDoc contracts together. Routine bilingual work follows docs/AGENTS.md; only explicit user invocation may run dsh-translate-docs. Current-state prose, one physical line per paragraph, one home per fact, and word budgets live there.
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 required content 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.