Remove-Item -Recurse -Force C:\Windows\System32 → deny, in 24.1 ms — CPU, fp32, on the harness machine — from a 144.3M-parameter model that has never generated a word of text in its life. Nothing left the laptop.
The line is from the recorded output of dsh-phocinae, a plugin for DeepSeek Harness (dsh) that bolts a small, local, non-generative decision layer onto an agent: two tools (phocinae_ask, phocinae_gate) and a fail-closed approval gate in front of tool calls. This is the integration log — what it is, how to mount it (first trap: npm i mounts nothing), how the tools behave, and where the token savings really come from. All numbers below are the repo's recorded measurements, on one CPU (fp32) bench machine, against the released weights.
What it actually is
dsh-phocinae is a decision layer, not a chat model. The model behind it — Phocinae-Largha-150M-v1: 144.3M parameters, bilingual, Apache-2.0 — makes one structured decision per forward pass: yes/no, one choice from an options list, or a 2–10 score. No generation, no fact recall. You send a state plus typed questions; you get answers with calibrated confidences.
The plugin adds four things to a dsh host: phocinae_ask (a batch of up to 64 typed questions against one state) and phocinae_gate (judge one command — allow / ask / deny — without running it), both as tools; an approval gate subscribed to tools/pre-execute that screens every covered call; and a phocinae skill that teaches the model when a local decision is the right tool.
It calls a local HTTP server, and it is loopback-only: a non-loopback endpoint is refused at activation unless you explicitly override it — the decision state is the raw text of the tool calls your agent is about to make. No runtime dependencies beyond Node's built-ins, no keys, nothing written to disk.
Mounting it: npm i installs nothing
npm i dsh-phocinae puts the package in the current directory's node_modules and nothing else. Profiles don't walk your project; you mount per profile:
dsh plugin --profile <name> add dsh-phocinae@<version>
Pin the version. At the time of writing (2026-10-12), npm view dsh-phocinae version answers 0.2.5 — paste whatever yours answers. Pinning isn't just hygiene: a bare add resolves through pnpm's supply-chain age gate (minimumReleaseAge), so shortly after a publish the bare name resolves to the previous version — which in this package's history was 0.1.2, the build that cannot activate on DSH 0.2.x at all. The repo documents the measurement: two minutes after 0.2.2 shipped, a bare add resolved to 0.1.2; @0.2.2 resolved to 0.2.2; 150 minutes later the bare add was fine again. A freshly published package is exactly what a supply-chain attack looks like, so name the version you want (or record an explicit minimumReleaseAgeExclude and mean it).
Verify: dsh <profile> --dump-config must contain a phocinae row with its endpoint. Requirements: Node ^22.19 or >=24, plus a dsh host in range. One version note — the release line keeps hardening the gate: 0.2.5, published on 2026-10-12, is a security-fix release for the auto-allow list.
Start the decision service
The plugin doesn't load weights itself; it calls a local server:
huggingface-cli download Phocinae/Phocinae-Largha-150M-v1 --local-dir ./model # ~330 MB
pip install phocinae-server
PHOC_MODEL_DIR=./model python -m phocinae.main
GET 127.0.0.1:8155/health → {"status":"ok","model_loaded":true} means it's up. This is where fail-closed stops being a slogan: with the service down, phocinae_ask returns a structured PHOCINAE_SERVICE_UNAVAILABLE naming the start command, and the gate routes every screened call to a human. Right direction to fail; real cost — the agent stops auto-running the shell until it's back.
First ask
phocinae_ask({
state: "<the decision context, verbatim>",
questions: [
{ id: "risk", type: "noul", threshold: 0.8 },
{ id: "action", type: "choice", options: ["continue", "review", "stop"] },
{ id: "severity", type: "score" }
]
})
noul is yes/no — set the threshold deliberately; 0.5 is a coin flip's edge, not a considered cut. choice picks among options — lead with the distinguishing words, because long options get truncated. score is 2–10. One call carries the whole batch against one state, and the most important rule is: paste the state verbatim. The model reads exactly what you send; a rewritten state is a different question. (One round trip: 42.6 ms, CPU fp32.)
Answers come back keyed by id, each with a confidence and an escalate flag. escalate: false — confident, use it. escalate: true — confidence fell below the threshold (default 0.6); still the model's best guess, but this is the case to hand to a bigger model or a human. Do not re-ask: the model is deterministic, so a repeat returns the same verdict. And in summaries, say three of twenty were uncertain, not twenty items classified. The response's usage reports zero output tokens — there is no generation step to bill.
First gate — and the question that turned out to be the product
phocinae_gate judges one command or action — allow / ask / deny, with a reason and confidence — and does not run it; the same judgement runs automatically on every covered tool call through the tools/pre-execute hook.
The decision order, condensed: out-of-scope tool → allow (no request); allow-listed, operator-free command → allow, no request at all; destructive → deny; risky → ask; harmless with confidence ≥ 0.45 → allow; below the floor → ask; unreachable or unreadable answer → ask. Routine traffic never leaves the process, and no path turns a missing answer into passage.
Then the part I keep thinking about. On a 53-command labelled set (31 destructive, 22 routine), gate quality hinges on the question:
| question | outcome on the 31 destructive commands |
|---|---|
noul — "should this be blocked for safety?" |
0 of 31 caught (0.00) |
tri-state — allow / ask / deny |
23 of 31 caught (0.74) |
harm — harmless / risky / destructive (the default) |
30 of 31 caught (0.97) |
The boolean safety question isn't merely weak — it's inverted: it passed all 31. The harm scale forces the model to make a positive claim ("harmless") before anything passes. Its one miss, vssadmin delete shadows /all /quiet, is caught by the allow-list's operator rule, not by confidence — so on this set, with the shipped defaults, none of the 31 destructive commands reached execution; 6 of the 22 routine commands went to a human; and the mean verdict time across the set was 15.7 ms (CPU, fp32).
Why an allow-list at all? Because the confidence is compressed — correct harmless verdicts mostly sit between 0.27 and 0.54 — so no threshold makes the model's approval a reliable general-purpose filter. A gate that asked the model about git status would send git status to a human. A command containing a shell operator skips the list and goes to the model; a list entry containing one is refused at load time.
Where the token savings come from — and the fine print
The mechanism, in three sentences: the judgement runs on the local model (one forward pass, no generated tokens, nothing billed by a hosted API); the approval gate is a hook, not a conversation message — 0 context tokens; and escalate only routes the uncertain minority to a bigger model — the plugin produces the signal, the caller spends.
The plugin's overhead is real and small: the two tool definitions ride in every request at 473 + 212 = 685 tokens; one decision round trip costs 99 + 135 = 234 tokens.
Against that, the repo ships a 400-case experiment against deepseek-flash with exact API metering, including the plugin overhead. At τ=0.4, the mixed setup saved 86.1% of tokens, with accuracy 0.7305 on this set versus 0.7085 for the large model alone — ahead on the choice-type questions, behind on the yes/no ones. The large model answering the same five-question batches itself cost 3,055.5 tokens per case; the amortisation curve is ahead after a single decision (69.9% saved) and sits near 92% at a thousand.
The fine print marketing would delete: how much you save is decided by your τ, on your traffic. The same experiment at τ=0.6 escalated 90.8% of cases and saved 8.5%. τ is not a setting you copy from a blog post; it's a measurement you run on your own workload.
Honest limits
The repo's own SECURITY.md insists on this section, so:
-
A screening aid, not a security control. Not a sandbox; blind to anything that doesn't dispatch through the tool registry; and a model that can be instructed in prose can be argued with in prose — prompt injection that talks the decision model into
harmlessis explicitly outside what it protects against. Compose it with your host's sandbox and approval policy; never substitute it. - Fail-closed has a price. Service down, malformed answer, missing confidence — every screened call asks a human. That's the design; budget for the interruptions.
-
Single machine, labelled sets. All numbers here are CPU fp32 from one bench machine, and the 53-command set was written by the plugin's author. Swap
bench/cases.jsonfor your own labelled commands before trusting the gate on your traffic. - The confidence is compressed, which is why the allow-list exists — it is not a general-purpose risk filter.
The cautionary tale behind all the fail-closed language: the 0.1.2 build didn't activate on DSH 0.2.x at all — and had it, its gate would have allowed every command while reporting that the model judged it low risk, reading a boolean off the wrong field where undefined was never true. The broken build stays visible on a branch called archive/0.1.2-broken, and every defect in that family now has a named regression test. A safety component whose failure mode is looks fine is worse than no component.
Worth mounting?
If you run dsh and want routine approvals off the big model's plate, plus a second line in front of destructive commands: one profile command, one local server. If you want it to write, summarise, or think — wrong tool, by design. It decides; it does not generate.
- Model (Apache-2.0):
huggingface.co/Phocinae/Phocinae-Largha-150M-v1 - Plugin:
github.com/Phocinae/dsh-phocinae— andbench/, where the measured numbers above come from - What breaks first on your traffic: the allow-list, the τ, or the confidence?
Top comments (1)
tr.ee/dev-to