dsh-first-hop MIT dsh-plugin v1.0.0 vision draft
A community plugin for DeepSeek Harness · first hop of the FUSOR architecture

Your agent wakes on evidence. Not on a clock.

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.

$ npm i dsh-first-hop
cordis.yml plugins: { dsh-first-hop: {} }
runs entirely local never phones home bring your own 9B shadow by default single package · no runtime deps
The interval

Three things can wake an agent today. Two of them are dumb.

The clock

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.

The human

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.

The evidence

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.

The control surface

Three positions. One law.

Try it — this demo is wired the way the plugin is wired.

~/.dsh/first-hop.state

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.

first-hop.heartbeat.json · fresh · beat 4,183 no heartbeat — the switch is off

ledger · inert the shadow ledger — what it would have done the live tape — what it did

Zero reads consumed. Zero writes made. Zero judgments formed. The switch is off, and off means absent.
would wakewake
CI went red on feature/ingest-retry — 41 minutes before the next scheduled poll would have looked. condition ci-red#4821 · floor rule · seen once, resolve pending shadow: verdict written, never consumed → followup() fired · landed as the session’s next turn · on both tapes
would counselcounsel
Premise drift: session B’s commit 9f21c renamed parseFrame(); session A is mid-rewrite against the old name. condition premise#0917 · judge · confidence 0.83 · 9B, local shadow: counsel drafted, never injected → inject() queued · waits for the next admitted request, logged
would flagflag
Declared vs. actual: npm install touched 40,312 files — 3 outside the declared scope. condition fs-scope#2210 · floor rule · deterministic, model-free shadow: card written, never surfaced → card surfaced in the session transcript
hold ×59hold ×59
Fifty-nine boundaries held this hour. Holds are on the tape too — silence you can audit is the difference between restraint and absence. condition grain · budgets: wake 2/h · flag 6/h — per condition class, never per boundary

Live is earned here, catch by catch. Shadow is the default; the ledger is where you decide.

The catches

What it catches — from streams your harness already carries.

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.

floor

The tests finished. The agent idled.

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.

floor

A human edited the file mid-rewrite.

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.

judge

Session B invalidated session A’s premise.

Two sessions, one repo. B renamed the interface A is coding against. Neither session can see the other; the resident sees both.

judge

The log scrolled past the thing that matters.

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.

floor

CI red on the watched branch.

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.

floor

Anything you can name in a rule.

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.

The judge

A floor that never guesses. A judge that earns its keep.

Tier 0 · deterministic

The floor

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.

  • It is also the null. The floor is the baseline the judge must beat — built in, not bolted on.
  • Some catches the floor wins. The bench prints it either way. That’s the method, not a bug.
  • Zero marginal cost. Rules fire on events, not schedules.
Tier 1 · local model

The 9B judge

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).

  • Bursty, not resident. Sub-1k tokens in, ~100 out, at event boundaries — seconds apart, not per token.
  • Budgets are constitutional. Fire-rate caps per condition class; see-it-once with resolve semantics.
  • An adapter seam, empty on purpose. judge.adapter loads a disposition LoRA the day you have one. Restraint belongs in weights; until then, shadow.
# 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
The constitution

Eight laws, enforced by construction.

Not a style guide — each law is either a test in CI, a file permission, or a structural property.

L1

Percepts are never dropped.

Judgment may be delayed under load; the world is never edited to fit the budget.

L2

Holds are on the tape.

Every judgment appends — including silence. Restraint you can audit; absence you can’t.

L3

See it once.

A caught condition is a ledger entry with resolve semantics, not a nag loop. Repeats are suppressed until you resolve or it materially changes.

L4

Budgets at condition grain.

Fifty-nine boundaries are not fifty-nine chances to speak. Grains never mix; every number carries its denominator.

L5

Self-echo suppression.

Its own injections are lane-stamped and never judged. A watcher that deliberates about interrupting itself is a space heater.

L6

Off is inert.

F-INERT ships as a test: with the switch off, harness behavior is byte-identical to the plugin being absent. Mid-flight.

L7

Shadow before live.

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.

L8

Model-visible ⟺ logged.

Inherited from dsh, kept sacred: anything that reaches your agent is a session event, replayable forever.

The bench

Print your own receipts.

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
the judge (9B, local) the floor (deterministic) polling frontier (the null)
0 25 50 75 100 0 15k 30k 45k 60k tokens spent per hour caught within 15 min (%) 1 poll/h · 6% caught within 15 min 4k tok/h · 16% 10k tok/h · 27% 20k tok/h · 39% 36k tok/h · 50% 56k tok/h · 58% best swept polling policy +49 pts at equal spend the floor · 41% caught · ~0 tokens (rules fire on events) the floor · 41% · ~0 tok the judge · 78% caught within 15 min · 12k tok/h · both grains printed the judge · 78% @ 12k tok/h

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.

Honest state

What this is not.

Read before installing

  • Not the kernel. No KV residency, no token-grain decode-on-delta, no microsecond aborts. Judgment here is boundary-batched, at commit grain — evidence-fired, but not resident.
  • The judge is prompted, not tuned. A stock 9B can flood or go deaf. That is why shadow is the default, budgets are constitutional, and the floor is co-equal — the failure modes are designed for, not denied.
  • Not an actuator. Live mode has two verbs, both landing inside your own session, under your eyes: a follow-up wake and injected counsel. It cannot touch the world, hold credentials, or approve anything.
  • Not a telemetry client. The tape never leaves your machine. There is no server, no account, no beacon. MIT, one package, dependencies: your harness and your model.

The socket · reserved

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.

transport: file-spool · lane-contract v0.1 · verdicts {hold, flag, counsel, wake}
Quickstart

Shadow tonight. Decide in a week.

1

Install

One package into your harness, two lines into your composition.

$ npm i dsh-first-hop
2

Point it at a local model

Any OpenAI-compatible endpoint. No GPU? The floor still works — it costs nothing.

$ ollama pull qwen3:9b
3

Flip to shadow

Read the ledger for a week. Then decide about live — it’s your file, and only your file.

$ echo shadow > ~/.dsh/first-hop.state