sunstrikes/dsh-reprise--packages-memory ↗★ 1
@dsh-reprise/memory
The injection packet: what a model turn actually sees, projected from facts through dsh-mnemon
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add 'github:sunstrikes/dsh-reprise#0c8b849c640d12c325e71227862403e8317fe5d2&path:packages/memory'说明文档
阅读完整 README ↗description: "A model-structure exploration loop as a DeepSeek Harness bundle: deterministic phases, a frozen baseline, a spend ledger, and a stop decision that only reads recorded facts." kind: "package-bundle"
English | 简体中文
dsh-reprise
An exploration loop whose conclusions you can check.
A DeepSeek Harness bundle: reproduce a published number, then iterate on the reproduction under control — deterministic phases, a baseline the run measures itself and then freezes, a spend ledger that reserves before it spends, and a stop decision computed from recorded facts rather than remembered intent.
Summary
dsh-reprise reproduces a published experiment and then iterates on that reproduction under control:
deterministic phases, a baseline the run measures with the same executor path a candidate takes and
then freezes, a budget that reserves before it spends, and a stop decision computed from recorded facts
instead of remembered intent.
A candidate is a structural hypothesis plus the batch of code changes it needs to land — not a hyperparameter value. The loop is ordinary code; the model is consulted in exactly five places, and what it says enters the fact plane only as an artifact a validator has accepted.
Mounting the bundle adds three surfaces and nothing else: a tick that advances the phase machine in a
workspace nobody has open, a reprise_status tool that reads the same projection from inside a
conversation, and a Reprise panel in the sidebar (backed by the read-only GET routes below). It does
not touch your repository, does not open a port, and does not call a model unless you name one in
model.command.
Do not reach for it if what you want is prompt search, a hyperparameter sweep, or a dashboard you can drive: v1 has no write endpoint at all, the browser half is read-only by construction, and the loop optimises one declared metric per round rather than a population of prompts.
Table of Contents
- When to use it
- Install and mount
- Configure it
- What it adds
- The five-minute path
- Checks and local development
- Design
- Source map
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
Use this package
When to use it
You have a metric that can be measured twice, a baseline you are willing to freeze, and a belief that the structure of a model — not its learning rate — is what is wrong with it. You want the answer to "why did this candidate stop?" to come from a file rather than from whoever was watching the terminal.
dsh-reprise fits when the run must survive being unattended: a crash mid-phase is a detectable lag
in the fact plane, a paused budget is a recorded decision, and every number on the page is derivable
from facts alone (dump --rebuild proves it). It is a poor fit when the metric is a human judgement,
when the baseline cannot be made reproducible, or when you need several concurrent runs against one
workspace — one workspace belongs to one run.
Install and mount
Install the release tarball. It carries its sibling packages inside itself — vendored under
vendor/packages/, with every @dsh-reprise/* specifier rewritten to a relative path — so an install
needs no checkout, no workspace to resolve and no build step:
dsh plugin --profile web add ./dsh-reprise-.tgz # the asset on the newest release
node scripts/build-release.mjs writes that file into dist/ from a checkout, and installing it by
path works the same way. A git install is not the shortcut it looks like:
github:sunstrikes/dsh-reprise#path:/packages/core fails three ways at once — the packages depend on
each other through workspace:*, which only the monorepo resolves; pnpm refuses any git dependency
below the root (ERR_PNPM_EXOTIC_SUBDEP); and packages/ui imports packages/core by relative path,
which no package manager's layout preserves. The tarball is the artifact a stranger can install.
To develop against it, clone and link — the profile links that directory instead of copying it, so edits are live and the sibling packages are the ones beside it:
git clone https://github.com/sunstrikes/dsh-reprise.git
cd dsh-reprise
node scripts/link-workspace.mjs # Node 22.5+; makes packages/* importable, idempotent
Use the host's own tooling rather than hand-editing the profile — install_bundle performs the
dependency install, the link and the bundle selection in one step, with target set to the absolute
path of /packages/core:
plugin_manager { action: "install_bundle", target: "/absolute/path/to/dsh-reprise/packages/core" }
Then supply your own configuration. The shipped patch has no config block at all: a run cannot
start without a workspace and a domain pack, and every path a real config needs belongs to the
machine that owns the run. Add an entry that targets reprise-core by id and restate the whole config — a matching override
replaces config wholesale rather than deep-merging it. Put it in your profile's
cordis.patch.yml (or $DSH_HOME/cordis.patch.yml, which outranks the profile layer);
packages/core/cordis.example.yml is the filled-in version of the
shape below, including the two path rules that cost a debugging round each.
- id: reprise-core
name: dsh-reprise
config:
workspace: /absolute/path/to/my-run # required; created facts live here
domain: /absolute/path/to/my-domain-pack # required; the metric contract, priors, surface, prompts, executor
interval_ms: 5000
candidate:
kind: command
command: ['node', '/absolute/path/to/apply-change.mjs', '{candidate}']
runner: { repo: model-repo, dir: worktrees }
model: { command: ['node', '/absolute/path/to/my-model-port.mjs'] }
rounds: { metric: { name: auc, direction: higher }, max_rounds: 3 }
metrics: { primary: auc, objectives: { auc: up } }
budget: { candidate_gpu_hours: 0.5, gpu_hours_limit: 0 }
An operator who starts one run per task should not have to edit that patch at all. Both of these work
without it, and they compose — the precedence is inline config → config_file →
environment:
- One file per run. Put
config_file: /path/to/run.jsonin the patch'sconfig(or pointDSH_REPRISE_CONFIGat it, for a launcher that writes no YAML). The file is read at mount, once: it is the base configuration and any inlineconfigkey wins over it — a shallow merge, key by key.workspacewritten inside that file resolves against the file's own directory, so a file can sit next to the run it describes; every other path keeps the host-cwd rule below. A missing, unreadable or non-JSON file is a named refusal on the boot log (path and parse error), never a silent fallback to defaults. - One environment variable.
DSH_REPRISE_WORKSPACEnames the workspace when the configuration does not; a relative value resolves against the host cwd, an absolute one is used as is. An inlineworkspacestill outranks it.
Either way, the boot log's run: line answers what is this host running? in one line: the resolved
workspace (and where it came from), the repository candidates branch from, where their worktrees are
created, whether they are kept, and which file the configuration was read from.
Restart the host (bundle lists are read at startup; with HMR a patch edit alone applies live). You are
mounted when the log carries the boot half's applied line, then the tick half's tick-applied, the
sidebar shows Reprise, and reprise_status is available to the model.
Three absences degrade instead of failing, by design: no timer service means the ticking half is
deferred, not failed (cordis reports the two identically, which is why the package ships two entry
points); no connection service means no dashboard; a workspace that cannot be opened or a config that
cannot work is one log line and a host that keeps booting. None of these three appear as an error.
A refused run configuration is not one of those absences, and it no longer takes the page with it.
The dashboard mounts on the workspace the configuration still names and says why the loop is silent:
the reason rides the overview's notes, which the strip on both surfaces paints in every state, the
empty page included. So the page shows the facts already recorded and never pretends the loop is
running.
A configuration that cannot name a workspace at all is the one case that still leaves nothing to mount.
The @dsh-reprise/memory bundle is optional and separate (row reprise-memory); it adds the injection
packet as a dsh-mnemon Source. Nothing in packages/core imports it, and it has no dependencies —
so that one installs straight from the repository subdirectory, no tarball involved:
dsh plugin --profile web add github:sunstrikes/dsh-reprise#path:/packages/memory.
Configure it
Everything domain-specific is data, and it lives in a domain pack — a directory whose
domain.json names its metric contract, its priors, its adjustable-surface whitelist, its five
prompts and its executor entry (examples/feed-structure is a complete
one). The kernel does not know a metric name, a model repository or a prompt; it reads them from the
pack, and a pack missing a part is refused by name at boot instead of running half-configured.
| Field | Default | Meaning |
|---|---|---|
workspace | required, or DSH_REPRISE_WORKSPACE | directory the run owns; the fact plane is workspace.db inside it. Relative resolves against the host cwd; an absolute path is used as is |
config_file | — (or DSH_REPRISE_CONFIG) | one JSON run configuration file, read once at mount: the base configuration, with every inline key winning over it (a shallow, key-by-key merge). workspace inside the file resolves against the file's directory. A missing/malformed file is a named boot refusal |
domain | required | directory of the domain pack; no pack, no metric contract, no prompts |
interval_ms | 5000 | tick cadence |
executor.kind | the pack's | local (a subprocess) or ray (Job Submission REST) |
executor.command | the pack's, required for local | argv of one job, resolved inside the candidate worktree |
executor.base_url | required for ray | Ray dashboard URL |
executor.root_dir / metrics_required | 'jobs' / true | where job dirs live; a job without metrics is a failure |
candidate.kind | 'command' | command, claude or dsh — the LAND implementer |
candidate.command | required for command | argv; {candidate} and {worktree} are substituted |
runner.repo / runner.dir | required with candidate | repo inside the workspace; where worktrees are created (/worktrees/) |
runner.keep | true | retention. With true nothing in the kernel removes a candidate worktree; false removes nothing by itself either — it only allows the explicit worktrees prune |
runner.base_ref | git default | the commit candidates branch from, until the run rolls its code base (see rounds.anchor) |
model.kind | 'command' | how a judgement reaches a model: dsh (dsh --profile headless --json -), claude (claude -p --output-format stream-json --verbose), or command for a wrapper of your own |
model.command | required for kind: command | the wrapper's argv: one JSON question on stdin, one JSON answer (a JSON object) on stdout |
model.timeout_ms | 600000 | how long one judgement may take before it is killed — a phase never waits for a model without a deadline |
rounds.metric.name / .direction | derived from the metric contract | the one metric the round gate optimises; restating it is how a run points at a different objective, and a restatement that disagrees with metrics is warned about |
rounds.metric.contract_ref | the contract's name | pointer to the frozen metric contract |
rounds.max_rounds / .candidates_per_round | 3 / 2 | round workflow shape |
rounds.anchor | 'automatic' | who may roll the code base forward: automatic applies the double-domination rule between rounds (a candidate must dominate both the current anchor and the frozen baseline); human files a proposal under anchor-pending/ and waits for dsh-reprise intents anchor --candidate-id … --commit …. Neither mode moves the differential denominator |
rounds.retry.base_ms / .max_ms / .max_attempts | 60000 / 3600000 / 3 | backoff for a refused artifact, then a human |
stop.max_rounds | 3 | 0 disables the rule |
stop.max_no_improvement_rounds / .consecutive_empty_rounds | 0 (off) | opt-in stop rules |
stop.max_wall_hours / .max_windows | 0 (off) | workspace-level ceilings: the run ends when it has been open this long, or has measured this many windows — a conclusion on _workspace, unlike a candidate rejection |
stop.regression_windows / .regression_floor | 0 (off) / 0 | kill a candidate worse than the baseline in each of the trailing N windows; the floor may raise a tolerance, never lower one |
budget.gpu_hours_limit | 0 (unlimited) | hard cap; an uncapped remote executor is warned about, every read |
budget.soft_ratio / .candidate_gpu_hours | 0.8 / 0.5 | stop dispatching at this fraction; what a dispatch reserves |
budget.max_runtime_hours / .judge_tokens | 0 (unlimited) / false | wall-clock gate; tokens are recorded, never judged |
metrics | the pack's whole contract | one account of "better" — primary, objectives, guardrail, minimum_effective_delta, window_mode — resolved by packages/core/lib/metric-contract.js for the ranking, the stop rules and the gate; a partial or self-contradictory restatement is refused at boot |
poll.base_ms / .max_ms / .grace_attempts | 1000 / 60000 / 2 | job poll backoff |
gate.max_attempts / .base_ms / .max_ms | 3 / 1000 / 60000 | how many times gate two may run before a candidate stops, and the backoff between attempts; 1 restores a single shot |
limits.max_wall_ms / .max_idle_ms | 3600000 / 1800000 | per-job wall-clock and silence deadlines |
One of model.kind and model.command must be there when rounds is set; the five call sites are the
only places a model is consulted, and they all go through this one adapter —
packages/exec/lib/model.js. It sends one structured question
(`{protocol