21-knowledge-sources.md 25 KB

21 资料库:托管目录、本机目录、远程存储、网络链接与资料集合

状态:实施中(2026-11-06):第一期(资料库类型重构、网络链接、资料集合)已完成,见 §15。取代 07 §4.1 中"登记服务器路径"的资料库。

1. 背景

现有资料库只能登记平台服务进程看得到的任意路径。平台以 agentsvc 运行,systemd 配置了 ProtectHome=yes、ProtectSystem=strict(06 §5、ADR-0012)。这带来四个问题:

  • 看不到需要的资料:home 下的资料无法登记;外接设备上的资料只有权限恰好允许时才能读。
  • 范围过宽:除禁止项外,任何路径都能登记,包括 /etc 等系统文件(已验证 agentsvc 能读 /etc/hostname)。隐藏文件(.git、.env)也不会被过滤。
  • 来源单一:网络上的资料(内网文档站、对象存储、NAS、网盘)没有登记方式。
  • 选择麻烦:Profile 只能一个个勾选资料库。

2. 目标与非目标

目标:

  1. 资料库分为四种类型:托管目录、本机目录、远程存储、网络链接;去掉"任意服务器路径"。
  2. 本机目录和远程存储由一个独立的挂载进程以部署用户的身份提供:
    • 只读;
    • 过滤隐藏文件;
    • 访问范围由挂载进程强制检查。 平台自身的 systemd 限制保持不变。
  3. 移动设备、远程存储不可用时,资料库保留,状态显示为失效;恢复后自动可用。
  4. 资料集合:Profile 可以勾选集合,也可以勾选单个资料库。

非目标:

  • 本机目录和远程存储不支持写入;
  • 不做索引检索(kb_search);
  • 网络链接不支持需要登录的地址;
  • 不做集合嵌套;
  • 设备断开、恢复不写审计日志;
  • 不支持需要 OAuth 的网盘和国内网盘(§12)。

3. 资料库类型

类型 kind 登记内容 Agent 看到的路径 Agent 读写 由谁提供
managed 托管目录 名称、说明 /srv/agent-server/knowledge/files/<id> 只读或可写 平台自己的目录,后台"文件管理"上传、编辑
local 本机目录 源目录(/home、/mnt、/media 下) /srv/agent-server/knowledge/mnt/<id> 只读 挂载进程
remote 远程存储 存储类型、连接参数、凭据、路径 /srv/agent-server/knowledge/mnt/<id> 只读 挂载进程(rclone)
link 网络链接 链接(http/https) 无,提示词中给出链接 只读 Agent 用 web_fetch 读取

所有资料库都有:名称、说明(告诉 Agent 这里有什么)、启用状态。

迁移:生产环境目前没有登记资料库。开发库中已有的记录迁移为 local,源目录取原路径;挂载进程检查不通过的,显示为"位置不允许"。Profile 中的引用保持不变。

4. 挂载进程(agent-mounter)

4.1 程序与运行方式

  • 一个 Go 程序,同时承担:
    • 挂载管理(Unix socket 接口);
    • 文件系统(go-fuse):每个本机目录和远程存储资料库各对应一个挂载,都在同一个进程中提供;
    • 远程存储时,启动并管理 rclone 子进程(§6)。
  • 源码在 apps/mounter/,依赖 go-fuse 和 golang.org/x/sys。deploy.sh 编译为静态程序,放到 /srv/agent-server/app/mounter/(root 所有、只读)。
  • 单独的 systemd 服务 agent-mounter.service,以部署用户运行(install.sh 执行时的 $SUDO_USER,生产上是 jerry-remote,已确认可以接受它等同于 root)。
  • 通过 setuid 的 fusermount3 挂载,不需要 root。需要在 /etc/fuse.conf 中开启 user_allow_other,agentsvc 才能访问。
  • systemd 限制:
    • 挂载必须发生在主机的挂载命名空间,所以不能用 ProtectSystem、ProtectHome、PrivateTmp、PrivateMounts;
    • fusermount3 是 setuid 程序,所以不能开 NoNewPrivileges;
    • 只能使用与挂载无关的加固,例如 ProtectKernelModules、LockPersonality。
  • 进程退出时,它提供的所有挂载随之失效。启动时先清理 mnt/ 下失效的挂载(fusermount3 -uz),再按登记重新挂载。只有挂载程序本身有改动时,deploy.sh 才重启它,挂载中断几秒后自动恢复。

