Task Pipeline Deliverable (Codex)

Historical design input(2026-07-23 校准):本文保留 Task Orchestrator、0 / 1 / N decisions、create_closed、Activity/Outcome 分离等 Task V2 依据,但不再单独作为实施规格。当前路线以 Task V2+ 工程审计与实施基线 为准;V3 内容仍是待评估 proposal。

用途: Codex 独立设计产出,避免和并行 AI 的 task-lifecycle-gap-analysis.md / task-lifecycle-gap-analysis-claude-code.md 修改冲突。 日期: 2026-05-30 范围: system design only。不改代码、不写 DB、不做 rollout、不设计完整 SMS pipeline。 设计口径: 这里写 final / long-term-proof target architecture。实现顺序可以另写 implementation plan,但概念模型先按最终态说清楚。 Review basis: 当前 task pipeline redesign 以这份 Codex 文档作为 review basis。旧 V2 task docs 已移动到 archive/,避免和新模型混读。 2026-05-31 补充: 全系统 pipeline 调研后,Task Orchestrator 应视为 unified architecture 的 shared mutation module,而不是 task feature 内部 helper。见 Task Pipeline Architecture Addendum

How to Read This Doc

这份文档按"先最终 contract,再解释为什么"组织。读者不用先理解所有历史问题,也能先看到最后系统应该长什么样。

顺序内容目的
Part 1: Final Design ContractExecutive Summary, object model, workflow, schema, write path, API contract, prompt contract直接回答"最终要建什么"
Part 2: Operating SemanticsCode vs Prompt, UI semantics, scenario coverage, metrics说明这些对象在真实产品里怎么用
Part 3: Implementation + Rationaleimplementation touchpoints, deep dives, competitor calibration保留全部设计依据和后续改动清单

核心阅读路径:

API contract -> schema -> workflow -> prompt contract

原因是 prompt 不应该先被设计出来。prompt 的输入/输出必须由稳定的 task API、schema 和 state machine 反推,否则会变成"AI 想怎么说就怎么说",代码和 UI 很难长期维护。

Executive Summary

结论: Task 是一个统一的 Work Object / 业务事项对象。它有二元 lifecycle: open / closed;中间执行过程写入 progress log;最终业务结果写入 close outcome。

最终决定设计
Task 是什么一个 customer-work objective,既能是待办,也能是当场完成后的结果记录
Lifecycleschema + code + UI 同步用 open / closed(测试环境直接迁移,prod 无压力。原 DB pending 一次性 UPDATE → 'open')
ProgressNo Answer, Left Voicemail, Text Sent, Callback Requested 是 progress,不关闭 task
Close outcomebooked, converted, cancel_saved, issue_resolved, not_interested, do_not_contact 是最终结果
Orchestrator所有 task mutation 统一经过 Task Orchestrator + deterministic state machine
AI vs CodeAI 判断语义;代码负责触发、校验、状态迁移、幂等、并发、审计
API-first数据模型要服务前端、dashboard、OAuth API、CLI、第三方系统和 AI agent

一句话模型

Task lifecycle:       open / closed
Task progress log:    no_answer / left_voicemail / text_sent / callback_requested / ...
Business outcome:     booked / converted / cancel_saved / issue_resolved / do_not_contact / ...

为什么用 open / closed,不是 pending / closed

结论: schema + code + UI 同步改名 open / closed。测试环境无 prod migration 压力,一次性改干净,不保留 pending 映射。

命名问题 / 优点
pending听起来像"还没开始/等待中"。但一个 task 可能已经打了 3 次、发了 SMS、留了 voicemail,仍然未完成。此时叫 pending 不够准确
open表示"这个事项还没有结束",不关心是否已经尝试过。更适合 API、UI、AI agent 和 manager metrics
closed表示 objective 已终止,可能是成功、失败、DNC、wrong number,不等于 business win

实施:

schema/code/API/UI: tasks.status = open | closed (DEFAULT 'open')
Migration: UPDATE tasks SET status='open' WHERE status='pending'
Sweep: 全 repo grep 'pending' 替换为 'open'
       (4 Lambda + studio-api raw SQL + CHECK constraints
        + partial unique index WHERE status='pending')

Current System Context

结论: task pipeline 不是单点功能,它处在 per-call AI、contact analyzer、lead pipeline、staff UI/API 和 manager metrics 中间。最终设计必须覆盖所有入口。

