> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# 后端架构(Unified Pipeline 当前状态)

> **Phase 1 进行中**(2026-06-06 verify)。Design 见 [unified-pipeline-final.md](/product-design/v2/unified-pipeline/unified-pipeline-final.md)
>
> **怎么读**:
>
> - **没 context**:看 Layer 1(30 秒)
> - **后端 / debug pipeline**:Layer 1 + Layer 2(5 分钟)
> - **要改代码 / 写 prompt**:Layer 3 各 zoom-in(按需)
>
> **图例**:✅ Phase 1 已收口 · ⚠️ 部分收口 · ❌ 未做

***

## Layer 1 — Mental Model(30 秒)

> 整个系统在干什么?

<object type="image/svg+xml" data="/images/backend-l1-mental-model.svg" style="max-width: 820px; width: 100%;">
Mental Model — 4 类外部事件 → AI 提案层 → Policy Guard + 3 个 Writer → 7 张 Neon 表
</object>

***

## Layer 2 — 后端 Pipeline 拓扑(5 分钟)

> 后端 Lambda + SQS + 表怎么连?Phase 1 哪里 done 哪里没 done?

<object type="image/svg+xml" data="/images/backend-l2-topology.svg" style="max-width: 1000px; width: 100%;">
后端拓扑 — 4 类外部触发 → 5 Lambda + studio-api → 共享写入层 → Neon 表。颜色编码 ✅⚠️❌ Phase 1 接入状态
</object>

**file:line 速查**:

| 路径                                                     | 状态 | 位置                                                                  |
| ------------------------------------------------------ | -- | ------------------------------------------------------------------- |
| contacts-analyzer → Task Orchestrator                  | ✅  | `lambda/contacts-analyzer/src/infrastructure/neon-repository.ts:29` |
| lead-processor → Task Orchestrator                     | ✅  | `lambda/lead-processor/src/core/persist-downstream.ts:38`           |
| message-processor → Contact Writer + Task Orchestrator | ✅  | `lambda/message-processor/src/infrastructure/neon-repository.ts:25` |
| ai-analysis-processor → Contact Writer                 | ⚠️ | 用 `NAME_TRUST` 常量,但没用 `upsertIdentity()`                            |
| studio-api → Task Orchestrator                         | ❌  | `apps/api/src/routes/tasks/{close,reopen,postpone}.ts` 仍是 raw SQL   |

***

## Layer 3a — AI Prompt 全景

> 所有 AI 决策点 / 触发 / 写哪。每个 prompt 节点 clickable 跳全文。

<object type="image/svg+xml" data="/images/backend-l3a-prompts.svg" style="max-width: 1000px; width: 100%;">
Prompt 全景 — 7 prompt + 1 代码 stage + 1 brain,按 Lambda 分组,标 file:line / 类型 / 写哪
</object>

**file:line 速查**:

| #  | Surface          | 文件:行                                                                     |
| -- | ---------------- | ------------------------------------------------------------------------ |
| ①  | Pre-triage       | `ai-analysis-processor/.../stages/pre-triage.ts:36 preTriageByRules`     |
| ②  | Triage           | `ai-analysis-processor/.../stages/prompts.ts:23 TRIAGE_SYSTEM_PROMPT`    |
| ③  | Classify         | `ai-analysis-processor/.../stages/prompts.ts:80 CLASSIFY_SYSTEM_PROMPT`  |
| ④  | Verify           | `ai-analysis-processor/.../stages/verify.ts:39 VERIFY_SYSTEM_PROMPT`     |
| ⑤  | Coaching         | `ai-analysis-processor/.../stages/prompts.ts:225 COACHING_SYSTEM_PROMPT` |
| ⑤b | Coaching Brain   | 🆕 待新建 `ai-analysis-processor/.../stages/coaching-brain.ts`              |
| ⑥  | Contact Analyzer | `contacts-analyzer/src/core/prompt-builder.ts:406 buildSystemPrompt`     |
| ⑦  | Task Playbook    | 🆕 代码无 — 待新建 lambda + studio-api Playbook tab                            |