4.2 文件系统的行为

挂载参数:ro,nosuid,nodev,noexec,allow_other。

行为 说明
只读 挂载层面只读,任何写入都返回"只读文件系统"
隐藏文件 以 . 开头的文件和目录不出现在目录列表中,按路径访问也返回"不存在"。ls -a、find、grep -r 都看不到(已实测)
权限 文件显示为所有人只读、目录为所有人可进入。实际以部署用户的身份读取,不修改原文件的权限
符号链接 原样呈现,由读取方在自己的文件系统视图中解析。指向挂载范围外的链接在平台和沙箱中会失效
防止路径替换 打开文件时,从源目录开始逐级打开(openat2 + RESOLVE_BENEATH \| RESOLVE_NO_SYMLINKS)。即使有人在访问过程中把目录替换成符号链接,也不会读到源目录以外的内容
不可用时 资料库状态不是"正常"时,挂载点根目录照常存在,读取目录或文件返回"输入/输出错误"

4.3 访问范围(本机目录,由挂载进程强制检查)

登记、列目录、每次解析源目录时都会检查:

  1. 解析符号链接后的真实路径必须位于 /home、/mnt、/media 之下。允许的根目录写死在代码中,不接受平台传来的配置。
  2. 不能是这三个目录本身,也不能是某个用户的 home 目录本身(/home/<user>),只能是更深一层的子目录。
  3. 路径中任何一段都不能以 . 开头。
  4. 必须是目录,并且部署用户能读取。
  5. 挂载点由挂载进程自己拼接(mnt/<id>),<id> 必须匹配资料库 ID 的格式。

后台逐级选择目录时,list 只返回子目录名,隐藏以 . 开头的目录,并标出不能选择的目录及原因。

4.4 本机目录的设备识别与自动恢复

现场情况:

  • 外接盘 /media/jerry-remote/Data 写在 fstab 中,开机自动挂载;
  • 它的挂载点下面本身就有文件:开机日志提示 "Directory … to mount over is not empty"。所以硬盘不在时,这个路径仍然存在,里面是别的文件。

因此,本机目录不能只按路径判断,而是按"设备加设备内路径"识别:

  • 登记时挂载进程返回源目录所在的文件系统:
    • 在系统盘(根分区所在的设备)上,例如 home 下的目录:只记路径;
    • 在其他设备上:记录文件系统 UUID(来自 /proc/self/mountinfo 和 /dev/disk/by-uuid)和设备内的相对路径,同时保留原路径用于显示。
  • 每次访问时,挂载进程先找到"这个 UUID 当前挂在哪里",再拼出实际路径。
    • 实际路径同样要经过 §4.3 的检查。设备被挂到允许范围以外时,状态为"位置不允许"。
    • 挂载位置的查找结果会缓存。挂载表有变化时(监听 /proc/self/mountinfo 的 POLLPRI 事件),立即重新计算状态,不需要定时检查。
  • 设备重新挂上后,资料立即恢复可用,即使挂载路径变了也可以(卷标变化、设备名从 sda 变成 sdb)。换成另一块设备时,即使路径相同,也不会被误认为是原来那块。

状态:

状态 判断
ok 正常 设备在、路径存在、检查通过
device_missing 设备未连接 这个 UUID 当前没有挂载在任何地方
path_missing 路径不存在 设备在,但其中的目录被删除或改名
not_allowed 位置不允许 实际路径不在 §4.3 允许的范围内
unreachable 无法连接 / auth_failed 认证失败 远程存储(§6)

设备由谁挂到系统上:挂载进程只负责识别设备回来了,不负责把设备挂到系统上。

方式 结论
fstab 中加 x-systemd.automount(以及 x-systemd.device-timeout=1s) 推荐用于用作资料的硬盘。访问路径时系统自动挂载,设备不在时很快报错。由 root 为每块盘配置一次
桌面自动挂载(udisks2) 不可靠:需要有人登录桌面,而且只有那个用户能读。目前登录桌面的是 jerry,jerry-remote 读不到 /media/jerry
挂载进程自己挂载设备 不做:需要 root 或桌面会话授权

