dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH plugin: device/runtime health monitoring, restart-pressure scoring, maintenance scheduling and action decisions for DeepSeek Harness. It never restarts anything itself.
English | 中文
Health monitoring, restart-pressure scoring, maintenance scheduling and action decisions for DeepSeek Harness — it never restarts anything itself.
dsh-health-scheduler watches a DS-Hns machine, reduces everything it can measure to a
single restart_pressure number between 0 and 100, decides what should happen next, and
then asks somebody else to do it. Levels 1 and 2 of its action ladder are handed to a
worker-control adapter; levels 3 and 4 are requests posted to the separate
dsh-restart bundle, which owns restart
execution.
The pipeline is one direction only:
providers -> normalization -> rolling windows -> trend -> pressure -> policy
-> maintenance scheduler -> action adapters (worker control | restart request)
| Concern | Owner | This plugin's role |
|---|---|---|
| Sensing device and runtime health | Health Scheduler | Owns it. Providers sample, normalize and retain history. |
| Judging how bad things are | Health Scheduler | Owns it. The pressure model and the policy engine live here. |
| Scheduling maintenance | Health Scheduler | Owns it. Window, target, deferral budget, safe point. |
Reducing load (THROTTLE, PAUSE_NEW_WORK) | Health Scheduler | Requests it through the worker-control adapter. |
| Actually restarting anything | dsh-restart | Does not own it. Emits a request and reads the answer. |
| Restart locks, rate limits, checkpoints, graceful shutdown | dsh-restart | Not here. |
| Relaunching after a crash, crash-loop breaker | Supervisor | Not here. |
| Task state, checkpoints, resume | DS-Hns Core | Not here. The safe-point query is a question, not storage. |
| A second Mega Core | nobody | Explicitly out of scope. |
Three promises follow from that table, and they are load-bearing:
taskkill, no reboot, no process kill, no
child_process call that could reach the operating system's restart path. The only
execFile in the codebase runs a configured telemetry probe (src/providers/sources.ts).unknown, never healthy. A dimension with no data scores
null, and its nominal weight is redistributed over the dimensions that do have data.dsh plugin --profile <name> <pnpm args> forwards its remaining arguments to pnpm
inside the profile directory and then reconciles the profile's bundle list, so an
installed package that declares dsh.bundle joins the layer stack automatically. This
package declares dsh.bundle.patch: ./cordis.patch.yml.
From a local checkout of this repository:
# Windows PowerShell, from the checkout directory
dsh plugin --profile web add <path-to-checkout>\dsh-health-scheduler
# any platform, from the checkout directory (a bare "." is anchored to your cwd)
cd dsh-health-scheduler
dsh plugin --profile web add .
From a package name or a tarball:
dsh plugin --profile web add dsh-health-scheduler
dsh plugin --profile web add ./dsh-health-scheduler-0.1.0.tgz
cordis.patch.yml inserts one row, id: health-scheduler, whose config block restates the
whole balanced preset with comments. The layer is applied after every earlier bundle and
before your own profile's cordis.patch.yml, so your overrides win.
A patch replaces the targeted row's whole config; it is not a deep merge. If you override
providerOptions in your profile patch, copy the entire nested object you want, not just the
one leaf you are changing. The plugin's own configuration resolution is a deep merge over the
selected preset, so a document you pass to the plugin directly behaves the way you would expect
— it is the profile patch layer that replaces.
plugin/manifest.json describes the same contract in machine-readable form: id, kind, entry
module, the install command and patch path, required and optional services, the settings
namespace, the three tool names, the six event names, the action levels this plugin applies
versus the ones it only requests, and what degrades when dsh-restart or worker control is
absent.
--dump-config prints the composed profile tree without booting it. Use it to confirm
that the plugin row is present and that its configuration is what you wrote:
dsh --profile web --dump-config
--dump-default-config prints the same tree without your user layer and without any
--patch overlays, which makes it the fastest way to see what this plugin contributes.
dsh --profile web
dsh web is a hardcoded alias for --profile web. A successful load logs one line:
health-scheduler: monitoring started (7 providers, interval 15000 ms, preset balanced)
A git-hosted plugin builds on install through its prepare script, and pnpm blocks that
build until you allow it. When dsh plugin ... add git+https://… fails, the CLI prints the
exact key pnpm wants; add it under allowBuilds in
<profile directory>/pnpm-workspace.yaml and re-run the same command.
This plugin ships "prepack": "npm run build" and lib/ is gitignored, so:
lib/ produced by the prepack hook.allowBuilds entry, because the checkout has
no lib/ and prepack must run tsc to create it.npm run build, and needs that build otherwise.The plugin works with no configuration at all — the balanced preset is the default and
every leaf in it is a default, not a law. A minimal useful document is one line:
// profile package.json -> dsh.profile, or the plugin's settings namespace
{ "preset": "balanced" }
A more realistic first configuration turns on the scheduled maintenance window and points the hardware provider at a thermal helper:
# $DSH_HOME/profiles/web/cordis.patch.yml
- id: health-scheduler
name: dsh-health-scheduler
config:
preset: balanced
maintenance:
enabled: true
targetTime: '04:00'
windowStart: '03:30'
windowEnd: '05:00'
maxDeferMs: 3600000
urgentOverridePressure: 92
safePointRequired: true
providerOptions:
hardware:
helperCommand: ['powershell', '-NoProfile', '-File', 'C:\\dsh\\gpu-temp.ps1']
helperTimeoutMs: 5000
statsFile:
paths: ['C:\\dsh\\telemetry\\metrics.json']
staleAfterMs: 120000
After a boot the plugin logs
health-scheduler: monitoring started (7 providers, interval 15000 ms, preset balanced)
and on demand the operator (or the model) reads the report, which begins like this:
Restart Pressure: 21 / 100
State: THROTTLED
Primary Cause: gpu_usage=95.00 pp scores 89/100, held 1260s
Telemetry coverage: 60%
Unknown dimensions: runtime, worker, computer_use_ui (not scored as healthy)
Maintenance: next window 03:30-05:00
Safe point: no safe-point source registered; readiness unknown
Capabilities: restart=unavailable, worker-control=unavailable
Every number there is traceable to a metric. There is no sentence in the output that is not backed by a measurement — "the model thought it should restart" is not a possible reason code.
All health data enters through a HealthProvider. The pressure model and the policy engine
are forbidden from reaching for NVML, LibreHardwareMonitor, HWiNFO, a Windows API, worker
internals or Electron internals. A provider that cannot measure a metric omits the key;
absence is the only way to say "unknown", and it is deliberately not spelled 0.
normalizeSample turns a raw sample into the canonical vocabulary. A reading that is not a
finite number, or that is outside the metric's hard physical bound, is dropped and reported
as a violation. A ratio sent as a percentage (1.0 < v <= 1.5) is clamped to 1 with a
violation attached rather than silently accepted. The surviving bag is sorted by metric name
so snapshots are stable.
Every metric keeps raw samples for windows.rawMs (30 minutes, raised to the longest
statistics window if that is longer), one mean/max/min bucket per windows.aggregateBucketMs
(5 minutes) retained for windows.aggregateRetentionMs (24 hours), and per-day summaries
retained for windows.dailyRetentionMs (14 days). Statistics are computed over 5 min / 30 min
/ 2 h / 6 h windows. Memory is bounded by construction: the store does not grow with uptime.
A leak is not a value, it is a slope. TrendAnalyzer reports a slope only when there are at
least trend.minSamples samples spread over at least trend.minSpanMs, and only trusts it
when R² ≥ trend.minRSquared. The metric registry owns polarity, so a rising GPU
temperature is worsening and a falling recovery_rate is too.
Each metric ramps linearly from its warn endpoint (0 points) to its critical endpoint
(100 points), with the registry's polarity deciding which end is which. A dimension's score
blends its weighted mean with its worst member at WORST_WEIGHT = 0.5, so one critical
metric is not averaged away by five calm ones. The six dimension scores are then combined with
the configured weights.
unknown, and coverage says soA dimension with no known metric scores null and is listed in unknownDimensions. Its
nominal weight is renormalized over the dimensions that do have data, and the renormalized
share actually backed by telemetry is published as coverage. A pressure reading at 40 %
coverage can never be mistaken for a full-confidence one, and every decision record carries
coverage_NNpct in its reason list.
Six independent mechanisms keep the plugin from becoming a source of churn:
| Mechanism | Default | What it stops |
|---|---|---|
| Sustain gate | per metric, e.g. 60 s for gpu_temp_c | A 30-second spike scoring at all. |
| Hysteresis | e.g. throttle enters at 55, exits at 45 | 55 -> throttle, 54 -> normal, 56 -> throttle. |
| Debounce | 2 consecutive evaluations | A single evaluation escalating to a high-risk level. |
| Dwell | 120 s | Any transition below restart level faster than the dwell time. |
minRepeatActionMs | 10 min | Re-issuing the same action sooner than allowed. |
| Three cooldowns | 5 / 30 / 60 min | Decision storms, including from a failing adapter. |
The cooldown starts when an adapter is actually invoked — including when it refuses or
throws. tests/scenarios.test.js drives a permanently critical machine for 30 minutes at a
15-second tick with a throwing restart adapter and asserts that at most three attempts reach
the adapter instead of one per tick.
The policy engine emits at most one action per tick and never touches a process. Levels 1
and 2 go to WorkerControlAdapter; levels 3 and 4 become a RestartRequest for
RestartAdapter. Every applied action produces exactly one audit record with its pressure,
coverage, named drivers, reason codes and the adapter's answer.
Three tools are registered when the profile has a tool runtime. All three are read-only: none of them exercises a capability.
health_statusReports the current health of the DS-Hns runtime. Parameters: section
(full | pressure | maintenance | providers, optional, defaults to full).
health_status({ "section": "pressure" })
Restart Pressure: 21 / 100
State: THROTTLED
Coverage: 60%
Primary cause: gpu_usage=95.00 pp scores 89/100, held 1260s
[0.3] gpu_usage_critical: gpu_usage=95.00 pp scores 89/100, held 1260s
[0.2] cpu_usage_critical: cpu_usage=90.00 pp scores 71/100, held 1260s
[0.2] gpu_temp_c_critical: gpu_temp_c=88.00 °C scores 71/100, held 1260s
The full section is the whole report: pressure, state, primary cause, coverage, unknown
dimensions, maintenance summary, safe-point summary, capability states, a per-dimension
table, drivers, worsening trends, provider status, memory lines, the last five decisions and
the tick's warnings. The maintenance and providers sections are single-topic views of the
same snapshot.
health_historyReturns rolling-window statistics for one canonical metric.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
metric | string | yes | A canonical metric name, e.g. gpu_temp_c. |
window_minutes | number | no | Restrict output to windows at or below this size. |
An unknown metric name throws with the full canonical list in the message.
{
"metric": "process_rss_bytes",
"windows": [
{
"window_minutes": 30,
"count": 120,
"mean": 2540000000,
"p95": 2870000000,
"max": 2900000000,
"latest": 2880000000,
"slope_per_hour": 1200000000,
"r_squared": 0.9821,
"consecutive_ms": 0,
"span_ms": 1785000
}
]
}
consecutive_ms is the sustain-gate duration: how long the metric has held its current band.
The full payload also carries min, earliest and median per window.
health_policyExplains the decision policy. Parameters: action (explain | config | decisions,
optional, defaults to explain).
health_policy({ "action": "explain" })
Action ladder (enter/exit pressure):
1 THROTTLE enter >= 55, exit <= 45
2 PAUSE_NEW_WORK enter >= 70, exit <= 60
3 REQUEST_APP_RESTART enter >= 80, exit <= 68
4 REQUEST_SYSTEM_REBOOT enter >= 95, exit <= 85
Levels 1 and 2 are applied by this plugin through the worker-control adapter.
Levels 3 and 4 are *requests* handed to dsh-restart, which owns restart execution.
A restart request is additionally gated by the maintenance window and by getMaintenanceReadiness().
Dimension weights: time=0.15, thermal=0.2, memory=0.25, runtime=0.15, worker=0.15, computer_use_ui=0.1
Weights are renormalized over the dimensions that actually have telemetry; the fraction that does is reported as coverage.
config returns the resolved numbers as JSON (preset, thresholds, weights,
cooldowns, anti_flap, maintenance, windows_ms, sampling). decisions returns the
last 20 audit records as JSON.
Configuration is a partial document. It is deep-merged onto the selected preset, so every
leaf you omit comes from the preset; arrays replace rather than merge. The plugin registers
the settings namespace health-scheduler when the profile provides a settings service,
and runs from the bundle patch alone when it does not.
One caveat belongs here rather than in the configuration reference: the profile patch layer
is not a deep merge. A cordis.patch.yml row replaces the targeted row's whole config, so an
override that restates only part of a nested object loses the rest. The deep merge described in
this section applies to the document the plugin actually receives. See
What the bundle contributes.
The complete key-by-key reference, including the stats-file format and the command-probe format, is in docs/configuration.md.
| Group | Keys | Default |
|---|---|---|
| Master | enabled, preset | true, balanced |
sampling | intervalMs, trendIntervalMs, summaryIntervalMs, providerBackoffMs, providerBackoffMaxMs | 15 s, 60 s, 300 s, 30 s, 600 s |
windows | rawMs, windowsMs, aggregateBucketMs, aggregateRetentionMs, dailyRetentionMs | 30 min, [5m,30m,2h,6h], 5 min, 24 h, 14 d |
trend | minSamples, minSpanMs, minRSquared | 3, 5 min, 0.5 |
weights | time, thermal, memory, runtime, worker, computer_use_ui | 0.15 / 0.20 / 0.25 / 0.15 / 0.15 / 0.10 |
thresholds | one {enter, exit} band per action | 55/45, 70/60, 80/68, 95/85 |
metrics | per canonical metric: band, weight, sustainMs, trendPointsPerHour, trendCap | see docs/metrics.md |
cooldowns | throttleMs, maintenanceMs, escalationMs | 5 min, 30 min, 60 min |
throttle | concurrencyLimit, concurrencyFactor | null, 0.5 |
maintenance | enabled, targetTime, windowStart, windowEnd, maxDeferMs, urgentOverridePressure, allowAppRestart, safePointRequired | false, 04:00, 03:30, 05:00, 60 min, 92, true, true |
antiFlap | minStateDwellMs, minRepeatActionMs, debounceEvaluations | 120 s, 10 min, 2 |
resilience | providerFailureLimit, providerRetryAfterBackoff, reportDegradedCapability | 3, true, true |
storage | enabled, directory, maxLogBytes, maxRecentDecisions | true, null, 4 MiB, 50 |
| Providers | disabledProviders, providerOptions | [], see docs |
A configuration that cannot be acted on is rejected loudly. resolveConfig throws a
ConfigError naming the dotted path; the plugin's apply catches it, logs it and continues
on the balanced preset rather than failing the boot.
Three horizons, all bounded, all configurable:
| Horizon | Where | Default | Answers |
|---|---|---|---|
| Raw samples | windows.rawMs | 30 min, raised to the longest windowsMs entry | Percentiles, slopes, consecutive-band duration. |
| Aggregate buckets | windows.aggregateRetentionMs, aggregateBucketMs | 24 h, 5 min buckets | Long-horizon movement without keeping raw points. |
| Daily summaries | windows.dailyRetentionMs | 14 days | "Was last Tuesday worse than today?" — one {count, mean, max, min} per metric per local day. |
Daily summaries are rolled up from the aggregate buckets on demand, so their cost is
proportional to the number of buckets, not to uptime. They are published on every
HealthSnapshot as dailySummaries and in the JSON payload as daily_summaries, and they are
frozen at local midnight boundaries so a day is comparable across a DST change.
All three presets start from the same neutral balanced document and differ in exactly four
places. PRESET_SCALES in src/core/presets.ts is the whole story:
conservative: { bands: 0.85, cooldown: 1.5, maintenance: 0.85 }
balanced: { bands: 1, cooldown: 1, maintenance: 1 }
aggressive: { bands: 1.15, cooldown: 0.7, maintenance: 1.3 }
scalePreset multiplies every ladder enter and exit by bands, so hysteresis keeps
its proportion instead of flattening; it multiplies the three cooldowns by cooldown; and it
multiplies maxDeferMs, minStateDwellMs and minRepeatActionMs by maintenance.
urgentOverridePressure is divided by bands, so a lower-pressure machine treats pressure
as urgent sooner. Scaled thresholds are clamped into 1 … 99, so no rung can be scaled out of
reach.
| Field | conservative | balanced | aggressive |
|---|---|---|---|
thresholds.throttle enter / exit | 47 / 38 | 55 / 45 | 63 / 52 |
thresholds.pause_new_work enter / exit | 60 / 51 | 70 / 60 | 81 / 69 |
thresholds.request_app_restart enter / exit | 68 / 58 | 80 / 68 | 92 / 78 |
thresholds.request_system_reboot enter / exit | 81 / 72 | 95 / 85 | 99 / 98 |
cooldowns.throttleMs | 7.5 min | 5 min | 3.5 min |
cooldowns.maintenanceMs | 45 min | 30 min | 21 min |
cooldowns.escalationMs | 90 min | 60 min | 42 min |
maintenance.maxDeferMs | 51 min | 60 min | 78 min |
maintenance.urgentOverridePressure | 108 | 92 | 80 |
antiFlap.minStateDwellMs | 102 s | 120 s | 156 s |
antiFlap.minRepeatActionMs | 8.5 min | 10 min | 13 min |
Note that a preset preserves the relative width of every hysteresis band rather than
flattening it, and entries are capped at 99 so no rung can be scaled out of reach: a
pressure above 100 does not exist, so an entry of 109 would silently delete that rung. On
aggressive the REQUEST_SYSTEM_REBOOT rung needs a perfectly saturated model
(entry 99, exit 98), which is exactly the "only under real duress" behaviour that preset is
meant to have. The metric table itself — every band, sustainMs and trend point — is
identical in all three presets.
The three presets are also shipped as JSON under presets/ for diffing, together
with a JSON Schema for a whole configuration document.
The plugin degrades; it does not fail. Each of these is a normal, well-typed outcome.
| Situation | What happens |
|---|---|
dsh-restart is not installed, or reports itself unusable | UnavailableRestartAdapter reports capability: 'unavailable'. Monitoring and throttling continue. A restart decision is downgraded to PAUSE_NEW_WORK with reason restart_capability_unavailable. A dsh-restart whose configuration disables every restart mode reports unavailable too, rather than accepting requests it could never carry out. |
| No worker-control service is bound | UnavailableWorkerControlAdapter reports unavailable. A THROTTLE attempt returns applied: false with a detail explaining it; the tick continues. |
| A provider throws | That provider only is disabled for an exponential backoff (providerBackoffMs * 2^steps, capped at providerBackoffMaxMs, starting once consecutive failures reach providerFailureLimit). Its metrics simply stop arriving and become unknown. |
A provider returns degraded: true | The metrics it did measure stay authoritative; the note is surfaced in the snapshot's warnings. |
| A sensor does not exist | The metric key is omitted. The dimension may become unknown, coverage drops, and the reason list says so. |
| No stats file exists yet / it is stale | Nothing is reported and a detail names the paths that were looked for; a stale file's contents are refused and the sample is degraded with the staleness detail. |
| A configured helper command fails or times out | The probe error is reported as a degraded sample; already-measured metrics are kept. |
| The decision log is not writable | DecisionLog counts the failure and exposes it via lastError; the scheduler keeps ticking. |
| No safe-point source is registered | foldReadiness returns safe: null. With safePointRequired: true a restart request is blocked with reason safe_point_unknown — an unanswered question is not a yes. A source that throws or hangs contributes an unknown reading after a 1-second per-source budget. |
| The settings service or tool runtime is absent | A warning is logged and the plugin runs from the bundle patch alone, or registers no tools. A ConfigError from a bad user document is logged and the balanced preset is used instead of failing the boot. |
dsh-restart