# Prompt Compiler Architecture

> Canonical path: `specs/daemon/prompt-compiler-architecture.md`
> Related docs: [../../docs/architecture-spec.md](../../docs/architecture-spec.md), [daemon.md](daemon.md), [../message/delivery-runtime.md](../message/delivery-runtime.md), [codex-instructions-prompt-split.md](codex-instructions-prompt-split.md), [../../docs/codex-app-server-native-protocol.md](../../docs/codex-app-server-native-protocol.md), [../../docs/tech_design.md](../../docs/tech_design.md)

## 1. Summary

当前 AgentTeam 的 prompt system 已经从“单段字符串”进化到 `baseInstructions` / `developerInstructions` / `FreshPrompt` / `ResumePrompt` 的分层，但整体仍然是：

- 规则内容分散在多个 builder / `skilldoc.go` / runtime adapter 之间
- 很多 prompt source 仍以 Go string builder 的形式存在
- 某个 lifecycle event 到底给 runtime 发了什么，缺少统一、直接、可追踪的 inspect surface
- 不同 runtime 虽然共享不少语义，但装配路径、调试手段和可视化方式仍不统一

本设计的目标是把 AgentTeam prompt system 从“多处代码拼接字符串”升级成一套真正的 **Prompt Compiler Architecture**：

- prompt source 明确化
- prompt semantic model 类型化
- compile / render / deliver 分层
- runtime 只负责 transport，不再参与 prompt 语义拼接
- 每个关键 lifecycle event 都有统一的 typed runtime input，并且实际发送给 runtime 的 prompt 文本会通过现有 activity trajectory 可见

一句话概括：

> **Prompt 不再是若干函数拼接出来的字符串，而是一份有显式来源、有 typed IR、有 renderer，并能把最终输入暴露到 activity 的编译产物。**

术语说明：本文保留 **Prompt Compiler** 这个名字，但这里的 `compile` 是统称，指“把某个 lifecycle event 需要送给 runtime 的输入整理成规范化产物”。它既可以产出 thread-level instructions，也可以产出 startup / resume 的 turn context，还可以产出 session 已在运行时的轻量 delivery envelope；**不等于每条消息都从零重拼一整段 prompt**。

## 2. Status / Scope

- Date: 2026-04-20
- Status: canonical
- Scope: AgentTeam daemon 内 agent prompt 的 canonical architecture boundary（适用于 `codex`、`claude`、`opencode` 等 runtime）

本规范不是“当前实现描述”；它定义 prompt system 的 canonical target shape 与长期架构边界。

## 3. Problem Statement

### 3.1 当前系统已经做对的部分

当前实现已经具备几个关键进展：

1. `codex` runtime 的 app-server 实现已支持把长期规则拆到 `baseInstructions` / `developerInstructions`
2. `FreshPrompt` / `ResumePrompt` 已把首次 turn input 与 resume 动态输入分开
3. `AgentTeam` channel 的 `skilldoc.go` 已把部分规则拆成 core / reference
4. runtime prompt 已为 role/local identity 建模；`IDENTITY.md` 是 bounded workspace identity doc，进入 persistent instruction surface，而不是 dynamic turn input
5. `MEMORY.md` / `state/*` / `kb/*` / `archive/*` 被视为 workspace 文件而不是整段 raw prompt 注入

这些都是正确方向。

### 3.2 当前系统仍然让 debug / tuning 困难的根因

真正的问题不是 prompt 太长，而是 **prompt 的来源、分类、编译、渲染、投递、可观测性分散在多个层**。

当前模型里至少存在这些认知成本：

1. **prompt source 分散**
   - 平台基础规则在 `prompt.go`
   - channel 行为规则在 `skilldoc.go`
   - workspace identity 在 `IDENTITY.md`
   - runtime 差异在 driver / tool prefix helper
   - notice / deferred continuation / resume reminder 在不同文件追加

2. **缺少 canonical IR（中间表示）**
   - 系统内部大多直接操作字符串
   - 很难回答“某段文本为什么会出现在这一轮 prompt 里”

3. **runtime adapter 仍然感知 prompt 分块语义**
   - `codex` app-server 实现与非 split runtime 的路径存在语义映射差异
   - 没有一套完全 canonical 的 runtime-agnostic prompt semantic model

4. **缺少统一的 prompt 可见性 surface**
   - 当前要 debug 往往只能读代码、打 log、手工还原
   - 很难直接在现有 activity / turn flow 里看到“这轮实际发给 runtime 的 prompt 文本”

5. **很多 prompt content 仍由 Go string builder 维护**
   - review diff 不够直观
   - 结构化校验困难
   - golden/snapshot 测试粒度不够理想

## 4. Goals

### 4.1 Primary Goals

- 定义一个 runtime-agnostic 的 **canonical prompt semantic model**
- 将 prompt source、compile、render、deliver 明确分层
- 让任何一轮 turn / delivery event 都可以直接 inspect：
  - 用了哪些 source
  - 每段 block 的类型与生命周期
  - 最终渲染到 runtime 的 payload
  - 实际发送给 runtime 的 prompt 文本
- 把 prompt tuning 的对象从“大段字符串”升级为“section / policy / ordering / render strategy”
- 让 drivers 只负责 runtime transport，不负责 prompt 语义

### 4.2 Secondary Goals

- 减少 `skilldoc.go` / `prompt.go` 中重复的文档字符串维护
- 为未来 runtime 增长提供统一 renderer 接口
- 让 prompt 改动具备 compiler-style testability
- 让 workspace docs（`IDENTITY.md`、`MEMORY.md`）与 prompt source 的边界更清晰

## 5. Non-Goals

本设计的目标是定义最佳形态，不受当前迁移成本约束；但它仍然不追求以下内容：

- 不要求 runtime 支持统一的 native instruction field；renderer 可以因 runtime 能力不同而不同
- 不要求一次性把所有 workspace 文档变成 prompt source
- 不要求 prompt source 必须 100% 是纯 markdown 文件；允许“typed generated source”存在
- 不要求今天就解决所有 token budget policy；本设计只定义应放在哪里做
- 不把 slash command `instruction` 的语义与 LLM prompt `instruction` 混为一谈

## 6. Design Principles

### 6.1 Semantic First, Transport Second

