---
name: index-gateway-memory
description: >
  教第三方用 Auto-Coder Index Gateway 的 HTTP API 完成记忆：用 Auto-Coder Chat
  的 API Key 鉴权，在自己的项目里写入、查询、定位、校验、导出一份 Computer Index。
  Use when a third party or their agent must store or recall memory through this
  gateway, or asks about 记忆, 写入记忆, 查询索引, /api/projects, or the Index
  Gateway API. Not for deploying or operating the gateway.
---

# 用 Index Gateway 完成记忆

同一份说明发布在 Gateway 官网的 `/docs/memory`，打开就能看，不需要登录。Markdown 原文在 `/docs/memory.md`。

这份说明给**调用方**，不是给部署这份服务的人。调用方是某个 Auto-Coder Chat 账户，或替这个账户做事的程序。记忆按 Chat 用户隔离：同一个账户的多把 API Key 看见同一批项目；别的账户即使猜到项目名，也只会得到「没有这个项目」。

服务不接收用户名。项目名由调用方起，命名空间由服务端从 Chat 用户派生。不要请求 `/v1/...`，那不是这层 API。

下文用 `$GATEWAY` 表示服务的公网 origin，例如 `https://example.com`，不要带路径，也不要自己拼端口以外的东西。用 `$API_KEY` 表示 Chat 账户设置里创建的 `ak_…`。Key 只放在 `Authorization` 头里，不要写进 URL、日志或仓库。

## 先选定调用方式

机器和脚本用 Bearer。不需要 Cookie，不需要 `Origin`，不需要 `X-CSRF-Token`。

```bash
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json"
```

浏览器页面用 Cookie。`POST /api/auth/login` 必须带与部署 origin 一致的 `Origin`（生产环境是 `https` 域名，不是调用方自己的 Host）。登录响应里的 `csrf_token` 要放进之后每一次 Cookie 写请求的 `X-CSRF-Token`。`GET /api/auth/me` 也会返回当前会话的 `csrf_token`。Bearer 请求不要走这条。

部署启用了浏览器 SSO 时，人打开 `GET /api/auth/sso/start` 并跟随跳转即可。失败会回到 `/?sso_error=`，值只可能是 `denied`、`state`、`failed`、`unavailable`、`rate`。程序不要依赖 SSO。

## 记住一件事

一条记忆是某个**项目**里的一整包 Computer Index，不是一次聊天记录。查询怎么打分取决于本服务启用的查询运行时：启用托管运行时时，查询用你自己的 JEV（必要时核实**包内**叶片证据，不会读包外文件）或你已保存的原生模型（只依据索引元数据与结构化 id 打分，不读正文）；只有旧版/未启用运行时的部署才退回逐层词法遍历。无论哪种模式，都要把能被想起来的话说进节点的 `name`、`description`、`tags`，以及 `metadata` 里的 `summary`、`intent_phrases`、`aliases`、`actions`。正文放在节点 `path` 指向的包内文件，等查中之后再读。

一个项目一次只存一包。`PUT` 是整包替换，包里没列出的旧文件会被删掉。不同主题用不同项目名，避免一次替换清掉无关记忆。`default` 会在第一次列出项目时自动出现，可以留给试验，不要当成唯一的库。

