把 Obsidian、任务清单和 AI Agent 接成一个项目状态闭环

项目一多,最先混乱的通常不是文件,而是项目状态。

代码在 Git 里,实验结果散在几篇 Markdown 中,会议决定藏在聊天记录里,任务清单还挂着一个两周前写下的截止日期。每一处看起来都有信息,但没人能直接回答:

这个项目现在究竟在哪里,下一步是否仍然有效?

我不想再造一套全能项目管理软件,也不想把所有东西塞进同一张表。最后,我把它拆成几层:

flowchart LR
  O[Obsidian<br/>项目事实与证据]
  T[任务清单<br/>动作、时间与提醒]
  E[代码仓库 / 设备 / 实验环境<br/>实际结果]
  A[AI 助手<br/>定时核对]
  W[微信<br/>向用户确认]
  U[用户回复]

  O --> A
  T --> A
  E --> A
  A -->|发现冲突或未知项| W
  W --> U
  U -->|确认项目状态| O
  U -->|确认时间性行动| T

这套系统的重点不是“让 AI 自动管理项目”,而是让它负责对账、暴露未知项,并把真正需要人决定的部分送到我面前。

先定义三种真相

我给不同系统划定了边界。

层工具保存什么
项目事实层Obsidian目标、范围、阶段、证据、风险、已确认节点
行动层任务清单下一步动作、截止时间、会议、课程、提醒
实现层Git、设备、实验环境当前代码、运行状态、现场结果、真实产物

它们不能互相冒充。

Obsidian 写着“验证完成”,不代表服务真的在线;Git 里有一个提交,也不代表项目状态页已经更新;任务清单上存在截止日期,更不代表这个动作现在仍值得做。

我把这条规则压缩成一句话:

Obsidian 解释状态,任务清单推动行动,真实环境证明实现。

每个项目只强制一个入口

每个活跃项目至少有一个 00-项目总览.md:

00-项目/<项目名>/
└── 00-项目总览.md

不先创建十几个空目录。只有材料真实出现后,项目才逐渐长成:

00-项目/<项目名>/
├── 00-项目总览.md
├── 当前/
├── 参考资料/
├── 附件/
└── 归档/

总览页是项目入口,不是过程材料的堆放处。它至少要回答:

  • 这个项目解决什么问题,哪些内容不在范围内;
  • 当前处于调研、实现、验证、等待、暂停还是完成;
  • 最近得到过哪些有证据的结果;
  • 用户确认过的下一节点是什么;
  • 还有哪些事情不能确定;
  • 当前材料、实验和决策记录从哪里进入。

一个够用的 Frontmatter 可以很简单:

---
title: <项目名>:项目总览
status: 进行中
updated: 2026-08-14
---

详细实验、会议和复盘拆成独立笔记。总览只保留当前结论和链接。

当前/ 里只放还在工作的材料。被新证据替代的方案进入 归档/,不删除,也不继续混在活跃视图里。项目管理中很危险的一类错误,就是早期计划在几个月后重新被读到,然后被误认为当前决策。

“没有下一步”不是低优先级

有了单项目入口后,我在 00-项目/ 根目录增加了一张组合视图:

项目当前阶段最近进展已确认的下一节点仍需确认
项目 A准备第二次验证第一轮结果已复盘周六 15:30 继续是否需要提前准备材料
项目 B需求调研已形成初稿周三完成确认稿何时与对方核对真实流程
项目 C原型准备硬件已到手待确认何时恢复

我最初给它们排过 P0、P1、P2。很快就发现这个模型会吞掉信息。

项目 C 没有下一步,可能不是因为不重要,而是我还没决定。AI 如果看到“没有任务、没有日期、依赖硬件”,然后自动把它降级,实际上是在替我做价值判断。

所以现在只允许两类状态:

  • 用户确认过的节点,写入时间线;
  • 未确认的节点,明确写成“待确认”。

候选技术路线可以保留,但不能伪装成计划。

用定时任务核对两边的状态

项目状态页和任务清单需要定期对账。我用 Hermes Agent 的 Cron 来做这件事。

调度时间可以直接写成标准 Cron 表达式:

0 8,12,17,22 * * *

也就是每天 08:00、12:00、17:00 和 22:00 扫描。

每轮读取:

  1. 跨项目状态总览;
  2. 各项目的 00-项目总览.md;
  3. 必要的“当前导航”;
  4. 上一轮待确认问题;
  5. 任务清单未来 14 天的未完成任务;
  6. 当前时间和时区。

检查逻辑是双向的:

flowchart LR
  O[Obsidian 项目事实] --> R[Reconciliation]
  T[任务清单近期动作] --> R
  R --> C{冲突 / 缺失 / 未知}
  C -->|无变化| S[SILENT]
  C -->|需要确认| Q[生成编号问题]
  Q --> M[微信]
  M --> U[用户回复]
  U --> W[确认后写回]
  W --> O
  W --> T