后台资料库页显示这些说明。设备状态为"未连接"时,提示检查设备是否已挂载。

4.5 与平台的通信

  • Unix socket:/srv/agent-server/mounter/mounter.sock。
    • 目录所有者为部署用户、组为 agentsvc,权限 0750;socket 0660;
    • 平台通过组权限连接。
  • 鉴权:Agent 的进程也是 agentsvc 用户,所以:
    • 挂载进程启动时生成随机密钥,写入 /srv/agent-server/mounter/token(0640);
    • 平台每次请求都带上这个密钥;
    • /srv/agent-server/mounter/ 整个目录加入内置全局禁止项,两个引擎的沙箱都看不到。目录里还有远程存储的凭据和缓存(§6)。
  • 协议:一行一个 JSON 请求和响应。
请求 说明
list { path } 列出可选择的子目录
check { source } 按 §4.3 检查本机目录,返回识别结果(是否在系统盘、UUID、设备内路径)
test { remote } 测试远程存储的连接,列出根目录
sync { mounts } 发送完整的登记列表,挂载进程挂上缺少的、卸载多余的、更新有变化的,并返回各挂载的状态
status 各挂载的状态及最近一次变化的时间
  • 平台是唯一的状态来源,登记保存在平台数据库中。
    • 挂载进程把最近一次 sync 的列表保存到 /srv/agent-server/mounter/state.json(0600),只用于开机后不等平台就能立即恢复挂载。平台每次 sync 都会覆盖它,而且挂载进程对每一项都会重新检查。
    • 远程存储的凭据随 sync 下发,也保存在这个文件中。

调用时机:

时机 动作
平台启动 sync(挂载进程没启动时,按退避间隔重试)
资料库新增、修改、删除、启用、停用 sync
挂载进程启动 读取 state.json 恢复挂载
后台打开资料库页 status
每次会话开始运行 status(超时 1 秒),不可用的资料库在提示词中标注"暂不可用"

不需要定时同步。

5. 托管目录

  • 平台在 /srv/agent-server/knowledge/files/<id> 下创建目录,所有者 agentsvc。用后台"文件管理"上传、新建、编辑、删除文件。
  • Agent 读写:
    • 默认只读;
    • 可以设为"允许 Agent 写入",按现有读写资料库的方式开放沙箱写权限。
    • 页面提示:可写的托管目录由所有使用它的会话共享,写入会影响其他会话。
  • 不经过挂载进程,所以隐藏文件不会被过滤。上传 zip 解压时跳过以 . 开头的项。
  • 删除资料库时,需要二次确认,然后删除目录及其中的文件。
  • 以后如果要像技能那样推送、审核、做版本,作为托管目录的一种维护方式(不在本次范围)。

6. 远程存储

6.1 方案

远程存储 ──rclone mount(只读)──▶ /srv/agent-server/mounter/remote/<id>(内部,Agent 看不到)
                                      │ 挂载进程的文件系统(隐藏文件过滤、只读、状态)
                                      ▼
                         /srv/agent-server/knowledge/mnt/<id>(Agent 看到的位置)
  • rclone 版本:随部署放一个固定版本的官方程序(下载时校验 sha256),放在 /srv/agent-server/app/mounter/rclone。Ubuntu 自带的 1.53 太旧。
  • 挂载进程为每个远程存储资料库启动一个 rclone 子进程:

    rclone mount kb: /srv/agent-server/mounter/remote/<id> --read-only
    --vfs-cache-mode full --cache-dir /srv/agent-server/mounter/cache/<id>
    --vfs-cache-max-size 1G --vfs-cache-max-age 1h --dir-cache-time 1m
    
    • --vfs-cache-mode full 让 pandas 等需要随机读取的程序可以正常工作(xlsx、parquet)。
    • 缓存有上限,过期自动清除,符合"用时读取,不长期保存"。
  • 连接参数和凭据:通过子进程的环境变量传给 rclone(RCLONE_CONFIG_KB_*),不放在命令行里,因为命令行在进程列表中所有人可见。

  • 状态:

    • 根据 rclone 子进程是否存活、列挂载根目录(带超时)的结果、rclone 日志中的认证错误,得出 ok、unreachable、auth_failed。
    • rclone 自己会重试连接;子进程退出时,挂载进程按退避间隔重启它。
    • 恢复后状态自动变回正常。
  • 挂载进程需要联网,只访问管理员登记的地址。

