Fork Strategy
This fork turns DeepSeek Harness into Avalanche AI by composition, not replacement. This document records the fork mechanics, the extension pattern, the upstream-merge policy, and the decisions that keep the two codebases reconcilable.
Repository topology
| Remote | URL | Purpose |
|---|---|---|
upstream | https://github.com/deepseek-ai/deepseek-harness (local mirror /root/dsh-upstream) | Original project; never pushed to |
origin | /root/avax-origin.git | This fork; all Avalanche work lands here |
The fork is a full clone with history. master tracks upstream release tag dsh-v0.2.2-rc.1 unchanged; all Avalanche work is additive on top. Upstream attribution and the MIT license are preserved: LICENSE, package manifests (repository, license: MIT), and the README attribution line are untouched.
Extension pattern
The fork extends the monorepo in place, with a new package group and one upstream registration:
packages/avalanche/avalanche-*— 19 new packages, all names@deepseek-ai/dsh-avalanche-*matching their directories soscripts/gen-tsconfig-paths.tsgenerates aliases automatically. Version matches upstream:0.2.2-rc.1.packages/util/official-bundles/src/bundles.ts— one entry inPROFILE_TEMPLATES:avalanche: { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-avalanche-app'] },- Generated artifacts:
tsconfig.base.jsonreferences andpnpm-lock.yaml.
Documentation-gate registries also gain the new group's audited entries: scripts/doc-standard.spec.ts (README kind for avalanche-schemas), scripts/verify-subsystem-pages.ts (group exemption), scripts/verify-package-readme-model-experience.ts (Model Experience allowlist), and scripts/gen-cordis-catalog.ts (service/event walk exemptions with their documentation owners). These are test-and-doc contract registries, not runtime behavior. Every other change is new files under packages/avalanche/ plus documentation and generated catalogs.
Why not a plugin outside the monorepo
The package group lives inside the monorepo because:
- The workspace build, TypeScript project references, Vitest alias resolution, and Loader package resolution all key off the workspace globs. An out-of-tree plugin would need a separate build, a separate lockfile, and a profile installation step, and would still not participate in the repo's own gates.
- The domain packages are plugin rows mounted by one bundle patch; they must resolve as workspace packages from the profile fixture and from the built CLI.
- Keeping the packages in-tree makes the upstream merge policy trivial: upstream never touches
packages/avalanche/, and the fork never touches upstream packages beyond the one registration.
Package inventory (19)
| Group | Packages |
|---|---|
| Foundations | avalanche-schemas, avalanche-context, avalanche-memory |
| Security | avalanche-policy, avalanche-evidence, avalanche-verification |
| Runtime | avalanche-network, avalanche-tooling, avalanche-agents, avalanche-orchestrator |
| Domains | avalanche-domain-developer, -security, -devops, -data, -protocol, -testing, -defi |
| Surface | avalanche-cli, avalanche-app |
Seven domains are separate packages because the build emits at most two entries per package (index, startup) and because each domain is independently mountable and independently testable. They share one registry and one runtime.
Build and test integration
scripts/gen-tsconfig-paths.tsscans workspace packages and generates path aliases; no manual alias edits.- Package
tsconfig.jsonreferencesare generated from manifest dependencies. Re-run the generator after any dependency change. - Tests resolve workspace packages from source through
vite-tsconfig-paths; Vitest does not require builtlib/output. - The package group is included in the root workspace glob
packages/*/*, so install, typecheck, and test discovery are automatic.
Upstream merge policy
- Never edit upstream package sources. The only upstream runtime file the fork edits is
bundles.ts; if upstream restructures that file, re-apply the one-entry change. Documentation-gate registries are edited only to register the new group's audited entries. - Never fork a core package to add an Avalanche hook. If a hook is needed, it must be expressible through an existing Cordis event, service, or config knob. If it is not, the gap is documented and upstreamed separately.
- Keep
packages/avalanche/self-contained: it may import upstream packages and their public types only. - Conflicts are resolved by rebasing
masteronto upstream and re-running the avalanche test suite. Because the avalanche group is untouched by upstream, conflicts are expected only in generated files (tsconfig.base.json,pnpm-lock.yaml) and the singlebundles.tsentry. - Version numbers track upstream release lines (
0.2.2-rc.1) so the fork is installable beside upstream packages without peer-range drift.
Decisions and their reasons
| Decision | Reason | Rejected alternative |
|---|---|---|
| Extend in place, not rewrite | Preserves upstream's plugin system, boot, CLI, session, and tool pipeline; smallest merge surface | Standalone app with its own runtime (loses every upstream benefit and duplicates the harness) |
| One bundle, one insert block | The entire runtime is one patch layer; row order stays readable and every row is patchable by a user layer | Seven bundle patches or seven profiles (seven runtimes, explicitly forbidden) |
| Domains as data packages | Routing, prompts, and tool masks are data; adding a domain is a package and one row | Domain classes with bespoke logic (routing becomes model-dependent and untestable) |
| Capability policy, not tool-name policy | A tool can be renamed or reached through a nested dispatch; capabilities describe what an action may do | Tool allowlists in prompts or schemas (bypassable; not enforcement) |
Executor-level tools/pre-execute gate | Every caller passes through it; agent, nested dispatch, and future HTTP surfaces cannot skip it | Filtering tool schemas or prompt instructions (advisory only) |
| Evidence separate from session log | Session text is model output; evidence must record what the runtime actually did | Receipts built from model text (would imply reasoning correctness) |
| Deterministic keyword routing | Model-agnostic, explainable, testable; same objective always routes the same way | LLM-based router (provider-dependent, untestable, and non-deterministic) |
| JSONL hash chain, not a database | Tamper-evident, dependency-free, readable by an operator; sufficient for the phase | PostgreSQL event store (Phase 3 candidate; unnecessary for the slice) |
CLI under dsh --profile avalanche | Upstream rule: only dsh profiles launch apps; package bins are forbidden | A new avalanche-ai bin (violates the application-entrypoint gate) |
| Commander parse → service → runner | Upstream startup/runner pattern; avoids a service-read race while rows activate asynchronously | Executing command bodies in the parse action (raced tree activation in the Loader test) |
Deliberate deviations from the brief
- No
avalanche-aiexecutable. The brief names a CLIavalanche-ai .... The upstream application-launch rule forbids a separate Node application path, so the CLI isdsh --profile avalanche <command>. The command surface is the release surface; a wrapper script may be added later outside the repository if a literal binary name is required. - Dashboard deferred. The Vite + React + TypeScript dashboard (no Next.js) is Phase 4. Phase 1 ships the runtime, the CLI, and the API-shaped services the dashboard will consume.
- No Rust package yet. Security-sensitive execution is TypeScript in this phase; Rust is planned for the signing/verification boundary in Phase 5, where the risk justifies it. The package boundary is already drawn so a Rust executor can replace the TypeScript one behind the same service contract.
- No PostgreSQL, Redis, or vector search. The phase's persistence is file-based; memory recall is lexical. Both are deliberate simplifications with documented upgrade paths.
What to verify after an upstream merge
pnpm install --no-frozen-lockfile
pnpm run gen-tsconfig-paths
pnpm exec vitest run packages/avalanche
Expected: all avalanche specs pass, including avalanche-app/tests/loader-composition.spec.ts, which boots the real shipped cordis.patch.yml through the real Loader and asserts every row mounts.