项目名按字符计，最多 128 个，区分大小写，不能有首尾空白、`/`、`\`、控制字符，也不能是 `.` 或 `..`。中文按一个字符计。URL 里这一段必须做 path 转义。

写入顺序：

1. `GET /api/projects`。没有目标项目就 `POST /api/projects`，正文只有 `{"project":"<名字>"}`。重复创建是幂等的，`created` 为 false 时不会清空已有索引。
2. 准备 `{"files":{...}}`。必须有 `root.yaml`。路径是包内相对路径，用 `/`，不能是绝对路径、`..`、`~`、`$`、空段，也不能有以 `.` 开头的段。每个节点的 `path` 同样要落在这包里面。不要写 `metadata.project_path`。
3. `PUT /api/projects/<名字>/index`。成功时旧包已被替换。返回的 `files` 是路径列表，不是正文。校验失败时旧包还在，可以再导出确认。

最小的一包长这样。`type: index` 指向另一页 YAML，`type: file` 指向正文。

```json
{
  "files": {
    "root.yaml": "apiVersion: autocoder.dream/v1\nkind: ComputerIndex\nmetadata:\n  name: root\n  description: 个人记忆\nnodes:\n  - name: Knowledge\n    description: 可复用的做法\n    path: knowledge.yaml\n    type: index\n    metadata:\n      kind: knowledge\n",
    "knowledge.yaml": "apiVersion: autocoder.dream/v1\nkind: ComputerIndex\nmetadata:\n  name: Knowledge\n  description: 做法\nnodes:\n  - name: 刷新 VPN\n    description: Windows 上重新连接 VPN 配置并确认路由\n    path: notes/vpn.md\n    type: file\n    tags: [vpn, windows]\n    metadata:\n      kind: howto\n      knowledge_id: howto.vpn.refresh\n      summary: 重连 VPN 配置后检查默认路由\n      intent_phrases: [vpn 断了怎么办, 刷新 vpn]\n",
    "notes/vpn.md": "# 刷新 VPN\n\n重连配置，然后确认默认路由已经回来。\n"
  }
}
```

包的上限：最多 256 个文件，单文件 UTF-8 不超过 1 MiB，正文合计不超过 8 MiB，单条路径不超过 240 字符。网关对这次 PUT 的外层限制是 12 MiB；超过上游上限会拒绝，且不会改掉旧索引。

## 想起一件事

`POST /api/projects/<名字>/query`。正文只允许这些字段：

| 字段 | 含义 |
|---|---|
| `query` | 字符串，最长 4000 字符。可以空，但空的时候至少要有一个过滤条件 |
| `limit` | 整数 1–100，默认 10 |
| `kind` | 字符串或字符串数组，匹配节点的语义种类，如 `howto` |
| `since` | `YYYY-MM-DD` 或 ISO 日期时间，只保留该日及之后的结果 |
| `status` | 字符串或字符串数组，匹配条目的 status |
| `project` | 在条目的名称、描述、chain 和相关元数据里做子串过滤。它不是 URL 里的项目名 |
| `path` | 在路径字段上做子串过滤 |
| `knowledge_rag` | 布尔值。本服务未开启 Knowledge RAG，传 `true` 也不会检索，结果里的 `knowledge_rag_result` 仍是 `{}` |

`mode`、`model`、`backend`、`runtime`、`index_dir`、`root`、`effort` 以及其他未列出的字段都会 400。查询正文**不能**选择模式、后端或运行时：启用托管运行时时，服务按「本人 JEV > 本人已保存模型」选择，两项都没配置才以 409 `configuration required` 拒绝；已配置但凭据无效/损坏时返回明确错误，绝不静默改用其他身份，也绝不悄悄改跑另一个引擎。损坏的本人 JEV 不会改用模型。只有旧版/未启用运行时的部署才走逐层词法遍历。

读结果时只用这些相对信息：`results[]` 的 `name`、`description`、`path`、`target_type`、`tags`、`metadata`、`chain`、`score`、`semantic_kind`。`path` 是包内路径。`target_type` 为 `file` 时，正文在导出包的 `files[path]`；为 `index` 时，`path` 是下一页 YAML，继续在包里打开它。

启用托管运行时时，响应里多一个 `query_runtime`：`mode` 为 `jev` 或 `model`，`source` 为 `personal_jev` 或 `model`，只说明本次实际用了哪条路径，不含路径、Key 或后端细节，调用方不用自己指定。

当 `root_traversal.strategy` 为 `ranked_traversal` 时，要按 `root_traversal` 自己的 `terminals`、`status`、`budget`、`stats` 读：非 ok 的 `status` 表示这次遍历没有成功完成，但响应里仍可能带着已验证的部分 terminal，所以既要报出准确的 `status`，也要报出这些部分结果，不能当成完整成功，也不要在同一个请求里静默换引擎或重走一遍。带 `model_ranking` 时，要说明它的 `status`、候选覆盖与截断情况；只有评到分的条目才是模型结果，未评分的词法命中不算模型产出。

`index_dir`、`root_path`、`resolved_path`、`document_path`、`read_next`、`yaml_files` 经常是**服务器上的绝对路径**。不要在调用方机器上打开，也不要把它们发回去。需要正文时 `GET /api/projects/<名字>/index`，用 `files` 这张相对路径表。

只有当 `root_traversal.strategy == "progressive_root_traversal"` 时，`results` 为空或 `root_traversal.required` 为 true 才按同样规则走 `root_traversal.candidates[]` 的 `path`。`strategy` 是 `root_traversal` 内部的字段（写法是 `root_traversal.strategy`），不是响应里的顶层字段。`root_traversal.strategy == "ranked_traversal"` 时不要这样走：那种响应按它自己的 `terminals`、`status`、`budget`、`stats` 读，`required` 只表示该引擎没有给出 terminal，不要据此重走一遍非 ok 的 ranked 引擎。响应里的 `help_command` 是引擎自己的句子，调用方不要在网关机器上执行它。

已经知道包内路径时，用 locate，不要再搜：

```json
{"fs_path": "notes/vpn.md", "limit": 10}
```

`fs_path` 与 `xpath` 必须二选一。`fs_path` 也是包内相对路径，规则和写入路径相同。XPath 作用在索引节点上，例如 `//node[contains(@description, 'VPN')]`。

