Upstream Audit: DeepSeek Harness
This document records what the upstream repository actually contains, measured from the checkout at /root/dsh-upstream (release tag dsh-v0.2.2-rc.1) before any Avalanche AI work. It is the factual basis for FORK_STRATEGY.md and ARCHITECTURE.md; where an assumption in the Avalanche AI brief disagreed with the source, the source wins.
Repository facts
| Fact | Value |
|---|---|
| Repository | https://github.com/deepseek-ai/deepseek-harness |
| License | MIT, Copyright (c) 2026 DeepSeek |
| Package name / version | @deepseek-ai/dsh-root 0.2.2-rc.1 |
| Runtime | Node.js >=24.9.0 |
| Package manager | pnpm@11.28.5 (Corepack) |
| Workspace globs | vendor/*, packages/*/*, native/system, native/system/packages/*, apps/*, benchmarks, website, python/sdk-runtime |
| Package count | 389 packages under packages/<group>/<package>, 5 apps under apps/, 9 vendored packages under vendor/ |
| Tracked files | ~20,700, of which ~6,100 TypeScript files |
| CI posture | Coverage gate at 100% per file on packages/*/*/src, doc-sync gates, translation pairing, package README limitations gate, application-entrypoint gate |
The repository is a source checkout: every package's main points at lib/index.js, but lib/ is a build output that does not exist until pnpm run build (or build:artifacts) runs. A bare tsx apps/cli/src/bin.ts boot therefore fails in base bundles before reaching any Avalanche row; verification of this fork's composition uses the Loader path directly (see TESTING_STRATEGY.md).
Architecture in one page
Everything is a Cordis plugin. Cordis supplies a shared Context where plugins contribute services (ctx.<name>), typed events (ctx.on / ctx.emit), and reversible effects (ctx.effect, ctx.on, ctx.provide). Unloading a plugin unregisters everything it contributed.
The runtime is assembled from ordered profile layers. A profile is a directory under the Harness home ($DSH_HOME/profiles/<name>) with a manifest listing bundle packages in order. Each bundle declares dsh.bundle.patch pointing at a cordis.patch.yml; patches apply to an empty entry list as insert: blocks (or replace targeted rows by id). dsh-base is the shared first layer for web, headless, sdk, and acp; the shipped templates are registered in packages/util/official-bundles/src/bundles.ts (PROFILE_TEMPLATES). Launch is exclusively through dsh --profile <name>: scripts/verify-application-entrypoints.ts rejects Node application paths that bypass the launcher.
The core loop that Avalanche AI composes with:
packages/core/agent—AgentRegistry(ctx.agents), createsAgenthandles with a per-agent scoped context and setup hook.packages/core/agent-loop— the concrete model loop; drives a session turn to quiescence.packages/core/session— append-onlySessionEventlog; events are read viactx.on('session/event', ...), never through deprecated synchronous reads.packages/core/tools—ToolRuntime(ctx.tools), tool registration, scoped restriction, and thetools/pre-execute/tools/resultpipeline events. Enforcement lives in the executor pipeline, not in schema filtering.packages/core/system-prompt— prompt-section assembly; sections are registered with a name, order, and text.packages/llm/*— model adapters andLlmRuntime(ctx.llm); adapters are pluggable per provider, andAgentDefaultModelConfigselects the model for an agent.packages/interaction/user-approval,packages/interaction/permission-presets,packages/sandbox/sandbox-policy— existing approval and sandbox policy primitives upstream; Avalanche AI does not reuse them as its chain-action policy engine because its capability classes differ, but it follows their fail-closed pattern.packages/boot/app-boot— the shared Loader boot: profile resolution, patch stacking, fail-loud startup audit, readiness.packages/boot/cmdline—cmdlineArgsandappExitservices;parseCmdlineimplements the upstream rule that a startup plugin parses arguments, publishes parsed values as a service, and a runner row injects that service and the runtime services it needs.packages/mcp/mcp-client— MCP client bridge that registers external MCP tools onctx.toolsundermcp__<server>__<tool>names.
The build constraint that shapes package layout
tsdown.config.ts builds each workspace package with the entry glob lib/types/{index,startup}.js — only index and startup per package survive the build. Arbitrary plugin subpaths (src/foo.ts) do not get emitted. This has two consequences the fork must respect:
- A package may expose exactly two built entries. Multi-plugin packages split into
src/index.ts+src/startup.ts(the pattern@deepseek-ai/dsh-headlessuses). - Anything that must be separately mountable is a separate package. Avalanche AI's seven domains are seven packages, not seven subpaths of one package.
Package tsconfig.json files list project references generated from manifest dependencies; scripts/gen-tsconfig-paths.ts generates the path aliases used by TypeScript and Vitest from the workspace globs.
Plugin conventions discovered in source
- Service packages default-export a class extending
Service; the constructor callssuper(ctx, '<serviceName>'). - Function plugins named-export
name,inject, optionalConfig, andapply, with no default export. Mixing forms makes the Loader discard the namespace (documented postmortemdocs/postmortem/0001-acp-default-export-drops-inject.md). - A plugin with unmet
static injectstays pending; its service isundefinedafterawait ctx.plugin(...). Dependencies must mount first, or the consumer must usectx.inject([...], callback)to bind when the service becomes available. ctx.get(name)is the strict global service read;ctx.<name>is topology-sensitive and reserved for declared injections.ctx.tools.restrict()throws outside an agent-scoped context; per-agent tool masks must be applied inside anAgentSetup.tools/pre-executeis the enforcement seam: a listener returns{ kind: 'allow' },{ kind: 'deny', reason },{ kind: 'ask', ... }, or{ kind: 'cancel' }, and every caller (agent, nested dispatch, future HTTP surface) passes through it.tools/resultcarries the completedToolExecution(includingexec.agent) and the result; it is the correct place to record what actually ran.
Existing surfaces relevant to Avalanche AI
| Need | Upstream mechanism | Fork decision |
|---|---|---|
| New product surface | A bundle package plus a PROFILE_TEMPLATES entry | Add avalanche template over dsh-base + dsh-avalanche-app |
| One shared runtime | Cordis context and services | One bundle patch mounts every service once; domains are data |
| Tool registry | ctx.tools + defineTool | All Avalanche tools register through one catalog package |
| Policy enforcement | tools/pre-execute event | Avalanche policy gate listens there; fails closed |
| Durable logs | Session log, JSONL persistence | Evidence ledger separate from session text; hash-chained JSONL |
| Model selection | AgentDefaultModelConfig currentSelection() | Orchestrator reads selection and passes it to agents.create |
| CLI | dsh --profile ... + cmdlineArgs/appExit | avalanche-cli parser (/startup) + runner (index) |
| External docs/data | MCP client bridge | Optional later phase; read-only docs source only |
Avalanche MCP server investigation
https://build.avax.network/api/mcp responds as MCP protocol 2024-11-05, server avalanche-mcp v2.5.0. Its declared tools cover documentation search/fetch, CLI/RPC/ACP lookup, P-Chain platform_* reads, info_* node reads, blockchain entity lookup, onchain_lookup/onchain_activity, chain_stats, and read-only build_plan/console_* runbook helpers. It is a documentation and data source, not an execution service: build_plan explicitly never signs.
Decision: treat the MCP server as an optional data/documentation source. If integrated, it mounts as an ordinary @deepseek-ai/dsh-mcp-client row, its tools are catalogued like any other tool under the same policy gate, and its output is external data that must be labeled by source. It must never become the runtime's only source of chain facts, and it must not be given write capabilities. This is Phase 2+ work, not part of the Phase 1 slice.
Gaps the fork must fill
The upstream has no Avalanche-specific capability: no chain access, no domain routing, no intent/task model, no chain-action policy vocabulary, no evidence ledger or receipts, no Avalanche CLI, and no dashboard. The fork adds these as additive packages, not by rewriting core packages. The only upstream files that change are:
packages/util/official-bundles/src/bundles.ts— onePROFILE_TEMPLATES.avalancheentry.tsconfig.base.json— project references for the new packages (generated).pnpm-lock.yaml— workspace lockfile (generated).scripts/doc-standard.spec.ts,scripts/verify-subsystem-pages.ts,scripts/verify-package-readme-model-experience.ts,scripts/gen-cordis-catalog.ts— documentation-gate registries with the new group's audited entries and service/event walk exemptions.docs/config-catalog.md,docs/config-catalog.zh.md,packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md— generated catalogs refreshed for the new services.
Everything else lives under packages/avalanche/.
Constraints carried into the fork
- Never replace upstream packages wholesale; extend by composition. Core packages stay unmodified so upstream merges remain tractable.
- License and attribution stay intact: MIT
LICENSEis inherited, package manifests keep the upstream repository URL andMIT, and the fork's README documents the upstream source. - No mainnet transactions, ever, in this build: the policy engine denies
tx:sign,chain:write, andinfra:operatewhen the network environment ismainnet, and the mainnet descriptor iswriteEnabled: false. - Tests run against Fuji or mocks only; no test writes to a real network.
- The repo's application-launch rule is binding: no new
binentry. The operator surface isdsh --profile avalanche ....