6.2 首期支持的存储

存储 填写 覆盖范围
S3 兼容 地址(endpoint)、区域(可选)、存储桶、路径前缀、Access Key、Secret Key MinIO、AWS S3、阿里云 OSS、腾讯云 COS、华为云 OBS、七牛、Ceph、R2 等
WebDAV 地址、用户名、密码、服务类型(通用 / Nextcloud / ownCloud / SharePoint) 坚果云、Nextcloud、群晖 WebDAV 等

SMB、SFTP 视需要在后续增加,表单字段已经确定:SMB 为服务器、共享名、用户名、密码;SFTP 为主机、端口、用户名、密码或私钥。NAS 也可以由系统用 fstab 挂到 /mnt 下,再按"本机目录"登记。

6.3 使用提示与限制

  • 提示词中把远程存储标注为"远程资料",并提示:先按文件名和目录结构查找,再读具体文件,避免对整个目录 grep -r(这相当于下载全部文件,又慢,公有云还会产生流量费)。
  • 文件很多的存储桶,应该用路径前缀只挂需要的部分。
  • 远程的改动在目录缓存过期(1 分钟)后才能看到。
  • 建议使用只读权限的密钥。

6.4 凭据

  • 凭据保存在资料库记录中。后台只写不读:已设置的显示"已设置",留空表示沿用原值。与 MCP 服务的做法相同。
  • 审计日志中不出现凭据的值。
  • 以后如有多个资料库共用同一组凭据的需要,再参照 MCP 凭据(20)增加共享凭据。

7. 网络链接

  • 登记:名称、链接(http/https)、说明(可选)。后台提供"测试访问"按钮。
  • 系统提示词和 kb_list_roots 的清单中列出名称、链接和说明,由 Agent 自己决定:
    • 网页用 web_fetch 转为 Markdown;
    • 文件下载到工作区后再处理。
  • 内网放行:设置 network.blockPrivateFetch 开启时(默认开启),网关 web_fetch 和 Claude 的 WebFetch 都会拦截私网地址。对当前会话可用的链接资料库:
    • 放行与登记链接同源(协议、主机、端口都相同)的地址;
    • 重定向的每一跳都要检查;
    • 只对勾选了这个资料库的 Profile 的会话生效。
  • Codex 在沙箱中用 curl 访问网络的规则不变;提示词中建议使用 web_fetch。

8. 资料集合

  • 集合包括:名称、说明、成员(资料库 ID 列表,可以混合各种类型)。一个资料库可以属于多个集合。不支持集合嵌套。
  • Profile 新增 knowledgeCollectionIds。生效的资料库 = 勾选的单个资料库 ∪ 所勾选集合的成员,去掉停用和已删除的。
  • 集合是引用关系:修改成员后,所有使用这个集合的 Profile 立即生效。集合页显示"被哪些 Profile 使用",添加成员时提示影响范围。
  • Profile 版本快照包含集合 ID。
  • 生效资料库统一由一个函数计算,用于:
    • 运行时的文件访问和提示词(runs.ts 三处);
    • 工作组能力卡片(teams/cards.ts);
    • 后台的访问预览。

9. 平台变更

9.1 数据模型

knowledge_roots
  + kind        'managed' | 'local' | 'remote' | 'link'
  + source      json  local:{ path, uuid?, relPath? };remote:{ type, 连接参数(不含凭据) };其他为空
  + secrets     json  remote:凭据(不出现在 DTO 中)
  + url         text  link
  path              派生:managed → knowledge/files/<id>;local、remote → knowledge/mnt/<id>;link 为空
  access            只有 managed 可以是 'write'

knowledge_collections (id, name, description, root_ids json, created_at, updated_at)

profiles
  + knowledge_collection_ids json 默认 '[]'

