用 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。
-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 转义。
写入顺序:
GET /api/projects。没有目标项目就POST /api/projects,正文只有{"project":"<名字>"}。重复创建是幂等的,created为 false 时不会清空已有索引。- 准备
{"files":{...}}。必须有root.yaml。路径是包内相对路径,用/,不能是绝对路径、..、~、$、空段,也不能有以.开头的段。每个节点的path同样要落在这包里面。不要写metadata.project_path。 PUT /api/projects/<名字>/index。成功时旧包已被替换。返回的files是路径列表,不是正文。校验失败时旧包还在,可以再导出确认。
最小的一包长这样。type: index 指向另一页 YAML,type: file 指向正文。
{
"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,不要再搜:
{"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 是字符串:
{"ok": false, "error": "not authenticated"}
索引服务拒绝时,正文原样返回,error 是对象:
{"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>。成功响应不改写,所以才会带上服务器绝对路径;忽略它们。
一次完整调用
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"] 取正文,不要请求服务器路径。