## 维护

- 备份：`GET .../index`，把返回的 `files` 存下来。点号开头的文件和目录不会出现在导出里。
- 健康：`GET .../status`。看 `healthy`、`status`、`validation_ok`、`issues`。`recommended_action` 为 `build` 或 `optimize` 只是建议；本 API 没有构建或优化动作，要改内容就改包再 PUT。
- 结构校验：`POST .../validate`，正文 `{}` 或省略。`{"require_knowledge_structure": true}` 会额外要求知识根结构，失败是 400，且不改索引。校验是只读的。
- 换包前先导出。不合法的 PUT 会留下旧包。

## 接口一览

除健康检查、官网文档、登录和 SSO 跳转外，下列接口都要鉴权。写请求在 Bearer 下只需要 Key；在 Cookie 下还要 `Origin` 和 `X-CSRF-Token`。登录本身只要 `Origin`，当时还没有 CSRF。

| 方法 | 路径 | 作用 |
|---|---|---|
| GET | `/healthz` | 不需登录。`{"ok":true,"checks":{"db":"ok","upstream":"ok"}}`，失败时 HTTP 503 |
| GET | `/` | 给人用的页面。页头有指向本文的链接 |
| GET | `/docs` | 不需登录，跳到 `/docs/memory` |
| GET | `/docs/memory` | 不需登录。本文的网页 |
| GET | `/docs/memory.md` | 不需登录。本文的 Markdown |
| POST | `/api/auth/login` | `{"api_key":"ak_…"}` → Cookie，以及 `csrf_token`、`account.email` |
| GET | `/api/auth/sso/start` | 仅在部署启用 SSO 时存在，302 到 Chat |
| GET | `/api/auth/sso/callback` | SSO 回跳。成功 303 到 `/`，失败 303 到 `/?sso_error=<枚举>` |
| POST | `/api/auth/logout` | 只撤销本服务的会话 |
| GET | `/api/auth/me` | 已登录：`authenticated:true` 和 email；Cookie 会话另有 `csrf_token`。未登录也是 200，`authenticated:false`，并带 `sso_enabled`。身份服务不可达是 503，`identity_status:"unavailable"`，此时不算已登录 |
| GET | `/api/projects` | `{"ok":true,"username":"u_<64位hex>","projects":["default",...]}`。`username` 是不透明命名空间，不要拿去拼 URL |
| POST | `/api/projects` | 创建。响应含 `project`、`created`、`user_created`、`project_created` |
| GET | `/api/projects/{p}/status` | 健康摘要 |
| POST | `/api/projects/{p}/validate` | 结构校验 |
| POST | `/api/projects/{p}/query` | 回忆 |
| POST | `/api/projects/{p}/locate` | 按包内路径或 XPath 取节点 |
| GET | `/api/projects/{p}/index` | 导出整包 |
| PUT | `/api/projects/{p}/index` | 整包替换。成功响应的 `files` 是路径数组 |
| GET | `/api/me/model-config` | `configured` 为 false，或 `provider`、`alias`、`model_label`、`updated_at`。永不返回 Key |
| PUT | `/api/me/model-config` | `{"provider":"deepseek"\|"openrouter","api_key":"..."}`。首次或更换 provider 必须带 Key；同一 provider 留空则沿用已存 Key。Key 最长 512，不能含空白 |
| DELETE | `/api/me/model-config` | 清除模型设置，幂等 |
| GET | `/api/me/jev-config` | 本人 JEV 安全状态：`configured`、`effective_source`（`personal_jev`/`model`/`unconfigured`）、`provider`/`provider_label`（固定 TypeSafe JEV）、`updated_at`。永不返回 Key、密文或路径 |
| PUT | `/api/me/jev-config` | `{"api_key":"..."}`（只接受这一个字段，≤8KiB）。首次保存必须带 Key；已保存后留空表示沿用原 Key，不会清空 |
| DELETE | `/api/me/jev-config` | 只清除本人 JEV，幂等；清除后生效来源是本人模型，没有模型则为 `unconfigured` |