`instruction` / `turn context` / `notice` / `reference` 的分类必须先在 semantic layer 决定，之后再由 runtime renderer 映射到 `baseInstructions`、`developerInstructions`、单字符串 prompt 或其他 transport 形式。

### 6.2 Single Source Of Truth

系统内部只能有一份 canonical compiled runtime input 作为“这一轮到底给 runtime 什么”的真相来源。

### 6.3 Drivers Are Transport Adapters Only

driver 不允许：

- 决定某段 block 属于 instruction 还是 turn context
- 擅自追加 prompt 语义文本
- 在 runtime adapter 内偷偷实现第二套 prompt policy

driver 只允许：

- 接收 compiler/render 结果
- 发送到 runtime
- 报告 transport-level success / failure / limits

### 6.4 Workspace Docs And Prompt Sources Are Different Kinds Of Inputs

- `IDENTITY.md` / `MEMORY.md` / `state/*` / `kb/*` / `archive/*` 是 workspace docs
- `prompt sources` 是 compiler 输入

某个 workspace doc 可以被 compiler 读取并 materialize 成 prompt block，但“doc 存在”不等于“必须 raw 注入 prompt”。
`IDENTITY.md` 是显式例外：它由 `specs/agent/role-identity.md` 定义为 bounded 常驻身份上下文，因此必须通过 compiler policy 进入 persistent instruction surface。

### 6.5 Prompt Must Be Observable

每一轮 turn / delivery event 都必须可解释：

- why this block exists
- where this text came from
- why it was placed in this slot
- what changed relative to the previous turn

### 6.6 Keep The Name, Clarify The Lifecycle Model

本设计不强制把术语改成 `runtime input compiler`。保留 `prompt compiler` 没问题，但 spec 必须明确：

- `thread/start` / `thread/resume` 事件主要编译 **thread contract**（例如 `baseInstructions` / `developerInstructions`）
- fresh start / wake / resume 事件主要编译 **turn context**（例如 `FreshPrompt` / `ResumePrompt`）
- session 已运行时的新消息投递主要编译 **delivery envelope**，它可能只是轻量的消息与上下文字段，不是完整 prompt

因此：

- 逻辑上，每个 lifecycle event 都应该有自己的 compiled runtime input
- 性能上，不要求每个 event 都 full rebuild 全部稳定部分
- 实现上，应优先支持“稳定 contract 缓存 + 当前事件增量编译 + 最终 render/deliver”

## 7. Canonical Semantic Model

### 7.1 Top-Level Concepts

最佳形态下，prompt system 里只有这些一等概念：

- **Prompt Source**：一份原始来源（模板、workspace doc、generated tool catalog、event payload 等）
- **Prompt Block**：compiler 归一化后的最小语义单元
- **Prompt Bundle**：仅用于 prompt-bearing event（thread contract / turn context）的 block-based typed IR
- **Delivery Envelope**：用于 live delivery 的 typed IR，需与 [`../message/delivery-runtime.md`](../message/delivery-runtime.md) 的 correlation / replay / evidence 契约保持一致，而不是只携带一条“消息文本”
- **Runtime Input Envelope**：某个 lifecycle event 的 canonical compiled 产物；内部承载 `PromptBundle` 或 `DeliveryEnvelope`
- **Prompt Renderer**：把 `RuntimeInputEnvelope` 映射为某个 runtime 的 transport payload

### 7.2 Block Type

```go
type BlockType string

const (
    BlockInstruction BlockType = "instruction"
    BlockTurnContext BlockType = "turn_context"
    BlockNotice      BlockType = "notice"
    BlockReference   BlockType = "reference"
)
```

#### `instruction`

长期规则。跨 turn、跨 wake、跨 resume 应保持成立。

典型例子：
- 你是谁
- long-running persistent agent 定义
- channel communication hard rules
- task board 核心约束
- workspace identity（IDENTITY.md）
- agent role / description

#### `turn_context`

当前这一轮事件输入。删除当前 wake / message / resume 事件后，这些内容应随之消失。

典型例子：
- 当前 message
- unread summary
- deferred continuation
- 当前 target / thread 头
- 当前 turn 的 retrieved context

#### `notice`

一次性或短生命周期的高优先级系统提示。

典型例子：
- identity notice
- 迁移 notice
- runtime repair notice
- explicit warning that only applies until acknowledged or next fresh turn

#### `reference`

查阅性材料。对 agent 行为有帮助，但被 compact / 摘要化后不应立即导致核心行为错误。

典型例子：
- 详细 tool catalog
- messaging example syntax
- formatting examples
- long-form protocol reference

### 7.3 Durability

```go
type Durability string

const (
    DurabilityPersistent        Durability = "persistent"
    DurabilityTurnOnly          Durability = "turn_only"
    DurabilityUntilAcknowledged Durability = "until_acknowledged"
    DurabilityCompactable       Durability = "compactable"
)
```

`BlockType` 是语义分类；`Durability` 是生命周期策略。二者不能混为一谈。

例如：
- `instruction` 通常是 `persistent`
- `turn_context` 通常是 `turn_only`
- `notice` 通常是 `until_acknowledged` 或 `turn_only`
- `reference` 通常是 `compactable`

### 7.4 Layer

每个 block 还要声明它来自哪个 semantic layer：

```go
type BlockLayer string

const (
    LayerPlatform         BlockLayer = "platform"
    LayerChannel          BlockLayer = "channel"
    LayerWorkspaceDoc     BlockLayer = "workspace_doc"
    LayerAgentConfig      BlockLayer = "agent_config"
    LayerEvent            BlockLayer = "event"
    LayerRetrieval        BlockLayer = "retrieval"
    LayerSystemNotice     BlockLayer = "system_notice"
    LayerRuntimeGuidance  BlockLayer = "runtime_guidance"
)
```

### 7.5 Output Slot

canonical IR 不直接等于 runtime payload；但它需要描述 block 期望进入哪个 semantic slot：

```go
type OutputSlot string

const (
    SlotBase       OutputSlot = "base"
    SlotDeveloper  OutputSlot = "developer"
    SlotTurn       OutputSlot = "turn"
    SlotReference  OutputSlot = "reference"
)
```