上下游它做什么现在怎么影响 task最终应该怎么影响 task
RingCentral ingress接收电话/SMS webhook,把原始事件送进转录、消息存储和后续分析不直接写 task。它只是把"发生了一通电话/一条短信"带进系统继续不直接写 task;只提供 activity evidence 给后面的 orchestrator
Per-call AI分析单通电话:是否打通、有没有业务结果、是否需要后续跟进产出 followUpNeeded, primaryOutcomeResult, callState 等 call facts,后续 Contact Analyzer 会读这些信号仍然只写 call facts,不直接 create/close task。它的输出是 task decision 的证据,不是最终 task mutation
Contact Analyzer聚合一个 contact 的 calls/messages/leads/tasks,做跨互动判断当前 prompt 直接输出 taskDecisions[],代码按 create/close/update 执行;当场完成常被跳过最终只输出 semantic proposal,例如 create_closed, record_progress, close;由 Task Orchestrator 校验并执行
Lead pipeline新 lead 到达后快速创建 outreach 待办直接插入 lead_outreach open task,绕过 AI,但有 onConflictDoNothing 防重复仍可 deterministic create,但应该走统一 task mutation contract,避免和 AI/员工路径写法不一致
Staff API/UI员工在 Tasks 页面记录进展或关闭任务当前 UI 看起来有 Status Update,但后端只有 close;no_answer / left_voicemail 被误写成 closeResult 并触发关旧建新Progress Updatetask_progress_events;Close Task 只写最终 business outcome
Dashboard metrics店长看前台/AI agent 完成了多少事项、多少转化/挽留当前容易把 close task 当 productivity,并被 closedByStaffName 过滤污染分开统计 completed objectives、business wins、attempt workload,不要把 no-answer 计成完成
Future API / AI agent外部系统、CLI、OAuth client 或 AI agent 读取/操作 task现在没有稳定 API contract,外部调用方会被迫理解 task 表和 timeline 细节提供 typed work-object API,让 agent 一次读懂 task 当前状态、进展、下一步和最终结果

1. Final Object Model

结论: 最终模型分 4 层,每层回答不同问题。不要把它们压进一个字段。

回答的问题存储位置示例
Activity客观发生了什么互动?calls, messagesinbound call, outbound call, SMS, voicemail
Progress这个 task 处理过程中做了什么?task_progress_eventsno_answer, left_voicemail, text_sent, callback_requested
Lifecycle这个业务事项还开着吗?tasks.statusopen, closed
Outcome这个事项最终业务结果是什么?tasks.closeResultconverted, booked, cancel_saved, issue_resolved

Task 的边界

场景是否是 taskLifecycle说明
新 lead 需要联系open前台待办
lead 没接电话不是新 task原 task 仍 open追加 progress
lead 当场 book introclosedcloseResult = booked
inbound 当场成交closedcreate-closed,closeResult = converted
会员投诉当场解决closedcloseResult = issue_resolved
客户问营业时间,员工已答普通 service activity,没有持续 objective

2. Final Workflow

结论: 目标状态下,所有 task 写操作都经过 Task Orchestrator;AI 只给 proposal,代码执行 state transition。

Desired Workflow: 每一步在做什么

这张图的重点不是"多一个复杂系统",而是把 task 写入拆成清楚的责任链:入口先过滤,AI 只判断语义,代码最后安全写库。

步骤谁负责做什么为什么需要这一步
1. 输入事件进入系统事件源电话结束、SMS 到达、新 lead、员工在 UI 操作task 可能从多条入口产生,不能只设计电话或只设计前端
2. 触发与过滤层代码去重、合并短时间内多条 SMS、过滤 thanks/ok、exact STOP 直接 DNC、判断是否值得调用 AI控制成本和噪音;简单确定的事不需要大模型判断
3. Task Orchestrator代码里的统一 task 写入服务统一接收 lead pipeline、contact analyzer、staff API、future AI agent 的 task mutation 请求避免每条路径各自直接改 task,导致语义不一致
4. 读取业务上下文Orchestrator / repository读取同一个 store 下的 contact、open tasks、recent calls/messages、progress history、timelineAI 和代码都必须基于同一个上下文判断,不能跨 store 或漏掉已有 open task
5. AI 语义判断Prompt / model只提出 proposal: 建什么目标、记录什么进展、是否完成、建议下一步语义判断需要读懂 transcript/SMS/contact history,但 AI 不直接写库
6. 代码执行层Orchestrator state transition校验 proposal、检查 taskId/store/status/DNC/enum/idempotency,然后在 transaction 里写库真正改变系统状态必须 deterministic、可审计、能处理并发
7. 写入结果DB transactiontasks, task_progress_events, contact_timeline让 task 当前状态、执行历史、contact 时间线同时一致
8. 下游消费UI / metrics / API / playbook前台看 open tasks,店长看 completed objectives 和 attempts,agent 读 typed work object同一份数据服务不同使用者,不用各自推断

SMS 在这个 workflow 里的位置:SMS 先作为 messages activity 存下来,再进入"触发与过滤层"。thanks/ok/emoji 这类 trivial message 不触发 AI;STOP 这类确定 DNC 由代码直接处理;购买、取消、投诉、预约、回流、DNC 自然语言表达等 meaningful SMS 才触发 Task Orchestrator。也就是说,SMS 不单独发明一套 task 系统,但它有自己的轻量触发规则。

现有 Workflow 及问题点

这张图描述的是当前系统怎么跑,以及为什么这个 flow 会让 task 语义混乱。它不是 final design。

