DeepSeek Harness gave every capability a socket — models, tools, sessions, schedulers, the UI. One chair was left empty, and their own architecture notes name it:
“Injected context waits in the inbox until another message wakes it.”
— DeepSeek Harness architecture documentation, naming its own open seat
Until that other message arrives, nobody is home. The chain of who-wakes-your-agent ends at a clock, a webhook, or you. dsh-first-hop seats a resident judge at that chair: it watches the streams your harness already carries and, at each boundary, decides — hold, flag, counsel, or wake. The first evidence-fired turn in any harness.
Cron, polls, follow-up timers. Fires whether or not anything happened — and misses everything that happens between ticks. Cost scales with paranoia, not with events.
You, noticing CI went red. You, pressing tab. You, re-reading a log your agent already streamed past. Presence rented from your own attention — the most expensive watcher on the machine.
This plugin. Fires when the world contradicts what your agent is doing — a failed check, a premise gone stale, a file changed under its feet. Silent otherwise. Silence is a judgment too, and it’s on the tape.
Every deployed agent is woken by something dumber than the mind it wakes. The wake decision is the whole product.
Try it — this demo is wired the way the plugin is wired.
The state is a file only you write. No code path writes it — not the plugin, not the harness, not the model. Off is inert mid-flight: with the switch off, your harness is byte-identical to this plugin not existing. That isn’t a promise; it’s a shipped test (F-INERT). Every flip lands on an append-only toggle tape.
Live is earned here, catch by catch. Shadow is the default; the ledger is where you decide.
No new instrumentation. dsh logs everything the model sees; the hooks bridge even carries rival harnesses’ sessions in the same schema. The evidence was always there — nobody was home to read it.
The suite went green at 14:02. The next timer would have noticed at 14:45. Forty-three minutes of a warm GPU and a cold trail.
You fixed a line by hand while the agent was three edits deep in the same file. Declared-vs-actual watches what really hit the disk — the blind spot hooks can’t see, closed deterministically.
Two sessions, one repo. B renamed the interface A is coding against. Neither session can see the other; the resident sees both.
A deprecation warning three hundred lines deep in a passing build — the one that breaks next month’s upgrade. Novelty isn’t relevance; the judge is prompted for contradiction, not surprise.
The oldest catch in the book, and still the one everyone polls for. A producer-side poll is legal — the judgment is the event, and it fires once, with resolve semantics, not every five minutes.
The floor is a config block, not a plugin fork. Name a condition class, give it a pattern and a budget, and it’s a first-class citizen on the same tape as the judge.
Named condition classes that need no model and no tokens: CI state, test completion, declared-vs-actual file effects, process exits, lockfile conflicts. Ships enabled.
For everything a rule can’t parse: strict-JSON verdicts from any OpenAI-compatible local endpoint — llama.cpp server, Ollama, LM Studio, vLLM. 9B-class recommended (~8 GB VRAM at Q5).
# cordis.yml — the whole configuration surface, validated at load, fails loud
plugins:
dsh-first-hop:
state: ~/.dsh/first-hop.state # off | shadow | live — a file only you write
judge:
endpoint: http://127.0.0.1:8080/v1 # llama.cpp · Ollama · LM Studio · vLLM
model: qwen3-9b-instruct-q5_k_m
adapter: null # disposition LoRA seam — optional
budgets:
wake: 2/hour # per condition class, never per boundary
flag: 6/hour
floor:
ci: { branch: feature/* }
fswatch: [ C:/repos/myapp ] # declared-vs-actual over an allowlist
Not a style guide — each law is either a test in CI, a file permission, or a structural property.
Judgment may be delayed under load; the world is never edited to fit the budget.
Every judgment appends — including silence. Restraint you can audit; absence you can’t.
A caught condition is a ledger entry with resolve semantics, not a nag loop. Repeats are suppressed until you resolve or it materially changes.
Fifty-nine boundaries are not fifty-nine chances to speak. Grains never mix; every number carries its denominator.
Its own injections are lane-stamped and never judged. A watcher that deliberates about interrupting itself is a space heater.
F-INERT ships as a test: with the switch off, harness behavior is byte-identical to the plugin being absent. Mid-flight.
Verdicts are written and never consumed until you flip the file. Live has exactly two verbs, both landing inside your own session, under your eyes.
Inherited from dsh, kept sacred: anything that reaches your agent is a session event, replayable forever.
Your session logs are replayable — dsh built them that way. The bench replays them, retro-labels every boundary, and races the judge against the best swept polling policy at every token budget. Catch-within-window and false-fires, both grains, printed.
Example replay — one operator’s own logs, one month. catch@15m = ground-truth events caught within 15 minutes, per condition class; false-fires reported beside it at both grains. Your logs produce your numbers; the bench refuses to quote anyone else’s.
# replay your own logs — no key, no cloud
$ npx dsh-first-hop bench \
--logs ~/.dsh/sessions \
--window 15m --grains both
If the judge does not dominate your polling frontier, the bench says so — and the README’s advice is: keep the floor, skip the judge. That result prints too. A tool that can’t lose can’t be trusted to win.
The judge is a seat, not a weld. The plugin speaks a one-page stream contract — frames in, verdicts out, the clock never crossing the seam. A native resident kernel (always-ingesting, token-grain, restraint trained into weights) drops into the same socket, and the TypeScript judge steps aside.
This plugin is the body’s first nerve. The heart, when it exists, has a socket waiting in every install.
One package into your harness, two lines into your composition.
$ npm i dsh-first-hop
Any OpenAI-compatible endpoint. No GPU? The floor still works — it costs nothing.
$ ollama pull qwen3:9b
Read the ledger for a week. Then decide about live — it’s your file, and only your file.
$ echo shadow > ~/.dsh/first-hop.state