---
name: task-handle
description: 话题 ↔ 飞书任务句柄 —— 主路径:用户建任务发到群里 @ 你,你认领并用任务评论维护它(v4 任务派单);辅路径:用户把话题转成任务后你静默认领(转任务)。
---

# 任务句柄(Task Handle)

> 权威设计:仓库 `docs/task-handle.md`。本 SKILL 只讲**你(agent)在 turn 内要做什么**。
> 有两条路径,先看 §0 判断本话题走哪条 —— 规则完全不同,不要混用。

## 0. 先分流:任务派单(主路径) vs 话题转任务(辅路径)

- prompt 里有 **`<task-root>` 块**(本话题根消息是一条飞书任务分享)→ **任务派单主路径**,按 §0.1 处理,
  **忽略** `<task-handle>` 块的候选匹配流程(派单的认领目标是确定的,不需要匹配)。
- prompt 里只有 **`<task-handle>` 块** → 辅路径(话题转任务),按 §1–§6 处理。
- 两个块都没有 → 本话题没有任务句柄相关的事,跳过本 SKILL。

## 0.1 任务派单主路径(v4/v4.1,docs/task-handle.md §15)

用户的操作是:建了一条飞书任务 →「发送任务到会话」发进群 → 在这条任务卡片的话题里 @ 你。
`<task-root>` 块里有 `task_guid`、`task_summary`、(通常还有)`topic_link` 和 `task_root_claimed`。

**认领(v4.2 起由 bridge 自动完成)**:主路径的绑定是机械确定的(话题根=任务卡片,guid 唯一),
所以 **bridge 在你跑起来之前就已经自动认领了**——`<task-root>` 块里 `task_root_claimed: yes` +
「bridge 本轮已自动认领」字样时,你只需要:
1. **双 @ 竞态护栏**:先读一遍该任务的现有评论(`lark-cli task comments list`)——已有**其他 agent**
   的认领声明 → 同话题的另一个 bot 已接手,评论区不再重复发认领评论(重复评论 = 骚扰),正常配合即可。
2. 发一条**认领评论**(`lark-cli task comments create --resource-type task --resource-id <task_guid> ...`):
   一句认领声明 + `topic_link`(用户从任务中心点它直达本话题——这是任务→话题唯一的回跳,别省略)。
   例:`已认领,工作话题:<topic_link>`。
3. 然后正常干活(任务标题/描述就是需求;信息太薄就在话题里问,或按 §0.1 里程碑规则发"等拍板"评论)。

**兜底(`task_root_claimed: no` 时——bridge 认领失败,如该任务已被别的话题认领)**:按 prompt 里的
指示走旧认领流程(先查评论区竞态护栏 → 把 `task_guid` 写进 state.json 的 `task_handle.guid` →
发认领评论),或按实际情况在话题里说明冲突。

**维护(全程只用任务评论——v4.1 铁律)**:
- 只在**交付 / 失败 / 等拍板**三类里程碑节点发任务评论(每条评论都会推送给任务创建者,滥发=骚扰;
  过程性进展写在话题回复里即可,不发评论)。
- **交付的那一轮**:state.json 的 `task_handle` 写 `done: true`,并发一条「已交付,看过请点完成」评论
  (附产出链接:MR/文档/结论)。`done: true` 不会让 bridge 勾完成——它只是让 bridge 停止对这个任务的
  停滞巡检;**完成永远由人在任务中心点**,这是产品语义,不是权限妥协。
- **绝对不要**:修改任务描述、勾完成、reopen、改标题——一律不做(多数情况下你也没有权限:任务只是
  被分享进群,你只有读+评论;即使恰好有权限也不做,见 docs/task-handle.md §15.6)。
- 用户在任务评论区留言会作为新的一轮转给你(无需 @),正常响应即可。
- 被 `[停滞提醒]` 唤醒时按 §7 处理(对派单任务同样适用)。

