Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/engineering/advice-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ Advice 按意图区分为 Hint 和 Concern,并映射到 Primary Agent 的两

Advisor 只在 Watch Run 期间主动调用 `advise`。Watch Run 之外,只有用户当前消息明确要求送达某个观点时才允许 Advice。Ask Advisor 的普通输出是给用户的 Second Opinion;`/advisor:handoff` 是用户确认后转交 Second Opinion 的独立动作。

### Advice 格式

每条 Advice 作为一条 custom message 送达 Primary Agent,使用 opening-only pseudo-XML metadata header,正文不做 XML text escaping:

```text
<advisor-advice kind="hint">
`sessionTtl` 应使用 camelCase,参考 https://docs.example.com/auth/v2。
```

正文延伸到 custom message 末尾。Advice 正文内的 `<`、`>`、`&` 等字符保持原始形式。这不是安全边界,不应交给 XML parser 解析。

## Provenance and Feedback Prevention

每条 Advice 都携带来源、类型和 delivery channel 元数据:
Expand Down
4 changes: 2 additions & 2 deletions docs/engineering/ask-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

Advisor 空闲时,`/advisor <消息>` 启动一次 Ask Advisor。Runtime 在请求到达时构造 Primary Transcript 快照,并向 Advisor Session 写入两条边界独立的消息:

1. 隐藏 custom message:使用 [Primary Transcript](primary-transcript.md) 定义的 Ask Context payload,携带 Primary Transcript 区间 `[start, end)`、Primary Agent loop state 和可选近期对话。
1. 隐藏 custom message:使用 [Primary Transcript](primary-transcript.md) 定义的 Ask Context payload,携带 `<primary-context>` opening-only metadata header、Primary Transcript 区间 `[start, end)`、Primary Agent loop state 和可选近期对话。正文延伸到该 custom message 末尾。
2. User message:只包含用户向 Advisor 提出的原始问题。

Ask Context 的 markdown body 与 Pull Transcript 使用同一 slice renderer 生成,格式完全一致(含 tool call/result、edit diff、custom message 等)。`start` 为最新 Primary user message index,`end` 为快照总长度,`end` 可直接作为后续 `pull_transcript` 的 `since_index`。
Ask Context 的 markdown body 与 Pull Transcript 使用同一 slice renderer 生成,格式完全一致(含 tool call/result、edit diff、custom message 等)。正文不做 XML text escaping,保持原始 Markdown。`start` 为最新 Primary user message index,`end` 为快照总长度,`end` 可直接作为后续 `pull_transcript` 的 `since_index`。

同一 Primary user turn 内重复 Ask 时,不重复注入 body,仅发送 position-only `<primary-head at="..." state="..." />`。Advisor 应使用上一次有 body 的 `<primary-context>` 的 `end` 或最近 Pull 的 `end`(取较大者)作为续拉起点。

Expand Down
2 changes: 1 addition & 1 deletion docs/engineering/overlay.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Overlay Component 直接服从 Pi 传入的实际宽度与终端可用高度。T

Overlay 订阅 Advisor `AgentSessionEvent`,把 thinking、text、tool calls、tool results 和 notices 增量投影到 UI state。Ask Advisor 与 Watch Run 使用同一个 Overlay 和同一份 Advisor Transcript。

