本文档地位:本文档是 Bubbles 产品层的唯一事实源(Single Source of Truth, SSoT)。
- 所有"产品定义、用户体验、能力边界、行为契约"层面的变更,必须先修改本文档,再落地代码。
- README.md / SECURITY.md / 命令帮助文本 / 配置 schema 注释 / Skills 文档与本文档冲突时,以本文档为准,并提交修复 PR。
- 实现细节(数据结构、类名、行号、依赖版本)不进本文档;那是代码层的事实,由代码本身陈述。本文档只关心"产品对用户承诺了什么"。
- 任何 PR 若引入用户可见的新能力 / 改了交互契约 / 改了配置字段语义 / 改了支持的渠道或 provider 列表,必须包含本文档的同步修改,否则视为不完整。
Bubbles 是一个本地优先、以 coding agent 为内核的个人 AI 助手框架。
它不是一个聊天机器人,而是一个长期运行、可被多渠道触达、有持久记忆与工作空间、能够主动执行任务的个人助理。一句话区分:
| 不是 | 是 |
|---|---|
| 一个 ChatBot 模板 | 一个把"个人助理"做成本地服务的框架 |
| 一个只能问答的 LLM 包装 | 一个能读写文件、跑命令、上网、定时唤醒、跨渠道发消息的执行体 |
| 一个云上 SaaS | 一个跑在用户自己机器或 NAS / VPS 上的进程 |
| 一个产品化的终端用户应用 | 一个面向"懂技术的个人用户"的可编程框架 |
- 统一入口:在用户已有的通讯工具里(Telegram / 飞书 / 微信 / 邮箱 ...),用同一个助手,跨渠道共享上下文。
- 可执行:助手不是只会回复文字,它有工具——读写文件、跑 shell、抓网页、搜索、定时、跨会话派生子 agent。
- 可记忆:每个会话是一个独立的工作目录,有自己的人设(SOUL)、长期记忆(MEMORY)、文件、技能(Skills)。
- 本地与隐私:所有会话数据存放在用户本地(
~/.bubbles/),用户自己掌握 LLM API Key,没有强制云依赖。 - 可被定时唤醒:助手不只是被动响应,可以基于 cron 在指定时间主动唤起一次会话并把结果投递回某个渠道。
懂技术、对自己时间和工作流有控制欲、愿意为"更好的助手"付出本地配置成本的个人用户:开发者、研究者、知识工作者、创作者。
非目标用户:纯小白用户、企业团队协作场景、需要 SLA 的客服机器人。
以下原则用于裁决产品决策上的分歧。当出现"该不该加这个功能"、"该不该这样做"的争论时,按顺序对照:
- Coding agent first:助手的推理与执行能力,由当前 SOTA 的 coding agent 范式提供(tool calling 循环 + 文件/Shell/Web 工具)。任何降级到"硬编码工作流"的方案需要强理由。
- 本地优先:默认所有状态、密钥、消息历史在用户本地。任何"必须上云"的功能要么不做,要么提供等价的本地路径。
- 渠道是入口,不是产品:渠道(Telegram/飞书/微信...)只负责把消息搬进搬出,不承载产品逻辑。同一个 agent 内核必须能挂接任意渠道。
- 会话即工作空间:每个会话是一个目录,不是一段聊天记录。这意味着助手有"地方"放它的笔记、产物、技能。
- 配置即契约:用户可见的产品行为由
~/.bubbles/config.json决定。新增一个用户可感知的开关 = 新增一个配置字段 + 修改本文档。 - 优雅胜过完备:宁可少支持一个渠道,也不要把渠道实现做得乱。宁可少一个 provider,也不要把 provider 路由做成猜谜。
- Alpha 阶段允许破坏性变更:版本未到 1.0 之前,可以为了长期正确放弃短期兼容。改名、删命令、调配置 schema 都允许,但必须同步改本文档与 README。
以下是 Bubbles 必须支撑的核心场景。任何与这些场景冲突的设计需要重新评估:
| 场景 | 描述 |
|---|---|
| CLI 私人副驾 | 在终端 bubbles agent,进入交互模式,绑定一个会话,聊任何事,让助手读写当前项目文件。 |
| 多渠道触达 | 启动 bubbles gateway,从 Telegram / 飞书 / 微信任何一处发消息给助手,得到响应。 |
| 群里 @ 我才回 | 在群里只有被 @ 时才回应,其他时候安静;私聊默认回应。 |
| 定时主动 | 设置一个 cron 任务:"每天早上 9 点总结今日日程并发到我 Telegram"。 |
| 跨渠道一致 | 在 Telegram 跟助手聊到一半,切到飞书继续聊(取决于 session_mode 配置)。 |
| Skill 复用 | 让助手做一份 PPT / PDF / Excel / 海报,调用预置 Skill 完成,不用用户每次现场教。 |
| 私人工作空间 | 助手在自己的会话目录里写代码、存草稿、维护 MEMORY,下次回来还在。 |
| 本地大模型 | 用户用 Ollama / vLLM / 任意 OpenAI 兼容端点替代云 API。 |
Bubbles 以**单一二进制 CLI(bubbles)**对外暴露能力,没有 GUI、没有 Web 控制台。
| 模式 | 命令 | 说明 |
|---|---|---|
| 交互式 CLI | bubbles agent |
终端聊天,最贴近开发自用场景,支持 /session /config /compact 等斜杠命令。 |
| 单次调用 | bubbles agent -m "…" -s <session> |
适合脚本化与定时调用,发完拿到响应即退出。 |
| 网关常驻 | bubbles gateway |
启动所有已 enable 的渠道 + cron 调度器,长时间运行,是"个人助理"的主形态。 |
| 运维命令 | bubbles status / channels status / cron … / provider login … / sync-skills |
配置、登录、同步、巡检。 |
注:
bubbles onboard是唯一的初始化命令,幂等可重入;任何"重置"操作不会自动删除已有会话数据。
bubbles status的产品契约:展示装机级基线(默认 model、已配置 provider、启用渠道)。会话维度只在该会话与默认不同时才出现——例如 model override、绑定了 cron job、开了心跳。沿用默认的会话在 status 里保持安静,避免把命令撑成会话审计。一直没有 session 维度的子命令;所有会话级细节通过此差异视图与交互模式的/config//prompt暴露。
- 本地直跑:
pip install -e .+bubbles onboard+bubbles gateway。 - Docker:基于
Dockerfile/docker-compose.yml构建镜像,挂载~/.bubbles作为持久卷。 - 网桥进程:WhatsApp 渠道依赖一个独立的 Node.js 网桥(项目内
bridge/目录),由bubbles channels login引导构建与登录。其他渠道全部在 Python 主进程内。
契约:
- 助手以"tool calling 迭代循环"方式工作:LLM 决定调哪个工具 → 工具执行 → 结果回填 → LLM 继续,直到产出最终回复或达到迭代上限。
- 用户在
agents.defaults配置默认模型、温度、上下文上限、最大迭代次数、记忆窗口、API 重试次数与超时、并发上限。 - 历史超过上下文限制时,助手会自动 compact(摘要旧消息、保留最近一段原文)。保留窗口按 token 预算选取而非固定条数:长工具输出可能只留两三轮,短对话能留几十轮。用户也可在交互模式下手动
/compact。 - 多会话并发:不同会话可同时处理,并发上限默认按 CPU 核数推算(可配)。同一个会话内部始终串行——同一份历史不会被两个回合同时写。
- 回合进行中仍可插话:助手正在跑工具时收到的新消息,会在它的下一次工具调用之前插入当前回合,而不是排队等整个回合结束。助手自行判断这是补充信息(继续原任务并一并交代结果)还是改变方向(放弃当前路径并说明放弃了什么)。斜杠命令不走此路径,仍按命令处理。
- 助手在每次调用时构建系统提示词 = 全局基底 + 会话 SOUL + 会话 MEMORY + 会话 Skills 索引 + 当前会话/渠道上下文。
- 工具调用过程支持流式回显"进度"(文字片段)与"工具提示"(如
read_file("…"));两者由channels.send_progress/channels.send_tool_hints控制是否回传到渠道。
用户可感知的能力:
| 工具类 | 用户可感知的能力 |
|---|---|
| 文件 | 在会话工作目录里读、写、改、列出文件。 |
| Shell | 在受控超时内跑 shell 命令,输出会回到对话。 |
| Web 搜索 / 抓取 | 用 Tavily 搜索;用 trafilatura 抓取并提取网页正文。 |
| 跨渠道发消息 | 助手主动给某个渠道某个会话发消息(不一定是当前对话渠道)。 |
| 派生子 agent | 启动一个独立子任务,不阻塞主对话,完成后异步通知。 |
| 定时任务 | 在对话里说"明天早上提醒我 X",助手把它落到 cron。 |
| Task 列表 | 维护"待办任务",跨轮次保留。 |
| MCP 协议工具 | 通过 MCP server 配置接入第三方工具集(stdio 或 streamable HTTP)。 |
契约边界:助手不会突破会话工作目录访问任意文件系统路径;shell 工具有超时;网络工具有超时与输出截断。
错误反馈策略(强默认,不提供开关):
- 处理消息时抛出的任何未捕获异常都进入日志(
logger.exception),不向用户暴露异常类型、堆栈、内部路径。 - 判据是"这一轮是否由用户主动触发",不是群聊 / 私聊:
- 用户触发(私聊、群里 @ 了机器人、CLI):回一条简短提示。有人在等回应,静默才是坏体验。
- 非用户触发(cron / 心跳等系统触发轮次,以及群里没 @ 机器人的旁听消息):完全静默。错误只进日志,不回任何文字、不发表情反应、不留痕迹。理由:没人在等的消息,"机器人在叫"的成本远高于"错过一次失败信号"。
- 模型接口失败会说明类别:重试仍失败时,回一条点明类别的提示(限流 / 接口暂时不可用 / 上下文超限 / 认证失败 / 请求被拒),并说明重试了几次。给类别是为了让用户能判断"等一下再试"还是"去检查 API key";类别之外不暴露任何异常类型、堆栈、内部路径。其他异常仍回与具体错误无关的固定文案。
- 节流:同一会话(
session_key)在 60 秒窗口内只发送一次错误反馈;窗口内的后续错误一律静默落日志。 - CLI 例外:CLI 是开发与调试主入口;节流命中时仍 publish 一条空 outbound,保证交互模式的"等待回合结束"信号能正常解锁,不让用户的 prompt 永久卡住。其他渠道无此特殊处理。
- 达到迭代上限不算错误:当助手用完
max_tool_iterations仍未结束,会回一条说明性的提示(非异常路径),不受上述策略约束。
已支持的渠道(按字母序,这是产品对外承诺的列表):
| 渠道 | 私聊 | 群聊行为 | 鉴权方式 | 备注 |
|---|---|---|---|---|
| CLI | ✓ | — | — | 内置交互模式,等价于一个本地"渠道"。 |
| DingTalk | ✓ | @ 触发 | ClientID + ClientSecret | Stream 模式。 |
| Discord | ✓ | @ 触发 | Bot Token | 走 Gateway WebSocket。 |
| ✓ | — | IMAP + SMTP 凭据 | 周期性轮询新邮件,自动回信开关可控。 | |
| Feishu | ✓ | @ 触发 | App ID + App Secret | WebSocket 长连接接入飞书开放平台。 |
| Matrix | ✓ | 由 group_policy 决定 |
Access Token | 可选依赖(pip install bubbles[matrix]),支持 E2EE。 |
| Mochat | ✓ | 由 mention.require_in_groups 决定 |
claw_token | 内部协作工具集成。 |
| ✓ | — | AppID + Secret | 基于官方 botpy。 | |
| Slack | DM 策略可控 | 由 group_policy 决定 |
Bot Token + App Token | Socket Mode。 |
| Telegram | ✓ | @ 触发 | Bot Token | 可走代理。 |
| ✓ | @ 触发 | wcferry(仅 Windows) | 受限于 wcferry 的平台支持。 | |
| ✓ | 群聊 @ 触发 | 扫码(经 Node.js 网桥) | 唯一一个需要外部进程协作的渠道。 |
所有渠道的统一契约:
- 白名单:每个渠道支持
allow_from(用户/账号粒度)作为最小权限模型;空列表的语义由各渠道的实现决定,但产品层规则是"默认应保守,公开访问需用户显式同意"。 - 群聊触发策略:群聊默认"被 @ 才回";私聊默认全回;具体策略字段(如
group_policy/mention.require_in_groups)属配置层细节,由Channels Config字段定义。 - 群聊标注:每条入站消息必须在 metadata 里标注是否来自群聊(标准字段
is_group: bool,缺省视为false/ 私聊)。这是错误反馈策略与触发策略统一处理的依据,新增渠道必须实现。 - 媒体支持:渠道实现负责把图片 / 文件下载到会话工作目录的
data/下,作为路径传入对话上下文。 - 回执:助手回复可附带"进度文字"与"工具调用提示",是否启用由
channels.send_progress与channels.send_tool_hints控制。
会话归属(channels.session_mode):
channel(默认):每个(渠道, chat_id)一个会话,互不串。user:同一用户跨渠道共享会话。global:全局单一会话。
@ 提及(跨渠道统一):
- 模型在文本中嵌入
<@id>标记表示 @ 某人;id是该渠道的原生用户 ID(微信 wxid、飞书 open_id 等)。 - 入站:渠道收到原生 @ 时(如微信
@昵称、飞书@_user_1占位符),统一转换为<@id>后交给模型。 - 出站:发送时渠道把
<@id>翻译回原生格式(微信填aters数组、飞书构造卡片<at>元素等)。 - 模型可通过
find_person(query)工具按姓名搜当前群成员,拿到对应的<@id>标记后嵌入回复文本中。仅群聊有效,私聊/CLI 返回"无成员"。
新增渠道的产品检查清单:必须支持白名单、必须明确群聊触发策略、必须在 metadata 标注
is_group、必须能把媒体落到会话工作目录、必须能回流进度。若渠道支持群 @,必须实现 inbound/outbound 的<@id>转译及get_group_members。
Bubbles 不绑定任何单一模型厂商。Provider 列表是产品对用户的承诺:用户切到任意一个支持的 provider,最小变更应只是改一行 model + 加一个 API key。
支持类别:
| 类别 | 代表 provider | 鉴权 |
|---|---|---|
| 直连标准模型厂商 | Anthropic / OpenAI / Google Gemini / DeepSeek / Groq / Moonshot / Minimax / Zhipu / Dashscope(通义) | API Key |
| 网关聚合 | OpenRouter / AiHubMix / SiliconFlow / VolcEngine | API Key |
| 自托管 / 兼容端点 | Custom(任意 OpenAI 兼容地址)/ Ollama / vLLM | 可选 API Key |
| OAuth 授权 | OpenAI Codex / GitHub Copilot | bubbles provider login <name> |
Provider 选择契约:
- 用户在
agents.defaults.provider显式指定,或留空让系统按model字符串自动匹配。 - 自动匹配在歧义场景下要可解释——"为什么我配了 model X 走了 provider Y"必须是用户能从 model 字符串前缀或关键词读出来的,不是黑盒。
- OAuth provider 不会被当作 fallback 使用,必须用户显式选择。
- Custom(任意 OpenAI 兼容端点)走独立 client,不经过 LiteLLM,以保证用户对请求格式的完全控制。
- 所有非 OAuth provider 都通过 LiteLLM 统一适配,新增 provider 的成本应仅是登记一行注册条目 + 改本文档。
失败与重试契约(对所有 provider 一致,不因换 provider 而变):
- 值得重试的失败会自动重试:限流、服务端故障、超时、连接失败。默认重试 2 次,指数退避;服务端给了
Retry-After就听它。 - 上下文超限会先压缩历史再重试一次,而不是直接把失败抛给用户。
- 重试无意义的失败立即放弃:认证失败、请求非法、内容策略拒绝。
- 重试次数与单次请求超时由用户配置。底层 SDK 的隐式重试一律关闭——否则各厂商默认值不同(有的 2 次、有的 0 次),实际请求数会变成两层相乘,既不可控也不可解释。
- 传输层失败不会被伪装成模型的回复:它不进对话历史、不会被当成摘要内容。用户可见部分见 §5.1 错误反馈策略。
对外产品承诺:用户在配置文件里看到的"支持哪些 provider 字段",等于本文档列出的清单,等于代码 providers 注册表。三者保持同步。
会话(Session)是 Bubbles 的核心抽象。一个会话 ≠ 一段对话历史,而是一个完整的工作单元:
~/.bubbles/sessions/<session_key>/
├── session.jsonl # 对话历史(含元数据头与压缩点)
├── SOUL.md # 该会话的人设
├── MEMORY.md # 该会话的长期记忆
├── config.json # 该会话对默认配置的覆盖(model / system_prompt 等)
├── data/ # 助手工作区与临时媒体缓冲区(无长期保留承诺,见下)
└── skills/ # 该会话可用的技能(从模板同步)
契约:
- 会话目录是助手的"工作目录",所有文件工具默认在此根下操作。
- 会话 key 由"渠道 + chat_id"按
session_mode派生,或由用户在 CLI 里/session <name>显式指定。 - 会话级配置(
config.json)覆盖全局默认;用户在交互模式下/config <key> <value>也写入此文件。 - 历史压缩(compaction)会在
session.jsonl里留下显式标记,可被读出但不会假装"消息没发生过"。 bubbles sync-skills会把模板里的 skills 同步到所有会话;用户在自己会话里改过的 skills 不会被覆盖(同步策略详见 sync-skills 命令的实际实现,但产品契约是"用户编辑不丢")。data/是工作区与临时缓冲区,无长期保留承诺:服务会按 mtime 自动清理 3 天未访问的文件。需要长留的事实写进MEMORY.md;需要长留的产物分发后即视为可清。session.jsonl/SOUL.md/MEMORY.md/config.json/skills/不受清理影响。- 执行沙箱(每会话可选):Shell 与文件工具的实际执行环境由该会话绑定的沙箱后端提供,全局默认在
tools.sandbox配置,会话可用/config sandbox <backend>覆盖。默认后端local与历史行为完全一致(共享宿主环境)。local_isolated给每个会话一份独立的$HOME(及 XDG / WindowsAPPDATA等),让会话内的 CLI 工具(gh/aws/gcloud/kubectl/ git 等)各自存取按会话隔离的凭证——即"每会话独立 CLI 身份"。该私有 home 落在会话工作目录之外(与sessions/平级的session_homes/<key>/),因此不受data/的 3 天清理影响,也不被模型的文件工具读到(只有 exec 子进程可见)。沙箱抽象为后续容器 / 远程后端预留,不改变工具的对外契约。
沙箱的隔离边界:
local_isolated只隔离"环境变量导向的凭证查找",不隔离整个文件系统——用绝对路径直读宿主文件仍可达。要"绝不可能跨会话/越出工作区"的硬隔离,需容器 / OS 级后端或部署侧隔离,见 §7 与SECURITY.md§4.1。
Skills 是助手的"长期能力",预装在会话模板里,新会话首次创建时复制过去。
当前预装的 Skills 清单(产品承诺):
| Skill | 能力 |
|---|---|
pdf |
生成 / 读取 PDF。 |
docx |
生成 / 读取 Word 文档。 |
xlsx |
生成 / 读取 Excel。 |
pptx |
生成 PPT。 |
canvas-design |
在画布上做视觉设计(含字体库)。 |
summarize |
长文本摘要工作流。 |
tmux |
终端多路复用辅助。 |
file-share |
把文件分享给用户的标准化流程。 |
契约:
- 每个 Skill 是一个目录,包含一个
SKILL.md(描述何时使用、能力清单、调用规范)以及它依赖的脚本/资源。 - 助手在系统提示词里看得到所有 Skills 的索引;具体内容按需加载。
- 用户可以在自己的会话里增删改 Skills,不影响全局模板。
- 增删默认 Skills 是产品级变更,必须改本文档。
契约:
- 用户可以通过
bubbles cron add/ 助手工具调用 / 自然语言"明天早上提醒我 X"三种方式登记定时任务。 - 调度类型支持:
at(一次性绝对时间)、every(间隔)、cron(标准 5 段表达式);时区可指定。 - 每个 job 包含一条要发给助手的消息。触发时助手会以这条消息为输入跑一次完整的 agent 循环。
- 任务可绑定一个会话 key,触发时复用该会话的上下文与历史。
- 任务可声明"投递目标"(渠道 + chat_id):助手响应除了写回会话历史,还会主动发到目标渠道。
- 任务有启用/禁用开关;禁用任务不会自动执行,但可被
cron run -f手动触发。 - 任务列表持久化在
~/.bubbles/cron/jobs.json,与会话目录平级。 - 会话隔离(仅 agent 工具):模型可调用的
cron工具的list/remove严格限定在当前会话——只看得到、只删得掉本会话创建的 job。其他会话的 job 既不出现在list返回中,对remove也一律返回与"不存在"完全相同的错误文案(不通过错误信息泄露其他会话的 job id)。CLI(bubbles cron …)保持全局视角不受此限,是跨会话运维入口。 - 系统触发 turn 不可用
cron工具:cron job 与心跳触发的 agent turn(即"系统触发 turn",与stay_silent同款判断)期间,模型完全看不到cron工具——无论 add / list / remove 都不可用。防止"一次触发 → 注册更多 job → 雪崩"的嵌套;triggered turn 想表达"完成后自删"等语义请走声明式字段(如delete_after_run),不要走运行时再调度。 - 沉默信号
stay_silent:cron job 触发的 agent turn(以及任何系统触发的周期/事件 turn,如群聊心跳)可调stay_silent工具表达"本轮判断无需动作"——调后不向 channel 发出任何 outbound,也不写回会话历史。这是"看一眼条件、不满足就别说话"模式的标准实现方式。该工具仅在系统触发的 turn 里注册,用户直接发问的 turn 看不到。 - 崩溃安全:服务按
next_run_at_ms锚点对齐持久化(每个everyjob 在创建时锁定 anchor,重启后按原节奏继续不漂移);执行前 pre-advance + 写盘,崩溃也不会重发。 - 失败退避:连续失败按 30s / 1m / 5m / 15m / 60m 指数退避,避开 API rate-limit 风暴;成功后自动清零。一次性
at任务失败不进退避(一次性,下次也不会跑)。
契约:
- 每个 session 可独立开启一个心跳;通过群内/CLI 内
/heartbeat <interval>(如/heartbeat 30m、/heartbeat 2h、/heartbeat 30默认分钟)开启,/heartbeat off关闭,/heartbeat无参看状态。 - 默认关闭。仅用户能开关,模型没有对应工具(防止 AI 给自己装上自动闹钟)。
- 间隔范围 1 分钟 – 24 小时。开启后底层会注册一个
every类型的 cron job,复用 §5.6 的全部基础设施(持久化、崩溃安全、退避)。 - 开启时若
<work_dir>/HEARTBEATS.md不存在则写入模板;存在则沿用用户编辑过的版本。 - 心跳触发的 turn 里,模型会看到 HEARTBEATS.md 加载在 context 中、并收到
[心跳触发]提示消息。模型按 HEARTBEATS.md 决策;无事可做就调stay_silent,默认偏向沉默。 - 当心跳处于开启状态时,系统 prompt 会出现
## Heartbeat: ON块告诉模型当前节奏;关闭时模型仍知道这是个用户可启用的功能(INSTRUCTIONS 里有静态说明),但不会以为自己当下在心跳态。
用户感知到的"助手能做什么",由以下工具集合定义:
| 类别 | 用户感知 |
|---|---|
| 文件 | 读、写、改、列目录(在会话绑定的沙箱内执行)。 |
| Shell | 跑命令、看输出(受时间与输出长度限制;在会话绑定的沙箱内执行,可选每会话独立 $HOME / CLI 凭证隔离)。 |
| Web | 搜索、抓网页正文。 |
| 消息 | 主动给指定渠道指定会话发消息(必须显式声明目标)。 |
| Spawn | 派生独立子任务,主对话不阻塞。 |
| Cron | 登记 / 修改 / 删除定时任务。 |
| Task | 维护 Claude Code 风格的待办列表,跨对话轮次保留。 |
| FindPerson | 按姓名搜当前群成员,拿到 <@id> 标记可嵌入回复实现 @ 提及。 |
| MCP | 接入用户在 tools.mcp_servers 里配置的任意 MCP server 提供的工具。 |
新增工具的产品检查清单:必须给用户带来一个可清晰命名的能力(一行话),必须有合理的副作用边界与超时,必须在本文档登记。
工具结果长度上限:任何工具(含 MCP)的单次结果都有 token 上限,超出时保留头尾、中间省略并明确告知助手已截断。理由:单条结果若超过 compact 的保留预算,任何压缩策略都守不住上下文上限。
pip install -e .bubbles onboard:建立~/.bubbles/config.json与 sessions 目录。- 编辑
~/.bubbles/config.json:至少配置一个 provider 的 API key 与默认模型。 bubbles agent:进入交互模式 →/session my→ 开始对话。- (可选)配置一个或多个渠道 →
bubbles gateway→ 长期常驻。
| 输入 | 行为 |
|---|---|
/session <id> |
绑定到指定会话;不存在则创建。 |
/config |
显示当前会话配置。 |
/config <k> <v> |
设置会话级配置项(model / system_prompt / sandbox)。 |
/prompt |
打印发给模型的完整系统提示词(调试用)。 |
/compact |
手动压缩当前会话历史。 |
exit / quit |
退出。 |
| 其他文本 | 作为用户消息发给助手。 |
~/.bubbles/config.json 是用户配置的唯一文件,它的 schema 决定了产品的可配置面:
| 顶级字段 | 用户面对的产品决定 |
|---|---|
agents |
默认模型 / 温度 / 上下文 / 迭代上限 / 记忆窗口 / 压缩保留预算 / API 重试与超时 / 会话并发上限。 |
channels |
启用哪些渠道、每个渠道的鉴权与白名单、会话归属模式、是否流式回显进度。 |
providers |
每个 provider 的 API key、自定义 base URL、自定义 header。 |
gateway |
网关进程绑定的 host 与 port。 |
tools |
Web 搜索 key、Shell 工具超时与 PATH、执行沙箱后端(sandbox,含每会话独立 HOME 开关)、MCP servers 列表。 |
契约:
- 所有顶级字段同时支持 camelCase 与 snake_case 两种 key 命名。
- 环境变量以
BUBBLES_前缀,嵌套字段以__分隔(如BUBBLES_AGENTS__DEFAULTS__MODEL),可覆盖配置文件。 bubbles onboard在已有配置文件时支持"覆盖默认"或"刷新(保留旧值,补全新字段)"两个分支,绝不静默丢用户配置。
SPEC 与 Schema 的分工:本文档只承诺顶级字段的产品语义、可配置的能力面、以及关键开关的产品行为。具体字段名、类型、嵌套结构、默认值、校验规则由配置 schema(项目里的 pydantic 模型)定义,schema 是配置层的事实源。两者保持一致:本文档新增产品决定 → schema 实现;schema 改了字段名/默认值 → 检查本文档的产品语义是否仍然成立。
新增配置字段的产品检查清单:是否给用户带来可被一句话描述的新能力 / 新选择?是否有合理默认值?是否会破坏旧配置?这三点要在本文档先回答,再写代码。
核心信念:用户的数据在用户机器上;助手是用户雇的私人助理,不是云服务的客户端。
承诺:
- 数据本地:会话历史、人设、记忆、产物、密钥全部存放在
~/.bubbles/,不主动上传到 Bubbles 任何托管服务(项目本身没有这种服务)。 - 明确出站:助手向外发出的网络请求只有三类——LLM provider API、用户配置的渠道平台 API、用户/助手主动调用的 Web 工具。新增任何"默认开启的对外网络行为"是产品级决策,必须改本文档。
- 白名单:所有渠道支持基于发件人/账号的
allow_from白名单。这是个人助理与公开机器人的边界。 - 危险命令防护:Shell 工具拦截一组明显有破坏性的模式(参考
SECURITY.md)。这是底线,不是充分防护——用户对自己的会话目录与 shell 行为负责。 - 路径沙盒:文件工具(
read/write/edit/list)和 Shell 工具的working_dir强制限制在会话工作目录内,绝对路径 /../ symlink 越界一律被拦下。Shell 命令本身的越界尝试(绝对路径、cd ..、变量间接引用、命令替换等)也做 best-effort 拦截。框架承诺到这一步为止——shell 是图灵完备的,静态分析不可能拦下全部 trick(base64 解码、runtime 构造路径等)。需要"绝不可能跨会话访问"的硬隔离,由部署侧或容器 / OS 级沙箱后端承担:每会话独立 OS 用户 /bubblewrap/firejail/sandbox-exec/ 容器边界等,参考SECURITY.md§4.1。 - 凭证隔离(可选,每会话):默认所有会话共享宿主环境(含
$HOME、已登录的 CLI 凭证)。开启local_isolated沙箱后端后,每个会话获得独立的$HOME,会话内 CLI 工具的凭证按会话隔离、彼此不可见,且存放在会话工作目录之外(模型的文件工具读不到)。边界:这只隔离环境变量导向的凭证查找,不隔离整个文件系统;硬隔离仍需容器 / OS 级手段(见SECURITY.md§4.1)。 - 密钥:API key 以明文存储在
~/.bubbles/config.json;配置文件权限收紧到用户私有。生产场景推荐用环境变量或 OS keyring 替代明文(详见SECURITY.md)。 - 审计:助手的每一步工具调用在日志中可见;用户可以用
--logs/--debug追到模型实际收到的系统提示词与每一次工具调用的入参与结果。
当前明确不做的(与未来不做承诺,但当前如此):
- 速率限制(依赖底层 provider 的额度)。
- 自动会话过期。
- 端到端审计日志。
- 多用户隔离(这是个人助理框架,不是多租户系统)。
为避免边界蔓延,以下是 Bubbles 当前明确不做的事情:
- 不做多用户 SaaS。一份
~/.bubbles/服务一个人。 - 不做企业 IM 网关。渠道集成的目的是让"我自己"在 IM 里用助手,不是给团队架起客服或群聊机器人。
- 不做 GUI。CLI 是唯一的官方界面。
- 不做模型训练 / 微调。Bubbles 是模型的消费者。
- 不做向量库 / RAG 平台。如果某个 Skill 需要 RAG,它走自己的 MCP server 或 Skill 内部实现,不进核心。
- 不做插件市场。Skills 与 MCP servers 是用户自己配置的,不存在中心化分发。
- 不与某个特定渠道深度绑定。任何只服务单一渠道的特性都属于 Skill / 工具层面,不进核心。
- 版本:Alpha(< 1.0)。
- 稳定性承诺:本阶段允许破坏性变更,但任何破坏性变更必须先改本文档。
- 公测渠道:项目以 GitHub 开源仓库形式分发,用户自行 clone / pip install。
- 支持平台:macOS / Linux 全功能;Windows 仅在使用 WeChat 渠道时是必需的,其他渠道在 Windows 上能跑但不是主要测试目标。
- 不要把实现搬进本文档。代码是实现的事实源,本文档是产品的事实源。本文档不应该出现 Python 类名、函数名、行号、依赖版本号。
- 新增能力 → 先改本文档。一个用户可见的新能力 = 一段本文档新文字 + 一份 PR 描述对应到这段文字 + 必要时改 README。
- 删除能力 → 先改本文档。删一个渠道 / 删一个工具 / 删一个 provider,先在本文档里删掉对应承诺,再在代码里删。
- 冲突时以本文档为准。如果 README、命令帮助文本、配置 schema 注释、Skills 文档与本文档不一致,请改它们而不是改本文档(除非是本文档错了——那就改本文档)。
- 保持本文档可被一口气读完。当一个章节膨胀到无法被一次读完时,要么压缩,要么拆出独立子文档并在此处保留一段不超过半页的概要。
- 每个产品级变更的 PR 描述必须回答三件事:变更了哪段文字、变更前后的产品承诺差异、用户感知层面是 break / non-break。