**权限异常**:读任务/发评论遇到 403 → 在话题里回一句「这个任务我读不到,请确认它已分享到本群」,
不要重试轰炸,下一轮自愈。

---

以下 §1–§6 是**辅路径(话题转任务)**的规则;§7(停滞唤醒)两条路径通用。

> 辅路径下,bridge 侧的机械回写(完成时勾选、失败时评论、reopen)已经自动发生,你不需要、也不应该自己去调这些"收尾"动作 ——
> 你只需要做**认领**、**声明是否真正完成(`done`)**和**里程碑评论/描述更新**这几件事。

## 前提:这个 bot 有没有可用的共享清单

每轮 prompt 事实里如果有 `<task-handle>` 块,说明这个 bot 已经有一个可用的
「Agent Team」共享清单(owner 的一整组 bot 共用同一个,不管来自哪个群、哪个 bot 被 @)。
块有两种形态,取决于本话题是否已经认领:

**已认领**(维护态,不需要你再做匹配):

```
task_handle_tasklist_guid: <guid>
task_handle_claimed: yes
本话题已认领任务句柄;bridge 已自动维护完成/失败/reopen,按 task-handle skill 处理里程碑评论与 done 声明即可。
```

**未认领,且清单里有候选**(v3:bridge 已经帮你把候选筛好,直接看这里,不用你再去查):

```
task_handle_tasklist_guid: <guid>
task_handle_claimed: no
以下是该共享清单里当前未被任何 agent 认领的候选任务(bridge 只做结构性筛选,不做匹配判断):
- guid=<guid1> | summary=<标题1>
- guid=<guid2> | summary=<标题2> | description: <描述摘要>
仅当其中一个候选与本话题高置信对应……才静默把该 guid 写入 state.json 的 task_handle.guid;歧义就什么都不做,
不要为了消歧义去调用 lark-cli 列清单——候选已经在这里,判断不到就是这一轮没有匹配。
```

**没有这个块** = 以下两种情况之一,处理方式相同(跳过本 SKILL 全部内容,不用区分):
- 这个 bot 暂时没有可用的清单(owner 还没授权 task scope、或清单尚未 provision);
- 有清单,但本话题未认领**且**清单里目前没有(结构性筛选后)剩下的候选任务——本轮没有可认领的东西。

`task_handle_claimed` 和候选列表都是 bridge 注入的**事实**,不是流程指令 ——
你据此决定要不要在本轮**顺带**做一次静默认领(见下面 §1),bridge 本身不做任何匹配判断,
也**不会**自己去调用 `lark-cli task tasklists tasks` 帮你查——候选已经在 prompt 里了。

## 1. 何时认领:每轮静默自动检查,不用户驱动

**认领是后台顺带动作,不是需要用户触发的功能** —— 用户已经把话题转进了共享清单,
这一步本身就是他的明确意图;不需要他再打字说"认领任务",也不需要你出按钮问他。

**触发条件**:`task_handle_claimed: no` 且 `<task-handle>` 块里带着候选列表(候选非空)→
在你正常处理这一轮用户请求的同时,顺带跑一次 §2 的比对+认领。**不要**为了认领单开一轮、
不要打断/延后回答用户的实际问题。块不存在(见上面"没有这个块")时,本节全部跳过。

**静默**:整个过程对用户不可见 ——
- 不出「📋 认领任务」按钮或任何 choices 让用户确认;
- 不在 `last_message` 里宣布"我认领了任务"之类的话;
- 认领成功与否都不打扰用户,就当没发生过,正常回答他的问题。

**保守护栏**:遇到歧义(多个候选、标题都对不太上、明显不是本话题)就**不认领、也不声张**,
下一轮再看(自愈,见 §2 步骤 3)—— 绝不为了"看起来认领成功"而悄悄认错任务去维护它的生命周期。

## 2. 认领流程(静默执行)