9.2 运行时

  • 文件访问(access-policy):
    • managed 按 access 开放读或写;
    • local、remote 只读开放挂载点;
    • link 不对应路径。
    • /srv/agent-server/mounter/ 加入内置全局禁止项。
    • 文件访问策略中按资料库设置的规则({ knowledgeRootId }):写规则只能指向可写的托管目录;读规则不能指向网络链接。
  • 提示词和 kb_list_roots:
    • 列出类型、路径或链接、说明;
    • 远程存储标注"远程资料"及使用提示(§6.3);
    • 不可用的资料库标注"暂不可用"及原因。
  • 网络放行:网关 web_fetch 和 Claude 的 WebFetch 拦截逻辑(providers/claude/policy.ts)接收当前会话的链接资料库列表,按 §7 放行。

9.3 后台

  • 资料库页:
    • 上方显示挂载服务的状态(正常 / 不可用)。
    • 新建时先选类型:
    • 托管目录:名称、说明、读写;
    • 本机目录:逐级选择目录,显示检查结果,以及是否在系统盘、设备 UUID;
    • 远程存储:存储类型、连接参数、凭据,"测试连接";
    • 网络链接:链接、说明,"测试访问"。
    • 列表显示类型、状态(失效原因、最近变化时间)。可以在文件管理中打开浏览。
    • 外接设备的配置说明(§4.4)。
  • 集合:资料库页内新增"集合"分组,可以新建、编辑成员,并显示被哪些 Profile 使用。
  • Profile:资料库选择分为"集合"和"单个资料库"两部分,并显示最终生效的列表。
  • 文件管理:
    • 托管目录可读写;
    • 本机目录和远程存储只读浏览(经由挂载点,同样看不到隐藏文件);
    • 网络链接不出现。

9.4 部署

install.sh(首次安装,需 root):

  • 在 /etc/fuse.conf 中开启 user_allow_other;
  • 创建目录:
    • /srv/agent-server/knowledge/files(agentsvc,0700);
    • /srv/agent-server/knowledge/mnt(部署用户:agentsvc,0750,平台只能进入,不能创建或删除挂载点);
    • /srv/agent-server/mounter(部署用户:agentsvc,0750);
  • 安装并启用 agent-mounter.service(User=$SUDO_USER),平台服务配置 Wants=agent-mounter.service。

deploy.sh:

  • 编译挂载进程;
  • 下载并校验固定版本的 rclone(远程存储一期起);
  • 只有挂载程序或 rclone 有变化时才重启 agent-mounter。

10. 安全分析

风险 对策
平台被攻破,指挥挂载进程挂载敏感目录 允许的根目录写死在挂载进程中;不能挂 home 目录本身和隐藏目录;隐藏文件在文件系统层过滤;只读。最坏情况是部署用户能读的普通资料被只读暴露
Agent(同为 agentsvc)调用挂载进程,或读取凭据 需要密钥;socket、密钥、state.json、rclone 缓存都在 /srv/agent-server/mounter/ 下,该目录在全局禁止项中
挂载进程被攻破 等同于部署用户被攻破(可 sudo)。对策:代码小、只接受本机 socket 上带密钥的请求、只访问登记的远程地址
访问过程中目录被替换为符号链接 逐级用 openat2 打开,不跟随符号链接
移动设备拔出后,挂载点下原有的其他文件被当成资料 按文件系统 UUID 识别设备
远程存储凭据泄露 不出现在命令行、DTO、审计日志中;建议使用只读密钥
链接资料库被用来访问任意内网地址 只放行同源地址,只对勾选了它的会话生效,重定向逐跳检查
集合扩大授权范围 集合页显示使用者,添加成员时提示
可写托管目录被会话改坏 页面提示共享影响;本机原始资料不受影响

11. 实测记录

2026-11-05:bindfs 方案(已放弃,改为自己的文件系统)。

测试 结果
jerry-remote 不用 root 挂载 ✅
挂载传入平台服务的挂载命名空间(/proc/<pid>/mountinfo) ✅ 平台服务中 /srv/agent-server/knowledge 是主机挂载的从属挂载
只读、权限映射 ✅
bubblewrap 沙箱中读取 ✅ 可读、不可写、指向 /home 的符号链接读不到

2026-11-06:go-fuse 原型(约 80 行)。

测试 结果
ls -la、find、grep -r ✅ 看不到 .ssh、.git、.env
按路径直接读取隐藏文件 ✅ "没有那个文件或目录"
bubblewrap 沙箱中 ✅ 同上
只读、传入平台服务 ✅

