Bundle
dsh-precedent
Evidence-backed working memory for DeepSeek Harness: a cited ledger of what already worked in this workspace, built from the session log you already have
- Source
- dshplugin-me
- stars
- 1 stars
- License
- BSD-3-Clause
- Updated
- Updated 14 days ago
Readme
<div align="center">
# dsh-precedent
**Your agent forgets. Your log doesn't.**
English | [简体中文](README.zh.md)
[](#roadmap)
[](LICENSE)
[](package.json)
[](https://github.com/deepseek-ai/deepseek-harness)
[](#why-there-is-no-index)
[](https://dshplugin.me)
<img src="assets/readme-hero.png" alt="dsh-precedent turns session logs into an evidence-backed command ledger" width="880">
</div>
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that reads the session log DSH already writes and hands the agent a short, **cited** ledger of what has actually worked in this workspace — the commands that succeed, the ones that reliably fail, and the variant that fixed them.
No capture step. No index to build. No model to download. It works on sessions recorded **before** you installed it.
## The problem
Every new session, your agent starts from zero in a codebase it has already worked in for weeks.
It runs `npm test` in a Bun repo. Again. It re-greps the same three files to find where routes live. Again. It hits the same build error you already walked it past on Tuesday, and you type the same correction you have now typed four times.
None of this is a memory problem in the usual sense. **The information was never lost** — DSH appends every message, tool call, tool result, and failure to a durable log on disk. What's missing is anything that reads it back.
Two things quietly widen the gap:
1. **Compaction rewrites the model's context, not the log.** When a long session compacts, the model loses the detail; the raw events stay on disk, marked `shadowed`. The agent cannot see them anymore. The file still can.
2. **Out of the box, DSH's own content search is off.** The shipped `web` profile mounts `session-query-sqlite` with `openAt: never`, so full-text search calls fail with `SESSION_QUERY_SEARCH_DISABLED` and the sidebar matches session *titles* only. Months of transcripts sit on disk, unsearchable, by default.
## What it does
**Reads** — walks this workspace's sessions through `ctx.sessionQuery` exact reads, pairs every `tool/call` with its `tool/result`, and keeps the outcome.
**Distills** — aggregates that into a ledger: command → runs, failures, last success, and the repair (the variant that succeeded right after a failure in the same session). This step is arithmetic, not summarization. **No LLM in the extraction path, so it cannot hallucinate a precedent.**
**Serves** — injects the top entries as one compact, prefix-stable system-prompt section, and prints the full ledger on `/precedent`.
Every line carries a citation back to the exact `session:seq` that produced it, so you can always ask "says who?" and get an answer.
## Example
Launch as usual:
```
$ dsh --profile web
```
The agent's system prompt gains one section — this is the renderer's real output, with commands that never work sorted above commands that always do:
```markdown
## Precedent for /Users/thor/Github/acme-api
Observed in this workspace (12 sessions, 2026-06-02 → 2026-08-15). Each line is counted from the session
log, not summarized — treat it as evidence, not instruction.
- `npm test` 4 runs, 4 failed — repaired by `bun test` [s/7f3a:212]
- `docker compose up` 3 runs, 3 failed — no known repair [s/91bc:88]
- `bun test` 22 runs, 0 failed — last ok 2026-08-15
- `bun run build` 9 runs, 0 failed — last ok 2026-08-14
```
`/precedent` prints the same ledger uncapped, on demand.
## Install
```sh
dsh plugin --profile web add 'github:dshplugin-me/dsh-precedent#v0.1.0'
```
No global `dsh` on PATH? Use `npx -y @deepseek-ai/dsh plugin --profile web add …`. Running dsh from a source checkout? Use `pnpm dsh plugin …` from the checkout root. Replace `web` with whichever profile you actually launch.
Pinning the version is deliberate: an unpinned git install resolves to whatever `main` points at right now, so a later push silently changes what is mounted in your profile. A commit sha (`#<sha>`) works too, and not even a maintainer can move that.
Pure JavaScript, no build step, no native module, no `allowBuilds` prompt.
Verify before launching:
```sh
dsh --profile web --dump-config # look for the "# == dsh-precedent" layer
dsh --profile web
```
Remove with `dsh plugin --profile web remove dsh-precedent`, which takes the patch layer and the dependency together.
## How it works
```mermaid
flowchart LR
log[("Session log<br/>~/.dsh · JSONL")]
sq["ctx.sessionQuery<br/>exact reads"]
led["Ledger<br/>pure aggregation"]
sp["ctx.systemPrompt<br/>.section()"]
cmd["ctx.commands<br/>/precedent"]
model(["Model"])
you(["You"])
log --> sq --> led
led --> sp --> model
led --> cmd --> you
```
### The seams it uses
| Harness surface | Used for | Notes |
|---|---|---|
| `ctx.sessionQuery.filterSessions` | Select this workspace's sessions | Filtered by `cwd`, the same conservative scope DSH's own cross-session tool uses |
| `ctx.sessionQuery.readSession` | Read one session's raw event log | Replay-validated; an unreadable log is skipped, never fatal |
| `ctx.systemPrompt.section` | Inject the ledger | One global section at order 150, rendering with tool guidance |
| `agent/pre-step` | Warm the ledger before the first request | Awaited waterfall — the only hook that runs before prompt assembly |
| `ctx.commands.register` | `/precedent` | Human surface, never sent to the model |
Pairing happens inside `readSession`'s event array: a `tool/call` with `name: 'bash'` is matched to its `tool/result` by `callId`, and the result's `error` field (or the result block's `isError`) decides the outcome.
### Why there is no index
`ctx.sessionQuery` splits into two halves. Full-text search (`searchSessions`, `searchEvents`) needs a provider and is **off in the shipped profile**. Everything else — `listSessions`, `filterSessions`, `readSession`, `listEvents`, `readEvent`, lineage and event traces — is backend-independent concrete behavior that works whether or not a search backend is open.
dsh-precedent uses only the second half. That is the whole reason it needs no index, no embedding model, and no warm-up: it reads the log the way the harness itself reads it for resume and export.
The cost is honest and bounded: a ledger build is a linear pass over this workspace's logs, done once per workspace per process, capped at `maxSessions`, and it stops blocking the first step after `buildTimeoutMs` whether or not it has finished.
### Why the extraction has no LLM in it
A command either exited non-zero or it didn't. `tool/result.error` records which. Pairing it to its `tool/call` by `callId` and counting is arithmetic — deterministic, reproducible, and impossible to hallucinate.
The one place judgment is genuinely required is deciding whether a user correction is a durable convention ("use bun, never npm") or one-off steering ("no, the other file"). That is why `v0.1.0` ships no correction mining at all: the command ledger stands on arithmetic alone.
## Configuration
```yaml
- id: precedent
name: dsh-precedent
config:
maxEntries: 40 # ledger lines injected per session
minRuns: 2 # ignore commands seen only once
lookbackDays: 90 # ignore sessions older than this
maxSessions: 200 # upper bound on logs read in one build
buildTimeoutMs: 5000 # stop blocking the first step after this
```
| Key | Default | Meaning |
|---|---|---|
| `maxEntries` | `40` | Hard cap on injected ledger lines, so the section stays a fixed, small token cost. `/precedent` is never capped |
| `minRuns` | `2` | A command seen once is an anecdote, not a precedent |
| `lookbackDays` | `90` | A convention from six months ago may no longer be true |
| `maxSessions` | `200` | Newest sessions first; a workspace with years of history stays cheap to read |
| `buildTimeoutMs` | `5000` | A slow build releases the first step and lands on a later one instead |
Scope is not configurable: sessions are matched on exact `cwd` string equality, the same conservative rule DSH's own cross-session authorization uses — a symlinked path is a different workspace.
## Commands
| Command | What it does |
|---|---|
| `/precedent` | Print the full ledger with citations, uncapped |
| `/precedent rebuild` | Discard the cached ledger and re-scan |
Everything the plugin injects is visible on demand before you trust it. A memory you cannot audit is a memory you cannot trust.
## What it is not
- **Not a search tool.** It does not answer "what did we discuss in June". It answers "what already works here", before you ask. If you want verbatim transcript retrieval, use a recall plugin — they compose fine.
- **Not a note-taker.** Nothing asks you to write memories. There is no capture step to forget to run.
- **Not context stuffing.** The injected section is capped and prefix-stable, so it costs a fixed small number of tokens and does not invalidate the KV cache between turns.
- **Not a replacement for `AGENTS.md`.** Hand-written intent still wins. Precedent covers the part nobody keeps up to date: what actually happened.
- **Not cross-workspace.** By design. See below.
## Privacy and safety
- **Everything stays local.** The plugin reads the session log through `ctx.sessionQuery` and holds the ledger in memory. No files written, no network calls, no telemetry, no upload path in the code.
- **Workspace-scoped.** Sessions are selected by exact `cwd` equality, mirroring the boundary DSH's own `tool-session-query` enforces. Another project's log is not read, even on the same machine.
- **Secrets are dropped, not stored.** Command strings routinely contain tokens (`curl -H "Authorization: …"`, `DEPLOY_KEY=… ./ship`). Every command is matched against secret-shaped patterns and the whole entry is discarded — not masked, discarded — before it reaches the ledger.
- **Auditable by construction.** `/precedent` prints exactly what gets injected, with a `session:seq` citation on every line that came from a failure.
## How it compares
| | Verbatim search plugins | Note-taking memory plugins | `AGENTS.md` editors | **dsh-precedent** |
|---|---|---|---|---|
| Works on pre-install history | Yes, after indexing | No — starts empty | No | **Yes, immediately** |
| Needs an index or model | Usually | No | No | **No** |
| Acts before you ask | No | Sometimes | Yes | **Yes** |
| Sources are citable | Yes | Rarely | N/A | **Always** |
| Can hallucinate an entry | No | Yes | N/A | **Not in the ledger path** |
| Answers "what did we discuss" | **Yes** | Partly | No | No |
Different jobs. A recall plugin is a search box; precedent is a track record. Running both is reasonable.
## Roadmap
- [x] `v0.1.0` — command ledger, system-prompt injection, `/precedent`
- [ ] `v0.2.0` — `tools/pre-execute` advisory on a known-bad call, `intercept: warn | ask | deny` via `ctx.tools.guard`
- [ ] `v0.3.0` — correction mining with citations, `/precedent why`, `/precedent pin`, `/precedent forget`
- [ ] `v0.4.0` — incremental rebuild as the current session appends, instead of once per process
- [ ] Later — export the ledger as an `AGENTS.md` draft for human review
Interfaces above are frozen against `deepseek-ai/deepseek-harness@47f943859bef` (read 2026-08-16). Anything that changes upstream will be noted here rather than silently adjusted.
## Contributing
Issues and PRs welcome. Useful contributions, roughly in order of value:
1. **A precedent your agent should have caught and didn't.** Paste the situation (no logs needed). Missed patterns are the roadmap.
2. **Extraction rules for a toolchain we get wrong.** Command normalization is heuristic; every ecosystem has its own shape.
3. **Secret-shaped patterns we fail to drop.** Treat these as security reports — open an issue and we will fix before discussing.
## License
[BSD-3-Clause](LICENSE).
---
<div align="center">
Part of [dshplugin.me](https://dshplugin.me) · sibling project: [dsh-plugin-radar](https://github.com/dshplugin-me/dsh-plugin-radar)
</div>
Install
dsh plugin --profile web add github:dshplugin-me/dsh-precedent
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-precedent from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.