模型与 JEV 设置**不是记忆内容**：导入、导出、定位、校验、状态查询都是确定性的，不做任何推断，也不调用模型或 JEV。它们只在**查询**时生效——启用托管运行时时，模型是「本人 JEV > 本人已保存模型」里的第二级。保存 Key 不会把文件编进索引，也不会改变已写入的包。不要为了让一条记忆生效去调用它们。

## 错误怎么读

网关自己拒绝时，`error` 是字符串：

```json
{"ok": false, "error": "not authenticated"}
```

索引服务拒绝时，正文原样返回，`error` 是对象：

```json
{"ok": false, "error": {"code": "index_invalid", "message": "..."}}
```

| HTTP | 含义 |
|---|---|
| 401 | 没有 Key、Key 无效或已撤销、会话失效。`error` 可能是 `not authenticated`、`identity credential is invalid or revoked`、`session expired or revoked`、`identity changed` |
| 403 | Cookie 写请求的 Origin 不可信，或 CSRF 不对 |
| 404 | 项目不存在。别人的项目也是 404，这不表示你探测到了对方 |
| 400 | 名字、正文或包不合法。常见 `code`：`invalid_request`、`invalid_bundle`、`invalid_name`、`index_invalid`、`confinement` |
| 409 | `name_conflict`，存储里的名字和命名空间键不一致；启用托管运行时时，也可能是查询缺少可用配置（`configuration required`，本人 JEV 与本人模型都没有）——先保存 JEV Key 或模型设置再查 |
| 413 | 正文超过对应接口的上限 |
| 429 | 登录过频（同一 IP 每分钟 10 次），或同时进行的索引操作超过 32 个 |
| 502 | 索引服务不可用，或成功响应超过 33 MiB。后者不会返回被截断的 JSON |
| 503 | Chat 暂时不可用。索引数据还在，只是这次没法确认你是谁 |

上游错误信息里的服务器数据目录会被改写成 `<index-home>`。成功响应不改写，所以才会带上服务器绝对路径；忽略它们。

## 一次完整调用

```bash
curl -s -H "Authorization: Bearer $API_KEY" "$GATEWAY/api/projects"

curl -s -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"project":"memory"}' "$GATEWAY/api/projects"

curl -s -X PUT -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d @bundle.json "$GATEWAY/api/projects/memory/index"

curl -s -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"query":"刷新 vpn","kind":"howto","limit":10}' \
  "$GATEWAY/api/projects/memory/query"
```

查到节点后，用同一次导出的 `files["notes/vpn.md"]` 取正文，不要请求服务器路径。
