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>
260 lines
12 KiB
Markdown
260 lines
12 KiB
Markdown
# 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
|
||
```
|