memory: polish help and docs (#569)

- add /memory help and clearer invalid-subcommand guidance
- register /memory in shared slash-command help
- align memory docs with current behavior and config
- add focused tests for help and discovery
This commit is contained in:
20bytes
2026-05-04 15:25:13 +08:00
committed by GitHub
parent 3179b552d4
commit 8aed1bb674
9 changed files with 204 additions and 4 deletions
+8
View File
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.8.9] - Unreleased
### Changed
- **User memory docs + help polish** (#497) — `/memory` is now listed in
slash-command help, supports `/memory help`, and the README /
configuration docs now point at the full `docs/MEMORY.md` guide and
document both `[memory].enabled` and `DEEPSEEK_MEMORY`.
## [0.8.8] - 2026-05-03
### Added
+1 -1
View File
@@ -231,7 +231,7 @@ A stabilization-focused release: a thick band of UX polish on top of the v0.8.6
- **Every `HookEvent` now has a live producer**`tool_call_before` / `tool_call_after` / `message_submit` / `on_error` fire from the runtime in addition to the existing session-lifecycle and mode-change events. Hooks remain read-only observers in v0.8.8 ([#455](https://github.com/Hmbown/DeepSeek-TUI/issues/455)).
- **`instructions = [...]` config array** lets you stack additional system-prompt files; paths capped at 100 KiB each, project array replaces user array wholesale ([#454](https://github.com/Hmbown/DeepSeek-TUI/issues/454)).
- **`deepseek pr <N>` subcommand** fetches a PR's title / body / diff via `gh` and launches the TUI with a review prompt already in the composer. Codepoint-safe diff cap at 200 KiB; optional `--repo` / `--checkout` ([#451](https://github.com/Hmbown/DeepSeek-TUI/issues/451)).
- **User-memory MVP** (opt-in) — `~/.deepseek/memory.md` injected into the system prompt as a `<user_memory>` block; `# foo` typed in the composer appends a timestamped bullet without firing a turn; `/memory [show|path|clear|edit]` for inspection. Default off; enable with `[memory] enabled = true` or `DEEPSEEK_MEMORY=on` ([#489](https://github.com/Hmbown/DeepSeek-TUI/issues/489)[#493](https://github.com/Hmbown/DeepSeek-TUI/issues/493)).
- **User-memory MVP** (opt-in) — `~/.deepseek/memory.md` injected into the system prompt as a `<user_memory>` block; `# foo` typed in the composer appends a timestamped bullet without firing a turn; `/memory [show|path|clear|edit|help]` for inspection. Default off; enable with `[memory] enabled = true` or `DEEPSEEK_MEMORY=on`. See [docs/MEMORY.md](docs/MEMORY.md) for the full guide and examples ([#489](https://github.com/Hmbown/DeepSeek-TUI/issues/489)[#493](https://github.com/Hmbown/DeepSeek-TUI/issues/493)).
### 🔒 Security
+10
View File
@@ -346,6 +346,16 @@ mod tests {
assert!(msg.contains("Aliases: dashboard, api"));
}
#[test]
fn test_help_memory_topic_shows_usage_and_description() {
let mut app = create_test_app();
let result = help(&mut app, Some("memory"));
let msg = result.message.expect("help topic should return message");
assert!(msg.contains("memory"));
assert!(msg.contains("persistent user-memory file"));
assert!(msg.contains("Usage: /memory [show|path|clear|edit|help]"));
}
#[test]
fn test_help_pushes_overlay() {
let mut app = create_test_app();
+91 -1
View File
@@ -9,6 +9,7 @@
//! - `/memory show` — alias for the no-arg form
//! - `/memory clear` — replace the file contents with an empty marker
//! - `/memory path` — show only the resolved path
//! - `/memory help` — show command-specific help and the resolved path
//!
//! Editor integration (`/memory edit`) is intentionally minimal: the
//! command prints a copy-pasteable shell line to open the file in the
@@ -17,10 +18,31 @@
//! doesn't have access to.
use std::fs;
use std::path::Path;
use super::CommandResult;
use crate::tui::app::App;
const MEMORY_USAGE: &str = "/memory [show|path|clear|edit|help]";
fn memory_help(path: &Path) -> String {
format!(
"Inspect or manage your persistent user-memory file.\n\n\
Usage: {MEMORY_USAGE}\n\n\
Current path: {}\n\n\
Subcommands:\n\
/memory Show the resolved path and current contents\n\
/memory show Alias for the no-arg form\n\
/memory path Print just the resolved path\n\
/memory clear Replace the file contents with an empty marker\n\
/memory edit Print the editor command for this file\n\
/memory help Show this help\n\n\
Quick capture: type `# foo` in the composer to append a timestamped\n\
bullet without firing a turn.",
path.display()
)
}
pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
if !app.use_memory {
return CommandResult::error(
@@ -55,8 +77,76 @@ pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
"to edit your memory file, run:\n\n ${{VISUAL:-${{EDITOR:-vi}}}} {}",
path.display()
)),
"help" => CommandResult::message(memory_help(&path)),
_ => CommandResult::error(format!(
"unknown subcommand `{sub}`. usage: /memory [show|path|clear|edit]"
"unknown subcommand `{sub}`. Try `/memory help`.\n\n{}",
memory_help(&path)
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use crate::tui::app::{App, TuiOptions};
use tempfile::TempDir;
fn create_test_app_with_memory(tmpdir: &TempDir, use_memory: bool) -> App {
let options = TuiOptions {
model: "deepseek-v4-pro".to_string(),
workspace: tmpdir.path().to_path_buf(),
config_path: None,
config_profile: None,
allow_shell: false,
use_alt_screen: true,
use_mouse_capture: false,
use_bracketed_paste: true,
max_subagents: 1,
skills_dir: tmpdir.path().join("skills"),
memory_path: tmpdir.path().join("memory.md"),
notes_path: tmpdir.path().join("notes.txt"),
mcp_config_path: tmpdir.path().join("mcp.json"),
use_memory,
start_in_agent_mode: false,
skip_onboarding: true,
yolo: false,
resume_session_id: None,
initial_input: None,
};
App::new(options, &Config::default())
}
#[test]
fn memory_help_lists_subcommands_and_resolved_path() {
let tmpdir = TempDir::new().expect("tempdir");
let mut app = create_test_app_with_memory(&tmpdir, true);
let result = memory(&mut app, Some("help"));
let msg = result.message.expect("help should return text");
assert!(msg.contains("Usage: /memory [show|path|clear|edit|help]"));
assert!(msg.contains("/memory edit"));
assert!(msg.contains(app.memory_path.to_string_lossy().as_ref()));
}
#[test]
fn memory_unknown_subcommand_points_to_help() {
let tmpdir = TempDir::new().expect("tempdir");
let mut app = create_test_app_with_memory(&tmpdir, true);
let result = memory(&mut app, Some("wat"));
let msg = result
.message
.expect("unknown subcommand should return text");
assert!(msg.contains("Try `/memory help`"));
assert!(msg.contains("/memory clear"));
}
#[test]
fn memory_disabled_returns_enablement_hint() {
let tmpdir = TempDir::new().expect("tempdir");
let mut app = create_test_app_with_memory(&tmpdir, false);
let result = memory(&mut app, None);
let msg = result.message.expect("disabled memory should return text");
assert!(msg.contains("user memory is disabled"));
assert!(msg.contains("DEEPSEEK_MEMORY=on"));
}
}
+7
View File
@@ -210,6 +210,12 @@ pub const COMMANDS: &[CommandInfo] = &[
usage: "/note <text>",
description_id: MessageId::CmdNoteDescription,
},
CommandInfo {
name: "memory",
aliases: &[],
usage: "/memory [show|path|clear|edit|help]",
description_id: MessageId::CmdMemoryDescription,
},
CommandInfo {
name: "attach",
aliases: &["image", "media"],
@@ -812,6 +818,7 @@ mod tests {
fn command_registry_contains_config_and_links_but_not_set_or_deepseek() {
assert!(COMMANDS.iter().any(|cmd| cmd.name == "config"));
assert!(COMMANDS.iter().any(|cmd| cmd.name == "links"));
assert!(COMMANDS.iter().any(|cmd| cmd.name == "memory"));
assert!(!COMMANDS.iter().any(|cmd| cmd.name == "set"));
assert!(!COMMANDS.iter().any(|cmd| cmd.name == "deepseek"));
}
+8
View File
@@ -238,6 +238,7 @@ pub enum MessageId {
CmdLoadDescription,
CmdLogoutDescription,
CmdMcpDescription,
CmdMemoryDescription,
CmdModelDescription,
CmdModelsDescription,
CmdNoteDescription,
@@ -424,6 +425,7 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
MessageId::CmdLoadDescription,
MessageId::CmdLogoutDescription,
MessageId::CmdMcpDescription,
MessageId::CmdMemoryDescription,
MessageId::CmdModelDescription,
MessageId::CmdModelsDescription,
MessageId::CmdNoteDescription,
@@ -739,6 +741,7 @@ fn english(id: MessageId) -> &'static str {
MessageId::CmdLoadDescription => "Load session from file",
MessageId::CmdLogoutDescription => "Clear API key and return to setup",
MessageId::CmdMcpDescription => "Open or manage MCP servers",
MessageId::CmdMemoryDescription => "Inspect or manage the persistent user-memory file",
MessageId::CmdModelDescription => "Switch or view current model",
MessageId::CmdModelsDescription => "List available models from API",
MessageId::CmdNoteDescription => {
@@ -1020,6 +1023,7 @@ fn japanese(id: MessageId) -> Option<&'static str> {
MessageId::CmdLoadDescription => "ファイルからセッションを読み込み",
MessageId::CmdLogoutDescription => "API キーを消去してセットアップに戻る",
MessageId::CmdMcpDescription => "MCP サーバを開く・管理する",
MessageId::CmdMemoryDescription => "永続ユーザーメモリファイルを確認・管理",
MessageId::CmdModelDescription => "現在のモデルを切り替え・確認",
MessageId::CmdModelsDescription => "API から利用可能なモデルを一覧表示",
MessageId::CmdNoteDescription => "永続ノートファイル(.deepseek/notes.md)に追記",
@@ -1281,6 +1285,7 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
MessageId::CmdLoadDescription => "从文件加载会话",
MessageId::CmdLogoutDescription => "清除 API 密钥并返回设置",
MessageId::CmdMcpDescription => "打开或管理 MCP 服务器",
MessageId::CmdMemoryDescription => "查看或管理持久用户记忆文件",
MessageId::CmdModelDescription => "切换或查看当前模型",
MessageId::CmdModelsDescription => "列出 API 中可用的模型",
MessageId::CmdNoteDescription => "将笔记追加到持久笔记文件(.deepseek/notes.md",
@@ -1530,6 +1535,9 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
MessageId::CmdLoadDescription => "Carregar a sessão de um arquivo",
MessageId::CmdLogoutDescription => "Limpar a chave de API e voltar à configuração",
MessageId::CmdMcpDescription => "Abrir ou gerenciar servidores MCP",
MessageId::CmdMemoryDescription => {
"Inspecionar ou gerenciar o arquivo persistente de memória do usuário"
}
MessageId::CmdModelDescription => "Trocar ou exibir o modelo atual",
MessageId::CmdModelsDescription => "Listar os modelos disponíveis pela API",
MessageId::CmdNoteDescription => {
+3 -1
View File
@@ -9,7 +9,9 @@
//! prompt alongside the existing `<project_instructions>` block.
//! - **`# foo`** typed in the composer appends `foo` to the memory
//! file as a timestamped bullet — fast capture without leaving the TUI.
//! - **`/memory`** opens the memory file in `$VISUAL` / `$EDITOR`.
//! - **`/memory`** shows the resolved file path and current contents, and
//! **`/memory edit`** prints a copy-pasteable `$VISUAL` / `$EDITOR`
//! command for opening the file yourself.
//! - **`remember` tool** lets the model itself append a bullet when it
//! notices a durable preference or convention worth keeping across
//! sessions.
+31
View File
@@ -128,6 +128,7 @@ These override config values:
- `DEEPSEEK_SKILLS_DIR`
- `DEEPSEEK_MCP_CONFIG`
- `DEEPSEEK_NOTES_PATH`
- `DEEPSEEK_MEMORY` (`1|on|true|yes|y|enabled` turns user memory on)
- `DEEPSEEK_MEMORY_PATH`
- `DEEPSEEK_ALLOW_SHELL` (`1`/`true` enables)
- `DEEPSEEK_APPROVAL_POLICY` (`on-request|untrusted|never`)
@@ -320,6 +321,11 @@ If you are upgrading from older releases:
used immediately by `/mcp`, but rebuilding the model-visible MCP tool pool
requires restarting the TUI.
- `notes_path` (string, optional): defaults to `~/.deepseek/notes.txt` and is used by the `note` tool.
- `[memory].enabled` (bool, optional): defaults to `false`. When `true`,
the TUI loads the user memory file into a `<user_memory>` prompt block,
enables `# foo` quick-capture in the composer, surfaces the `/memory`
slash command, and registers the `remember` tool. The same toggle is
available via `DEEPSEEK_MEMORY=on`.
- `memory_path` (string, optional): defaults to `~/.deepseek/memory.md`.
Used by the user-memory feature when enabled — see
[`MEMORY.md`](MEMORY.md) for the full feature surface (`# foo`
@@ -369,6 +375,31 @@ If you are upgrading from older releases:
- `hooks` (optional): lifecycle hooks configuration (see `config.example.toml`).
- `features.*` (optional): feature flag overrides (see below).
### User memory
User memory is split across one top-level path setting and one opt-in
toggle table:
```toml
memory_path = "~/.deepseek/memory.md"
[memory]
enabled = true
```
Notes:
- `memory_path` stays at the top level beside `notes_path` and
`skills_dir`; it is not nested under `[memory]`.
- `DEEPSEEK_MEMORY_PATH` overrides the file path from the environment.
- `DEEPSEEK_MEMORY=on` (also `1`, `true`, `yes`, `y`, or `enabled`)
flips the feature on without editing `config.toml`.
- The feature is inert when disabled: no file is injected, `# foo`
falls through to normal message submission, and the model does not
see the `remember` tool.
- See [`MEMORY.md`](MEMORY.md) for examples and the full `/memory`
command surface.
### Parsed but currently unused (reserved for future versions)
These keys are accepted by the config loader but not currently used by the interactive TUI or built-in tools:
+45 -1
View File
@@ -20,6 +20,9 @@ Either set the env var:
export DEEPSEEK_MEMORY=on
```
Accepted truthy values are `1`, `on`, `true`, `yes`, `y`, and
`enabled`.
…or add to `~/.deepseek/config.toml`:
```toml
@@ -31,7 +34,25 @@ Restart the TUI after toggling. Disabling is the same in reverse.
The memory file lives at `~/.deepseek/memory.md` by default; override
with `memory_path` in `config.toml` or `DEEPSEEK_MEMORY_PATH` in
the environment.
the environment. `DEEPSEEK_MEMORY_PATH` wins over the config file when
both are set.
## Quick examples
```text
# remember that this repo prefers cargo fmt before commits
/memory
/memory path
/memory edit
/memory help
```
- Type `# remember that this repo prefers cargo fmt before commits` in
the composer to append a timestamped bullet without firing a turn.
- Run `/memory` to confirm where the feature is writing and what is
currently stored.
- Run `/memory edit` when you want to groom the file manually in your
editor.
## What gets injected
@@ -84,11 +105,18 @@ Inspect, clear, or get hints about editing the file:
| `/memory path` | Print just the resolved path |
| `/memory clear` | Replace the file with an empty marker |
| `/memory edit` | Print the `${VISUAL:-${EDITOR:-vi}} <path>` shell line |
| `/memory help` | Show command-specific help and the current path |
The `/memory edit` form intentionally just prints the command rather
than spawning the editor in-process — that keeps the slash-command
handler simple and consistent regardless of which editor you use.
You can also discover the feature from the general help surfaces:
- `/help memory` shows the slash-command summary and usage line.
- `/memory help` prints the memory-specific subcommands plus the
resolved path.
### 3. The `remember` tool (auto-update, #489)
When memory is enabled the model gets a `remember` tool with this
@@ -134,6 +162,22 @@ about the timestamp format; it just reads the whole file as the
memory block. The timestamp is convention so you can tell when each
note was added when grooming the file.
## Hierarchy and imports
Memory is intentionally **user-scoped** rather than repo-scoped. It
sits alongside — not inside — project instruction sources such as
`AGENTS.md`, `.deepseek/instructions.md`, and `instructions = [...]`.
- Use **memory** for durable personal preferences that should follow
you across repos and sessions.
- Use **project instructions** for repo-specific conventions that
should travel with the codebase.
The memory loader currently reads one resolved file path verbatim.
`@path` imports / includes are **not** supported today; if you need a
larger reusable instruction bundle, put it in a project instruction
file or a skill instead.
## What stays out of memory
Memory is for **durable** signal. Things that should NOT live there: