9.3 KiB
PicoBot 架构机制
核心数据流
Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionManager → MessageBus → OutboundDispatcher → Channel
↑
ControlChannel → SessionManager (dialog 操作: 创建/切换/归档/删除)
模块职责
| 模块 | 职责 |
|---|---|
gateway |
HTTP/WebSocket 服务器,持有 GatewayState |
client |
TUI 聊天客户端 |
channels |
外部集成(飞书、CLI),仅收发消息 |
bus |
异步消息队列,纯队列不路由 |
session |
会话生命周期管理、dialog 操作 |
agent |
LLM 调用循环、工具执行、上下文压缩、媒体处理、子 Agent |
providers |
LLM API 客户端(OpenAI 兼容、Anthropic) |
tools |
Agent 工具(bash、文件操作、搜索、HTTP、web、browser、memory、delegate 等) |
skills |
Skill 加载、管理和 prompt 构建 |
storage |
SQLite 持久化 |
scheduler |
Cron 作业调度 |
observability |
Observer 模式,agent/工具遥测事件 |
protocol |
WebSocket 协议消息定义 |
config |
配置加载、环境变量替换、路径解析 |
memory |
长期记忆存储与检索 |
mcp |
MCP(Model Context Protocol)工具集成 |
功能边界
- Channels 仅收发消息,不感知 session 或 LLM
- MessageBus 是纯异步队列,不路由
- SessionManager 拥有 session 状态,不直接调 LLM;负责注入 skills prompt
- AgentLoop 无状态,接收 dialog 事件调用 LLM、执行工具
- Providers 是纯 HTTP 客户端,无 bus/session/channel 感知
- Tools 接收原始参数,返回字符串结果
- MCP 工具在 Gateway 初始化时连接服务器、发现工具,并包装成普通 Tool 注册到 ToolRegistry
- 子 Agent 由
delegate工具创建,复用 provider 配置和按需过滤后的工具集;后台任务结果通过 MessageBus 发回原会话
关键约束
- Gateway 启动时切换到 workspace 目录
- SQLite 数据在
{workspace}/picobot.db - ChannelManager 持有 MessageBus 和所有 channel
- OutboundDispatcher 通过 ChannelManager 路由出站消息
- Config
.env加载使用unsafe { env::set_var(...) } browser工具只有在browser.enabled=true时注册,依赖 Chrome/Chromium 与 WebDriver
上下文压缩
当上下文接近 token 限制时触发:
- 快速裁剪:合并连续同角色消息,截断工具输出
- 硬截断:移除过老消息
- 压缩后保留用户消息确保结构完整
Skill 系统
三个优先级(高覆盖低):
{workspace}/skills/— 最高优先级~/.picobot/skills/— 中等优先级~/.agents/skills/— 最低优先级
同名 skill 按优先级覆盖。每个 skill 是包含 SKILL.md 的目录。内置 skill 在 ~/.picobot/skills/ 下不存在时自动从二进制释放安装。
会话系统
会话 ID 格式
统一会话 ID 为三段式:<channel>:<chat_id>:<dialog_id>
| 部分 | 含义 | 示例 |
|---|---|---|
channel |
消息渠道 | cli_chat、feishu |
chat_id |
聊天/群组标识 | sid_abc123 |
dialog_id |
对话标识 | default、d_xxxx(短 ID) |
同一 channel:chat_id 下可有多个 dialog。chat_scope() 返回 "channel:chat_id" 用于分组。
Session 生命周期
create → 存入 Storage → 载入 memory → 设为当前 dialog
↓
get_or_create
↓
← 接收消息、LLM 响应 →
↓
switch → rename → archive → delete(soft)
| 操作 | 效果 |
|---|---|
create |
新建 dialog_id,立即持久化到 SQLite,设为当前 |
get_or_create |
先在内存 HashMap 中找 → 再查 Storage → 都不存在则新建 |
switch_dialog |
切换当前 dialog,目标 session 自动从 Storage 恢复入内存 |
list_dialogs |
列出 channel:chat_id 下最近 10 个 session |
rename |
更新标题,内存 + Storage 同步 |
delete |
软删除(设 deleted_at),从内存移除 |
archive |
当前为空操作 |
SessionManager 数据结构
两层追踪:
sessions:HashMap<String, Arc<Mutex<Session>>>— 所有已加载的 session,key 为完整 session IDcurrent_sessions:HashMap<String, String>— 每个channel:chat_id当前的 session ID
消息到达时 resolve_dialog_id() 按顺序确定接收 session:当前 session → Storage 最近活跃 session → 新建。
消息处理三阶段
阶段 1(持锁):斜杠命令检测 → 用户消息入库 → 提取记忆上下文 → 构建系统提示(skills + memory_context)→ 上下文压缩 → 创建 AgentLoop
阶段 2(无锁):agent.process(history) → LLM 调用 + 工具执行。上下文溢出时自动重新压缩重试
阶段 3(持锁):持久化 agent 响应消息 → 自动生成标题(消息数 ≥ 5 且标题为"新对话"时)
会话恢复
从 Storage 恢复 session 时:
- 若
last_compressed_message_at存在:先加载近 3 条 Timeline 记忆作为[Previous Context],再加载压缩标记后的原始消息 - 若无压缩记录:正常加载全部消息
- 自动修复断链的工具调用(gateway 崩溃中途重启导致)
记忆系统
记忆类别
| 类别 | 用途 | 生命周期 | 检索方式 |
|---|---|---|---|
| Knowledge | 事实、偏好、模式、洞察 | 长期保留,手动删除 | 每轮注入系统提示,关键词匹配 |
| Timeline | 历史会话摘要 | 自动清理(默认 90 天) | timeline_recall 工具按需检索 |
MemoryEntry 字段
| 字段 | 说明 |
|---|---|
id |
UUID |
key |
唯一键,同 key 写入覆盖旧值 |
content |
记忆内容 |
category |
knowledge 或 timeline |
importance |
权重 (0.0–1.0),Timeline 默认为 0.3 |
session_id |
关联会话(可选) |
存储与检索
- 主表
memories+ FTS5 虚拟表memory_fts(key, content)全文索引 - 中文分词使用 jieba-rs 逐词精确匹配,用 OR 连接
- FTS5 无结果时回退到 LIKE 模糊匹配
- 支持 category、session_id、时间范围过滤
工作流程
用户消息到达
→ MemoryManager::recall(content, 5, Knowledge)
返回最多 5 条匹配的知识记忆(按 importance DESC)
→ 格式化为 "- key: content"
→ 注入系统提示的 "记忆上下文" 部分
→ LLM 可见,辅助回答
记忆工具
| 工具 | 写操作 | 说明 |
|---|---|---|
memory_store |
是 | 存储 Knowledge。必填: key, content。可选: importance |
memory_recall |
否 | 搜索 Knowledge。必填: query(空格分隔关键词)。可选: since, until, limit |
timeline_recall |
否 | 搜索 Timeline(压缩后的会话摘要)。必填: query。可选: session_id, since, until |
memory_forget |
是 | 按 key 删除记忆 |
上下文压缩与 Timeline
LLM 对话上下文接近 token 限制 (默认 128K × 70%) 时自动触发压缩:
- 快速裁剪:工具输出 ≥ 2000 字符时截断
- LLM 摘要:最多 3 轮,每轮找连续用户消息对,将中间的 assistant/tool 消息压缩为摘要 → 摘要作为 Timeline 记忆 持久化(importance 0.3)
- 硬截断:若仍超 90%,只保留前 N + 后 N 条消息
压缩后 last_compressed_message_at 标记边界,后续恢复时从标记点加载原始消息,以 Timeline 提供更早的上下文。
关键集成点
| 时机 | 操作 |
|---|---|
| 每次消息处理 | memory_manager.recall() 提取 Knowledge 上下文 |
| 系统提示构建 | MemorySection 渲染记忆指南 + 匹配的记忆 |
| 有压缩历史时 | HistorySection 提示 LLM 使用 timeline_recall |
| 压缩完成后 | 摘要自动存储为 Timeline 记忆 |
| 空闲时 | 可配置自动 consolidation(idle_consolidation_minutes) |
MCP 工具集成
Gateway 初始化时读取 config.mcp.servers:
- 按服务器配置连接
stdio、sse或streamable-http传输 - 调用 MCP
list_tools - 将每个 MCP tool 包装为
McpToolWrapper - 注册到当前 session 的
ToolRegistry
/mcp 斜杠命令会显示 MCP 服务器连接状态和工具列表。
子 Agent / delegate
delegate 工具用于把独立任务交给子 Agent:
| 模式 | 行为 |
|---|---|
inline |
当前轮阻塞等待子 Agent 返回 |
background |
后台运行,完成后通过原 channel/chat 通知 |
parallel |
多个子 Agent 并发执行并聚合结果 |
默认工具集是只读工具:file_read、file_search、content_search、web_fetch、http_request、calculator。调用时可通过 allowed_tools 显式放开其他工具。后台任务会写入 background_tasks 表,默认 24 小时后清理。
当前斜杠命令
| 命令 | 说明 |
|---|---|
/new |
创建新对话 |
/sessions |
列出最近对话 |
/switch <dialog_id> |
切换到指定对话 |
/rename <title> |
重命名当前对话 |
/delete |
删除当前对话 |
/compact |
手动触发上下文压缩 |
/info |
显示当前对话信息 |
/dump |
保存当前对话为 markdown |
/?, /help |
显示帮助 |
/mcp |
显示 MCP 状态 |
/stop |
停止当前任务并清空消息队列 |