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

# Task Prompt Architecture And Agent Evolution

> **Current legacy prompt architecture（2026-07-15）**：本文的 push/pull context、schema/writer 分层和 self-check 方法仍可参考；Objective-first、`create_closed lead_follow_up`、Category Matrix 与 global `closeResult` 不是 Target。现行 Task/AI runtime contract 见 [Task System Design V3](/product-design/v3/tasks-feature/task-domain-lifecycle.md)。

## 0. 一句话

retaintive 是一台 **把客户那边发生的任何事，翻译成该达成的目标、该做的事** 的机器。

- **发生的事** = call、SMS、voicemail、lead form；以后还会有 Salesforce、Slack、email、web form。
- **翻出来的** = 今天主要是 `taskDecisions[]` / `task`；产品上正在演进成更稳定的 `objective`。
- **中间那层翻译** = AI 做判断，但 AI 只提出 proposal，不拥有最终写入权。

所以所有 AI/task 设计争议，本质都是同一个问题：**这台翻译机该信什么、谁说了算。**

本文记录两件事：

1. 今天怎么系统性改 `contacts-analyzer` prompt，不再盲改。
2. 未来怎么从 `push` context 走向 `pull` context / self-check agent，但不在底座没稳时上 agent。

## 1. 当前现实：一个 call，内部分层

当前 `contacts-analyzer` **还没有拆成两个 AI call**。runtime 仍然是一个 combined prompt，一次输出：

- contact profile fields
- `taskDecisions[]`

但是 prompt 内部必须按职责分层：

```text
system prompt + user prompt + Zod schema
  -> model output
  -> deterministic fallback
  -> writer / Policy Guard
  -> DB / audit artifacts
```

当前 combined `system prompt` 应该按这个顺序组织：

```text
1. Purpose / worldview
2. Evidence Authority
3. Contact Profile Policy
4. Task Decision State Machine
5. Action Semantics
6. Generated Taxonomy
7. Category Matrix
8. Examples / edge cases
9. Output Contract
10. Input Context Contract
```

在 code 里对应：

| Section                         | 责任                  | 例子                                                      |
| ------------------------------- | ------------------- | ------------------------------------------------------- |
| `contacts-purpose`              | 说明这套 AI 是什么机器       | customer-side events -> objective / work                |
| `contacts-evidence-authority`   | 共享证据裁判规则            | fresh intent、hard facts、hard stops、writer authority     |
| `contacts-profile-policy`       | Contact 判断          | lifecycle、leadStatus、DNC、summary、goals、objections       |
| `contacts-task-decision-policy` | Task 判断             | state machine、action semantics、category matrix、examples |
| `contacts-output-contract`      | JSON shape reminder | 每个 action required / optional fields                    |
| `contacts-input-context`        | user prompt 格式说明    | `RECENT CALLS`、`OPEN TASKS`、`taskRef`、`sourceRefs`      |

## 2. System prompt / user prompt / schema / writer 分工

| Layer                 | 放什么                                                                                             | 不放什么                       |
| --------------------- | ----------------------------------------------------------------------------------------------- | -------------------------- |
| `system prompt`       | 稳定判断标准、Evidence Authority、Contact/Task policy、taxonomy 用法、examples                              | 某个 contact 的 runtime facts |
| `user prompt`         | 单个 contact 的事实材料：stored snapshot、calls、messages、leads、open tasks、closed tasks、playbook guidance | 稳定 business policy         |
| `Zod schema`          | JSON shape、enum、action shape 互斥，以及哪些 model output shape 可以进入 deterministic fallback             | 业务推理理由、最终业务写入权             |
| writer / Policy Guard | 最终写入权威：DNC、store isolation、duplicate、taskRef resolve、evidence attach、DB constraints             | 让模型自由写库                    |
| eval                  | expected behavior、pass criteria、known gaps、case explanation                                     | 生产写库逻辑                     |

关键规则：

