Single-file persistent companion memory: interests, preferences, tasks, and past works summarized into the system prompt, with read/update tools.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:w4xxx/dsh-xia-plugins#path:/packages/gameassist-memory
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
中文 | English
gameassist-memory provides a small persistent memory bank for the Xia game-development assistant. It stores interests, preferences, notes, tasks, and past works in one JSON file, contributes a summary to the system prompt, and exposes read and update tools.
This package is not published to npm yet; this repository distributes source under the project's own
@w4xxxscope. Use the package name from package.json when composing Loader/preset entries.
Features and runtime behavior
- At plugin load, the package reads
memoryFileonce. A missing, unreadable, or unparsable file starts with an empty in-memory bank. - System-prompt section
gameassist:memoryat order11renders the current summary and tells the assistant to record new information and task-status changes. memory_updatechanges memory, writes the JSON file, and re-registers the prompt section, so the next model request sees the new content.- Plugin disposal removes the prompt section and both tools.
- The plugin requires the Cordis services
systemPromptandtools.
Memory hygiene contract
The plugin injects a fixed contract into the system prompt to keep the memory bank lean and maintainable:
- Memory holds state only: each task/work keeps one line of "current progress + next step"; daily development logs, change details, and pitfalls belong in the knowledge base or project docs, not in memory.
- Update, don't duplicate: when a task's status changes, update the existing entry by its
taskIdinstead of creating a same-named task repeatedly (otherwise the bank bloats and injected token cost grows). - Truncation as a safety net: even if long text piles up in the bank, the injected summary is clipped by
maxNoteChars/maxSummaryChars, andmemory_readalways returns the full content.
The same contract lives in the memory_update tool description, so the assistant sees it on every call.
Configuration and integration
Config fields:
memoryFile: string(required): the absolute path to one JSON memory file. Saving recursively creates its parent directory.maxNoteChars: number(optional): max characters kept per tasknotesin the injected system-prompt summary. Default200; set0to hide notes entirely. Full content stays available throughmemory_read.maxSummaryChars: number(optional): max characters kept per worksummaryin the injected system-prompt summary. Default120; set0to hide summaries entirely. Full content stays available throughmemory_read.
Truncation only affects the injected prompt summary. The on-disk file always keeps the full content, and
memory_readalways returns it in full — so a large memory bank no longer inflates every request's token cost, and details are one tool call away.
Loader/profile patch example:
- insert:
- id: gameassist-memory
name: '@your-scope/gameassist-memory'
config:
memoryFile: 'D:/dsh-data/gameassist/memory.json'
maxNoteChars: 200
maxSummaryChars: 120
Agent preset agent.cordis.yml example:
- id: gameassist-memory
name: '@your-scope/gameassist-memory'
config:
memoryFile: 'D:/dsh-data/gameassist/memory.json'
maxNoteChars: 200
maxSummaryChars: 120
The plugin provides no Cordis service, so it does not need an isolate realm. Multiple presets or Loader rows pointing to one file still hold independent in-memory copies with no cross-instance coordination. Mount one writer only, or configure a separate file per instance.
JSON data format
The file is one UTF-8 JSON object. Successful saves use two-space indentation and one trailing newline:
{
"interests": ["game development", "anime"],
"preferences": ["communicate in English"],
"profileNotes": "Call the user Master.",
"tasks": [
{
"id": "forum-game",
"title": "Advance the forum game",
"status": "doing",
"notes": "Polish the first battle",
"updatedAt": "2026-08-30T00:00:00.000Z"
}
],
"works": [
{
"id": "my-forum-game",
"name": "My Forum Game",
"kind": "game",
"summary": "A forum-themed game",
"tech": ["Godot 4.7", "GDScript"],
"path": "D:/projects/my-forum-game",
"status": "in development",
"updatedAt": "2026-08-30T00:00:00.000Z"
}
],
"updatedAt": "2026-08-30T00:00:00.000Z"
}
The implementation only applies a TypeScript assertion to parsed JSON; it performs no runtime schema validation or migration. A parseable file with missing or incorrectly typed fields can fail during prompt rendering or update.
Tools and update semantics
memory_read
Takes no arguments and returns the current in-memory summary. It does not re-read the file.
memory_update
Every field is optional, but the call must provide at least one value other than undefined:
interests,preferences: split on English/Chinese commas, ideographic comma, semicolons, or newlines. Providing a field replaces the whole list; an empty string clears it rather than appending.profileNotes: replaces the whole note; an empty string clears it.taskId,taskTitle,taskStatus,taskNotes: update bytaskId. If the id is not found, creation requirestaskTitle; omission of an id generates at...id, and a new task defaults to statustodo.removeTaskId: remove a task by id.workId,workName,workKind,workSummary,workTech,workPath,workStatus: update byworkId. If the id is not found, creation requiresworkName; omission of an id generates aw...id.workTech: like interests and preferences, providing it replaces that work's entire technology list; an empty string clears it.removeWorkId: remove a work by id.
Removal happens before upsert in the same call, so deleting an id while also supplying enough fields to create can recreate that id. Every effective update refreshes the document updatedAt; a task or work that is created or actually updated also receives its own timestamp. Status strings are not restricted to an enum.
Prompt, model, and cache effects
The rendered summary and fixed recording instruction enter the system prompt on every model request, and both tool schemas are model-visible. Token cost grows linearly with interests, tasks, works, and note length; the injected summary is truncated by default (maxNoteChars/maxSummaryChars, 200/120 chars) so oversized entries do not enter the prompt — call memory_read for full content. Stable memory preserves the prefix. After memory_update changes the prompt, later requests cannot reuse the old prefix from that section onward. The tool result also returns the complete updated summary to the current conversation.
Persistence, privacy, and security
All memory is stored in one JSON file at memoryFile. Saving directly overwrites the target with writeFile; it does not use a temporary file plus atomic replacement and has no file lock, transaction, concurrent-write merge, or cross-process synchronization. Process interruption, disk failure, or competing writers can leave a truncated file or lose updates. Back it up externally and ensure there is only one writer.
The file can contain personal preferences, project paths, tasks, works, and free-form notes. Its summary is sent to the selected model and can appear in tool results, session logs, or UI. Store it in an access-controlled location, avoid credentials and unnecessary sensitive data, and do not commit a private memory file to version control. Repositories and plugin packages should contain only sanitized examples.
Source, build, and tests
src/index.ts: config, data types, rendering, update semantics, file I/O, and tool registration.tests/memory.spec.ts: list splitting, whole-list replacement, task upsert/removal, and work-rendering tests.tsconfig.jsonandtsdown.config.ts: TypeScript emitslib/types, then tsdown produces Node ESMlib/index.js.
From the repository root:
pnpm exec vitest run packages/companion/gameassist-memory/tests/memory.spec.ts
pnpm --filter @w4xxx/dsh-gameassist-memory bundle
bundle expects lib/types/index.js to have been emitted first. Use the repository-level pnpm run build for a complete build.
Known Limitations and Deferred Work
- The package supports one complete JSON document only, with no multi-user namespaces, query index, or history.
- The file is read only at plugin load; external edits do not hot-reload, and
memory_readdoes not re-read disk. - Writes are non-atomic and unlocked; concurrent tool calls, plugin instances, or processes can overwrite each other or corrupt the file.
- Loaded data has no schema validation, size limit, or migration; any read/parse error silently falls back to empty memory.
- Interests, preferences, and
workTechuse whole-list replacement, with no single-item append/remove operation. - The prompt summary is truncated by default (
maxNoteChars/maxSummaryChars) but still performs no redaction or access control; full content always stays on disk. - Current tests cover pure update logic, not a real Loader composition, file-I/O failures, concurrent writes, or plugin lifecycle.
Links
More in this category
vectorize-io/hindsight#coding-agents★ 50109
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★ 40027
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★ 3610
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★ 2752
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★ 1205
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★ 649
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.