docs(contract): v0.76 登记三面(工具回看 toolCalls[] / 未读 unreadCount / 记忆整理试算加性异步 B15)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- ① 消息读取面 toolCalls[]:数据源 = 已落盘轮次链路图(flow_graphs/0022,168h),
  不从正文反解;图取不到 ⇒ 字段整体缺席(绝不回空数组冒充「没调过工具」)
- ② message_persisted.data.unreadCount + GET /sessions 列表项同键;
  口径 = 绝对未读 + 主人单一已读游标(chat_sessions.read_at)⇒ 多端共享(与安卓已对齐)
- ③ 记忆整理试算加性异步:?async=1 ⇒ 202 + runId,新增 B15 轮询端点;
  不带 async 行为逐字不变;结果仅进程内存(TTL 30min / 并发 1 / 重启即失效 ⇒ 如实 404)
- 三批均标注「契约先行 · 尚未落地」,实现落地后回改并去掉标注
This commit is contained in:
AskaEth committed 2026-10-06 07:42:19 +08:00
1 parent 6233ece1fa
commit cb232e7360
1 file changed
+34 -2
+34 -2
View File
@@ -1,6 +1,6 @@
# Cyrene v2 客户端 API 契约
> **契约版本:`v0.75`**(上一版 `v0.74`,其条目见 §6;`v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
> **契约版本:`v0.76`**(上一版 `v0.75`,其条目见 §6;`v0.74` / `v0.73` / `v0.72` / `v0.71` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`;`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,后端全量字段级文档)。归档的原因是**结构性臃肿 + 历史条目并列 + 长期漂移**;本文档**不复制**那段历史,需要考古时去归档。
@@ -18,6 +18,8 @@
> **v0.74 是 IM 路由主动消息的提醒版(已实现,纯增量)**:`message_persisted` 的 `data` 新增**可选**结构化判据 `proactive:true` —— IM 路由(`im_bridge`)的主动推送在落库之后**追加**一条带该键的同类帧,客户端据此在**非前台**补一发浏览器通知(此前通知只挂在 `proactive_message` 上,而 `auto` 提示在主人有已连接 IM 渠道时**优先走 IM** ⇒ 实际最可能发生的路径恰恰不弹)。判据是**结构化字段**(不是正文/提示词):普通消息(Web 回合 / IM 回复 / IM 镜像)的帧**永不**带该键 ⇒ 不可能被误判成主动消息。同一行仍照旧发一条**不带**该键的普通镜像帧(v0.72 起的行为逐字不变,两帧同 id ⇒ 客户端按 id 去重后仍是一个气泡、仍只提醒一次)。详见 **§3.4.4 后的「`message_persisted` 的 `proactive` 判据」**。
> **v0.76 是三面增量版(⚠️ 契约先行:三批形状**已登记**,实现**进行中**,登记时尚**未落地**)**:① **消息读取面新增 `toolCalls[]`** —— `GET /api/v1/sessions/{id}/messages` 的消息对象加可选 `toolCalls`(`omitempty`),每条含工具名 / 状态(`ok`/`error`/`unknown`)/ 耗时毫秒 / **已脱敏**的入参与结果摘要;数据源是**已落盘的轮次链路图**(`flow_graphs`,迁移 `0022`,保留期 168h),**不从正文反解**。🔴 **缺席语义**:图取不到(超保留期 / 该轮未落盘 / 隔离渠道会话从不入库)⇒ 字段**整体缺席**,**绝不回空数组**冒充「这一轮没调过工具」。② **未读面** —— `message_persisted` 的 `data` 加可选 `unreadCount`(`omitempty`),`GET /api/v1/sessions` 列表项加同键;口径 = **绝对未读 + 主人单一已读游标(`chat_sessions.read_at`)⇒ 多端共享**(Web 读了移动端也清),这是与安卓侧已对齐的**取舍**,不是缺陷。③ **记忆整理试算加性异步** —— `POST /api/v1/admin/memories/tidy/dry-run?async=1` ⇒ `202` + `runId`;新增 `GET /api/v1/admin/memories/tidy/dry-run/{runId}` 轮询;**不带 `async` 仍是原同步行为**。**无破坏性变更**(新键 / 新可选参数 / 新端点;老客户端零影响)。详见 §1(会话列表与消息读取)、§2.2 B15 / §4.12.1、§3.4.4。
> **v0.75 是记忆整理试算版(已实现,纯增量)**:新增 1 条管理端点 **`POST /api/v1/admin/memories/tidy/dry-run`**(**只读语义的 POST**,`writeAuth` = owner/admin)—— 跑**同一套**记忆整理规则(召回门 + 包含判定 + 规则否决,与低峰定时轮共用代码路径),把候选**直接回在响应里**,**一行都不写**(不写 `memory_tidy_candidates` / `memory_tidy_runs`,绝不改 `memory_items`)。修的是「整理器只在 23:00–07:00 跑(design/23 §4),在它第一次开跑前面板只能空着」——主人要「现在就想看到在 736 条上它会建议合并哪些」。**零副作用是硬约束**(后端有逐行快照单测钉死);失败**一律如实作答**(`tidy_unavailable` / `tidy_disabled` / `tidy_recall_unavailable` / `tidy_timeout`),**绝不回空候选数组假装「没有建议」**。**无破坏性变更**(新端点;既有端点/帧一字未改)。详见 **§2.2 B14 / §4.12**。
---
@@ -175,6 +177,7 @@
| B12 | GET | `/api/v1/admin/config-facts` | **配置事实面(v0.68 新增,只读;v0.75 追加 3 键)**:记忆整理 7 键 + 后台思考心跳 3 键(`ENABLE_BACKGROUND_THINKING`/`THINK_INTERVAL_SEC`/`THINK_QUIET_INTERVAL_SEC`)+ 安静时段 `QUIET_START_HOUR`/`QUIET_END_HOUR` 的**进程生效值**,逐条带 `default`/`isDefault`。**不含任何密钥/令牌值**(连「是否已配置」都不回凭据类的)。形状见 §4.10 |
| B13 | GET | `/api/v1/admin/flow/graph` | **链路图快照 / 多轮历史回放(v0.71 新增;v0.73 增量)**,只读、不审计。`?sessionId=`(省略 = 主会话 `web`)+ **v0.73 新增 `?turnId=`**(省略 = 最近一轮,**先内存后落盘**)。响应 `{data:{sessionId,turnId,source,graph,turns[]}}`:`graph` 是**一份 §3.4.5 `graph_update` 同形快照**,没有该轮时**如实为 `null`**(**绝不画假图**);`source` ∈ `live`(内存最近一轮)/`persisted`(落盘的历史轮)/`none`;`turns[]`(`{turnId,createdAt}`,最新在前)供面板选历史轮(**v0.73**)。未接线图源 → `500 flow_unavailable`。语义见 §4.11 |
| B14 | POST | `/api/v1/admin/memories/tidy/dry-run` | **记忆整理「试算」(v0.75 新增,只读语义的 POST)**:跑同一套整理规则、把候选**直接回在响应里**,**零副作用**(不写任何表)。请求体可为空。响应 `{data:{dryRun:true,recallMode,durationMs,ruleVersion,partitions,scanned,compared,pairs,vetoed,vetoReasons,truncated?,candidates[]}}`;`candidates` 恒为非 null 数组(空数组 = 「算过了,没有建议」,与「没算出来」的错误响应**必须能区分**)。失败:未接线 → `500 tidy_unavailable`、配置关闭 → `409 tidy_disabled`、语义召回不可用 → `500 tidy_recall_unavailable`、超时 → `503 tidy_timeout`。**不审计**(只读计算)。语义与形状见 §4.12 |
| B15 | GET | `/api/v1/admin/memories/tidy/dry-run/{runId}` | **异步试算结果查询(v0.76 新增,⚠️ 契约先行 · 尚未落地)**:配合 `POST …/memories/tidy/dry-run?async=1`(后者仍属 B14,`async=1` 时回 `202` + `{data:{runId,status:"running",startedAt}}`)轮询取回结果。响应 `{data:{status:"running"|"done"|"failed", result?, error?}}`:`done` 时 `result` 与 §4.12 同步响应的 `data` **同形**(**同一套 JSON,不是第二份**);`failed` 时 `error` 为 `{code,message}`(`code` 沿用 §4.12 的失败码)。⚠️ **结果只活在进程内存**(单二进制、零跨进程):TTL **30 分钟**、同时只允许 **1** 个在跑;进程重启后旧 `runId` **如实失效** ⇒ `404 tidy_run_not_found`,**绝不假装还在跑**。鉴权与 B14 同档(`writeAuth` = owner/admin)、同样**不审计**。语义见 §4.12.1 |
> **读取 `GET /admin/observability/{logs,metrics,trace}` 兼任可观测推送的订阅握手**(该凭据的连接全部断开即退订)。REST 响应形状不变。
@@ -833,6 +836,24 @@
> ⚠️ 本端点的错误 `message` 只说明**原因类别**,原始错误(可能含内网地址等内部细节)只进服务端日志。
### 4.12.1 记忆整理「试算」的**加性异步**(`?async=1` + B15;v0.76 新增 · ⚠️ 契约先行、尚未落地)
**为什么有这条**:试算在**真机**上实测 **89.9 秒 / 744 条**(784 条那轮同量级)。同步 HTTP 会被反代/客户端超时切断,而**把上限抬高**(治理「护栏被打满 ⇒ 候选数被上限而不是规则决定」)只会让它更慢。因此加一条**可选**的异步路径:**默认行为一字不改**(不带 `async` 仍是 §4.12 的同步语义),只在显式带 `async=1` 时改走后台。
| 项 | 口径 |
|---|---|
| 触发 | `POST /api/v1/admin/memories/tidy/dry-run?async=1`(请求体与 §4.12 同:可为空) |
| 立即响应 | **`202`** + `{data:{runId,status:"running",startedAt}}`;`runId` 为进程内不透明字符串(客户端当不透明值用,**不要**解析其结构) |
| 轮询 | `GET /api/v1/admin/memories/tidy/dry-run/{runId}`(B15)⇒ `running` / `done`(带 `result`)/ `failed`(带 `error`) |
| 结果形状 | `done` 时的 `result` = §4.12 同步响应的 `data` **逐键同形**(含 `candidates` 恒非 null) |
| 生命周期 | **进程内存**,TTL **30 分钟**;**同时只允许 1 个在跑**(第二个请求如实拒绝或复用既有 `runId`,实现口径见 `Docs` 这一版的实现提交);**进程重启 ⇒ 旧 `runId` 失效** ⇒ `404 tidy_run_not_found` |
| 鉴权 | 与 B14 同档(`writeAuth` = owner/admin);**不审计** |
| 零副作用 | 🔴 与同步路径**同一条硬约束**:异步路径同样**一行都不写** `memory_tidy_candidates` / `memory_tidy_runs`、**绝不改 `memory_items`**(单测必须**在异步路径上**也钉住逐行快照比对) |
| 失败 | 沿用 §4.12 的失败码(`tidy_unavailable` / `tidy_disabled` / `tidy_recall_unavailable` / `tidy_timeout` / `tidy_dry_run_failed`);**不回空候选数组假装「没有建议」** |
| 破坏性 | **无**(新可选参数 + 新端点;老客户端逐字不变) |
> ⚠️ **契约先行的诚实标注**:本小节与 B15 在 v0.76 登记时**尚未实现**(形状由设计先行定下,供客户端与实现并行推进)。落地后须回本文件就近更正,并在 §6 的 v0.76 条目里去掉「契约先行」标注。
---
## 5. env 与模型配置(与契约相关)
@@ -899,7 +920,18 @@
> 只保留最近若干版(**v0.60 起**);更早的全部历史见归档 `Docs/archive/api-client.v0.65.md` §5(v0.28 → v0.59)。
> 每版一句话 + 三档标注。**三档定义见 [§0.4](#04-三档标注惯例)。**
### v0.75(当前契约版本)
### v0.76(当前契约版本 · ⚠️ **契约先行:三批形状已登记,实现进行中**)
**三面增量 —— ① 消息读取面 `toolCalls[]`(工具回看)|② 未读面 `unreadCount`(多端共享单游标)|③ 记忆整理试算的加性异步**(依据:安卓侧 `Cyrene-For-Android/devdocs/02` 明确「工具回看面比未读面更重要、且要带工具输出/摘要」,以及主人 2026-10-05 裁定的「抬高 + 治入库太宽」导致试算更慢)。
- **① 工具回看面(纯增量)**:`GET /api/v1/sessions/{id}/messages` 的消息对象加可选 `toolCalls`(`omitempty`)—— 每条含 `name` / `status`(`ok`|`error`|`unknown`)/ `durationMs` / `argsSummary` / `resultSummary`(后两者是**图里已有的、已脱敏**内容并按上限截断)。数据源 = **已落盘的轮次链路图**(`flow_graphs`,迁移 `0022`,保留期 168h),**不从正文反解、不从内存态猜**。🔴 **缺席语义**:图取不到 ⇒ 字段**整体缺席**(**绝不回空数组**冒充「没调过工具」)。修的是安卓侧「回看这条回复时看不到它当时调了什么」。
- **② 未读面(纯增量)**:`message_persisted` 的 `data` 加可选 `unreadCount`(`omitempty`,**缺席 = 老行为**),`GET /api/v1/sessions` 的列表项加同键。口径 = **绝对未读 + 主人单一已读游标(`chat_sessions.read_at`,迁移 `0010`)⇒ 多端共享**:Web 读了,移动端的红点也清 —— 这是与安卓侧**已对齐**的取舍(他们明确赞成),**不是缺陷**,不许改成「每端独立未读」而不经裁定。
- **③ 记忆整理试算加性异步**:`POST …/memories/tidy/dry-run?async=1` ⇒ `202` + `runId`(新增 B15 轮询端点)。**不带 `async` 仍是同步** ⇒ 老客户端(含现有面板)逐字不变。结果活在**进程内存**(TTL 30 min、并发 1、重启即失效 ⇒ 如实 `404`)。
- **行为变更**:无(①②只在既有响应里加可选键;③只在显式带参时改变路径)。
- **破坏性**:无。
- **⚠️ 迁移号预告**:本版实现若需新迁移,编号从 **`0023`** 起(`0020` 预留给语音切片、`0021`/`0022` 已用)。
### v0.75
**记忆整理「试算」端点 —— 新增 `POST /api/v1/admin/memories/tidy/dry-run`:跑同一套整理规则、把候选直接回在响应里,且一行都不写**(依据主人 2026-10-05 要求「现在就想看到在 736 条上它会建议合并哪些,而不等到 23:00」;对齐 `design/23 §4`「低峰定期后台任务 + 手动可触发的只读试算」)。