KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
05 · 插件、记忆与部署:bundled 也守同一条边界 — keel 龙骨
VISION.md 第 67 到 87 行把插件体系写成项目主线:「OpenClaw has an extensive plugin API. Core stays lean; optional capabilities should usually ship as plugins.」,并给出两层门槛的区分——核心的每一次新增都摊到「每个 operator 的每一次模型请求」上,插件与技能没有这个税。第 78 行则给出接口化的判据:「O
VISION.md 第 67 到 87 行把插件体系写成项目主线:「OpenClaw has an extensive plugin API. Core stays lean; optional capabilities should usually ship as plugins.」,并给出两层门槛的区分——核心的每一次新增都摊到「每个 operator 的每一次模型请求」上,插件与技能没有这个税。第 78 行则给出接口化的判据:「Once several independent PRs or requests wire in the same kind of capability, the right response is a contract, not a queue of merges.」
这一章覆盖三个系统加一层运维:
- 插件的双重声明与
OpenClawPluginApi的能力注册面; - bundle-style 与 code plugin 的分工,以及官方目录的阈值签名校验;
- 五层记忆与「单一槽位」约束、技能加载链、压缩模式;
- 自托管部署形态、密钥注入与「审计只存元数据」。
一、插件双重声明:清单与包元数据
每个 bundled plugin 都同时有两份声明,缺一不可。
第一份是 package.json 里的 openclaw 块。以 extensions/telegram/package.json 为例:extensions(第 19 行,指向实现入口)、setupEntry(第 22 行)、setupFeatures(第 23 行,含 configPromotion)、channel(第 26 行,含 configuredState、approvalFlags、docsPath 等展示与就绪元数据)。不同类型插件会用到 skills / install / compat / release 等更多键。
第二份是同目录的 openclaw.plugin.json,承载 id / skills / activation / channels / contracts / configSchema。
为什么必须拆两份?extensions/AGENTS.md 第 62 到 64 行给了答案:控制平面元数据必须与运行时代码分离——「Keep control-plane metadata separate from runtime logic. Discovery, config validation, setup hints, onboarding hints, and activation planning should be expressible from manifest/descriptors whenever possible.」发现、配置校验、setup 提示都要能在不执行插件代码的前提下算出来。因此第 68 到 70 行还禁止靠「eager global registry seeding 或 import-time 副作用」让插件变可用:「Plugin availability should come from manifest ownership plus targeted activation.」
二、OpenClawPluginApi:能力注册面
插件 API 类型是 OpenClawPluginApi(src/plugins/plugin-api.types.ts:175)。已核对到的注册点:
runtime: PluginRuntime(第 191 行)——插件拿到的 host 能力。runContext: OpenClawPluginRunContextApi(第 201 行)。registerTool(第 204 行)、registerHook(第 208 行)、registerChannel(第 222 行)、registerProvider(第 271 行)、registerToolMetadata(第 354 行)。
另外三块按命名空间组织:session.* 门面(注释分布在第 107 到 147 行,涵盖「为下一次 agent turn 排队注入上下文」「把已验证文件发到 direct-outbound 路由」等能力)、agent.events.*(注释在第 150 到 158 行,订阅被脱敏的 agent 事件)、lifecycle.*(注释在第 169 到 172 行)。
文件的注释形态本身就是一次设计说明:大量条目被标 @deprecated 并指向新门面,例如 api.session.state.registerSessionExtension(...)、api.agent.events.emitAgentEvent(...)、api.runContext.setRunContext(...)。这套 API 是「扁平字段迁移到命名空间门面」的中间态,旧字段保留但不再推荐,说明扩展面在收缩而不是膨胀。
构建器是 src/plugins/api-builder.ts:buildPluginApi()(第 189 行);而插件在激活期调用的注册动作会被捕获而不是立即生效,落在 src/plugins/captured-registration.ts——这是「注册与激活分离」的实现载体。
三、两种插件风格与被强制的边界
VISION.md 第 80 到 87 行给出两种风格:code plugin 运行 OpenClaw 插件代码,适合深度运行时扩展;bundle-style plugin 打包稳定的外部面(skills、MCP servers 及相关配置),并明确「Prefer bundle-style plugins when they can express the capability. They have a smaller, more stable interface and better security boundaries.」
这条偏好解释了目录里为什么有大量「只带清单不用写代码」的插件:extensions/ 下 148 个带 package.json 的目录之外,还有只带 openclaw.plugin.json 的清单型插件;Dockerfile 第 34 行的注释也确认这点——「Manifest-only bundled plugins remain valid selections but need no workspace metadata.」
边界由 extensions/AGENTS.md 强制:第 27 到 28 行要求「Extension production code should import from openclaw/plugin-sdk/* and its own local barrels」,第 29 到 30 行禁止「import core internals from src/**, src/channels/**, src/plugin-sdk-internal/**, or another extension's src/**」。第 78 到 83 行进一步规定扩展边界只能加 typed SDK subpath:「If an extension needs a new seam, add or replace a typed Plugin SDK subpath instead of reaching into core.」,且「ALL bundled plugins must move to modern SDK seams in the same change.」——bundled 插件不许留旧接口的兼容路径。
发现与兼容:src/plugins/discovery.ts:discoverOpenClawPlugins()(第 1507 行)与 discoverConfiguredPluginLoadPaths()(第 1482 行)负责扫描 bundled / workspace / global / package / bundle 各类 root(候选类型 PluginCandidate 第 79 行、结果 PluginDiscoveryResult 第 107 行);bundled 目录解析在 src/plugins/bundled-dir.ts;API 范围兼容判定是 src/plugins/package-compat.ts:satisfiesPluginApiRange()(第 117 行,配套 resolvePackagePluginApiRange() 第 83 行)。市场入口是 src/plugins/marketplace.ts。
官方外部插件目录多一层密码学校验:src/plugins/official-external-plugin-catalog-envelope.ts:verifyOfficialExternalPluginCatalogSignedEnvelope()(第 56 行)。它做的是 Ed25519 阈值签名校验——错误码闭集含 "missing-trust-key"(第 44 行)与 "invalid-signature"(第 45 行),阈值经 Math.max(1, Math.trunc(params.threshold ?? 1)) 归一(第 93 行),成功条件是已验签的可信公钥集合达到阈值(第 115 与 120 行)。阈值签名而非单签的意义是没有单一私钥能单独伪造目录。
四、五层记忆与单槽位
记忆按用途分五层:
- Instructions:
AGENTS.md,项目级指令。 - Curated core:
MEMORY.md/USER.md,始终注入。规范文件名常量是CANONICAL_ROOT_MEMORY_FILENAME = "MEMORY.md"(src/memory/root-memory-files.ts:7)。 - Episodic:按日归档的
memory/YYYY-MM-DD.md与 transcript。 - Prospective:standing intents,落在 SQLite。
- Review:
DREAMS.md,由 dreaming 流程产出。
实现跨两处:src/memory-host-sdk/(已核对 dreaming.ts、query.ts、engine-storage.ts、event-store.ts、event-export.ts、status.ts、secret.ts)与 packages/memory-host-sdk/。内置实现插件是 extensions/memory-core、extensions/memory-lancedb、extensions/memory-wiki。
关键约束是单槽位。 src/config/types.plugins.ts 的 PluginSlotsConfig(第 50 行)只有两个字段:
export type PluginSlotsConfig = {
/** Select which plugin owns the memory slot ("none" disables memory plugins). */
memory?: string; // 第 52 行
/** Select which plugin owns the context-engine slot. */
contextEngine?: string; // 第 54 行
};
即同一时刻只能有一个 memory 插件生效。这与 VISION.md 第 96 到 97 行的说法一致:「Memory is a special plugin slot where only one memory plugin can be active at a time. Today we ship multiple memory options; over time we plan to converge on one recommended default path.」
五、技能:SKILL.md 与运行时快照
技能的能力单位是 SKILL.md(YAML frontmatter + 正文)。契约接口是 src/skills/loading/skill-contract.ts:Skill(第 5 行),字段含 baseDir(第 15 行)、sourceInfo(第 18 行)、disableModelInvocation(第 19 行)——最后一项允许某个技能只作为资料存在、不被模型主动调用。渲染侧有 COMPACT_DESCRIPTION_MAX_CHARS = 220(第 35 行)、formatSkillsForPromptCore()(第 71 行)、formatSkillsCompactForPrompt()(第 101 行)与 escapeSkillXml()(第 26 行)。
依赖声明在 frontmatter 里。skills/github/SKILL.md 的 metadata(第 4 行)下有 requires: { bins: ["gh"] }(第 9 行)与 install(第 10 行)——技能可以声明自己需要哪些可执行文件以及怎么装。
运行时不直接读文件,而是用快照:src/skills/runtime/snapshot-hydration.ts 负责水合、refresh.ts 负责刷新、remote-skills.ts 负责远端技能、tool-dispatch.ts 负责把技能派发成工具。快照的意义是一次 run 内技能集合稳定,不会因为运行中文件变更导致提示词与实际能力不一致。
六、压缩与可插拔上下文引擎
压缩默认模式是 safeguard。类型在 src/config/types.agent-defaults.ts:AgentCompactionMode = "default" | "safeguard"(第 364 行),求值在 src/agents/agent-settings.ts:resolveEffectiveCompactionMode()(第 106 行):配置了 compaction.provider 直接提升为 safeguard(第 109 行),否则看 compaction.mode === "safeguard"(第 111 行)。
实现分三处:src/agents/embedded-agent-runner/compact.ts(主流程)、direct-compaction.ts(直连压缩)、compact.runtime.ts(运行时挂接),钩子在 src/agents/agent-hooks/compaction-safeguard.ts(默认导出 compactionSafeguardExtension,第 988 行)与 compaction-instructions.ts。压缩的硬要求是保留 tool-call / toolResult 配对切割:不能在切点把一对调用与结果拆开,否则上下文对模型来说是不合法的。
上下文引擎本身可插拔:src/context-engine/registry.ts(注册与解析,解析入口 resolveContextEngine() 第 712 行)、legacy.ts(默认实现)、init.ts(初始化)。它占用的是 PluginSlotsConfig.contextEngine 那个槽位,同样是单槽位。
七、模型接入:协议注册表
网络协议层的注册表是 packages/ai/src/api-registry.ts:ApiProvider(第 27 行)声明 { api, stream, streamSimple },工厂 createApiRegistry()(第 76 行)提供 registerApiProvider(第 79 行)、getApiProvider(第 94 行)、unregisterApiProviders(第 102 行)。协议 id 是闭集 KnownApi(packages/llm-core/src/types.ts:7),共九个:openai-completions、mistral-conversations、openai-responses、azure-openai-responses、openai-chatgpt-responses、anthropic-messages、bedrock-converse-stream、google-generative-ai、google-vertex。注意 Api = KnownApi | (string & {})(第 19 行)——自定义 provider 可以用集合外的 id,闭集只约束「有第一方适配器」的部分。
厂商实现分散在 packages/ai/src/transports/ 与 packages/ai/src/providers/,内置注册在 packages/ai/src/providers/register-builtins.ts。OpenClaw 侧的注册表是 src/llm/model-registry.ts(ModelRegistry 第 5 行)与 src/agents/prepared-model-registry.ts:loadPreparedAgentModelRegistry()(第 141 行)。
更大量的 provider 以插件形式存在。已核对的 extensions/ 目录包含 openai、anthropic、google、deepseek、mistral、xai、groq、cerebras、fireworks、together、openrouter、litellm、ollama、lmstudio、vllm、sglang、llama-cpp、qwen、kimi-coding、moonshot、minimax、zai、amazon-bedrock、amazon-bedrock-mantle、anthropic-vertex、cloudflare-ai-gateway、copilot。model ref 形如 provider/model,models.providers.* 下可配 apiKey / baseUrl / maxTokens / timeoutSeconds / contextWindow / contextTokens。
八、权限、密钥与审计
权限闭集。 Gateway 的 operator scope 在 src/gateway/operator-scopes.ts,八个常量:operator.admin(第 3 行)、operator.read(第 4 行)、operator.write(第 5 行)、operator.approvals(第 6 行)、operator.questions(第 7 行)、operator.pairing(第 8 行)、operator.talk(第 9 行)、operator.talk.secrets(第 10 行)。文件头注释点明它同时被「connection auth 与 method policy」消费。
密钥注入走 sentinel。 src/secrets/sentinel.ts 定义 SECRET_SENTINEL_PREFIX = "oc-sent-v2."(第 5 行)与 SECRET_SENTINEL_SUFFIX = ".end"(第 6 行)。替换发生在出口代理层:src/secrets/egress-proxy/stream-substitution.ts 用前后缀字节做流式定位与替换(含跨 chunk 的边界处理),proxy-server.ts 做整体替换。未解析的 sentinel 采取 fail-closed:src/secrets/runtime-degraded-state.ts 的 SecretAssignmentDisposition = "fail-closed" | "isolate"(第 25 行)就是这条策略的类型化表达。相关工具链是 src/secrets/{configure,apply,plan,audit-store,private-plan-file}.ts。
审计只存元数据。 src/audit/audit-event-store.ts 第 1 行的文件头注释直接写:「SQLite persistence and stable cursor queries for metadata-only audit events.」写入是 recordAuditEvent()(第 605 行),读取是 listAuditEvents()(第 661 行)且用稳定游标分页(cursor 走 sequence < cursor,第 678 到 679 行),保留期 AUDIT_EVENT_RETENTION_MS = 30 * 24 * 60 * 60_000(第 49 行,30 天),清理是 pruneExpiredAuditEvents()(第 724 行)。身份经 pseudonymizeAuditIdentity() 化名(第 494 行调用)。设计承诺是「永不存 prompt、消息正文、工具参数、工具结果」——这对上的是 VISION.md 第 61 到 63 行的隐私立场。
配置侧的自检在 src/security/audit-*.ts(已核对包含 audit-channel.ts、audit-gateway-config.ts、audit-plugins-trust.ts、audit-model-refs.ts、audit-extra.sync.ts 等),静态规则在 security/opengrep/(含 rules/、precise.yml、compile-rules.mjs)。
九、部署形态与自更新
镜像。 Dockerfile 是多阶段:依赖层用 node:24-bookworm(基础镜像按 digest 固定,第 16 行),构建层用 oven/bun:1.3.14(第 21 行),运行时是 bookworm-slim(第 17 到 18 行),且「runtime image is always bookworm-slim」(第 10 行注释)。构建参数 OPENCLAW_EXTENSIONS / OPENCLAW_BUNDLED_PLUGIN_DIR(第 11 到 12 行)配合 scripts/lib/docker-plugin-selection.mjs 只挑选需要的插件,依赖层仅抽取 package.json,这样「main build layer is not invalidated by unrelated source changes」。
Compose。 docker-compose.yml 单服务 openclaw-gateway,把宿主 ~/.openclaw 挂到 /home/node/.openclaw(第 45 行),并显式设 OPENCLAW_STATE_DIR / OPENCLAW_CONFIG_PATH / OPENCLAW_WORKSPACE_DIR(第 18 到 21 行,注释说明是为了防止宿主路径经 .env 泄漏进容器)。安全加固是 cap_drop: [NET_RAW](第 59 到 60 行),restart: unless-stopped(第 71 行)。
Fly 与 Render。 fly.toml 的进程命令是 node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan(第 18 行),健康检查打 /startupz(第 33 行),挂载 openclaw_data 到 /data(第 39 到 41 行)。deploy/fly.private.toml 是私有变体——没有 [http_service] 段即无公网入口。render.yaml 用 dockerCommand: node openclaw.mjs gateway --allow-unconfigured(第 9 行),OPENCLAW_GATEWAY_TOKEN 走 generateValue: true(第 19 行),磁盘 sizeGB: 1(第 23 行)。
自更新。 appcast.xml 是 macOS 端的 Sparkle 更新源(根元素声明 xmlns:sparkle,第 2 行),条目带 sparkle:minimumSystemVersion(第 11 行,已核对为 15.0)与 sparkle:edSignature 签名的 enclosure(第 1753 行)。CLI 侧的对应命令是 update 子命令。
CI 与本地钩子。 .github/workflows/ 中与发布直接相关的有 macos-release.yml、ios-periphery.yml、linux-app-release.yml、android-release.yml、openclaw-npm-release.yml、docker-release.yml、full-release-validation.yml。本地 git-hooks/pre-commit 只对 staged 文件跑格式化(先经 scripts/pre-commit/filter-staged-files.mjs 过滤),并在 rebase / cherry-pick / merge 进行中直接 exit 0 跳过。
QA。 qa/ 是私有 QA 资产:scenarios/(按子系统分组的 yaml 场景,含 channels / matrix-e2ee / agents / security 等)、frontier-harness-plan.md、maturity-scores.yaml、convex-credential-broker/(Convex 实现的凭据代理)。
代码地图
| 机制 | 位置 | 要点 |
|---|---|---|
| 插件包元数据 | extensions/telegram/package.json: openclaw |
第 18 行;含 extensions/setupEntry/channel |
| 插件清单 | extensions/telegram/openclaw.plugin.json |
与 package.json 双重声明 |
| 插件 API | src/plugins/plugin-api.types.ts: OpenClawPluginApi |
第 175 行;runtime 191、runContext 201 |
| 工具/钩子注册 | src/plugins/plugin-api.types.ts: registerTool |
第 204 行;registerHook 208、registerChannel 222、registerProvider 271 |
| 工具元数据注册 | src/plugins/plugin-api.types.ts: registerToolMetadata |
第 354 行 |
| API 构建 | src/plugins/api-builder.ts: buildPluginApi |
第 189 行 |
| 注册捕获 | src/plugins/captured-registration.ts |
注册与激活分离的载体 |
| 插件发现 | src/plugins/discovery.ts: discoverOpenClawPlugins |
第 1507 行;候选类型第 79 行 |
| bundled 目录解析 | src/plugins/bundled-dir.ts |
bundled plugin 根目录 |
| API 范围兼容 | src/plugins/package-compat.ts: satisfiesPluginApiRange |
第 117 行 |
| 市场入口 | src/plugins/marketplace.ts |
插件分发接入口 |
| 官方目录签名校验 | src/plugins/official-external-plugin-catalog-envelope.ts: verifyOfficialExternalPluginCatalogSignedEnvelope |
第 56 行;Ed25519 阈值;错误码第 44-45 行 |
| 边界定义 | extensions/AGENTS.md: 第 27-30 行、第 78-83 行 |
只走 plugin-sdk subpath,禁止深导入 src/** |
| 记忆槽位 | src/config/types.plugins.ts: PluginSlotsConfig |
第 50 行;memory 52、contextEngine 54 |
| 根记忆文件名 | src/memory/root-memory-files.ts: CANONICAL_ROOT_MEMORY_FILENAME |
第 7 行;值 MEMORY.md |
| dreaming 实现 | src/memory-host-sdk/dreaming.ts |
与 query / engine-storage / event-store 同目录 |
| 技能契约 | src/skills/loading/skill-contract.ts: Skill |
第 5 行;disableModelInvocation 第 19 行 |
| 技能依赖声明 | skills/github/SKILL.md: metadata |
第 4 行;requires.bins 第 9 行、install 第 10 行 |
| 技能运行时快照 | src/skills/runtime/snapshot-hydration.ts |
与 refresh / remote-skills / tool-dispatch 同目录 |
| 压缩模式求值 | src/agents/agent-settings.ts: resolveEffectiveCompactionMode |
第 106 行;有 provider 直接 safeguard |
| 压缩模式类型 | src/config/types.agent-defaults.ts: AgentCompactionMode |
第 364 行;default / safeguard |
| 压缩钩子 | src/agents/agent-hooks/compaction-safeguard.ts: compactionSafeguardExtension |
第 988 行默认导出 |
| 上下文引擎注册 | src/context-engine/registry.ts: resolveContextEngine |
第 712 行;legacy 为默认实现 |
| 协议注册表 | packages/ai/src/api-registry.ts: ApiProvider |
第 27 行;createApiRegistry 第 76 行 |
| 协议 id 闭集 | packages/llm-core/src/types.ts: KnownApi |
第 7 行;九个 id,Api 允许集合外扩展 |
| 模型注册表加载 | src/agents/prepared-model-registry.ts: loadPreparedAgentModelRegistry |
第 141 行 |
| operator scope | src/gateway/operator-scopes.ts: ADMIN_SCOPE |
第 3 行;八项止于 TALK_SECRETS_SCOPE 第 10 行 |
| sentinel 前缀 | src/secrets/sentinel.ts: SECRET_SENTINEL_PREFIX |
第 5 行;值 oc-sent-v2.,后缀第 6 行 |
| 解密失败策略 | src/secrets/runtime-degraded-state.ts: SecretAssignmentDisposition |
第 25 行;fail-closed / isolate |
| 审计库 | src/audit/audit-event-store.ts: recordAuditEvent |
第 605 行;listAuditEvents 661(游标)、保留 30 天第 49 行 |
| 审计身份化名 | src/audit/audit-event-store.ts: pseudonymizeAuditIdentity 调用 |
第 494 行 |
| 安全自检 | src/security/audit-gateway-config.ts |
与 audit-channel.ts / audit-plugins-trust.ts 并列 |
| 静态规则 | security/opengrep/rules/ |
配 precise.yml 与 compile-rules.mjs |
| 镜像构建 | Dockerfile: FROM workspace-deps |
第 30 行;node:24 依赖层、bun 构建层、slim 运行时 |
| Compose 加固 | docker-compose.yml: cap_drop |
第 59-60 行 NET_RAW;服务名第 2 行 |
| Fly 进程 | fly.toml: [processes] app |
第 18 行;健康检查 /startupz 第 33 行 |
| 私有 Fly 变体 | deploy/fly.private.toml |
无 http_service 段即无公网入口 |
| Render 蓝图 | render.yaml: OPENCLAW_GATEWAY_TOKEN |
第 18-19 行 generateValue;磁盘 1GB 第 23 行 |
| macOS 自更新源 | appcast.xml: sparkle:edSignature |
第 2 行命名空间;minimumSystemVersion 第 11 行 |
| 本地钩子 | git-hooks/pre-commit |
只处理 staged 文件;sequencer 中直接 exit 0 |
关键取舍
bundled 插件被强制当成第三方,代价是核心重构要同步迁移全部内置插件。extensions/AGENTS.md 要求 bundled 插件只能用 openclaw/plugin-sdk/*,并且「ALL bundled plugins must move to modern SDK seams in the same change」——不允许保留扩展侧的兼容路径。收益是这道门永远是热的,API 不会因为「核心自己人要用」而腐化;代价是任何 SDK seam 变更都变成上百个目录的同步改动,且必须是一次性完成。
注册动作在激活期被捕获而不立即生效,代价是要维护两阶段状态。captured-registration.ts 的存在意味着「插件调用了 registerTool」不等于「工具已可用」。收益是激活可以是计划式的(见 activation-planner.ts),能在真正加载运行时代码之前算清「这个插件会带来什么」,从而实现 manifest 优先的发现与校验;代价是任何依赖「注册即生效」的直觉写法都会出错。
官方外部插件目录用阈值签名,代价是信任根与密钥轮换有运维负担。verifyOfficialExternalPluginCatalogSignedEnvelope 要求达到 threshold 个可信公钥验签通过,缺少信任根时报 missing-trust-key、签名不符报 invalid-signature。收益是没有单一私钥能伪造整个目录;代价是公钥集合本身成为必须分发与更新的资产,且阈值越高对签名流程的可用性要求越高。
记忆与上下文引擎都是单槽位,代价是无法组合多个记忆实现。PluginSlotsConfig 只给 memory 与 contextEngine 各一个字符串位置。收益是「记忆」这个横切能力在任何时刻只有一条写入路径,不需要解决多实现的写冲突与读取优先级;代价是想要「向量检索 + 日志归档 + 知识库」的组合能力,只能由一个插件内部去编排,或者在 code plugin 里自己实现路由器。
审计只存元数据,代价是事后取证能力有上限。audit-event-store.ts 的文件头与保留期常量把承诺写死:metadata-only、30 天、稳定游标分页、身份化名。收益是审计库即便泄露也不直接暴露对话内容,且体量小、可长期保留;代价是排查「模型为什么回了这句话」时审计帮不上忙,必须回到 transcript 体系(那是另一套存储,拥有不同的访问控制)。
自测题
OpenClawPluginApi里既有registerTool这种扁平方法,也有api.session.*/api.agent.events.*命名空间。请说明这种「扁平 + 门面」并存对插件作者意味着什么,以及为什么迁移不能一次删掉旧字段。- 为什么控制平面元数据(发现、配置校验、setup 提示)必须能在不执行插件代码的前提下算出来?请举一个如果必须执行代码才能算,就会出问题的具体场景。
- 阈值签名相比单签名,在「官方目录被篡改」这个威胁上分别提供什么保证?如果阈值配成 1,与单签名有什么实质区别?
- 记忆是单槽位。如果你要同时提供「向量检索」与「按日归档」两种能力,说明两种可行做法(一种在单个 memory 插件内部编排,一种绕开槽位),并各指出一个代价。
apps之外,本仓库同时存在Dockerfile、docker-compose.yml、fly.toml、deploy/fly.private.toml、render.yaml五个部署描述。请说明deploy/fly.private.toml与fly.toml的关键差异,以及这个差异为什么不能靠环境变量实现。