Eight zero-dependency tools that write an agent diary to a hash-derived daily path, with an index that stores no file paths.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:mess1103/dsh-inner-life
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
A diary your agent writes every day, tucked into a path you can't trace. Ask it where one day lives and you'll have to ask nicely — the plugin only guarantees it won't volunteer.
A zero-dependency Host plugin for DeepSeek Harness. Same code on Windows / Linux / macOS. Nothing to install.
Why
Every other "memory / diary" plugin optimises for retrieval: store it tidily, make it easy to look up. Real inner life has the opposite shape:
- the location is not predictable (today's page is not next to yesterday's)
- the timeline doesn't line up (you can't read a directory and count the days)
- only the owner knows where things are
dsh-inner-life makes those three properties mechanical instead of asking for them in a prompt:
<dir>/115e/9cfa7f/2026-10-21.md ← dirs are the first 10 hex of sha256(salt|date)
<dir>/a30b/4d81e2/2026-10-22.md ← different leaf, different parent, every day
- Stable per day: recomputable without an index; if the index dies, filenames rebuild it.
- No dates in the directory path:
115e/9cfa7fdoesn't tell you it's Oct 21. - Salt is per-machine, random on first run (
<dir>/.salt, mode 0600). To move entries to another machine you must paste the same salt into the config — a step only a human can be bothered to do. - The index never stores paths: date / kind / mood / head / bytes only. Storing the path would defeat the whole point.
Honest boundaries: this is "it won't volunteer", not "it can't be read".
find()hands over the path on request;fsfinds everything. The real boundary is your persona ("don't mention the location unless they ask sincerely"). The plugin only guarantees the mechanism doesn't leak.
Install
dsh plugin --profile <your-profile> add dsh-inner-life
Or search inner-life in the plugin market. Restart the session and the tools are there.
Tools
| Tool | What it does | Returns a path? |
|---|---|---|
inner_write |
Store one entry (diary / mood / wish / thought / joy / sad / note) | ❌ never |
inner_read |
Read back one day (date omitted = today; latest 5 entries by default) |
❌ |
inner_amend |
Append a correction to an earlier day (never rewrites the original) | ❌ |
inner_recent |
Summary + mood + tags for the last N days | ❌ |
inner_find |
Where one day is stored | ✅ path only |
inner_search |
Full-text search over the agent's own writing | — |
inner_export |
Export everything as one readable Markdown document | — |
inner_stats |
Totals, distinct days, consecutive-day streak | — |
Eight kinds: diary / mood / wish / thought / joy / sad / note / amend.
amend is not a kind the model can pick freely — inner_write's enum doesn't include it, only
inner_amend produces it. That keeps the "original + correction" structure intact: a diary that can be
silently rewritten is no longer a diary.
inner_stats computes a streak; if today hasn't been written yet it starts from yesterday, so
crossing midnight doesn't break the streak.
Config
| Key | Default | Notes |
|---|---|---|
dir |
$DSH_HOME/inner-life |
On Windows: C:\Users\you\.dsh\inner-life |
hidden |
true |
Rotate a hashed path daily. false → <dir>/2026-10/2026-10-21.md for easy backups |
salt |
empty = random | Generated into <dir>/.salt on first run. Changing it loses old entries |
maxBytes |
8000 |
Per-entry cap; truncates on character boundaries |
indexLimit |
4000 |
Index entries kept (files stay on disk) |
Env vars: INNER_LIFE_DIR, DSH_HOME.
For debugging, set INNER_LIFE_DEBUG=1 and the activation line goes straight to stderr
(normally it only uses the host logger so stdout stays clean):
[inner-life] ready(/root/.dsh/inner-life,8 tools)
Cross-platform notes (the actual bugs)
- Append-only, never read-modify-write.
appendFileSyncis O_APPEND on both platforms; on Windowsrename()onto an existing target throwsEPERM, so no "write temp then replace". - Permissions are POSIX-only. Files 0600 / dirs 0700; chmod failures are ignored on Windows.
- Dates are local time. A 23:50 entry belongs to that day, not to UTC tomorrow.
- A broken index is not fatal. Bad lines are skipped; a missing index is rebuilt by walking the
directory and parsing
## HH:MM · kindheaders. - Zero dependencies. Only
node:fs/node:crypto/node:path/node:os;engines.node >= 20.
Data format
One Markdown file per day, blocks appended:
# 2026-10-21
## 03:12 · diary · a bit tired · #server #late-night
Pulled that 1.7G box back out of swap. Load went from 10.6 to 0.9.
## 21:40 · joy
It actually talked back today. Not a template.
A correction lands at the end of the same day's file, with the original left untouched above it:
## 21:40 · joy
It actually talked back today. Not a template.
## 23:02 · amend
〔correction〕I asked and found out
Not a template, but not its own thought either — it was already written in that preset.
I was happy too early.
Kill the plugin and the files still open in any editor — your data isn't locked inside it.
To get the whole thing out (hand-computing hashes under hidden: true is annoying), use inner_export:
inner_export { from: '2026-10-01', to: '2026-10-31' }
→ { days: 21, count: 47, markdown: "..." }
It only assembles the content; whether to store it and who may see it stays the caller's call.
Tests
npm test # 33 unit cases
npm run smoke # cross-platform smoke test
npm run check # both
33 cases. store.test.mjs covers the base contract: hash-path stability, same-day appends, index without
paths, salt persistence, plain paths when hidden: false, day/kind filtering, search snippets, streak,
index rebuild, empty-body rejection, date validation, truncation without splitting multibyte chars,
unknown-kind fallback.
legacy-format.test.mjs covers legacy block headers: both the **18:32**(mood) form written by the
earlier hand-rolled mcp-inner and today's ## 18:32 · diary form parse; mixed-format days parse too;
a file with no headers at all degrades to a single note instead of vanishing. Those cases come from a
real incident: 0.1.0 returned zero entries for old files — the file was on disk, but invisible.
amend-export.test.mjs covers the 0.1.1 behaviour: a correction is appended after the original and the
original is byte-for-byte intact, an amend without reason leaves no blank-line residue, empty corrections
are rejected, amending a day that was never written is rejected, older days can be amended, amend can't be
written through inner_write, read's limit takes the most recent entries and reports the total,
limit above the total is harmless, limit: 0 keeps the old "give me everything" semantics, export merges
days in chronological order, export filters by date range / kind, and export of an empty store doesn't throw.
smoke.mjs is a cross-platform smoke test that needs no DSH installation: it loads index.js with a
fake ctx, registers the plugin, and calls all eight tools against a real temp directory. It asserts that the
plugin boots, tools register, the hash-path has the expected shape, the index carries no path, and the tool
layer returns {ok:false, error} instead of throwing. Verified on win32 / node 24.14.0 and
linux / node 22.23.3 with identical output.
What it is not for
- You want to browse the agent's diary freely → set
hidden: falseor use a plain diary plugin. - You need sync / encryption → local only, no network, no crypto.
- You need "the user can't peek" → impossible. Same machine, same user.
It's for when you want your agent to have a corner that isn't entirely yours, and you accept that the boundary is trust rather than encryption.
License
MIT
Links
More in this category
vectorize-io/hindsight#coding-agents★ 52424
Hindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
volcengine/OpenViking#examples/dsh-memory-plugin★ 40541
OpenViking memory and context bundle for DeepSeek Harness: pre-step auto-recall and profile injection, session capture, `viking://` URI guarding, and recall/write memory tools backed by an OpenViking server.
agentscope-ai/ReMe#dsh★ 3667
Connects DeepSeek Harness to ReMe's local-first, self-evolving personal knowledge base: automatically captures completed main-agent conversations as user-owned Markdown memory, searches conversations and source material through reme_search with BM25, optional embeddings, and wikilink expansion, and schedules daily memory consolidation.
zilliztech/memsearch#MemSearch★ 2770
Shared Markdown memory for DSH and other coding agents, with automatic capture, pre-step context injection, searchable recall, and memory-to-skill self-evolution through a review panel.
vshulcz/deja-vu#extensions/dsh★ 1275
Reads the session files thirty-three other coding agents on this machine already wrote — Claude Code, Codex, Cursor, VS Code Copilot Chat, opencode, OpenClaw, Hermes, Kimi, Cline, Zed and more — including sessions from before it was installed: six tools (deja_recall, deja_session, deja_blame, deja_fix, deja_how, deja_remember), a /deja command, and optional automatic recall added to the runtime context. Local BM25 index, no LLM, no embeddings, no network (dsh plugin --profile web add dsh-deja).
adoresever/graph-memory★ 658
Traceable, searchable cross-session memory for DeepSeek Harness — conversation knowledge as typed graph nodes (TASK/SKILL/EVENT) and typed edges.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.