步骤现在怎么跑问题在哪里
1. 电话/SMS/lead 进入系统电话会进入 per-call AI;SMS 当前主要先存储;lead pipeline 可直接建 task入口很多,但 task 写入规则不统一
2. 单通电话 AI 写 call factsper-call AI 判断是否打通、是否成功、是否需要后续跟进这些 call facts 是证据,但后续 prompt/代码会把它们和 task outcome 混起来
3. Contact Analyzer 聚合判断prompt 看 contact 历史,输出 create/close/update taskprompt 当前没有 create_closedrecord_progress 这种清晰动作
4. 代码直接写 tasksrepository 按 prompt 输出 create/close/update;staff close API 也直接写 task缺少统一 Orchestrator,不同入口行为不一致
5. 前台 UI 分成 Status Update 和 Close TaskUI 心智其实是"记录进展" vs "关闭事项"后端没有真正的 progress update,所以 no-answer/voicemail 被塞进 close
6. 当场完成的事单通电话 AI 可能判断"业务已完成 + 不需要跟进"Contact Analyzer 当前规则可能不建 task,导致 completed work 漏记

图里的"业务已完成 + 不需要后续跟进"对应当前代码/prompt 信号里的 followUpNeeded = falseprimaryOutcomeResult = success。文档正文避免使用 fu=no + out=success 这种内部缩写,因为它对不熟悉 prompt 字段的人不直观。

Architecture Recommendation

结论: 建一个明确的 Task Orchestrator / task domain service,作为所有 task mutation 的唯一协调层。它不是一个大 prompt,也不是一个新 UI 组件;它是代码里的 state machine + policy + transaction boundary。

责任放在 Orchestrator 的原因
统一入口lead pipeline、contact analyzer、staff API、SMS meaningful trigger、future AI agent 都通过同一套 mutation contract
读取上下文统一读取 contact、open tasks、recent calls/messages、progress history、timeline
决定是否调用 AItrivial no-op / exact STOP / pure retry 可以代码处理;模糊语义才调 prompt
提供 allowed set给 prompt 注入 allowedTypeCategories, allowedProgressTypes, allowedCloseResults
校验 AI proposalZod/schema 校验、taskId/store guard、enum guardrail、confidence policy
执行 state transitioncreate open、create closed、record progress、update、close 都由代码事务执行
写审计同步写 tasks, task_progress_events, contact_timeline
处理并发/幂等unique constraint、idempotency key、conditional update、DNC hard stop

它不负责:

不负责应由谁负责
理解 transcript 里客户到底想取消、升级、投诉还是预约AI semantic proposal
生成个性化 playbook 文案playbook prompt
直接改 UI 文案frontend/UI layer
替代 calls/messages 原始 activity logactivity pipeline

3. Final Schema

结论: tasks 存当前事项快照和最终结果;task_progress_events 存执行过程;contact_timeline 存统一时间线投影。

tasks: work object snapshot

字段/概念说明现在是否已有Final 动作
taskId / idtask 主键已有保留
contactPhone, storeIdstore-scoped contact identity已有保留;所有 read/write 继续带 storeId guard
typeCategory业务目标类型,如 lead_follow_up, retention, upgrade已有保留;AI 在 code-provided allowed set 内选择
status事项是否还 open。schema + code + UI 同步用 open / closed已有 status 字段测试环境一次性 UPDATE + 全 repo sweep,不保留 pending 命名
priority, dueAt当前排序和下一次处理时间已有保留;progress update 可调整 dueAt
suggestedActionsAI 或规则生成的当前建议已有保留;由 prompt 生成,代码校验长度/格式
attemptCount列表展示用的尝试次数快照表里没有字段;当前 task list API 用历史 closed sibling tasks 推导 attempt_count可新增 denormalized counter;source of truth 是 task_progress_events
closeType任务是人工关闭、AI 自动关闭、还是当场完成创建 closed已有部分 close typecreate_closed;保留 manual_closed, auto_closed
closeResult最终 business outcome已有,但混入 disposition保留字段,清理语义:只放 outcome,不放 no-answer/voicemail
closedAt, closedBy...close metadata已有保留;补足 actor/executor attribution
executorType完成/执行者类型: human, ai_agent, system新增新增,为 future AI agent 和 metrics 归因预留

tasks.closeResult: keep vs move out

结论: closeResult 不删除这个字段,但要把它从"混合结果桶"改成"最终业务 outcome"。

当前/目标值Final 去向原因
booked, converted, cancel_saved, issue_resolved, not_interested, wrong_number, do_not_contact, cancelled留在 tasks.closeResult这些表示事项终局
no_answer, left_voicemail移到 task_progress_events.progressType这是一次执行进展,不是事项完成
callback_later拆分语义后迁移客户要求回拨是 progressType=callback_requested;真正完成改约才可能是 closeResult=rescheduled
rescheduled可作为新 closeResult,但只用于"改约这个业务目标已完成"避免和普通 callback/follow-up 混淆
attemptedFuture 不再作为默认 closeResult;改由 progress events + attemptCount 表达"尝试过"是过程,不是业务终局
unable_to_reach建议新增 terminal closeResult,用于达到最大尝试次数后仍联系不上它表达最终结论"无法联系上",比 attempted 更像业务 outcome
already_member建议保留为 terminal closeResult 或并入 other 前先产品拍板它表示这个 task 目标不成立,不是一次联系进展
other保留,但要求 closeNote作为无法分类的 terminal outcome fallback

attempted: final definition

结论: attempted 不再作为 future 默认 closeResult。尝试过程用 task_progress_eventsattemptCount 表达;真正终止 task 时,必须写一个 terminal business outcome。