说明：
- `SlotBase` / `SlotDeveloper` / `SlotTurn` 是 renderer 关注的主要目标
- `SlotReference` 是 semantic slot；renderer 可把它折叠进首次 turn、compactable history 或 runtime-specific side channel

### 7.6 Prompt Source Reference

```go
type PromptSourceRef struct {
    ID          string // stable source id, e.g. agentteam.channel.core
    SourceKind  string // markdown_template | workspace_doc | generated | event_payload | retrieval | notice
    Path        string // repo path / workspace path / logical name
    Version     string // git sha / schema version / doc mtime hash
    Owner       string // platform | channel:agentteam | agent | app:github | system
    Description string
}
```

### 7.7 Prompt Block

```go
type PromptBlock struct {
    ID            string
    Title         string
    Type          BlockType
    Layer         BlockLayer
    Slot          OutputSlot
    Durability    Durability
    Priority      int
    Source        PromptSourceRef
    Text          string
    TokenEstimate int
    Tags          []string
    DebugLabel    string
}
```

要求：
- `ID` 在同一 bundle 内稳定可引用
- `Priority` 决定 merge 顺序
- `TokenEstimate` 是 compile-time debug 字段，不是 billing source of truth
- `DebugLabel` 是 UI 用的可读说明

### 7.8 Prompt Bundle

```go
type CompileEventType string

const (
    EventThreadStart           CompileEventType = "thread_start"
    EventThreadResume          CompileEventType = "thread_resume"
    EventFreshStart            CompileEventType = "fresh_start"
    EventWake                  CompileEventType = "wake"
    EventResume                CompileEventType = "resume"
    EventDeferredContinuation  CompileEventType = "deferred_continuation"
    EventRoutedMessageDelivery CompileEventType = "routed_message_delivery"
    EventReactionDelivery      CompileEventType = "reaction_delivery"
    EventFinalVisibleReplyCheck CompileEventType = "final_visible_reply_check"
)

type PromptBundle struct {
    SchemaVersion string
    EventID       string
    EventType     CompileEventType
    TurnID        string // optional; empty for pre-turn compiles such as thread/start
    AgentID       string
    ChannelType   string
    Blocks        []PromptBlock
    Fingerprint   string
}
```

说明：
- `PromptBundle` 是 **runtime-agnostic** 的 block IR；render target 不写在 bundle 里，而由 renderer / `RenderOptions` 决定
- `EventID` 是每次 compile / inspect 的稳定主键；`TurnID` 只是可选的 runtime turn 关联字段，不是所有 compile 都有
- `EventType` 记录细粒度 lifecycle identity（`thread_start`、`wake`、`reaction_delivery` 等）；`CompileEventKind` 只负责较粗的 render / policy 分类，不能替代 `EventType`
- canonical IR 使用一个 flat `Blocks` 列表 + explicit metadata，比直接维护多个字符串更稳定
- `Fingerprint` 是稳定语义哈希，至少应覆盖 `AgentID`、`ChannelType`、`EventType`、`block.ID`、`Source.Version`、`Priority`、`Slot`、`Durability` 与 `Text` hash；它用于稳定 contract cache 命中、prev-turn diff 对齐与 inspect 去重
- render 结果不写回 `PromptBundle`；render 结果属于 runtime renderer 的输出 surface

### 7.9 Delivery Envelope

```go
type DeliverySourceKind string

const (
    DeliverySourceRoutedMessage DeliverySourceKind = "routed_message"
    DeliverySourceReaction      DeliverySourceKind = "reaction_signal"
)

type ConversationBindingRef struct {
    Target      string
    ThreadRef   string
    SurfaceKind string
}

type DeliveryTransportBinding struct {
    DeliveryMode     string // turn_direct | stdin_delivery | wake_start | deferred_replay | other explicit runtime path
    RuntimeSessionID string // optional but canonical when the runtime surface exposes one
    ThreadID         string // optional
    ActiveTurnID     string // optional
    WakeToken        string // optional but required when the runtime reliability contract provides one
    Attempt          int
    DeferredBatchID  string // optional batch/cutover identity for replayed deliveries
}

type DeliveryCorrelation struct {
    AgentID         string
    MessageID       string // required for routed_message
    DeliveryID      string // required for reaction_signal; may also be set for other delivery ledgers
    SourceMessageID string // optional, e.g. target message for reaction metadata
    TargetKey       string // stable scope identity used by freshness/routing checks
    TargetSeq       int64  // target-local sequence for freshness
    WakeToken       string // duplicated here for diagnostics joins when available
}

type DeliveryReplayContext struct {
    ReplayState       string    // direct_live | deferred_replay
    DeferredReason    string    // optional; e.g. busy_runtime
    AcceptedAt        time.Time // when daemon accepted the handoff
    OriginalOrderKey  string
    PriorAttemptCount int
}

type DeliveryEvidenceClass string

const (
    EvidenceDeferredWhileBusy  DeliveryEvidenceClass = "deferred_while_busy"
    EvidenceReplayStarted      DeliveryEvidenceClass = "replay_started"
    EvidenceWakeFailed         DeliveryEvidenceClass = "wake_failed"
    EvidenceRuntimeStartFailed DeliveryEvidenceClass = "runtime_start_failed"
    EvidenceRuntimeExited      DeliveryEvidenceClass = "runtime_exited_unexpectedly"
    EvidenceVisibleReplySent   DeliveryEvidenceClass = "visible_reply_sent"
    EvidenceCompletedNoReply   DeliveryEvidenceClass = "completed_without_visible_reply"
)

type DeliveryEnvelope struct {
    SourceKind        DeliverySourceKind
    Binding           ConversationBindingRef
    Transport         DeliveryTransportBinding
    Correlation       DeliveryCorrelation
    Replay            DeliveryReplayContext
    EvidenceClass     DeliveryEvidenceClass
    AuthorDisplayName string
    MessageType       string
    PayloadText       string
    Metadata          map[string]any
    Fingerprint       string
}
```

说明：

- `DeliveryEnvelope` 是 live delivery 的 canonical IR，覆盖 **routed messages** 与 **reaction delivery signals**；它不是只给普通文本消息用的特例结构
- 它不是 `PromptBlock` 列表，因为 live delivery 的核心问题是“消息 / signal、路由、replay 与 transport metadata 怎么规范进入 runtime”，而不是“再拼一份 prompt”
- `Binding` 是 canonical conversation binding；renderer 不应再依赖外部 side channel 去知道 target / threadRef / surface
- `Transport` 提供 renderer 选择 direct turn input / stdin delivery / wake / deferred replay 等 transport path 所需的 canonical binding
- `Correlation` 必须满足 `../message/delivery-runtime.md` 的 diagnostics / replay contract：对 routed messages 至少带 `messageId`，对 reaction delivery 至少带 `deliveryId`，并在有 `wakeToken` 时保留它
- `AuthorDisplayName` 来自 daemon receive/process-delivery adapter 已解析出的 canonical `AuthorIdentity`；`DeliveryEnvelope` / `DeliveryCompileInput` 不接收 `senderType` / `senderName` 这类旧 wire 字段，旧 daemon 兼容只能留在 adapter 边界
- `MessageType` / `PayloadText` / `Metadata` 必须与对应 canonical transport shape 对齐：routed message 走普通消息 transport，reaction signal 走 `../message/reaction.md` 定义的 reaction delivery shape，而不是另造一套 undocumented payload
- `Transport.RuntimeSessionID`、`Transport.Attempt`、`Transport.DeferredBatchID` 与 `Replay.PriorAttemptCount` 是 transport/replay 关联字段；它们必须进入 canonical envelope，而不是靠 renderer/driver 私藏 side metadata
- `Correlation` 只携带可复现的消息、目标、序列与 wake 传输关联。模型可见
  prompt 不承载 daemon 私有 selector；同一 provider turn 的多条消息依靠
  各自的 exact target 与 message ID 保持独立。`WakeToken` 仅用于 daemon
  delivery journal、surface receipt 与诊断关联，不是模型输入或公共 routing
  capability。
- `Replay` 必须足够区分 direct live delivery 与 deferred replay，避免把 replay 判断留给外部猜测
- `EvidenceClass` 是本次 delivery compile 对应的 runtime reliability evidence class；它必须能与 `../message/delivery-runtime.md` §5.2 / §5.4 的 durable evidence contract 对齐。对于 reply/completion/crash 等后续 outcome，后续 evidence row 继续复用同一组 correlation fields，而不是重新发明另一套 identity
- `Fingerprint` 应覆盖 `SourceKind`、`Binding`、`Transport`、`Correlation`、`Replay`、`EvidenceClass`、`AuthorDisplayName`、`MessageType`、payload hash 与 metadata hash；随机或易失的 delivery 状态不得进入 fingerprint。它用于 hot-path 去重、delivery diff 与 transport diagnostics 对齐

### 7.10 Runtime Input Envelope

```go
type CompileEventKind string

const (
    EventThreadContract   CompileEventKind = "thread_contract"
    EventTurnContext      CompileEventKind = "turn_context"
    EventDeliveryEnvelope CompileEventKind = "delivery_envelope"
)

type RuntimeInputEnvelope struct {
    SchemaVersion string
    EventID       string
    EventType     CompileEventType
    TurnID        string // optional; only when the compile is attached to a concrete runtime turn
    AgentID       string
    ChannelType   string
    EventKind     CompileEventKind
    Bundle        *PromptBundle
    Delivery      *DeliveryEnvelope
    Fingerprint   string
}
```

要求：

- `RuntimeInputEnvelope` 是 **per-event** 的 canonical compiled 产物
- `EventID` 是 compiled runtime input、UI inspect 与 diff 的 primary identity；compile caller 不应为 pre-turn compile 伪造 `TurnID`
- `EventType` 是真实 lifecycle identity；例如 `thread_start`、`wake`、`deferred_continuation`、`routed_message_delivery`、`reaction_delivery`
- `EventThreadContract` / `EventTurnContext` 必须携带 `Bundle`
- `EventDeliveryEnvelope` 必须携带 `Delivery`
- 同一 envelope 中，`Bundle` 与 `Delivery` 必须恰有一个非空
- `Fingerprint` 是 envelope-level 稳定哈希；它可以复用 payload fingerprint 作为核心输入，但必须把 `EventKind` 与 `EventType` 纳入哈希边界

## 8. Classification Rules

系统不应靠文件名或函数名判断某段内容是 `instruction` 还是 `turn_context`。判断标准必须是语义性的。

### 8.1 Instruction vs Turn Context

判断题：

> **如果删除“当前 message / 当前 thread / 当前 wake 事件”，这段话还应该继续存在吗？**

- **应该继续存在** → `instruction`
- **不该存在** → `turn_context`

### 8.2 Notice vs Turn Context

判断题：

> **这段内容是不是系统主动提醒 agent 某个特殊状态，而且它的存在不只是在描述当前消息本身？**

- 是 → `notice`
- 否 → `turn_context`

### 8.3 Reference vs Instruction

判断题：

> **如果这段内容被 compact 或摘要化，agent 会不会立刻违反硬行为约束？**

- 会 → `instruction`
- 不会，只是降低效率 / 可用性 → `reference`

### 8.4 Workspace Docs Boundary

最佳形态里对 workspace docs 的规则必须非常明确：

- `IDENTITY.md`
  - 是 workspace doc
  - 是常驻身份上下文，默认 materialize 为 persistent/base instruction block
  - 必须受产品级长度限制保护；当前 canonical 限制见 `specs/agent/role-identity.md`
  - prompt 仍应保留一段很薄的 `Role & local identity` 平台 block，说明本地副本与服务端角色资产不是自动双向同步
- `MEMORY.md` / `state/*` / `kb/*` / `archive/*`
  - 默认不 raw 注入
  - 它们是 workspace continuation files，prompt 只保留关于“如何使用 memory”的平台规则
- 未来如果需要让 workspace 里的某类文档进入 prompt，必须通过显式 source kind / compiler policy，而不是任意 markdown 自动拼接

### 8.5 Developer Instructions Boundary

最佳形态里，developer slot 不能只靠隐含约定存在，必须显式建模：

- `AGENTS.md`
  - 是 agent/user-owned instruction source
  - 默认 materialize 为 `LayerAgentConfig` + `SlotDeveloper` + `DurabilityPersistent` 的一个或多个 `instruction` block
- agent description / initial role
  - 是 `agent_config` source
  - 默认 materialize 到 `LayerAgentConfig` + `SlotBase` + `DurabilityPersistent`