***

## Layer 3b — Prompt Before / After 对比

> 2026-06-02 用户交付 6 份 prompt 文件(`02-classification` / `03-coaching` / `03b-coaching-brain` / `04-contact-profile` / `05-task-decision` / `05-task-playbook`)。**全部还没接入代码**。这一节回答:**接入后哪些 prompt 被替换 / 新增 / 不动?**

<object type="image/svg+xml" data="/images/backend-l3b-before-after.svg" style="max-width: 1100px; width: 100%;">
Prompt Before/After 对比图 — 左列当前 5 prompt + 1 代码 stage(live);右列接入后 7 prompt + 1 brain + 1 代码 stage。REPLACE/拆出/新增/不动 按行颜色编码,每个节点 clickable 跳到 prompt 全文
</object>

### 6 份新文件 → 接入位置(精简表)

| 新文件                                                                                                          | 替代 / 新增                                                                                      | 接哪个代码文件                                                                          | 文档站对应                                                 |
| ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------- |
| [`02-classification-system-prompt.txt`](/product-design/v2/unified-pipeline/prompts/02-classify.md)          | **REPLACE** 老 Classify(`prompts.ts:80`),改 enum 结构 + 新增 `topic_type` standard/cancellation 互斥 | `ai-analysis-processor/.../stages/prompts.ts:80` 替换 `CLASSIFY_SYSTEM_PROMPT` 字符串 | `prompts/02-classify.md`(覆盖)                          |
| [`03-coaching-system-prompt.txt`](/product-design/v2/unified-pipeline/prompts/04-coaching.md)                | **REPLACE** 老 Coaching(`prompts.ts:225`),只留框架                                                | 同 prompts.ts 替换 `COACHING_SYSTEM_PROMPT`                                         | `prompts/04-coaching.md`(覆盖)                          |
| [`03b-coaching-playbook-brain.txt`](/product-design/v2/unified-pipeline/prompts/04b-coaching-brain.md)       | 🆕 **新增** read-only Brain 层(老代码 0 hit)                                                       | 新建 `stages/coaching-brain.ts` + 改 `coaching.ts` 把 brain 拼到 user message          | `prompts/04b-coaching-brain.md`(新增)                   |
| [`04-contact-profile-system-prompt.txt`](/product-design/v2/unified-pipeline/prompts/05a-contact-profile.md) | **拆出** 老 Contact Analyzer 的画像段                                                               | `contacts-analyzer/.../prompt-builder.ts:406` 改成 2 个内部函数,这是其一                    | `prompts/05a-contact-profile.md`(新增,填 planned 占位)     |
| [`05-task-decision-system-prompt.txt`](/product-design/v2/unified-pipeline/prompts/06-task-decision.md)      | **拆出** 老 Contact Analyzer 的 task 段                                                           | 同上,这是其二;两段拼成一个 system prompt 一次调用                                                | `prompts/06-task-decision.md`(新增,填 planned 占位)        |
| [`05-task-playbook-system-prompt.txt`](/product-design/v2/unified-pipeline/prompts/07-task-playbook.md)      | 🆕 **全新增**(老代码 `grep TASK_PLAYBOOK` 0 hit)                                                   | 待新建 lambda + UI Playbook tab 引用                                                  | `prompts/07-task-playbook.md`(已存在,2026-06-01 用户提供 V1) |

### 关键约束(防止接入时搞错)

