【唤醒实战笔记】2026-09-29 | 目标-任务联动:当状态变成“纯派生值“
【唤醒实战笔记】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 且 全部子目标 completed | completed |
对应到端侧的实际评估逻辑(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' // 全做完了
}
几个值得注意的点:
- 新目标默认
paused——刚创建的目标是空的(零任务零子目标),符合"零任务零子目标 → paused"的规则; active是默认值——只要有一个未完成项,就是 active;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
更多推荐

所有评论(0)