* 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>
7.8 KiB
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:
- Semantic mixing — “Prefers TypeScript” and “Always wants conclusions first, then details” both land in
personalContextorbehaviorfacts. The model must infer which is collaboration protocol vs project preference. - 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
cognitiveStyleonly - Replacing
personalContextor 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
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:
- Enable
memoryinconfig.yaml, runmake dev. - In a thread, state a stable collaboration rule (e.g. “先给结论,不要长铺垫”).
- Wait ≥
debounce_seconds, open Settings → Memory orGET /api/memory. - Confirm
user.cognitiveStyle.summaryand/or acognitivefact; start a new thread and check behavior.
Issue
Title: feat(memory): add cognitiveStyle for stable reasoning & collaboration habits
Summary:
- Adds
user.cognitiveStyleto 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; supportscognitivefact category. - Documents rationale in
backend/docs/MEMORY_COGNITIVE_STYLE.mdand 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 页面失败。