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.