mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-25 14:06:18 +00:00
* feat(memory): add cognitiveStyle for collaboration preferences Extend cross-session memory with user.cognitiveStyle and cognitive facts, backward-compatible normalization, prompt injection as Thinking Style, and settings UI. Add design notes and unit tests. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(memory): normalize legacy memory imports on frontend Add normalizeMemoryPayload aligned with backend normalize_memory_data, use it for Settings import and API reads, and add unit/regression tests. Document schema-evolution steps and sync FACT_EXTRACTION_PROMPT cognitive category. * fix(memory): harden legacy memory normalization * fix(memory): keep import envelope validation strict * fix(memory): keep unknown fields through frontend import normalization normalizeMemoryPayload rebuilt the memory document from a section whitelist, so every key it did not name was dropped. Because readMemoryResponse routes all memory API responses through it, the loss showed up on read, export, and import alike: revision vanished, and any future backend field would silently break lossless export/import. The Gateway models are extra="allow" and deermem normalizes additively, which left the frontend as the only lossy hop in the chain. Spread before overriding at each nesting level so normalization only validates and defaults contract-owned fields, leaving the envelope check as strict as before. Covered by a unit test on the strict import path, two backend tests for extension preservation and caller immutability, and an E2E round-trip that asserts on the captured import request body. * docs(memory): state the normalization invariant that actually holds The docstring cited `display` as a backend-driven field alongside `revision`, but #4546 was closed unmerged and no such field exists in the contract. It also promised lossless export/import, which the Gateway's `extra="ignore"` models preclude for any undeclared field. Replace both with the invariant the code is really defending: the frontend must not be narrower than the Gateway contract, using the declared top-level `revision` as the concrete example. * fix(memory): preserve legacy migration and reject lossy imports * fix(memory): align legacy fact normalization defaults * test(memory): use authenticated app for import regression --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
141 lines
7.8 KiB
Markdown
141 lines
7.8 KiB
Markdown
# Memory: Cognitive Style
|
||
|
||
Design note for contributors. Explains `user.cognitiveStyle` and related memory/prompt touchpoints.
|
||
|
||
## One-line pitch
|
||
|
||
**Skills teach the agent how to do tasks; memory’s `cognitiveStyle` teaches the agent how to think and collaborate with this user.**
|
||
|
||
## Problem
|
||
|
||
Cross-session memory already stores work context, personal preferences, and facts. In practice, two gaps show up:
|
||
|
||
1. **Semantic mixing** — “Prefers TypeScript” and “Always wants conclusions first, then details” both land in `personalContext` or `behavior` facts. The model must infer which is *collaboration protocol* vs *project preference*.
|
||
2. **Wrong layer for collaboration prefs** — Task skills (`SKILL.md`) are procedural and shared. Stable response/collaboration preferences (structure, depth, correction style) are **user-scoped slow variables**, not one-off task steps.
|
||
|
||
Without an explicit slot, collaboration style is under-specified in injection and easy to drop under token pressure.
|
||
|
||
## Approach (not a new subsystem)
|
||
|
||
Extend the existing memory pipeline:
|
||
|
||
| Layer | Role |
|
||
|-------|------|
|
||
| `user.cognitiveStyle.summary` | 2–4 sentence paragraph: reasoning & collaboration habits |
|
||
| `facts[]` with `category: cognitive` | Atomic, confidence-ranked supplements |
|
||
| `normalize_memory_data()` | Backward-compatible fill for older sections and fact metadata |
|
||
| `core/prompts/memory_update.chat.yaml` | LLM sets `cognitiveStyle.shouldUpdate` only when new signals are clear |
|
||
| `format_memory_for_injection()` | Injects as `Thinking Style:` under User Context |
|
||
|
||
**Non-goals (this change):**
|
||
|
||
- Vector / embedding “personality library”
|
||
- Separate debounce or sampling schedule for `cognitiveStyle` only
|
||
- Replacing `personalContext` or skills
|
||
|
||
## Update frequency (read vs write)
|
||
|
||
| Event | Behavior |
|
||
|-------|----------|
|
||
| **Read (every turn)** | If `injection_enabled`, current `cognitiveStyle` is loaded into `<memory>` within `max_injection_tokens` |
|
||
| **Write (after turn)** | `MemoryMiddleware` queues filtered conversation; **debounce** (`debounce_seconds`, default 30s) batches updates |
|
||
| **Write (cognitive field)** | Same LLM pass as other user sections; field changes only when JSON has `cognitiveStyle.shouldUpdate: true` |
|
||
|
||
So: conversations **trigger** the memory job often; **cognitiveStyle text changes** only when the updater model sees durable new evidence—not every chit-chat turn.
|
||
|
||
## How this differs from nearby concepts
|
||
|
||
| Concept | Scope | Lifetime |
|
||
|---------|--------|----------|
|
||
| **Thread / checkpointer** | This session’s messages & tools | Session |
|
||
| **Skill** | How to run a task type | Shared / installable |
|
||
| **workContext / topOfMind** | What the user is doing | Cross-session, changes often |
|
||
| **personalContext** | Language, interests, tone | Cross-session |
|
||
| **cognitiveStyle** | How they reason, structure answers, give feedback | Cross-session, **slow** |
|
||
| **fact (`cognitive`)** | One line habit or meta-preference | Cross-session, ranked by confidence |
|
||
|
||
## Adding a new memory field (schema evolution)
|
||
|
||
When extending the global summary JSON (`user.*` / `history.*`) or the per-agent Markdown fact schema, keep **read**, **import**, migration, and **API** paths aligned so older exports still work.
|
||
|
||
| Step | Location |
|
||
|------|----------|
|
||
| 1. Backend normalize | `deerflow/agents/memory/backends/deermem/deermem/core/storage.py` — add keys to `normalize_memory_data()` / fact normalization; update `create_empty_memory()` |
|
||
| 2. Frontend normalize | `frontend/src/core/memory/import-memory.ts` — add section keys and normalize recoverable legacy fact metadata before narrowing to `UserMemory` |
|
||
| 3. Types & API models | `frontend/src/core/memory/types.ts`, `backend/app/gateway/routers/memory.py` (`UserContext` / `HistoryContext`) |
|
||
| 4. Updater prompt | `core/prompts/memory_update.chat.yaml` and `core/prompts/fact_extraction.yaml`; add injection rendering in `core/prompt.py::format_memory_for_injection()`. Fact categories must also be added to `storage.py::CORE_CATEGORIES` |
|
||
| 5. Settings UI & i18n | `memory-settings-page.tsx`, `en-US.ts` / `zh-CN.ts` |
|
||
| 6. Tests | Backend: legacy sections and facts in `tests/test_memory_storage.py` / `tests/test_memory_normalize.py` / `tests/test_deermem_self_contained.py`. Frontend: import and API-read behavior in `tests/unit/core/memory/` |
|
||
| 7. Import path | Keep the stable export envelope strict (`version`, `lastUpdated`, object `user`/`history`, array `facts`), then normalize additive fields inside that valid envelope |
|
||
|
||
**Avoid:** normalizing only sections while leaving legacy fact metadata unchecked, or making one unrecoverable fact fail the entire background API read. User-initiated imports remain strict for facts without usable content; API reads drop only those unrecoverable entries.
|
||
|
||
## Verification
|
||
|
||
```bash
|
||
cd backend
|
||
PYTHONPATH=. uv run pytest -q tests/test_memory_storage.py tests/test_memory_prompt_injection.py tests/test_memory_normalize.py tests/test_deermem_self_contained.py
|
||
PYTHONPATH=. uv run pytest tests/test_memory_router.py -v
|
||
|
||
cd ../frontend
|
||
pnpm test tests/unit/core/memory
|
||
```
|
||
|
||
Manual:
|
||
|
||
1. Enable `memory` in `config.yaml`, run `make dev`.
|
||
2. In a thread, state a stable collaboration rule (e.g. “先给结论,不要长铺垫”).
|
||
3. Wait ≥ `debounce_seconds`, open **Settings → Memory** or `GET /api/memory`.
|
||
4. Confirm `user.cognitiveStyle.summary` and/or a `cognitive` fact; start a **new thread** and check behavior.
|
||
|
||
## Issue
|
||
|
||
**Title:** `feat(memory): add cognitiveStyle for stable reasoning & collaboration habits`
|
||
|
||
**Summary:**
|
||
|
||
- Adds `user.cognitiveStyle` to memory schema with backward-compatible normalization.
|
||
- Teaches the memory updater to extract thinking/collaboration habits separately from work/personal context.
|
||
- Injects as `Thinking Style:` in system prompt; supports `cognitive` fact category.
|
||
- Documents rationale in `backend/docs/MEMORY_COGNITIVE_STYLE.md` and harness memory docs.
|
||
|
||
**Motivation:** Cross-session memory should distinguish project context from stable collaboration preferences (response structure, correction style, depth). This change extends the existing memory harness only; it does not add a new store.
|
||
|
||
---
|
||
|
||
## 中文说明
|
||
|
||
### 背景
|
||
|
||
跨会话 memory 已有 `workContext`、`personalContext` 与 `behavior` 类 facts。实践中两类信息容易混在同一字段里:
|
||
|
||
- 项目/工具偏好(例如常用 TypeScript)
|
||
- 协作偏好(例如先给结论、控制篇幅、纠错方式)
|
||
|
||
后者更新频率低于 `topOfMind`,又不同于一次性会话信息。单独增加 `user.cognitiveStyle` 便于注入时固定展示为 `Thinking Style:`,并与任务级 Skill 区分。
|
||
|
||
### 实现范围
|
||
|
||
- 扩展全局 summary JSON 与 per-agent Markdown fact schema,`normalize_memory_data()` 兼容旧 section 和缺少元数据的旧 facts
|
||
- `core/prompts/memory_update.chat.yaml` 输出 `cognitiveStyle.shouldUpdate`;可选 `category: cognitive` 的 facts
|
||
- 复用现有 MemoryManager → DeerMem → 防抖队列 → Updater → 注入链路,不新增子系统
|
||
|
||
### 更新频率
|
||
|
||
| 事件 | 行为 |
|
||
|------|------|
|
||
| 每轮对话开始 | 在 `max_injection_tokens` 内注入已有 `cognitiveStyle`(读) |
|
||
| 每轮对话结束 | 与其它 memory 段相同,可能入队;默认 `debounce_seconds` 合并 |
|
||
| 写入 `cognitiveStyle` | 仅当 LLM 返回 `shouldUpdate: true` 时更新段落 |
|
||
|
||
### 与 Skill 的区别
|
||
|
||
| 类型 | 内容 |
|
||
|------|------|
|
||
| Skill | 某类任务的步骤与模板(可共享、可安装) |
|
||
| `cognitiveStyle` | 该用户稳定的回复结构、讨论深度、反馈习惯(按用户持久化) |
|
||
|
||
### 以后新增 memory 字段时
|
||
|
||
按上文 **Adding a new memory field** 清单同步改后端 `normalize_memory_data()` 与前端 `normalizeMemoryPayload()`;导入走 normalize,不要只对完整新 schema 做严校验。后台 API 读取应丢弃无法恢复的单条 fact,而不是让整个 Memory 页面失败。
|