场景Final 行为
打了一次没人接不 close task;写 progressType=no_answer;attemptCount += 1;task 仍 open
留了 voicemail不 close task;写 progressType=left_voicemail;task 仍 open
发了 SMS不 close task;写 progressType=text_sent;等待 meaningful reply 或下一步
达到最大尝试次数仍联系不上close task;建议 closeResult=unable_to_reach;同时保留 progress history 说明尝试过几次
AI 低置信度、不确定结果不用 attempted 兜底 close;输出 no_op / needs_review / low-confidence proposal,由代码或人决定是否执行

历史 attempted 数据暂不作为本设计处理范围。这里定义的是 future semantics 和新写入行为。

task_progress_events: progress source of truth

字段说明现在是否已有Final 动作
taskId所属 task新增新表必需
storeId, contactPhone查询和 store guard 用新增建议冗余,避免每次 join tasks
progressTypeno_answer, left_voicemail, text_sent, callback_requested, follow_up_scheduled, customer_considering新增;其中部分值现在误在 closeResult新增 enum/constant
channelphone, sms, voicemail, email, future channel新增新增
actorType, actorIdstaff / system / ai_agent / contact_analysiscontact_timeline 有类似 actor 概念新增到 progress event,便于 task-level query
callId, messageId关联 activity evidencecalls/messages 已有 id新增 nullable FK/reference
note员工或 AI agent 的简短说明task note 已有,但不是 per-attempt新增,记录这一次进展
nextDueAt这次 progress 后建议/确认的下一次处理时间tasks.dueAt 已有当前值新增,保留历史为什么 dueAt 被改
occurredAt事件发生时间新增新增
idempotencyKey防重复写入部分路径有 onConflictDoNothing新增,统一去重

contact_timeline: contact-level timeline projection

contact_timeline 继续做统一 timeline 和审计,但不承载所有 task progress 查询语义。每次 task progress 应同步写入 timeline event:

task_progress_events: 业务事实 SoT
contact_timeline:     contact 维度 timeline projection / audit feed
tasks:                当前快照
字段/概念说明现在是否已有Final 动作
eventTypetimeline 事件类型已有新增/使用 task.progress_recorded
entityType, entityId指向 task/call/message 等实体已有progress event 投影时 entityType=task, entityId=taskId
actor fields谁触发了这次变化已有保留,与 progress event actor 对齐
AI forensic fieldsAI run/model/confidence/evidence已有保留;AI proposal 导致 mutation 时写入
oldValue, newValue / metadatatimeline 展示和审计 payload已有progress 投影写简洁摘要,不要让 timeline 成为唯一 source of truth

4. Progress Write Path

结论: 一次 progress update 应该在一个 transaction 里写 progress event、timeline event,并更新 task 快照。

例子: staff 点击 Left Voicemail

1. INSERT task_progress_events
   progressType = left_voicemail
   channel = phone
   actorType = staff
   callId = ...
   occurredAt = ...

2. INSERT contact_timeline
   eventType = task.progress_recorded
   entityType = task
   entityId = taskId
   newValue = { progressType, channel, callId }

3. UPDATE tasks
   attemptCount = attemptCount + 1
   dueAt = next retry window
   updatedAt = now

Timeline event mapping

结论: task_progress_events 新增后,现有 contact_timeline 不废弃。两者并存:progress table 是 task progress 的 SoT,timeline 是 contact-level 展示和审计投影。

Task 行为Source of truthTimeline event说明
创建 open tasktaskstask.created保留现有语义
创建 closed tasktaskstask.created + task.status_changedtask.closed可按现有 timeline 事件模型落地;重点是能审计 create_closed
记录 progresstask_progress_events新增 task.progress_recorded例如 no answer、voicemail、text sent、callback requested
更新 dueAt/priority/suggestedActionstaskstask.due_at_changed / task.updated保留现有事件
关闭 tasktaskstask.status_changed / task.closedcloseResult 只写 business outcome
staff override / dismiss AI tasktasks + optional progress/reasontask.updated / task.status_changed with actor=staff显式记录人覆盖 AI 的理由

Transaction policy:

问题推荐
progress 写成功但 timeline 写失败怎么办整个 transaction fail;不要留下没有审计投影的 progress
timeline 是否可反推 progress不作为 source of truth。API 查 task progress 读 task_progress_events,timeline 只用于 contact 视图
progress 是否需要约束需要 CHECK/enum 限制 progressType/channel/actorType;taskId 必填;occurredAt 必填
progress 是否需要幂等需要 idempotencyKey unique,或 (taskId, callId/messageId, progressType) partial unique,避免同一 call/SMS 被重复记录

Why not only contact_timeline

只用 timeline 的问题独立 progress events 的好处
需要从 JSONB 里 parse progress结构化字段直接查询
task detail 要过滤跨实体 timelineWHERE task_id = ... 即可
manager metrics 要从 timeline 推断 attemptprogress 表直接聚合
API/AI agent 要理解 timeline schemaendpoint 返回 typed progress objects

查询性能

性能不是反对 task_progress_events 的理由。Postgres 很适合这种 append-only event 表,关键是索引:

