akslcw/dsh-negative-ledger
Evidence-bound negative-knowledge ledger: persists disproven paths (command_failed, file_missing) with their outcome and precondition evidence, then warns or blocks repeat attempts until the evidence changes.
Listed
3
Memory
Bundle verified
Preview
What it does
Evidence-bound negative-knowledge ledger: persists disproven paths (command_failed, file_missing) with their outcome and precondition evidence, then warns or blocks repeat attempts until the evidence changes.
Best for
- Multi-agent or long-running DSH workflows where failed commands or missing files may otherwise be retried across sessions.
- Teams that want repeat attempts warned or blocked only while the recorded environmental evidence remains unchanged.
- Workflows that need a persistent, inspectable record of disproven paths rather than general-purpose memory.
Not ideal for
- Semantic deduplication of differently worded but equivalent commands; matching uses exact fingerprints.
- Positive knowledge storage, semantic recall, caching tool results, or bug-regression tracking.
- Concurrent multi-process use with the legacy JSONL backend, which supports only a single writer.
- Workflows where non-zero exits commonly have non-failure meanings, since per-tool recording policies are not yet implemented.
README
dsh-negative-ledger
A negative-knowledge ledger for coding agents. It records only disproven paths — failed commands, missing files, rejected approaches, unavailable APIs — together with the evidence behind each conclusion and the conditions under which a retry becomes legitimate. When the evidence changes, the conclusion is invalidated automatically.
What it is not
- Not memory: no positive knowledge, no semantic recall.
- Not a cache: it stores conclusions, not tool results.
- Not a bug regression tracker: it covers any tool call and file read, not just fix attempts.
The core loop
- A tool call fails (non-zero exit,
FS_NOT_FOUND, …) → a negative fact is recorded with outcome witnesses (exit code, error code) and precondition witnesses (file state from DSH’sfs/observed). - The next identical attempt matches the fact’s fingerprint (normalized command + cwd, or file path).
- While every precondition witness is unchanged, the attempt is warned (
warnmode) or denied (blockmode). - Any precondition change marks the fact stale — the reminder is withdrawn and the retry is allowed. A successful retry marks it resolved.
The differentiation: a DSH-native, evidence-bound persistent negative-memory gate — failure conclusions activate and revoke themselves with the environmental evidence, and stay transactionally consistent across concurrent agents.
Quick start — one-command install
dsh plugin --profile <name> add @akslcw/dsh-negative-ledger
Installs the package and activates its bundle layer: the shipped cordis.patch.yml (declared by the dsh.bundle manifest) mounts the ledger policy with production defaults — sqlite backend, warn mode, .ledger directory, default TTLs. Verify without booting, then boot:
dsh --profile <name> --dump-config # the "@akslcw/dsh-negative-ledger" layer and its negative-ledger row
dsh --profile <name> # boot
Remove: dsh plugin --profile <name> remove @akslcw/dsh-negative-ledger. A clean-environment end-to-end smoke (add → layer → headless warn + sqlite ledger → remove → profile still boots) is powershell -File smoke/plugin-add-smoke.ps1.
pnpm 11 note: pnpm ≥11 turns “ignored build scripts” into a hard error and fails the
addwithERR_PNPM_IGNORED_BUILDS: better-sqlite3. better-sqlite3 ships official prebuilds, so the ignored script is harmless — no compilation happens. In the profile directory runpnpm config set --location project strict-dep-builds false, then re-run theadd. (Allowing the build instead would compile better-sqlite3 from source and require a C++ toolchain.)
In-checkout hacking (engine and CLI only, no DSH composition):
node src/cli.ts --dir .ledger stats
node demos/run-demos.ts # S1 command dedup, S2 missing-file dedup, S3 evidence-change invalidation
node smoke/real-mount.ts # real-mount smoke inside a deepseek-harness checkout
Requires Node ^22.19.0 || >=24.0.0 (aligned with the official DSH engines range).
CLI
node src/cli.ts [--dir <path>] [--backend sqlite|jsonl] <list | show <id> | stale | stats>
The backend flag wins; otherwise the directory is auto-detected (ledger.db → sqlite, ledger.jsonl → jsonl); with neither present the primary sqlite backend is used.
| Command | Output |
|---|---|
list |
Every fact: status, kind, id, claim |
show <id> |
One fact as pretty JSON |
stale |
Facts invalidated by evidence change |
stats |
Honest interception counters (duplicate failures observed, warnings emitted, calls denied) |
Engine API
Two store backends sit behind one LedgerStore seam: the default transactional SQLite store (SqliteLedgerStore: WAL, revision-based optimistic concurrency, operation receipts, retry leases, JSONL import) and the legacy single-process JSONL store (JsonlLedgerStore).
-
getFact(scope, kind, fingerprint)/queryFacts(filter)— current facts with revision and active-lease summaries. -
commitAttemptDecision(request)— the only decision entry:deny/observe-warn/verify-retry(allow and stale-allow both compete for a lease); revision conflicts re-read and re-decide. -
recordFact(input, meta)— records a disproven path; repeats append versions on the same id; idempotent by operation receipt and(fact, toolCallId, operation_kind). -
transitionFacts(batch, meta)— batched, all-or-nothing state transitions (one FS observation can invalidate many facts). -
settleLease(settlement)— the lease holder’s retry outcome: succeeded → resolved, failed → new evidence version, released → fact untouched. -
summarize(scope?)— three honest counters:duplicateFailuresObserved,warningsEmitted,callsDenied. No token estimates — trajectory replay/A-B diffing owns that number.
DSH integration
The bundle layer shipped in the package is exactly:
- id: negative-ledger
name: '@akslcw/dsh-negative-ledger'
Override the row by id in a later layer (your profile’s cordis.patch.yml) — a patch replaces the whole config, so restate every key you change:
- id: negative-ledger
name: '@akslcw/dsh-negative-ledger'
config:
backend: sqlite # sqlite (default, transactional) | jsonl (legacy single-process)
mode: block # off | warn | block (default warn)
dir: .ledger # ledger directory (default .ledger)
commandRetryAfterMs: 300000 # TTL on auto-recorded command facts
commandTools: [bash, pwsh] # recorded as command_failed
readTools: [read] # recorded as file_missing
-
The store connection and the background invalidation queue are owned by the plugin fiber: disposal drains the queue and closes the store (HMR-safe).
-
warn(default): attachesadditionalContextsontools/post-execute; never blocks, never rewrites tool results. -
block: denies attools/pre-executebefore dispatch. Denied calls still flow through post-execute and are recognized by the plugin’s own denial prefix, so one attempt is never double-counted. - Auto-recorded command facts carry a short
afterTTL (commandRetryAfterMs, default 5 minutes):blockmode releases them automatically instead of locking a command forever on transient failures.never/manualare reserved for facts an explicit, trusted author recorded. -
off: disables recording and interception entirely. -
fs/observedevents (presentwith version, orabsent) map one-to-one ontofile-stateprecondition witnesses; the emitting execution is correlated so a model-supplied path (scoped by the session cwd) and the backend’s resolveddisplayPathwitness the same fact; every observation change drives invalidation, so file hashing is never needed. - Successful tool results resolve the fact through settlement or a lease-free transition — the reminder is withdrawn after a working retry.
- The ledger is shared across agents (subagents do not repeat the parent’s failures); counters are transactional columns (sqlite) or append-only hit lines (jsonl).
Security posture:
- Claims never embed raw command text; model-facing previews are control-character-sanitized and length-capped. Raw commands stay in the ledger FILE (they are the fingerprint) — the file is written 0600 inside a 0700 directory.
- The ledger renders facts as quoted data, never as instructions.
- Single-writer JSONL applies to the legacy backend only; the sqlite backend is multi-process (WAL).
Boundary with repeat-tool-reminder: that guard nudges on byte-identical consecutive repeats within one session; the ledger is persistent, evidence-bound, and auto-invalidating across sessions.
Known limitations and deferred work
-
Single-writer JSONL (legacy):
backend: jsonlkeeps the v0 single-process store for migrations and debugging; concurrent multi-process writers are unsupported there. The defaultbackend: sqliteis the transactional WAL store with unique indexes, idempotent operation receipts, and crash recovery. - Command fingerprints use the calling agent’s session cwd; a sandbox-policy workspace-root override is not visible to the plugin. Raw command text is preserved (no whitespace collapsing) so semantically different shell programs never collide, but equivalent re-spellings do.
- A non-zero exit does not always disprove a path (e.g.
grepexits 1 for “no match”); the short TTL and the warn-default posture bound the damage, but per-tool recording policy is deferred. - v0 matches exact fingerprints only; no semantic similarity.
-
approach_rejectedandapi_unavailablekinds exist in the model but are not wired to tools yet. - Token savings are deliberately not estimated here; the trajectory lab (#4) owns A/B and replay diffs.
- Future seams: failed_attempts in subagent contract results (#1), active/stale projection into task checkpoints (#3), repeat-failure rates in trajectory regression (#4), fail-closed promotion of high-risk paths (#2).
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:akslcw/dsh-negative-ledger in a DSH-enabled shell. The command resolves the public package metadata and keeps the plugin attached to the catalog identity shown on this page.
Compatibility follows the bundle and profile status shown above. If a profile is not detected, keep the plugin disabled there and check the repository documentation before enabling it in production.
The GitHub link and activity metadata are the source of truth for releases and maintenance. Revisit this page after a new release to confirm the catalog has observed the latest version.