dsh-headwater: Give Harness Agent a "Memory of Its Own"

Preface

The most common fatal flaw of agents in long conversations is “forgetting who you are while chatting.” Relying on hard-stuffing into the context window will eventually be blocked by compression; once compression happens, “why he is rigorous” becomes just “he is a rigorous person,” and the origin is lost. Vector retrieval can store sentences, but what is stored are fragments, and what is answered is similarity; there is no judgment of “whether to say it,” nor a clear explanation of where this impression grew from.

Below, I introduce dsh-headwater (maintained by h1-cat; Category: Memory, Current Star 0). Its approach is not “store more,” but to separate “original words” and “understanding” using a three-layer file system: original words are never rewritten, understanding can be changed anytime, and if changed incorrectly, there is a ledger to check.

What is this

One-sentence positioning: Add a memory system to a DSH Harness agent—original words are never lost, grow “who I understand you to be” from the original words, and judge whether this should be said before speaking.

  • Maintainer: h1-cat
  • Category: Memory
  • License: MIT
  • No model installation, no vector retrieval, no chunking, just three layers of files plus ten small tools

It divides memory into three layers:

<workspace>/.composure/
├── episodes.jsonl    L0 Log         Every sentence appended as-is, never rewritten
├── book.json
│   ├── portrait      L1 Report      "Current understanding": who he is / unfinished / recent status
│   ├── relation      L2 Ledger      Familiarity, tension, capacity
│   ├── open          L2 Ledger      Open threads
│   ├── stances       L2 Ledger      Stances I have taken
│   └── sayings       L2 Ledger      Heavy words I have said + subsequent reactions

The only rule is: L0 never changes, L1/L2 can change freely. If understanding is wrong, edit the report; it will be overwritten on the next recalc. If facts are lost, it means they were never in the log.

Installation and Enablement

The official install command is not dsh plugin add github:..., but with profile:

dsh plugin --profile desktop add github:h1-cat/dsh-headwater

Paste into PowerShell and hit Enter; DSH doesn’t need to be running. If using the web version (web profile), change desktop to web in the command.

Then restart DSH, and “Memory” will appear in the settings page.

To uninstall:

dsh plugin --profile desktop remove dsh-headwater

Memory data will not be deleted. It stays in the .composure directory of your workspace; it is your data, not the application’s.

The most common error is the second step showing “dsh command not found,” indicating you installed the minimal, portable, or store version without the CLI tools. In this case, you must install manually once; the steps are in INSTALL.md in the repository. For other errors, just submit to Issues.

Session Start: Memory Appears

The plugin appends a small section to the end of the prompt every round, named headwater, order 850:

## What I Remember
Who he is: …
Unfinished: …
Recent Status: …
## Tact
Familiarity 0.30, Tension 0.00, Capacity 0.62. Relationship stable.
Open threads: …
Things I said: …

This is not memory that only appears when the model calls a tool, but auto-injected, usually 3 to 10 lines.

Before Speaking: Ask Once

headwater_check(draft, intent, topic)
  → verdict: say | soften | warn_then_say | hold | stop

The five meanings are, in order: directly say; say but first catch the other’s state; say “this might sound bad” first; don’t say yet; stop, chat casually (leave the door open).

The 3rd tier handles “harsh but necessary,” and the 5th handles “the user doesn’t want to talk.” Neither is “speak less,” but a difference in tact.

For Original Text: Recall

headwater_recall(query, days, role, limit)

Scan the original text by time or keywords, return timestamped fragments, not summaries. You can directly check what was said before and on what day.

Rewrite Understanding

headwater_portrait(action: 'write', who, threads, mood)

Full overwrite, write current judgment, changes as understanding deepens, “this is correct.”

It Recalculates Itself

No need for anyone to remember to call it. Every 8 turns of conversation, and if > 30 minutes since the last recalc, the plugin will use a lightweight model call to compress the last 40 original texts into a new version of understanding.

These two knobs are written in the data directory’s settings.json and take effect immediately:

headwater_config(everyTurns=8, minMinutes=30)   Recalculate every N turns and minimum interval between them

You can also force a manual recalc:

headwater_recompute()

It returns detail explaining where this step got to: material (not enough raw text) / empty (model only produced reasoning) / json (no parseable JSON) / finish (call interrupted).

A practical pitfall: models like deepseek-flash that are reasoning models will spend the token budget on reasoning first, so the main body might have no text-delta at all. The plugin will first probe the minimum reasoning effort the model supports, then give maxTokens: 3000. If you switch models and recalc always shows at: 'empty', that is the reason.

Speaking Style Follows

