AVALANCHEAI
Phase 1 liveUpstream
DocsForkUpstream audit10 min read

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

FactValue
Repositoryhttps://github.com/deepseek-ai/deepseek-harness
LicenseMIT, Copyright (c) 2026 DeepSeek
Package name / version@deepseek-ai/dsh-root 0.2.2-rc.1
RuntimeNode.js >=24.9.0
Package managerpnpm@11.28.5 (Corepack)
Workspace globsvendor/*, packages/*/*, native/system, native/system/packages/*, apps/*, benchmarks, website, python/sdk-runtime
Package count389 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 postureCoverage 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), creates Agent handles 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-only SessionEvent log; events are read via ctx.on('session/event', ...), never through deprecated synchronous reads.
  • packages/core/tools — ToolRuntime (ctx.tools), tool registration, scoped restriction, and the tools/pre-execute / tools/result pipeline 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 and LlmRuntime (ctx.llm); adapters are pluggable per provider, and AgentDefaultModelConfig selects 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 — cmdlineArgs and appExit services; parseCmdline implements 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 on ctx.tools under mcp__<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:

  1. A package may expose exactly two built entries. Multi-plugin packages split into src/index.ts + src/startup.ts (the pattern @deepseek-ai/dsh-headless uses).
  2. 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 calls super(ctx, '<serviceName>').
  • Function plugins named-export name, inject, optional Config, and apply, with no default export. Mixing forms makes the Loader discard the namespace (documented postmortem docs/postmortem/0001-acp-default-export-drops-inject.md).
  • A plugin with unmet static inject stays pending; its service is undefined after await ctx.plugin(...). Dependencies must mount first, or the consumer must use ctx.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 an AgentSetup.
  • tools/pre-execute is 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/result carries the completed ToolExecution (including exec.agent) and the result; it is the correct place to record what actually ran.

Existing surfaces relevant to Avalanche AI

NeedUpstream mechanismFork decision
New product surfaceA bundle package plus a PROFILE_TEMPLATES entryAdd avalanche template over dsh-base + dsh-avalanche-app
One shared runtimeCordis context and servicesOne bundle patch mounts every service once; domains are data
Tool registryctx.tools + defineToolAll Avalanche tools register through one catalog package
Policy enforcementtools/pre-execute eventAvalanche policy gate listens there; fails closed
Durable logsSession log, JSONL persistenceEvidence ledger separate from session text; hash-chained JSONL
Model selectionAgentDefaultModelConfig currentSelection()Orchestrator reads selection and passes it to agents.create
CLIdsh --profile ... + cmdlineArgs/appExitavalanche-cli parser (/startup) + runner (index)
External docs/dataMCP client bridgeOptional 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 — one PROFILE_TEMPLATES.avalanche entry.
  • 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 LICENSE is inherited, package manifests keep the upstream repository URL and MIT, and the fork's README documents the upstream source.
  • No mainnet transactions, ever, in this build: the policy engine denies tx:sign, chain:write, and infra:operate when the network environment is mainnet, and the mainnet descriptor is writeEnabled: 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 bin entry. The operator surface is dsh --profile avalanche ....