- 未来如果需要引入其他 prompt-facing per-agent docs，也必须走显式 `agent_config` taxonomy，而不是混进 `IDENTITY.md`、channel pack 或 runtime payload

developer-side invalidation 规则也必须明确：

- `AGENTS.md` 内容变化
- agent `description` / `initial role` 变化
- compiler policy 改变了 `agent_config` source 的 materialization / slotting 结果

以上任一变化，都必须进入 thread-contract fingerprint，并触发 contract recompile / cutover 判定；不能让 stale base/developer instructions 静默沿用。

## 9. Prompt Source Organization

### 9.1 Repo-Level Prompt Packs

理想目录结构：

```text
desktop/prompts/
  platform/
    core.md
    memory.md
    capabilities.md
  channel/
    agentteam/
      core.md
      tool_catalog.md
      messaging_examples.md
  events/
    new_message.md
    unread_summary.md
    deferred_continuation.md
  notices/
    identity_notice.md
    runtime_repair.md
```

### 9.2 Generated Sources

不是所有 source 都应手写 markdown。下面这些更适合由 typed data 生成：

- MCP tool catalog
- tool syntax examples
- runtime capability examples
- retrieved local workspace memory summary

这类 source 应作为 `SourceKind=generated` 或 `SourceKind=retrieval` 进入 compiler，而不是由多个 `skilldoc.go` 重复手写。

### 9.3 Workspace Sources

workspace doc 属于另一类 source：

- `IDENTITY.md` → `workspace_doc` source
  - 默认 materialize 成 persistent/base instruction block；它是 bounded workspace identity doc
- 未来若有显式 prompt-facing workspace docs（例如 `PROMPTS/*.md`），也应有独立 source kind

但最佳形态不鼓励“任意 workspace markdown 自动注入 prompt”。

### 9.4 Agent Developer Sources

developer-side source taxonomy 也必须是 first-class：

- `AGENTS.md` → `agent_config` source，默认进入 `SlotDeveloper`
- agent `description` / `initial role` → generated `agent_config` source，默认进入 `SlotBase`
- 未来若有显式 per-agent prompt packs，也必须作为 `agent_config` source 进入 compiler

这些 source 共同构成 canonical agent-owned instruction taxonomy：`AGENTS.md` 落在 `developerInstructions`，而 `initial role` 这种 identity anchor 落在 `baseInstructions`；二者都不应散落在 driver fallback 或 undocumented builder special case 中。

## 10. Compiler Pipeline

最佳形态下，prompt compile pipeline 必须是一个显式、可测试的流水线。

### 10.0 Event-Scoped Compile Model

最佳形态里，compile 的单位不是固定的一整段长 prompt，而是 **某个 lifecycle event 对应的 runtime input**。这里有两层标识：

- `CompileEventKind`
  - 粗粒度 category：`thread_contract` / `turn_context` / `delivery_envelope`
- `CompileEventType`
  - 细粒度 lifecycle identity：`thread_start`、`thread_resume`、`fresh_start`、`wake`、`resume`、`deferred_continuation`、`routed_message_delivery`、`reaction_delivery`、`final_visible_reply_check`

二者都必须可 inspect；不能只保留 category，然后把真实 lifecycle subtype 藏在调用方 side metadata 里。

按 category 看，至少有三类：

1. **Thread Contract Compile**
   - 发生在 `thread/start`、`thread/resume`、instruction invalidation cutover 等低频事件
   - 产物是 `RuntimeInputEnvelope{EventKind=thread_contract, Bundle=...}`
   - `Bundle` 内承载长期 thread-level contract，例如 `baseInstructions` / `developerInstructions` 对应的 semantic blocks
   - 应优先复用缓存，只在稳定 source 变化时重编

2. **Turn Context Compile**
   - 发生在 fresh start、wake、resume、deferred continuation、final visible reply check 等事件
   - 产物是 `RuntimeInputEnvelope{EventKind=turn_context, Bundle=...}`
   - `Bundle` 内承载当前 turn 需要发送给 runtime 的动态上下文
   - 例如 `FreshPrompt`、`ResumePrompt` 或其他 runtime 等价输入

3. **Delivery Envelope Compile**
   - 发生在 session 已活着时的新 delivery signal 投递（包括 routed message 与 reaction signal）
   - 产物是 `RuntimeInputEnvelope{EventKind=delivery_envelope, Delivery=...}`
   - `Delivery` 内是 live-delivery envelope：payload、target/thread binding、wakeToken、delivery correlation、replay context、transport binding、evidence class
   - 这类 compile 不应被误解为“重建整段 system prompt”

因此，最佳形态应支持：

- **per-event compiled runtime input**：每个事件都能产出可 inspect 的规范输入
- **incremental reuse**：稳定 contract 与 reference 尽量缓存，不在每次 delivery 时 full rebuild
- **per-event prompt visibility**：无论是 bootstrap、wake 还是 ongoing delivery，都能在现有 activity trajectory 里看到这一事件实际发给 runtime 的输入文本

### 10.1 Phase A — Collect

收集 compile context：

- agent id / name / config
- runtime type
- channel type
- event id / event type / optional turn id
- conversation binding
- delivery transport binding（如 runtime session id、active turn id、direct-delivery capability）
- delivery correlation（如 messageId / deliveryId / wakeToken）
- deferred replay state（如 acceptedAt、original order、attempt、deferred batch）
- current message
- unread summary
- deferred queue state
- active notices
- workspace docs (`IDENTITY.md` 等)
- retrieval results（如本地 workspace docs）

输出：`CompileContext`

### 10.2 Phase B — Resolve Sources

根据 `CompileContext` 选择本轮适用的 prompt sources：

- platform pack
- channel pack
- runtime guidance pack
- workspace doc sources
- agent config sources
- event sources
- notice sources
- generated app-tool sources
- retrieval sources

输出：`[]ResolvedPromptSource`

### 10.3 Phase C — Materialize Blocks

对 bundle-bearing compile，把 source 渲染成 canonical `PromptBlock`：

- placeholder interpolation
- tool name adaptation placeholder 仍保留为 source-level capability，由 compiler 注入 runtime naming policy
- workspace doc trimming / validation
- example generation
- token estimation

输出：`[]PromptBlock`