Remembering what a person says, and speaking like a person, are two different things. The latter can only be learned from the tone in the original text; writing “he is a pragmatic person” cannot teach it. So, a sample section is added at the end of every prompt:

He usually speaks this way (original text, one word not changed. This is a ruler, not a script—learn the length, punctuation, and word choice, don't retell the content):
- "..."
- "..."

Five samples are picked by code from episodes.jsonl, without involving the model. Rules include: length 10–160 chars, skip code blocks, don’t collect if ends with a question; split by length into three tiers to avoid same tone and topic; don’t collect the last two said (avoid model treating “he just said” as “I should say”); skip the whole block if it contains “said before” inside a > quote; if total word count > 260, cut from the longest. Original text is unchanged, the model polishes it once, and the tone is smoothed.

To disable: headwater_config(voice=false).

Need to clarify: The samples provide a ruler for “how people speak,” not a setting for “how you should be.” It does not define your personality for you.

Words on the Ground, Plugin Picks Them Up

No need for the model to remember to call headwater_open. The plugin looks at every incoming sentence itself:

Short (<=20 chars) + not a question + contains "Forget it / Whatever / Don't want to talk / It's fine / Don't ask / Whatever / Boring / Don't want to chat"
  → Auto-hang a relational open thread

Deduplicated, max 5 at the same time, marked auto: true.

The reason it’s not done by “waiting for the model to be conscious” is direct: that is equivalent to making the most critical action rely on the most unreliable thing; it is normal for the model to forget to call the tool in the 30th round. After auto-hanging, it will appear in the next prompt, and the model decides whether to accept it.

Not Erased by Updates

Three mechanisms block compression from erasing impressions.

The first is anchors, which do not participate in compression, only add, never delete, appearing every round:

headwater_anchor(action='add', text='He wants something that can run, not a statement')

During recalc, the model can also propose new anchors (anchors_add), but the code only handles deduplication and appending, never overwriting existing ones. Removal requires an explicit call with a reason, and is logged.

The second is the change log, recording who / threads / mood changes from what to what during every recalc:

who:   ... → ... (added layer "worry understanding is ground down into a label")

headwater_state returns the last 6, answering “when did it start looking at me this way.”

The third is a protection period for core impressions; who defaults to 6 hours before a rewrite is allowed, while threads and mood change every time:

headwater_config(whoMinutes=360)

Details change fast, outlines change slow.

The fourth is that data has its own history. If the ledger directory (default .composure) is a git repository, the plugin will automatically commit changes into it: checks every autoCommitMinutes (default 20) minutes, commits if there are changes; commits immediately after every recalc of “current understanding.” The commit message includes reason and time, e.g., memory: portrait recomputed 2026-11-05 04:20. It does not init if there is no .git in the directory, only logs it; the repo boundary is defined by humans. It uses the git exit code to judge, doesn’t read subprocess output, so it runs in restricted environments; failures are only logged, never affecting memory writes.

Thus, “when did it start looking at me this way” is not only checkable via change logs, but also via git log / git diff to see what specific version of the ledger looks like:

git -C .composure log --oneline -5

Reaction Recording

After a relatively substantial sentence is actually said (after passing headwater_check), the system watches the next user message: engaged for follow-up details, dismissed for “Mmhmm/Okay/Whatever,” and acknowledged for others. This is the only thing in the full system that others won’t remember for you—how that person was after speaking.

What It Doesn’t Do

  • No vector retrieval, no chunking, no model installation
  • Doesn’t change persona, voice layer provides a ruler, not a personality setting
  • Doesn’t write lines for you, only provides conclusions and reasons

Use Cases and Notes

Suitable for long-communication agents that need to remember what was said before and need to judge tact. It runs with the permissions of the current DSH process. You should open the source code before installing to confirm the MIT license fits your distribution requirements.

A principle贯穿整个实现:写入中立,读取带倾向。The ledger records facts (what he said, how long ago, what words he used), and bias only happens during interpretation. This way, changing persona or model leaves the underlying ledger unchanged.

All data is in your own workspace .composure, offline and not uploaded. The only external call is the recalculation of “current understanding,” using the model you configured yourself in DSH.

Disable

Add disabled: true to the headwater line in the profile’s cordis.patch.yml, or disable it in the plugin manager. No writes after disabling, the ledger stays.

Closing

The value of dsh-headwater is not “remembering a few more sentences,” but separating original text, understanding, and tact into three layers, keeping original text forever, making understanding rewriteable, and passing a judgment before speaking. The page is at https://www.skillhub.cn/plugins/h1-cat/dsh-headwater, and the source code is at https://github.com/h1-cat/dsh-headwater.

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye