docs(contract): v0.83 —— 记忆整理应用面五条端点 + 读侧 status 闸 + 两处过时口径改正
新增 §2.2 B20–B24(候选列表 / 单条应用 / 批量应用 / 单条回滚 / 手动真跑)、 §4.15 详解(三条硬约束:幂等不是错误 / 超限必须如实 / 软动作永不物理删除)、 错误码表补 404 tidy_run_not_found、409 tidy_disabled、500 六个 tidy_*、503 tidy_timeout; 计数 writeAuth 19 → 24、总数 73 → 78(按 routes.go 核:writeAuth 挂载 admin 26 条, 减去历史口径的 channels/events 与 counters ⇒ 24)。 改正两处已成假话的 §7.2 条目: - 「整理候选 apply/revert/列表/运行均未实现」→ 已实现五条,仍缺 reject / 运行历史读口 / 按 run 筛候选 - 「面板记忆页无法过滤」→ 补一句:v0.83 起服务端已按 status 过闸,字段面仍未补齐 诚实边界写进契约:生产库零动作 / 面板真机未跑 / 旧候选 run_id 为空串 / run_id 列只在存储包前滚 / 语义召回不可复现。
This commit is contained in:
1 parent
f847769698
commit
ef8fc66e54
1 file changed
+69
-12
+69
-12
@@ -1,6 +1,6 @@
|
||||
# Cyrene v2 客户端 API 契约
|
||||
|
||||
> **契约版本:`v0.82`**(上一版 `v0.81`,其条目见 §6;`v0.80` / `v0.79` / `v0.78` / `v0.77` / `v0.76` / `v0.75` / `v0.74` / `v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
|
||||
> **契约版本:`v0.83`**(上一版 `v0.82`,其条目见 §6;`v0.81` / `v0.80` / `v0.79` / `v0.78` / `v0.77` / `v0.76` / `v0.75` / `v0.74` / `v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
|
||||
> **读者**:Web 前端 / 移动端原生客户端(含 Android)/ IM 桥接等**跨平台客户端开发者**。
|
||||
> **唯一真源 = 当前代码**(`internal/services/gateway/*` 等)。本文档由代码逐条核对得出;与代码不符时**以代码为准**,并回改本文档。
|
||||
> **旧的超详细版已归档、只读**:`Docs/archive/api-client.v0.65.md`(412 KB,含 v0.28→v0.65 全部历史条目)与 `Docs/archive/api-reference.v0.65.md`(170 KB,后端全量字段级文档)。归档的原因是**结构性臃肿 + 历史条目并列 + 长期漂移**;本文档**不复制**那段历史,需要考古时去归档。
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
> **v0.77 是链路图按路分桶 + 记忆整理护栏/异步落地版(已实现;纯增量)**:① **`graph_update` 按路分桶**(切片 FV2)—— 帧 `data` 新增可选 `turnId`(该图的**路**的权威回合标识)与 `round`(会话轮次号);真并行(`parallel:true`)时同一会话**每一路各发自己的帧**(`data.id` / `data.turnId` 各不相同),两路不再互相顶掉;`GET /api/v1/admin/flow/graph` 响应新增 `lanes[]`(**最近一轮每一路**的图,恒为非 null 数组),`graph` 语义不变(最新一路 / 指定轮 / `null` 诚实空态)⇒ **老的读取方零改动**。**无破坏性变更**(两个新键可选;单路时 `turnId` 缺席 ⇒ 客户端行为与 v0.75 逐字相同)。详见 **§2.2 B13 / §3.4.5 / §4.11 / §7.4-19**。② **记忆整理护栏抬高 + 诚实截断 + 试算异步落地**(切片 MT-A)—— 每分区候选上限 `MEMORY_TIDY_CANDIDATE_LIMIT` 默认值 `20 → 200`、两两比对上限改为 env 键新键 `MEMORY_TIDY_COMPARE_LIMIT`(默认 `2000`,原先**写死** `400`);配置事实面 **12 → 13 条**;`truncated` 新增第 4 种标签 **`(budget)`**(墙钟时间预算截断);**v0.76 ③ 的加性异步已落地**(`?async=1` + B15 轮询可用;新错误码 **`tidy_run_not_found`**)。详见 **§2.2 B14/B15 / §4.10(a) / §4.12 / §4.12.1 / §5**。③ **「入库前审查」的 4 个 env 键登记**(切片 MT-B,纯增量)—— `MEMORY_REVIEW_ENABLED`(`true`)/ `MEMORY_REVIEW_QUEUE_SIZE`(`256`)/ `MEMORY_REVIEW_SCAN_LIMIT`(`200`)/ `MEMORY_REVIEW_TIMEOUT_SEC`(`20`);⚠️ 该切片的**面板读侧 / 回滚端点目前没有 HTTP 入口**(见 §7.2)。详见 **§5**。
|
||||
|
||||
> **v0.83 是记忆整理「应用面」版(已实现并推库:切片 TIDY-1 `454ddb0` + 读侧 status 闸 `9126d65` + 网关端到端回归 `f847769`;**纯增量、无破坏性变更**)**:① **新增 5 条 `writeAuth` 端点**,补上「整理只产建议、永远不落地」这个两天没闭环的缺口 —— **B20** `GET /api/v1/admin/memories/candidates`(候选列表)、**B21** `POST …/candidates/{id}/apply`(单条应用)、**B22** `POST …/candidates/apply?runId=&limit=`(批量应用;`runId` 省略 = **全部待定**)、**B23** `POST …/candidates/{id}/revert`(单条回滚)、**B24** `POST …/memories/tidy/run`(手动**真跑**一轮:写候选 + 留痕,**不自动应用**)。语义与形状见 **§4.15**。🔴 **三条硬口径(客户端按此编程)**:**(i) 幂等不是错误** —— 已决定过的候选回 `200` + `status:"skipped"`,界面**不得**画成失败;**(ii) 超限必须如实** —— 批量回执的 `total` 是**真正命中筛选的待定候选数**,而 `applied+skipped+failed` 只覆盖 `limit` 之内,两者不等时 `truncated:true`(不许把「本轮 100 / 共 342」画成「全部完成」);**(iii) 永不物理删除** —— merge / archive 都是**软动作**(只改 `status`,正文一个字不改、行一行不删),因此 `revert` 无损可逆。② **读侧 status 闸(行为修复,形状零变化)**:记忆的**四条列表读路径**(`ListByPersona` / `List` / `ListScope` / `ListProject`)现在只回 **`status='active'`** 的行 ⇒ 被软合并(`'merged'`)与被归档(`'archived'`)的记忆**不再被召回、不再进上下文、也不再出现在面板 `GET /admin/memories`**(此前它们照样出现 ⇒ 等于「清了也白清」)。🔴 **刻意不过闸的两处**:按 id 的 `Get`(撤销软合并必须先读得到被吸收的行)与 **`GET /admin/memories/reviews`**(审查留痕**本身没有 status 概念**,且它是软删行的查看与撤销入口 —— 加闸等于审计自我抹除)。③ **调度不再饿死(内部行为,对客户端零形状影响)**:整理从「ticker 从进程启动算起、重启十几次就永远走不完 24h」改成**到点即跑 + catch-up**,以 `memory_tidy_runs` 的留痕行为**持久事实**判「上次到底跑过没有」,并把手动真跑端点(B24)做成 panic button。④ **计数**:`writeAuth` 段 19 → **24**、管理路由总数 73 → **78**(`14 + 24 + 40`;按 `routes.go` 逐条核对:经 `writeAuth` 挂载的 **admin** 路由实有 **26** 条,按历史口径减去 §2.4(a) 的 `channels/events` 与 `.../counters` ⇒ **24**)。⚠️ **诚实边界(别当已验证)**:**生产库全程零动作** —— 1191 行记忆与 77 条待定候选**都没有被应用或清理**(等主人拍板,见 §4.15(d));**面板上的真机端到端未跑**(前端操作面在本版登记时尚未落地,并行切片在做);旧候选的 `run_id` 是空串 ⇒ 「**按 run 批量**」找不到它们,要清它们必须走**全部待定**那一档;候选表的 `run_id` 列目前**只在存储包前滚**(`memory.NewStore`),**未**同步进迁移 `0019`。
|
||||
>
|
||||
> **v0.82 是三面增量版(① 已实现(切片 Y1 `215eedf`);② 已实现(切片 Y3 `4213b26`);③ 形状已定、代码在树(切片 Y4 `e0a6bca` + 组合根挂点);**纯增量、无破坏性变更**)**:① **`tool_execution.end` 的 `data` 新增可选 `resultSummary`(string)** —— 工具卡从此能在**实时**显示「这次调用拿到了什么」,不必等事后翻回看面。它修的是「实时只看到工具名与状态、结果要事后才知道」。🔴 **缺席语义(安卓端按此编程)**:**键在场 ⇒ 渲染结果区;键缺席 ⇒ 只显示工具名与状态** —— **内容与回看面 `toolCalls[].resultSummary` 同源同口径**(同一份**已脱敏**文本 `trace.RedactText`:凭据形状清洗 + 截断)、**同一个上限 `≤500` rune**(超长按 rune 截断、末位 `…`);**空值 ⇒ 整键缺席,绝不用空串顶位**(空串会让客户端渲染出一个空的结果区)。**只有「真的没有结果」才缺席**:调用成功但结果为空(nil / 空串)、或错误文本本身为空;`tool_execution.start` / `.update` **不带**该键。⚠️ **失败也带**(本版口径):调用失败 / 取消 / 超时的 `.end` 帧带的是**该次错误文本的脱敏渲染** —— 与回看面(那里失败调用回的是图上的 `error` 摘要)**给同一份说法**,客户端不必为「实时」与「回看」写两套判据。详见 **§3.4.2**。② **自排的成本可见面**(切片 Y3,design/12 §N C.5/C.6)—— `GET /api/v1/admin/backthink` 新增**可选** `?trigger=` **服务端筛选**(取值域 = **闭集**:三个内建标签 + 本服务订阅的事件主题,真源 `backthink.TriggerFilterValues()`;值**未知或为空** ⇒ **`400 invalid_trigger`**,**绝不静默忽略** —— 静默忽略会让面板在显示全部的同时声称「正在筛选」;合法值**下推 SQL**,因此 `limit` 数的是**匹配**条数);新增 1 条 `writeAuth` 只读端点 **`GET /api/v1/admin/backthink/self-wake`**(§2.2 B19 / §4.10(e),**三态** `available`/`running`/`reason` + `gate` 快照 + `configFile`)。⚠️ **`trigger` 的「显示口径」仍是开放的**(客户端按字面值显示、不做闭集校验)—— **筛选白名单**与**显示取值域**是两件事,别混(见 §4.10(d))。③ **`browser` 插件管理面板(B4)**(切片 Y4,design/28 §8)—— `GET /api/v1/admin/panels` 的面板目录新增 `browser` / `settings`(「浏览器与截图」,4 个区块:配置表单 + 状态 / 指纹 / 截图三张只读表),该插件声明**六个动作**(3 读 3 写,见 §4.14(d));**不开任何新路由**(走既有 A14 通用动作分发),因此 §2.0 计数**只**因 ② 的 self-wake 端点 +1。**模型面**(非客户端契约面)另有一条:`browser` 工具的 `screenshot` 返回值新增 **`turnAttachment`**(闭集 `queued` / `no-session` / `outbox-full` / `disabled`)。**④ 本版把 §4.10(d) 一句已成假话的口径改正**:`request_think` **已接线**(`cmd/cyrene/wire_services.go` 的 `wireSelfWakeTool`;启动日志 `self wake tool registered tool=request_think`)⇒ `trigger="self_wake"` 与 4 条 `backthink.self_wake.*` 审计动词**在生产里会真的出现**。**⑤ 计数**:`writeAuth` 段 18 → **19**、管理路由总数 72 → **73**(`14 + 19 + 40`)。⚠️ **登记时的工作树状态(如实)**:③ 的**组合根两个挂点**(`cmd/cyrene/wire_frontend.go` 的 `wireBrowserPanel(hub, a, a.log)`、`cmd/cyrene/wire_runtime.go` 的 `TransformContext: a.browserShotTransform(visionResolver),`)与 ① 的**失败也带摘要**(`internal/runtime/loop/tool_frame_summary.go`)在本版登记时**仍在工作树、未提交** ⇒ 提交前,跑着的后端里**没有**这些行为;提交后请按 `git show` 逐字复核。
|
||||
|
||||
> **v0.81 是「她自排轮的用户可见面 + 时间轴保留期信号 + `round` 跨重启单调修复」版(切片 C1-1 `94da6bc` / C1-2 `ed50053`(自排)+ 切片 X1(时间轴两处真缺陷);**无新端点、无破坏性变更**)**:① **`GET /api/v1/admin/backthink` 的行 `trigger` 新增取值 `"self_wake"`**(= 这一轮是**她自己排起**的思考,`design/12 §N C`)—— `trigger` 的取值域**不封闭**(`heartbeat` 周期心跳 / `startup` 启动轮 / 事件轮 = 事件主题 / `self_wake` 自排),客户端**按字面值显示、不要当闭集校验**(见 §4.10(d))。⚠️ **可达性**:她的入口工具 `request_think` **尚未接线** ⇒ 该取值与 ② 的动词在生产里**目前不会出现**(形状先登记)。—— ✅ **本条已过期,已由 v0.82 ④ 改正**:`request_think` 已接线(`cmd/cyrene/wire_services.go` 的 `wireSelfWakeTool`,切片 X2 `2d211c2`),生产里**会真的出现**。② **新增 4 条审计动词** `backthink.self_wake.{queued,coalesced,capped,denied}`,经既有 `GET /api/v1/admin/observability/audit`(B9)可查,`who` 恒为 `backthink`(加性,见 §4.10(d))。③ **时间轴新增 `retentionBoundaryReached`(bool)并与 `truncated` 分意**:`truncated:true` **只**表示「本次不是全部」(分页未到底 **或** 落盘源未接线),**不能**据此断言「更早的轮次已被保留期清扫」——那件事由 **`retentionBoundaryReached:true`** 单独回答(判据三条缺一不可 + **三个盲区如实登记**,见 §4.11.1(a)(b)-3)。④ **`round` 跨重启不再重复**(发布器先从落盘播种计数器):修的是「重启后轮次号从 1 重来 ⇒ 时间轴把重启前后两轮并成一块」的真机缺陷,契约里「`round` 每会话单调」这句**从此真的成立**(见 §4.11.1(e))。⑤ **计数**:本版**无新端点** ⇒ §2.0 权限矩阵维持 **14 + 18 + 40 = 72**(§2.2 段内 B1–B18 共 **18** 条)。⚠️ **同批对客户端零形状影响(如实说明)**:技能渐进披露接线(L2 `6475eb8`)、示例技能与端到端验收(V3 `4a04ad4`)、浏览器 B1 接线 + B2 薄层工具面(V5 `f1fbc24`)**都不改变任何 REST / WS / admin 形状**。⚠️ 括注原文写「浏览器是**模型面工具**,不进客户端契约面」—— **对 B1/B2 成立,但已部分过期**:v0.82 的 **B4**(浏览器管理面板,切片 Y4)**进**客户端契约面(`GET /admin/panels` 多一条目录项 + 六个动作走既有 A14),见 **§4.14(d)**。⚠️ **③④ 的形状取自切片 X1 的工作树实现,登记时尚未提交** ⇒ 提交后请按 `git show` 复核键名与判据。
|
||||
@@ -146,10 +148,10 @@
|
||||
| 档位 | 中间件 | 条数 | 说明 |
|
||||
|---|:--:|:--:|---|
|
||||
| **只读监控档** | `viewerAuth`(owner/admin/viewer) | **14** | 非敏感投影:无凭据、无他人账号数据、无消息正文。**v0.78 新增两条**:面板接入点快照(A13)与插件动作分发(A14,**写门落在 handler 内**,见 A14 行注) |
|
||||
| **owner/admin 读档** | `writeAuth`(owner/admin) | **19** | viewer 不得看到的一切:记忆、LLM 栈、账号/key、后台思考、审计链、渠道消息日志、完整会话导出、配置事实面(v0.68)、最近一轮链路图快照(v0.71)、记忆整理试算(v0.75)、入库前审查留痕与回滚(**v0.79**,B16/B17)、链路图时间轴(**v0.80**,B18)、**自排闸只读快照(v0.82,B19)** |
|
||||
| **owner/admin 读档** | `writeAuth`(owner/admin) | **24** | viewer 不得看到的一切:记忆、LLM 栈、账号/key、后台思考、审计链、渠道消息日志、完整会话导出、配置事实面(v0.68)、最近一轮链路图快照(v0.71)、记忆整理试算(v0.75)、入库前审查留痕与回滚(**v0.79**,B16/B17)、链路图时间轴(**v0.80**,B18)、自排闸只读快照(**v0.82**,B19)、**记忆整理应用面五条(v0.83,B20–B24)** |
|
||||
| **owner-only 档** | `ownerAuth`(owner) | **40** | 带审计的策略写、每个获取凭据的渠道端点、其余全部变更 |
|
||||
|
||||
**管理路由总数 = 14 + 19 + 40 = 73 条。**
|
||||
**管理路由总数 = 14 + 24 + 40 = 78 条。**
|
||||
|
||||
> **v0.78 计数变更**:`viewerAuth` 段新增两条(A13 `GET /admin/panels`、A14 `POST /admin/plugins/{id}/actions/{action}`)⇒ 12 → **14**、总数 66 → **68**。A14 走 `viewerAuth` 中间件,但**写动作在 handler 内按 owner/admin 收紧**(路由中间件只能有一道门)—— 因此它**是**本表的一行,读写面却不同档,详见 §2.1 A14 与 §4.14。
|
||||
|
||||
@@ -162,7 +164,9 @@
|
||||
|
||||
> **v0.82 计数变更(实数,已按 `routes.go` 核对)**:`writeAuth` 段新增一条(**B19 `GET /admin/backthink/self-wake`**,切片 Y3)⇒ 段内 **18 → 19**、总数 **72 → 73**。三张清单的行数同步为 **§2.1 14 行**(A1–A14)/ **§2.2 19 行**(B1–B19)/ **§2.3 40 行**(C1–C40)。**核对方式(可复现)**:`internal/services/gateway/routes.go` 里经 `writeAuth` 挂载的 **admin** 路由**实有 21 条**,按历史口径减去 §2.4(a) 的 `channels/events` 与 `.../counters` ⇒ **19**,与 §2.2 的行数一致;`viewerAuth` 挂载 **14**、`ownerAuth` 挂载 **40**(逐条列举见 `grep -c 'mux.Handle("[A-Z]* /api/v1/admin.*<middleware>'`)。⚠️ **本版另一个「看似该 +1、其实不动计数」的面**:`browser` 插件的六个动作(§4.14(d))**不开任何新路由** —— 它走**既有** A14 `POST /admin/plugins/{id}/actions/{action}`,因此 §2.1 的 14 与总数都**不变**(A14 那一行本就写明「写门落在 handler 内」,动作名由**声明**白名单决定,声明多一个插件不新增路由)。
|
||||
|
||||
> **计数口径(v0.71 明确)**:总数是 §2.1 / §2.2 / §2.3 三张清单里**列出的行数**(与 v0.68 起的口径一致)。⚠️ `internal/services/gateway/routes.go` 里实际经 `writeAuth` 挂载的 **admin** 路由比 §2.2 多:v0.67 登记的 `GET /admin/channels/events` 与 `.../counters`(见 §2.4(a))**未计入本表**——这是历史口径,本版未改,也**未**把它算进上表的 **writeAuth 条数**(v0.82 为 **19**)。逐条以代码为准。
|
||||
> **v0.83 计数变更(实数,已按 `routes.go` 逐条核对)**:`writeAuth` 段新增**五**条(**B20–B24**:候选列表 / 单条应用 / 批量应用 / 单条回滚 / 手动真跑,切片 TIDY-1)⇒ 段内 **19 → 24**、总数 **73 → 78**。三张清单行数同步为 **§2.1 14 行** / **§2.2 24 行**(B1–B24)/ **§2.3 40 行**。**核对方式(可复现)**:`grep -c 'mux.Handle("[A-Z]* /api/v1/admin.*writeAuth' internal/services/gateway/routes.go` = **26**,按历史口径减去 §2.4(a) 的 `channels/events` 与 `.../counters` ⇒ **24**(`viewerAuth` **14** / `ownerAuth` **40**,与上表一致)。
|
||||
|
||||
> **计数口径(v0.71 明确)**:总数是 §2.1 / §2.2 / §2.3 三张清单里**列出的行数**(与 v0.68 起的口径一致)。⚠️ `internal/services/gateway/routes.go` 里实际经 `writeAuth` 挂载的 **admin** 路由比 §2.2 多:v0.67 登记的 `GET /admin/channels/events` 与 `.../counters`(见 §2.4(a))**未计入本表**——这是历史口径,本版未改,也**未**把它算进上表的 **writeAuth 条数**(v0.83 为 **24**)。逐条以代码为准。
|
||||
|
||||
> ⚠️ **与归档版不一致(已更正)**:`Docs/archive/api-client.v0.65.md` §2.0 写「12 + 11 + 38 = 61」(其 v0.62 条目又写「12 + 10 + 38 = 60」,自相矛盾)。**以代码为准 = 12 + 11 + 40 = 63**。
|
||||
|
||||
@@ -211,6 +215,12 @@
|
||||
| B18 | GET | `/api/v1/admin/flow/timeline` | **链路图时间轴 / 多轮串联(v0.80 新增,切片 W1;v0.81 增量)**,只读、不审计。`?sessionId=`(省略 = 主会话 `web`)+ `?limit=`(缺省 **50**、> **200** 钳到 200、非整数 `400 bad_request`、`≤0` 同缺省)+ `?before=`(**游标**:取开始时刻**严格早于**它的轮次;省略 = 最新那一页;取值 = 响应里的 `nextBefore`,也接受 RFC3339 或 Unix 毫秒,非法 → `400 bad_request`)。响应 `{data:{sessionId,source,rounds[],nextBefore,hasMore,truncated,retentionBoundaryReached,retentionHours,retentionEnabled}}`:`rounds` **按时间正序**(最早在前),每项 `{turnId,round,startedAt,endedAt,lanes[]}`(`lanes[]` 每项 `{turnId,graph}`,`graph` 与 `graph_update` 同形);`round` 为 `null` = **未知**(老行),`endedAt` 为空串 = **尚未收尾**(在飞轮);`source` ∈ `persisted`(本页至少一轮来自落盘)/`live`(本页全部来自内存)/`none`。🆕 **v0.81(切片 X1)**:① 新增 **`retentionBoundaryReached`**(bool)= 「该会话确有更早的轮次已被保留期清扫」——**与 `truncated` 分意**,只有它 `true` 时面板才可说「更早的轮次已过保留期」(判据/盲区见 **§4.11.1(a)**);② **`round` 跨重启不再重复**(发布器先从落盘播种计数器,见 **§4.11.1(e)**)⇒ 按 `round` 分组不再把重启前后两轮并成一块。未接线(内存 + 落盘都没接)→ `500 flow_unavailable`。语义与空态见 **§4.11.1** |
|
||||
| B19 | GET | `/api/v1/admin/backthink/self-wake` | **自排闸只读快照(v0.82 新增,切片 Y3;design/12 §N C.5 的读侧)**,只读、不审计、不写库。**无参数**。响应 `{data:{available,running,reason?,configFile?,gate?}}` —— **三态必须可区分**(面板**不得**把「看不到」画成「0 次」):① `available:false` ⇒ 本进程给不出快照(后台思考未接线 / 实现不提供读口 / 闸不在),此时 **`gate` 缺席**、`reason` 说明原因(**HTTP 仍是 `200`**,不假装有一个 0/0 的闸);② `available:true, running:false` ⇒ 已接线但不在运行,`gate` **仍带**配置面数字(上限/窗口/代码默认值),`reason` 说明「此刻她排不出新的一轮」;③ `available:true, running:true` ⇒ 正常态,`reason` 缺席。`gate` 快照 = `{pending,used,limit,windowSec,defaultLimit,defaultWindowSec,limitIsDefault,windowIsDefault}`(`used` 与工具收据 `SelfWakeReceipt.Used` **同一口径**;`limit`/`windowSec` 是**进程生效**值,已 normalize)。🔴 **`limitIsDefault` / `windowIsDefault` 的语义**:报告生效值**恰好等于**代码默认值 —— **不是**「配置文件里没写这个键」的断言(网关**不读** `backthink.json`,与 config-facts 的 `isDefault` 同一条诚实口径:无从区分「配了 4」与「没配」)。`configFile` = **相对落点**(`config/backthink.json`,相对 `$DATA_DIR`)。⚠️ **它不含敏感信息、也不是写口**:改上限/窗口要改配置文件 + 重启(面板写路径**未做**)。语义与三态口径见 **§4.10(e)**。✅ **真机已核(路由存在性,无需凭据)**:`:10942` 上 `POST /api/v1/admin/backthink/self-wake` → **`405` + `Allow: GET, HEAD`**、`GET` 同路径无 token → **`401`**(负对照:同法打一个不存在的 admin 路径 → `404`)⇒ 这条路由**确在**运行中的产物里(⚠️ **不代表**已用真凭据拿到过 `data`;后者**未验**) |
|
||||
|
||||
| B20 | GET | `/api/v1/admin/memories/candidates` | **整理候选列表(v0.83 新增,切片 TIDY-1)**,只读、不审计。`?status=`(**省略 ⇒ 默认只回 `pending`**;落库取值是**闭集** `pending` / `applied` —— `revert` 会把候选退回 `pending`;⚠️ **`rejected` 尚未实现**,没有 reject 端点)+ `?limit=`(缺省 **100**、`>500` 钳到 500、非整数 → `400 bad_request`)。响应 `{data:{candidates:[…],count:N}}`,`candidates` **恒为非 null 数组**(空数组 = 「真没有候选」,与「没接线」的错误响应必须能区分)。每条:`id` / `action` ∈ `merge\|link\|archive\|reclassify` / `status` / `sourceIds[]`(恒为数组)/ `targetId?` / `personaId` / `scope` / `projectId?` / `kind?` / `proposedContent?` / `mergedContent?` / `reason` / `ruleVersion` / `runId?` / `scoreMin` / `scoreMax` / `createdAt`(**UTC RFC3339**)/ `evidence`。失败:未接线 → `500 tidy_unavailable`(**绝不回空数组**假装没有候选)、读取失败 → `500 tidy_candidates_failed`。语义见 **§4.15(a)** |
|
||||
| B21 | POST | `/api/v1/admin/memories/candidates/{id}/apply` | **应用一条候选(v0.83 新增,切片 TIDY-1;design/23 §7/§8)** —— 唯二真正改记忆的粒度之一。**无请求体**。`200 {data:{candidateId,action,status,reason?,targetId?,affectedIds[]}}`:`status` ∈ `applied`(真改了)/ `skipped`(**幂等**:这条已被决定过 ⇒ 不是错误,界面不得报红)。🔴 **别把这个 `status` 与 B20 候选行上的 `status` 混读**:这里是**本次操作的结果**(`memory.TidyApplyStatus`,另有 `failed` 一档),B20 那个是**候选的落库状态**(闭集 `pending`/`applied`)。`affectedIds` = 这次真的被改动的 `memory_items` id(**恒为数组**)。失败:候选不存在 → `404 not_found`;未接线 → `500 tidy_unavailable`;写失败 → `500 tidy_apply_failed`。**落审计** `memory.tidy.apply`(`result` 以 `ok` 开头 + 动作/结果/受影响行数;**绝不带正文**)。🔴 **软动作**:merge = 被吸收行 `status='merged'` + `merged_into=<存活 id>`,存活行 `merged_from` 追加 + `merge_count`+1 + `confidence` 取较大者 + `updated_at` 刷新;archive = `status='archived'`;**正文一个字不改、行一行不删** |
|
||||
| B22 | POST | `/api/v1/admin/memories/candidates/apply?runId=&limit=` | **批量应用候选(v0.83 新增,切片 TIDY-1)**。`runId` 给出 ⇒ 只应用**该轮产出的**候选;**省略 ⇒ 全部待定**(面板的「清全部」档)。`limit` 省略/空 ⇒ 领域侧默认 **100**;`>500` **钳到 500**;非整数或负数 → `400 bad_request`。`200 {data:{runId?,limit,total,truncated,applied,skipped,failed,merged,archived,reclassified,results[]}}`。🔴 **两条读数铁律**:① `total` = **真正命中筛选的待定候选数**,而 `applied+skipped+failed` 只覆盖 `limit` 之内 ⇒ 两者不等时 **`truncated:true`**(客户端**必须**如实显示「本轮处理 N / 共 M」);② `results[]` 逐条给 `status`(**失败隔离**:一条失败不拖累其余)。失败:`runId` 查不到(**留痕行**不存在)→ `404 tidy_run_not_found`(与「这一轮没有待定候选」**不是一回事**);未接线 → `500 tidy_unavailable`;写失败 → `500 tidy_apply_failed`。**落审计** `memory.tidy.apply`(带 run_id / limit / total / truncated / applied / skipped / failed)。⚠️ **旧候选(TIDY-1 之前产出的 77 条)的 `run_id` 是空串** ⇒ 「按 run 批量」找不到它们,要清它们只能走**全部待定**这一档 |
|
||||
| B23 | POST | `/api/v1/admin/memories/candidates/{id}/revert` | **回滚一条已应用的候选(v0.83 新增,切片 TIDY-1;design/23 §8「回滚」)**。**无请求体**。响应形状与 B21 **逐字同形**(`{candidateId,action,status,reason?,targetId?,affectedIds[]}`)。**无损可逆**:被吸收行复原为 `active` + `merged_into=''`、从存活行 `merged_from` 摘除、`merge_count` 减 1(下限 1)、候选退回 `pending`;**行永不物理删除**。失败:候选不存在 → `404 not_found`;未接线 → `500 tidy_unavailable`;写失败 → `500 tidy_revert_failed`。**落审计** `memory.tidy.revert`(与 B17 的 `memory.review.revert` **不同**:B17 按**记忆 id** 撤销 D3 的软合并,本条按**候选 id**,故不复用同一动词) |
|
||||
| B24 | POST | `/api/v1/admin/memories/tidy/run` | **手动真跑一轮整理(v0.83 新增,切片 TIDY-1)** —— 不等 23:00 的 panic button:按**同一套规则与配置**立刻跑一轮,**写候选 + 写运行留痕**,**不自动应用**(人工在环,能落地的只有 B21/B22)。**无请求体**。`200 {data:{runId,trigger:"manual",ruleVersion,durationMs,partitions,scanned,compared,pairs,vetoed,errors,truncated[],vetoReasons{},candidatesCreated,candidatesDuplicate,candidatesByAction{}}}`。🔴 **如实计数**:`candidatesCreated` = 本次**新建**候选数、`candidatesDuplicate` = 被**幂等键**挡下的重复数(同一批数据重复跑 ⇒ 全进 duplicate,**不是失败**);`errors` 非 0 面板必须显示;`truncated[]` 是**被护栏截断**的分区标签(扫描/比对/候选上限,或墙钟预算 `[budget]`)。失败:配置关闭 → `409 tidy_disabled`;超时 → `503 tidy_timeout`(**已跑部分仍写入了留痕**);只接线了试算(没接真跑)→ `500 tidy_run_unavailable`;未接线 → `500 tidy_unavailable`;失败 → `500 tidy_run_failed`。**审计不在这里写** —— 由领域侧记一条 `memory.tidy.run`(`trigger=manual` + run_id + 计数),**一次点击一条**。语义见 **§4.15(b)** |
|
||||
|
||||
> **读取 `GET /admin/observability/{logs,metrics,trace}` 兼任可观测推送的订阅握手**(该凭据的连接全部断开即退订)。REST 响应形状不变。
|
||||
|
||||
### 2.3 owner-only(40 条,`ownerAuth`)
|
||||
@@ -507,15 +517,15 @@
|
||||
| 400 | `bad_request`、`image_too_large`、`too_many_documents`、`invalid_payload`(**v0.79**)、`unknown_provider`(**v0.79**)、`not_configured`(**v0.79**)、`primary_not_configured`(**v0.80**)、`invalid_trigger`(**v0.82**) | 请求体/字段非法、图片超限(`image` **字符串长度**,**仅 base64 / `data:` 形态会触发**,见 §4.5)、附件超条数;**插件动作的载荷形状错 / provider 名未登记 / 该家没配齐**(这三个码自 v0.79 起由动作处理器给出,见 §4.14(b)(c));**面板保存的 `primary` 指名一家「已登记但未配齐」的家**(**v0.80**,写盘**之前**就拒 —— 那份配置会让后端下次起不来,见 §4.14(c));**`GET /admin/backthink?trigger=` 的值未知或为空**(**v0.82**,见 §4.10(e-1);`browser` 面板的**未知字段**也走 `invalid_payload`,见 §4.14(d)) |
|
||||
| 401 | `unauthorized`、`invalid_grant` | 无/坏凭据;refresh token 非法 |
|
||||
| 403 | `forbidden`、`unknown_role`(**v0.78**,面板快照的角色无法识别,理论不可达、fail-closed) | 角色不足;主会话不可删;**插件写动作的调用者不是 owner/admin**(v0.78,见 §4.14(b)) |
|
||||
| 404 | `not_found`、`session_not_found`、`document_not_found`、`account_not_found`、`media_not_found`(**v0.67 登记、v0.79 已实现**)、`tidy_run_not_found`(**v0.77 登记、已实现**)、`action_not_declared`(**v0.78 登记、已实现**) | 资源缺失;消息列表的会话不存在;文档缺失(含名字非法/穿越);渠道账号 id 不存在(C19/C20);**语音副本缺失**(含名字非法/穿越,三者同一响应,见 §4.5);**异步试算的 `runId` 未知 / 超 TTL / 进程重启后查不到**(三种原因刻意不区分,见 §4.12.1);**插件动作名未在该插件声明里出现过**(**不是**「没权限」,见 §4.14(b));**回滚的记忆条目不在被合并状态**(`not_found`,v0.79,见 §2.2 B17) |
|
||||
| 409 | `chat_busy`、`account_not_registered`、`plugin_disabled`(**v0.78**)、`config_unstartable`(**v0.80**)、`browser_unavailable` / `browser_session_not_open` / `config_rejected`(**v0.82**) | 待处理队列满;渠道账号已配置但没有已注册适配器(C19/C20,v0.66);**插件已被显式停用,其声明还在但入口与动作都下线**(v0.78,见 §4.14(b));**这次面板保存的结果会让 web-search 起不来**(主 provider 未配齐 / 未登记 ⇒ 下次启动 fail-fast)⇒ 已**按字节回滚**、运行中的工具保持原样(**v0.80**,见 §4.14(c));**`browser` 面板(v0.82,见 §4.14(d))**:本机没有可用浏览器来源 / 当前没有打开的页面(面板**不替模型开页**)/ 这次保存没能生效**已按字节回滚** |
|
||||
| 404 | `not_found`、`session_not_found`、`document_not_found`、`account_not_found`、`media_not_found`(**v0.67 登记、v0.79 已实现**)、`tidy_run_not_found`(**v0.77 登记、已实现**)、`action_not_declared`(**v0.78 登记、已实现**) | 资源缺失;消息列表的会话不存在;文档缺失(含名字非法/穿越);渠道账号 id 不存在(C19/C20);**语音副本缺失**(含名字非法/穿越,三者同一响应,见 §4.5);**异步试算的 `runId` 未知 / 超 TTL / 进程重启后查不到**(三种原因刻意不区分,见 §4.12.1);**插件动作名未在该插件声明里出现过**(**不是**「没权限」,见 §4.14(b));**回滚的记忆条目不在被合并状态**(`not_found`,v0.79,见 §2.2 B17);**候选 id 不存在**与**「按 run 批量应用」的留痕行不存在**(**v0.83**,B21/B23 与 B22 —— 后者刻意与「这一轮没有待定候选」区分开) |
|
||||
| 409 | `chat_busy`、`account_not_registered`、`plugin_disabled`(**v0.78**)、`config_unstartable`(**v0.80**)、`browser_unavailable` / `browser_session_not_open` / `config_rejected`(**v0.82**) | 待处理队列满;渠道账号已配置但没有已注册适配器(C19/C20,v0.66);**插件已被显式停用,其声明还在但入口与动作都下线**(v0.78,见 §4.14(b));**记忆整理被配置关闭**(`tidy_disabled`,v0.75 试算既有口径、**v0.83 的手动真跑 B24 复用同码同义** —— `MEMORY_TIDY_ENABLED=false`);**这次面板保存的结果会让 web-search 起不来**(主 provider 未配齐 / 未登记 ⇒ 下次启动 fail-fast)⇒ 已**按字节回滚**、运行中的工具保持原样(**v0.80**,见 §4.14(c));**`browser` 面板(v0.82,见 §4.14(d))**:本机没有可用浏览器来源 / 当前没有打开的页面(面板**不替模型开页**)/ 这次保存没能生效**已按字节回滚** |
|
||||
| 413 | `payload_too_large`、`document_too_large` | 上传/文档超限 |
|
||||
| 415 | `unsupported_media_type` | 图片/文档类型拒绝 |
|
||||
| 429 | `rate_limited` | 限流(框架级) |
|
||||
| 500 | `internal`、`chat_unavailable`、`chat_failed`、`voice_unavailable`、`stt_failed`、`tts_failed`、`tts_empty`、`voice_clone_failed`、`usage_unavailable`、`storage_unavailable`、`channel_events_unavailable`(**v0.67 已实现**,见 §2.4(a))、`refresh_unavailable`、`sessions_failed`、`messages_failed`、`session_failed`、`message_failed`、`document_upload_failed`、`login_failed`、`memory_review_unavailable`(**v0.79 新增**,切片 L45:入库前审查留痕存储未接线 —— 列表与回滚**共用**)、`reviews_failed`(**v0.79**:读留痕失败)、`revert_failed`(**v0.79**:回滚写失败)、`config_unreadable`(**v0.79**:面板可变源存在但读不出来,**不覆盖**原文件)、`config_write_failed`(**v0.79**:可变源写不进去,本次改动**没有**生效;⚠️ **回滚本身失败**时也用这个码并明说「盘上可能已经变了」—— **v0.82 的 `browser` 面板复用这两个码**,见 §4.14(d)) | 内部错误或依赖未接线(**绝不伪装成功**) |
|
||||
| 500 | `internal`、`chat_unavailable`、`chat_failed`、`voice_unavailable`、`stt_failed`、`tts_failed`、`tts_empty`、`voice_clone_failed`、`usage_unavailable`、`storage_unavailable`、`channel_events_unavailable`(**v0.67 已实现**,见 §2.4(a))、`refresh_unavailable`、`sessions_failed`、`messages_failed`、`session_failed`、`message_failed`、`document_upload_failed`、`login_failed`、`memory_review_unavailable`(**v0.79 新增**,切片 L45:入库前审查留痕存储未接线 —— 列表与回滚**共用**)、`reviews_failed`(**v0.79**:读留痕失败)、`revert_failed`(**v0.79**:回滚写失败)、`config_unreadable`(**v0.79**:面板可变源存在但读不出来,**不覆盖**原文件)、`config_write_failed`(**v0.79**:可变源写不进去,本次改动**没有**生效;⚠️ **回滚本身失败**时也用这个码并明说「盘上可能已经变了」—— **v0.82 的 `browser` 面板复用这两个码**,见 §4.14(d))、**记忆整理应用面(v0.83,切片 TIDY-1)**:`tidy_unavailable`(整理写面未接线;**候选列表 / 应用 / 回滚共用**,与只读试算组同名同义)、`tidy_candidates_failed`(读候选失败)、`tidy_apply_failed`(单条或批量应用失败)、`tidy_revert_failed`(回滚写失败)、`tidy_run_unavailable`(**只接线了只读试算、真跑入口没接** —— 「没跑」与「跑了没产出」必须能分辨)、`tidy_run_failed`(真跑失败) | 内部错误或依赖未接线(**绝不伪装成功**) |
|
||||
| 501 | `not_implemented`、`panels_unavailable`(**v0.78**)、`action_not_implemented`(**v0.78**) | HistoryStore 等未注入;**插件面板中枢未接线**(见 §4.14(a));**动作已声明,但宿主没有登记它的处理器** —— 🔴 **v0.79 起不再是兜底档**:内置 `web-search` 的动作已全部登记(见 §4.14(b)(c)),这条只在「声明写了却没实现」时出现 |
|
||||
| 502 | `action_failed`(**v0.78**)、探针码 `unauthorized` / `rate_limited` / `quota_exceeded` / `network` / `upstream` / `bad_response` / `no_results` / `unknown`(**v0.79**)、`browser_action_failed`(**v0.82**) | **插件动作执行失败**(结构化错误码 + 文案,详细原因只进后端日志);中间一组是 `plugin.testProvider` 探针的**上游结论**(其中 **`no_results` = 连通正常但没查到结果**);末一个 = `browser` 面板的截图 / 指纹自检**执行失败**(**v0.82**,见 §4.14(b)(d)) |
|
||||
| 503 | `timeout`、`chat_dispatch_failed`、`catalog_unavailable` | 派发/上游不可用 |
|
||||
| 503 | `timeout`、`chat_dispatch_failed`、`catalog_unavailable`、`tidy_timeout`(**v0.83** 手动真跑超时:**本轮未完整跑完,已跑部分仍写入了留痕** —— 不假装跑全了) | 派发/上游不可用 |
|
||||
|
||||
> **未确证**:`conflict`(409) 的通用码存在(框架级 `ErrConflict`),但除了 `chat_busy` 之外,本文档未逐一确证还有哪些端点会带它。
|
||||
> **⚠️ `416` 不是错误信封(v0.67 登记,**v0.79 已实现**)**:`GET /api/v1/media/{name}` 的 `Range` 语法非法或范围不可满足时,**由 Go 标准库直写** `416`(`text/plain` + `Content-Range: bytes */<size>`),**不套** `{"error":{…}}` ⇒ 客户端**不要**拿 `error.code` 去解析它(见 §4.5)。这是字节流端点的既有事实(`/audio/speech` 的 `200`、`/documents/{name}` 的 `200` 同样不是 JSON 信封),**不是**本版新引入的怪癖。
|
||||
@@ -1297,7 +1307,41 @@
|
||||
|
||||
---
|
||||
|
||||
## 5. env 与模型配置(与契约相关)
|
||||
### 4.15 记忆整理「应用面」(B20–B24;**v0.83 新增,切片 TIDY-1**)
|
||||
|
||||
> 依据 `design/23`(§7 人工在环 / §8 软动作与回滚 / §10 status 列 / §11 管理面)。**背景**:v0.75 起只有「试算」(B14/B15 —— 只回建议、**零副作用**)⇒ 主人的实测口径是「候选 77 条**全 pending**、两天**一条没应用**」。本版把**执行面**补上:候选能列、能应用、能回滚,整理能手动真跑。
|
||||
|
||||
#### (a) 端点族一览
|
||||
|
||||
| 端点 | 语义 | 关键参数 | 审计 |
|
||||
|---|---|---|---|
|
||||
| B20 `GET …/memories/candidates` | 列候选(含 `id`/`runId`/证据) | `status`(省略 = 全部)、`limit`(缺省 100 / 钳 500 / 非法 400) | 无(只读) |
|
||||
| B21 `POST …/candidates/{id}/apply` | 应用**一条** | 无体 | `memory.tidy.apply` |
|
||||
| B22 `POST …/candidates/apply` | **批量**应用 | `runId`(省略 = **全部待定**)、`limit`(缺省 100 / 钳 500 / 非法 400) | `memory.tidy.apply` |
|
||||
| B23 `POST …/candidates/{id}/revert` | 回滚**一条**已应用 | 无体 | `memory.tidy.revert` |
|
||||
| B24 `POST …/memories/tidy/run` | 手动**真跑**一轮(写候选 + 留痕,**不自动应用**) | 无体 | 领域侧记 `memory.tidy.run`(一次点击一条) |
|
||||
|
||||
形状逐字段见 §2.2 的 B20–B24 行。**门一律 `writeAuth`**(owner\|admin)—— 候选正文含隐私,与同族 `GET /admin/memories`、`POST …/tidy/dry-run` 同档。⚠️ `design/23 §11` 表格把**回滚**标为 owner-only,而实现用了 `writeAuth`:**口径差异待裁决**(若以设计为准,`routes.go` 里把那两行换成 `ownerAuth` 即可,一行一处;与 §2.2 B17 的同类差异是同一件事)。
|
||||
|
||||
#### (b) 三条硬约束(客户端与面板都按这个编程)
|
||||
|
||||
1. **幂等不是错误**:执行闸是**候选状态的原子条件更新**(`WHERE id=? AND status='pending'` + `RowsAffected`),记忆行侧还有第二道闸(`AND status='active'`)。因此并发重复点击**只有一个赢家**,其余拿到 `200` + `status:"skipped"` —— 面板**不得**把它渲染成失败,也**不得**据此提示「重试」。
|
||||
2. **超限必须如实**:批量回执的 `total` 与 `applied+skipped+failed` **口径不同**(前者 = 命中筛选的**真**待定数,后者只覆盖 `limit` 之内),差集由 **`truncated:true`** 表达。🔴 这是「不许把本轮 100 / 共 342 画成全部完成」的契约级要求。
|
||||
3. **软动作、永不物理删除**:merge = 被吸收行 `status='merged'` + `merged_into`;archive = `status='archived'`;**`content` 一个字不改、行一行不删** ⇒ `revert`(B23)必然无损可逆。同时,**软删行必须真的从读侧消失**(v0.83 行为修复):记忆的四条列表读路径(`ListByPersona` / `List` / `ListScope` / `ListProject`)只回 `status='active'`,因此被软合并/归档的记忆**不再被召回、不再进上下文、不再出现在 `GET /admin/memories`**。🔴 **只有两处刻意不过闸**:按 id 的 `Get`(撤销软合并必须先读得到被吸收的行)与 **`GET /admin/memories/reviews`**(审查留痕本身没有 status 概念,且它是软删行的查看入口 —— 加闸等于审计自我抹除)。
|
||||
|
||||
#### (c) 调度:到点即跑 + catch-up(对客户端零形状影响)
|
||||
|
||||
原实现是「`interval=24h` 的 ticker 从**进程启动**算起」⇒ 后端两天重启十几次,**24h 永远走不完**(`memory_tidy_runs` 只有 1 行,这就是「两天没跑」的一半根因)。现在以 **`memory_tidy_runs` 的留痕行**作为「上次到底跑过没有」的**持久事实**,启动时与心跳到点都判一次(三条判据),**重启不饿死**。B24 是它的手动入口(panic button),与心跳**同一套规则、同一份配置**。
|
||||
|
||||
#### (d) 诚实边界(**别当已验证**)
|
||||
|
||||
- 🔴 **生产库全程零动作**:1191 行记忆与 77 条待定候选**都没有被应用或清理**。本版交付的是**能力**,不是「已经清过了」—— 真跑与批量应用要主人拍板后执行(可分批、可 revert)。
|
||||
- **面板上的真机端到端未跑**:本版登记时**前端操作面还没落地**(前端切片并行在做),因此本版证据是**存储层单测 + 网关端到端测试 + 变异自证**,不含「面板点一下真生效」。
|
||||
- **旧候选的 `run_id` 是空串**(77 条在生产库里都是这样)⇒ 「**按 run 批量**」(B22 带 `runId`)**找不到它们**;要清它们必须走**全部待定**那一档。
|
||||
- 候选表的 `run_id` 列目前**只在存储包前滚**(`memory.NewStore` 的 `ensureTidySchema`),**未**同步进迁移 `0019`;组合根每次启动都会跑到前滚,因此生产库拿到该列没问题,但「迁移是不是唯一真源」这条口径**有欠**。
|
||||
- **`memory.recall` 的语义召回在本机不可复现**(远端 embedder 对同一文本返回不同向量,TIDY-2 诊断发现)—— 它**不影响**本族的规则层判定与状态机,但**影响「整理候选是否可复现」**,登记在此以免被当成实现缺陷。
|
||||
|
||||
|
||||
|
||||
> **`.env` vs 面板文件**:面板「LLM 模型」页保存写 `<DATA_DIR>/models.json`(默认 `./data/models.json`);「渠道管理」页写 `<DATA_DIR>/channels.json`。**只要文件存在且有效,运行时优先用它**,`.env` 只作兜底/播种层。`.env.example` 与实现同步(键名逐字一致、值占位符)。
|
||||
|
||||
@@ -1370,7 +1414,20 @@
|
||||
> 只保留最近若干版(**v0.60 起**);更早的全部历史见归档 `Docs/archive/api-client.v0.65.md` §5(v0.28 → v0.59)。
|
||||
> 每版一句话 + 三档标注。**三档定义见 [§0.4](#04-三档标注惯例)。**
|
||||
|
||||
### v0.82(当前契约版本)
|
||||
### v0.83(当前契约版本)
|
||||
|
||||
**记忆整理「应用面」—— 五条 `writeAuth` 新端点 + 读侧 status 闸(行为修复)**(切片 TIDY-1 `454ddb0` + `9126d65` + `f847769`;依据:`design/23` §7/§8/§10/§11;主人实测口径「候选 77 条全 pending、两天一条没应用」+「重复无效记忆都一千多条了」。)
|
||||
|
||||
- **① 五条新端点(B20–B24)**:`GET …/memories/candidates`(候选列表,只读不审计)、`POST …/candidates/{id}/apply`(单条应用)、`POST …/candidates/apply?runId=&limit=`(批量;`runId` 省略 = 全部待定)、`POST …/candidates/{id}/revert`(单条回滚)、`POST …/memories/tidy/run`(手动真跑,写候选 + 留痕、**不自动应用**)。语义与逐字段形状见 **§2.2 B20–B24** 与 **§4.15**。审计:应用/回滚走 `memory.tidy.apply` / `memory.tidy.revert`(**只记计数与动作、不带正文**);真跑的 `memory.tidy.run` 由**领域侧**记(一次点击一条)。
|
||||
- **② 读侧 status 闸(行为修复,形状零变化)**:记忆的四条**列表**读路径(`ListByPersona` / `List` / `ListScope` / `ListProject`)现在只回 `status='active'` ⇒ 被软合并(`'merged'`)/被归档(`'archived'`)的记忆**不再被召回、不再进上下文、不再出现在 `GET /admin/memories`**。修的是「软动作写了 status,读侧却不看 ⇒ 清了也白清」(生产库已有 10 条 `merged` 行是现成反例)。🔴 **两处刻意不过闸**:`Get`(撤销软合并要先读得到被吸收的行)与 `GET /admin/memories/reviews`(留痕表本身没有 status 概念,且它是查看入口 —— 加闸等于审计自我抹除)。
|
||||
- **③ 调度不再饿死(对客户端零形状影响)**:整理由「ticker 从进程启动算起、重启十几次就永远走不完 24h」改为**到点即跑 + catch-up**,以 `memory_tidy_runs` 留痕行为**持久事实**判「上次跑过没有」;配 B24 作手动 panic button。
|
||||
- **行为变更**:**有(一处)**:② 让软删行从检索/上下文/面板里**真的消失** —— 这正是本版的目的;对**不软删任何东西**的部署,行为与 v0.82 逐字相同。
|
||||
- **破坏性**:**无**(五条都是新端点;② 是读侧收紧,不删键、不改键义、不改既有端点形状)。
|
||||
- **计数**:`writeAuth` 段 **19 → 24**、管理路由总数 **73 → 78**(`14 + 24 + 40`;`routes.go` 核:经 `writeAuth` 挂载的 admin 路由实有 26,按历史口径减 §2.4(a) 的 `channels/events` 与 `.../counters` ⇒ 24)。§2.2 行数 **B1–B24**。
|
||||
- **验证证据(本版实测)**:`go build ./...` + `go vet` 绿;`internal/runtime/memory` 包全量测试绿 + 新增读路径逐条断言(4 条列表 + 检索端到端「内容逐字相同的两条,软合并后只召回一条」+ `Get` 反向护栏);`internal/services/gateway` 新增面板端到端用例(真 `sql.DB` + 真 store + 真 HTTP handler);**两组变异**(overlay 把闸门改成恒真)都被打红。
|
||||
- ⚠️ **诚实边界**:① **生产库全程零动作**(1191 行记忆 / 77 条候选都还没应用或清理 —— 交付的是**能力**);② **面板真机端到端未跑**(前端操作面在本版登记时未落地,并行切片在做);③ 旧候选 `run_id` 为空串 ⇒ 只能用「全部待定」档清;④ 候选表 `run_id` 列只在存储包前滚、**未**入迁移 `0019`;⑤ 远端 embedder 对同一文本返回不同向量 ⇒ **语义召回不可复现**(不影响规则层与状态机,但影响「候选是否可复现」)。
|
||||
|
||||
### v0.82(上一版)
|
||||
|
||||
**三面增量 —— ① `tool_execution.end` 带可选 `resultSummary`(工具执行结果实时可见,切片 Y1 `215eedf`)|② 自排的成本可见面:`?trigger=` 服务端筛选 + 新端点 `GET /admin/backthink/self-wake`(切片 Y3 `4213b26`)|③ `browser` 插件的管理面板与六个动作(切片 Y4 `e0a6bca`)**(依据:安卓端按 ① 把工具卡 UI 定死;`design/12 §N C.5/C.6` 的自排成本口径与筛选;`design/28 §8 B4` 的面板入口。)
|
||||
|
||||
@@ -1701,8 +1758,8 @@
|
||||
| **env `CHAT_MAX_PARALLEL_TURNS`** | **未接线**:`Deps.MaxParallelTurns` 是构造期旋钮,但组合根未从任何 env 读它 ⇒ **生产恒为默认 2** |
|
||||
| **`tool_execution.update` 的生产者** | wire 映射已实现,但工具分发器暂无中途进度上报源(全仓无 `StreamKindToolExecutionUpdate` 的生产者)⇒ 实际**暂无生产者**(`start`/`end` 真实发出) |
|
||||
| **`chat_messages.turn_id` 上的 IM 行** | IM 渠道桥**不走**网关编排器 ⇒ 其行 `turn_id` 留空属**预期**(契约里该键缺席),不是缺陷 |
|
||||
| **记忆整理候选的 apply / reject / revert + 候选列表 + 运行历史**(`design/23 §11`) | **未实现**(v0.75 **只**加了「试算」B14):试算只「算给我看」、**不改任何记忆**。`GET /admin/memories/candidates`、`POST .../candidates/{id}/{apply,reject,revert}`、`GET /admin/memories/tidy/runs`、`POST /admin/memories/tidy/run` **均尚未实现**。客户端**不要**假定它们存在(`design/23` 的第一版口径本就是「只产建议,apply 才写记忆」,apply 未做的前置原因见该节开放点)。⚠️ **别与 v0.79 的 `POST /admin/memories/{id}/revert` 混淆**:后者是**入库前审查(D3)软合并**的回滚(§2.2 B17),与「整理候选」是**两件事** |
|
||||
| **`GET /api/v1/admin/memories` 的整理相关字段**(`kind` / `tags` / `confidence` / `createdAt` / `updatedAt` / `sourceConversation` / `status` / `mergedInto` / `mergeCount`) | **未实现**:该端点当前仍只回 `{id,scope,text}`(`design/23 §11` 登记的读侧缺口)。v0.75 **未**改它的形状 ⇒ 面板暂时无法在这一页按 kind/status 过滤(试算端点 B14 的候选自带这些分区键与 kind) |
|
||||
| **记忆整理候选的 apply / reject / revert + 候选列表 + 运行历史**(`design/23 §11`) | ⚠️ **已部分实现(v0.83,切片 TIDY-1)** —— v0.75 只加了「试算」(B14,**不改任何记忆**);v0.83 补上了**执行面**:`GET /admin/memories/candidates`(B20,只按 `status` 筛)、`POST .../candidates/{id}/apply`(B21)、`POST .../candidates/apply`(**批量**,B22)、`POST .../candidates/{id}/revert`(B23)、`POST /admin/memories/tidy/run`(**手动真跑**,B24)**都已实现并推库**。🔴 **仍缺三样(别假定它们存在)**:① **`reject`**(把候选标成「不采纳」而不应用)**没有端点**;② **运行历史读侧** `GET /admin/memories/tidy/runs` **没有**(真跑的留痕行**已经落库**在 `memory_tidy_runs`,只是没有读口);③ 候选列表**不能按 `runId` 筛**(只能按 `status`)—— 按 run 只能走**应用**那一档(B22 的 `runId`)。⚠️ **别与 v0.79 的 `POST /admin/memories/{id}/revert` 混淆**:后者是**入库前审查(D3)软合并**的回滚(按**记忆 id**,§2.2 B17),与「整理候选」(按**候选 id**)是**两件事** |
|
||||
| **`GET /api/v1/admin/memories` 的整理相关字段**(`kind` / `tags` / `confidence` / `createdAt` / `updatedAt` / `sourceConversation` / `status` / `mergedInto` / `mergeCount`) | **未实现**:该端点当前仍只回 `{id,scope,text}`(`design/23 §11` 登记的读侧缺口)。v0.75 **未**改它的形状 ⇒ 面板暂时无法在这一页按 kind/status 过滤(试算端点 B14 的候选自带这些分区键与 kind)。⚠️ **v0.83 起服务端已按 status 过滤**(四条列表读路径只回 `active`)⇒ 被软合并 / 被归档的记忆**不再出现在这一页**;但**字段面仍未补齐** ⇒ 面板在这一页**看不到** `status`/`mergedInto`/`mergeCount`,也就**无法显示「哪些已被合并」**(那要看 **B20 候选列表**或 **B16 审查留痕**) |
|
||||
| **✅ 记忆「入库前审查」的面板面**(`GET /api/v1/admin/memories/reviews`、`POST /api/v1/admin/memories/{id}/revert`) | **已实现(v0.79,切片 L45;v0.77 登记时无 HTTP 入口)**:两条都走 `writeAuth`(owner\|admin),形状见 §2.2 B16/B17。⚠️ **残留**:① 留痕列表**只有 `limit`、没有分页游标**(无 `before`/`offset`);② `memory_ingest_reviews` **无 TTL / GC**;③ 回滚**只回滚「被吸收行」这一侧**(不恢复存活行被抬高的 `confidence`);④ 门与 `design/23 §11` 表格的 owner-only **有差异**(待裁决)。同样是**新审计动词** `memory.review.revert`(`design/23 §16.4` 表里还没有它,需设计侧补记) |
|
||||
| **✅ v0.78 插件动作的「执行器」**(`POST /admin/plugins/{id}/actions/{action}` 的动作本体) | **已接线(v0.79,切片 L1/PLG-2 执行面接线)**:插件管理器新增**动作处理器登记面**(`Manager.RegisterActions`;单一全局 `SetExecutor` 已删除),组合根在 `wirePanelHub` 里登记内置 `web-search` 的四个动作 + 候选来源 `plugin.providers` ⇒ **这些动作真执行**(真读写可变源、探针真发一次 HTTP)。**`501 action_not_implemented` 只在「已声明但宿主未登记处理器」时出现**(不再是兜底档)。✅ **v0.80(切片 W3)**:面板保存后**热重载**(重建工具 + 原子替换注册表条目,**下一轮生效、不需重启**;会让后端下次起不来的配置 ⇒ **按字节回滚** + `409 config_unstartable`);面板**已可配主 provider / 降级链**(`primary` / `fallback[]`)。⚠️ **残留**:**探针只用 `httptest` 假上游**(未对真实上游连通);**启用态不跨重启持久化**;**真机面板端到端未跑**。见 §4.14(b)(c) / §7.4-26 / §7.4-27 |
|
||||
| **🔴 v0.78 热装插件的面板声明来源**(`plugins/<id>/manifest.json` 读取) | **未实现**(切片 PLG-2 / PLG-1):目录能键热装插件、生命周期已接,但**没人喂声明** ⇒ 热装插件在 `GET /admin/panels` 的 `pluginIds` / `groups` 里**恒为「未声明管理页面」**。有声明的是**内置**插件(组合根装配期 `Declare`):`web-search`(v0.78 起)、**`browser` / `settings`(v0.82 起,切片 Y4)** |
|
||||
|
||||
Reference in new issue
Block a user