对 `delivery envelope compile`，这一阶段不强制产出 `PromptBlock`；它应直接 materialize 成 `DeliveryEnvelope` 所需字段，而不是为了套模型硬造 fake bundle。
其中至少包括：

- `SourceKind`
- canonical conversation binding
- canonical transport binding
- correlation fields（`messageId` / `deliveryId` / `wakeToken`）
- replay/deferred fields
- evidence class
- payload text / metadata

Typed `DeliveryEnvelope` 是 transport、ack、freshness、replay 和 diagnostics 的权威
载体；model-visible `deliveryText` 不是它的字段转储。`deliveryText` 只投影模型完成
当前动作需要的 message/author、canonical target、按需 thread history、条件性业务
上下文和通信工具指引。它不携带 daemon-private routing metadata 或任何 settlement
凭据。`runtimeSessionId`、provider turn、wake token、freshness frontier、相同的
message/delivery ID 以及 replay bookkeeping 等内部字段保留在 typed envelope 和
daemon state，不重复进入文本。Replay/deferred 状态只有在会改变模型处理方式时才以
一句最小提示投影。

### 10.4 Phase D — Normalize / Merge Policy

对 bundle-bearing compile，统一 ordering、dedupe、conflict resolution：

- base instructions 的顺序必须稳定
- developer slot 的顺序必须稳定
- base / developer fingerprint 与 invalidation 必须独立可解释；`AGENTS.md` 的变更应明确落在 developer slot，agent description / initial role 的变更应明确落在 base slot，而不是靠 undocumented side effect 漂移
- notices 的优先级高于普通 turn context
- reference 不允许 silently 进入 base slot
- dedupe 以 `block.ID` 为主，而不是裸文本比对

对 delivery-bearing compile，则统一 envelope schema、routing metadata、wake metadata 与 fingerprint 生成规则。

### 10.5 Phase E — Render

renderer 根据 runtime capability 把同一份 `RuntimeInputEnvelope` 渲染成 transport payload。

例如：

#### `codex`
- 对 `EventThreadContract` input：`Bundle` 中的 `SlotBase` → `baseInstructions`
- 对 `EventThreadContract` input：`Bundle` 中的 `SlotDeveloper` → `developerInstructions`
- 对 `EventTurnContext` input：`Bundle` 中的 `SlotTurn` + effective notices + selected reference → `FreshPrompt` / `ResumePrompt`
- 对 `EventDeliveryEnvelope` input：`Delivery` → runtime-native delivery payload / direct turn input / wake envelope
- 对 `EventDeliveryEnvelope` 的 renderer 输出，`wakeToken` / `deliveryId` / `runtimeSessionId` / `attempt` / `deferredBatchId` / `evidenceClass` 必须保留到 wire payload；driver 不允许再补、改、删这些 correlation 字段

#### non-split runtimes
- 对 bundle-bearing input：把 `SlotBase` / `SlotDeveloper` / `SlotTurn` 以稳定顺序渲染成单字符串 prompt
- 对 delivery-bearing input：渲染为 runtime 能理解的轻量 delivery payload，而不是伪装成完整 prompt 重发
- 对 delivery-bearing input：同样必须把 correlation / transport / evidenceClass 字段保留到 wire payload，而不是在 adapter 里丢失
- `reference` 的 render policy 可因 runtime 不同而不同，但必须由 renderer 明确决定，而不是靠调用方临时拼接

### 10.6 Phase F — Emit Prompt Visibility

编译与渲染之后，bundle-bearing event 与 delivery-bearing event 都必须把**实际发送给 runtime 的 prompt 文本**暴露到现有 `agent:trajectory` 流中：

- entry kind 固定为 `prompt_input`
- 至少带 `text`、`promptKind`、`runtime`、`eventType`
- 对 split runtime，可按实际发送的 section 分别 emit（例如 `baseInstructions`、`developerInstructions`、`turnInput`）
- 对 non-split runtime，可 emit 一条最终合成后的 prompt 文本
- 这是用户可见的默认 surface；不新增 dedicated prompt inspection API、polling 或本地 artifact persistence

### 10.7 Phase G — Deliver

最后才进入 driver：

- driver 只拿渲染结果
- driver 不参与 semantic merge
- driver 可以报告 runtime 限制，但不能重新定义 block 分类

## 11. Runtime Renderer Contract

### 11.1 One Semantic Model, Many Renderers

系统里只允许有一套 canonical prompt semantics。

不允许出现：
- `codex` app-server 实现有一套 prompt 语义
- `claude` / `opencode` 再各自长出一套独立语义模型

可以有不同 renderer，但不能有不同 semantic truth。

### 11.2 Renderer Interface

```go
type PromptRenderer interface {
    Runtime() agent.RuntimeType
    Render(input RuntimeInputEnvelope, opts RenderOptions) (RenderedPromptPayload, error)
}
```

说明：

- render target 由 `Runtime()` / `RenderOptions` 决定，而不是写死在 `PromptBundle`
- renderer 必须同时理解 bundle-bearing input 与 delivery-bearing input，但不得重新定义 semantic classification

### 11.3 Driver Constraints

driver 不允许：

- 自己 prepend 新 notice
- 决定 fresh/resume 哪些 block 该存在
- 修改 block 顺序
- 按启发式剪裁 prompt
- 对 delivery payload 重新塞入或覆盖 `wakeToken` / `deliveryId` / `runtimeSessionId` / `attempt` / `deferredBatchId` / `evidenceClass`

driver 允许：

- 基于 runtime transport 特性选择 `turn/start`、stdin notification、queued replay 等 API
- 把 renderer 产出的字段放到 wire payload
- 回报 transport failure / retryability / context-window errors

## 12. Observability / Prompt Visibility

最佳形态的核心不只是“结构更优雅”，而是 **prompt 成为现有 activity timeline 里的可见输入**。canonical 方案是：

- 不新增 standalone inspector page
- 不新增 dedicated prompt debug API
- 不持久化本地 prompt debug artifact 目录
- 每次真正送给 runtime 的输入，都通过现有 `agent:trajectory` websocket 流追加为 `prompt_input`

### 12.1 Activity Surface

`prompt_input` entry 至少包含：

