Reins — durable Objectives for DeepSeek Harness
English · 简体中文
Harness executes. Reins holds the reins.
Tell Reins what you want in one sentence. It plans it with you, keeps it moving across Tasks, sessions,
approvals, CI, failures and restarts, and calls it done only when the check it runs itself passes.
Install ·
Quick start ·
Commands ·
Configuration ·
Architecture ·
Security model ·
Tests ·
Development
Why Reins
An Agent conversation is good at "do this now". Real engineering goals take longer: change the code, run the tests, wait for someone to approve a push, wait for CI, fix what CI found, wait for review… and somewhere in between the process crashes or the machine restarts.
Keeping an Agent loop spinning while it waits wastes resources, and it makes it easy to mistake "the model said it is done" for "it is done". Reins moves all of that into a durable orchestration layer:
- Describe the goal in plain words; your conversation's model plans it with you and asks when there is a real choice to make.
- Waiting is durable state, not an idle Agent loop; when the event arrives, a new Attempt continues.
- A sensitive call is denied when no one approves it; an approval authorizes exactly that one call.
- A Task is complete only when it passes the acceptance gate. A finished Goal or a successful Attempt is not enough, and tests are run by Reins itself, never taken from the Agent's word.
- Everything lives in SQLite. After a crash, Reins reconciles conservatively: nothing uncertain is ever treated as success.
How it works
From one sentence to an accepted result
/reins add a delete command to my notes tool
- The model of the conversation you typed this in looks at the project first.
- When the request leaves a real choice open, it asks you (DSH's question card with options). Things it can find out itself, it does not ask.
- If no existing test covers the change, it writes the check first and confirms that the check fails today.
- It calls
reins_create_objective with the Tasks, their order and a check command per Task. Reins pins the check files by hash, so the Agent doing a Task cannot rewrite its own exam.
- Reins' own Agents do the work in the background, in their own sessions. When one needs approval,
/reins-todo lists it, and /reins-approve <id> lets the Task continue where it stopped.
- Reins runs the check itself. Exit code 0 is the only way to "done".
This was verified with a real DeepSeek model in DSH Web. Asked for "a delete function for the notes tool", the model asked whether to delete by index or by text, wrote check-delete.mjs and saw it fail, and created the Objective. A Reins Agent implemented delete-by-index, and Reins ran the pinned check: exit 0, accepted.
Features
| Capability | What it means |
|---|
| 💬 | Plain-words planning | /reins <what you want>: the conversation's model plans with you, asks about real choices, writes the check first, then creates the Objective |
| 🧭 | Task DAG | An Objective holds several Tasks with dependencies; a downstream Task becomes ready only when its upstream Tasks are accepted |
| 🔁 | Attempt boundary | Every run is an immutable Attempt; a finished Attempt never comes back to life, every resume is a new Attempt |
| ✋ | Durable approvals | Sensitive tools are denied and parked when no one approves; an approval becomes a one-shot grant bound to Task / Attempt / session / Goal / tool / arguments / policy version / expiry and is consumed atomically, once |
| ⏳ | Durable waits and events | awaiting_approval / awaiting_ci / awaiting_timer / awaiting_external_event; immutable events, idempotent consumption, duplicate suppression |
| 📡 | Polling watchers | GitHub Checks, PR reviews, local/remote processes; backoff, timeouts, rate limits, offline handling, stale results refused; credentials never stored or logged |
| 🧩 | Safe replanning | Graph revisions; the DAG changes only at safe points, and an Attempt from an old revision cannot be accepted (enforced in the database) |
| 🌿 | Parallel worktrees | Parallel Tasks each get their own git worktree; orphan reconciliation, branch conflict avoidance, merging, acceptance bound to artifact hashes |
| 🤖 | Subagents / session fork | Route a Task to a normal session, a read-only subagent or a session fork; seeing a tool is not permission to use it |
| 🧪 | Acceptance gate | Frozen criteria and deterministic validators (file checks, durable reins.* facts, and reins.command_succeeds, where Reins runs the test command itself); only a pass receipt completes a Task |
| 📉 | No-progress detection | Deterministic failure fingerprints; repeated identical failures, a stalled acceptance, repeated denials or tool errors put the Task on hold for you |
Install
Requirements
- Node.js ≥ 22.13 (for the built-in
node:sqlite)
- DeepSeek Harness
@deepseek-ai/dsh@0.2.0-rc.1 (Reins pins its peer dependencies to this version)
npm install -g @deepseek-ai/dsh@0.2.0-rc.1
Below, <DSH_HOME> is the Harness data directory (the DSH_HOME environment variable, ~/.dsh when unset) and web is the profile to install into.
1. Get a fixed copy
Install from a fixed snapshot directory, so that later changes to a working copy do not change what is installed:
git clone --branch v0.4.0 https://github.com/dreamfarer-space/dsh-reins.git reins-0.4.0
2. Install the plugin
Reins is a DSH plugin bundle. Either:
-
In DSH Web: sidebar Plugins → Add plugin, enter the absolute path of the snapshot directory (for example D:/plugins/reins-0.4.0), then Enable now.
-
From the command line:
dsh plugin --profile web add file:/absolute/path/to/reins-0.4.0
Reins then appears under Plugins → Installed with its icon and description; the switch on the right starts and stops it. You do not need to edit cordis.patch.yml.
3. Settings on the Plugins page
Open Reins on the Plugins page. Settings are edited right there and apply as soon as you save, without a restart:
| Setting | What it does |
|---|
| Approval policy | Strict: every tool needs approval. Recommended (default): reading and editing files is trusted; commands, network, subagents and unknown tools need approval. Custom: the recommended set plus tools you tick (pwsh, bash, web_fetch, …), and a list of tools that always need approval. Trust all: everything except the always-ask list (only for directories you can roll back) |
| Continue automatically | Continue after events, retries or a finished Task without waiting for /reins-tick (the Objective must allow it too: ask for it in /reins, or /reins-goal --auto) |
| Approval wait in the page | How long an approval may wait in the conversation before the Agent is released and it moves to /reins-todo |
| Default workspace | Where Objectives without their own workspace work |
| Desktop notification / reminder in conversation | How Reins tells you that it needs you or that something finished |
These settings are stored in the profile's cordis.patch.yml (config: under - id: reins) and can be edited by hand as well; the rest of the options are under Configuration.
4. Check
Type /reins in a conversation: the usage text lists all commands. Typing /reins also shows them in DSH's command menu. Reins only governs the Attempts it starts; your own conversations with an Agent are not affected.
Update, roll back, uninstall
- Update: prepare a new snapshot directory and run
dsh plugin --profile web add file:<new directory> again, or add it again on the Plugins page.
- Roll back: before installing, back up
package.json, pnpm-lock.yaml and cordis.patch.yml under <DSH_HOME>/profiles/web; to roll back, copy them back and run dsh plugin --profile web install.
- Stop / uninstall: switch it off on the Plugins page to stop Reins (database and settings are kept); Uninstall removes the bundle.
- From before 0.3.0: older versions needed a hand-written
- insert: - id: reins ... in cordis.patch.yml. The bundle now declares that entry itself; change the old insert block into a settings override (- id: reins with config:), or reins is loaded twice.
- From 0.3.x: every
/reins <subcommand> is now its own command (/reins todo → /reins-todo, /reins dag <id> → /reins-status <id>, …). The old forms still work and print the new name; /reins followed by anything else is now a plain-words request.
Quick start
Say what you want
/reins the login endpoint crashes with a null pointer, fix it
/reins add CSV export to the report page, keep the current JSON export
The conversation's model plans it with you as described in How it works. It works in any conversation, including as the first message of a new one. If the project is not the conversation's directory, name the directory in the request.
One sentence, your own check
When you already know the command that proves the work is done, skip the planning:
/reins-goal fix the clamp edge cases
/reins-goal make lint pass --test "npm run lint" --workspace D:/work/app
/reins-goal fix clamp --dry-run # preview only
/reins-goal fix clamp --auto # continue on its own after events / retries (also needs unattendedEnabled)
Without --test, Reins detects the test command (package.json test script, Node test files, Cargo, Go, pytest) and tells you to pass --test if it finds none.
Then two commands are enough
/reins-todo # everything waiting for you: approvals, held Tasks (with the last error), Tasks waiting for a tick
/reins-approve 3f9c1a2b # approve with the short id shown by /reins-todo
After an approval, the Task continues in its previous session (the Agent remembers where it was). You do not need /reins-tick. Reading and editing files is trusted by default; running commands (pwsh / bash), network access and subagents still need approval (see trustPreset under Configuration).
In DSH Web
- Reins sessions are in the sidebar: the Agent conversation of each Task appears under "未分组" (Ungrouped), named after the Task. Open it to see what the Agent did.
- Approve either way: a session that needs approval is marked "待审批" (awaiting approval), and you can click "允许一次 / 拒绝" (allow once / deny) there. If you do not, after
interactiveApprovalTimeoutMs (5 minutes by default) the Agent is released and the request moves to /reins-todo with a short id. DSH's own approvals (for example pwsh asking to widen the sandbox) follow the same rule.
- Reins tells you: when something needs you (an approval, a held Task, a finished or failed Objective), Windows shows a notification, and the next time you talk to any DSH conversation the Agent mentions it briefly, with the command to run.
- After DSH was force-stopped: if nothing was running, Reins takes over on restart by itself. If an Objective had an Attempt running, Reins cannot prove it stopped, so it holds only that Objective (no takeover, no changes) and runs everything else.
/reins-todo, the reminder and the notification all point to it. Once you are sure the old DSH is gone, run /reins-recover <id> --confirm: the unfinished Attempt is recorded as interrupted, side effects with an unknown result wait for you, and the Objective continues.
Several Tasks by hand: get a PR mergeable
For full control over Tasks, dependencies and criteria, write the Task graph yourself with /reins-objective: implement, test, then wait for CI.
/reins-objective {"id":"pr-7","summary":"Get PR #7 mergeable","workspace":"D:/work/app","tasks":[
{"id":"fix","title":"Implement the fix","criteria":[{"kind":"file_contains","spec":{"path":"src/fix.js","substring":"FIXED"}}]},
{"id":"tests","title":"Run the tests","dependsOn":["fix"],"criteria":[{"kind":"reins.command_succeeds","spec":{"argv":["node","--test"],"timeoutMs":300000}}]},
{"id":"ci","title":"Wait for CI","dependsOn":["tests"],"criteria":[{"kind":"reins.wait_satisfied","spec":{"waitKind":"ci","eventType":"ci.passed"}}]}
]}
What happens next:
- Reins starts an Attempt for
fix. The Agent gets a structured handoff (goal, current Task, criteria, last failure, budget, …) and calls task_complete when done.
- The acceptance gate checks the criteria; only a pass completes
fix, which makes tests ready. For tests, Reins runs node --test itself and looks only at the exit code; no test report written by the Agent counts.
- In
ci, the Agent pushes and calls reins_wait (kind: "ci"). The Attempt ends, the Agent stops, and a watcher starts polling.
- Pushing is sensitive: with no one there it is denied and parked.
/reins-todo shows the request, /reins-approve <short id> approves it, and the Task continues in its previous session.
- If CI fails, a new Attempt continues with a compact failure context (failed checks, failure fingerprint); once CI passes, the Task is accepted.
See everything at any time:
/reins-status pr-7
[!IMPORTANT]
Unattended runs are off by default. Attempts started by an event, a timer, an automatic retry or "the previous Task was accepted" are unattended; they do not start on their own, /reins-todo lists them and /reins-tick <objectiveId> runs them. Approvals you gave are not in that group: an approval authorizes the call and the Task continues by itself. To let Reins continue on its own all the way, set both the configuration unattendedEnabled: true and the Objective field "unattendedOptIn": true (--auto for /reins-goal; say so in a /reins request).
Commands
Commands
Every action is its own top-level command, so DSH's command menu lists and completes it. approve, deny and resolve accept the short id shown by /reins-todo (a unique prefix of at least 4 characters; an ambiguous prefix is refused).
| Command | What it does |
|---|
/reins <what you want> | Plain-words request: this conversation's model plans it with you and creates the Objective. Without text: usage |
/reins-goal <sentence> [--test "<cmd>"] [--workspace <dir>] [--id <id>] [--auto] [--dry-run] | One-Task Objective that is done when the test command exits 0 |
/reins-todo | Everything waiting for you: approvals (with short ids), approvals waiting in a DSH conversation, side effects with an unknown result, held Tasks and their last error, Tasks waiting for a tick, Objectives held after a forced stop |
/reins-status [objectiveId] | Without an id: all Objectives, unfinished first. With an id: Objective, Task DAG, current Attempts, acceptance, waits, approvals, watchers, budget, lease, recent events |
/reins-approve <id> [reason] | Approve a durable approval request |
/reins-deny <id> [reason] | Deny it; the Agent is told why |
/reins-pause <objectiveId> | Pause; running Attempts stop, state is kept |
/reins-resume <objectiveId> | Resume a paused Objective; also lifts held Tasks inside an active one (no progress, approval loop, wait timeout) and restarts no-progress counting |
/reins-cancel <objectiveId> | Cancel; unused grants are revoked first |
/reins-tick [objectiveId] | Run scheduling now (counts as attended) |
/reins-recover [objectiveId] [--confirm] | After a forced stop of DSH: list Objectives held by the old process; --confirm releases them once the old process is gone (also works when Reins could not start) |
/reins-objective <json> | Create an Objective from a hand-written Task graph (fields below) |
/reins-replan <objectiveId> <json> | Replan at a safe point: {"add":[…],"remove":[…],"modify":[…],"reopen":[…],"reason":"…"} |
/reins-event <json> | Deliver an external durable event |
/reins-resolve <id> done|not_done | Say whether a side effect with an unknown result happened |
/reins-merge <objectiveId> <taskId> |
Objective and Task fields
| Field | Meaning |
|---|
id | Objective id (optional, random by default) |
summary | What the Objective is about |
workspace | The Objective's directory (absolute, must exist). Agents work there, and file criteria and acceptance commands run there. Default: evidenceRoot / repoRoot |
tasks[] | Tasks, at least one |
tasks[].id / title / description | Task id, title and instructions |
tasks[].dependsOn | Ids of Tasks that must be accepted first (no cycles) |
tasks[].required | Whether the Objective needs this Task, default true |
tasks[].criteria[] | Acceptance criteria: {"kind","spec","required"} |
tasks[].capability | Routing, see below |
tasks[].maxAttempts / maxWallTime | Task budget |
maxAttempts / maxWallTime | Objective budget; maxWallTime defaults to 7 days and includes waiting |
maxActiveMs | Limit on actual running time, excluding waiting |
maxParallel | Parallel Attempts of one Objective (above 1 needs worktree isolation) |
noProgressLimit | No-progress threshold (default: noProgressAttemptLimit) |
unattendedOptIn | Whether it may continue unattended |
Routing: capability
| Field | Meaning |
|---|
route | session (default, normal Agent + Goal), subagent (in-process child Agent), fork (forked from the previous Attempt's session) |
readOnly | Default true for subagent / fork: sensitive tools are always denied |
isolation | none or worktree; a subagent that writes code must use worktree |
provider | Subagent provider (default spawn, fork for forks) |
model / modelProvider | Model and model provider |
tools.allow / tools.deny | Limit visible tools. Visible is not allowed: sensitive calls still need approval |
maxGoalRounds | Goal rounds per Attempt, default 3 |
Acceptance criteria
| kind | spec | Passes when |
|---|
file_exists / file_absent | {"path"} | the file exists / does not exist |
file_contains | {"path","substring"} | the file contains the text |
file_sha256_equals | {"path","sha256"} | the file has this hash |
json_field_equals | {"path","pointer","expected"} | the JSON field equals the value; pointer is a dotted path (a.b) |
marker_count_equals | {"path","marker","count"} | the marker occurs that many times |
reins.wait_satisfied | {"waitKind","eventType"} | the Task's last wait was satisfied by a durable event of that type, and the current Attempt is newer than the event |
reins.output_contains | {"substring"} | the subagent's output contains the text |
reins.no_ambiguous_side_effects | {} | the Task has no side effect with an unknown result |
reins.command_succeeds | {"argv","cwd","timeoutMs","expectExitCode","pins","env"} | Reins runs the command itself at acceptance and the exit code equals expectExitCode (default 0) |
File paths are relative to the Attempt's directory (the Objective's workspace, evidenceRoot or the worktree); absolute paths and .. are refused.
[!WARNING]
File criteria check files in the working directory, and the Agent can write those files too. In a real-model test, after a failing test run the model rewrote test-report.json to {"passed":true}, and json_field_equals passed. For "tests pass" or "the build succeeds", use reins.command_succeeds, never a report file.
Rules of reins.command_succeeds:
Agent tools
| Tool | Used by | What it does |
|---|
reins_create_objective | the conversation where you typed /reins | Creates the planned Objective. Works once per /reins in that conversation, never in Reins' own sessions; validates Tasks, order, check commands, and pins checkFiles. dryRun shows the plan without creating it |
task_complete | Reins Agents | Claims the Task is done; Reins pre-checks, the acceptance gate decides |
reins_wait | Reins Agents | Ends the current Attempt and waits durably; a new Attempt continues when the event arrives |
reins_wait kinds:
| kind | Arguments | Resumes on |
|---|
ci | repo (owner/name), sha, optional requiredChecks | ci.passed / ci.failed |
pr_review | repo, number | review.approved / review.changes_requested / review.commented / pr.merged / pr.closed |
process | exitFile or pid, optional name | process.succeeded / process.failed / process.lost |
remote | url, optional name (host must be in remoteWatcherHosts) | remote.succeeded / remote.failed / remote.cancelled |
timer | delayMs | timer.fired |
external_event | subject, eventTypes | events delivered with /reins-event |
Every wait takes a timeoutMs (default 24 hours); after it, the Task is held for you instead of being retried blindly.
External event example (subject carries the ext: prefix and matches the subject of reins_wait):
/reins-event {"source":"deployer","sourceEventId":"deploy-42","type":"deploy.done","subject":"ext:deploy-42","objective":"pr-7","payload":{"env":"staging"}}
source + sourceEventId is the event's identity: a repeated delivery is suppressed, and the same identity with different content is flagged as a conflict instead of overwriting the original.
Single-Task commands (Phase 2/3, for scripts)
These are reachable as /reins <subcommand> only: create <summary> | <task title>, criteria-add <objectiveId> <taskId> <kind> <json-spec>, criteria-list <objectiveId> <taskId> [revision], criteria-revise <objectiveId> <taskId>, accept <objectiveId> <taskId> [attemptId], receipts <objectiveId> <taskId>. /reins-status, -pause, -resume and -cancel work for these Objectives too. For new work use /reins or /reins-goal.
Configuration
Configuration is validated strictly: unknown keys, invalid numbers, cloud URLs and UNC network paths are refused.
| Key | Default | Meaning |
|---|
databasePath | ~/.dsh/dsh-reins/reins.sqlite | Local SQLite path (the directory is created if missing). One live Reins process per database |
evidenceRoot | null | Default directory for Objectives without a workspace; with neither this nor repoRoot, orchestrated Objectives must bring their own workspace |
approvalRequiredTools | [] | Tools that always need approval |
trustPreset | workspace | Built-in trusted set. workspace: read, glob, grep, write, edit, str_replace_editor, todo_write, get_goal, update_goal and Reins' task_complete, reins_wait; commands, network, subagents and unknown tools still need approval. none: only trustedTools |
trustedTools | [] | Tools trusted in addition to the preset (for example ["pwsh"]) |
trustUnlistedTools | false | Whether unlisted tools are trusted. Not by default: an unknown side effect needs approval |
unattendedEnabled | false | Allow unattended runs (the Objective must also have unattendedOptIn) |
defaultMaxAttempts | 5 | Default maximum Attempts per Task |
defaultMaxWallTimeMs | 300000 | Default wall-clock budget of a single-Task Objective |
orchestratedDefaultWallTimeMs | 7 days | Default wall-clock budget of an orchestrated Objective (including waits) |
noProgressAttemptLimit | 3 | No-progress threshold |
maxConcurrentAttempts |
Budgets: maxWallTime is wall-clock time, including waits for CI and approvals; maxActiveMs counts only the time Attempts actually run; maxGoalRounds bounds each Attempt. There is no reliable token telemetry, so there is no token or cost budget.
Architecture
Reins architecture
Reins is a Cordis plugin that uses only the public DeepSeek Harness API and never changes the Harness core:
- Commands and planning (
src/commands/): the /reins and /reins-* commands; /reins <request> hands the request to the conversation's model with agent.followup, and reins_create_objective turns its plan into an Objective.
- Orchestrator (
src/orchestrator/): schedules ready Tasks, keeps the DAG and graph revisions, durable waits and events, watchers, approvals, worktrees, routing, failure fingerprints and handoffs.
- Runtime (
src/orchestration/runtime.js): atomic admission, lease fencing, outer settle (disarm → cancel → idle → flush → final receipt), tool and step guards. Inside an Attempt, only the Harness goal-round driver continues the work.
- Acceptance gate (
src/acceptance/): the only path to completed; database triggers make completion without a pass receipt impossible.
- Storage (
src/store/ + migrations/): SQLite (node:sqlite), migrations 001–015, append-only audit, key invariants enforced by triggers.
How a resume happens
An approval, from park to acceptance
CI, timers, PR reviews and external events all take the same path: wait → durable event → idempotent consume → new Attempt.
Security model
Every capability of Reins obeys these invariants; the tests and the real-Harness verification are built around them:
- Harness executes, Reins orchestrates. Public API only, no private imports, no changes to the Harness core.
- Only the goal-round driver continues work inside an Attempt; one Objective has exactly one valid writer (lease + epoch fencing).
- A finished Attempt never comes back to life; a parked Attempt always resumes as a new Attempt.
- A sensitive call without approval is denied, and the denial must still be followed by park / cancel and a proof of quiescence.
- Approval is not acceptance, and a finished Goal is not a finished Task. Approval cannot bypass the sandbox, guards, leases, budgets or the acceptance gate.
- A wait keeps no Agent loop.
- A side effect with an unknown result is never replayed automatically; a consumed grant is never restored, even if the tool failed or the process crashed.
- SQLite and the Harness session log are separate consistency domains: quiet only with a flush proof; after a restart nothing uncertain is treated as success.
- Taking over after a crash needs an exit proof bound to owner / Objective / epoch / execution; without it, that Objective is held (
LEASE_BUSY) and never touched.
- One live Reins process per database: another live owner makes startup fail with
DATABASE_IN_USE; a process whose database was taken over stops scheduling and polling at once.
- "Tests pass" is verified by Reins itself:
reins.command_succeeds runs the frozen command at acceptance; if a pinned file changed, it does not run at all.
- A planned Objective comes only from you:
reins_create_objective works only after you typed /reins in that conversation, once, never in Reins' own sessions, and check files the planner wrote are pinned before any Agent starts.
- Conservative takeover after a forced stop: a lease is taken over automatically only when the old process is recorded on this host, is confirmed gone, and had nothing running; otherwise you confirm with
/reins-recover --confirm, and that confirmation is append-only.
- After an approval, the same session continues: the next Attempt reopens the parked Harness session (still a new Attempt with a new Goal; the grant still allows exactly that one call). After a failure, crash or replan, a new session is used. A Task parked for approval 10 times in a row is held for you to decide.
- URLs chosen by an Agent only reach allowed hosts: waits check when registered and before every poll, refuse redirects and never carry credentials.
Tests and verification
npm ci # dependencies
npm run typecheck # type check against the public Harness API
npm test # main regression suite (about 70 seconds)
npm run test:runner # scenario driver and verifier regression (including verifier negatives)
npm run test:live # real DeepSeek Harness scenarios (about 4 minutes)
npm run test:live:phase3 # Phase 3 single-Task real Harness scenarios (about 2 minutes)
node scripts/build-diagrams.mjs # rebuild the bilingual README diagrams
Current results:
| Suite | Result |
|---|
npm test | 499 / 499 |
npm run test:runner | 48 / 48 (including 28 Phase 4 verifier negatives) |
npm run test:live | 7 / 7 |
npm run test:live:phase3 | 11 / 11 gates |
npm run typecheck | passes |
Real Harness scenarios (npm run test:live)
Each case starts a real dsh --profile process in an isolated DSH_HOME: real Cordis, Agent, agent loop, goal-round driver, session persistence, tool pipeline and headless approval; only the model is a deterministic fixture. User actions go through the really registered /reins-* commands; GitHub is a local stand-in that keeps its state across process restarts.
| Case | What it proves |
|---|
e2e | "Get a PR mergeable" with five Tasks: no approver → park → approve → a new Attempt pushes exactly once; CI fails → fix → approved push again → CI passes; review handled; Objective completes after every acceptance |
crash-running | Attempt killed with SIGKILL while running, restarted with an exit proof: reconciled as interrupted, retried, completed |
crash-consumed | Killed after the grant was consumed and the push happened but before the result was recorded: the side effect is marked ambiguous, not replayed, completed after you confirm |
crash-awaiting-ci | Killed while waiting for CI: the watcher resumes polling and drives the next Attempts to completion |
no-proof | Killed without an exit proof: that Objective is held (LEASE_BUSY), untouched, while Reins runs |
second-process | A second process on the same database while the first runs e2e: it refuses to start with DATABASE_IN_USE and runs nothing; the first completes normally |
subagent | Real subagent and session fork: sensitive calls from a read-only child are recognized and denied with zero physical side effects; the child session's parentSession is the Reins anchor session; a fork retry really derives from the previous session |
The independent verifier scripts/phase4-harness-verify.mjs reads only SQLite (opened read-only), the physical side-effect log, the supervisor records and the real session logs, with 6–59 checks per case, and does not trust the scenario plugin's own conclusions. tests/runner/phase4-verifier-negative.test.js tampers with a real run snapshot one fact at a time to prove the verifier fails.
[!NOTE]
The scenarios above use a deterministic model and a local GitHub stand-in. With a real model (DeepSeek), these were verified separately in DSH Web: /reins planning (look first, ask about a real choice, write and pin the check, create; then the Reins Agent did the work and the pinned check passed); /reins-goal; normal fixes, correcting after a denial, approvals, an impossible task, the default tool policy, a forged test report, and reins.command_succeeds acceptance (with a forged report present, Reins ran the tests itself, got exit 1, and the Task did not complete).
Project layout
reins/
├── src/
│ ├── plugin.js # plugin entry (Cordis apply)
│ ├── config.js # configuration schema and strict validation
│ ├── live-config.js # settings saved on the Plugins page apply without a reload
│ ├── commands/ # /reins and /reins-* commands, /reins planning (plan.js)
│ ├── orchestrator/ # DAG, waits/events, watchers, approvals, worktrees, routing, fingerprints, handoffs
│ ├── orchestration/ # runtime: admission, budgets, no-progress, outer settle
│ ├── acceptance/ # acceptance gate, frozen criteria, validators, receipt integrity
│ ├── approval/ # relational approvals (Phase 2)
│ ├── store/ # SQLite store, transactions, migrations, instance lock
│ ├── reconcile/ # startup reconciliation
│ ├── harness/ # public Harness API adapter
│ └── tools/ # task_complete tool
├── client/client.js # settings page on DSH Web's Plugins page
├── migrations/ # 001–015, only ever added to
├── scripts/ # real Harness scenario drivers, independent verifiers, diagram builder
├── tests/ # regression tests grouped by feature (see tests/README.md)
├── docs/assets/ # README images (English and Chinese)
└── smoke/public-api.ts # public API type-check sample
Development
Rules
- For a feature or a fix, first write a test that reproduces the problem, then change the code.
- Never delete or loosen a safety assertion to make a test pass. Identity binding, lease fencing, and default-deny for approvals and acceptance stay.
- Task and Objective completion goes through the acceptance gate, never through a model's "done" or a direct database edit.
- Never commit API keys, real user configuration, real databases, logs or run evidence.
- After every change run
typecheck, test and test:runner; for framework lifecycle, recovery or tool calls also test:live.
Conventions
- Schema changes only add migrations; 001–015 are never rewritten.
- A new user action is a new top-level
/reins-<action> command (a row in COMMANDS in src/commands/register.js); the real-Cordis unload test pins the exact command set. listValidatorKinds() stays at the 6 file validators; new acceptance kinds go into the separate reins.* registry.
- Temporary test files go through
tests/fixtures/scratch.js; never hard-code local paths.
- Text files use LF (enforced by
.gitattributes).
- The English and Chinese diagrams are built from one description by
scripts/build-diagrams.mjs.
What gets published
package.json files contains the plugin only (src, migrations, client, locale, the bundle patch, icon, license and README); tests, scripts and dev dependencies are not published.
Known limitations
- File criteria trust files in the working directory, which the Agent can write; for "tests / build pass" use
reins.command_succeeds.
reins.command_succeeds runs code from the working directory; any file not pinned may have been changed by the Agent. It does not run in a sandbox.
/reins planning depends on the judgement of the conversation's model: it may decide something you would have liked to be asked about, or pick a check that is weaker than you want. Read its summary; /reins-status <id> shows the exact check of every Task. /reins-goal does not pin test files; for a stronger guarantee use /reins (which pins checks it wrote) or /reins-objective with pins.
- With a real model,
/reins planning, /reins-goal and single-Task scenarios are verified; CI / PR watchers, crash recovery, parallel worktrees, subagent / fork and replanning were verified in the real Harness with a deterministic model only.
- Tested on Windows only; Linux and macOS are unverified.
- Desktop notifications are built in on Windows only; elsewhere configure
notifyCommand.
- DSH's sandbox blocks commands that start child processes, such as
node --test, so an Agent may ask to widen the sandbox; acceptance is not affected, because Reins runs the tests itself outside the sandbox.
- Single-Task Objectives (
/reins create) run one at a time and, without evidenceRoot, work in the DSH process's start directory; orchestrated Objectives run in parallel execution slots.
License
MIT.