折叠的 Pull Block 使用 `pull_transcript` tool result details 中的 display items,不解析返回给 Advisor 模型的 markdown。Display item 与 markdown 由同一次 Primary Transcript slice 序列化产生,tool call 与 tool result 合并为一个 item,因此 Pull header 的 `N msgs` 表示 Overlay 最终展示的逻辑条目数。Context header 的 `N msgs` 表示 display items 条目数(含 tool rows);没有新的 Primary 内容时显示 `Context · 0 msgs`,说明空 Block 是 position-only Context。Context 与 Pull 中的 Primary user/agent 正文统一使用 `text`,role prefix 使用 `dim`;Pull 中真正的工具行继续使用 `toolTitle` 与 `toolOutput`。展开的 Pull Block 直接呈现保留的完整 `<primary-transcript>` tool-result text;展开的 Context Block 直接呈现发送给 Advisor 的完整 `<primary-context>`(有 body)或 `<primary-head>`(position-only)hidden custom message。两种原始 payload 统一使用 `text`,并通过各自的背景与标题颜色表达 Context 和 Pull 语义,同时保留模型实际收到的 XML 边界、markdown marker 和正文字符。
折叠的 Pull Block 使用 `pull_transcript` tool result details 中的 display items,不解析返回给 Advisor 模型的 markdown。Display item 与 markdown 由同一次 Primary Transcript slice 序列化产生,tool call 与 tool result 合并为一个 item,因此 Pull header 的 `N msgs` 表示 Overlay 最终展示的逻辑条目数。Context header 的 `N msgs` 表示 display items 条目数(含 tool rows);没有新的 Primary 内容时显示 `Context · 0 msgs`,说明空 Block 是 position-only Context。Context 与 Pull 中的 Primary user/agent 正文统一使用 `text`,role prefix 使用 `dim`;Pull 中真正的工具行继续使用 `toolTitle` 与 `toolOutput`。展开的 Pull Block 直接呈现保留的完整 `<primary-transcript>` tool-result text(opening-only header + body);展开的 Context Block 直接呈现发送给 Advisor 的完整 `<primary-context>`(有 body)或 `<primary-head>`(position-only)hidden custom message。两种原始 payload 统一使用 `text`,并通过各自的背景与标题颜色表达 Context 和 Pull 语义,同时保留模型实际收到的 pseudo-XML header、markdown marker 和正文字符。

Advisor 正在 streaming 时,新消息通过 Steer 排队。Ask Context 注入与 `session.prompt()` 之间的短暂启动窗口使用 transient guard:如果新的提交遇到尚未进入 streaming 的活动 Ask 或 Watch Run,把输入恢复到 Overlay draft,避免丢失输入,不引入额外消息队列状态机。

Expand Down
31 changes: 25 additions & 6 deletions docs/engineering/primary-transcript.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ Primary Transcript 是 Primary Agent 提供给 Advisor 的内容契约。Runtime

两种输入都不包含 Primary assistant thinking。Advisor 需要判断工作过程时,依赖 assistant text、工具意图、工具状态和可见变更,避免把高体量 thinking 注入 Advisor context。

## 外层格式:Opening-only Pseudo-XML Metadata Header

`<primary-context>`、`<primary-transcript>`、`<advisor-advice>` 和 `<advisor-handoff>` 使用 opening-only pseudo-XML metadata header。正文由所在 message 的边界或下一个 opening tag 决定范围,不设 closing tag,不进行 XML text escaping。

- Ask Context 的 `<primary-context>` 作为一条 hidden custom message 发送给 Advisor;正文延伸到该 message 末尾。
- Pull Transcript 的 `<primary-transcript>` 作为 `pull_transcript` tool result 返回;正文延伸到该 tool result 末尾。
- Advice 的 `<advisor-advice>` 作为一条 custom message 送达 Primary Agent;正文延伸到该 message 末尾。
- Handoff 的 `<advisor-handoff>` 作为一条 user message 送达 Primary Agent,内含 `<original-request>`、`<second-opinion>`、`<user-instructions>` 三个 opening-only section header;每段正文延伸到下一个 header 或 message 末尾。

```text
<primary-context start="9" end="12" state="idle">
**user**:
请审查<T>泛型的使用。

**agent**:
→ read(src/auth.ts) ⇒ ok · 80 lines
```

正文中的 `<`、`>`、`&` 等字符保持原始形式,不做 XML entity 转义。用户和 Primary Agent 有意写入的字面量(如 `</primary-transcript>`)不会影响结构,因为外层不设 closing tag,范围由 message boundary 提供。

这不是安全边界;pseudo-XML header 不应交给 XML parser 解析。`<primary-head>` 保持单行 self-closing 形式不变。

## 共享序列化

