【唤醒实战笔记】2026-09-29 | 目标-任务联动:当状态变成"纯派生值"


date: 2026-09-29
tags: [HarmonyOS, 数据建模, 端侧, 状态管理, 目标管理, 任务管理, 智能体]
type: 实战笔记

一、背景:数据挂上了,行为却没联动

系统里有两类实体:目标(GoalManage)和任务(ActionManage)。它们之间的关联,最初只有两个字段:

  • 任务可以挂到目标下(goalId);
  • 目标可以挂到父目标下(parentGoalId)。

但这是数据层面的挂接,不是行为层面的联动。典型症状:

  • 你把一个目标下的任务全部标记"已完成",目标自己的状态还是 active,纹丝不动;
  • 目标的状态靠用户(或大模型)手动改,改慢了、改错了、忘了改,状态就和现实脱节。

一句话:数据之间有关系,但关系没有"活"起来。


二、核心决策:状态是"算"出来的,不是"存"出来的

这个问题的解法,是一个数据建模上的决定:

目标的状态(status)不该是存储值,而该是派生值——由它下面任务的完成度实时计算,端侧自动维护,不可手动设置。

定案时的原则只有一句:“一切以 act(任务)完成度为准。”

这意味着 goal.status 这个字段,虽然还写在数据库里,但它的唯一合法写入者变成了端侧的评估逻辑。任何外部(用户、大模型)想直接改它,都会被拒绝。


三、派生规则:三个值,怎么算

目标状态只有三个值,派生规则清晰到可以写成一个判定:

条件status
零任务 + 零子目标paused
存在未完成任务(pending)或未完成子目标(非 completed)active
全部任务 done 且 全部子目标 completedcompleted

对应到端侧的实际评估逻辑(evaluateGoal),核心就是这段:

const total: number = acts.length
let done: number = 0
for (const a of acts) {
  if (a.status === 'done') { done++ }
}
let pendingGoals: number = 0
for (const g of children) {
  if (g.status !== 'completed') { pendingGoals++ }
}
const remaining: number = total - done

let target: string = 'active'                       // 默认 active
if (total === 0 && children.length === 0) {
  target = 'paused'                                 // 空目标
} else if (remaining === 0 && pendingGoals === 0) {
  target = 'completed'                              // 全做完了
}

几个值得注意的点:

  1. 新目标默认 paused——刚创建的目标是空的(零任务零子目标),符合"零任务零子目标 → paused"的规则;
  2. active 是默认值——只要有一个未完成项,就是 active;
  3. completed 是最苛刻的——必须任务全 done 且 子目标全 completed,缺一不可。

四、联动触发 + 向上传播

哪些操作会触发重估

  • 任务 create(挂了 goalId 时)、update(状态或 goalId 变了,新旧目标都要重估)、delete;
  • 目标 create(挂了 parentGoalId 时,父目标要重估)。

状态变化沿父链向上传播

子目标的状态变了,父目标也可能跟着变(比如最后一个子目标 completed 了,父目标就该评估是否 completed)。所以评估完一个目标后,要沿着 parentGoalId 一路往上重估:

async syncGoal(goalId: string): Promise<GoalEvalResult | null> {
  const first = await this.evaluateGoal(goalId)
  const visited: Set<string> = new Set<string>()
  visited.add(goalId)
  let current = await this.getGoal(goalId)
  while (current !== null && current.parentGoalId !== '' && !visited.has(current.parentGoalId)) {
    const parentId = current.parentGoalId
    visited.add(parentId)
    await this.evaluateGoal(parentId)
    current = await this.getGoal(parentId)
  }
  return first
}

注意那个 visited 集合——防环兜底。目标树理论上不该有环(A 的父是 B,B 的父又是 A),但数据是外部来的,防御式编程必须假设它可能有环。一个 Set 记录已访问节点,遇到重复就停,不会死循环。


五、显式操作守卫:拒绝,而不是静默

状态既然是派生的,那用户想"手动设置"时怎么办?答案是拒绝 + 给清单 + 说人话。

守卫一:update status=completed 是"请求完成信号",不是"设置状态"

当用户说"把这个目标标记完成",端侧收到 update(status=completed)。这不是一个直接的状态写入,而是一个请求——端侧先检查有没有未完成项:

  • 有未完成项 → 拒绝(code=0),返回 blockedActions / blockedGoals 清单 + 人话备注:

    目标"读完一本书"还有 2 个任务未完成(完成 1/3),另有 1 个子目标未完成,无法标记完成。请先处理未完成任务(删除任务,或用 ActionManage update 将任务 goalId 改为 none 解除关联),处理完毕后目标将自动标记为完成。

  • 全部完成 → 幂等成功,直接同步状态为 completed。

  • 空目标(零任务零子目标) → 拒绝,并提示"目标下没有任务也没有子目标,无法标记完成"。

守卫二:active / paused 主动设置一律拒绝

目标状态由任务完成度自动维护,无法手动设置。目标下任务全部完成时自动 completed,出现未完成任务时为 active,零任务且零子目标时为 paused。

守卫三:delete 前先清空

目标下还有任务或子目标时,删除被拒:

目标"自驾游云南"下还有 3 个任务、2 个子目标,无法删除。请先删除其下任务与子目标(或用 ActionManage update 将任务 goalId 改为 none 解除关联)。

只有零任务零子目标的"空目标"才放行删除。

核心思想:这些守卫不是给用户添堵,而是让"状态"这个真相源不被污染。如果允许手动改状态,派生逻辑就永远算不准——因为存在一个"手动覆盖"的旁路。


六、进度反馈:把派生结果说给用户听

联动不只是内部状态的变化,还要反馈出来。任务操作成功时,返回带 goalProgress(JSON)+ 一句人话备注:

还差 2 个任务完成(完成 1/3)

云端大模型拿到这句,直接转述或鼓励用户,不用自己再算一遍。用户完成任务的那一刻,能立刻听到"目标进度推进了"的反馈——这正是"联动"这个特性最有体感的地方。


七、设计取舍:为什么是"派生值",为什么是"act 完成度"

1. 为什么状态必须是派生值,而不是存储值

存储值有个根本缺陷:它会过期。任务全做完了,状态还是 active,就是"存储值没跟上现实"。而派生值每次评估都现算,永远和现实一致——这就是前面数据模型里反复强调的那句"引用是死的快照,查询是活的信息"在状态上的应用。

2. 为什么真相源是"act 完成度",不是别的

一个目标"算不算完成",本质取决于它下面的任务做没做完。任务(act)是最小、最不可再分、最客观的执行单位——它只有 pending / done 两个值,不存在歧义。以它为准,整个派生链条就有了一个稳定的地基。

3. 为什么存量数据不迁移

旧数据里可能存在"active 但零任务"的目标(手动设的,和派生规则矛盾)。这些不主动迁移,而是靠"下次联动触发时自动重估"来最终一致——因为任何一次任务增删改都会触发重估,旧数据的错误状态会随着使用自然被修正。


八、总结

这一篇讲的是一个字段的性质转变:goal.status 从"存出来的"变成"算出来的"。

  • 派生规则三值清晰:空 → paused,有未完成 → active,全完成 → completed;
  • 联动靠任务/目标的增删改触发,状态沿父链向上传播(防环兜底);
  • 守卫把"手动改状态"挡在门外,拒绝时给清单、说人话;
  • 反馈把派生结果翻译成"还差 N 个任务完成"说给用户。

而这一切的根,是一个更朴素的数据建模原则:凡是能被别的数据推导出来的字段,就别存成事实——存派生规则,让真相永远实时。


学习小结:目标-任务联动的核心,是把 goal.status 从存储值改成派生值——“一切以任务完成度为准”,状态由端侧实时计算、自动维护、不可手动设置。派生规则三值清晰:零任务零子目标 → paused,存在未完成项 → active,全完成 → completed;新目标默认 paused。联动靠任务/目标的增删改触发,状态沿父链向上传播(用 Set 防环兜底)。显式操作守卫把"手动改状态"挡在门外:update status=completed 被当作"请求完成信号",有未完成项就拒绝并返回清单+人话备注;active/paused 主动设置一律拒绝;delete 前先清空。进度反馈把派生结果翻译成"还差 N 个任务完成(完成 X/Y)"说给用户。最深的一条:能被别的数据推导出来的字段,就别存成事实——存派生规则,让真相永远实时。

懿路向前 · AI辅助整理
2026-09-29

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