Prompt 中 Task 部分的待解决问题
Historical research / Superseded(2026-07-15):本文的问题发现(尤其 typeCategory ↔ closeResult drift、attempt 与 terminal Outcome 混淆)仍有参考价值,但字段、mapping 和方案不再具有规范效力。现行设计见 Task System Design。
来源:逐句分析现有和更新的Prompt中task部分的逻辑, 并对照最新schema字段得出。
问题 1:unreachable / neglected定义中的次数阈值脱离时间窗,逻辑不通
contact-analyzer 把 12 个 leadStatus 按客户轨迹分成 3 个 Phase:
- Phase 1(往前推进,7 个):
new/attempted/connected/booked/showed/trialed/converted - Phase 2(交谈过但卡住,3 个):
bad_timing/not_interested/lost_contact - Phase 3(从未接通,2 个):
unreachable/neglected
Phase 3 用纯次数判定——neglected(<3 次)/ unreachable(≥3 次),没有任何时间约束。次数脱离时间没有约束力,举两例:
- 3 通电话打在 10 分钟内 vs 摊在 一周里,都判
unreachable - 1 次尝试,对刚进来 1 小时的 lead 和进来 3 周的 lead,都判
neglected
两版 prompt 都有这个问题——pipeline-2(部署版 prompt-builder.ts)和 oliver-proposals 2026-05 提案版这段一字不差。不是 Oliver 引入的,部署版原来就这样。
建议
unreachable / neglected的次数阈值必须带时间锚才有意义。最自然的做法是把 temperature 重新启用——"客户随时间衰减的冷热度"本来就是它的语义,让它真正承担"距上次尝试 > N 天"这种约束,Phase 3 / neglected 用它来界定时间。
问题 2:task status 命名漂移——open → pending
现状
contact-analyzer prompt 原文(L552):
Tasks have binary
status(pendingorclosed) at the database level.
数据库 schema、所有 prompt(oliver 2026-05 提案 / pipeline-2 部署版 / prompt-eval v1b-20260504 历史版本)、SQL 查询里全部用 pending / closed。
但产品一直叫 "open"——这个漂移没有任何记录,也没经过产品确认**。
这不是 Oliver 2026-05 引入的
Schema 早期 V1 就把 status 限定为 pending / closed(见 archive 的 task-system-v1.md 和 task-system-schema-investigation.md)。当前 tasks.ts 用 Drizzle 的 { enum: TASK_STATUS } 约束(TS 类型层——status 字段本身没有显式 DB CHECK,只有 typeCategory / closeResult 才加了 check())。后续所有 prompt / 代码都被迫跟随。Oliver 没动这个字段,只是按现有 schema 写的。
状态
待团队对齐:到底是 pending 还是 open?
问题3:typeCategory ↔ closeResult 现状梳理
隐含 3 个问题(可能后续抽出独立 issue)
- closeResult 是统一池,没按 typeCategory 隔离 —— prompt 让 AI 自己选,schema 不强制 "lead_follow_up 不能用
cancel_saved" 这种业务约束。AI 配错,代码层没硬挡。 - 没有 typeCategory ↔ closeResult 的合法映射表 —— 现在只能从 prompt 规则反推,没有 schema 层的 mapping enforcement。
booked/cancelled已于 2026-05-23 加入 DB enum(callytics-infrastructure #980),这是 Task V2 enum 演进历史;当前 V3 selected Target 的语义与迁移见 Task V3 Product / Domain Contract §16.2。
typeCategory 有 9 种
closeResult 目标态:20 个(12 原有 + ★ 8 新增)
先按"现有 / 建议新增"分,性质(🟢正向 / 🔴负向 / ⚪中性)降为表内一列。
现有 12 个(backend TASK_CLOSE_RESULT 已支持)
建议新增 8 个(★ backend enum 还未支持)
基于以上, 建议建立typeCategory ↔ closeResult 映射
下表只列 typeCategory-specific 的正负向。do_not_contact / wrong_number(通用负向)、other(通用中性)对所有 typeCategory 都适用,不重复列。
★ = 新增,backend TASK_CLOSE_RESULT enum 还未支持。
注 1:
lead_declined的底层触发 = contact 的lifecycleState变成paused/terminal(当前lead_follow_up随之关闭);其中paused牵扯问题 7。注 2:
unable_to_reach触发条件依赖unreachableleadStatus(≥N 次真实尝试后仍未接通)。但unreachable目前只看次数不看时间窗(见问题 1),导致unable_to_reach可能被过早触发——问题 1 不修,这个负向结果就不可靠。
设计说明
attempted 的语义 —— 专门给 lead_outreach 用
attempted 表示员工完成了首次 SLA 外联任务(打了那通电话)—— 这是 lead_outreach 的"正向 = 任务完成"。
不给其他 typeCategory 用。Oliver 在两处对 attempted 的用法对这个词的语义理解有偏差:
- [SECTION 7(line 529-530)]:禁止把 cancellation / complaint / manager-callback / upgrade / referral / win-back task 在"一次未接通后"关成
attempted——这里attempted不适用于cancellation / complaint / manager-callback / upgrade / referral / win-back task, 这段描述没必要. - [SECTION 4 Billing/payment recovery 模板(line 359-360)]:推荐在"付款链接已发出但付款未确认"时 close as
attempted——这里attempted被当作"完成了一次有意义的行动(发出链接)"
这进一步支持映射表的设计:attempted 专属 lead_outreach(首次外联完成的正向结果),其他 typeCategory 各有专属的负向 closeResult,不再用 attempted 兜底。
如果
attempted这词容易引起歧义("尝试"听起来像负向),可以考虑改名为lead_outreached—— 直接表达"首次外联已完成"。详见问题 4。
为啥 1 个 typeCategory 没有对应的负向结果?
设计原则:如果一个任务无法界定负向结果, 但是在没有收到明确否定信号的情况下就需要继续跟下去的话, 那么这个任务需要员工手动用other来关闭, 并填入具体关闭原因, 或者有通用的负向结果出现, 也可以关闭.。
具体到 1 个没有 typeCategory-specific 负向的:
注:
booked_not_converted虽然没有"客户拒绝"的明确负向,但有already_member兜底(发现对方已是会员,task 目标不成立)。
8 个新增 closeResult 待 backend 同步
加完后 closeResult enum 从 11 → 20。
问题 4:attempted 不应删除,建议改名
attempted 专属于 lead_outreach 的正向关闭结果,语义是"首次外联已完成"(staff 完成了 SLA 任务,打出了那通电话)。
Final schema 提议删掉 attempted,是因为误解了其语义(以为它是进展事件而非 task 完成结果)。实际上 lead_outreach task 的正向结束就是"首次外联已打出",没有这个 closeResult,lead_outreach task 就没有正向出口。
attempted(正向)和 unable_to_reach(负向)是并列关系,不是替换关系:
建议:保留语义,改名为 lead_outreached,明确表达"首次外联已完成",避免被误解成"只是尝试过但没有结论"。
问题 5:Oliver 引入 in-place UPDATE 现状梳理
现状:目前是 close + create 循环,不是真 UPDATE
现行 spec(task-lifecycle.md)第 6.1 节写得很清楚:
Contact Analysis 每次运行时,先 auto-close 该客户的旧 task,再根据最新分析决定是否创建新 task
也就是说:AI 不修改老 task,而是关掉老的、建新的。
Oliver 的 UPDATE 改的是 task 的哪些字段
从 Oliver prompt 的 OUTPUT JSON SCHEMA 看:
只能改 2 个字段(至少给 1 个):
不能改: typeCategory(那是 task 身份)、taskId、createdAt。
但 Oliver 这个 UPDATE 的字段集不全——补全后应该是这样
直接 mutate 现有列(5 个)
task_events 事件(用现有 audit log 表,不加新列)
AI update / staff 编辑 task 应该沿用这套:
为啥不加 lastAiUpdateAt / updatedBy / lastAiUpdateRunId 列?
- update 可发生 N 次 → 加专用列每次 overwrite,丢历史;
task_events天然保留 - close 加专用列因为是终态(每 task 1 次,写完不变);update 是可重复事件,不该比照 close
- "上次 AI 更新时间" / "谁更新" / "哪次 run" 全部从 task_events 查:
前端用 computed API field 算"未读 AI 更新数"(自上次员工查看以来的事件数),渲染卡片角标。
📝 alignment 缺口:task.due_at_changed 当前在 contact_timeline,不在 task_events
两张表设计上的语义分工(已对照 schema task-events.ts / contact-timeline.ts):
⚠️ 但实际 schema 里两表的 task 事件有重叠,分工并不干净:task.created 两张表都有,contact_timeline 还另有 task.status_changed / task.updated。所以下面"该搬"的方向成立,但别把它当成"两表本来泾渭分明、只有 due_at_changed 跑偏"——task 事件本来就两边都落。
task.due_at_changed 只在 contact_timeline 的 enum 里(task_events 没有这个值),进 contact_timeline 是历史决策。严格按"单 task 审计"语义它更该在 task_events,但要不要补 alignment PR 搬过来,是另一个待决策项。
⚠️ 冲突边界:不该 AI 直接覆盖的
task-lifecycle.md §五 已经在讲员工手动 Adjust 时留过"AI 重建覆盖员工调整"的 caveat,UPDATE 机制也得处理同一类问题。
❌ 不该在 UPDATE 里改的(11 个)
总结
问题 6:suggestedActions 模板清单混入"建不建任务"的判断——字段职责错位
拆分后状态(2026-06-03 核查)
Oliver 将 contact 和 task 拆成两个 prompt 后:
- [04-contact-profile] ✅ 已处理干净:
suggestedActions只作为 contact 级信号输出,无"建不建 task"判断,无no human action渠道选项。 - [05-task-decision] ❌ 问题仍然存在:SECTION 4 的 OTF 模板清单原封不动保留了以下问题(见下方现状分析)。
需要修改的是 05-task-decision SECTION 4,不是 04-contact-profile。
现状(05-task-decision SECTION 4)
[SECTION 4(line 287-379)]的 OTF 模板清单列了 8 个场景,但其中 2 个放错了位置。
机制分三层:
suggestedActions(task_issues 表 jsonb 列)= 做什么 actionactionNeeded(task_issues 表 boolean 列)= 要不要跟进- 建 / 关 / 改 task =
taskDecisions[]里的action字段——不是数据库列
三者职责分开,但 SECTION 4 把"建不建任务"混进了 suggestedActions 模板:
剩下 6 个场景(新 lead、问价、取消风险、账单、投诉、课后跟进)才是 suggestedActions 真正该管的内容。
no human action 被列为必填渠道
[SECTION 4 line 298]要求每条 suggestedActions 元素必须包含 channel(可选值为 call / SMS / manager callback / no human action),选项包含 no human action。但"不需要人工"和"这条建议存在"本身冲突——应该走 actionNeeded=false + suggestedActions=[],根本不会有这条建议。
内容重复
"Already booked 不建 task"(SECTION 4 line 337)和 SECTION 5 CREATE RULES(line 404)重复写了同一条规则。
建议(修改 05-task-decision SECTION 4)
- 从模板清单删掉 "Already booked" 和 "Do not contact / no action" 两个场景,只留 6 个真正的
suggestedActions场景 - channel 选项从
call / SMS / manager callback / no human action改为call / SMS / manager callback - SECTION 4 删掉的场景由 SECTION 5 的 DO NOT create 规则覆盖,不重复
问题 7:paused 缺少和 terminal 对等的 task 停止联动,且与状态定义自相矛盾
设计意图
lead_follow_up类 task应该一直跟,直到对应 contact 的 contacts.lifecycleState 变成 paused 或 terminal 才停。
现状:terminal 写全了,paused 没写
prompt 对 terminal 有三重保险:
但 paused 只有状态定义里一句泛泛的"paused = stop proactive outreach, wait for reactivation"(prompt L98)。上面三条硬规则(actionNeeded RULE / DO NOT create / lead_follow_up 创建条件)全都只写 terminal,没写 paused。
两个具体后果
- 自相矛盾:定义说 paused 要"停止主动外联",但 actionNeeded 的 RULE 只拦 terminal——prompt 没有任何规则阻止 paused 状态下
actionNeeded=true。定义说停、规则没拦。 - 已存在 task 无人管:唯一映射到 paused 的 leadStatus 是
bad_timing。bad_timing不在lead_follow_up创建条件里,所以不会新建;但"已经存在的lead_follow_uptask,客户变成bad_timing(paused) 之后要关闭task"——prompt 完全没提及。
建议(含一个待决策)
把 paused 补进 actionNeeded RULE 和 task 停止逻辑。
问题 8:一个 contact 可以有多个 task?
背景
当前设计(schema + Oliver prompt)允许一个 contact 同时有多个 open task,每个 typeCategory 最多 1 个。例如一个 member 可以同时有 retention(投诉未解决)和 upgrade(想升级)两个 task。
Oliver SECTION 2 Constraints 原文([line 111-113]):
Max 1 pending task per typeCategory per contact. If the same typeCategory is already pending, update it, close it, or leave it unchanged. Never create a duplicate.
即:同一个 typeCategory 不能重复建,但不同 typeCategory 可以并存——Oliver 认为一个 contact 同时有 retention + upgrade 两个 pending task 完全合法。
产品提案
一个 contact 最多有 1 个 open task(task = 一次通话的容器)。有多个需求不新建第二个 task,而是在这个 task 里新增一个 issue。每个 issue 对应一个 typeCategory,各自独立管理状态和 closeResult。
业务理由:给同一个客户打两个电话是浪费,一通电话应该把所有事情一起说清楚。
Oliver 那条去重规则平移到新结构:同一个 task 里,同一个 typeCategory 最多 1 个 open issue。原文:
Max 1 pending task per typeCategory per contact. If the same typeCategory is already pending, update it, close it, or leave it unchanged. Never create a duplicate.
新结构下有两层约束:
- 1 个 contact 最多 1 个 open task(新增)
- 1 个 task 里同一个 typeCategory 最多 1 个 open issue(Oliver 原规则平移)
数据结构设计(关系型,对照当前 schema)
下表基于 2026-05 当时的
taskssnapshot,不能当作当前 schema 或批准后的 Target schema。当前 V3 contract 中的 source snapshot 与 Target delta 分别见 §9.1 和 §9.3;引用前仍须核对 live code。
tasks 表(联系人维度,1 task per contact)
保留联系人身份、整体生命周期、关闭元数据:
从 tasks 表移出、下沉到 task_issues 的字段:taskType / typeCategory / actionNeeded / actionNeededReason / suggestedActions / sourceType / sourceLeadId / contactAnalysisRunId / closeResult / closeNote / closedAt(issue 级)/ closedByStaffName(issue 级)/ closedByStaffId(issue 级)/ closeType(issue 级)
task_issues 表(类别维度,N issues per task)
唯一约束:(taskId, typeCategory, status='pending') — 同一个 task 里同一个 typeCategory 最多 1 个 pending issue。
task 关闭逻辑
影响面(给 teammate 评估)
状态
待 teammate 评估 schema 迁移成本。
问题 9:taskDecisions action 字段缺 3 个值,prompt 需补全
现状
action 是 taskDecisions[] 每个元素里的一个字段,值决定后端对 tasks 表做什么操作。
Oliver SECTION 10(line 650-699)目前只定义了 3 种 action shape:create / close / update。
2026-05 Final proposal 当时列出 6 种 Task V2 action;当前 V3 command surface 见 Task V3 Product / Domain Contract §10.2。
6 种 action
需要做的事
prompt 补全 3 个缺失的 action(create_closed / record_progress / no_op)之前,需要结合 schema 和后端实现规格,为全部 6 种 action 正式定义:
- 写哪张表(
tasks/task_issues/task_progress_events/ 不写库) - 必须带哪些字段
- 不能带哪些字段
不能只靠 prompt 推断——字段规格必须先在 schema / backend 层拍板,再同步进 prompt。
其中 record_progress vs update 的边界需要特别说清楚:两者都针对已有 task,但 record_progress 是"发生了一件事(打电话没接、留了语音),task 本身不变",update 是"task 的信息需要调整(优先级变了、建议换了)"——这个区别必须写进 prompt,否则 AI 会混用。
问题 10:record_progress 启用时 progressType 枚举待统一
现状
Oliver 的正式输出([SECTION 10 line 650-699]没有 record_progress。他在 [SECTION 11 line 703-724]写了一段未来提案,标明"Recommended future backend support / Use this only after backend support exists",里面设想了 record_progress 的 JSON shape 及其 progressType 枚举(8 值)。
目前 record_progress 两边都没有落地,但将来启用时,Oliver 提案的 8 值和 Final schema 的 6 值需要统一:
两套值名字不一样、数量不一样:
两套逻辑分析
Oliver 的 8 值——"staff 做了什么"(动作日志):记录员工执行了哪个动作,纯行为记录,不管结果。
Final schema 的 6 值——"事情进展到什么状态"(状态日志):记录这次互动后客户侧/进展处于什么状态。
关键发现:record_progress要和update区别开来, 但是两套都有值会和 update 交联
record_progress 和 update 的边界必须清晰——record_progress 只记录"发生了什么",task 本身不变;update 改变 task 的 priority / suggestedActions / dueAt。两者不能因同一个理由同时触发。
以下是两套全部值的逐一分析(Oliver 8 值 + schema 6 值,去重后 11 个独立概念):
结论:
- 安全用于
record_progress(4 个):no_answer/left_voicemail/text_sent/called - 应该走
update或close,不该进record_progress(2 个):manager_callback_promised/follow_up_scheduled - 边界模糊,需要讨论拍板(4 个):
payment_link_sent/form_sent/callback_requested/customer_considering
待拍板
progressType 最终枚举应只保留不引发任务信息变化的事件,从两套合并后剔除交联值,建议从 4 个安全值出发,再决定边界模糊的 4 个是否纳入(以及纳入后如何防止和 update 混用)。
状态
待团队拍板 progressType 最终枚举,再同步进 prompt 和 schema。
问题 11:SECTION 9 优先级判断混入"要不要建 task"的规则
现状
[SECTION 9 PRIORITY JUDGMENT(line 630-642)] 在描述 low 优先级时写了两类内容:
第一类(正确):low 的使用条件——"有明确但不紧的后续动作",这是优先级判断应该做的事。
第二类(放错位置):"以下情况不要建 low 优先级 task":
- 例行预约确认
- 确认短信
- intake / waiver 表单提醒
- 到店提示
- no-answer / 满信箱 / 无意义语音(无高意向内容)
问题所在
第二类规则实际上不是在说"优先级怎么定",而是在说"这些场景根本不该建 task"——这是 CREATE 决策,属于 [SECTION 5 CREATE RULES(line 382-410)]的职责范围。
两个 section 的职责边界:
把"不建 task"的规则写进优先级章节,会让 AI 在两个地方找到关于"建不建"的判断,逻辑分散,容易漏读。
与问题 6 的关系
问题 6 是 suggestedActions 模板区混入了"建不建 task"判断;本问题是 SECTION 9 优先级区混入了同类判断。根因相同:把"不建 task"的规则分散写在多个 section,而不是集中在 SECTION 5 CREATE RULES 一处。
建议
把 SECTION 9 里"Do not create low-priority tasks for..."的列表移到 SECTION 5 CREATE RULES 的"Do not create a task when"列表下,SECTION 9 只保留优先级判断逻辑。这样 AI 找"建不建 task"只看一个地方。
问题 12:"configured outreach threshold" 概念与已有设计前后矛盾
出现位置
[05-task-decision] 中两处使用了 "threshold" 概念:
- SECTION 4 New lead 模板(line 326-327):"close the task when the customer books or the configured outreach threshold is exhausted"
- SECTION 3 renewal 规则(line 255):"keep/update until threshold or manual close"
问题
产品决策是所有阈值写死为常量,当前没有自定义阈值的功能。[04-contact-profile]里已全部硬编码为 3:
unreachable= 至少尝试 3 次从未接通neglected= 少于 3 次- Lead attempt threshold = 3 次
05-task-decision 用"configured outreach threshold"——"configured" 暗示这是个可配置参数,但这个功能根本不存在。同一套 prompt 里一边写死 3,一边用"configured threshold",前后矛盾,会让人误以为阈值可以调整。
建议
删掉"configured outreach threshold"这个概念,统一改为直接写明"3 次尝试后仍无回应",或改用 unable_to_reach closeResult(见问题 4)作为触发条件,和 04-contact-profile 保持一致。
问题 13:员工行动指导中涉及关闭/更新的逻辑必须与 AI 规则一致
设计原则
suggestedActions 是写给员工看的行动指引,但部分模板里包含了"什么时候关 task"或"什么时候更新 task"的描述。这些描述必须与 AI 的对应规则完全一致:
- 关闭条件 → 必须与 [SECTION 7 CLOSE RULES]对应
- 更新条件 → 必须与 [SECTION 6 UPDATE RULES]对应
如果 suggestedActions 里的关闭/更新条件与 SECTION 6/7 不一致,会导致员工行为和 AI 判断相互矛盾——员工以为可以关,AI 却不认这个理由;或者员工按 AI 的规则操作,却在 suggestedActions 里找不到对应指引。
已发现的不一致
建议
审查 SECTION 4 所有 8 个 OTF 模板,逐一核对其中提到的关闭/更新条件与 SECTION 6 / SECTION 7 的对应关系。不一致的地方二选一:
- 修改
suggestedActions模板,改用 SECTION 7 里正确的 closeResult - 在 SECTION 7 里补充对应的关闭规则
问题 14:DNC 时 task 关闭由谁执行
两版冲突
原版(合并版):DNC 时 AI 输出 taskDecisions=[](空数组),不枚举任何 close 指令。原因是当时采用 close + create 循环——每次分析都先批量关掉旧 task,再建新的,DNC 就是"不建新的",所以 AI 什么都不用做,系统处理。
Oliver 新版([SECTION 7 line 514-516]):改为 AI 输出 close 指令,引用每个 pending task 的 taskId,用 closeResult=do_not_contact 逐个关闭。原因是新版不再批量关,AI 需要明确指定要关哪个 task。
Oliver 的逻辑(AI 枚举 taskId)在技术上可行——输入里的 PENDING TASKS 本来就是按 contactPhone 查出来的,AI 能看到该 contact 所有的 pending task。但这增加了 prompt 的复杂度:AI 需要识别所有 pending task 并逐个输出 close 指令,而不是简单地让系统处理。
但在"1 contact 最多 1 task"的设计下(见问题 8),这个问题自动消解:
- 一个 contact 最多 1 个 pending task
- DNC 时系统只需要用
contactPhone就能找到并关闭这个唯一的 task - AI 不需要枚举 taskId,只需要输出
doNotContact=true - 不存在"漏关某个 task"的风险,因为最多只有 1 个
建议
采用系统负责关闭的方式:AI 只需设置 doNotContact=true,系统根据 contactPhone 自动关闭该 contact 的 pending task,taskDecisions 不需要输出 close 指令。这与"1 contact 最多 1 task"设计配合,是最简洁且无漏关风险的方案。