理解 LearnGraph
从一条真实调用链开始
LearnGraph 是一个目标驱动、证据驱动的学习图谱智能体。本页描述当前仓库中可验证的架构边界, 以及 Agent、Tools、Skills、MCP、沙箱、记忆和系统提示词如何协同工作。
“已注册接口”不等于“外部服务已连通”,“支持 Provider”不等于“当前部署已配置”。 文中始终把代码能力、运行配置和真实远程验收分开描述。
Reading guide
实现状态与阅读约定
LearnGraph 仍处于早期开发阶段。开发者文档采用三种状态词,防止将未来设计或配置入口误解为已完成能力。
可在路由、服务、仓库或 Provider 适配器中追踪到完整调用链,并有持久化模型支撑。
代码已接入,但依赖部署配置、外部凭据、Docker 或远程服务;缺失时应显式返回 unavailable。
仅存在于总体架构或设计 TODO 中,不应作为当前 API 行为或验收结论。
设计共识→设计 TODO→总体架构→实现 TODO→OpenAPI / 模型 / 代码
Quick start
本地启动
根目录脚本负责编排 React/Vite 前端与 FastAPI 后端。默认数据库和对象存储均为本地实现。
# 安装依赖并启动(首次运行)
npm.cmd run dev:install
# 后续联合启动
npm.cmd run dev
# 静态检查、现有测试与生产构建
npm.cmd run check
http://127.0.0.1:5173http://127.0.0.1:8000http://127.0.0.1:8000/docs/api/v1/health
未配置模型 Provider 或远程调用失败时,服务必须明确报错。
确定性的本地演示 Provider 仅在 LEARNGRAPH_ENABLE_LOCAL_DEMO_PROVIDER=true 时启用,
不能作为真实验收证据。
Architecture
系统架构
当前实现是前后端分离的模块化单体。浏览器只调用 LearnGraph 后端;外部模型、搜索、抓取、研究、Memory、 MCP 和存储都由后端经 Port/Adapter 边界访问。
/api/v1
Bearer Session · Workspace Scope · RBAC / ACL · Error Envelope
后端依赖方向
业务服务不直接依赖厂商 SDK。新增外部能力应先定义或复用 Port,再提供适配器、能力探测和明确失败状态。
backend/app/main.py · backend/app/api/router.py · backend/app/providers/ports/Request gateway
API 网关与请求边界
当前仓库没有独立的网关微服务。FastAPI 的统一 /api/v1 路由层就是应用网关:
它终止认证、解析工作区作用域、校验权限、稳定错误结构,并把请求交给领域服务。
网关职责
正式登录接口签发不透明 Bearer Session;浏览器不持有 Provider Secret。
X-Workspace-ID 只是请求提示,不能替代服务端授权。
普通资源使用 JSON;Chat 使用可恢复 SSE,但消息与事件仍落库。
Provider unavailable、权限拒绝与输入错误必须保持可区分,禁止伪造成功。
路由域
authdashboardgoalsgraphssessions
filesresearchsourcesevidenceexercises
memorymcp-skillsprovidersusageplugins
componentsmigrationsauditworkflowsandbox
Durable facts
持久化与事实源
SQLite 是 MVP 的规范业务事实源。本地文件系统承载默认对象存储和 Markdown Memory 投影, 但文件路径、外部 UUID 或 SSE 事件都不能替代数据库中的稳定业务身份。
| 层 | 保存内容 | 关键边界 |
|---|---|---|
| SQLAlchemy / SQLite | 账号、RBAC、Goal、Graph、Session、Evidence、Provider、授权、审计 | 当前规范事实源 |
| Message timeline | Message、MessageVersion、MessagePart、SSE 事件、Provider Trace | SSE 只负责传输 |
| Object Storage | 上传文件、生成产物、内容寻址 Blob | 默认本地,可经迁移边界切换 |
| Memory projection | Workspace 隔离的 Markdown 文件树或 Mem0 记录 | Canonical ID/Revision 仍在 SQLite |
数据库、对象存储和 Memory Provider 切换需要维护锁、预检、校验、切换点与回滚;不能静默双写或降级。
Capability adapters
Provider 层
Provider 是外部能力网关,不是业务事实源。业务层依赖 Protocol 定义的 Port; 适配器负责协议差异、探测、超时、限额和 Provider Trace。
模型消息抽象
ProviderChatMessage 保留 system/user/assistant/tool 角色、tool calls、reasoning summary
与协议原生 response items。原生 continuation state 作为不透明数据保存,不能被业务层臆造或跨协议消费。
backend/app/providers/ports/model.py · backend/app/providers/remote/Agent runtime
Agent 执行循环
Agent 模式是在持久化 Chat Session 上运行的受控工具循环。模型决定是否发起工具调用, 但工具可见性、授权、执行、结果裁剪、审计和持久化均由 LearnGraph 控制。
一次 Agent Turn
- 01持久化用户输入
用户消息、附件引用、所选节点与 Session 绑定先成为可追踪事实。
- 02构建授权上下文
只加入本次允许访问的节点、文件、选区、Memory 和 Skill 指令;文档片段被标记为不可信参考数据。
- 03计算工具注册表
内置工具按角色开放;声明式 Skill 与 MCP 还必须具有 enabled 状态和持久
alwaysGrant。 - 04调用模型 Provider
适配器把结构化消息与 JSON Schema 工具定义发送给已配置远程模型。
- 05受控执行 Tool Call
服务端解析参数、重新校验作用域与授权、执行领域服务或 Provider,并保存 Invocation / Audit。
- 06继续或结束
工具结果作为
tool消息回填;模型可继续调用工具,最终文本和所有 MessagePart 一并持久化。
lg_graph_propose_change 只创建可审核的 GraphChangeSet 与组件,
不改变已发布图谱;Evidence 由 Agent 写入时保持 pending,不会直接授予 mastery。
Prompt compiler
系统提示词策略
提示词由多个 system message 分层编译,而不是把所有内容拼成无法审计的单块文本。 核心规则稳定,工作区风格和本次授权上下文动态注入。
身份、真实性、安全、隐私、指令优先级与通用回答行为。
基础风格 + 温和、热情、标题、Emoji、详细度五个离散特征。
普通 Chat 禁止工具;Agent 模式允许使用本次注册的函数。
工具前给简短可见说明,工具后可更新进展,结束时给完整答案。
选中节点、附件、文档选区、Memory 与最多 8 个已授权 Skill 包。
协议有效的历史消息、必要的上下文压缩,以及当前用户输入。
风格策略
工作区可选择 default、professional、friendly、candid、
efficient、exploratory、quirky 或 cynical。
五个附加特征均使用 -2…2。风格只控制表达方式,不得改变事实、工具权限或用户当前明确格式。
提示词注入防线
文档与网页摘录明确视为不可信参考,不作为系统指令。
只注入当前用户和工作区有权读取、且与本轮相关的内容。
单包最多 8,000 字符、每轮最多 8 个;脚本不会因提示词自动执行。
接近窗口阈值时保留协议有效的最近后缀,旧事务用持久摘要表达。
backend/app/prompts/fragments.py · backend/app/prompts/compiler.py · backend/app/services/chat.pyTool registry
Tool 注册与调用
Tool 是给模型看的 JSON Schema 函数定义与服务端执行器的配对。注册本身无副作用; 只有模型输出 tool call 后才进入参数校验、权限检查和执行。
| 工具族 | 代表能力 | 写入边界 |
|---|---|---|
| 会话与时间 | 检索 Session 片段、读取历史区段、读取当前时区时间 | 只读 |
| 图谱 | 读取目标/能力图谱、候选节点更新、提出图谱变更 | 正式图谱须用户审核 |
| 学习闭环 | 路线读取/重规划、行动计划、Mastery 读取、Evidence 草稿 | Evidence 只写 pending |
| 记忆 | 检索会话证据、读取记忆证据、提出 Memory Draft | 草稿与正式记忆分离 |
| Provider | 列 Provider/模型、能力查询、配置与 Secret 轮换 | 管理写入要求 workspace.manage |
| Web | search_web、parallel_web_research | 仅在 SearchProvider 可用时注册 |
| 内容产物 | 图片生成、可信组件、Magic Card、沙箱文件 Artifact | 不允许任意代码进入宿主 DOM |
| 扩展 | 声明式 Skill、已授权 MCP 工具 | 沿用各自 Grant 与 Invocation 边界 |
调用结果
执行器返回规范化的成功或失败对象,并可附带来源、Artifact、Extension Invocation ID、Sandbox Session ID 等安全元数据。意外异常不会把 SQL、HTTP、Provider 或沙箱实现细节泄露给模型;权威堆栈保留在服务端日志。
{
"status": "completed",
"data": { "…": "domain result" },
"meta": {
"extension_invocation_id": "…",
"source_count": 3
}
}
Instruction & workflow extensions
Skills
LearnGraph 区分两类 Skill。它们的安装、授权和运行语义不同,不能把文件包中的脚本当作自动 Tool。
指令型文件包
- 至少包含
SKILL.md - 启用且具有
alwaysGrant 后按需注入 system context scripts/*不注册为函数工具- 脚本仅可通过 Docker 无网
sandbox-run显式试运行 - 文件按 ContentBlob 内容寻址保存,禁止绝对路径与
..
声明式工作流
- Manifest 类型为
declarative_review或declarative_workflow - 精确声明受审计的内置领域工具与最小权限
- 启用且持久授权后可注册为 Agent function tool
- 不允许 Shell、Python、JavaScript 或用户可执行代码进入宿主进程
- 图谱写入只产生候选修订;Evidence 保持 pending
生命周期
authorization_required→ready_once / enabled→authorization_required
来源、版本、Manifest、权限或内容哈希变化会废止旧 Grant
Skill Market 只提供系统级预缓存卡片;安装后才复制进工作区授权边界。本机 Skill 探测默认关闭, 只适用于前后端同机环境。翻译是按 content hash、locale 与 model 缓存的查看层,不修改源文件。
backend/docs/mcp-skills.md · backend/app/services/mcp_skills.py · backend/app/services/skill_package.pyModel Context Protocol
MCP
MCP Server 是工作区级外部工具目录。运行时不会信任注册表中的旧描述: 每次调用前重新发现能力并保存不可变 CapabilitySnapshot。
Transport 与网络边界
streamable_http真实执行 JSON-RPC 初始化、工具发现与调用;公网只允许 HTTPS,本机 HTTP 仅限 loopback。
stdio当前没有隔离命令运行器,明确返回 available=false;不会在宿主启动任意命令。
Bearer静态 Token 只保存为加密 Secret 引用,API 响应只返回掩码。
OAuth授权码、动态客户端注册与刷新令牌流程尚未实现,不能宣称可用。
重授权规则
服务身份、工具、资源、Prompt、Schema 或注解变化会改变快照哈希;来源、版本、端点、认证指纹、
Manifest、权限或运行边界变化会改变授权哈希。任一变化都使旧 Grant 失效。
allow_once 在调用前消费,即使远程执行失败也不会恢复。
只有当前快照 enabled 且具有明确持久 always Grant 的 MCP 工具才进入 Agent Tool Registry。
Isolated execution
沙箱
沙箱是 Docker-only、默认断网、按「聊天会话 × 属主用户」隔离的代码与文件执行边界。
宿主机永远不是 fallback:Docker、镜像 digest 或配置缺失时 probe 返回明确 unavailable,不会退化执行。
系统内有两条互相独立的执行路径——sandbox-policy-v1 固定任务(只能跑内置 runner 检查已上传文件)与
sandbox-agent-v1 Agent 工作区(模型写文件、跑 .py/.js 脚本)——共用同一张
SandboxSession 表与容器后端,但按 policy_revision 严格互斥。
容器硬化基线
network_mode=none、根文件系统只读、/tmp 为 noexec/nosuid tmpfs(64 MiB);容器以 sleep infinity 常驻,命令经 exec 注入。
非 root(uid/gid 65532)、cap_drop=ALL、no-new-privileges、默认拒绝的 seccomp 白名单(显式省略 io_uring)。
镜像必须以 @sha256: 固定;未 pin 时 probe 直接不可用。基础镜像同样按 digest 固定,Bootstrap 冒烟通过后才原子落盘。
Python 强制 -I -B 隔离模式;PIP_NO_INDEX=1、代理变量置空;前端工具链预装在镜像 /node_modules,零下载、不占工作区配额。
生命周期与双 TTL
CREATED → COLD → STARTING → RUNNING → WARM_IDLE → (COLD | EXPIRED)
# STARTING 先落库占位再调 Docker,容量统计含 STARTING/RUNNING/WARM_IDLE
# 超时 kill:容器被丢弃回 COLD;工作区数据保留
# 清理调度器对 STARTING/RUNNING 会话不动 bind mount(看门狗保护)
| 维度 | 闲置 TTL | 绝对 TTL | 到期动作 |
|---|---|---|---|
| 容器(runtime) | 180s | 1800s | 删除容器,会话降级 COLD(cool-down,工作区保留) |
| 工作区(workspace) | 1800s(活动可外推) | 86400s(创建即定死,不可外推) | 删容器 + 二次逃逸校验后 rmtree,会话 EXPIRED |
资源护栏
磁盘配额是双机制:内核级 fsize ulimit 限单文件 + 宿主侧聚合统计在写入前后与执行轮询每一拍检查。
宿主层另有保护:单用户 2 个活跃容器、部署级 20 个、宿主内存分配 ≤70%、CPU ≤80%、保留磁盘 ≥20 GiB、
单用户留存工作区 ≤10 个。默认值见 backend/app/core/config.py(LEARNGRAPH_SANDBOX_*)。
一次 Agent 命令的执行管线
- 01argv 策略
解释器白名单(python/node),禁止
-c/-m/-e/--eval/-p/-i内联代码;入口必须是工作区内相对.py/.js文件;cwd 只能是.;参数数与单参字节受限。 - 02破坏性预授权
rm / dd / mkfs / Remove-Item等命令按路径逐一核对会话级 Grant(仅work/子树,TTL 60s–24h);缺失则 403 授权挑战经 SSE 弹窗回传。 - 03幂等与审计
Idempotency-Key去重;持久化SandboxAgentCommand只存 argv 摘要与脱敏副本,stdout/stderr 落库前同样脱敏。 - 04容量与容器
进程锁 + 跨进程文件锁下校验用户/宿主容量,先提交 STARTING 占位再
docker create;失败回滚 COLD。 - 05快照 → 执行 → 恢复
执行前对工作区做 inode 级快照;100ms 粒度轮询超时与聚合配额;执行后凡未授权的删除一律从快照恢复并抛授权挑战——argv 拦不住的
shutil.rmtree类软删除由此兜底。 - 06结果与 SSE
exit code / 超时 / 截断 / 延迟落库;结果转成
sandbox_status/sandbox_artifactMessagePart 流式呈现。
双层存储:物理工作区 + 逻辑工作区
宿主 {root}/{user}/{session} 挂载为容器 /workspace;随沙箱会话过期被清理。
SessionWorkspaceEntry + ContentBlob(sha256 去重、引用计数)随聊天会话存续。写入双写两层、读取优先逻辑层——Docker 不可用时附件读取与文件列举依然可用。
Agent 工具面
六个工具(sandbox_write_file / read_file / list_files / exec / transcribe_audio / publish_file)
仅在 workspace.manage 权限且 SANDBOX_AGENT_ENABLED 时注入;chat_session_id
由服务端注入,模型不可伪造。Agent 模式开流前先过 agent_sandbox_readiness() 就绪契约,
不可用则整轮拒绝并给出中文修复步骤。音频转写是宿主侧桥:容器不联网、不接触凭据,ASR 调用在宿主完成计费与执行后回写工作区。
API 面
| 端点组 | 权限 | 职责 |
|---|---|---|
/sandbox/profiles · /bootstrap* · /agent/readiness | 成员 / workspace.write | 能力探测、一键镜像构建(进度/日志)、Agent 就绪契约 |
/sandbox/agent/sessions · /agent/commands · /agent/files/* | workspace.manage | Agent 会话、命令执行(幂等)、文件读写列举 |
/sandbox/tasks* · /sessions* | 成员 | 固定 runner 任务(file_inspect / extract_inert_text)、会话查询与手动清理 |
/sandbox/workspace/* · /authorizations* | workspace.manage / workspace.write | 逻辑工作区条目与发布;破坏性删除 Grant 的创建与吊销 |
seccomp 相对 Docker 默认档有意放行 clone/unshare/chroot 以支持 Chromium 的
user-namespace 沙箱;删除 Grant 在 TTL 窗口内可重复使用(非一次性);
runtime_kind 的两个取值已解析到同一镜像(python-node-browser 已标记废弃);
network_policy.allowed_hosts、任务队列(SANDBOX_QUEUED_TASKS_PER_USER)与
SANDBOX_TASK_TTL_SECONDS 为占位,当前执行全部同步阻塞。
沙箱验收依赖需要真实 Docker 的 backend/scripts/verify_sandbox_runtime.py 端到端脚本。
本节指 Docker 执行沙箱;前端另有 iframe 生成式组件预览(sandbox-artifact.tsx,严格 CSP、connect-src 'none'),以及 MessagePart.type="sandbox" 这一混合渲染类型。三者互不相同,勿混用。
backend/app/services/sandbox.py · backend/app/services/sandbox_authz.py · backend/app/services/session_workspace.py · backend/app/providers/remote/sandbox.py · backend/sandbox/Durable memory
记忆系统
记忆系统是一套 Goal-Scoped Memory Graph:Canonical 事实存 SQLite,Provider 只做可重建投影。
每条记忆由 LearnGraph 生成稳定 lgm_<uuid>;Provider 路径或 Mem0 UUID 只是 Binding。
基线召回不依赖任何外部模型;抽取、语义检索、会话摘要三条增强管线按工作区独立开启。
记忆类型注册表
九种类型集中定义在 domain/memory_types.py,每种声明默认知识作用域、跨作用域合并策略与衰减策略。
权威业务状态(掌握分、路线版本、存储路径等)禁止写入记忆正文,创建时会被 422 拒绝。
| 类型 | 默认作用域 | 合并策略 | 衰减 |
|---|---|---|---|
semantic_memory 语义事实 | workspace | UNION | SLOW |
learning_preference 学习偏好 | workspace | INHERIT_UNTIL_OVERRIDE | NONE |
teacher_focus 教学侧重 | goal | UNION | GOAL_LIFECYCLE |
misconception 错误概念 | node | LOCAL_ONLY | FAST(验证驱动) |
strategy_effectiveness 策略有效性 | goal | KEYED_MERGE | SLOW |
decision 决定 | goal | APPEND | SLOW |
goal_constraint 目标约束 | goal | OVERRIDE | GOAL_LIFECYCLE |
ai_observation AI 观察 | session | LOCAL_ONLY | FAST(需确认) |
event_summary 事件总结 | workspace | APPEND | SLOW |
召回管线(每轮对话)
- 01策略闸门
工作区总开关与 Session 开关必须同时开启,否则返回空包;Agent 的记忆/跨会话检索工具随同一开关一起消失,关记忆等于真隔离。
- 02候选筛选
只取
active且位于hot / recent / topics区的记录;archive区不参与召回。会话命名空间只见本 Session。 - 03作用域动态继承
子作用域不复制父记忆,查询时按 workspace → goal → node 组装有效视图;当前消息与选中节点作为召回上下文传入。
- 04打分与语义放大
启发式得分乘上可选的 Embedding 余弦相似度放大项;Embedding 未配置、失败或预算耗尽时静默退回纯启发式。
- 05类型感知合并
OVERRIDE / INHERIT_UNTIL_OVERRIDE只保留最近作用域赢家并记录冲突;LOCAL_ONLY不跨界。 - 06Top-8 + Token 预算
注入块受输入预算约 8%(400–3000 tokens,CJK 感知估算)约束,低排名条目被丢弃而非溢出;只有实际注入的记忆才累计访问强化。
strength = importance + 0.25·ln(1+access) + 0.30·confirm + 0.20·success
+ goal_bonus − decay_rate·elapsed_days − conflict_penalty
score = strength × proximity × confidence × zone_factor × boost
# proximity: node 1.0 / goal 0.85 / session 0.8 / workspace 0.55
# zone_factor: hot 1.0 / recent 0.9 / topics 0.8;resolved 误区降至 0.15
# 配置 Embedding 后: score ×= 1 + weight·max(cosine, 0)
草稿流:唯一的自动写入通道
Agent 与后台抽取都不能直写长期记忆,一切变更先落 MemoryDraft(八种操作)。
CREATE 在置信度 ≥ 0.75 且类型不要求确认时可自动提交;UPDATE 一律人工审核;
SUPERSEDE 会创建新记忆并以 supersedes_id 记录血缘,旧记录移入 archive 冷区留作审计。
写入与删除语义
- 01Canonical mutation
创建新 Revision、Provider-neutral Journal 与内容 Hash;更新走
expected_revision乐观锁,冲突返回 409。 - 02Provider projection
写入 Active Provider,并按稳定 ID + Revision 精确回读校验。
- 03Commit binding
只有回读 Hash 一致才提交 Binding 与业务事务。
- 04Recoverable delete
删除后正文以每记忆独立密钥加密保留 30 分钟,密钥落在 SQLite 之外;Revision 正文立即清空。
- 05Cryptographic destruction
窗口到期由保留期调度器销毁内容密钥与恢复密文;删除 Journal 元数据最长保留 7 天。
可选增强管线
配置集中在 WorkspaceSetting memory.enhancement,三条管线互相独立、默认关闭;
全部调用计入 usage_events(feature 分别为
memory_extraction、memory_embedding、context_summarization),常见模型自动落价。
后台调度器在会话安静(默认 180s)后,用独立配置的模型从新增轮次抽取候选记忆;
游标存 memory_extraction_states,按 content hash 去重,严格走草稿流。预算耗尽时跳过且不推进游标。
任意 OpenAI 兼容 /embeddings 端点(如 Qwen text-embedding-v4);
向量缓存于 memory_embeddings 并在召回时懒回填,支持一键重建索引。移除配置即恢复无 Embedding 行为。
长会话较早内容由模型滚动压缩为 ContextSummary kind='model';
聊天压缩优先复用覆盖前缀,仅对未覆盖尾部降级为机械截断。模型留空时复用抽取模型。热路径零额外延迟。
GET /memory/package 返回某 Session 下一轮实际注入的记忆清单、得分、作用域冲突与完整 prompt 文本;
前端记忆页提供「AI 眼中的我」一键预览。
API 面
| 端点组 | 职责 |
|---|---|
/memory · /{id}/revisions · /{id}/journal · /{id}/bindings | CRUD、版本历史与回滚、审计日志、Provider Binding |
/memory/drafts · /drafts/{id}/decision | 草稿提交与 commit / reject 审核 |
/memory/policy · /memory/package | 工作区 / Session 双开关;注入预览 |
/memory/enhancement · /reindex · /extract/{sid} · /summarize/{sid} | 增强管线配置、向量重建、手动抽取 / 摘要触发 |
/memory/export · /maintenance/purge-expired | Markdown ZIP 导出(含 manifest 哈希);保留期清理 |
记录属于另一 Provider 世代时,Mutation 返回 409 memory_provider_migration_required;不会静默双写到新 Provider。
backend/app/services/memory.py · backend/app/services/memory_enhancement.py · backend/app/domain/memory_types.py · backend/tests/test_memory_subsystem.pyStreaming contract
消息与 SSE
SSE 是传输层,不是事实源。Session、Message、MessageVersion、MessagePart 与流事件均持久化, 从而支持刷新读取、断点续传、分支、版本比较和 Provider 协议续接。
前端以 Message Part 渲染文本、来源、推理摘要、工具状态、可信组件和沙箱 Artifact。 未知组件安全降级为数据预览,不注入任意 HTML、脚本或 React 代码。
Trusted components
可信组件
可信组件是助手消息流里的声明式 UI 卡片(选择题、填空、天气卡、指标卡、图片框等)。它用 JSON Schema
声明数据面,而不是把任意 HTML/React 注入宿主 DOM。Agent 可通过 canvas_emit_trusted_component
发布内置组件,也可注册并授权第三方组件 Manifest 再发布。所有写操作经后端 Schema 守卫、哈希与审计,
前端对未注册或未通过 Schema 的组件安全降级为 JSON 预览,绝不执行脚本。
内置目录 · 宿主 DOM
- 8 个内置
component_type:weather_card、metric_card、option_group、single_choice、multiple_choice、fill_blank、short_answer_table、image_frame - 渲染器
trusted-bundle,由前端编译好的 React 组件在宿主 DOM 直接渲染 - 端到端可用,是 Agent 默认的表单 / 展示通道
- 权限由系统白名单固定,工作区不可改写
第三方 Manifest · 隔离浏览器
- 任意
component_id,经工作区导入 Manifest 注册 - 渲染器固定为
sandbox,结果以sandbox_artifact交付 - 条件可用:依赖未配置的隔离浏览器渲染器,缺失时
runtime_status=unavailable安全降级 - 需工作区显式授权后才可发布或生成 Artifact
协议生命周期
register→health_check→authorize(+enable)→artifact / event_validate
版本、Schema、权限或包哈希变化会废止旧授权,需重新授权;同一 version 的 Manifest 不可替换。
- 01register
提交完整 Manifest,服务端做静态 Schema 守卫、签名记录与 example_data 校验,落 Plugin 与 ManifestVersion。
- 02health_check
对当前 Manifest 跑 JSON Schema 校验(render 检查在隔离浏览器未配置时标记 unavailable,不阻断)。
- 03authorize + enable
工作区对当前版本 + 权限指纹授权并启用插件;内置组件由系统固定,工作区不可改。
- 04artifact / event_validate
用授权后的组件把
data校验成 Artifact,或把用户事件按event_schema校验后再处理。发布前由assert_can_enable校验 Manifest、授权与 health 全部当前。
Manifest 字段规范
component_id全局唯一标识,^[A-Za-z0-9][A-Za-z0-9._-]{1,119}$;内置 8 个 ID 与 source=builtin 保留,第三方不可占用。
versionSemVer,max 40 字符;同一 version 落库后不可替换。
display_name展示名,1–160 字符。
renderer第三方导入固定为 sandbox;trusted-bundle 仅系统内置可用。
source来源标签,1–160 字符;builtin 为系统保留值。
package_hash组件包 sha256(64 位十六进制),由发布方计算并声明;服务端标记 declared_unverified。
signature可选签名声明(ed25519 或 ecdsa-p256-sha256);当前无信任锚,仅记录为 unverified。
data_schema组件数据的 JSON Schema,必须顶层 type=object 且 additionalProperties=false。
event_schema用户事件 JSON Schema,同样要求顶层闭合对象。
permissionsnetwork_domains(精确 DNS 主机,禁通配/带 scheme)、file_read、clipboard_write、message_actions(小写标识)。
size_limitsmin_height ≥40、max_height ≤2000,且 min ≤ max。
example_data必须通过自身 data_schema 校验的最小示例。
skill_triggers最多 64 条触发词,可选。
Schema 安全约束
data_schema / event_schema 顶层必须 type=object 且 additionalProperties=false。
字段名不得为 html、raw_html、javascript、script、srcdoc、react_code、dangerouslysetinnerhtml;实例字符串不得含 <script、javascript:、<iframe、srcdoc= 等标记。
Schema 不得用非 #/ 开头的 $ref;不得声明 contentMediaType 为 text/html、application/javascript、text/javascript。
Schema 不得含 pattern 字段(不执行调用方提供的正则)。
单 Schema ≤64KB、单实例 ≤64KB、深度 ≤16、节点 ≤1000。
可选字段省略即可,不要传 null;后端对内置 props 会做 null 剥离。
内置组件最小合法 props
{
"title": "你更希望怎样验收这次学习?",
"description": "单选一项后继续",
"options": [
{ "id": "project", "label": "完成一个小项目" },
{ "id": "explain", "label": "能够清楚讲解" }
],
"allow_custom": true,
"allow_skip": true,
"submit_label": "确认并继续"
}
{
"title": "请补全 ACID 中的 A",
"prompt": "ACID 中的 A 代表 ____",
"placeholder": "Atomicity / 原子性",
"multiline": false,
"submit_label": "提交",
"blank_ids": ["answer"]
}
{
"title": "简答题",
"columns": ["问题", "你的回答"],
"rows": [["为什么需要索引?", ""]]
}
{
"title": "杭州明日天气",
"location": "杭州",
"condition": "多云",
"temperature_c": 27,
"high_c": 29,
"low_c": 21,
"summary": "适合户外轻量复习",
"unit": "C",
"actions": [
{ "id": "create_plan", "label": "生成明日学习计划", "event": "create_plan" }
]
}
{
"title": "今日学习指标",
"description": "来自当前目标进度",
"metrics": [
{ "id": "mastery", "label": "掌握度", "value": "62%", "hint": "近 7 日" },
{ "id": "reviews", "label": "待复习", "value": 3 }
]
}
{
"title": "示意图",
"alt": "B+ 树结构示意",
"status": "placeholder"
}
完整第三方 Manifest 示例
以下是一个自定义 quiz_card 的完整 Manifest,可直接用于 POST /api/v1/plugins/components
或智能体的 component_register_manifest 工具。它满足全部 Schema 安全约束:顶层闭合对象、无禁用字段、
无 pattern / 外部 $ref、example_data 能通过自身 data_schema。
{
"component_id": "acme.quiz_card",
"version": "1.0.0",
"display_name": "Quiz Card",
"renderer": "sandbox",
"author": "Acme",
"source": "acme-marketplace",
"package_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"compatible_learngraph": { "minimum": "0.1.0" },
"uninstall_behavior": "retain_data",
"data_schema": {
"type": "object",
"additionalProperties": false,
"required": ["question", "answer"],
"properties": {
"title": { "type": "string", "maxLength": 500 },
"question": { "type": "string", "maxLength": 2000 },
"answer": { "type": "string", "maxLength": 2000 },
"hint": { "type": "string", "maxLength": 500 }
}
},
"event_schema": {
"type": "object",
"additionalProperties": false,
"required": ["type", "value"],
"properties": {
"type": { "type": "string", "enum": ["submit", "change", "select"] },
"value": {
"oneOf": [
{ "type": "string", "maxLength": 10000 },
{ "type": "array", "maxItems": 100, "items": { "type": "string", "maxLength": 1000 } }
]
}
}
},
"permissions": {
"network_domains": [],
"file_read": false,
"clipboard_write": false,
"message_actions": ["submit"]
},
"size_limits": { "min_height": 80, "max_height": 480 },
"skill_triggers": [],
"example_data": {
"title": "练习",
"question": "2 + 2 = ?",
"answer": "4",
"hint": "十以内加法"
}
}
事件协议
用户在卡片上的交互以事件形式回传,按组件 Manifest 的 event_schema 校验。内置组件的默认
event_schema 要求 type(submit / change / select)
与 value(字符串或字符串数组)。allowed_events 限定可触发的事件名,
选项类默认 ["submit"],weather_card/metric_card 由
actions[].event 推导,image_frame 默认为空。
智能体创建并应用
Agent 在拥有 workspace.manage 权限时,可直接在对话里创建并应用可信组件,无需切到设置页:
- 01component_register_manifest
提交第三方 Manifest,服务端做静态与健康检查并落审计;内置 ID 被拒绝。
- 02component_authorize
对刚注册的
plugin_id+manifest_version_id授权当前工作区并启用插件(enable=false可仅授权不启用)。 - 03component_list
查询已注册组件及其授权/启用状态,决定下一步是授权还是发布。
- 04canvas_emit_trusted_component
对自定义
component_type传其component_id发布;已授权则按sandbox_artifact交付。
第三方组件渲染器固定为 sandbox,依赖隔离浏览器渲染器;当前未配置,发布会以
sandbox_artifact 安全降级(runtime_status=unavailable)交付,不会把任意代码注入宿主 DOM。
内置 8 类组件不受此限制,端到端可用。
API 面
| 端点组 | 权限 | 职责 |
|---|---|---|
POST /plugins/components | workspace.write | 导入并校验第三方 Manifest(注册) |
GET /{plugin_id}/manifests · /authorizations · /checks | workspace.write | 列出版本、授权与检查记录 |
POST /{plugin_id}/authorizations · /authorizations/revoke | workspace.write | 授权或撤销当前工作区 |
POST /{plugin_id}/checks | workspace.write | 触发 health / render 检查 |
POST /{plugin_id}/artifacts | 已授权 | 按授权边界生成受控 Artifact |
POST /{plugin_id}/events/validate | 已授权 | 按 event_schema 校验用户事件 |
component_register_manifest / component_authorize / component_list
复用 ComponentService,与上方 HTTP 端点共享 Schema 守卫、哈希、审计与重授权逻辑;
Agent 工具额外要求 workspace.manage。
backend/app/domain/schemas/components.py · backend/app/services/components.py · backend/app/services/canvas_cards.py · frontend/src/components/chat/trusted-component-renderer.tsx · backend/app/skills/canvas_emit_trusted_component/SKILL.mdSecurity model
安全与授权
安全边界贯穿路由、服务、扩展授权、Provider Secret、沙箱和输出渲染,而不是单独依赖前端隐藏按钮。
所有资源查询由后端重新校验 Membership、Permission 与 ACL;跨工作区 ID 返回不可枚举错误。
Provider Key 由后端加密保存,不进入浏览器、日志、SSE、审计或导出。
MCP/Skill 的权限集合必须与服务端推导的最小集合完全一致,少一项或多一项都拒绝。
正式目标图谱的重要变更、删除影响与高风险执行保留用户确认。
远程 Provider、Docker 或 Transport 失败时保持失败事实,不落回 mock、宿主执行或硬编码结果。
授权、拒绝、撤销、失败、超时、大小阻断和成功调用均记录工作区审计。
Repository map
代码地图
frontend/src/main.tsxReact 入口frontend/src/App.tsx页面路由总表frontend/src/api/client.ts认证、工作区头、JSON/SSE 客户端frontend/src/features/按业务领域组织页面与交互backend/app/main.pyFastAPI 生命周期与调度器backend/app/api/routers/HTTP/SSE 契约与依赖边界backend/app/services/chat.pyChat、上下文、Agent 循环与持久化backend/app/services/agent_runtime.py工具定义、注册与执行分派backend/app/services/mcp_skills.pyMCP/Skill 生命周期、Grant 与 Invocationbackend/app/services/sandbox.py沙箱任务与 Agent Workspacebackend/app/prompts/系统提示词片段与风格编译器backend/app/repositories/工作区作用域数据访问backend/app/providers/ports/外部能力契约backend/app/providers/local|remote/适配器实现backend/app/domain/models.py核心持久化模型backend/app/domain/schemas/API 输入输出 SchemaExtension guide
扩展指南
新增 Provider
- 01定义 Port
优先复用现有 Protocol;不要让业务 Service 依赖厂商 SDK。
- 02实现 Adapter
显式建模可用性、超时、响应校验、远程能力和安全诊断。
- 03接入 Catalog / Probe
配置保存后执行真实探测,失败保持 disabled。
- 04记录 Trace / Usage
保存 Provider ID、请求标识、用量与安全错误分类。
- 05真实验收
用部署方明确配置的远程服务验证,缺凭据时标记未完成。
新增 Agent Tool
- 01从领域用例开始
Tool 只调用现有 Service 或受控 Port,不直接访问数据库句柄。
- 02声明严格 Schema
限制字段、长度、枚举与 additionalProperties。
- 03确定授权与副作用
按用户角色、工作区、资源范围与是否需二次确认注册。
- 04规范化结果
裁剪大小、脱敏错误、记录 Invocation / Audit,并返回可追踪来源。
- 05验证完整循环
真实模型发起调用、真实服务执行、刷新后仍可读取结果。
Verification
验证与完成定义
“现有测试通过”不等于业务闭环已完成。重要变更需要同时验证可见结果、后端事实和一个高风险失败/恢复边界。
# 常规检查(不自动包含 E2E 与真实远程 Provider)
npm.cmd run check
# 真实浏览器 E2E
npm.cmd --prefix frontend run test:e2e
# 已配置真实远程模型后
.\scripts\verify-real-provider.ps1
保持来源、权限、事务、审计和用户审核边界一致。