- `text`：实际发送给 runtime 的 prompt 文本
- `promptKind`：例如 `base_instructions` / `developer_instructions` / `turn_input` / `deferred_continuation` / `resume` / `delivery_text`
- `runtime`
- `eventType`

前端默认在 Activity panel 中与 `text` / `tool_output` 同级展示，默认折叠，支持展开查看全文与复制。

### 12.2 Inspectability Boundary

当前 canonical inspect boundary 变为：

- compiler 内部仍保留 typed IR、source metadata、fingerprint、validator 与 renderer
- 用户默认看到的是 final prompt text，而不是 per-block explain / diff / source-map explorer
- 如未来重新引入更重的 inspect surface，必须另立 canonical spec；本规范当前只把 `prompt_input` activity 作为用户默认可见面

## 13. APIs / Developer Experience

### 13.1 Compiler Package

理想 package：

```text
desktop/internal/promptcompiler/
  model.go
  collect.go
  resolve.go
  materialize.go
  merge.go
  render.go
```

### 13.2 Public-ish Internal API Shape

```go
type CompileDescriptor struct {
    EventID   string
    EventKind CompileEventKind
    EventType CompileEventType
    TurnID    string // optional
}

func CompileRuntimeInput(ctx context.Context, desc CompileDescriptor, in CompileInput) (RuntimeInputEnvelope, error)

func CompileThreadContract(ctx context.Context, eventType CompileEventType, eventID string, in CompileInput) (RuntimeInputEnvelope, error)
func CompileTurnContext(ctx context.Context, eventType CompileEventType, eventID string, turnID string, in CompileInput) (RuntimeInputEnvelope, error)
func CompileDeliveryEnvelope(ctx context.Context, eventType CompileEventType, eventID string, in CompileInput) (RuntimeInputEnvelope, error)

func RenderRuntimeInput(ctx context.Context, input RuntimeInputEnvelope, rt agent.RuntimeType, opts RenderOptions) (RenderedPromptPayload, error)
```

### 13.3 Activity / Delivery Surface

最佳形态不提供 dedicated prompt inspection API。用户可见的 prompt surface 统一走现有 `agent:trajectory` 流里的 `prompt_input` entry。

对于内部开发者体验：

- `CompileRuntimeInput(...)` 返回 canonical typed IR
- `RenderRuntimeInput(...)` 返回 runtime-specific payload surface
- UI 默认只消费 `prompt_input` trajectory，而不是额外的 inspect endpoint

## 14. Testing Strategy

最佳形态下，prompt testing 更像 compiler / renderer testing，而不是简单 contains。

### 14.1 Source Validation Tests

验证 prompt source metadata：

- layer/type/slot/durability 是否合法
- template placeholder 是否可解析
- generated source schema 是否稳定

### 14.2 Compile Tests

给定 compile context，验证 `RuntimeInputEnvelope`：

- event id / event kind / event type 是否正确
- 对 bundle-bearing input：选中了哪些 blocks、order 是否稳定、slot 是否正确
- 对 delivery-bearing input：envelope fields / routing metadata / wake metadata / correlation / evidence class 是否正确
- dedupe / merge policy 是否正确

### 14.3 Render Tests

给定同一份 compiled runtime input，验证不同 renderer 的输出：

- `codex` split payload / delivery payload 是否正确
- single-string runtime render 是否正确
- runtime-specific tool name adaptation 是否正确

### 14.4 Golden / Snapshot Tests

最佳形态应大量使用 golden：

- runtime-input golden
- bundle golden
- rendered payload golden
- source-map golden
- prompt diff golden

### 14.5 Invariant Tests

例如：
- `reference` block 不得直接进入 `baseInstructions`
- `turn_context` 不得被错误标记为 `persistent`
- driver 无法绕开 compiler 注入额外 block
- workspace doc 超过 `MaxWorkspaceDocBytes` 时，必须按 compiler policy 明确 fail-fast 或 structured compaction，不能静默越过
- materialized block 超过 `MaxBlockTokens` 时，必须在 compiler 边界 compact 或 reject，而不是 runtime 临时爆掉

## 15. Size / Safety / Policy

### 15.1 Workspace Doc Limits

`IDENTITY.md` 直接影响每轮 prompt 成本，因此需要明确限制与验证策略。
产品级字符限制由 compiler policy `DefaultMaxIdentityDocCharacters`（默认 trim 后最多 4096 个 Unicode 字符）定义并在 compile 时强制；byte/token 级兜底由 `MaxWorkspaceDocBytes` / `MaxBlockTokens` 提供，不依赖 dashboard handler。

最佳形态里，compiler policy 至少要暴露两个边界：

- `MaxWorkspaceDocBytes`
  - 任意 workspace doc source 在 raw materialization 前允许读取的最大字节数
- `MaxBlockTokens`
  - 任意单个 materialized block 允许占用的最大 token 预算

并且必须显式定义 overflow 策略：

- **fail-fast**
  - 超限后直接返回 compile-time error，并给出 source id / path / limit 信息
- **structured compaction**
  - 先经过明确的 compiler compaction policy（例如保留 frontmatter / role / constraints，再摘要长说明），然后再生成 block

在 compiler 的 prompt-materialization 路径上（`compileThreadContract`），对 `IDENTITY.md` 以及被 compiler 读作 prompt block 的 `AGENTS.md` 等 always-on workspace 文档，默认策略是 **structured compaction（优雅降级）**，而不是 fail-fast：文档超限时在 compiler 边界按 rune 边界截断到预算内（identity 走产品级 `MaxIdentityDocCharacters`，所有 workspace doc 走 `MaxWorkspaceDocBytes`，软限），并注入一条 persistent notice（列出所有触发的约束：doc / path / 当前大小 / 各限制 / 如何恢复），让 agent 继续启动并在下一轮自行精简——compile / start 不再因文档长度被"砖死"。这与 `desktop/internal/workspace/memory.go` 对 `MEMORY.md` 的降级范式一致；原先的 fail-fast 会让 marginally 超限的 identity（或绕过 dashboard 写入侧 guard 的 soul migration / agent 自写）直接无法启动，代价过重。`MaxBlockTokens` 仍作为单个 materialized block 的 token 硬上限保留给非 doc / turn-critical block（current message、event context 等，截断会丢必要信息）；doc 类 block 在 materialize 前已被软预算预截，正常触达不到该硬限（仅当 wrapper 自身 overhead 超过 token 预算时，doc block 标记为不可 materialize 并跳过，但仍注入 notice）。注：OpenCode 等 native-carrier runtime 的 `AGENTS.md` 由 carrier 原样保留 + 追加 managed contract，**不是** prompt materialization，不在本软预算范围内——对其截断会破坏用户文件；其内存放大防护作为独立 follow-up（streaming copy / 硬上限）处理。