查询索引
一个 task 的进展(task_id, occurred_at DESC)
门店 workload(store_id, occurred_at DESC)
员工 attempt(actor_type, actor_id, occurred_at DESC)
类型/渠道统计(progress_type, channel, occurred_at DESC)

tasks.attemptCount 只是列表快照;纠错和历史以 task_progress_events 为准。


5. API Contract

结论: 未来 API / CLI / OAuth / AI agent 不应该解析 prompt 文本或 timeline JSONB。它们应该读 typed work objects。

推荐 API shape

GET /v3/tasks/:taskId
  -> task snapshot

GET /v3/tasks/:taskId/progress
  -> ordered task_progress_events

POST /v3/tasks/:taskId/progress
  -> record_progress, idempotencyKey required

POST /v3/tasks/:taskId/close
  -> close with business outcome

GET /v3/contacts/:contactId/timeline
  -> cross-entity contact_timeline

GET /v3/contacts/:contactId/work-objects
  -> open/closed objectives + recent progress + recommended next action

API response should separate three concepts

{
  "task": {
    "id": "task_123",
    "status": "open",
    "typeCategory": "lead_follow_up",
    "priority": "high",
    "dueAt": "2026-05-30T21:00:00Z",
    "attemptCount": 2
  },
  "latestProgress": {
    "progressType": "left_voicemail",
    "channel": "phone",
    "occurredAt": "2026-05-30T18:10:00Z"
  },
  "close": null
}

Closed example:

{
  "task": {
    "id": "task_456",
    "status": "closed",
    "typeCategory": "lead_follow_up"
  },
  "latestProgress": {
    "progressType": "callback_requested",
    "occurredAt": "2026-05-30T17:20:00Z"
  },
  "close": {
    "closeType": "manual_closed",
    "closeResult": "booked",
    "closedAt": "2026-05-30T18:00:00Z"
  }
}

6. Prompt Contract Derived From API

结论: prompt 的职责不是"设计 task 状态",而是在 API/schema 允许的动作空间里提出语义 proposal。

Prompt input should include

输入说明
contactstore-scoped customer identity and lifecycle context
openTasks当前可操作的 open work objects,带 taskId, typeCategory, dueAt, attemptCount, recent progress
recentActivitiescalls/messages/lead events,只作为 evidence
recentProgresstask-specific progress history,避免重复记录同一件事
allowedProgressTypes当前系统支持的 progress enum
allowedCloseResults当前系统支持的 business outcome enum
allowedTypeCategories由代码按 customer lifecycle 算出的可选 category set
storePolicyDNC、SLA、retry threshold、store guard 等 deterministic policy 摘要

Prompt output should be proposed mutations

{
  "taskDecisions": [
    {
      "action": "record_progress",
      "taskId": "task_123",
      "progressType": "callback_requested",
      "evidence": "Customer asked to be called back Friday afternoon.",
      "nextDueAtHint": "2026-06-05T18:00:00Z",
      "confidence": 0.88
    },
    {
      "action": "create_closed",
      "typeCategory": "lead_follow_up",
      "closeResult": "booked",
      "evidence": "Staff booked an intro class during the call.",
      "confidence": 0.93
    }
  ]
}

Prompt must not do

禁止事项原因
直接决定 tasks.status mutationstate transition 必须由代码执行
no_answer, left_voicemail, text_sent 输出为 closeResult这些是 progress,不是 business outcome
invent taskId只能引用 input 里的 open task
绕过 allowedTypeCategoriescategory guardrail 由代码根据 lifecycle 提供
决定是否通过 DB unique / idempotency / concurrencyprompt 不掌握全局并发状态
把 timeline 当 source of truth 改写timeline 是 projection/audit,不是 prompt 直接操作对象

7. Code vs Prompt Responsibilities

结论: AI 负责语义判断,代码负责世界状态。typeCategory 是 hybrid: AI 在代码允许范围内选择。

决策点推荐负责层为什么为什么不放另一层现状
是否触发 task pipelineCodeevent、debounce、cost gate、SQS 幂等都应 deterministicprompt 只有被调用后才存在,不能决定是否调用自己per-call followUp / cron / on-demand / task close 等多入口分散触发
是否值得深度分析Code first,AI secondtrivial SMS/no-answer/system noise 可代码过滤;边界语义再给 AI纯 AI 成本高;纯代码无法判断自然语言购买/取消/投诉意图call 已有 pre-triage;SMS 当前只存储等 batch
typeCategoryAI choice + code allowed set需要理解 customer lifecycle 和通话语义纯代码会变硬编码 taxonomy;纯 AI 会越界选不存在/不该选的 categoryprompt 直接选,代码 guardrail 不够显式
当场完成是否建 taskCode policy + AI/staff evidencecreate_closed 是产品规则和状态机能力;完成事实来自语义证据只靠 prompt 会被 repository/schema 卡住;只靠代码读不懂 transcriptprompt 禁止 resolved interaction 建 task;repository 只能 insert open
Progress UpdateCode state machine + staff/AI inputprogress 是同 task 的 append-only record,不是 closeprompt 不应维护 attempt counter/dueAt;close API 不应承载 progressclose.ts 把 no_answer/voicemail close + recreate
priorityAI suggestion + code normalization业务紧急度需要语义;SLA/dueAt 上下限要 deterministic纯 AI 不稳定;纯代码不知道取消/投诉/高意向 nuanceprompt 输出 priority,代码算 dueAt
suggestedActionsAI/prompt下一步话术和行动依赖 transcript/contact context代码模板只能 fallback,不够个性化lead task 有模板;AI task 有 suggestedActions
Closestaff/AI proposal + code execution结果语义由人/AI提出;状态迁移必须原子、幂等、可审计纯 AI 会 hallucinate taskId/race;纯代码无法判断 cancel saved/resolvedAI auto-close + staff close 两条路径
DNCCode hard stop + AI detectionexact STOP 可代码判;自然语言 DNC 需要 AI纯 AI 风险太高;纯代码漏掉 "please stop calling me"doNotContact 在 contact 层,task close 行为不统一
PlaybookDedicated prompt + code context/cachingplaybook 是执行指导,不是 task mutation放 task prompt 会混淆决策和教练;纯代码模板太死前端 panel 有,后端 prompt 未完整接入
并发/幂等/fallbackCode/DB必须跨 invocation 一致prompt 没有全局状态和事务能力依赖部分 unique/onConflict,但没有统一 task mutation layer