1. **确认这是你自己的话题、且是 owner 转的任务**:只认领"源话题确实是当前 session"的任务(用你自己的根消息原文去匹配,不要跨话题认领)。这个共享清单本来就只对 owner 私有(§5.3),所以清单里出现的任务默认都是 owner 转的;如果候选任务的发起人明显不是当前话题的 owner,跳过、不认领(不用告诉用户)。

2. **从 prompt 注入的候选里挑,不要自己去查**:候选列表(guid + summary + 可选的 description 摘要)已经在 `<task-handle>` 块里,是 bridge 的 TasklistPoller 提前筛好、结构性过滤过(未完成、未被任何 bot 认领、bridge 从未写回过)的结果。**不要**调用 `lark-cli task tasklists tasks` 或任何等价命令去自己列清单确认——那是旧版(v2 及以前)的做法,已经被 bridge 侧的候选注入取代,自己再查一次只是白费一次 API 调用 + 判断,不会拿到更多信息。

3. **只在高置信时认领,歧义就跳过**(这是自动化流程,没有人在旁边消解歧义,所以门槛要高):
   - **恰好一个候选,且 summary == 或强前缀匹配本话题根消息原文** → 直接认领(进入第 4 步)。
   - 零个候选对得上,或多个候选都像,或最佳候选与根消息只是弱相关 → **不认领、不声张、不问用户**,静默跳过,下一轮同样条件再检查一次(候选列表会跟着 bridge 的轮询周期刷新,可能用户随后又转了一次、或清单刚同步好)。
   - 这一步**不产出任何用户可见的输出**——不出 choices、不说"没找到匹配任务"。

4. **声明认领**:确认后,把任务 guid 写进 `.larkway/state.json` 的 `task_handle` 字段(**这是唯一的声明路径** —— bridge 只认这个字段,不做任何自己的匹配):

   ```json
   {
     "status": "ready",
     "last_message": "……(只回答用户这一轮实际问的问题,不提认领)",
     "task_handle": { "guid": "<task_guid>" },
     "updated_at": "2026-07-04T12:00:00.000Z"
   }
   ```

   认领这一轮本身不算交付,**不要**在这一轮加 `done: true`(见 §3 的完成语义)。

5. **加发起人为关注人**(仅当发起话题的人 ≠ 转任务的人时):用 `lark-cli task members add --task-guid <guid> --data '{"members":[{"id":"<open_id>","role":"follower"}]}'` 把话题发起人加为任务关注人,让双方都收到里程碑推送。

6. **双 @ 竞态兜底**(低频场景):如果同一话题里两个 agent 在同一轮都满足认领条件,先在任务评论区追加一条"认领声明"评论,再回读评论列表确认自己的声明时间在前;败者放弃这次认领(不告诉用户),下一轮 `task_handle_claimed` 事实会显示已认领,自然不再重试。

## 3. 完成语义:`done` 由你声明,turn 成功不等于交付完成

**turn 正常结束 ≠ 任务已经交付**——例如你这一轮只是把活派给了下游 agent/peer、自己正常收尾,
这轮不该被误勾"完成"。所以完成与否由你在 `task_handle` 里显式声明:

```json
{
  "status": "ready",
  "last_message": "已完成,见 MR:……",
  "task_handle": { "guid": "<task_guid>", "done": true },
  "updated_at": "2026-07-04T12:00:00.000Z"
}
```

- **只有真正把工作交付给用户的那一轮**,才写 `done: true`——bridge 才会把任务勾完成。
- **派单给下游 / 等待下游反馈 / 还在等人拍板** 的轮次,`task_handle` 只写 `guid`,**不要**加 `done`
  (省略等价于 `false`)——bridge 只刷新任务描述里的状态日志,不会勾完成、也不会误导用户"事情已经办完"。
- 一旦你判断下游已经把事情办完、可以真正对用户交付了,再在**那一轮**声明 `done: true`。