- prompt 说 `MUST` 不够；如果字段是最终写入必需，schema、fallback、writer 必须合起来保证它不会以坏状态落库。
- schema 不是永远越严越好。对模型常见但可确定修复的半成品输出，例如 fieldless `update`，schema 可以先放行，让 deterministic fallback 有机会修成 `record_progress` / `close` / valid `update`。
- schema 通过不代表业务正确；writer / Policy Guard 仍然要二次校验。
- user prompt 只是事实输入，不应该塞长期 policy。
- stale contact snapshot 是 context，不是 fresh task intent 的硬 gate。

## 3. Contact 和 Task 的边界

Contact 回答：

```text
这个人是谁？
他们现在是 lead / member / churned / unknown？
关系状态和 profile 怎么更新？
```

Task 回答：

```text
客户侧发生的事说明哪个 objective 状态变了？
应该 create_open、create_closed、close、update、record_progress，还是 []？
```

Shared / Evidence Authority 回答：

```text
多条证据冲突时信谁？
谁拥有最终写入权？
```

重要边界：

```text
A terminal lifecycle state stops proactive future profile-driven outreach.
It does not erase material task ledger outcomes such as create_closed.
```

例子：

- `booked = terminal` 是 lead first-booking lifecycle 的 contact 规则。
- 但如果最新 interaction 里 new lead 当场 booked，且没有 open task，task 层仍应 `create_closed lead_follow_up/booked` 记 ledger。
- 如果已经有 open `lead_follow_up`，则 `close T1 booked`，不是 `create_closed`。

## 4. Prompt 里必须有什么

每次改 `contacts-analyzer` prompt，都检查这些 section 是否存在且职责清楚：

| 必须 section                  | 为什么必须有                                                             |
| --------------------------- | ------------------------------------------------------------------ |
| Purpose / worldview         | 让模型知道它不是分类器，而是 event -> objective / work translator                |
| Evidence Authority          | 解决 stale snapshot vs fresh interaction、hard facts、hard stops       |
| Contact Profile Policy      | lifecycle/profile 规则有自己的边界，不误伤 task                                |
| Task Decision State Machine | 让模型先判断 action path，不靠零散 examples 猜                                 |
| Action Semantics            | 每个 action 的业务含义和字段含义                                               |
| Generated Taxonomy          | enum / definitions 从 source of truth 注入                            |
| Category Matrix             | 怎么把业务 evidence 映射到 `typeCategory` / `closeResult` / `progressType` |
| Examples / edge cases       | 只放高信号边界，覆盖 eval 失败模式                                               |
| Output Contract             | 让模型知道 JSON shape；和 schema 保持一致                                     |
| Input Context Contract      | 让模型知道 user prompt 的 facts 怎么读                                      |

## 5. Taxonomy 使用原则

taxonomy 的 source of truth 是 code/common，不是 prompt 手写文本。

在 `callytics-infrastructure` 里：

- Task taxonomy 从 `@retaintive/common/db` 和 `@retaintive/common/taxonomy/task` 动态注入。
- Contact taxonomy 从 `@retaintive/common/taxonomy/contact` 动态注入。
- Prompt 只应该写“怎么用 taxonomy 做判断”，不要复制第二份 enum/definition。

如果发现 taxonomy definition 本身太薄，例如：

- `referral` 不够说明 guest pass / friend details / referral credit 边界。
- `renewal` 不够说明 payment recovery vs freeze expiry。
- `retention` 不够说明 complaint / billing dispute / service issue。

正确动作：

```text
优先改 callytics-common taxonomy definition
  -> prompt registry 自动注入
  -> tests / snapshot / eval 一起验证
```

不要只在 prompt 里 patch 一句，因为那会让别的 consumer 继续吃旧 taxonomy。

## 6. 怎么避免信息重复

目标不是“同一个词永远不能出现两次”，而是每层不抢职责。

合理重复：

```text
Task policy: 什么时候用 update
Output contract: real update 应该输出什么 shape，例如 non-empty suggestedActions
Zod schema: 接受可修复的 model shape，让 deterministic fallback 有机会运行
Writer: 真正落库的 update 没有 payload 不写库 / 不信模型
```

这是 contract chain，不是重复。

不合理重复：

- contact section 讲完整 task state machine。
- task section 重写 full lifecycle profile policy。
- user prompt 注入长期 business policy。
- prompt 手写复制 common taxonomy。
- examples 试图覆盖所有 enum 全排列。