待测(实施时):

  • 开启 user_allow_other 后,agentsvc 在平台服务和两个引擎的沙箱中读取挂载目录;
  • 外接设备拔出、插回(或用 root 创建 loop 设备模拟);
  • MinIO 加 rclone 两层挂载的读取、pandas 随机读取、断网与恢复。

12. 以后可以支持的介质(不在本次范围)

介质 方式 说明
SMB、SFTP、FTP、HTTP 目录、HDFS、Azure Blob、GCS、Swift rclone 原生支持 增加表单即可
NFS 系统挂载到 /mnt,按本机目录登记 rclone 不支持
Google Drive、OneDrive、Dropbox、Box 等 rclone 原生支持 需要在后台完成 OAuth 授权;国内网络访问不稳定
百度网盘、阿里云盘、夸克、115 等 AList / OpenList 转为 WebDAV 依赖非公开接口,可能失效,还有违反服务条款的风险,不建议作为正式来源
飞书 / 钉钉文档、Confluence、Notion、语雀 MCP 服务 在线文档不适合挂载成文件
Git 仓库 托管目录定期 git pull 以后再考虑

13. 测试

  • 挂载进程(Go 单元测试):
    • 路径检查(根目录、home 本身、隐藏路径段、..、符号链接逃逸);
    • 隐藏文件过滤;
    • openat2 防路径替换;
    • 设备识别:用模拟的 mountinfo 测试 UUID 解析、设备缺失、位置不允许、挂载表变化;
    • 协议鉴权;
    • sync 的增删改;
    • rclone 子进程的状态判断与重启(用模拟的程序)。
  • 平台测试:
    • 四种类型的增删改与校验;
    • 凭据只写不读、不进入审计日志;
    • 集合并集与停用过滤;
    • Profile 快照;
    • 链接放行(同源放行、不同源拦截、重定向逐跳检查、未勾选的会话不放行);
    • 全局禁止项包含 mounter 目录;
    • 挂载进程不可用时的状态;
    • 文件访问规则的限制。
  • 生产实测:见 §11 待测项,以及在 Claude 和 Codex 会话中分别用 grep、pandas 读取本机目录和 MinIO 中的资料。

14. 分期

期 内容 依赖
一 资料库类型重构(托管目录、网络链接,去掉任意路径)、内网同源放行、资料集合 只改平台
二 挂载进程:文件系统、隐藏文件过滤、本机目录、设备识别与自动恢复、install.sh / deploy.sh / systemd 开启 user_allow_other
三 远程存储:rclone、S3 兼容、WebDAV、凭据 第二期;MinIO 测试环境

每期完成后部署、实测,经你确认后再提交。

15. 实施记录

第一期(2026-11-06)

  • 数据:迁移 0024 重建 knowledge_roots(kind、source、secrets、url,去掉 path,Agent 看到的目录由类型和 id 派生);新增 knowledge_collections;profiles.knowledge_collection_ids。已有记录迁移为本机目录(只读),在生产数据库副本上验证过。
  • 服务端:apps/server/src/knowledge/service.ts(四种类型的增删改、集合、effective() 计算生效资料库、测试访问)、knowledge/describe.ts(提示词与 kb_list_roots 共用的清单、链接放行站点)。本机目录与远程存储暂时不能新建(需要挂载服务)。
  • 放行:网关 web_fetch 每一跳按目标是否与会话的链接资料库同源决定是否拦截内网;Claude 的 WebFetch 同样处理(RunContext.fetchAllowOrigins)。
  • 文件访问:只有托管目录可写;网络链接不对应路径;/srv/agent-server/mounter/ 加入内置全局禁止项。去掉"任意路径"的检查(checkKnowledgePath),/paths/check 只保留通用检查。
  • 后台:资料库页按类型新建,显示状态与所属集合,托管目录可在文件管理中打开,网络链接可测试访问;资料集合的增删改与使用者;Profile 中分别勾选集合与单个资料库,并显示实际可用的列表。
  • 测试:test/knowledge.test.ts,以及 access-policy、gateway(同源放行、跳转到其他站点仍拦截)、file-manager 的相应修改。