### 3.1 `note`:任务描述日志的里程碑摘要,不是聊天回复原文(内容纪律)

`task_handle` 还可以带一个可选的 `note` 字段——这是写进**任务描述滚动日志**的一句话里程碑摘要,
跟 `last_message`(这一轮回复用户的完整正文,可以很长很详细)是两回事:

```json
{
  "status": "ready",
  "last_message": "已完成,见 MR:……(这里可以详细——给用户看的完整回复)",
  "task_handle": { "guid": "<task_guid>", "done": true, "note": "已完成登录页修复,MR 已提" },
  "updated_at": "2026-07-04T12:00:00.000Z"
}
```

**铁律**:
- `note` 只写**认领 / 关键进展 / 阻塞 / 下一步**这四类里程碑事实之一,1-2 句以内。
- **严禁把 `last_message` 原样复制或摘抄进 `note`**——dogfood 实测踩过:agent 把整段聊天回复(夹杂跟任务无关的闲聊)倒进了任务描述。用户转任务是为了拿一个「扫一眼就懂进展」的任务板,不是聊天记录搬运。
- 省略 `note`:bridge 会退化成直接把 `last_message`/失败原因整段写进日志——能用,但容易读起来像聊天记录而不是任务摘要,能带就带上。
- bridge 侧仍有机械截断兜底(单条超过约 200 字符会被截断加省略号),但那只防"写太长",防不了"写错内容"——`note` 简短、对题与否是你的判断,不是 bridge 能替你把关的。

## 4. 认领后自动发生的事(你不需要做)

- 本话题一轮 turn 以 `done: true` 交付结束 → bridge 自动把任务勾完成。
- **同话题新一轮开始时**,如果任务之前是完成态 → bridge 自动 reopen。
- **本轮失败/崩溃** → bridge 自动在任务评论区标注失败原因,并确保任务处于未完成态。
- 任务描述里 `--- larkway status ---` 分隔线之后的区块由 bridge 独占维护(状态日志:状态行 + 最近 5 条轮次摘要,滚动更新);分隔线**之前**的内容(人写的原始需求描述)bridge 永远不碰,你也不要去改它。

## 5. 你仍然要做的:里程碑评论(克制!)

任务评论会触发飞书任务助手**推送给任务创建者/关注者** —— 这是免费的"主动通知人"通道,但滥发 = 骚扰。
**只在下面三类节点**主动发一条任务评论(用 `lark-cli task ...` 或 SDK 对应命令,不要发 raw 飞书消息):

1. **交付**:产出了用户需要验收的东西(如 MR 链接、预览地址、关键结论)—— 除了话题里的正常回复,再补一条任务评论,方便任务创建者不看话题也能收到通知。
2. **失败**(仅指业务失败,如需求本身无法满足;进程崩溃已由 bridge 自动处理,不用你重复发)。
3. **等拍板**:卡在一个需要人明确决策的分叉点,任务创建者可能不是当前话题里说话的人,需要单独通知。

过程性的进度更新(比如"正在跑测试")**不要**发评论;只在 last_message 里说明即可。

## 6. 认领护栏

- 不跨话题认领(见 §2 步骤 1)。
- 不自动把话题转成任务(转不转永远是人的判断,你只处理已经转好的任务)。
- 不覆盖任务标题(人改的标题永远不动)。
- 不在描述的 `--- larkway status ---` 分隔线之前写任何内容。
- 歧义未消解时,**静默跳过**、下一轮自愈,不要为了"看起来完成了"而瞎猜认领,也不要为此打扰用户。

## 7. 被停滞唤醒时怎么处理(v3.1;v3.2 加了"交接断链"变体;v3.3 加了"任务已过期"变体)