typeCategory allowed set

lifecycle / contact stateAI 可选 typeCategoryCode guardrail
lead activelead_follow_up, booked_not_convertedAI 不得创建 lead_outreach
member activecancellation_risk, retention, upgrade, renewal, referral必须有对应语义证据
churned re-engagementwin_back只有客户主动回流才允许
terminal / DNC / wrong numbernone,只允许 close existing禁止创建 outreach task

8. UI Semantics

结论: Tasks 页面应明确分成两种操作:记录进展 vs 关闭事项。左侧操作建议命名为 Progress UpdateLog Progress,不要叫后端意义上的 status update。

UI 区域后端含义允许动作
Progress Update / Log Progress任务还没完成,记录一次执行进展No Answer, Left Voicemail, Text Sent, Follow-up Scheduled, Callback Requested, Note
Close Task任务已经完成或终止,记录最终业务结果Intro Booked, Converted, Cancel Saved, Issue Resolved, Not Interested, Wrong Number, Do Not Contact, Cancelled

Prompt 输出也应使用 progress proposal,而不是 "status update":

{
  "action": "record_progress",
  "taskId": "uuid-from-input",
  "progressType": "callback_requested",
  "reason": "Customer asked staff to call back Friday afternoon.",
  "nextDueAtHint": "Friday afternoon",
  "confidence": 0.88
}

Human / AI Race Conditions

结论: 人可能比 AI 快,AI 也可能比人快。UI/API 必须让 staff override 成为一等行为,但所有写入仍走同一个 Orchestrator。

场景正确行为设计影响
人打完电话立即 close,AI 还在分析staff close 先成功;AI later proposal 发现 task 已 closed 后 no-op 或追加 audit note,不能 reopen/重复 closeclose API 要 conditional update;AI mutation 要读最新 task status
AI 先创建 open task,人认为不需要staff 可以 dismiss/close with not_interested, wrong_number, already_member, other 或 future not_needed;记录 staff override reasonUI 需要提供 dismiss/close path;timeline 记录 human override
inbound 当场成交,人先记录 closed work,AI 后分析同一通电话人的 create_closed/close 是 authoritative;AI 后续只能补 evidence 或 no-op,不能再建重复 taskidempotency key 应包含 call/message evidence;create_closed 需要防重复
人记录 progress,AI 同时建议 closeOrchestrator 以最新状态和 evidence 决定;如果 close 证据强且 task 仍 open,可 close;否则保留 progress 并进入 reviewprogress 和 close 都必须走同一状态机
AI 低置信度不自动 mutation;进入 needs_review 或 no-op + audit避免用 attempted/other 作为 AI 万能兜底

9. Scenario Coverage

结论: 同一套 objective/progress/outcome 模型覆盖关键场景,不需要为每个场景写特殊状态。

场景正确行为
lead no answer原 task 仍 open;写 progressType=no_answer;更新 attempt/dueAt
left voicemail原 task 仍 open;写 progressType=left_voicemail
text sent原 task 仍 open;写 progressType=text_sent;meaningful reply 再触发分析
customer asks callback later原 task 仍 open;写 progressType=callback_requested;设置 nextDueAt
intro bookedcloseResult=booked;无 open task 时 create_closed
inbound 当场成交create_closed,closeResult=converted
cancel savedclose existing 或 create_closed,closeResult=cancel_saved
complaint resolvedcloseResult=issue_resolved;仍有 promise/escalation 时只 update/progress
DNCexact STOP 代码处理;自然语言 DNC 由 AI;关闭 open outreach task
SMS meaningful replytrivial 不触发;购买/取消/投诉/预约/回流/DNC 意图触发 orchestrator
跨门店同号码所有 read/write 以 (contactPhone, storeId) 隔离

10. Explicit Conflict Decisions

结论: brief 里的冲突不是二选一,但每个都要有明确边界。推荐方案是统一 work object + progress/outcome 分层。

