Skip to content
//0x
0x1d//工具设计

从 Token 注入到自管理执行器:设计一个跨 Agent 的 Todoist Skill

起点:让 Agent 记住对话里真正需要做的事

我想做一个 Todoist Skill。

它不只是响应“把这件事加入 Todoist”这样的明确命令,还要能在对话过程中识别用户已经作出的承诺:

如果承诺足够明确,Agent 可以低干扰地创建待办;如果存在关键歧义,再问一个简短的问题。

这个想法看似简单:判断一句话是不是任务,然后调用 Todoist API。真正开始实现后才发现,API 调用反而是最容易的部分。困难集中在三个边界上:

  1. 谁负责管理凭证?
  2. 如何让同一个 Skill 在不同 Agent 客户端中运行?
  3. Agent 可以修改哪些已经创建的任务?

第一版错误:让调用方把 Token 交给 Skill

最初的设计是由 Agent 读取 Token,再通过受保护的通道把它传给 Skill。

这个方案表面上很干净:

但它隐藏了一个严重耦合:每个 Agent 客户端都必须提供相同的安全注入能力。

在一个客户端中,这套调用能够工作;换到另一个客户端后,Agent 按照 Skill 的安全规则拒绝继续:

后来在明确要求下,第二个客户端也完成了真实任务创建,但这不代表原设计是通用的。它只能证明:在人工介入足够多时,这条路径可以被打通。

这次失败让我重新定义了“公共 Skill”的含义:

公共 Skill 不能只让决策规则跨客户端复用,执行和认证契约也必须跨客户端成立。

重新划分责任:凭证属于执行器

最终采用的结构是:

对话
  ↓
Agent 识别承诺
  ↓
Skill 决定是否创建任务,并构造非敏感字段
  ↓
随 Skill 发布的本地执行器
  ├── 自行发现并校验用户凭证
  └── 调用 Todoist API

各层职责被明确分开:

组件负责什么不负责什么
Agent理解当前对话读取或传递 Token
Skill判断任务、生成字段、限制操作边界保存凭证、直接拼接 API 请求
执行器读取凭证、校验参数、调用 Todoist判断一句话是否构成承诺
用户配置一次性保存个人 Token每次会话重新输入 Token

这里有一个容易混淆的细节:并不是让 SKILL.md 自己读取凭证,而是让随 Skill 一起分发的可执行程序负责凭证。

Skill 是决策与行为规范,执行器才是认证边界。

凭证应该由用户配置一次

Todoist 的个人 API Token 属于每个用户自己的账户。公共 Skill 不应该附带统一凭证,也不应该关心使用者具体是谁。

因此,用户只需要在操作系统的私人配置目录中保存一次 Token。之后,Codex、OpenCode 或其他兼容客户端调用同一个执行器时,都不需要再次输入。

执行器在自己的进程内完成以下工作:

Agent 只会执行类似这样的调用:

node <skill-dir>/scripts/todoist-task.mjs \
  task add \
  --scope "工作" \
  --scope "项目A" \
  --content "复查同步异常" \
  --json

调用方传递的只有任务字段,没有凭证。

需要承认的是:个人 API Token 在服务端仍然可能拥有较广权限。执行器只开放少量命令,可以减少 Agent 的误操作面,但不能把一个广权限 Token 变成真正的服务端细粒度授权。

这是程序边界,不是 OAuth Scope。

“自动捕获”不等于“看到动作就创建任务”

凭证问题解决后,下一步是控制 Agent 的主动性。

最终采用三级置信度:

置信度行为
高自动创建,不中断主要对话
中询问一个影响任务成立的关键问题
低静默跳过

自动创建必须同时满足几个条件:

  1. 行动由用户本人负责;
  2. 事情尚未完成;
  3. 能写出具体、可执行的任务标题;
  4. 表达的是承诺、义务、提醒或后续动作;
  5. 不需要猜测会改变任务含义的重要事实。

以下内容不会被自动捕获:

尤其是最后一点很重要。