## 7. Prompt accuracy 修改流程

### Phase 1：还原现状，不改东西

先回答“AI 到底看到了什么”：

1. 拼出当前 assembled `system prompt`。
2. 找出 `userMessage` 实际包含哪些字段。
3. 确认 `Zod schema` 对输出有什么 admission rule 和硬约束。
4. 看 deterministic fallback 和 writer / Policy Guard 怎么消费 output。
5. 画出当前链路。

常看代码：

```text
lambda/contacts-analyzer/src/core/prompts/index.ts
lambda/contacts-analyzer/src/core/prompts/*
lambda/contacts-analyzer/src/core/prompt-builder.ts
lambda/contacts-analyzer/src/core/models.ts
lambda/contacts-analyzer/src/handler.ts
lambda/contacts-analyzer/src/infrastructure/neon-repository.ts
lambda/contacts-analyzer/scripts/golden-eval/*
```

### Phase 2：按层拆问题

每个问题都要归因到一层：

| 失败模式                         | 可能层                                           |
| ---------------------------- | --------------------------------------------- |
| 模型没看到 facts                  | user prompt / context builder                 |
| 模型看到 facts 但 action 选错       | system prompt policy                          |
| 模型 action 对但 payload 缺字段     | output contract / Zod schema                  |
| 模型 action 半成品可修复，但 parse 前被拒 | Zod schema admission / deterministic fallback |
| 模型输出对但没写库                    | writer / Policy Guard / DB constraints        |
| expected 本身有争议               | eval / product decision                       |

### Phase 3：先写结构，再写文字

不要直接 patch 一句。先说明要改哪个 section：

```text
Shared -> Evidence Authority
Contact -> lifecycle/profile/DNC
Task -> state machine/action/category/examples
Schema -> admission rules / required fields / enum
Writer -> final authority/safety
Eval -> expected behavior
```

### Phase 4：用 eval failures 验证结构

每个失败 case 用这个模板：

```text
Case:
Tests:
Expected:
Actual:
Why wrong:
Likely layer:
Recommended fix:
```

重点看这些模式：

- `create_closed` 被输出成 `[]`。
- referral 边界弱。
- `record_progress` vs `update` 混。
- `update` 选对了但缺 `suggestedActions`。
- stale contact snapshot 静默压掉 fresh task intent。

### Phase 5：改 runtime prompt / schema

改动顺序：

1. 修改 section text。
2. 区分 model-facing output contract 和 schema admission policy：最终合法输出要清楚，但 schema 不要过早挡住 fallback 可修复的模型输出。
3. 如果 required/optional 或 schema admission 变了，同步改 `models.ts`。
4. 同步 generated output contract。
5. bump prompt version。
6. regenerate assembled prompt snapshot。
7. 跑 unit tests。
8. 跑 real-model eval。

常用命令：

```bash
cd callytics-infrastructure

bun -e "import { buildSystemPrompt } from './lambda/contacts-analyzer/src/core/prompt-builder'; await Bun.write('./lambda/contacts-analyzer/tests/unit/__snapshots__/system-prompt.golden.txt', buildSystemPrompt());"

bun test \
  lambda/contacts-analyzer/tests/unit/models.test.ts \
  lambda/contacts-analyzer/tests/unit/prompt-builder.test.ts \
  lambda/contacts-analyzer/tests/unit/system-prompt-snapshot.test.ts \
  lambda/contacts-analyzer/tests/unit/ai-client.test.ts \
  lambda/contacts-analyzer/tests/unit/handler.test.ts \
  --timeout 120000

# 只导出 scenario review，不调用模型
npm run eval:golden -- --review-md ./task-accuracy-scenarios.md

# 真实模型 eval，需要 OPENROUTER_API_KEY
npm run eval:golden -- --runs 3
```

## 8. 未来方向：从 push context 到 pull context

一句话：

> 把 task 系统从「push，一次性把所有信息塞满 prompt」演进成「pull，AI 按需调 read tools 自取上下文」，并加一个独立自检 agent 评估 task 生成得对不对。

状态：

- 方向已定。
- 技术可行性已确认到平台层。
- 不在当前 mega PR scope。
- 必须等 `2026-06-21-task-system-redesign-plan.md` 的门禁 + 模型 B + `task_sources` 底座落地后启动。