它寻找的主要异常包括:

  • 两边的日期、时间和完成状态不一致;
  • Obsidian 有明确近期节点,任务清单却没有行动或提醒;
  • 任务清单出现了项目动作,项目总览却毫无记录;
  • 项目没有确认过的下一步,也没有暂停或等待原因;
  • 已经过期的节点仍被写成未来;
  • AI 建议或技术候选被误写成了确定计划。

扫描阶段只有读权限和“写待确认清单”的权限。它不能根据推断修改项目事实,也不能自行创建正式任务。

发现问题后,完整依据进入 Obsidian 的 项目状态待确认.md,手机只收到一条短消息:

项目状态核对:发现 3 项待确认

  1. 项目 A:周六课程是否照常,是否需要提醒?
  2. 项目 B:任务截止是周二还是周三?
  3. 项目 C:近期恢复,还是继续暂停?

没有变化时,Agent 最终只返回:

[SILENT]

Cron 会抑制投递。一天运行四次,不等于一天打扰四次。

微信接入的第一层:/sethome

Hermes 的定时任务可以配置:

deliver: weixin

但 weixin 只是一个平台级别的路由名。系统仍然需要知道这个平台的默认目标会话是谁。

因此,在实际接收通知的微信 DM 中需要执行:

/sethome

它会把当前聊天登记为 Weixin Home Channel。之后 deliver: weixin 才能在运行时解析到正确的微信会话。

这一步在重新绑定账号后尤其重要。重新登录 iLink Bot 解决的是“Gateway 使用哪个微信机器人”,/sethome 解决的是“定时通知发给机器人下的哪个会话”。它们不是同一个配置。

可以把路由过程理解为:

flowchart LR
  C[Cron deliver: weixin] --> H[WEIXIN_HOME_CHANNEL]
  H --> D[执行过 /sethome 的微信 DM]

如果换绑微信后忘记重新 /sethome,可能出现几种状态:

  • Cron 没有可解析的 Home;
  • 通知仍指向旧会话;
  • 手动直发使用了新 context token,但平台级 Home 仍不稳定;
  • 扫描成功,最终投递失败。

所以我的验收顺序是:重新绑定、重启 Gateway、在目标 DM 执行 /sethome,然后再做短消息直发和 Cron 投递测试。

微信接入的第二层:让回复拥有上下文

/sethome 解决投递目标,但还没有解决会话连续性。

Cron 默认在一个隔离会话中运行。它生成问题后可以把结果投递到微信,但微信里的日常 Agent 会话未必拥有这条 Cron 消息的上下文。

问题会在用户用编号简短作答时暴露:对用户而言,问题就在上一条消息里;对系统而言,Cron 投递和日常对话可能属于两个不同的会话状态。于是消息虽然出现在同一个聊天窗口,Agent 却未必知道编号对应什么。

要让 Cron 可继续回复,需要同时满足两个条件:

  1. 任务从用户真正用于回复的微信 DM 中创建,因此保存正确的 origin;
  2. 创建或更新任务时设置 attach_to_session=true。

任务记录应包含类似结构:

origin:
  platform: weixin
  chat_id: <当前微信 DM>
attach_to_session: true
deliver: weixin

三项各有职责:

配置作用
/sethome把当前 DM 设为 Weixin 默认投递目标
origin记录这项自动化从哪个真实对话创建
attach_to_session把 Cron 投递镜像进 origin 会话上下文

deliver 回答“消息发往哪里”;attach_to_session 回答“用户回复时,Agent 是否记得自己刚才问过什么”。

我第一次是在 Desktop 中创建任务,再让它把结果发送到微信。消息确实到了,但任务并不是从这段微信对话创建的,因此没有记录当前微信会话是它的来源。

问题在真正回复时暴露了。Cron 发来五个编号问题,我直接回答:

  1. 是。2. 是。3. 明天可能汇报不了,提醒我重新约时间。

微信里的 Astra 能处理第三项,因为我把事情重新说了一遍;但它不知道前两个“是”对应哪两个问题。对我来说,问题就在上一条消息里。对 Agent 来说,那条消息只是一次外部投递,没有进入它正在使用的对话上下文。

最后我保留原来的时间、检查规则和工具配置,只调整任务的创建位置与会话设置:

  • 暂停 Desktop 创建的旧任务;
  • 在实际接收和回复的微信 DM 中重新创建;
  • 确认新任务记录了当前微信 origin;
  • 开启 attach_to_session: true;
  • 再用“1. 是;2. 否”测试 Agent 能否还原原问题。

所以,消息出现在同一个聊天窗口里,并不自动意味着 Agent 拥有同一段上下文。投递目标和回复上下文需要分别配置。

任务成功和通知成功也不是一回事

Cron 的 last_status: ok 只说明 Agent 完成了扫描和输出,不说明微信收到了消息。

至少还要检查:

last_run_at
last_status
last_delivery_error

以及:

  • 对应运行的本地输出文件是否存在;
  • Gateway 日志里平台发送是否成功;
  • 当前消息是否投递到了最新 Home Channel。