冲突问题明确结论理由
"每个当场办成的事都是 task" vs "conversion funnel 不是 completed task"当场办成的 customer-work objectivecreate_closed task;普通 funnel event 不是 tasktask 记录前台/AI agent 完成的事项,不替代 conversion funnel
"只提醒一次" vs "没打通还要回头再试"只提醒一次 = 不重复创建同一 objective;不是只尝试一次no-answer/voicemail 是 progress,原 task 仍 open,用 dueAt/attemptCount 管下一次
"AI 判断灵活" vs "代码状态机 deterministic"AI 提 semantic proposal;代码执行 state transitionAI 适合理解意图,代码负责幂等、并发、审计和 guardrail
"SMS 不想触发 AI" vs "有意义 SMS 需要即时跟进"SMS 先走 code gate/debounce;meaningful reply 再触发 orchestrator"thanks/ok" 不花 LLM 成本;购买/取消/投诉/DNC/预约类回复需要及时处理
"task 是待办" vs "task 是业绩记录"task 是统一事项对象: open 是待办,closed 是事项结果记录用 lifecycle 区分当前状态,用 closeResult 区分业务结果,不会污染待办列表
activity vs task progress vs business outcome三层不同,不压进同一个字段activity 是客观互动;progress 是任务处理过程;outcome 是最终业务结果

11. Metrics Impact

结论: completion 和 attempt workload 必须分开。

指标推荐口径
Completed objectivestasks.status='closed'
Business winsclosed tasks where closeResult IN ('converted','booked','cancel_saved','issue_resolved','win_back','renewed','upgraded','referral_obtained')
Attempt workloadtask_progress_events count,按 progressType/channel/staff 分组
Open workloadopen tasks by dueAt/backlog/priority
Productivity ratecompleted objectives / (completed objectives + still-open objectives in window) 或 manager review 后定;不能用 closed_by_staff_name IS NOT NULL 过滤分母

12. Implementation Touchpoints

结论: 这是 final target 的设计清单,不是阶段拆分。

