deer-flow/backend/docs/MEMORY_COGNITIVE_STYLE.md
tomyan007 45411cee69
feat(memory): add cognitiveStyle for collaboration preferences (#3182)
* 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>
2026-09-23 18:29:12 +08:00

7.8 KiB
Raw Blame History

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

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 页面失败。