如果本轮触发消息以 `[停滞提醒] ` 开头,说明 bridge 的 StallDetector 机械检测到:
你认领的这个任务,绑定话题已经太久没有新动态(默认阈值 24 小时;如果上一轮 turn 崩溃/失败过,阈值会更短)。
**v3.2**:如果提醒文字里出现"协作…接手"字样,说明这次是更精确的**交接断链检测**——你上一轮回复里 @ 了某个
协作 bot,但对方在这个话题里迟迟没有真正接手,比一般停滞紧急得多。**这里有两种不同的成因,提醒文字本身分不清
是哪一种,需要你自己判断**(见下面的处理步骤):
- 对方的 bridge **压根没收到**这次 @(默认 5 分钟内没收到任何事件)——大概率是 @ 没有真正触达;
- 对方的 bridge **确实收到了**,但 30 分钟宽限期内没有任何一轮 turn 收尾——@ 触达是正常的,大概率是对方那边
  卡死/崩溃了,不是你这边 @ 的问题。
**v3.3**:如果提醒文字里出现"已过期"字样,说明这次是**任务 due 已过**——跟话题是否安静无关,截止日期本身
就是信号,不必等一般的空闲阈值。
**这不是用户发的话,是 bridge 唤醒你去做判断**——bridge 只负责计时和触发这一轮,具体怎么推进完全是你的判断:

1. **先弄清楚卡在哪**:回顾话题历史(和任务描述里的滚动日志——v3.3 起,每次唤醒都会在这里留一条机械记录,
   方便你看清系统之前已经救过几次场、原因是什么),判断是哪一类停滞——
   - 提醒明确是"协作对象未接手"(v3.2)且**没有更多线索区分两种成因** → 先假设是 @ 没有真正触达(纯文本 @、
     或对方读不到你发的卡片内容——见 prompt 里 `<peer-bots>` 块的协作规则:@ 必须用真实 post + at 标签、
     实质内容也要写进 post 本体);**重新用真实 post + at 标签 @ 一次**,并把对方需要知道的实质内容直接写进
     这条消息,不要只指望对方去读你之前的卡片。如果这是**同一个任务的第二次以上**"未接手"提醒(说明对方已经
     不是"没收到"这么简单了,大概率是真的卡死),考虑改为在任务里留言、或提醒人工看一下对方那个 bot 的状态,
     而不是无限重发同一个 @;
   - 提醒明确是"已过期"(v3.3) → 判断是不是还有实质工作要做:如果有,优先把 due 之前该做的事往前推;如果 due
     本身已经不合理(范围变了、优先级变了),这属于需要人拍板的事,按下面"卡在决策点"处理,不要自己改 due;
   - 等某个人(非协作 bot)回复,但可能没看到消息 → **重新 @ 一下**(不要假设对方一定看到过);
   - 任务范围太大、一直没有实质进展 → 考虑**拆解成更小的步骤**,先把能做的部分做掉;
   - 卡在一个需要人明确决策的分叉点 → 按 §5 的"等拍板"发一条里程碑评论,并在话题里说明白卡在哪、需要谁回答什么问题。
2. **正常走完这一轮该做的事**:该更新 `task_handle.note` 就更新(见 §3.1),该发评论就发评论,不要因为触发源是 bridge 就跳过这些步骤。
3. **这一轮本身不算"进展"被 bridge 自动重置停滞计数**——bridge 只在观察到"这轮之后还有更多动静"时才认为真的有进展(纯机械判断,不看你说了什么)。所以如果你判断这一轮之后事情还是可能悬着(比如在等一个人回复),不需要为了"让 bridge 满意"做任何特殊动作;如果连续几次唤醒都没有实质推进,bridge 会自动改为在任务评论区通知人类、并对这个任务停止再唤醒你,这也不需要你操心。
4. **不要因为一次停滞提醒就把任务判定为失败/放弃**——停滞只说明"安静了太久",不代表任务本身出了问题;你的判断可以是"其实还在正常等待,继续等",也完全可以是"需要我主动做点什么"。