区域文件/模块改动方向
Task schemacallytics-common/src/db/schema/tasks.ts支持 create_closed;TASK_STATUS = ['open', 'closed'] + DEFAULT 'open'(原 pending/closed,测试环境一次性迁移);closeResult 保持 business outcome;加 executorType;可保留 attemptCount
Progress schematask_progress_events定义 progressType, channel, actorType, callId, messageId, nextDueAt, note, occurredAt
Timeline schemacontact_timeline增加/使用 task.progress_recorded event projection
Task UI enumcallytics-common/src/db/schema/task-ui.tsCLOSE_RESULT_OPTIONS 只展示 outcome;progress options 单独建常量
AI decision schemacontacts-analyzer/src/core/models.tsTaskDecision 支持 create_open, create_closed, update, close, record_progress
Contact promptcontacts-analyzer/src/core/prompt-builder.tsSECTION 4 改成 objective/progress/outcome 规则
Orchestratorcontacts-analyzer + studio API shared task domain module统一执行 task mutation: validate, state transition, idempotency, timeline
Staff APIstudio-website-monorepo/apps/api/src/routes/tasks/*close API 只 close outcome;新增 progress update API
Frontendapps/web/src/pages/tasks/components/*Status Update 改成 Progress Update 语义
Dashboardapps/api/src/routes/v3/dashboard-staff.tscompletion 和 attempt workload 分开
External APIapps/api/src/routes/v3/tasks/*, contacts/*task snapshot、progress history、timeline、work-objects endpoint
Prompt evalprompt-eval/expected_results.json加 create_closed、record_progress、DNC、SMS meaningful expected outputs
Playbookplaybook-panel.vue + backend route/prompt用 task + progress history + contact context 生成

Implementation Staging, Without Weakening Final Design

结论: final model 一次定清楚;实现可以先做最小闭环,但不能把 schema/prompt 设计成只适合短期。

类别应先落地Future-compatible 但 schema/API 要预留
Lifecycleschema + code + UI 同步 open/closed(测试环境直接迁移)不保留 pending 命名
Progressfirst-class task_progress_events;staff progress API;no-answer/voicemail/text sent 从 close 移出AI agent 自动记录 progress;更多 channel/email/chat
Outcomeclose API 只接受 business outcome;支持 create_closed更细 revenue attribution / outcome taxonomy
Orchestratorstaff API + contact analyzer task mutation 先统一SMS meaningful trigger、AI voice agent、third-party API 复用
PromptTaskDecisionrecord_progress, create_closed;allowed set guardraillow-confidence human review, model routing
Timelineprogress write 同 transaction 投影到 contact_timelinericher audit/event replay
Metricscompleted objectives 与 attempt workload 分开dashboard 体系后续再整体重做

13. Rationale / Deep Dives

1. 为什么不是 Agile-style workflow status

Agile task status 管的是工程流程,例如 Todo -> In Progress -> In Review -> Done。Retaintive task 管的是客户事项是否还需要处理。

不推荐:

open -> attempted -> voicemail_left -> waiting_callback -> text_sent -> closed

推荐:

task.status = open
latestProgress = left_voicemail
attemptCount = 2
dueAt = tomorrow

2. callback_later 怎么拆

场景正确定义
客户说 "Friday call me back"progressType=callback_requested,task 仍 open
staff 自己安排明天再试progressType=follow_up_scheduled,task 仍 open
任务目标就是 reschedule class,且已改好时间closeResult=rescheduled,task closed

3. closeResult 只保留 business outcome

下面两组不是映射关系,而是两类不同 enum。

Business outcome examples:

converted
booked
cancel_saved
issue_resolved
not_interested
wrong_number
do_not_contact
unable_to_reach
other

Progress examples:

no_answer
left_voicemail
text_sent
callback_requested
follow_up_scheduled
customer_considering

4. 并发 / 幂等 / fallback

风险代码策略
AI output parse fail不写 mutation;保留 open task;记录 failure/timeline
AI hallucinated taskId只允许引用同 (phone, storeId) 的 open tasks
staff 和 AI 同时 closeWHERE status='open' 保证只有一个成功
staff record progress 时 AI 正在 closestate machine 先读最新 status;closed 后 progress 只能作为 late activity
cron 与 per-call 同时 createunique index + idempotency key + conflict no-op
DNC 与新 task 同时出现DNC hard stop 优先;阻止 create outreach,关闭 existing open task

5. SMS 为什么不单独做一套 task pipeline

结论: SMS 应该先作为 activity 存储,再通过 channel-specific trigger/filter 进入同一个 Task Orchestrator。不要为 SMS 发明另一套 task lifecycle。

问题设计判断
SMS 要不要触发 AI不是"永远不触发",也不是"每条都触发"。先代码过滤 trivial message,meaningful SMS 才触发
什么是 trivial SMSthanks, ok, emoji, 自动回复,没有业务意图的确认
什么是 meaningful SMS购买/预约/取消/投诉/回流/DNC/改时间/明确提出下一步
多条 SMS 怎么处理短窗口 debounce/merge,合并后再判断,避免客户连续发 3 条导致 3 次 AI 分析
SMS 和 call 是否共用 prompt最终共用 task mutation contract,但前置 gate 和输入摘要可以按 channel 区分
为什么不单独建 SMS task 系统task 表达的是 customer-work objective,不是 channel。电话、SMS、future email/chat 都应该汇入同一个 objective/progress/outcome 模型

业界老 CRM 常把 call、SMS、email 都挂到同一个 contact activity/timeline 下,但触发 task 的规则常因 channel 不同而不同。这个方向对 Retaintive 有参考价值:统一 objective,但不统一所有触发成本。2026 AI 成本下降后,也不应该简单照搬"SMS 一律等 daily batch";更合理的是 lightweight code/classifier gate + meaningful message 触发 orchestrator。

6. 竞品 API 校准

API / 产品观察对我们的影响
HubSpot Tasks APItask status 是 lifecycle,如 COMPLETED / NOT_STARTED不把 No Answer 当 task status
HubSpot Calls APIcall outcome/status 放在 call activity 上disposition/progress 从 close outcome 分离
Salesforce TaskTask 有 Status,也有 call fields可以关联 task 和 call,但不要混语义
SalesloftCalls / Call Data Records / Dispositions / Tasks 分 endpoint不同对象有不同 API contract
Outreachcall task mapping 特别处理 no-answer 和 duplicate submissionsduplicate prevention 和 idempotency 放代码层

竞品只是校准。很多旧 CRM 设计来自人工录入、AI 不成熟、LLM 成本高的时代。Retaintive 应利用 AI-native 条件,提供更清楚的 work-objects API,让外部系统和 AI agent 一次读懂当前事项、进展、结果和下一步。

Official references:


14. Brief Compliance Checklist

结论: 当前版本满足 brief 的核心交付和 acceptance criteria。剩余需要 Max review 的不是文档遗漏,而是产品 policy 拍板。

Brief 要求覆盖位置状态
代码 vs prompt 职责矩阵§7Covered
现状 vs 设计后 workflow 图§2Covered
architecture recommendation§2 Architecture RecommendationCovered
gap 分析文档更新本文档即为 gap analysis 的 Codex review-basis 版本;旧 V2 docs 已归档避免混读Covered
需要改的代码/prompt 清单§12Covered
API-first / AI-agent-readable 视角§5, §6Covered
task 到底是什么Executive Summary, §1Covered: unified Work Object
每个"谁负责"说明为什么不是另一层§7Covered
关键场景覆盖§9Covered
final target vs future-compatible§12 staging tableCovered without changing final model
six conflict questions§10Covered
activity / progress / outcome 分层§1, §3, §10Covered
SMS 留接口但不设计完整 SMS pipeline§2, §7, §9, §10, §13.5Covered
metrics 只说明影响,不重做体系§11Covered
future AI agent compatibility§3, §5, §6, §12Covered

Open product policy items:

问题推荐默认值
create_closed 是否计入 productivity计入 completed objectives,按 actor/executorType 归因
rescheduled 是 closeResult 还是 progress只有业务目标就是 reschedule 且已完成时才是 closeResult;普通"回头再打"是 progress
lead retry threshold默认 3 次 meaningful attempts,但按 task type 可配置
low-confidence AI proposal 怎么处理不自动 mutation;进入 human review 或 no-op + audit