硬约束：

```text
门禁判断逻辑还错着的时候上 agent = 歪地基上盖楼。
```

### 起因

现在 `contacts-analyzer` 生成任务时，把 contact profile、open tasks、policy、call facts、history 一起塞进 prompt。Max 的真实怀疑是：

```text
context 喂太多，模型反而混乱。
```

想要的是：

1. 给 AI 一组只读接口 / tools，例如 read contact、read task history、read call facts、read messages。
2. AI 自己决定需要什么 context 就调什么 tool。
3. 另起一个只读 self-check agent，评估 task 是否生成正确，把人工 track 的工作自动化。

术语对照：

| Max 的话         | 工程术语                          | 文档对应层                               |
| -------------- | ----------------------------- | ----------------------------------- |
| 一股脑塞满 prompt   | context-stuffing / push       | foundation 第 2 层现状 prompt-builder   |
| AI 自主调工具取上下文   | agentic / tool-calling / pull | foundation 第 2 层目标态 Context Builder |
| 评估任务对不对的 agent | eval / self-check agent       | Eval / Replay / Observability 层     |

## 9. 技术可行性和 caveat

平台层可行：

| Layer   | 当前可用路径                                                                                            | 判断                                          |
| ------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| SDK     | Vercel AI SDK `generateText({ tools })` / tool calling                                            | 可做 function-calling loop                    |
| Gateway | OpenRouter supports tool calling interface across supported models                                | 可作为 gateway，但具体 provider/model 要 smoke test |
| Model   | DeepSeek API supports tool calls；OpenRouter model support 需按 model page / supported parameters 验证 | 可行但不能只看 marketing label                     |

重要 nuance：

- `generateObject` + giant context 是 push。
- `generateText` / tool loop + read tools 是 pull。
- 迁移不是改一个参数，而是换调用模式、加 loop、定义 tool schemas、加 cost/timeout/trace。
- 不需要从零造 agent runtime；用现有 Lambda + Vercel AI SDK loop + OpenRouter 即可试水。
- 不需要引入 MCP。MCP 是给外部 AI client 接入；这是产品内 AI，function-calling 足够。

启动前必须做一个 staging smoke：

```text
same model + same provider + same SDK
  -> tool call
  -> tool result
  -> final structured verdict
  -> trace captured
```

原因：官方文档说明机制支持 tool calling，不等于我们选定的模型 / provider / output schema 组合在多轮 loop 上稳定。

参考：