我遇到过 iLink 返回频率限制:扫描结果已经生成,本地文件也存在,但最后一步发送失败。

如果防重复只看“问题是否生成过”,就会出现一个漏洞:首次投递失败,下一轮却认为“已经问过”,之后一直保持静默。

因此我的规则是:

  • 上次成功送达,问题不变:静默;
  • 上次投递失败:下一轮允许补发;
  • 已经送达但没有回复:只在事实变化、截止进入 24 小时内,或超过约定时间后再提醒。

运行状态和投递状态必须分别建模。

不要把 Cron 运行档案发到手机

Hermes 会保存 Cron 运行产物。这类文件可能包含:

  • 加载的 Skill;
  • 完整 Prompt;
  • 运行上下文;
  • 调试信息;
  • 最终 Response。

它是审计文件,不是用户消息。

我曾经为了补发结果,直接执行了类似操作:

hermes send --to weixin --file <cron-output.md>

于是四万多字的运行档案被微信拆成多条消息,真正有用的五个问题埋在中间。

现在的输出规则是:

  • 手机只收到最终 Response;
  • 总长度尽量控制在 1000 个中文字符内;
  • 最多五个编号问题;
  • 完整证据只写进 Obsidian;
  • 补发时提取最终结果,绝不发送整个运行产物;
  • Agent 不在 Prompt 内调用发送工具,由 Cron 统一投递一次。

手机适合接收简短问题,不适合承载完整运行日志。

用户回复之后才允许写回

用户可以回复:

项目核对:1. 照常;2. 改到周三;3. 暂停到设备到手。

因为本轮 Cron 已挂载到微信会话,Agent 能将编号映射回问题,然后执行:

  1. 读取本轮 Cron 提问;
  2. 读取 Obsidian 中的完整依据;
  3. 更新对应项目的 00-项目总览.md;
  4. 更新跨项目状态总览;
  5. 对有明确时间的动作创建或更新任务;
  6. 纯状态变化不强行创建待办;
  7. 清除已解决的待确认项;
  8. 下一轮不再重复询问。

如果用户顺便提到“明晚聚餐”,Agent 应识别它是新的时间性事项,但不能未经确认直接猜成 18:30。自动化的目标是减少交互成本,不是把猜测落成日历事实。

Obsidian Sync 负责传输,不负责信息架构

为了让服务器 Agent 和本地 Obsidian 读写同一个 Vault,我使用官方 Obsidian Sync:

flowchart LR
  L[电脑 / 手机 Obsidian] <-->|Obsidian Sync| R[远端 Vault]
  R <-->|Headless continuous sync| S[服务器 Vault 副本]
  S <--> A[AI Agent]

服务器使用 Headless 客户端持续双向同步 Markdown 和附件,但不启用 .obsidian 配置同步。

这样做是为了避免:

  • 桌面主题和社区插件污染服务器;
  • 工作区状态在设备之间互相覆盖;
  • 服务器生成无意义的 UI 配置;
  • 插件缓存进入协作数据层。

同步服务也需要验证。文件在服务器上出现,只能证明本地已经写入。要确认它进入 Obsidian Sync,应查看 Headless 客户端输出或 systemd Journal 中写入后的:

Upload complete
Fully synced

Sync、Git、发布和备份分成四套机制

这四件事经常被说成同一件事,实际上恢复保证完全不同:

机制解决的问题不能保证什么
Obsidian Sync多设备与服务器保持同一 Vault误删后的独立恢复点
Git已提交内容的版本历史未提交文件、账号与运行配置
Quartz / 静态站将选定 Markdown 发布为网站私有 Vault 的完整保存
独立备份在同步系统之外恢复数据实时多设备协作

我只允许 60-发布/ 进入公开链路,每篇文章还要显式设置:

publish: true

这里有两道限制:文件必须位于公开目录,并且明确选择发布。项目总览、私密附件和协作记录不会因为存在于同一 Vault 就自动进入网站。

最后留下四条规则

这套系统最后可以归结为四条规则:

  1. 状态分工:Obsidian、任务清单和真实环境分别保存什么;
  2. 证据边界:结论来自哪里,哪些信息仍未知;
  3. 会话连续:Cron 的问题怎样进入用户实际回复的会话;
  4. 确认后写回:只有用户确认后,Agent 才修改项目状态和任务。

端到端验收也很明确:

  • Cron 能读到最新项目和任务;
  • 没有变化时保持静默;
  • 有问题时微信只收到一条摘要;
  • /sethome 指向当前接收会话;
  • 任务保存正确的 origin;
  • attach_to_session=true 已实际写入任务配置;
  • 用户只回复编号,Agent 仍能准确还原问题;
  • 确认结果被写回正确的项目与任务;
  • 下一轮不再重复询问。

我不需要 AI 替我决定项目优先级。它更适合做一件朴素但高频的事:持续扫描状态漂移,把冲突和未知项压缩成几个可以回答的问题。

答案仍然由人给出。系统负责让答案不再丢失。