Ask Context 与 Pull Transcript 的 markdown body 由同一个 slice renderer 生成,使用相同的 `formatSessionHistoryMarkdown` 参数:
Expand All @@ -23,7 +45,7 @@ Ask Context 与 Pull Transcript 的 markdown body 由同一个 slice renderer
- `expandEditDiffs: true`
- `displayItems`(同步生成,供 Overlay 使用)

两者的差异仅在 XML 外层和区间来源:
两者的差异仅在外层 header 和区间来源:

- Ask Context:区间 `[start, end)` 由 `selectAskContext` 决定,`start` 为最新 Primary user message index。
- Pull Transcript:区间 `[start, end)` 由 Advisor 通过 `since_index` 和 `count` 指定。
Expand All @@ -43,7 +65,6 @@ Ask Context 把选定的 `[start, end)` 消息范围渲染成与 Pull Transcript
**agent**:
我先看一下现有的 auth 模块。
→ read(src/auth.ts) ⇒ ok · 80 lines
</primary-context>
```

同一 Primary user turn 内重复 Ask 时,不重复注入 body,仅发送 position-only payload,通过 `at` 指明当前 Primary Transcript 进展:
Expand All @@ -56,23 +77,21 @@ Ask Context 把选定的 `[start, end)` 消息范围渲染成与 Pull Transcript

Pull Transcript 把选定的 `[start, end)` 消息范围渲染成紧凑 markdown:

- 完整 markdown body 作为 XML text node 转义后写入 `<primary-transcript>`;正文里的 XML 字面量不会改变 transcript 边界。

- 完整 markdown body 以原始形式直接放置在 `<primary-transcript>` header 之后;正文范围延伸至 tool result message 末尾,不设 closing tag,不做 XML text escaping。
- user、developer 和 assistant text 分别使用 `**user**:`、`**developer**:` 和 `**agent**:` role marker;连续同角色消息复用同一个 marker。
- tool call 与对应 result 合并成一条 `→ tool(args) ⇒ status` 摘要,保留工具意图、成功/错误状态和输出行数,不附带完整 tool output。
- edit tool result 携带 diff 时,在工具摘要后附带完整 unified diff。
- Primary 发起的 bash/python execution 使用单行命令、状态和输出行数摘要。
- plan-mode constraints 与 approved plan 使用 `<primary-context kind="…">` 保留全文;其他 custom message、branch、compaction 和 file mention 使用单行摘要。
- Primary assistant thinking 始终省略。

两种输入的 markdown body 通过同一个 slice renderer 生成,外层标签不同(`<primary-context>` 与 `<primary-transcript>`)。Advisor 收到的完整 tool result 示例:
两种输入的 markdown body 通过同一个 slice renderer 生成,外层 header 不同(`<primary-context>` 与 `<primary-transcript>`)。Advisor 收到的完整 tool result 示例:

```text
<primary-transcript start="5" end="11" total="11" state="running" wait="new_messages" waited-ms="842">
**agent**:
我先看一下现有的 auth 模块。
→ read(src/auth.ts) ⇒ ok · 80 lines
</primary-transcript>
```

`start`、`end` 和 `total` 基于同一个过滤后消息序列。Markdown 与 Overlay 使用的结构化 display items 必须从同一个消息 slice 同步生成;display items 只服务 Overlay,Advisor 模型读取上面的完整文本 tool result。
Expand Down
42 changes: 24 additions & 18 deletions docs/prd.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Overlay 是 top-center 的独立面板。用户可在 Overlay 中直接向 Advis
- 运行中发送的消息只包含用户输入,不创建 Ask Context custom message,不构造新的 Primary Transcript 位置,也不消耗 Ask Context 注入记录。
- 这条消息在当前 assistant turn 的工具调用结束后、下一次模型调用前送达,让用户可以及时补充、纠正或对齐 Advisor。
- 每次 Ask Advisor 都会告诉 Advisor 当前 Primary Transcript 的右开边界位置和 Primary Agent 运行状态,Advisor 可以用该位置判断进展变化并按需 Pull。
- 当最新 Primary user message 还没有用于当前 Advisor Agent Session 的自动注入时,Ask Advisor 附带 Ask Context:`<primary-context>` payload 的 markdown body 与 Pull Transcript 格式完全一致(含 tool call/result、edit diff 等),区间为 `[primaryUserMessageIndex, end)`。
- 当最新 Primary user message 还没有用于当前 Advisor Agent Session 的自动注入时,Ask Advisor 附带 Ask Context:`<primary-context>` opening-only metadata header + markdown body(不做 XML text escaping),格式与 Pull Transcript 完全一致(含 tool call/result、edit diff 等),区间为 `[primaryUserMessageIndex, end)`。正文延伸到该 hidden custom message 末尾。
- 当最新 Primary user message 已经用于当前 Advisor Agent Session 的自动注入时,后续 Ask 发送 position-only `<primary-head at="..." state="..." />`,不重复附带 body。Advisor 之前的 Pull 不参与这个去重判断。
- Ask Context 不完整或用户问题需要更多历史、工具过程时,Advisor 能通过 Pull 主动补充 Primary Transcript。
- 多次 Ask Advisor 之间,Advisor 能延续自己的上下文。
Expand All @@ -80,26 +80,32 @@ Overlay 是 top-center 的独立面板。用户可在 Overlay 中直接向 Advis

转交消息格式:

三个元素的正文都作为 XML text node,经 XML 转义后写入。未提供 `instructions` 时使用默认值。
Handoff 消息使用 opening-only pseudo-XML metadata header,每个 section 的正文延伸到下一个 opening tag 或 message 末尾,不做 XML text escaping。未提供 `user-instructions` 时使用默认值。

```xml
```text
<advisor-handoff>
<original-request>...</original-request>
<second-opinion>...</second-opinion>
<instructions>Use this Second Opinion as supporting context.</instructions>
</advisor-handoff>
<original-request>
审查 <T> 泛型的使用是否安全。
<second-opinion>
<T> 是正确的,Java 的类型擦除保证了向后兼容。
<user-instructions>
Use this Second Opinion as supporting context.
```