- Vercel AI SDK `generateText`: [https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text)
- Vercel AI SDK tools: [https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling)
- OpenRouter tool calling: [https://openrouter.ai/docs/guides/features/tool-calling](https://openrouter.ai/docs/guides/features/tool-calling)
- OpenRouter AI SDK provider: [https://github.com/OpenRouterTeam/ai-sdk-provider](https://github.com/OpenRouterTeam/ai-sdk-provider)
- DeepSeek tool calls: [https://api-docs.deepseek.com/guides/tool\_calls](https://api-docs.deepseek.com/guides/tool_calls)

## 10. Self-check and pull-context agents

### Self-Check Agent（独立自检 agent）：先做

做什么：

```text
task 生成后，一个只读 agent 自主调 read tools，
读取 call facts + task + contact + history，
评估这个 task 生成得对不对。
```

为什么先做它：

1. 只读，不碰生产写库路径。
2. 直接解决当前痛点：人工逐条看 task 是否对。
3. 用低风险场景验证 tool-calling loop、trace、cost、timeout。
4. 它属于 Eval / Replay / Observability 层，不改核心生成管道。

输出不应该直接改 task。它应该输出：

```text
verdict: correct | suspicious | wrong | insufficient_context
expectedDecision?: structured expected decision
reason
evidenceRefs
toolTrace
cost / latency
```

### Pull-Context Generation Agent（生成时自取上下文）：后做

做什么：

```text
contacts-analyzer 不再把所有 context push 进 prompt；
AI 先看 minimal case header，再按需调 read tools 获取 contact/task/call/message/timeline context。
```

为什么后做：

1. 它动核心生成管道。
2. 它解决“怎么取上下文”，不解决“判断逻辑对不对”。
3. 如果门禁和 state machine 还错，pull context 只会让错误更难 debug。
4. 先用 self-check agent 验证 loop，再上核心生成路径。

## 11. 顺序

```text
1. 落地 task-system-redesign-plan
   - 门禁
   - 模型 B
   - task_sources

2. 稳定 current prompt / schema / writer / eval
   - Evidence Authority
   - Task Decision State Machine
   - taxonomy from common
   - golden eval + DB replay

3. 做 Self-Check Agent
   - read-only tools
   - no write
   - verdict only
   - trace + audit

4. 用 Self-Check Agent 的结果反哺 prompt/schema/taxonomy/eval

5. 再设计 Pull-Context Generation Agent
   - minimal initial context
   - read tools
   - loop budget
   - final structured output
   - writer unchanged / Policy Guard unchanged
```

CTO 判断：

- 门禁是地基。地基错时上 agent，会把不稳定放大。
- self-check agent 是低风险 tool-calling 试水点。
- generation agent 动核心管道，等底座稳定再上。

## 12. 启动时再细化的设计

| 议题             | 要定什么                                                                                |
| -------------- | ----------------------------------------------------------------------------------- |
| Tool set       | AI 能读哪些 context：contact、task history、call facts、messages、timeline、taxonomy、playbook |
| Tool schema    | 每个 tool 的 input / output schema、pagination、evidence refs、redaction                  |
| Permission     | read-only vs write；self-check agent 只能 read                                         |
| Loop control   | max steps、timeout、cost ceiling、fallback                                             |
| Context policy | 哪些 facts 默认给，哪些必须 tool read                                                         |
| Observability  | 每步调了什么 tool、读了什么、最后怎么判                                                              |
| Eval           | self-check agent 自己也要被 eval                                                         |
| Versioning     | recipe / prompt / schema / toolset 怎么 pin                                           |

## 13. Recipe label mapping

`recipeLabel` 不是构想。`traceplane` 里已经有基础实现，可以作为未来 eval/agent recipe versioning 的参考。

当前行为：

- `recipes` 存具体版本，主键是 `project_id + target + version`。
- `recipe_labels` 存 label 映射，主键是 `project_id + target + label`，值是 `version`。
- 运行时解析优先级：如果请求传 `recipeVersion`，直接按版本取；否则按 `recipeLabel ?? "prod"` 查 label。
- 保存 recipe 时：`POST /v1/recipes?label=prod` 会保存版本，并写入 label -> version 映射。

代码位置：

```text
traceplane/packages/core/src/reducer.ts
traceplane/packages/cloudflare/src/schema.ts
traceplane/packages/cloudflare/src/d1-store.ts
traceplane/apps/worker/src/routes/recipes.ts
traceplane/apps/worker/src/routes/runs.ts
```

还没产品化的部分：

- candidate -> prod 的发布 / 晋级 workflow。
- rollback。
- audit UI。
- approval policy。
- cost / usage view。

## 14. 不做什么

明确排除：

- 当前底座 mega PR 不含 agent / tool-calling 代码。
- 不从零造 agent runtime。
- 不引入 MCP 作为产品内 AI 的第一实现。
- 不在门禁修复前上 Pull-Context Generation Agent。
- 不让 self-check agent 写 production DB。
- 不让 prompt 手写复制 taxonomy。

## 15. 给未来 AI agent 的执行口令

当未来 agent 被要求“提高 task prompt 准确率”时，必须先做：

```text
1. Read this doc.
2. Read current assembled system prompt snapshot.
3. Read system prompt section files and prompt assembly order.
4. Read user prompt builder.
5. Read Zod schema.
6. Read writer / Policy Guard behavior.
7. Classify each proposed rule into:
   - system prompt
   - user prompt
   - schema
   - writer / guard
   - taxonomy
   - eval
8. If taxonomy definition is the source problem, change common taxonomy first.
9. Only then patch runtime prompt/schema.
10. Regenerate prompt snapshot and run tests/evals.
```

If the agent cannot explain what the model sees and which layer owns the rule, it is not allowed to patch prompt text.