用户说“帮我修复这个测试”时,Agent 应该去修复测试,而不是顺手创建一条“修复测试”的用户待办。否则 Todoist 很快会变成 Agent 工作日志,而不是用户的行动系统。

用可读路径解决多项目归属

公共 Skill 不能假设每个人都有相同的 Todoist 项目结构,也不一定有权限读取项目列表。

但如果所有任务都直接进入 Inbox,多项目协作时很快会失去归属信息。

最后采用了一种可见的标题路径:

私人 / 预约牙医
工作 / 提交工时表
工作 / 项目A / 复查同步异常
待归类 / 续费域名

分隔符固定为 /:斜线左右各一个 ASCII 空格。

这个格式同时兼顾了两件事:

路径按“宽范围 → 具体项目 → 行动”排列,最多使用两级归属。它只是标题分类,不会假装已经把任务放进某个 Todoist Project。

这是一种很轻量的命名空间:即使任务仍在 Inbox,也不会失去来源。

修改任务比创建任务更危险

创建接口跑通后,一个自然问题是:既然已经能创建任务,能不能修改标题或完成状态?

技术上当然可以。但如果 Skill 允许 Agent 根据标题搜索历史任务再修改,就会出现新的风险:

最终的权限边界是:

只允许修改当前会话中由该 Skill 成功创建,并且已经拿到稳定 ID 的任务。

当前会话维护一个临时注册表:

任务 ID + 当前完整标题 + 完成状态

这个注册表有严格限制:

即使任务在注册表中,修改也必须由用户明确提出。隐式捕获只授权创建新任务,不授权编辑现有数据。

执行器目前只开放两种已有任务操作:

删除、恢复、移动、搜索、批量修改以及日期、描述、优先级和项目位置调整都不在接口范围内。

这不是因为 Todoist API 做不到,而是因为 Skill 没有足够可靠的上下文去安全地做。

跨客户端验证揭示的真正问题

最终验证分为两层:

  1. 使用结构校验器和适配器测试确认 Skill 与执行器本身有效;
  2. 分别在两个 Agent 客户端中创建真实 Todoist 任务,再到网页端确认结果。

第一次跨客户端测试失败,暴露的是认证责任错位;第二次测试成功,才证明两个客户端使用的是同一条自管理执行路径。

这也让我意识到,“兼容多个 Agent”不能只看 SKILL.md 是否能被发现。真正的兼容性至少包括:

只共享一份提示词,不等于共享一项能力。

名称也是公共接口

Skill 最初使用了一个较长的名字,能准确描述功能,但不利于用户记忆和显式触发。

后来名称收敛为 todoist-capture。

重命名不只是改目录名,还涉及:

新的执行器优先读取新位置,但在新凭证不存在时,可以安全回退到旧位置;如果新位置已经存在但权限或格式有问题,则直接报错,不通过回退掩盖配置错误。

这类细节决定了重命名是平滑迁移,还是一次破坏性升级。

最终得到的五条原则

回看这次迭代,我认为最值得复用的是五条原则:

  1. 把决策与执行分开。 Skill 判断该不该做,执行器负责可靠地做。
  2. 让执行器拥有认证责任。 不要要求不同 Agent 客户端分别实现秘密转运协议。
  3. 公共 Skill 是完整的调用契约。 提示词、执行器、凭证发现、错误语义和安全边界缺一不可。
  4. 用可见命名空间保留上下文。 范围 / 项目 / 行动 同时服务人类阅读和脚本处理。
  5. 修改权限必须依赖来源证明。 知道任务 ID 不等于有权修改它;会话内的创建结果才是可信来源。

结尾

一开始,我以为这是一个“调用 Todoist API”的小工具。真正做完后,它更像一次 Agent 权限模型的微型实验。

最有价值的设计并不是让 Agent 能创建更多任务,而是明确回答:

一个好的 Agent Skill,不只是教模型如何完成动作。

它还要告诉模型:什么时候不该行动,以及它的权限究竟到哪里结束。


Share this post on:

Previous Post
把文档库改造成 AI Agent 的项目地图:让上下文探索成本降 80%
Next Post
从文档到实战:把一次手动发布做成可复用的 Agent Skill