- **拆开但仍是 1 次 LLM 调用** — 04 / 05 拆开**不是** 2 次 API 请求,只是把 588 行 prompt 拆成 2 段文本然后在 `buildSystemPrompt()` 里拼回去(详见 [拆 prompt 的方式](/product-design/v2/unified-pipeline/unified-pipeline-final.md#拆-prompt-的方式拆文本组装不拆-llm-调用经-codex--gemini-cross-validate))
- **05a 不输出 taskDecisions** / **06 不输出 contact 画像字段** — 两个 prompt header 都写明 hard boundary
- **06 可以读 05a 的输出当输入**(单向依赖:先画像后 task 决策)
- **Playbook 三处不要混**:(a) Coaching Brain(coaching 读 / 不输出)(b) Store Playbook(06 Task Decision 可选 input)(c) Task Playbook(给 UI Playbook tab 的输出)

### Prompt 架构细节

Prompt 的职责边界、standard template、schema 注入方式、registry definition render 规则、DeepSeek / OpenRouter structured-output 边界,统一放在 [3-prompt-architecture.md](/architecture/3-prompt-architecture.md)。本页只保留 backend prompt surfaces 的位置图。

***

## Layer 3c — Contacts-Analyzer 节点内部(最复杂)

> 唯一同时写多张共享表的 AI 节点 / 4 个触发源 / 内部 stage 拆解

```
                    contacts-analyzer Lambda 内部
                    ════════════════════════════

  触发源(4 种,共享 dailyBatchQueue FIFO):

  ┌────────────────────────────────────┐
  │ per_call_analysis                  │ ← ai-analysis-processor 每通电话后
  │ cron       (EventBridge 06:00 UTC) │ ← daily fan-out 全 active contacts
  │ on_demand  (studio-api manual)     │ ← UI 触发 manual reanalysis
  │ reprocess  (manual replay)         │ ← cooldown gate (24h)
  └─────────────────┬──────────────────┘
                    │
                    ▼ SQS body: { phone, storeId, source, ... }
  ┌────────────────────────────────────────────────────────────────┐
  │  handler.ts                                                    │
  │                                                                │
  │  1. resolvePhoneIdentity()  ← 查 store_id / franchise_id        │
  │  2. Context Builder         ← 聚合 calls + messages + leads    │
  │                               + contacts + open tasks          │
  │                                                                │
  │  3. ⑥ Contact Analyzer AI 调用                                  │
  │     ┌──────────────────────────────────────────────────────┐   │
  │     │ system prompt (588 行,Target State 拆成 04 + 05)     │   │
  │     │   ├─ [Target] 模板1: Contact Profile (画像)           │   │
  │     │   └─ [Target] 模板2: Task Decision                    │   │
  │     │                                                       │   │
  │     │ user message: 聚合的 calls/messages/leads/tasks 数据  │   │
  │     └──────────────────────────────────────────────────────┘   │
  │                          │ 1 个 JSON output                    │
  │                          ▼                                      │
  │     {                                                          │
  │       contact 字段 (18 个画像 + DNC + lifecycle...),            │
  │       taskDecisions[] (6 种 action)                             │
  │     }                                                          │
  │                                                                │
  │  4. neon-repository.ts                                         │
  │                                                                │
  │     ├─→ ContactWriter.upsertIdentity()  ✅ 已接入               │
  │     │                                                          │
  │     └─→ taskDecisions.map(d =>                                  │
  │            applyTaskAction(client, d, context))  ✅ 已接入      │
  │            (6 处调用,line 702 / 744 / 773 /                    │
  │             805 / 838 / 871)                                   │
  │                                                                │
  │     全部 statement 放进 db.batch() 原子提交                     │
  └──────────────────────┬─────────────────────────────────────────┘
                         │
                         ▼
                   ┌─────────────────────────────────┐
                   │ contacts                        │
                   │ tasks                           │
                   │ task_progress_events            │
                   │ contact_timeline                │
                   └─────────────────────────────────┘
```

***

## Layer 3d — 共享写入层内部

> Phase 1 新建的 4 个 module:接受什么 / 输出什么 / 谁调

```
                                共享写入层(@retaintive/common/domain)
                                ════════════════════════════════════

                  调用方                                Module                          表
                  ──────                                ──────                          ──

  contacts-analyzer  ──┐
                      │       ┌──────────────────────────┐
  lead-processor    ──┼─────→ │  Policy Guard            │
                      │       │  (in-process,不是      │
  message-processor ──┤       │   独立 Lambda)           │
                      │       │                          │
  studio-api (Phase 2) ┘       │  - DNC sticky guard      │
                              │  - storeId guard         │
                              │  - duplicate guard       │
                              │  - 状态机                │
                              │  - 幂等                  │
                              │  - AI hallucination      │
                              │  - human authority       │
                              └────────┬─────────────────┘
                                       │ 返回: allow / reject / needs_review*
                                       │
                       ┌───────────────┼───────────────┐
                       ▼               ▼               ▼
              ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
              │ Task         │ │ Contact      │ │ Timeline     │
              │ Orchestrator │ │ Writer       │ │ Writer       │
              │              │ │              │ │              │
              │ applyTask    │ │ upsert       │ │ writeTime    │
              │   Action():  │ │   Identity   │ │   lineEvent  │
              │              │ │ setDNC       │ │ event catalog│
              │ 6 个 action: │ │ touchActivity│ │ Zod schema   │
              │ - create_open│ │              │ │              │
              │ - create_    │ │ NAME_TRUST   │ │              │
              │   closed     │ │ 共享常量     │ │              │
              │ - close      │ │              │ │              │
              │ - update     │ │              │ │              │
              │ - record_    │ │              │ │              │
              │   progress   │ │              │ │              │
              │ - reopen     │ │              │ │              │
              │              │ │              │ │              │
              │ + closeAll   │ │              │ │              │
              │   OpenFor    │ │              │ │              │
              │   Contact()  │ │              │ │              │
              └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
                     │                │                │
                     ▼                ▼                ▼
              ┌──────────┐    ┌──────────┐    ┌──────────────────┐
              │  tasks   │    │ contacts │    │ contact_timeline │
              │          │    │          │    │                  │
              │  task_   │    └──────────┘    └──────────────────┘
              │  progress│
              │  _events │
              └──────────┘

  * needs_review: type 第三态,Phase 1 暂不走该分支(低置信度直接 reject)

  Module 文件位置: callytics-common/src/domain/
    ├─ index.ts             (统一 export)
    ├─ types.ts             (Contract types)
    ├─ policy-guard.ts      + .test.ts
    ├─ task-orchestrator.ts + .test.ts
    ├─ contact-writer.ts    + .test.ts
    ├─ timeline-writer.ts   + .test.ts
    └─ idempotency.ts       + .test.ts
```

***

## Phase 1 进度速查

```
✅ 已完成(可以直接用)
   ├─ Schema: tasks.status ['open','closed'] / task_progress_events 表 / attempt_count / source_call_id
   ├─ Shared modules: Task Orchestrator / Contact Writer / Timeline Writer / Policy Guard (含 unit test)
   ├─ contacts-analyzer: 6 处 applyTaskAction 调用
   ├─ lead-processor: applyTaskAction({ action: 'create_open' })
   └─ message-processor: setDNC + closeAllOpenForContact

⚠️ 部分收口(用了共享但没全用)
   └─ ai-analysis-processor: 用 NAME_TRUST 常量 + Drizzle,但没用 ContactWriter.upsertIdentity()

❌ 未做(Phase 1 计划但未完成)
   ├─ studio-api close/reopen/postpone: 仍是 raw SQL,未接入 Task Orchestrator
   ├─ Prompt 拆分: prompt-builder.ts 仍是 single 588 行 prompt(并行外包,文件已交付 — 04 + 05)
   ├─ Coaching brain 拆分: COACHING_SYSTEM_PROMPT 仍含 domain knowledge(文件已交付 — 03 + 03b)
   └─ Task Playbook(⑦): 还没实现
```

***

## 相关文档

- 设计源:[unified-pipeline-final.md](/product-design/v2/unified-pipeline/unified-pipeline-final.md)
- 实施计划:[implementation-plan/normative-spec.md](/product-design/v2/unified-pipeline/implementation-plan/normative-spec.md)
- Task 4 层 Object Model:[task-pipeline-deliverable-codex.md](/product-design/v2/tasks-feature/design/task-pipeline-deliverable-codex.md)
