GrayPrint · reasoning-style fingerprints for dsh
中文 | English

A DeepSeek Harness (dsh) web plugin that starts at the bottom-right of the chat page with two independent live fingerprints: (1) whether the current reasoning text resembles the grayscale or current-release corpus, and (2) whether paragraph openers inside reasoning blocks lean toward Let me exploration or We need planning. Both describe writing form; neither proves model identity or capability.
| Pole | Style | Evidence |
|---|---|---|
| 🟢 Grayscale fingerprint | First-person narrative: I'm planning out… I'll set up… I've got… | 41 dsv4-grayscale opencode sessions shared on opncd.ai — 1,749 raw reasoning blocks / 7,870 internal paragraphs |
| 🔵 Current-release fingerprint | Collective-person shorthand: We need answer likely… Let's inspect…, not full sentences | 41 current-release DSH sessions on the standard preset — 3,021 raw reasoning blocks / 53,994 internal paragraphs |
Why these two poles
This started as a check on whether NoLetMe's criterion holds on this corpus. It does not — the polarity is inverted: total let me matches are not stable enough to enter the grayscale score. Only Let me / We need at the start of an internal paragraph feed a separate organization-style tendency. GrayPrint v0.4 uses one consistent unit everywhere: split each reasoning block into its actual Markdown paragraphs, then inspect only the start of each paragraph.
A correction that mattered
v0.1's control group was wrong. Its "current" pole came from the anchored-standard preset — a preset explicitly designed to anchor the model back onto the minimal telegraphic trajectory. Using it as the representative of the current release baked the conclusion into the premise. Re-testing across 169 real sessions stratified by preset showed that the same current model swings from 10% to 86% purely by changing preset, and 8 current-release sessions were misclassified as grayscale.
With standard as the control, and with each reasoning block split into internal paragraphs, the real separation is:
| Axis (per internal paragraph) | Grayscale | Current standard | Single-axis accuracy | Weight |
|---|---|---|---|---|
First-person openers (I'm / I'll / I've / I) | 22.15% | 3.76% | 93.9% | 49% |
Paragraphs opening with I'm / I'll | 14.94% | 1.28% | 96.3% | 51% |
Paragraphs opening with we / let's (count only) | 0.0% | 1.13% | 54.9% (chance) | — |
Paragraphs opening with We / Let's / Need (count only) | 0.0% | 1.35% | 54.9% (chance) | — |
| Mean of session-median paragraph lengths (display only) | 342 chars | 198 chars | 95.1% | — |
Both scored axes are paragraph-opening rates: each internal paragraph can contribute at most once, and repeated words later in the same paragraph are not accumulated. The we / let's weak axis is near chance, so its progress, gray-direction rating, and weight have been removed; only its paragraph-opener count remains. The broader We / Let's / Need opener count and paragraph length are display-only. Let me is counted only as an opener for the independent organization fingerprint.
Paragraph unit
reasoning block is a transport container, not a statistical paragraph. The plugin normalizes CRLF to LF, splits each non-empty block on one or more blank lines, trims the parts, and discards empties. The raw block count remains visible as a separate detail. The two grayscale axes, the Let me ↔ We need fingerprint, the style-signal guard, and sample gates all use paragraph openers. Runtime no longer collects or displays whole-text word frequencies; Raw statistics contains opener counts and non-frequency metadata only.
The classifier
Two weighted paragraph-opening axes (the broad first-person axis is linear; the low-frequency I'm / I'll opener axis uses log10(x+0.01) to spread the low end, with each axis clamped to [0,1] between the two poles). The detail label “Relative axis position” is the normalized location between the current-release and grayscale references—not an opener rate or model probability; the actual rate appears separately under “This session.” Weights are normalized at runtime. Score ≥55% → Grayscale fingerprint, ≤25% → Current-release fingerprint, in between → Mixed.
Asymmetric evidence gate: a grayscale verdict additionally requires ≥48 internal paragraphs. Grayscale is the surprising claim, and paragraph-opening ratios are unstable on small samples. Below 48 paragraphs the panel reports "leaning grayscale, sample small". Ordinary samples below 5 internal paragraphs receive no verdict.
Paragraph-opening style tendency (independent, v0.4)
The second progress bar is independent of the grayscale score and counts openers of internal paragraphs inside reasoning blocks:
Let me…/Now, let me…→ exploratory poleWe need…/Now, we need…→ planning pole
Progress is We need openers / (Let me openers + We need openers). Fewer than three matching paragraphs produces “not enough fingerprint evidence” instead of an artificial 50%. The panel labels the chips and Let me opener rate explicitly; raw block counts remain separate. This helps distinguish organization styles inside the current release, but does not claim that wording alone causes quality.
Validation
Holdout accuracy 94.2% (200 random 70/30 splits, threshold re-chosen inside each training fold; each split first segments internal paragraphs and then extracts opener-only features). That is the honest figure — the 96.3% claimed by v0.1 was measured on the same data used to design the thresholds.
Shipped code against the full corpora:
| Corpus | n | Grayscale | Mixed | Current | Guard-refused |
|---|---|---|---|---|---|
| Grayscale opencode (41 sessions / 1,749 raw blocks / 7,870 paragraphs) | 41 | 38 | 1 | 1 | 1 |
Current standard (41 sessions / 3,021 raw blocks / 53,994 paragraphs) | 41 | 1 | 4 | 29 | 7 |
| Current, other presets (out of calibration, 49 sessions) | 49 | 0 | 2 | 41 | 6 |
These figures come from the same backup corpus snapshot. Raw reasoning blocks and internal paragraphs are separate units and should not be read as the same sample size.
Score quantiles: grayscale median 96% (p25 79%); current standard median 5% (p75 14%); current other presets median 0%. Pybm06QA is the lowest-scoring grayscale sample (13%, Current), while 6AWmBTvh has a high score but only 47 internal paragraphs, so the asymmetric gate reports “leaning grayscale, sample small”. One current standard session lands in the grayscale band, reinforcing that this is a style similarity—not model identification.
Six guards (refusing to fake a reading)
1. Chinese reasoning — refuses a verdict above 15% CJK. 2. Other non-English text — refuses when Latin letters are below 40% of all letters, preventing Japanese, Korean, Russian, etc. from falling into the current-release band. 3. Weak English style evidence — even Latin-script text is refused when I'm / I'll / we / let's signals are too sparse, preventing French or Spanish from receiving a false current-release verdict. 4. Too few paragraphs — no verdict below 5 internal reasoning paragraphs. 5. Asymmetric gate — a grayscale verdict needs ≥48 internal paragraphs. 6. No reasoning at all — reports the anomaly instead of inventing a trajectory from visible reply text.
Honest limits
- The two poles come from different rendering channels: grayscale from opencode share pages (narrative presentation), current from DSH's native
reasoningblocks. Part of the gap may be the channel rather than the model version — this has not been ruled out. Ruling it out needs sessions of the current model running through opencode; the archive is grayscale-era and has none. - The grayscale side has positives only (community-curated before publishing — survivorship bias). Neither side carries task scores or a rubric.
- Preset matters more than version: the same model spans 10%–86% across presets, so a grayscale reading cannot infer a model version.
- Therefore this panel measures the narrative person of reasoning, not capability, backend, routing, or checkpoint identity.
Install
Prerequisite: dsh CLI ≥ 0.1.0-rc.7, with a target profile.
From GitHub (recommended) — no build permission needed. lib/ is hand-written and committed; there is no prepare script, so pnpm has nothing to approve:
dsh plugin --profile web add github:HongzhongL/dsh-grayprintPin a commit if you prefer: github:HongzhongL/dsh-grayprint#<sha>.
From a local tarball (built with npm pack, or downloaded from an existing release):
dsh plugin --profile web add ./dsh-grayprint-<version>.tgzFrom a local checkout:
git clone https://github.com/HongzhongL/dsh-grayprint.git
dsh plugin --profile web add ./dsh-grayprintRestart the dsh web host, then refresh the page. Removal: dsh plugin --profile web remove dsh-grayprint.
Usage
The panel starts bottom-right, and both the expanded card and collapsed chip are draggable. They share one user-position anchor; expansion is temporarily clamped inward so the full card remains visible. A short click on the sticky header collapses it, while a long press or drag does not misfire, and both directions are animated. The default view keeps only the grayscale-sample fingerprint and the independent Let me ↔ We need opener-style tendency; the two scored axes and raw opener counts live in a disclosure section. The UI follows dsh's Chinese/English locale, persists open and position state in localStorage, and always states that style similarity does not identify the actual model version.
Data scope and privacy
- Counts reasoning blocks only (
kind === 'reasoning') in the browser's current session snapshot. Visible reply text is used solely to diagnose the "no reasoning" anomaly and does not feed any style statistic. - Each reasoning block is split into internal Markdown paragraphs; classifier axes, organization fingerprints, style guards, and sample gates inspect paragraph starts only. Runtime retains no whole-text word frequencies; raw block counts, paragraph lengths, and opener counts are displayed separately.
- Per-block paragraph counts are cached in a
WeakMap, with the current text checked on every hit. If the host mutates an existing block object in place, GrayPrint recounts it instead of retaining stale paragraph totals and opener counts. - Nothing leaves your browser.
Architecture
lib/index.js # Node (host) half — no-op, satisfies the Loader
lib/client.js # browser bundle (hand-written closure factory, no build step)
# counting engine / classifier / guards / live session source / store / panel
evidence/profile.json # the calibration record
test.mjs # dependency-free engine, language-guard, dual-fingerprint, and disposal testsThe browser bundle is a window.__ModuleLoader__.load({id, factory}) closure-factory artifact; externals resolve through the injected require (this plugin only needs react). It mounts through the official shell.overlay slot and uses the official locale service. Session subscriptions, the stats store, and injected styles are tied to the plugin lifecycle and disposed on hot reload or uninstall. No existing UI is modified or patched.
Acknowledgements
The initial criterion came from NoLetMe (Yuer6327) and xiaobright/modeltest's trajectory research. The grayscale corpus comes from opncd.ai share links collected by YunhaoFu/dsv4ga-news-gather. GrayPrint reaches a different conclusion; the reasons are documented above.
License
[MIT](LICENSE)