用户提供自定义 `instructions` 时替换默认值:
用户提供自定义 instructions 时替换默认值:

```xml
```text
<advisor-handoff>
<original-request>...</original-request>
<second-opinion>...</second-opinion>
<instructions>先只改 auth 模块,其余部分等我看完再说。</instructions>
</advisor-handoff>
<original-request>
审查 <T> 泛型的使用是否安全。
<second-opinion>
<T> 是正确的,Java 的类型擦除保证了向后兼容。
<user-instructions>
先只改 auth 模块,其余部分等我看完再说。
```

这不是安全边界;pseudo-XML header 不应交给 XML parser 解析。

验收标准:

- handoff 只使用最近一次完成的 Ask Advisor 可见回答。
Expand Down Expand Up @@ -139,16 +145,16 @@ Overlay 的 UI 内容约定和渲染示例见 [`docs/ui.html`](ui.html)。

**区块格式**:Overlay 内容使用带背景色的 Block,与 Pi 的 tool call/result block 视觉一致。背景色覆盖整行即 block 边界,不额外缩进。不使用 emoji 图标。

| Block | Header | 背景色 | 说明 |
| ------- | ------------------------------ | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| Context | `Context` | `customMessageBg` + `customMessageLabel`/`customMessageText` | 默认显示前 5 个视觉行;无新文本时保留紧凑 Block;Ctrl+O 展开完整 `<primary-context>` payload |
| Pull | `Pull [0, 12) → 8 msgs · 1.2s` | `toolSuccessBg` + `toolTitle`/`toolOutput` | 默认显示前 5 个视觉行;Ctrl+O 展开完整 `<primary-transcript>` payload |
| Block | Header | 背景色 | 说明 |
| ------- | ------------------------------ | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| Context | `Context` | `customMessageBg` + `customMessageLabel`/`customMessageText` | 默认显示前 5 个视觉行;无新文本时保留紧凑 Block;Ctrl+O 展开完整 `<primary-context>` opening-only header + body |
| Pull | `Pull [0, 12) → 8 msgs · 1.2s` | `toolSuccessBg` + `toolTitle`/`toolOutput` | 默认显示前 5 个视觉行;Ctrl+O 展开完整 `<primary-transcript>` opening-only header + body |

Block 内部消息行使用文本前缀:`user:`(用户)、`agent:`(Primary 回复)、`→ tool_name(args) ⇒ ok · N lines`(工具调用合并结果)。

Advisor 自身消息使用 Pi 官方 theme token:用户提问(`userMessageBg` + `userMessageText`)、streaming thinking(`thinkingText` + italic,无背景)、assistant text(默认 `text` 色,无背景)、工具调用成功(call + result 合并一行,`toolSuccessBg` + `toolTitle`/`toolOutput`)、工具调用等待(`toolPendingBg`)、工具调用错误(`toolErrorBg`)、Hint/Concern(背景色为视觉别名,Pi 无专用 hintBg token,实现时选用现有 token)。

**折叠展开**:使用 Pi 的 `app.tools.expand` keybinding(默认 Ctrl+O)。Overlay 监听同一 action,不依赖 Primary 状态,并统一切换所有 Context 与 Pull Block。折叠时最多显示前 5 个视觉行,单个长条目换行后仍受该上限约束,末尾显示 `... (N more lines, Ctrl+O to expand)`。展开时使用等宽 Text 逐字显示 Advisor 实际收到的 hidden custom message 或 tool-result text,保留 XML 根元素及状态属性、role marker、tool intent、Primary Context 和 edit diff 等边界字符。
**折叠展开**:使用 Pi 的 `app.tools.expand` keybinding(默认 Ctrl+O)。Overlay 监听同一 action,不依赖 Primary 状态,并统一切换所有 Context 与 Pull Block。折叠时最多显示前 5 个视觉行,单个长条目换行后仍受该上限约束,末尾显示 `... (N more lines, Ctrl+O to expand)`。展开时使用等宽 Text 逐字显示 Advisor 实际收到的 hidden custom message 或 tool-result text,保留 pseudo-XML header 及其状态属性、role marker、tool intent、Primary Context 和 edit diff 等边界字符。

不进入 Overlay 的内容:未序列化给 Advisor 的 Raw Primary Session、被 Pull serializer 省略的原始 tool result、重复 footer hints、仅供实现调试的状态噪音。

Expand Down
10 changes: 4 additions & 6 deletions docs/ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@
</div>
</div>

<!-- Context block — customMessageBg. The header counts injected Primary messages. Expanded payload metadata lives on one XML root element. -->
<!-- Context block — customMessageBg. The header counts injected Primary messages. Expanded payload begins with an opening-only pseudo-XML header. -->
<div class="blk context">
<div class="blk-line"><span class="hd">Context</span> · 3 msgs</div>
<!-- prettier-ignore -->
Expand All @@ -200,16 +200,15 @@
**primary**:
Let me first read the current auth module structure to understand the existing token refresh flow.

The key issue is the retry interceptor — it fires on every 401 without deduplication.
&lt;/primary-context&gt;</pre>
The key issue is the retry interceptor — it fires on every 401 without deduplication.</pre>
</div>

<!-- streaming thinking — thinkingText + italic, no background -->
<div class="ov-line think">
Let me check what the primary agent has done so far, then pull the recent transcript...
</div>

<!-- Pull block — toolSuccessBg. Expanded payload moves status metadata into one XML root element. -->
<!-- Pull block — toolSuccessBg. Expanded payload begins with an opening-only pseudo-XML header. -->
<div class="blk pull">
<div class="blk-line"><span class="hd">Pull</span> [0, 12) → 8 msgs · 1.2s</div>
<!-- prettier-ignore -->
Expand All @@ -221,8 +220,7 @@
Let me first read the current auth module structure to understand the existing token refresh flow.
→ read(/src/auth.ts:1-200) ⇒ ok · 200 lines
→ grep(refreshToken @ /src/**/*.ts) ⇒ ok · 12 matches
→ write(/src/auth-refresh.ts) ⇒ ok · 85 lines
&lt;/primary-transcript&gt;</pre>
→ write(/src/auth-refresh.ts) ⇒ ok · 85 lines</pre>
</div>

<!-- thinking -->
Expand Down
Loading
Loading