Back to home@bochen2029-pixel

dsh-first-hop

Your agent wakes on evidence, not on a clock. A community plugin for DeepSeek Harness: watches the streams your harness already carries and decides - hold, flag, counsel, or wake. Local judge, shadow by default, MIT.

Stars
0
Language
TypeScript
Created
Aug 25, 2026
Updated
Aug 25, 2026

Introduction

dsh-first-hop

Your agent wakes on evidence. Not on a clock.

A community plugin for DeepSeek Harness. It watches the streams your harness already carries and, at each boundary, decides one of four things — hold, flag, counsel, or wake.

MIT · runs entirely local · shadow by default · bring your own 9B.

"Input reaches the driver through one inbox. Some messages wake it immediately; injected context waits in the inbox until another message does." — DeepSeek Harness architecture documentation, docs/architecture.md

Until that other message arrives, nobody is home. The chain of who-wakes-your-agent ends at a clock, a webhook, or you. dsh ships a scheduler that wakes on timers; this plugin is its evidence-fired sibling.

Status: v0.1.0, pre-release. The constitution below is implemented and tested. The wake and counsel verbs are gated behind a runtime capability check and are not yet verified against a pinned harness build — see Verify before live. Run it in shadow.


Install

npm i dsh-first-hop
# cordis.yml
plugins:
  dsh-first-hop:
    judge:
      tier: floor            # floor | local | mock

Then opt in. Nothing happens until you do:

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

Read the tape for a week before you consider live.

The switch

~/.dsh/first-hop/first-hop.state   →   off | shadow | live

The state is a file only you write. No code path in this package writes it — not to create a default, not to recover from a bad value, not at any version. An absent or unrecognized file reads as off.

PositionWhat happens
off (also: no file)Nothing. No file created, no percept read, no judge consulted, no pill written. Off means absent.
shadowWatches, judges, and records everything — and consumes no verdict. This is where trust is earned.
liveAdditionally resolves verdicts into harness acts, within the limits below.

Three files appear beside the switch once you are awake:

  • spool.tsv — every percept, in lane-contract v0.1 frames
  • first-hop.tape.jsonl — every judgment, including every hold
  • first-hop.heartbeat.json — the pill; older than three beats means stalled

None of them ever leaves your machine.

The two tiers

Tier 0 — the floor. Deterministic rules that need no model and no tokens: CI state, test results, non-zero exits. Ships enabled. It is also the null — the baseline the model tier has to beat before anyone should run it.

Tier 1 — the 9B judge. For what no rule can name. Any OpenAI-compatible local endpoint: llama.cpp server, Ollama, LM Studio, vLLM. One bounded request per boundary, strict-JSON answer, closed vocabulary.

plugins:
  dsh-first-hop:
    judge:
      tier: local
      endpoint: http://127.0.0.1:8080/v1
      model: qwen3-9b-instruct-q5_k_m
    budgets:
      wake: 2                # per condition class, per hour
      flag: 6
    verbs:
      wake: true
      counsel: false         # off by default, deliberately
    floor:
      builtins: true
      rules:
        - id: deploy-failed
          pattern: 'deploy (failed|rejected)'
          flags: i
          action: wake
          reason: a deployment reported failure
      retired: []

Every deployment-varying value is a validated config field, and a malformed composition fails at load rather than at the first boundary. The full surface is src/config.ts.

What this is not

  • 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. It is an analog of that mechanism, and no number it produces should be quoted as if it were that mechanism's.
  • The judge is prompted, not tuned. A stock 9B can flood or go deaf. That is precisely 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 that reach a model, both landing inside your own session. It cannot touch the world, hold credentials, or approve anything.
  • Not a telemetry client. No server, no account, no beacon. The tape stays on your disk.
  • The judge reads everything. To judge your sessions it must see them — every message, every tool result. That is exactly why the endpoint should be yours. Point it at a hosted API and you have handed your sessions to a vendor and reintroduced the request boundary this plugin exists to close. It will let you.

The nine laws

Off is inert · holds are on the tape · see it once · budgets at condition grain · self-echo suppression · shadow before live · percepts are never dropped · model-visible ⟺ logged · lane text is data, never instructions.

Each one is a test, a file permission, or a structural property — with the file named. See LAWS.md.

L9 is the one the other eight exist to protect. The streams this plugin watches are attacker-reachable, so nothing a judge returns is trusted: every verdict is re-derived into a closed vocabulary (four literal actions, a re-slugged condition id, a control-stripped and capped reason), and every other field a model invents is dropped. A poisoned log can argue for a wake; it cannot become one.

Verify before live

wake and counsel construct a harness message and hand it to the agent's whenIdlerunMaintenancefollowup sequence. Those three methods were read from packages/core/agent/src/runtime-types.ts, but the message shape this package builds has not been verified against a pinned harness build, so dispatch() performs a runtime capability check and refuses cleanly rather than guessing. Until you have verified it against your own pin:

  • run in shadow — everything is recorded, nothing is consumed
  • see the FIXME(pin) in src/index.ts for the one function to check

This is the honest state of a pre-release plugin against a pre-release harness (SESSION_FORMAT_VERSION: 0, compatibility breaks expected). Pin your harness, and CI against the pin.

Architecture

src/core/      the constitution — pure Node, zero harness imports
  state.ts       the control file (the reader is total; nothing writes it)
  spool.ts       lane-contract v0.1 frames; the buffer that makes L1 true
  tape.ts        append-only JSONL, holds included
  ledger.ts      see-it-once + budgets at condition grain
  verdict.ts     the closed vocabulary — where L9 is enforced
  judge/         floor (deterministic) · local (9B) · mock (for CI)
src/plugin/    the harness-facing edge
  tap.ts         events → percepts, structurally typed, pure
  verbs.ts       verdict → act, behind a runtime capability check
  pill.ts        the heartbeat

src/core imports nothing from the harness, which is why the whole constitution is testable in CI with no harness, no key, and no GPU — and why another venue could reuse it to be judged by the same rules over a different stream.

Note on dependencies: this package deliberately does not depend on @deepseek-ai/dsh-session. That package is published but currently uninstallable — it depends on @deepseek-ai/dsh-type-meta, which is not on the registry. Harness event shapes are therefore read structurally, which is also the looser coupling.

Development

pnpm install
pnpm run typecheck
pnpm test          # 48 tests, no GPU, no key, no harness
pnpm run build

Roadmap

  • v0.2 — verified live dispatch against a pinned harness; the first-hop CLI (status, tape, retire, resolve).
  • v0.3first-hop bench: replay your own session logs, retro-label the boundaries, and race the judge against the best swept polling policy at every token budget, both grains printed. If the judge does not dominate your polling frontier, the bench says so, and the advice is: keep the floor, skip the judge. A tool that cannot lose cannot be trusted to win.
  • v0.4 — more lanes (filesystem, CI); the lane-contract socket, so a native resident kernel can take the judge's chair without the plugin changing.

Lineage

The first hop of the FUSOR architecture, in portable form. FUSOR is a substrate for minds that do not take turns; this is the smallest useful piece of that idea that runs on someone else's harness, on your hardware, under your switch.

License

MIT