Coding Agent 这类工具用久了,会撞上一个很隐蔽的痛点:它记不住事。你让它改一个模块,它改到一半上下文被截断,前面梳理好的待办清单全丢了;你让它跑一个多步骤的重构,它做完第一步就忘了后面还有三步。我一开始靠手动维护一个TODO.md来兜底,结果发现真正麻烦的不是"记",而是"记了之后谁来盯、谁来更新、谁来提醒"。于是我干脆做了个桌面看板,让 Agent 自己往里面写待办、自己更新状态,再配一只像素桌宠在旁边晃悠,状态一变它就换个表情。整套东西基于 MCP 协议对接 Coding Agent,数据落在本地 SQLite 里,开源可跑。这篇就把我从"为什么要做"到"踩了哪些坑"的完整链路摊开讲,适合正在用 Coding Agent 做多步任务、又苦于任务状态管理的人参考。
1. 为什么 Coding Agent 需要一个外部待办看板
1.1 上下文窗口不是记忆,是草稿纸
很多人对 Coding Agent 有个误解,觉得它"记得住"整个项目。实际上它的记忆边界就是当前上下文窗口,一旦对话轮次变多、文件读取变多,早期内容会被压缩甚至丢弃。我实测过一个中等规模的重构任务,Agent 在第 8 轮之后就开始重复问我已经确认过的接口命名,第 12 轮直接把之前定好的迁移顺序搞反了。
这不是模型能力问题,是机制问题。上下文窗口更像草稿纸,写满了就得擦掉重写。你没法要求草稿纸拥有长期记忆,但你可以给它配一个笔记本——这就是外部待办看板存在的根本理由。待办清单一旦落到 Agent 上下文之外,它就不再受窗口大小限制,任务拆解、进度、依赖关系都能稳定保存。
1.2 待办状态天然适合"外置"
我对比过几种方案。第一种是让 Agent 自己在回复里维护一个 Markdown 清单,问题是每次都要重新输出整个清单,token 消耗大,而且一旦某轮忘了输出,状态就断了。第二种是写进项目里的TODO.md,好处是持久,坏处是 Agent 得主动去读写文件,容易漏,而且人看起来不直观。
第三种就是外置成一个独立服务,Agent 通过工具调用去增删改查。这个方案的关键优势在于:状态和 Agent 的对话解耦了。Agent 这轮记不记得不重要,因为状态存在数据库里,下一轮它只要查一下就能恢复。这就像团队协作里,任务不放在某个人脑子里,而是放在看板上,谁都能看、谁都能更新。
1.3 看板比纯文本清单多出来的价值
纯文本清单只能表达"有这些事",看板能表达"这些事处于什么阶段"。我设计的看板有三列:待办、进行中、已完成。Agent 开始做某件事时把卡片移到进行中,做完移到已完成。这个状态流转看起来简单,但它解决了一个很实际的问题——当 Agent 中途被打断,你一眼就能看出它卡在哪一步,而不是从头读一遍对话记录去猜。
另外看板天然支持"人机共看"。Agent 在写,你在看,桌宠在旁边用表情反映当前状态。这种可视化的即时反馈,比翻日志高效太多。
2. 用 MCP 把看板接进 Coding Agent 的完整思路
2.1 MCP 到底解决了什么对接问题
MCP 全称 Model Context Protocol,你可以把它理解成 Agent 和外部工具之间的"标准插座"。在没有 MCP 之前,每接一个工具都要为特定 Agent 写一套适配代码,换个 Agent 就得重写。MCP 把这层抽象出来了:工具方实现一个 MCP Server,暴露若干工具方法;Agent 方只要支持 MCP,就能直接调用。
我选 MCP 而不是自己写 HTTP 接口,核心原因就是复用性。看板服务实现一次 MCP Server,理论上任何支持 MCP 的 Coding Agent 都能接。这比给每个 Agent 单独写插件省事得多,也符合开源项目"一次实现、多方可用"的定位。
2.2 看板需要暴露哪些工具方法
工具方法的设计直接决定 Agent 用起来顺不顺手。我一开始设计得太细,暴露了十几个方法,结果 Agent 经常选错。后来收敛成五个核心方法,覆盖 95% 的场景:
| 工具方法 | 作用 | 关键参数 |
|---|---|---|
add_task | 新增一张待办卡片 | 标题、描述、优先级 |
list_tasks | 查询当前所有卡片 | 状态过滤(可选) |
update_task | 更新卡片内容或状态 | 卡片 ID、新状态 |
move_task | 专门用于状态流转 | 卡片 ID、目标列 |
delete_task | 删除卡片 | 卡片 ID |
把move_task从update_task里拆出来,是我踩坑之后的决定。因为状态流转是最高频操作,单独一个语义明确的方法,Agent 调用准确率明显更高。这属于典型的"接口语义影响调用质量"的案例。
2.3 工具描述怎么写才不会被 Agent 忽略
MCP 工具的 description 字段不是写给人看的,是写给 Agent 看的。我最初写得很随意,比如add_task: 添加任务,结果 Agent 经常在应该更新状态的时候去调add_task,导致卡片重复。
后来我把描述改成了带场景约束的写法,比如move_task: 当某个任务从待办进入进行中,或从进行中变为已完成时调用此方法,不要用 add_task 重复创建。改完之后误调用率下降非常明显。这个经验很通用:给 Agent 写工具描述,要像给新人写操作手册,把"什么时候用"和"什么时候别用"都写清楚。
3. 数据落地的选择:为什么是 SQLite 而不是 JSON 文件
3.1 本地优先与零依赖的权衡
看板是桌面应用,用户不会为了记个待办去装数据库服务。SQLite 的最大优势就是零配置、单文件、进程内运行。整个看板的数据就是一个.db文件,拷走就能迁移,删掉就等于重置。这对开源项目特别友好,用户 clone 下来直接跑,不需要任何额外环境准备。
我对比过 JSON 文件方案。JSON 在数据量小的时候确实简单,但一旦涉及并发写入就麻烦——Agent 在写、UI 在刷新、桌宠在读状态,三个进程同时操作一个文件,很容易出现写覆盖。SQLite 自带锁机制和事务,这类并发问题它帮你兜住了。
3.2 表结构设计:够用就好
我没有设计复杂的多表关联,就一张主表加一张操作日志表。主表存卡片当前状态,日志表记录每次状态变更,方便回溯。
CREATE TABLE tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, status TEXT NOT NULL DEFAULT 'todo', priority INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE task_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, from_status TEXT, to_status TEXT, changed_at DATETIME DEFAULT CURRENT_TIMESTAMP );status字段我用文本而不是枚举数字,原因是调试时直接看数据库一目了然,不用查映射表。性能上这点差异可以忽略,可读性收益更大。
3.3 十万条数据下的查询表现
有人会担心 SQLite 撑不住。我实测过,往tasks表塞十万条记录,带status索引的查询基本在毫秒级返回。看板这种场景,卡片数量通常就几十到几百,离性能瓶颈差得远。真正需要注意的是给status和updated_at建索引,因为看板刷新时经常按状态过滤、按更新时间排序。
CREATE INDEX idx_tasks_status ON tasks(status); CREATE INDEX idx_tasks_updated ON tasks(updated_at DESC);提示:SQLite 默认的
journal_mode是 delete,桌面应用建议改成 WAL 模式,读写并发表现会好很多。执行PRAGMA journal_mode=WAL;即可,这个设置是持久化的。
4. 像素桌宠:让状态变化"看得见"
4.1 桌宠不是装饰,是状态指示器
很多人以为桌宠就是个卖萌的装饰,我一开始也这么想。但做完之后发现,它其实承担了一个很实际的功能:把数据库里的状态变化翻译成即时可见的视觉反馈。Agent 新增一张卡片,桌宠冒个气泡;卡片进入进行中,桌宠开始"忙碌"动画;全部完成,桌宠躺平休息。
这种反馈的价值在于,你不需要一直盯着看板窗口。桌宠在屏幕角落晃,你余光扫一眼就知道 Agent 现在是在干活还是卡住了。这比弹通知温和,比看日志直观。
4.2 像素动画的实现取舍
桌宠我用的是逐帧像素动画,每套动作就是一组 PNG 序列帧。选像素风而不是矢量动画,原因很实际:像素帧资源小、渲染简单、跨平台一致性好。矢量动画在不同 DPI 下容易出现模糊或错位,像素风反而因为本身就是"块状"的,缩放时用最近邻插值就能保持锐利。
状态和动画的映射我做成了一张配置表,方便后续扩展:
| 看板状态 | 桌宠动作 | 触发条件 |
|---|---|---|
| 空闲 | 待机呼吸 | 无进行中任务 |
| 忙碌 | 敲键盘 | 存在进行中任务 |
| 新增 | 冒气泡 | 检测到新卡片 |
| 完成 | 举手庆祝 | 所有任务完成 |
| 异常 | 冒问号 | 长时间无状态更新 |
最后那条"异常"状态是我后来加的,因为遇到过 Agent 卡死但看板没变化的情况。加了个超时检测,超过设定时间没有状态更新,桌宠就冒问号提醒你去看一眼。
4.3 桌宠与看板的数据同步方式
桌宠和看板是两个独立进程,它们之间怎么同步状态是个关键设计点。我试过两种方案。第一种是桌宠直接读 SQLite,简单直接,但轮询有延迟,而且频繁读库不优雅。第二种是看板进程在状态变更时通过本地 IPC 广播事件,桌宠订阅事件。
最终我选了第二种为主、第一种兜底。正常情况走事件推送,实时性好;如果事件通道断了,桌宠降级为定时轮询数据库,保证不会完全失联。这种"主通道加降级通道"的设计,在桌面应用里很实用,因为本地 IPC 偶尔会因为进程重启而断开。
5. 从零跑通这套看板的实操步骤
5.1 环境准备里最容易忽略的一步
整个项目依赖不算多,主要是运行环境和 SQLite 驱动。但有一个坑我必须提前说:SQLite 的版本。不同系统自带的 SQLite 版本差异很大,WAL 模式和某些 SQL 语法在旧版本上行为不一致。我建议在项目里显式检查版本,低于 3.35 的直接提示用户升级。
# 检查当前 SQLite 版本 sqlite3 --version # Linux 下如果版本过低,可以装一个较新的 sudo apt install sqlite3 libsqlite3-devWindows 用户如果不想折腾命令行,用 DB Browser for SQLite 这类图形工具查看.db文件很方便,但注意它只是查看器,不影响程序运行。
5.2 启动 MCP Server 并接入 Agent
看板服务启动后,会以 MCP Server 的形式监听。接入 Agent 时,需要在 Agent 的配置里声明这个 Server 的启动命令。不同 Agent 的配置格式不一样,但核心信息就三样:启动命令、参数、工作目录。
{ "mcpServers": { "task-board": { "command": "node", "args": ["/path/to/board-server/index.js"], "env": { "DB_PATH": "/path/to/tasks.db" } } } }配置完之后,重启 Agent,让它重新加载 MCP Server 列表。如果 Agent 支持查看已连接工具,确认add_task、move_task这些方法都出现了,就说明接上了。
注意:如果 Agent 提示找不到 MCP 工具,九成是启动命令的路径写错了,或者工作目录不对导致相对路径失效。先把命令单独在终端跑一遍,确认能启动再填进配置。
5.3 验证 Agent 是否真的会用看板
接上不等于会用。我建议做一次最小验证:给 Agent 一个明确的多步任务,比如"把项目里三个文件的日志格式统一",然后观察它是否主动调用add_task拆出三条待办,做完一条是否调用move_task更新状态。
如果它不主动用,通常是工具描述不够明确,或者系统提示里没有引导它使用。我后来在 Agent 的系统提示里加了一句"处理多步任务时,请先使用 task-board 工具拆解待办",主动使用率就上来了。这属于"提示工程"和"工具设计"配合的典型场景。
6. 实际使用中踩过的坑与排查链路
6.1 卡片重复创建:从现象到根因
现象是看板里出现大量标题几乎一样的卡片。我一开始以为是 Agent 抽风,后来抓了调用日志才发现,Agent 在每一轮对话里都会重新"拆解任务",而它拆解出来的待办和上一轮高度重合,于是每次都调add_task。
根因是 Agent 没有先查询已有待办的习惯。修复分两步:一是把list_tasks的描述改成"在新增任务前应先调用此方法查看是否已存在",二是在add_task内部做去重——标题相同且状态为待办的,直接返回已存在的卡片 ID 而不是新建。双保险之后,重复问题基本消失。
6.2 状态卡在进行中不动了
这个坑更隐蔽。Agent 做完任务后没有调用move_task,卡片一直停在进行中。排查下来发现,Agent 在任务完成的那一轮,上下文里已经积累了大量文件内容,它"忘了"还有更新状态这一步。
我的解法是在工具层面加一个"完成信号"约定:当 Agent 输出里包含明确的完成语义时,看板服务主动询问一次是否需要更新状态。这有点像给 Agent 加了个提醒器。另外桌宠的超时冒问号也帮了大忙,它让我第一时间发现卡住,而不是等到晚上才发现任务没推进。
6.3 数据库被锁:并发写入的真实教训
有一次看板 UI 突然卡死,日志里全是database is locked。原因是桌宠的轮询读和 Agent 的写入撞上了,而当时还没开 WAL 模式。开了 WAL 之后,读和写可以并行,这个问题就再没出现过。
这个坑让我意识到,桌面应用虽然用户少,但多进程访问同一个数据库是常态,不能按单进程思路设计。WAL 模式加上合理的 busy_timeout 设置,基本能覆盖这类场景。
PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;busy_timeout设成 5000 毫秒,意思是遇到锁时最多等 5 秒再报错,给并发操作留出缓冲。
6.4 桌宠动画和状态不同步
偶尔会出现看板已经显示完成,桌宠还在敲键盘。排查发现是事件广播丢了。本地 IPC 在进程重启的瞬间,订阅关系会断,而重连需要时间。我的处理是让桌宠在收到事件后,额外做一次轻量校验——读一下当前状态,如果和事件里的不一致,以数据库为准。这样即使丢事件,也能在下一轮自愈。
7. 这套方案还能往哪些方向长
7.1 多 Agent 协作下的任务分配
现在这套看板是单 Agent 场景。如果同时跑多个 Coding Agent,看板可以升级成任务池:每个 Agent 从待办列认领任务,认领时加锁,避免两个 Agent 抢同一张卡片。SQLite 的事务机制正好能支撑这种"认领即锁定"的操作,把卡片状态从待办改成进行中的那一步,用事务包起来就行。
7.2 任务依赖与阻塞关系
目前卡片之间是平级的。实际项目里任务常有依赖,比如"改接口"必须在"改调用方"之前完成。可以在表里加一个depends_on字段,存依赖的卡片 ID。看板渲染时把被阻塞的卡片标灰,Agent 查询待办时也跳过被阻塞的,这样任务顺序就有了约束。
7.3 把操作日志变成复盘素材
task_logs表现在只是记录状态变更。如果把它和 Agent 的对话轮次关联起来,就能复盘出"哪类任务最容易卡住""Agent 在什么阶段最常忘记更新状态"。这些数据对优化工具描述、调整系统提示都很有价值。我打算下一步给日志表加一个session_id字段,把每次 Agent 会话和任务变更串起来。
7.4 桌宠作为通用状态面板
桌宠现在只反映看板状态。但它的本质是一个"轻量状态显示器",完全可以接更多数据源,比如构建状态、测试通过率、代码检查结果。只要把数据源抽象成统一的事件格式,桌宠就能变成一个通用的桌面状态面板。这个扩展方向我觉得挺有意思,因为它把"卖萌"和"实用"真正结合起来了。
最后分享一个我在调试这套东西时的小习惯:每次改完 MCP 工具描述,我都会用同一个多步任务跑一遍,对比 Agent 的调用序列有没有变好。工具描述这种"软配置"的改动效果,只能靠固定用例反复验证,凭感觉改很容易越改越乱。这套看板从最初的手动TODO.md到现在能自己跑,中间返工最多的就是工具描述,但每次返工都让 Agent 的表现更稳一点,这个投入是值得的。