用 Sparse Agent Memory 完成记忆
先了解功能和用法:云端记忆使用说明与示例。本文提供程序接入说明。
同一份说明发布在 Sparse Agent Memory 官网的 /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、日志或仓库。
先选定调用方式
索引请求的执行名额由服务端管理员配置,默认全站 20 个并发任务,满额后按到达顺序排队(等待队列最多 256 个请求)。普通 HTTP 调用等待后返回原有 JSON;调用方设置的超时须覆盖排队和执行,不要在超时后盲目重发写请求,先回读确认。
需要显示等待进度时,在索引请求中加 Accept: application/x-ndjson。服务端每行返回一个 JSON 事件:{"state":"queued","message":"你的请求正在排队","queue":{"position":1,"max_concurrency":20,"running":20,"queued":1}};获得名额后为 running;最终为 {"state":"completed","http_status":200,"result":{...}}。以最终 http_status 与 result 判断成功;流本身的 HTTP 200 不代表任务成功。position 从 1 起,前面等待人数为 position−1。断开连接会移除尚未执行的请求;已经开始的任务仍保留名额到执行结束。
管理员入口只对 Chat 已验证邮箱 allwefantasy@gmail.com 开放:GET /api/admin/config 查看并发及队列数量,PUT /api/admin/config 提交 {"max_concurrency":N}(1–1000)。保存立即生效且重启保留;Cookie 写请求沿用 Origin/CSRF 要求,普通账号返回 403。
机器和脚本用 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 指向的包内文件,等查中之后再读。
用户说「帮我记忆……」而没有指定 Project 时,直接使用 Default。服务会在首次使用时幂等创建它,重复调用保留已有内容;不需要先让用户手工建项目。HTTP 接口可省略项目路径段,例如 PUT /api/projects/index、POST /api/projects/query,都使用本人 Default。显式 /api/projects/Default/... 也会自动创建。调用现有 auto-coder.index.cloud CLI 时,代理给命令传 --project Default。用户明确指定其他项目时使用该名字;旧的小写 default 与 Default 是不同项目,旧索引保留。
一个项目一次只存一包。PUT 是整包替换,包里没列出的旧文件会被删掉。追加记忆前必须先导出已有包,再合并内容和索引节点后 PUT,不能只上传新记忆而清掉旧内容。需要独立整包时可使用不同项目名。
项目名按字符计,最多 128 个,区分大小写,不能有首尾空白、/、\、控制字符,也不能是 . 或 ..。中文按一个字符计。URL 里这一段必须做 path 转义。
写入顺序:
- 未指定 Project 时使用
Default:可直接调用省略项目的接口,也可POST /api/projects提交{}或空正文。指定名字时先GET /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({} 或空正文)使用 Default。响应含 project、created、user_created、project_created |
| GET / POST / PUT | /api/projects/<操作> |
省略项目名时使用本人 Default;支持 status、validate、query、locate、index,方法与下面的带项目接口一致 |
| 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"] 取正文,不要请求服务器路径。
账号准入与邀请
普通用户首次登录需要一次性邀请码;管理员和已绑定账号直接登录。发布前已有账号保留索引和个人配置,初始继续邀请额度为 0。管理员为 William 的 allwefantasy@gmail.com,权限固定到 Chat 稳定账号身份,邮箱变更不会转移权限。
新成员可用页面的 Chat SSO 登录,也可同源 POST /api/auth/login 提交 api_key 与 invitation_code。SSO 首次登录通过同源 POST /api/auth/sso/start 提交 invitation_code,再跳转返回的 url;码只通过 POST 和邀请链接 fragment 传递,不放进查询参数。未提供码返回 403 / invite_required,无效、已领取或过期返回 403 / invite_invalid,禁用账号为 403 / account_disabled。已有成员的普通 API Key 可继续用于 Bearer 调用。
Sparse Agent 使用经 Chat userinfo 验证的独立 mem_at_ 记忆授权时免邀请码,要求 aud=auto-coder-index、client_id=sparse-agent-gateway-memory、有效期和操作 scope 均有效;只访问自己索引及范围允许的模型配置。该豁免不产生网页成员、邀请额度或管理员权限,调用方声明的 User-Agent/X-Sparse-Agent 不能获得豁免。明确禁用的账号和失效凭据仍被拒绝。Chat 与 Sparse 的配套授权已于 2026-10-06 上线;服务端凭据仅保存在两个服务端,不进入浏览器或 Agent。公开登录客户端不能签发记忆授权,旧授权需在工作台重新连接,原索引保留。
管理员页面的「邀请管理」支持额度 0–100、层级 0–10和 1–720 小时有效期(默认 168)。普通用户继续邀请默认关闭;额度 3、层级 1 表示 A 累计邀请 3 人,B 不能继续邀请。未领取码占名额,过期或撤销释放,已领取额度不退回;降低授权或关闭继续邀请会撤销不再符合授权的未领取码。既有下级账号及私人索引保留。
| 方法 | 路径 | 用途 |
|---|---|---|
| GET / POST | /api/invitations |
已注册用户查看自己的记录,或在额度/层级内生成码;创建体为 {"invite_quota":0,"invite_depth":0,"expires_hours":168},原码只返回一次 |
| DELETE | /api/invitations/{id} |
创建者或管理员撤销未领取码 |
| GET | /api/admin/access |
管理员读取成员授权和邀请开关 |
| PUT | /api/admin/invitation-policy |
管理员提交 {"members_may_invite":true} |
| PUT | /api/admin/members/{id} |
管理员提交完整 {"invite_quota":3,"invite_depth":1,"disabled":false} |
Cookie 写请求仍需同源 Origin 与 CSRF;记忆授权不能调用这些管理接口。
Conditional model updates for Sparse cloud memory
PUT /api/me/model-config accepts an optional expected_updated_at string. Read the current updated_at first; use an empty string only when no model exists. A changed or deleted configuration returns 409 and is preserved. Verified Sparse offline grants require memory:model for model access and remain restricted to their existing memory operation scope.