Files
Hy-Memory/docs/scene_coding_usage.md
T
alvinfeiandClaude Opus 5.5 4746681efe fix(sdk): coding reconcile model failures fail the write, nothing half-applied
Experience reconcile no longer falls back to ADD-all, and fact reconcile no
longer inserts without superseding, when the model call fails, returns empty
content, or returns a reply that is not a JSON object with an ops list. The
write returns success=false, agent_error_code=RECONCILE_FAILED and the
cause in reconcile_error_code (LLM_UNAVAILABLE / EMPTY_RESPONSE /
JSON_PARSE_FAILED), the same as chat.

Each reconciler is split into plan() (retrieve + model decision, no store)
and apply() (store). CodingWriter plans both layers before applying either,
so a decision failure in either layer stores no experience or fact and
supersedes no older fact. reconcile() still runs plan + apply.

An experience reply of {"ops": []} is a valid "no changes" decision and
stores nothing. A non-empty ops list with no recognizable op is a parse
failure. A fact reply of {"ops": []} inserts without superseding.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-02 17:01:47 +08:00

260 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Working 与 Coding 场景
Hy Memory 的公开 `scene` 参数支持三个值:
| `scene` | 用途 | 存储与检索路径 |
|---|---|---|
| `"chat"` | 闲聊、个人事实、一般问答 | 通用 memory pipeline |
| `"working"` | 文档、办公、研究、财经、内容创作和电脑操作等生产力任务 | working 双层 memory |
| `"coding"` | 编码、调试、测试、构建、API/SDK、脚本、SQL 等软件工程任务 | coding 双层 memory,并附带版本演进链 |
working 与 coding 复用同一套生产力 pipeline 结构和数据 schema,但使用不同的抽取规范、SQLite 文件和向量集合,数据不会混在一起。
## 1. 写入路由
### 显式指定 scene
生产力写入需要包含 tool message 或 assistant `tool_calls`:
```python
from hy_memory import HyMemoryClient
client = HyMemoryClient(mode="pro")
messages = [
{"role": "user", "content": "Fix the timeout in the search client."},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {
"name": "apply_patch",
"arguments": '{"path":"src/search.py"}'
},
}
],
},
{
"role": "tool",
"tool_call_id": "call_1",
"name": "apply_patch",
"content": "Patch applied successfully",
},
]
result = client.add(
messages,
scene="coding",
user_id="user_123",
workspace_id="github.com/example/service",
branch="fix/search-timeout",
)
```
公开行为:
- `scene="chat"`:跳过 scene judge;tool 轨迹会被移除后进入 chat pipeline。
- `scene="working"`:跳过 scene judge,进入 working pipeline。
- `scene="coding"`:跳过 scene judge,进入 coding pipeline,并尝试记录版本演进链。
- 显式 `working/coding` 但输入没有 tool/tool_calls:当前实现会回退 chat pipeline。
- 非法 scene 会抛出 `ValueError`。
### 不指定 scene
`scene=None` 时:
- 输入没有 tool/tool_calls:直接进入 chat pipeline,不调用 scene judge LLM。
- 输入包含 tool/tool_calls:使用 user query 与工具名的有界摘要进行一次 `chat / working / coding` 三分类。
- judge 失败或输出无法解析:回退 chat。
- 判定为 chat 后,tool 轨迹会被移除再进入 chat pipeline。
分类边界:
- `coding`:代码或开发产物是工作对象或实现媒介,包括源码、脚本、SQL、notebook、测试、构建、编译/运行错误、API/SDK 和代码评审。
- `working`:存在明确生产力操作或交付物,但代码不是核心工作媒介,例如文档、PPT、表格、翻译、财经/业务分析、研究报告、邮件日程、项目管理、设计、文件整理、软件安装和设备操作。
- `chat`:闲聊、个人信息、建议和一般事实问答。仅使用 search/browser 工具不会自动变成 working。
## 2. 搜索路由
显式传 `scene` 会直接选择对应检索路径,不调用 judge/rewrite LLM:
```python
result = client.search(
"Which timeout implementation was finally kept?",
scene="coding",
user_ids=["user_123"],
workspace_id="github.com/example/service",
branch="fix/search-timeout",
limit=5,
)
```
默认与 agentic 行为:
| 调用方式 | 当前行为 |
|---|---|
| `search(query="...", search_mode="default")` | 固定走 chat/normal light search,零 LLM |
| `search(queries=[...], search_mode="default")` | Coding 开启时分类并 rewrite;仅 Query Rewrite 开启时只 rewrite/time;两者关闭时零 LLM |
| `search(query="...", search_mode="agentic")` | LLM 三分类、query rewrite 和时间范围解析 |
| `search(queries=[...], search_mode="agentic")` | 同上,前置 query 作为上下文 |
| 任意模式显式传 `scene` | scene 优先,零 judge/rewrite LLM |
`search_mode="agentic"` 使用有界 query 视图,单 query 与多 query 都会走 judge/rewrite。`search_mode="default"` 的单 query 保证不调用该 LLM。
default 多-query 超过 `MEMORY_QUERY_REWRITE_MAX_CHARS` 时不改写;Coding 开启时仍做有界
场景分类,关闭时直接使用最后一条 query。
若 coding 功能被 `MEMORY_CODING_ENABLED=false` 关闭,生产力搜索会安全回退 chat/normal 检索。
## 3. Extractor 输入窗口
working/coding extractor 将输入分成:
- `LAST 5 USER TURNS`:之前 write 的历史,只用于指代消解和上下文,不重复抽取。
- `NEW MESSAGES`:本次 `add()` 传入的全部消息,全部作为抽取主体,不受 last-5 裁剪。
当前压缩规则:
- tool result 默认最大 `2048` bytes,保留头尾并省略中段;由 `MEMORY_CODING_TOOL_RESULT_MAX_BYTES` 配置。
- 单条 assistant 文本超过 `6000` 字符时,默认保留前 `3000` 和后 `3000` 字符。
- 历史按 user-turn 窗口裁剪;一个 user-turn 包含该 user 消息以及下一条 user 消息前的 assistant/tool 消息。
- `MEMORY_EXTRACT_HISTORY_TURNS` 控制 store 回读多少历史,默认 5;当前 extractor 最终最多注入最近 5 个 user-turn,因此大于 5 的配置不会扩大 prompt 中的历史窗口。
例如本次写入包含 10 个 user-turn 时,10 个 turn 都进入 `NEW MESSAGES`;历史区另外最多提供最近 5 个 user-turn。
working/coding 的 Extractor、经验 Reconcile、事实 Reconcile 与跨 session 进展总结均有
中英文模板。路由优先检查本次新输入前 10 个有效语言字符;Extractor 只看本次 NEW user
messages,不使用历史或 tool result 决定语言。中文开头后接大量代码、路径或错误信息时仍走
中文模板;英文开头后面偶尔出现中文术语时保持英文模板。
## 4. 返回结构
working/coding 写入返回双层结果:
```python
{
"success": True,
"scene": "productivity",
"request_id": "...",
"ops": [...],
"ops_summary": {...},
"experience_ids": [...],
"fact_ops": [...],
"fact_ops_summary": {...},
"fact_ids": [...],
"memory_id": "..." or None,
"elapsed_ms": 123.4,
}
```
抽取 LLM 调用失败、返回空 content 或回复解析不出 JSON,以及经验 / 事实向量化失败时,
写入返回 `success=False`、`error_code=502`,`agent_error_code` 依次为 `LLM_ERROR`、
`EMPTY_RESPONSE`、`JSON_PARSE_FAILED`、`EMBEDDING_FAILED`,`error_message` 以
`[CODE]` 开头;没有向量的经验 / 事实不会落库,失败的写入也不追加对话 trace,换一个
`request_id` 重跑即可。模型正常作答但没有值得记的内容仍是 `success=True`、0 条。
显式开启 `MEMORY_CODING_VERBATIM_FALLBACK_ENABLED` 时抽取失败沿用原文兜底。
经验层 / 事实层 reconcile 的模型决策同样不再兜底:LLM 调用失败、返回空 content 或回复
不是带 `ops` 列表的 JSON 对象时,写入返回 `success=False`、
`agent_error_code="RECONCILE_FAILED"`,原因在 `reconcile_error_code`(`LLM_UNAVAILABLE` /
`EMPTY_RESPONSE` / `JSON_PARSE_FAILED`,与 chat 链路一致)。两层的模型决策都在落库之前
完成,任何一层失败时本次一条经验 / 事实都不存,旧事实也不会被覆盖。经验层回复
`{"ops": []}` 表示「无需任何操作」,写入成功、经验层不存;`ops` 非空却没有一条可识别的
op 视为解析失败。事实层回复 `{"ops": []}` 表示都不覆盖、照常插入新事实;没有候选时不
调用 LLM。
`scene="coding"` 还会返回 `chain_rounds`(写入失败时不记录演进链,也不返回该字段)。出于历史兼容,该字段当前统计本次识别并处理
的 diff hunk 数,而不保证等于最终合并后的唯一演进 round 数。`0` 表示没有识别到可记录
的代码修改,不代表双层 memory 写入失败。
working/coding 搜索返回:
```python
{
"request_id": "...",
"scene": "productivity",
"facts": [...],
"experiences": [...],
"elapsed_ms": 123.4,
}
```
`limit` 分别限制 facts 与 experiences,因此两层最多各返回 `limit` 条。
`scene="coding"` 额外返回 `chain_block` 和 `chain_used` 字段(键始终存在;默认内容为空):
- 注入 diff / 版本演进块需显式开启 `MEMORY_CODING_CHAIN_BLOCK_ENABLED=true`
(或 `CodingConfig.chain_block_enabled=True`)。默认关闭,以节约 prompt token。
- 开启后,`chain_block` 是与已召回 fact 对齐的版本演进上下文;无法挂链时为 `""`。
- `chain_used` 是已经进入 chain block 的 `facts` 下标。
- SDK 不会从 `facts` 删除这些条目。调用方若要拼成“chain block + 剩余 facts”,需自行按 `chain_used` 过滤。
## 5. 存储与作用域
默认本地文件:
```text
<coding db directory>/
├── coding_memory.db # working 经验/事实 metadata
├── coding_memory_cv.db # coding 经验/事实 metadata
├── coding_memory_trace.db # working/coding 共用的历史消息
└── hy_code_chain/
└── hy_code.db # coding 版本演进链
```
向量集合:
| 变体 | 经验层 | 事实层 |
|---|---|---|
| working | `{base}_coding_keys` | `{base}_working_facts` |
| coding | `{base}_coding_keys_cv` | `{base}_working_facts_cv` |
同一个 client 可以交替使用 working 与 coding;实现会按变体分别缓存 writer/store。
`workspace_id` 与 `branch` 用于生产力 memory 的作用域:
- 不传 `workspace_id`:不按 workspace 过滤。
- 传 `workspace_id`:精确匹配该 workspace。
- `branch` 只有在同时传 `workspace_id` 时生效。
- branch 过滤会保留 `branch IS NULL` 的历史数据,避免未记录分支的通用经验被误删。
建议写入和搜索使用相同的规范化 `workspace_id`。版本链的 owner 由 `user_id` 与 `workspace_id` 共同构成,避免不同用户或仓库的 patch 串链。
## 6. Coding 版本演进链
版本演进链由 `hy_memory/coding/chain_store.py` 实现。它不调用 LLM 或 embedding,而是从以下来源识别修改:
- 设置 `MEMORY_CODING_CHAIN_REPO_ROOT` 后,从本地 Git 工作区或变更中读取 diff。
- 从工具轨迹中识别带 path、old、new 的确定性替换动作。
无法可靠解析的编辑形式会被跳过。链层是 best-effort:记录失败不会阻断 working/coding 的双层 memory 写入或检索。
## 7. 配置
| 变量 | 默认值 | 作用 |
|---|---|---|
| `MEMORY_CODING_ENABLED` | `true` | 启用 working/coding pipeline |
| `MEMORY_CODING_DB_PATH` | `{DATA_DIR}/data/coding_memory.db` | working 基础 SQLite 路径;coding/trace 路径由此派生 |
| `MEMORY_CODING_TOOL_RESULT_MAX_BYTES` | `2048` | 单条 tool result 的字节上限 |
| `MEMORY_CODING_VERBATIM_FALLBACK_ENABLED` | `false` | 模型无结果时是否保存原文 fact;默认关闭,避免 assistant/tool trace 直接展示 |
| `MEMORY_CODING_CHAIN_BLOCK_ENABLED` | `false` | search 是否注入版本演进链块(`chain_block` / diff);默认关闭(opt-in) |
| `MEMORY_EXTRACT_HISTORY_TURNS` | `5` | 回读的历史 user-turn 数;当前 prompt 最终最多使用 5 |
| `MEMORY_CODING_CHAIN_REPO_ROOT` | 空 | coding 版本链读取本地 Git diff 的仓库路径 |
`HCM_*` 变量属于版本链的高级调优项,不是稳定的公共配置契约;普通接入不应依赖其默认值。
其中 `HCM_BLOCK_OFF=1` 可在已开启 `MEMORY_CODING_CHAIN_BLOCK_ENABLED` 时做消融(完全不出块)。
## 8. 定向验证
```bash
python -m pytest -q \
tests/coding/test_write_scene_routing.py \
tests/coding/test_search_scene_routing.py \
tests/coding/test_scene_coding_variant.py \
tests/coding/test_scene_coding_search.py \
tests/coding/test_coding_pipeline_log.py
```