mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 03:30:40 +00:00
feat(memory): managed auto-memory and auto-dream system (#3087)
* docs: add auto-memory implementation log
* feat(core): add managed auto-memory storage scaffold
* feat(core): load managed auto-memory index
* feat(core): add managed auto-memory recall
* feat(core): add managed auto-memory extraction
* feat(cli): add managed auto-memory dream commands
* feat(core): add auxiliary side-query foundation
* feat(memory): add model-driven recall selection
* feat(memory): add model-driven extraction planner
* feat(core): add background task runtime foundation
* feat(memory): schedule auto dream in background
* feat(core): add background agent runner foundation
* feat(memory): add extraction agent planner
* feat(core): add dream agent planner
* feat(core): rebuild managed memory index
* feat(memory): add governance status commands
* feat(memory): add managed forget flow
* feat(core): harden background agent planning
* feat(memory): complete managed parity closure
* test(memory): add managed lifecycle integration coverage
* feat: same to cc
* feat(memory-ui): add memory saved notification and memory count badge
Feature 3 - Memory Saved Notification:
- Add HistoryItemMemorySaved type to types.ts
- Create MemorySavedMessage component for rendering '● Saved/Updated N memories'
- In useGeminiStream: detect in-turn memory writes via mapToDisplay's
memoryWriteCount field and emit 'memory_saved' history item after turn
- In client.ts: capture background dream/extract promises and expose
via consumePendingMemoryTaskPromises(); useGeminiStream listens
post-turn and emits 'Updated N memories' notification for background tasks
Feature 4 - Memory Count Badge:
- Add isMemoryOp field to IndividualToolCallDisplay
- Add memoryWriteCount/memoryReadCount to HistoryItemToolGroup
- Add detectMemoryOp() in useReactToolScheduler using isAutoMemPath
- ToolGroupMessage renders '● Recalled N memories, Wrote N memories' badge
at the top of tool groups that touch memory files
Fix: process.env bracket-access in paths.ts (noPropertyAccessFromIndexSignature)
Fix: MemoryDialog.test.tsx mock useSettings to satisfy SettingsProvider requirement
* fix(memory-ui): auto-approve memory writes, collapse memory tool groups, fix MEMORY.md path
Problem 1 - Auto-approve memory file operations:
- write-file.ts: getDefaultPermission() checks isAutoMemPath; returns 'allow'
for managed auto-memory files, 'ask' for all other files
- edit.ts: same pattern
Problem 2 - Feature 4 UX: collapse memory-only tool groups:
- ToolGroupMessage: detect when all tool calls have isMemoryOp set (pure memory
group) and all are complete; render compact '● Recalled/Wrote N memories
(ctrl+o to expand)' instead of individual tool call rows
- ctrl+o toggles expand/collapse when isFocused and group is memory-only
- Mixed groups (memory + other tools) keep badge-at-top behaviour
- Expanded state shows individual tool calls with '● Memory operations
(ctrl+o to collapse)' header
Problem 3 - MEMORY.md path mismatch:
- prompt.ts: Step 2 now references full absolute path ${memoryDir}/MEMORY.md
so the model writes to the correct location inside the memory directory,
not to the parent project directory
Fix tests:
- write-file.test.ts: add getProjectRoot to mockConfigInternal
- prompt.test.ts: update assertion to match full-path section header
* fix(memory-ui): fix duplicate notification, broken ctrl+o, and Edit tool detection
- Remove duplicate 'Saved N memories' notification: the tool group badge already
shows 'Wrote N memories'; the separate HistoryItemMemorySaved addItem after
onComplete was double-counting. Keep only the background-task path
(consumePendingMemoryTaskPromises).
- Remove ctrl+o expand: Ink's Static area freezes items on first render and
cannot respond to user input. useInput/useState(isExpanded) in a Static item
is a no-op. Removed the dead code; memory-only groups now always render as
the compact summary (no fake interactive hint).
- Fix Edit tool detection: detectMemoryOp was checking for 'edit_file' but the
real tool name constant is 'edit'. Also removed non-existent 'create_file'
(write_file covers all writes). Now editing MEMORY.md is correctly identified
as a memory write op, collapses to 'Wrote N memories', and is auto-approved.
* fix(dream): run /dream as a visible submit_prompt turn, not a silent background agent
The previous implementation ran an AgentHeadless background agent that could
take 5+ minutes with zero UI feedback — user saw a blank screen for the entire
duration and then at most one line of text.
Fix: /dream now returns submit_prompt with the consolidation task prompt so it
runs as a regular AI conversation turn. Tool calls (read_file, write_file, edit,
grep_search, list_directory, glob) are immediately visible as collapsed tool
groups as the model works through the memory files — identical UX to Claude Code.
Also export buildConsolidationTaskPrompt from dreamAgentPlanner so dreamCommand
can reuse the same detailed consolidation prompt that was already written.
* fix(memory): auto-allow ls/glob/grep on memory base directory
Add getMemoryBaseDir() to getDefaultPermission() allow list in ls.ts,
glob.ts, and grep.ts — mirrors the existing pattern in read-file.ts.
Without this, ListFiles/Glob/Grep on ~/.qwen/* would trigger an
approval dialog, blocking /dream at its very first step.
* fix(background): prevent permission prompt hangs in background agents
Match Claude Code's headless-agent intent: background memory agents must never
block on interactive permission prompts.
Wrap background runtime config so getApprovalMode() returns YOLO, ensuring any
ask decision is auto-approved instead of hanging forever. Add regression test
covering the wrapped approval mode.
* fix(memory): run auto extract through forked agent
Make managed auto-memory extraction follow the Claude Code architecture:
background extraction now uses a forked agent to read/write memory files
directly, instead of planning patches and applying them with a separate
filesystem pipeline.
Keep the old patch/model path only as fallback if the forked agent fails.
Add regression tests covering the new execution path and tool whitelist.
* refactor(memory): remove legacy extract fallback pipeline
Delete the old patch/model/heuristic extraction path entirely.
Managed auto-memory extract now runs only through the forked-agent
execution flow, with no planner/apply fallback stages remaining.
Also remove obsolete exports/tests and update scheduler/integration
coverage to use the forked-agent-only architecture.
* refactor(memory): move auxiliary files out of memory/ directory
meta.json, extract-cursor.json, and consolidation.lock are internal
bookkeeping files, not user-visible memories. Move them one level up
to the project state dir (parent of memory/) so that the memory/
directory contains only MEMORY.md and topic files, matching the
clean layout of the upstream reference implementation.
Add getAutoMemoryProjectStateDir() helper in paths.ts and update the
three path accessors + store.test.ts path assertions accordingly.
* fix(memory): record lastDreamAt after manual /dream run
The /dream command submits a prompt to the main agent (submit_prompt),
which writes memory files directly. Because it bypasses dreamScheduler,
meta.json was never updated and /memory always showed 'never'.
Fix by:
- Exporting writeDreamManualRunToMetadata() from dream.ts
- Adding optional onComplete callback to SubmitPromptActionReturn and
SubmitPromptResult (types.ts / commands/types.ts)
- Propagating onComplete through slashCommandProcessor.ts
- Firing onComplete after turn completion in useGeminiStream.ts
- Providing the callback in dreamCommand.ts to write lastDreamAt
* fix(memory): remove scope params from /remember in managed auto-memory mode
--global/--project are legacy save_memory tool concepts. In managed
auto-memory mode the forked agent decides the appropriate type
(user/feedback/project/reference) based on the content of the fact.
Also improve the prompt wording to explicitly ask the agent to choose
the correct type, reducing the tendency to default to 'project'.
* feat(ui): show '✦ dreaming' indicator in footer during background dream
Subscribe to getManagedAutoMemoryDreamTaskRegistry() in Footer via a
useDreamRunning() hook. While any dream task for the current project is
pending or running, display '✦ dreaming' in the right section of the
footer bar, between Debug Mode and context usage.
* refactor(memory): align dream/extract infrastructure with Claude Code patterns
Five improvements based on Claude Code parity audit:
1. Memoize getAutoMemoryRoot (paths.ts)
- Add _autoMemoryRootCache Map, keyed by projectRoot
- findCanonicalGitRoot() walks the filesystem per call; memoize avoids
repeated git-tree traversal on hot-path schedulers/scanners
- Expose clearAutoMemoryRootCache() for test teardown
2. Lock file stores PID + isProcessRunning reclaim (dreamScheduler.ts)
- acquireDreamLock() writes process.pid to the lock file body
- lockExists() reads PID and calls process.kill(pid, 0); dead/missing
PID reclaims the lock immediately instead of waiting 2h
- Stale threshold reduced to 1h (PID-reuse guard, same as CC)
3. Session scan throttle (dreamScheduler.ts)
- Add SESSION_SCAN_INTERVAL_MS = 10min (same as CC)
- Add lastSessionScanAt Map<projectRoot, number> to ManagedAutoMemoryDreamRuntime
- When time-gate passes but session-gate doesn't, throttle prevents
re-scanning the filesystem on every user turn
4. mtime-based session counting (dreamScheduler.ts)
- Replace fragile recentSessionIdsSinceDream Set in meta.json with
filesystem mtime scan (listSessionsTouchedSince)
- Mirrors Claude Code's listSessionsTouchedSince: reads session JSONL
files from Storage.getProjectDir()/chats/, filters by mtime > lastDreamAt
- Immune to meta.json corruption/loss; no per-turn metadata write
- ManagedAutoMemoryDreamRuntime accepts injectable SessionScannerFn
for clean unit testing without real session files
5. Extraction mutual exclusion extended to write_file/edit (extractScheduler.ts)
- historySliceUsesMemoryTool() now checks write_file/edit/replace/create_file
tool calls whose file_path is within isAutoMemPath()
- Previously only detected save_memory; missed direct file writes by
the main agent, causing redundant background extraction
* docs(memory): add user-facing memory docs, i18n for all locales, simplify /forget
- Add docs/users/features/memory.md: comprehensive user-facing guide covering
QWEN.md instructions, auto-memory behaviour, all memory commands, and
troubleshooting; replaces the placeholder auto-memory.md
- Update docs/users/features/_meta.ts: rename entry auto-memory → memory
- Update docs/users/features/commands.md: add /init, /remember, /forget,
/dream rows; fix /memory description; remove /init duplicate
- Update docs/users/configuration/settings.md: add memory.* settings section
(enableManagedAutoMemory, enableManagedAutoDream) between tools and permissions
- Remove /forget --apply flag: preview-then-apply flow replaced with direct
deletion; update forgetCommand.ts, en.js, zh.js accordingly
- Add all auto-memory i18n keys to de, ja, pt, ru locales (18 keys each):
Open auto-memory folder, Auto-memory/Auto-dream status lines, never/on/off,
✦ dreaming, /forget and /remember usage strings, all managed-memory messages
- Remove dead save_memory branch from extractScheduler.partWritesToMemory()
- Add ✦ dreaming indicator to Footer.tsx with i18n; fix Footer.test.tsx mocks
- Refactor MemoryDialog.tsx auto-dream status line to use i18n
- Remove save_memory tool (memoryTool.ts/test); clean up webui references
- Add extractionPlanner.ts, const.ts and associated tests
- Delete stale docs/users/configuration/memory.md and
docs/developers/tools/memory.md (content superseded)
* refactor(memory): remove all Claude Code references from comments and test names
* test(memory): remove empty placeholder test files that cause vitest to fail
* fix eslint
* fix test in windows
* fix test
* fix(memory): address critical review findings from PR #3087
- fix(read-file): narrow auto-allow from getMemoryBaseDir() (~/.qwen) to
isAutoMemPath(projectRoot) to prevent exposing settings.json / OAuth
credentials without user approval (wenshao review)
- fix(forget): per-entry deletion instead of whole-file unlink
- assign stable per-entry IDs (relativePath:index for multi-entry files)
so the model can target individual entries without removing siblings
- rewrite file keeping unmatched entries; only unlink when file becomes
empty (wenshao review)
- fix(entries): round-trip correctness for multi-entry new-format bodies
- parseAutoMemoryEntries: plain-text line closes current entry and opens
a new one (was silently ignored when current was already set)
- renderAutoMemoryBody: emit blank line between adjacent entries so the
parser can detect entry boundaries on re-read (wenshao review)
- fix(entries): resolve two CodeQL polynomial-regex alerts
- indentedMatch: \s{2,}(?:[-*]\s+)? → [\t ]{2,}(?:[-*][\t ]+)?
- topLevelMatch: :\s*(.+)$ → :[ \t]*(\S.*)$
(github-advanced-security review)
- fix(scan.test): use forward-slash literal for relativePath expectation
since listMarkdownFiles() normalises all separators to '/' on all
platforms including Windows
* fix(memory): replace isAutoMemPath startsWith with path.relative()
Using path.relative() instead of string startsWith() is more robust
across platforms — it correctly handles Windows path-separator
differences and avoids potential edge cases where a path prefix match
could succeed on non-separator boundaries.
Addresses github-actions review item 3 (PR #3087).
* feat(telemetry): add auto-memory telemetry instrumentation
Add OpenTelemetry logs + metrics for the five auto-memory lifecycle
events: extract, dream, recall, forget, and remember.
Telemetry layer (packages/core/src/telemetry/):
- constants.ts: 5 new event-name constants
(qwen-code.memory.{extract,dream,recall,forget,remember})
- types.ts: 5 new event classes with typed constructor params
(MemoryExtractEvent, MemoryDreamEvent, MemoryRecallEvent,
MemoryForgetEvent, MemoryRememberEvent)
- metrics.ts: 8 new OTel instruments (5 Counters + 3 Histograms)
with recordMemoryXxx() helpers; registered inside initializeMetrics()
- loggers.ts: logMemoryExtract/Dream/Recall/Forget/Remember() — each
emits a structured log record and calls its recordXxx() counterpart
- index.ts: re-exports all new symbols
Instrumentation call-sites:
- extractScheduler.ts ManagedAutoMemoryExtractRuntime.runTask():
emits extract event with trigger=auto, completed/failed status,
patches_count, touched_topics, and wall-clock duration
- dream.ts runManagedAutoMemoryDream():
emits dream event with trigger=auto, updated/noop status,
deduped_entries, touched_topics, and duration; covers both
agent-planner and mechanical fallback paths
- recall.ts resolveRelevantAutoMemoryPromptForQuery():
emits recall event with strategy, docs_scanned/selected, and
duration; covers model, heuristic, and none paths
- forget.ts forgetManagedAutoMemoryEntries():
emits forget event with removed_entries_count, touched_topics,
and selection_strategy (model/heuristic/none)
- rememberCommand.ts action():
emits remember event with topic=managed|legacy at command
invocation time (before agent decides the actual memory type)
* refactor(telemetry): remove memory forget/remember telemetry events
Remove EVENT_MEMORY_FORGET and EVENT_MEMORY_REMEMBER along with all
associated infrastructure that is no longer needed:
- constants.ts: remove EVENT_MEMORY_FORGET, EVENT_MEMORY_REMEMBER
- types.ts: remove MemoryForgetEvent, MemoryRememberEvent classes
- metrics.ts: remove MEMORY_FORGET_COUNT, MEMORY_REMEMBER_COUNT constants,
memoryForgetCounter, memoryRememberCounter module vars,
their initialization in initializeMetrics(), and
recordMemoryForgetMetrics(), recordMemoryRememberMetrics() functions
- loggers.ts: remove logMemoryForget(), logMemoryRemember() functions
and their imports
- index.ts: remove all re-exports for the above symbols
- memory/forget.ts: remove logMemoryForget call-site and import
- cli/rememberCommand.ts: remove logMemoryRemember call-sites and import
* change default value
* fix forked agent
* refactor(background): unify fork primitives into runForkedAgent + cleanup
- Merge runForkedQuery into runForkedAgent via TypeScript overloads:
with cacheSafeParams → GeminiChat single-turn path (ForkedQueryResult)
without cacheSafeParams → AgentHeadless multi-turn path (ForkedAgentResult)
- Delete forkedQuery.ts; move its test to background/forkedAgent.cache.test.ts
- Remove forkedQuery export from followup/index.ts
- Migrate all callers (suggestionGenerator, speculation, btwCommand, client)
to import from background/forkedAgent
- Add getFastModel() / setFastModel() to Config; expose in CLI config init
and ModelDialog / modelCommand
- Remove resolveFastModel() from AppContainer — now delegated to config.getFastModel()
- Strip Claude Code references from code comments
* fix(memory): address wenshao's critical review findings
- dream.ts: writeDreamManualRunToMetadata now persists lastDreamSessionId
and resets recentSessionIdsSinceDream, preventing auto-dream from firing
again in the same session after a manual /dream
- config.ts: gate managed auto-memory injection on getManagedAutoMemoryEnabled();
when disabled, previously saved memories are no longer injected into new sessions
- rememberCommand.ts: remove legacy save_memory branch (tool was removed);
fall back to submit_prompt directing agent to write to QWEN.md instead
- BuiltinCommandLoader.ts: only register /dream and /forget when managed
auto-memory is enabled, matching the feature's runtime availability
- forget.ts: return early in forgetManagedAutoMemoryMatches when matches is
empty, avoiding unnecessary directory scaffolding as a side effect
* fix test
* fix ci test
* feat(memory): align extract/dream agents to Claude Code patterns
- fix(client): move saveCacheSafeParams before early-return paths so
extract agents always have cache params available (fixes extract never
triggering in skipNextSpeakerCheck mode)
- feat(extract): add read-only shell tool + memory-scoped write
permissions; create inline createMemoryScopedAgentConfig() with
PermissionManager wrapper (isToolEnabled + evaluate) that allows only
read-only shell commands and write/edit within the auto-memory dir
- feat(extract): align prompt to Claude Code patterns — manifest block
listing existing files, parallel read-then-write strategy, two-step
save (memory file then index)
- feat(dream): remove mechanical fallback; runManagedAutoMemoryDream is
now agent-only and throws without config
- feat(dream): align prompt to Claude Code 4-phase structure
(Orient/Gather/Consolidate/Prune+Index); add narrow transcript grep,
relative→absolute date conversion, stale index pruning, index size cap
- fix(permissions): add isToolEnabled() to MemoryScopedPermissionManager
to prevent TypeError crash in CoreToolScheduler._schedule
- test: update dreamScheduler tests to mock dream.js; replace removed
mechanical-dedup test with scheduler infrastructure verification
* move doc to design
* refactor(memory): unify extract+dream background task management into MemoryBackgroundTaskHub
- Add memoryTaskHub.ts: single BackgroundTaskRegistry + BackgroundTaskDrainer shared
by all memory background tasks; exposes listExtractTasks() / listDreamTasks()
typed query helpers and a unified drain() method
- extractScheduler: ManagedAutoMemoryExtractRuntime accepts hub via constructor
(defaults to defaultMemoryTaskHub); test factory gets isolated fresh hub
- dreamScheduler: same pattern — sessionScanner + hub injection; BackgroundTask-
Scheduler initialized from injected hub; test factory gets isolated hub
- status.ts: replace two separate getRegistry() calls with defaultMemoryTaskHub
typed query methods
- Footer.tsx (useDreamRunning): subscribe to shared registry, filter by
DREAM_TASK_TYPE so extract tasks do not trigger the dream spinner
- index.ts: re-export memoryTaskHub.ts so defaultMemoryTaskHub/DREAM_TASK_TYPE/
EXTRACT_TASK_TYPE are available as top-level package exports
* refactor(background): introduce general-purpose BackgroundTaskHub
Replace memory-specific MemoryBackgroundTaskHub with a domain-agnostic
BackgroundTaskHub in the background/ layer. Any future background task
runtime (3rd, 4th, …) plugs in by accepting a hub via constructor
injection — no new infrastructure required.
Changes:
- Add background/taskHub.ts: BackgroundTaskHub (registry + drainer +
createScheduler() + listByType(taskType, projectRoot?)) and the
globalBackgroundTaskHub singleton. Zero knowledge of any task type.
- Delete memory/memoryTaskHub.ts: its narrow listExtractTasks /
listDreamTasks helpers are replaced by the generic listByType() call.
- Move EXTRACT_TASK_TYPE to extractScheduler.ts (owned by the runtime
that defines it); replace 3 hardcoded string literals with the const.
- Move DREAM_TASK_TYPE to dreamScheduler.ts; use hub.createScheduler()
instead of manually wiring new BackgroundTaskScheduler(reg, drain).
- status.ts: globalBackgroundTaskHub.listByType(EXTRACT_TASK_TYPE, ...)
- Footer.tsx: globalBackgroundTaskHub.registry (shared, filtered by type)
- index.ts: export background/taskHub.js; drop memory/memoryTaskHub.js
* test(background): add BackgroundTaskHub unit tests and hub isolation checks
- background/taskHub.test.ts (11 tests):
- createScheduler(): tasks registered via scheduler appear in hub registry;
multiple calls return distinct scheduler instances
- listByType(): filters by taskType, filters by projectRoot, returns []
for unknown types, two types co-exist in registry but stay separated
- drain(): resolves false on timeout, resolves true when tasks complete,
resolves true immediately when no tasks in flight
- isolation: tasks in hubA do not appear in hubB
- globalBackgroundTaskHub: is a BackgroundTaskHub instance with registry/drainer
- extractScheduler.test.ts (+1 test):
- factory-created runtimes have isolated registries; tasks in runtimeA
are invisible to runtimeB; all tasks carry EXTRACT_TASK_TYPE
- dreamScheduler.test.ts (+1 test):
- factory-created runtimes have isolated registries; tasks in runtimeA
are invisible to runtimeB; all tasks carry DREAM_TASK_TYPE
* refactor(memory): consolidate all memory state into MemoryManager
Replace BackgroundTaskRegistry/Drainer/Scheduler/Hub helper classes and
module-level globals with a single MemoryManager class owned by Config.
## Changes
### New
- packages/core/src/memory/manager.ts — MemoryManager with:
- scheduleExtract / scheduleDream (inline queuing + deduplication logic)
- recall / forget / selectForgetCandidates / forgetMatches
- getStatus / drain / appendToUserMemory
- subscribe(listener) compatible with useSyncExternalStore
- storeWith() atomic record registration (no double-notify)
- Distinct skippedReason 'scan_throttled' vs 'min_sessions' for dream
- packages/core/src/utils/forkedAgent.ts — pure cache util (moved from background/)
- packages/core/src/utils/sideQuery.ts — pure util (moved from auxiliary/)
### Deleted
- background/taskRegistry, taskDrainer, taskScheduler, taskHub and all tests
- background/forkedAgent (moved to utils/)
- auxiliary/sideQuery (moved to utils/)
- memory/extractScheduler, dreamScheduler, state and all tests
### Modified
- config/config.ts — Config owns MemoryManager instance; getMemoryManager()
- core/client.ts — all memory ops via config.getMemoryManager()
- core/client.test.ts — mock MemoryManager instead of individual modules
- memory/status.ts — accepts MemoryManager param, drops globalBackgroundTaskHub
- index.ts — memory exports reduced from 14 modules to 5 (manager/types/paths/store/const)
- cli/commands/dreamCommand.ts — via config.getMemoryManager()
- cli/commands/forgetCommand.ts — via config.getMemoryManager()
- cli/components/Footer.tsx — useSyncExternalStore replacing setInterval polling
- cli/components/Footer.test.tsx — add getMemoryManager mock
This commit is contained in:
parent
07475026f6
commit
9e2f63a1ca
137 changed files with 9809 additions and 2737 deletions
509
docs/design/auto-memory/memory-system.md
Normal file
509
docs/design/auto-memory/memory-system.md
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
# Memory 记忆管理系统
|
||||
|
||||
> 本文介绍 Qwen Code 中 **Managed Auto-Memory**(托管自动记忆)的记忆管理机制、触发时机和实现细节。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概述](#概述)
|
||||
2. [存储结构](#存储结构)
|
||||
3. [记忆类型](#记忆类型)
|
||||
4. [记忆条目格式](#记忆条目格式)
|
||||
5. [核心生命周期](#核心生命周期)
|
||||
6. [Extract — 提取](#extract--提取)
|
||||
7. [Dream — 整合](#dream--整合)
|
||||
8. [Recall — 召回](#recall--召回)
|
||||
9. [Forget — 遗忘](#forget--遗忘)
|
||||
10. [索引重建](#索引重建)
|
||||
11. [遥测埋点](#遥测埋点)
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
Managed Auto-Memory 是一套在 AI 会话过程中**自动**积累、整合和检索用户相关知识的持久化记忆系统。它通过四个核心操作维护记忆的生命周期:
|
||||
|
||||
| 操作 | 英文 | 触发方式 | 作用 |
|
||||
| ---- | ------- | -------------------------- | -------------------------------------- |
|
||||
| 提取 | Extract | 自动(每轮对话后) | 从对话记录中提炼新知识写入记忆文件 |
|
||||
| 整合 | Dream | 自动(周期性后台任务) | 对记忆文件去重、合并,保持整洁 |
|
||||
| 召回 | Recall | 自动(每轮对话前) | 检索与当前请求相关的记忆注入到系统提示 |
|
||||
| 遗忘 | Forget | 手动(用户命令 `/forget`) | 精确删除指定的记忆条目 |
|
||||
|
||||
---
|
||||
|
||||
## 存储结构
|
||||
|
||||
### 目录布局
|
||||
|
||||
```
|
||||
~/.qwen/ ← 全局基础目录(默认)
|
||||
└── projects/
|
||||
└── <sanitized-git-root>/ ← 项目标识(基于 Git 根路径)
|
||||
├── meta.json ← 元数据(提取/整合时间戳、状态)
|
||||
├── extract-cursor.json ← 提取游标(已处理的对话偏移量)
|
||||
├── consolidation.lock ← Dream 进程互斥锁
|
||||
└── memory/ ← 记忆主目录
|
||||
├── MEMORY.md ← 索引文件(自动生成,汇总所有条目)
|
||||
├── user.md ← 用户偏好记忆(示例)
|
||||
├── feedback.md ← 反馈规范记忆(示例)
|
||||
├── project/
|
||||
│ └── milestone.md ← 项目记忆(支持子目录)
|
||||
└── reference/
|
||||
└── grafana.md ← 外部资源记忆
|
||||
```
|
||||
|
||||
> **环境变量覆盖**:
|
||||
>
|
||||
> - `QWEN_CODE_MEMORY_BASE_DIR`:替换全局基础目录
|
||||
> - `QWEN_CODE_MEMORY_LOCAL=1`:改用项目内路径 `.qwen/memory/`
|
||||
|
||||
### 关键文件说明
|
||||
|
||||
| 文件 | 说明 |
|
||||
| --------------------- | ---------------------------------------------------------------------- |
|
||||
| `meta.json` | 记录最后一次 Extract / Dream 的时间、会话 ID、涉及的记忆类型、执行状态 |
|
||||
| `extract-cursor.json` | 记录当前会话已处理到对话历史的哪个偏移量,避免重复提取 |
|
||||
| `consolidation.lock` | Dream 运行时的文件锁,内容为持有者 PID,超过 1 小时自动失效 |
|
||||
| `MEMORY.md` | 所有主题文件的索引,每次 Extract/Dream 后重建,格式为 Markdown 列表 |
|
||||
|
||||
---
|
||||
|
||||
## 记忆类型
|
||||
|
||||
系统支持四种内置记忆类型,每种对应不同的信息维度:
|
||||
|
||||
| 类型 | 存储内容 | 何时写入 | 何时读取 |
|
||||
| ----------- | ----------------------------------------------------- | ---------------------------------------- | ---------------------------- |
|
||||
| `user` | 用户的角色、技能背景、工作习惯 | 了解到用户角色/偏好/知识背景时 | 回答需要根据用户背景定制时 |
|
||||
| `feedback` | 用户对 AI 行为的指导:避免什么、继续什么 | 用户纠正 AI 或确认某种非显而易见的做法时 | 影响 AI 行为方式时 |
|
||||
| `project` | 项目进展、目标、决策、截止日期、Bug 追踪 | 了解到谁在做什么、为什么、截止何时时 | 帮助 AI 理解工作背景和动机时 |
|
||||
| `reference` | 外部系统资源指针(Dashboard、工单系统、Slack 频道等) | 得知某种外部资源及其用途时 | 用户提及外部系统或相关信息时 |
|
||||
|
||||
**不应该存入记忆的内容**:代码模式/约定、Git 历史、调试方案、临时任务状态、已在 QWEN.md/AGENTS.md 中记录的内容。
|
||||
|
||||
---
|
||||
|
||||
## 记忆条目格式
|
||||
|
||||
每个主题文件使用 **YAML frontmatter + Markdown body** 格式:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: 记忆名称
|
||||
description: 一句话描述(用于判断召回相关性,要具体)
|
||||
type: user|feedback|project|reference
|
||||
---
|
||||
|
||||
记忆主体内容(summary 行)
|
||||
|
||||
Why: 背后原因(让 AI 能理解边界情况而不是盲目遵守规则)
|
||||
How to apply: 适用场景和使用方式
|
||||
```
|
||||
|
||||
对于 `feedback` 和 `project` 类型,强烈建议填写 `Why` 和 `How to apply`,使记忆在边界情况下仍能正确应用。
|
||||
|
||||
---
|
||||
|
||||
## 核心生命周期
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([用户发送请求]) --> B
|
||||
|
||||
subgraph "召回 Recall"
|
||||
B[扫描所有主题文件] --> C{文档数量和\n查询内容是否有效?}
|
||||
C -- 否 --> D[返回空提示词\nstrategy: none]
|
||||
C -- 是 --> E{是否配置了 Config?}
|
||||
E -- 是 --> F[模型驱动选择\nside query]
|
||||
F --> G{选出相关文档?}
|
||||
G -- 是 --> H[strategy: model]
|
||||
G -- 否 --> I[strategy: none]
|
||||
E -- 否 --> J[启发式关键词评分]
|
||||
F -- 失败 --> J
|
||||
J --> K{有得分 > 0 的文档?}
|
||||
K -- 是 --> L[strategy: heuristic]
|
||||
K -- 否 --> I
|
||||
H --> M[构建 Relevant Memory 提示词\n注入系统提示]
|
||||
L --> M
|
||||
I --> N[不注入记忆]
|
||||
end
|
||||
|
||||
M --> O([AI 处理请求])
|
||||
N --> O
|
||||
D --> O
|
||||
|
||||
O --> P([AI 返回响应])
|
||||
|
||||
subgraph "提取 Extract(后台)"
|
||||
P --> Q{本轮 AI 是否\n直接写了记忆文件?}
|
||||
Q -- 是 --> R[跳过\nmemory_tool]
|
||||
Q -- 否 --> S{提取任务是否\n正在运行?}
|
||||
S -- 是 --> T[放入队列或跳过\nalready_running / queued]
|
||||
S -- 否 --> U[加载未处理的对话切片\n基于 extract cursor]
|
||||
U --> V[调用提取 Agent\nrunAutoMemoryExtractionByAgent]
|
||||
V --> W[去重规范化 patches]
|
||||
W --> X{有 touched topics?}
|
||||
X -- 是 --> Y[更新 meta.json\n重建 MEMORY.md 索引]
|
||||
X -- 否 --> Z[仅更新 extract cursor]
|
||||
Y --> Z
|
||||
end
|
||||
|
||||
subgraph "Dream 整合(后台,周期性)"
|
||||
P --> AA{Dream 调度门控检查}
|
||||
AA --> AB{是否同一会话?}
|
||||
AB -- 是 --> AC[跳过\nsame_session]
|
||||
AB -- 否 --> AD{距上次 Dream\n≥ 24 小时?}
|
||||
AD -- 否 --> AE[跳过\nmin_hours]
|
||||
AD -- 是 --> AF{距上次 Dream 后\n新会话数 ≥ 5?}
|
||||
AF -- 否 --> AG[跳过\nmin_sessions]
|
||||
AF -- 是 --> AH{consolidation.lock\n是否存在?}
|
||||
AH -- 是 --> AI[跳过\nlocked]
|
||||
AH -- 否 --> AJ[获取锁\n写入 PID]
|
||||
AJ --> AK{是否配置了 Config?}
|
||||
AK -- 是 --> AL[Agent 路径\nplanManagedAutoMemoryDreamByAgent]
|
||||
AL --> AM{Agent 是否触碰了文件?}
|
||||
AM -- 是 --> AN[记录触碰的 topics]
|
||||
AM -- "否/失败" --> AO
|
||||
AK -- 否 --> AO[机械去重路径\n解析+去重+按字母排序]
|
||||
AO --> AP[写回更新后的主题文件]
|
||||
AN --> AQ[重建 MEMORY.md 索引\n更新 meta.json]
|
||||
AP --> AQ
|
||||
AQ --> AR[释放锁]
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extract — 提取
|
||||
|
||||
### 触发时机
|
||||
|
||||
每次 AI 完成一轮响应后,由 `scheduleAutoMemoryExtract` 自动触发(后台非阻塞)。
|
||||
|
||||
### 调度逻辑(`extractScheduler.ts`)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[scheduleAutoMemoryExtract 被调用] --> B{本轮历史记录中\n是否有写记忆文件的工具调用?}
|
||||
B -- 是 --> C[登记 skipped 任务\n原因: memory_tool]
|
||||
B -- 否 --> D{isExtractRunning?}
|
||||
D -- 是 --> E{是否已有 queued 请求?}
|
||||
E -- 是 --> F[更新 queued 请求的\nhistory 参数]
|
||||
E -- 否 --> G[注册 pending 任务\n放入 queue]
|
||||
D -- 否 --> H[注册 running 任务\n调用 runTask]
|
||||
H --> I[markExtractRunning\nsetCurrentTaskId]
|
||||
I --> J[runAutoMemoryExtract]
|
||||
J --> K[任务完成]
|
||||
K --> L[clearExtractRunning\n检查 queue → startQueuedIfNeeded]
|
||||
F --> M[返回 skipped: queued]
|
||||
G --> M
|
||||
C --> N[返回 skipped: memory_tool]
|
||||
```
|
||||
|
||||
**跳过原因说明**:
|
||||
|
||||
| 原因 | 含义 |
|
||||
| ----------------- | ----------------------------------------------- |
|
||||
| `memory_tool` | 本轮主 Agent 已直接写了记忆文件,跳过以避免冲突 |
|
||||
| `already_running` | 提取正在进行且无法入队 |
|
||||
| `queued` | 已有提取在运行,本次请求已入队 |
|
||||
|
||||
### 核心提取流程(`extract.ts`)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[runAutoMemoryExtract] --> B[ensureAutoMemoryScaffold\n初始化目录和文件]
|
||||
B --> C[buildTranscriptMessages\n将 Content[] 转换为带 offset 的消息列表]
|
||||
C --> D[readExtractCursor\n读取上次处理到的位置]
|
||||
D --> E[loadUnprocessedTranscriptSlice\n截取未处理的消息段]
|
||||
E --> F{slice 为空?}
|
||||
F -- 是 --> G[返回无 patches 结果]
|
||||
F -- 否 --> H[runAutoMemoryExtractionByAgent\n调用 forked agent 提取 patches]
|
||||
H --> I[dedupeExtractPatches\n去重+规范化]
|
||||
I --> J{有 touched topics?}
|
||||
J -- 是 --> K[bumpMetadata\n更新 meta.json]
|
||||
K --> L[rebuildManagedAutoMemoryIndex\n重建 MEMORY.md]
|
||||
L --> M[writeExtractCursor\n记录最新 offset]
|
||||
J -- 否 --> M
|
||||
M --> N[返回 AutoMemoryExtractResult]
|
||||
```
|
||||
|
||||
**提取游标(Cursor)**:
|
||||
|
||||
- 字段:`{ sessionId, processedOffset, updatedAt }`
|
||||
- 每次提取后更新 `processedOffset` 为当前历史长度
|
||||
- 下次提取时,只处理 `offset >= processedOffset` 的消息
|
||||
- 跨会话时(`sessionId` 变化)从偏移量 0 重新开始
|
||||
|
||||
**Patch 过滤规则**:
|
||||
|
||||
- 摘要长度 < 12 字符 → 丢弃
|
||||
- 摘要以 `?` 结尾 → 丢弃(疑问句)
|
||||
- 包含临时性关键词(today/now/currently/temporary 等)→ 丢弃
|
||||
- 相同 `topic:summary` 组合 → 去重
|
||||
|
||||
---
|
||||
|
||||
## Dream — 整合
|
||||
|
||||
### 触发时机
|
||||
|
||||
每次 AI 完成一轮响应后,由 `scheduleManagedAutoMemoryDream` 自动触发(后台非阻塞)。但受多个门控条件保护,大多数情况下会被跳过。
|
||||
|
||||
### 调度门控(`dreamScheduler.ts`)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[scheduleManagedAutoMemoryDream 被调用] --> B{Dream 功能是否启用?}
|
||||
B -- 否 --> C[跳过: disabled]
|
||||
B -- 是 --> D[ensureAutoMemoryScaffold\n读取 lastDreamSessionId]
|
||||
D --> E{当前 sessionId\n== lastDreamSessionId?}
|
||||
E -- 是 --> F[跳过: same_session]
|
||||
E -- 否 --> G{elapsedHours ≥ 24h\n或从未 dream?}
|
||||
G -- 否 --> H[跳过: min_hours]
|
||||
G -- 是 --> I{距上次 session scan\n< 10 分钟?}
|
||||
I -- 是 --> J[跳过: min_sessions\n等待下次扫描窗口]
|
||||
I -- 否 --> K[扫描 chats/*.jsonl mtime\n统计上次 Dream 后的新会话数]
|
||||
K --> L{新会话数 ≥ 5?}
|
||||
L -- 否 --> M[跳过: min_sessions]
|
||||
L -- 是 --> N{lockExists?\nPID 检查 + 过期检查}
|
||||
N -- 是 --> O[跳过: locked]
|
||||
N -- 否 --> P{dedupeKey 是否已有\n同项目 Dream 任务?}
|
||||
P -- 是 --> Q[跳过: running\n返回已有 taskId]
|
||||
P -- 否 --> R[调度后台任务\nBgTaskScheduler]
|
||||
R --> S[acquireDreamLock\n写入 PID 到 consolidation.lock]
|
||||
S --> T[runManagedAutoMemoryDream]
|
||||
T --> U[更新 meta.json\n释放锁]
|
||||
```
|
||||
|
||||
**门控参数**:
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
| -------------------------- | -------- | ----------------------------- |
|
||||
| `minHoursBetweenDreams` | 24 小时 | 两次 Dream 之间的最小时间间隔 |
|
||||
| `minSessionsBetweenDreams` | 5 个会话 | 触发 Dream 所需的最小新会话数 |
|
||||
| `SESSION_SCAN_INTERVAL_MS` | 10 分钟 | 会话文件扫描的节流间隔 |
|
||||
| `DREAM_LOCK_STALE_MS` | 1 小时 | lock 文件被视为过期的时间阈值 |
|
||||
|
||||
**锁机制**:
|
||||
|
||||
- lock 文件位于 `<project-state-dir>/consolidation.lock`
|
||||
- 内容为持有进程的 PID
|
||||
- 检查时:若 PID 进程已不存在(`kill(pid, 0)` 失败)或 lock 超过 1 小时 → 视为过期,自动清除
|
||||
|
||||
### 整合执行流程(`dream.ts`)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[runManagedAutoMemoryDream] --> B{是否配置了 Config?}
|
||||
B -- 是 --> C[Agent 路径\nplanManagedAutoMemoryDreamByAgent]
|
||||
C --> D{Agent 是否修改了文件?}
|
||||
D -- 是 --> E[从文件路径推断 touched topics]
|
||||
E --> F[bumpMetadata\n重建 MEMORY.md 索引]
|
||||
F --> G[updateDreamMetadataResult]
|
||||
G --> H[记录遥测事件]
|
||||
H --> I[返回结果]
|
||||
B -- 否 --> J[机械去重路径]
|
||||
C -- 抛出异常 --> J
|
||||
D -- 否 --> J
|
||||
|
||||
J --> K[scanAutoMemoryTopicDocuments\n读取所有主题文件]
|
||||
K --> L[对每个文件执行 buildDreamedBody]
|
||||
L --> M[解析 entries → 按 summary 去重\n按字母升序排序 → 重新渲染]
|
||||
M --> N{body 有变化?}
|
||||
N -- 是 --> O[写回文件]
|
||||
O --> P[记录 touched topic]
|
||||
N --> Q[检查跨文件重复\ndedupeKey = type:summary]
|
||||
Q --> R{发现重复文件?}
|
||||
R -- 是 --> S[合并 entries 到 canonical 文件\n删除重复文件]
|
||||
S --> P
|
||||
R -- 否 --> T{有 touched topics?}
|
||||
P --> T
|
||||
T -- 是 --> U[bumpMetadata\n重建 MEMORY.md 索引]
|
||||
U --> V[updateDreamMetadataResult\n记录遥测 → 返回结果]
|
||||
T -- 否 --> V
|
||||
```
|
||||
|
||||
**机械去重逻辑**:
|
||||
|
||||
1. 对每个主题文件内部:按 `summary.toLowerCase()` 去重,合并 `why`/`howToApply` 字段
|
||||
2. 按 summary 字母顺序重新排序
|
||||
3. 跨文件:相同 `type:summary` 的条目合并到最先发现的文件,删除重复文件
|
||||
|
||||
---
|
||||
|
||||
## Recall — 召回
|
||||
|
||||
### 触发时机
|
||||
|
||||
每轮 AI 处理用户请求之前,由 `resolveRelevantAutoMemoryPromptForQuery` 自动触发,将相关记忆注入系统提示词。
|
||||
|
||||
### 召回流程(`recall.ts`)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[resolveRelevantAutoMemoryPromptForQuery] --> B[scanAutoMemoryTopicDocuments\n扫描所有主题文件]
|
||||
B --> C[filterExcludedAutoMemoryDocuments\n过滤本轮已写入的文件]
|
||||
C --> D{query 为空\n或 docs 为空\n或 limit <= 0?}
|
||||
D -- 是 --> E[返回空 prompt\nstrategy: none]
|
||||
D -- 否 --> F{是否配置了 Config?}
|
||||
F -- 是 --> G[selectRelevantAutoMemoryDocumentsByModel\n发起 side query 请求模型选择]
|
||||
G --> H{模型返回结果?}
|
||||
H -- 有文档 --> I[strategy: model]
|
||||
H -- 无文档 --> J[strategy: none\n仍然返回空]
|
||||
G -- "失败/异常" --> K[回退到启发式选择]
|
||||
F -- 否 --> K
|
||||
K --> L[tokenize query\n提取 ≥3 字符的 token]
|
||||
L --> M[scoreDocument 打分\n关键词匹配 +2 / 类型关键词 +1 / 有内容 +1]
|
||||
M --> N[过滤 score=0 的文档\n按分数降序排列,取 Top 5]
|
||||
N --> O{有得分文档?}
|
||||
O -- 是 --> P[strategy: heuristic]
|
||||
O -- 否 --> J
|
||||
I --> Q[buildRelevantAutoMemoryPrompt\n构建 Relevant Memory 区块]
|
||||
P --> Q
|
||||
Q --> R[返回注入主系统提示的 prompt 片段]
|
||||
```
|
||||
|
||||
**评分规则(启发式)**:
|
||||
|
||||
| 条件 | 加分 |
|
||||
| -------------------------------- | ---------------- |
|
||||
| query token 出现在文档内容中 | +2(每个 token) |
|
||||
| query token 是该类型的特征关键词 | +1(每个 token) |
|
||||
| 文档 body 非空 | +1 |
|
||||
|
||||
**每种类型的特征关键词**:
|
||||
|
||||
- `user`:user, preference, background, role, terse
|
||||
- `feedback`:feedback, rule, avoid, style, summary
|
||||
- `project`:project, goal, incident, deadline, release
|
||||
- `reference`:reference, dashboard, ticket, docs, link
|
||||
|
||||
**Prompt 构建规则**:
|
||||
|
||||
- 最多注入 5 篇文档(`MAX_RELEVANT_DOCS`)
|
||||
- 每篇文档 body 截断至 1200 字符(`MAX_DOC_BODY_CHARS`)
|
||||
- 超出截断时追加提示:"NOTE: Relevant memory truncated for prompt budget."
|
||||
- 包含文档的新鲜度信息(基于文件 mtime)
|
||||
|
||||
---
|
||||
|
||||
## Forget — 遗忘
|
||||
|
||||
### 触发时机
|
||||
|
||||
由用户手动执行 `/forget <query>` 命令触发。
|
||||
|
||||
### 遗忘流程(`forget.ts`)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[forgetManagedAutoMemoryEntries\nquery + config] --> B[ensureAutoMemoryScaffold]
|
||||
B --> C[listIndexedForgetCandidates\n扫描所有文件的所有 entry]
|
||||
C --> D[为每个 entry 生成稳定 ID\n单 entry 文件: relativePath\n多 entry 文件: relativePath:index]
|
||||
D --> E{是否配置了 Config?}
|
||||
E -- 是 --> F[selectByModel\n构建 selection prompt\n发起 side query temperature=0]
|
||||
F --> G{模型选择成功?}
|
||||
G -- 是 --> H[strategy: model]
|
||||
G -- 失败 --> I[selectByHeuristic\n关键词匹配]
|
||||
E -- 否 --> I
|
||||
I --> J[strategy: heuristic]
|
||||
H --> K[遍历选中的 candidates]
|
||||
J --> K
|
||||
K --> L{entries.length == 1?}
|
||||
L -- 是 --> M[删除整个文件\nfs.unlink]
|
||||
L -- 否 --> N[解析文件中的所有 entries\n移除目标 entry\n重新渲染写回]
|
||||
M --> O[记录 removedEntries]
|
||||
N --> O
|
||||
O --> P{有 touched topics?}
|
||||
P -- 是 --> Q[bumpMetadata\n重建 MEMORY.md 索引]
|
||||
P --> R[返回 AutoMemoryForgetResult]
|
||||
Q --> R
|
||||
```
|
||||
|
||||
**Entry ID 设计**:
|
||||
|
||||
- 单条目文件(常见情况):`relativePath`(如 `feedback/no-summary.md`)
|
||||
- 多条目文件:`relativePath:index`(如 `feedback/style.md:2`)
|
||||
- 使用稳定 ID 使模型可以精确定位条目而不影响同文件的其他条目
|
||||
|
||||
---
|
||||
|
||||
## 索引重建
|
||||
|
||||
`MEMORY.md` 是所有主题文件的导航索引,每次 Extract 或 Dream 后调用 `rebuildManagedAutoMemoryIndex` 重建:
|
||||
|
||||
```
|
||||
- [用户偏好](user/preferences.md) — 用户是资深 Go 工程师,第一次接触 React
|
||||
- [反馈规范](feedback/style.md) — 保持回复简洁,不要尾部总结
|
||||
- [项目里程碑](project/milestone.md) — 移动端发布切分支前的合并冻结窗口
|
||||
```
|
||||
|
||||
**索引限制**:
|
||||
|
||||
- 每行最多 150 字符(超出用 `…` 截断)
|
||||
- 最多 200 行
|
||||
- 总大小不超过 25,000 字节
|
||||
|
||||
---
|
||||
|
||||
## 遥测埋点
|
||||
|
||||
系统内置三类遥测事件,用于监控记忆操作的性能和效果:
|
||||
|
||||
### Extract 遥测
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------------- | --------------------------- | ----------------------- |
|
||||
| `trigger` | `'auto'` | 触发方式(当前仅自动) |
|
||||
| `status` | `'completed'` \| `'failed'` | 执行结果 |
|
||||
| `patches_count` | number | 提取到的有效 patch 数量 |
|
||||
| `touched_topics` | string[] | 被写入的记忆类型列表 |
|
||||
| `duration_ms` | number | 总耗时(毫秒) |
|
||||
|
||||
### Dream 遥测
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ----------------- | ------------------------------------- | ---------------------- |
|
||||
| `trigger` | `'auto'` | 触发方式 |
|
||||
| `status` | `'updated'` \| `'noop'` \| `'failed'` | 执行结果 |
|
||||
| `deduped_entries` | number | 机械路径去重的条目数量 |
|
||||
| `touched_topics` | string[] | 被修改的记忆类型列表 |
|
||||
| `duration_ms` | number | 总耗时(毫秒) |
|
||||
|
||||
### Recall 遥测
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --------------- | -------------------------------------- | ---------------- |
|
||||
| `query_length` | number | 查询字符串长度 |
|
||||
| `docs_scanned` | number | 扫描的文档总数 |
|
||||
| `docs_selected` | number | 最终注入的文档数 |
|
||||
| `strategy` | `'none'` \| `'heuristic'` \| `'model'` | 选择策略 |
|
||||
| `duration_ms` | number | 总耗时(毫秒) |
|
||||
|
||||
---
|
||||
|
||||
## 相关源文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
| ---------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `packages/core/src/memory/types.ts` | 类型定义:`AutoMemoryType`、`AutoMemoryMetadata`、`AutoMemoryExtractCursor` |
|
||||
| `packages/core/src/memory/paths.ts` | 路径计算:`getAutoMemoryRoot`、`isAutoMemPath`、各类文件路径 helpers |
|
||||
| `packages/core/src/memory/store.ts` | 脚手架初始化:`ensureAutoMemoryScaffold`,索引/元数据读写 |
|
||||
| `packages/core/src/memory/scan.ts` | 扫描主题文件:`scanAutoMemoryTopicDocuments`,解析 frontmatter |
|
||||
| `packages/core/src/memory/entries.ts` | 条目解析和渲染:`parseAutoMemoryEntries`、`renderAutoMemoryBody` |
|
||||
| `packages/core/src/memory/extract.ts` | 提取核心逻辑:`runAutoMemoryExtract`,游标管理,patch 去重 |
|
||||
| `packages/core/src/memory/extractScheduler.ts` | 提取调度器:`ManagedAutoMemoryExtractRuntime`,队列/运行状态机 |
|
||||
| `packages/core/src/memory/extractionAgentPlanner.ts` | 提取 Agent:`runAutoMemoryExtractionByAgent` |
|
||||
| `packages/core/src/memory/dream.ts` | 整合核心逻辑:`runManagedAutoMemoryDream`,Agent 路径 + 机械去重 |
|
||||
| `packages/core/src/memory/dreamScheduler.ts` | 整合调度器:`ManagedAutoMemoryDreamRuntime`,门控检查,锁管理 |
|
||||
| `packages/core/src/memory/dreamAgentPlanner.ts` | 整合 Agent:`planManagedAutoMemoryDreamByAgent` |
|
||||
| `packages/core/src/memory/recall.ts` | 召回逻辑:`resolveRelevantAutoMemoryPromptForQuery`,启发式+模型双路径 |
|
||||
| `packages/core/src/memory/forget.ts` | 遗忘逻辑:`forgetManagedAutoMemoryEntries`,候选生成+精确删除 |
|
||||
| `packages/core/src/memory/indexer.ts` | 索引重建:`rebuildManagedAutoMemoryIndex`,`buildManagedAutoMemoryIndex` |
|
||||
| `packages/core/src/memory/prompt.ts` | 系统提示模板:记忆类型说明、格式示例、使用规范 |
|
||||
| `packages/core/src/memory/governance.ts` | 治理建议类型:`AutoMemoryGovernanceSuggestionType` |
|
||||
| `packages/core/src/memory/state.ts` | 提取运行状态:`isExtractRunning`、`markExtractRunning`、`clearExtractRunning` |
|
||||
| `packages/core/src/memory/memoryAge.ts` | 新鲜度描述:`memoryAge`、`memoryFreshnessText` |
|
||||
|
|
@ -8,7 +8,6 @@ export default {
|
|||
'exit-plan-mode': 'Exit Plan Mode',
|
||||
'web-fetch': 'Web Fetch',
|
||||
'web-search': 'Web Search',
|
||||
memory: 'Memory',
|
||||
'mcp-server': 'MCP Servers',
|
||||
sandbox: 'Sandboxing',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
# Memory Tool (`save_memory`)
|
||||
|
||||
This document describes the `save_memory` tool for Qwen Code.
|
||||
|
||||
## Description
|
||||
|
||||
Use `save_memory` to save and recall information across your Qwen Code sessions. With `save_memory`, you can direct the CLI to remember key details across sessions, providing personalized and directed assistance.
|
||||
|
||||
### Arguments
|
||||
|
||||
`save_memory` takes one argument:
|
||||
|
||||
- `fact` (string, required): The specific fact or piece of information to remember. This should be a clear, self-contained statement written in natural language.
|
||||
|
||||
## How to use `save_memory` with Qwen Code
|
||||
|
||||
The tool appends the provided `fact` to your context file in the user's home directory (`~/.qwen/QWEN.md` by default). This filename can be configured via `contextFileName`.
|
||||
|
||||
Once added, the facts are stored under a `## Qwen Added Memories` section. This file is loaded as context in subsequent sessions, allowing the CLI to recall the saved information.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
save_memory(fact="Your fact here.")
|
||||
```
|
||||
|
||||
### `save_memory` examples
|
||||
|
||||
Remember a user preference:
|
||||
|
||||
```
|
||||
save_memory(fact="My preferred programming language is Python.")
|
||||
```
|
||||
|
||||
Store a project-specific detail:
|
||||
|
||||
```
|
||||
save_memory(fact="The project I'm currently working on is called 'qwen-code'.")
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
- **General usage:** This tool should be used for concise, important facts. It is not intended for storing large amounts of data or conversational history.
|
||||
- **Memory file:** The memory file is a plain text Markdown file, so you can view and edit it manually if needed.
|
||||
|
|
@ -254,6 +254,15 @@ If you are experiencing performance issues with file searching (e.g., with `@` c
|
|||
>
|
||||
> **Migrating from `tools.core` / `tools.exclude` / `tools.allowed`:** These legacy settings are **deprecated** and automatically migrated to the new `permissions` format on first load. Prefer configuring `permissions.allow` / `permissions.deny` directly. Use `/permissions` to manage rules interactively.
|
||||
|
||||
#### memory
|
||||
|
||||
| Setting | Type | Description | Default |
|
||||
| -------------------------------- | ------- | --------------------------------------------------------------------------------- | ------- |
|
||||
| `memory.enableManagedAutoMemory` | boolean | Enable background extraction of memories from conversations. | `true` |
|
||||
| `memory.enableManagedAutoDream` | boolean | Enable automatic consolidation (deduplication and cleanup) of collected memories. | `false` |
|
||||
|
||||
See [Memory](../features/memory) for details on how auto-memory works and how to use the `/memory`, `/remember`, and `/dream` commands.
|
||||
|
||||
#### permissions
|
||||
|
||||
The permissions system provides fine-grained control over which tools can run, which require confirmation, and which are blocked.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export default {
|
|||
'sub-agents': 'SubAgents',
|
||||
arena: 'Agent Arena',
|
||||
skills: 'Skills',
|
||||
memory: 'Memory',
|
||||
headless: 'Headless Mode',
|
||||
checkpointing: {
|
||||
display: 'hidden',
|
||||
|
|
|
|||
|
|
@ -71,7 +71,10 @@ Commands for managing AI tools and models.
|
|||
| `/model` | Switch model used in current session | `/model` |
|
||||
| `/model --fast` | Set a lighter model for prompt suggestions | `/model --fast qwen3-coder-flash` |
|
||||
| `/extensions` | List all active extensions in current session | `/extensions` |
|
||||
| `/memory` | Manage AI's instruction context | `/memory add Important Info` |
|
||||
| `/memory` | Open the Memory Manager dialog | `/memory` |
|
||||
| `/remember` | Save a durable memory | `/remember Prefer terse responses` |
|
||||
| `/forget` | Remove matching entries from auto-memory | `/forget <query>` |
|
||||
| `/dream` | Manually run auto-memory consolidation | `/dream` |
|
||||
|
||||
### 1.5 Built-in Skills
|
||||
|
||||
|
|
|
|||
166
docs/users/features/memory.md
Normal file
166
docs/users/features/memory.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# Memory
|
||||
|
||||
Every Qwen Code session starts with a fresh context window. Two mechanisms carry knowledge across sessions so you don't have to re-explain yourself every time:
|
||||
|
||||
- **QWEN.md** — instructions *you* write once and Qwen reads every session
|
||||
- **Auto-memory** — notes Qwen writes itself based on what it learns from you
|
||||
|
||||
---
|
||||
|
||||
## QWEN.md: your instructions to Qwen
|
||||
|
||||
QWEN.md is a plain text file where you write things Qwen should always know about your project or your preferences. Think of it as a permanent briefing that loads at the start of every conversation.
|
||||
|
||||
### What to put in QWEN.md
|
||||
|
||||
Add things you'd otherwise have to repeat every session:
|
||||
|
||||
- Build and test commands (`npm run test`, `make build`)
|
||||
- Coding conventions your team follows ("all new files must have JSDoc comments")
|
||||
- Architectural decisions ("we use the repository pattern, never call the database directly from controllers")
|
||||
- Personal preferences ("always use pnpm, not npm")
|
||||
|
||||
Don't include things Qwen can figure out by reading your code. QWEN.md works best when it's short and specific — the longer it gets, the less reliably Qwen follows it.
|
||||
|
||||
### Where to create QWEN.md
|
||||
|
||||
| File | Who it applies to |
|
||||
|---|---|
|
||||
| `~/.qwen/QWEN.md` | You, across all your projects |
|
||||
| `QWEN.md` in the project root | Your whole team (commit it to source control) |
|
||||
|
||||
You can have both. Qwen loads all QWEN.md files it finds when you start a session — your personal one plus any in the project.
|
||||
|
||||
If your repository already has an `AGENTS.md` file for other AI tools, Qwen reads that too. No need to duplicate instructions.
|
||||
|
||||
### Generate one automatically with `/init`
|
||||
|
||||
Run `/init` and Qwen will analyze your codebase to create a starter QWEN.md with build commands, test instructions, and conventions it finds. If one already exists, it suggests additions instead of overwriting.
|
||||
|
||||
### Reference other files
|
||||
|
||||
You can point QWEN.md at other files so Qwen reads them too:
|
||||
|
||||
```markdown
|
||||
See @README.md for project overview.
|
||||
|
||||
# Conventions
|
||||
- Git workflow: @docs/git-workflow.md
|
||||
```
|
||||
|
||||
Use `@path/to/file` anywhere in QWEN.md. Relative paths resolve from the QWEN.md file itself.
|
||||
|
||||
---
|
||||
|
||||
## Auto-memory: what Qwen learns about you
|
||||
|
||||
Auto-memory runs in the background. After each of your conversations, Qwen quietly saves useful things it learned — your preferences, feedback you gave, project context — so it can use them in future sessions without you repeating yourself.
|
||||
|
||||
This is different from QWEN.md: you don't write it, Qwen does.
|
||||
|
||||
### What Qwen saves
|
||||
|
||||
Qwen looks for four kinds of things worth remembering:
|
||||
|
||||
| What | Examples |
|
||||
|---|---|
|
||||
| **About you** | Your role, background, how you like to work |
|
||||
| **Your feedback** | Corrections you made, approaches you confirmed |
|
||||
| **Project context** | Ongoing work, decisions, goals not obvious from the code |
|
||||
| **External references** | Dashboards, ticket trackers, docs links you mentioned |
|
||||
|
||||
Qwen doesn't save everything — only things that would actually be useful next time.
|
||||
|
||||
### Where it's stored
|
||||
|
||||
Auto-memory files live at `~/.qwen/projects/<project>/memory/`. All branches and worktrees of the same repository share the same memory folder, so what Qwen learns in one branch is available in others.
|
||||
|
||||
Everything saved is plain markdown — you can open, edit, or delete any file at any time.
|
||||
|
||||
### Periodic cleanup
|
||||
|
||||
Qwen periodically goes through its saved memories to remove duplicates and clean up outdated entries. This runs automatically in the background once a day after enough sessions have accumulated. You can trigger it manually with `/dream` if you want it to run now.
|
||||
|
||||
While cleanup is running, **✦ dreaming** appears in the corner of the screen. Your session continues normally.
|
||||
|
||||
### Turning it on or off
|
||||
|
||||
Auto-memory is on by default. To toggle it, open `/memory` and use the switches at the top. You can turn off just the automatic saving, just the periodic cleanup, or both.
|
||||
|
||||
You can also set them in `~/.qwen/settings.json` (applies to all projects) or `.qwen/settings.json` (this project only):
|
||||
|
||||
```json
|
||||
{
|
||||
"memory": {
|
||||
"enableManagedAutoMemory": true,
|
||||
"enableManagedAutoDream": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### `/memory`
|
||||
|
||||
Opens the Memory panel. From here you can:
|
||||
|
||||
- Turn auto-memory saving on or off
|
||||
- Turn periodic cleanup (dream) on or off
|
||||
- Open your personal QWEN.md (`~/.qwen/QWEN.md`)
|
||||
- Open the project QWEN.md
|
||||
- Browse the auto-memory folder
|
||||
|
||||
### `/init`
|
||||
|
||||
Generates a starter QWEN.md for your project. Qwen reads your codebase and fills in build commands, test instructions, and conventions it discovers.
|
||||
|
||||
### `/remember <text>`
|
||||
|
||||
Immediately saves something to auto-memory without waiting for Qwen to pick it up automatically:
|
||||
|
||||
```
|
||||
/remember always use snake_case for Python variable names
|
||||
/remember the staging environment is at staging.example.com
|
||||
```
|
||||
|
||||
### `/forget <text>`
|
||||
|
||||
Removes auto-memory entries that match your description:
|
||||
|
||||
```
|
||||
/forget old workaround for the login bug
|
||||
```
|
||||
|
||||
### `/dream`
|
||||
|
||||
Runs the memory cleanup now instead of waiting for the automatic schedule:
|
||||
|
||||
```
|
||||
/dream
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Qwen isn't following my QWEN.md
|
||||
|
||||
Open `/memory` to see which files are loaded. If your file isn't listed, Qwen can't see it — make sure it's in the project root or `~/.qwen/`.
|
||||
|
||||
Instructions work better when they're specific:
|
||||
- ✓ `Use 2-space indentation for TypeScript files`
|
||||
- ✗ `Format code nicely`
|
||||
|
||||
If you have multiple QWEN.md files with conflicting instructions, Qwen may behave inconsistently. Review them and remove any contradictions.
|
||||
|
||||
### I want to see what Qwen has saved
|
||||
|
||||
Run `/memory` and select **Open auto-memory folder**. All saved memories are readable markdown files you can browse, edit, or delete.
|
||||
|
||||
### Qwen keeps forgetting things
|
||||
|
||||
If auto-memory is on but Qwen doesn't seem to remember things across sessions, try running `/dream` to force a cleanup pass. Also check `/memory` to confirm both toggles are enabled.
|
||||
|
||||
For things you always want Qwen to remember, add them to QWEN.md instead — auto-memory is best-effort, QWEN.md is guaranteed.
|
||||
Loading…
Add table
Add a link
Reference in a new issue