workspace doc 的 oversize 不再走 fail-fast：它不产生 compile-time error，而是注入上述 persistent notice，因此不映射到 start/wake failure。仍保留 fail-fast 的来源是 `MaxBlockTokens` 对非 doc / turn-critical block 的硬上限（以及任何未来显式声明 fail-fast 的 source）。对这些 fail-fast 来源，canonical recovery path 至少必须包括：

- compiler 返回 typed error（例如 `PromptCompileError{Kind: BlockTooLarge, BlockID, Tokens, Limit, EventID, EventType}`）
- dispatcher / runtime boundary 把它 surface 成 operator-visible failure，并与 `../message/delivery-runtime.md` 的 evidence contract 对齐：
  - `thread_start` / `thread_resume` → 至少可映射到 `runtime start failed`
  - `wake` / `deferred_continuation` / live delivery replay → 至少可映射到 `wake failed`
- 用户可见 surface 必须能指出是哪个 source 超限、当前大小、限制是多少、如何恢复；不能让 start/wake 被静默"砖死"

### 15.2 Token Budget Policy Belongs In Compiler

最佳形态里，token budget 决策必须发生在 compiler policy：

- 哪些 reference 可以 compact / shorten
- 哪些 generated examples 可以裁剪
- 哪些 retrieved context 要截断

不允许由 driver 在最后一刻临时裁掉一部分 prompt。

### 15.3 Prompt Visibility Privacy

`prompt_input` 会把真实发送给 runtime 的文本暴露到当前 workspace 的 activity 流里，因此仍然可能包含：

- 用户消息
- retrieved local workspace memory
- internal notices

因此：

- 它只应出现在已有权限的 workspace activity surface 中
- 默认不额外持久化为本地 artifact
- 若未来需要更长期的 prompt archival / search，必须单独定义 retention、权限与脱敏策略

## 16. Mapping To Current Implementation

最佳形态并不否认当前实现的价值；它是当前实现的进一步抽象。

### 16.1 Current Components That Would Be Absorbed

- `desktop/internal/dispatcher/prompt.go`
  - 保留语义，但从 string builder 迁移到 compiler phase / prompt source pack
- `desktop/internal/channel/*/skilldoc.go`
  - 迁移为 channel prompt packs + generated tool catalog blocks
- `desktop/prompts/platform/workspace_memory.md` + `desktop/internal/dispatcher/prompt_compiler.go`
  - 已收敛为平台 prompt pack / prompt compiler 输入，描述 `MEMORY.md` + `state/` + `kb/` + `archive/` 的本地恢复模型
- `desktop/internal/dispatcher/identity_update.go`
  - 保留 notice generation，但不再直接 prepend raw text，而是产出 notice block
- `driver_codex.go`
  - 只保留 renderer-output transport 发送逻辑

### 16.2 Current Implementation Already On The Path

当前实现已经具备这些最佳形态的前置条件：

- `baseInstructions` / `developerInstructions` split
- `FreshPrompt` / `ResumePrompt` split
- `SkillDocSplit(Core, Reference)`
- `IDENTITY.md` / `MEMORY.md` 边界更清晰
- instruction fingerprint policy

因此本设计不是完全推翻，而是把这些零散演进收束为统一 compiler architecture。

## 17. Ideal End-State Module Layout

```text
desktop/
  prompts/
    platform/
    channel/
    events/
    notices/
  internal/
    promptcompiler/
      model.go
      collect.go
      resolve.go
      materialize.go
      merge.go
      render/
        interface.go
        codex.go
        claude.go
        codex_cli.go
        opencode.go
```

### Dispatcher Responsibilities In The End-State

dispatcher 只负责：

1. 收集 compile input
2. 调 compiler 生成 runtime input envelope
3. 调 renderer 渲染 runtime payload
4. 把最终输入作为 `prompt_input` entry 发到现有 trajectory 流
5. 调 driver 投递

dispatcher 不再自己知道某段 prompt 的具体文案。

## 18. Recommended Acceptance Criteria For The Architecture

达到“最佳架构”时，应至少满足：

1. 系统内部存在明确的 `RuntimeInputEnvelope` + `PromptBundle` typed IR
2. 任意送给 runtime 的 prompt 文本都会以 `TrajectoryEntry{Kind:prompt_input}` 出现在现有 activity 流里
3. bundle-bearing event 中的 block 仍然保留显式 source metadata，可追踪回 source id / source path
4. runtime driver 不再持有 prompt semantic decision
5. `AGENTS.md`、`IDENTITY.md`、agent description / role、channel rules、tool catalog、event context、notices 全部通过 compiler pipeline 统一进入 compiled runtime input（bundle 或 delivery envelope）；`IDENTITY.md` 作为 bounded workspace identity doc 进入 persistent instruction surface，并由 compiler 输出一段稳定的 `Role & local identity` 平台说明
6. prompt 测试以 compile/render/golden 为主，而非只做 contains
7. prompt tuning 可以围绕 block / policy / order / render strategy 进行，而不是只改字符串
8. developer slot 的 source taxonomy 与 invalidation rules 在 canonical spec 中明文列出，并可与 base slot 独立解释

## 19. Appendix: Practical Guidance For Cross-Spec Alignment

本文件现已是 prompt compiler architecture 的 canonical spec。后续若其他 canonical docs 涉及相关边界，应与本文件保持一致：

- runtime boundary / driver contract ↔ `specs/daemon/daemon.md`
- higher-level architecture constraints ↔ `docs/architecture-spec.md`
- current implementation mapping ↔ `docs/tech_design.md`
- `codex` app-server implementation-specific mapping ↔ `specs/daemon/codex-instructions-prompt-split.md`

也就是说：
- 这份文档定义 prompt compiler architecture 的 **canonical boundary**
- 其他文档若覆盖其子域，应引用并保持与本文件一致，而不是重新定义另一套语义
