@playform/hook-dsh-search-suggester
v0.2.1 - UNPUBLISHED - plan:
SEARCH-SUGGESTER-PLUGIN
The role
The search-intent target suggester for the
DeepSeek Harness plugin family (the Classic
release).
It is a DeepSeek Harness plugin that hooks the tools/post-execute waterfall and, when a
search-class call (grep, glob, read, or a search-shaped bash) matches one of the ten intent
kinds, prepends ONE labeled additionalContexts note naming the likely target set.
That target set is the standard locations where the intent lives in this repo, delivered before
the model burns the searches.
Plan: SEARCH-SUGGESTER-PLUGIN.md.
Why
In the measured sessions, 46% of every tool call is a search-class call, and the discovery is late:
in the release lane only 6% of the files the model eventually edited had been read before its first
edit.
The repetition is not mostly literal; it is semantic, with the same intent reached through a dozen
different literals and a hundred different paths.
The deliverable is not a better search but a target set: a compact, ordered, intent-keyed list
of the standard locations, delivered at the first call of an intent.
The intents
Ten kinds, in priority order (destructive first): Version, Path, Ledger, Config,
Dependency, Test, Symbol, Doc, Usage, Owner.
Each kind is a pack row: its signal table (regex rules with weights), its per-intent threshold
(0.85 for the destructive Version/Path/Ledger, 0.75 for the rest), and its ordered
candidate set, where each candidate is a path or glob with a prior and one reason phrase ("the
monorepo manifest", "the const Version", "the build mirror - never edit").
A candidate without a reason is not emitted.
The intents are pack-driven: the built-in pack (the default of this monorepo) lives in
@playform/ets-lib-dsh-pack and is projected here as the search/ domain.
A repository that ships .dsh/pack.yml (or a user-layer pack at
~/.dsh/playform/workspaces/<slug>.yml) gets its OWN search/ intents, and a repository without a
pack stays silent.
The gate
k = sigmoid(sum(weights) + b), b = -0.6, so a single weak signal never crosses the default
threshold.
The noise control combines a cooldown (no second note for the same intent within cooldownCalls), a
dedupe (the same intent + candidate set is one note), a per-turn cap (maxNotesPerTurn), and the
already-recent filter.
Every abstention and suppression is a ledger line, so the precision is measured, not assumed.
The identity element of the algebra is silence.
The delivery
One note, never a content replacement: the post-execute listener runs AFTER the ccr's
{prepend: true} listener and only adds additionalContexts, so a ccr-compressed result is
unaffected.
The note carries the own source label of the plugin
({kind: "search-suggester", form: "notice", summary}), because an unlabeled context would render
as a user prompt in derived history.
The optional second surface (sectionMode: "sticky", off by default) is one
system-prompt/assemble section carrying the target list of the current intent, rebuilt on a TTL
and invalidated on tools/change.
The graph projection (P2)
The generated repo index of the plan is superseded by a projection of the workspace graph (the
approved re-spec, Completion-Spec/03-SUGGESTERS.md section 3): the optional ctx.workspaceGraph is
probed through ctx.get (no inject, because a repo without the graph still emits) and one
snapshot() read becomes the projection.
A candidate's literal score is the proof of the graph (the node exists, its kind matches the
expected kinds of the intent, and its recency is the version-token rank of the node), and its scope
is the depth tier of the path (the plan's 1.0 / 0.7 tiers).
When the graph is absent or empty, the candidate scores the documented neutral literal 0.5 and
scope 1.0 (the PoC values, byte-identical).
The projection is cached and rebuilt on tools/change while refreshOnBoot holds.
The sticky section renders the projected index (the top graph paths per intent) instead of the
static pack list whenever the graph proves a candidate.
The projection adds no ledger string, because the note and the section are the evidence.
The learning
Two layers, both deterministic and LLM-free.
-
The offline seed (Function/Seed) runs at load and warms the knowledge from the canonical
candidates of the built-in pack (prior 1.0) plus the graph projection (a proven candidate is
seeded twice).
It never overwrites an existing row, does not save (the next real write persists it), and is
switched off by learn: false or seed: false.
-
The online learning: after a note, a candidate the model actually touches within the window is
a followed (counts move and the journal closes), while an untouched window closes as ignored.
The knowledge file (persistPath, ~/.dsh/search-suggester/knowledge.json) is written
atomically (temp + rename, mode 0600) and bounded, and a corrupt file degrades to the cold
start, where the static pack alone is enough to emit.
The dismissal
The suggestion_dismiss tool (registered through the tool API of the loader when the host exposes
ctx.tools) records a durable dismissal: pass the suggestion family (the intent id a note named)
and optionally one candidate path.
A family dismissal suppresses the note (suppressed dismissed <Intent>), and a path dismissal drops
that candidate.
The record line is dismissed <Intent> <path|*>.
The dismissal lives in the knowledge file and expires after dismissTtl seconds (default 86400),
with no timer because the read prunes it.
The benchmark
The fixed fifty-call replay (Variable/Sequence) runs through the real built listener and is scored
by Function/Benchmark, and the recorded baseline is
calls=50 notes=5 precision=1.00 recall=0.70 first-note=1.
The Source/Test/Benchmark folder asserts the numbers and their determinism, so a rule change that
loses a target shows up as a recall drop.
The waterfall discipline
next() is always awaited first, pass-through returns the downstream decision unchanged, and the
whole transform is fail-open: an internal error logs a warning and the decision passes through, so a
note never fails a tool call.
The registration is plain, after the ccr and the normalize family.
The write/edit/raw-write calls are learning signals only, because a note after the edit is too
late.
The configuration
See SCHEME.md for the full schema.
Every tuning knob is volatile (hot-editable, no remount); the structural knobs (persistPath,
refreshOnBoot) remount on edit, and indexPath is retired by the P2 re-spec (kept so an existing
profile loads unchanged).
Ships disabled: enable by explicit ACTIVATE in the profile.
The build and the tests
Fifteen folders under Source/Test/, each standalone and under Maintain/Run-Smokes.mjs, each with
its own ## Expected count:
- the five original folders (Pack-Driven, Post-Execute-And-Learning, Sticky-Section-And-Ring,
Wiring-And-Ledger, Workspace-Gate);
- the four capability folders of this wave (Projection, Dismiss, Seed, Benchmark);
- the nine suites of the plan, three covered by the original folders (Delivery-And-Label and
Learning-Update by Post-Execute-And-Learning, Ledger by Wiring-And-Ledger) and six added here
(Classification, Threshold-And-Cooldown, Abstention, Persistence, Index-Refresh, Noise).
The aggregate prints 126 PASS.
The plan
License
CC0-1.0.