@chanlerdev/scorel 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,65 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.7 - 2026-06-24
6
+
7
+ ### Highlights
8
+
9
+ - Introduce structured system_reminder content blocks across CLI, GUI, WebUI, and daemon, replacing ad-hoc XML strings.
10
+ - Bundle GUI runtime dependencies for self-contained execution.
11
+
12
+ ### Changes
13
+
14
+ - System reminders now use structured `system_reminder` content blocks with origin, visibility, and scope, enabling consistent handling across all interfaces.
15
+ - GUI's CLI runtime is now bundled with all dependencies, eliminating the need for node_modules.
16
+
17
+ ### Fixes
18
+
19
+ - Snip tool result no longer exposes internal span IDs or event counts to the model, keeping output concise.
20
+
21
+ ### Breaking Changes
22
+
23
+ - Protocol version incremented from 4 to 5; session headers must now carry version 5.
24
+
25
+ ### Verification
26
+
27
+ - All existing tests pass, with new tests covering system_reminder lowering, message-attached reminders, projector filtering, and bundled runtime integrity.
28
+
29
+ - Protocol version incremented to 5 with structured `system_reminder` content blocks.
30
+ - Snip tool results now return a concise model-visible confirmation while keeping internal span details out of provider context.
31
+
32
+ ## 0.0.6 - 2026-06-23
33
+
34
+ ### Highlights
35
+
36
+ - Packed GUI now bundles its own CLI runtime, enabling fully self-contained local Host startup without Node.js or global CLI.
37
+ - Agents can now use the `snip` tool to hide completed user turns from future context — reducing token waste and keeping conversations focused.
38
+
39
+ ### Changes
40
+
41
+ - GUI now bundles its own CLI runtime for fully self-contained Host startup (no Node.js or global CLI required).
42
+ - Added `snip` tool that agents can call to mark a completed user turn as hidden from future LLM context.
43
+ - Protocol version incremented to 4 with new `context_control` event and `hide_user_turn` operation.
44
+ - UI (GUI and WebUI) now hides model-only text blocks (like `snip` reminders) from the visible transcript.
45
+ - Provider adapters now pass each tool's own parameter schema instead of hardcoding tool-specific parameters.
46
+
47
+ ### Fixes
48
+
49
+ - Increased Host startup timeout from 10s to 30s to prevent timeouts in slower development environments.
50
+
51
+ ### Breaking Changes
52
+
53
+ - Protocol version incremented from 3 to 4; requires protocol-version-aware clients.
54
+ - Packaged GUI no longer accepts `SCOREL_CLI_ENTRYPOINT` or `SCOREL_NODE_PATH` environment variables.
55
+
56
+ ### Verification
57
+
58
+ - Unit and release tests verify bundled CLI usage in packaged GUI, snip tool end-to-end behavior, hidden spans in context builds, protocol version bump, and UI projector rendering.
59
+
60
+ ### Internal
61
+
62
+ - Added planned spec S0107 for system reminder unification (documentation only).
63
+
5
64
  ## 0.0.5 - 2026-06-19
6
65
 
7
66
  ### Highlights
package/docs/ROADMAP.md CHANGED
@@ -600,6 +600,9 @@ M5 WebUI 的正式产品方向记录在 [`S0030`](spec/ship/S0030-webui-product-
600
600
  | M9.F1.24 | [`S0102`](spec/ship/S0102-device-only-config.md) | Config 彻底收敛为设备级唯一配置,移除 Project config 运行时语义 | Done |
601
601
  | M9.F1.25 | [`S0103`](spec/ship/S0103-daemon-lifecycle-and-settings-resilience.md) | Daemon 生命周期按入口区分,并修复 GUI Settings remote 切换黑屏风险 | Done |
602
602
  | M9.F1.26 | [`S0105`](spec/ship/S0105-cli-update-and-gui-release.md) | CLI 命令面统一补齐、NPM 手动/自动更新、GUI release 打包和增量更新框架 | Done |
603
+ | M9.F1.27 | [`S0106`](spec/ship/S0106-snip-context-control.md) | `context_control` 持久事件和 `snip` tool,让 agent 隐藏已完成 user turn 的未来 LLM context 投影 | Done |
604
+ | M9.F1.28 | [`S0107`](spec/ship/S0107-system-reminder-unification.md) | 统一 system reminder 的持久化、构造、LLM 投影和 UI visibility 语义 | Done |
605
+ | M9.F1.29 | [`S0108`](spec/ship/S0108-gui-bundled-cli-runtime.md) | GUI release 内置同版本 CLI runtime,packaged GUI 用 bundle 内可执行文件启动本地 Host | Done |
603
606
 
604
607
  **Not in M9 Follow-up**:
605
608
 
@@ -779,6 +782,9 @@ HTTP adapter 必须映射已有 Host use cases,不复制领域逻辑。
779
782
  | [`S0103`](spec/ship/S0103-daemon-lifecycle-and-settings-resilience.md) | Daemon lifecycle and Settings resilience | Done |
780
783
  | [`S0104`](spec/ship/S0104-tool-result-artifacts.md) | Tool result artifacts for oversized Bash output | Done |
781
784
  | [`S0105`](spec/ship/S0105-cli-update-and-gui-release.md) | CLI update and GUI release | Done |
785
+ | [`S0106`](spec/ship/S0106-snip-context-control.md) | Snip context control | Done |
786
+ | [`S0107`](spec/ship/S0107-system-reminder-unification.md) | System reminder unification | Done |
787
+ | [`S0108`](spec/ship/S0108-gui-bundled-cli-runtime.md) | GUI bundled CLI runtime | Done |
782
788
 
783
789
  ---
784
790
 
package/docs/SHIP.md CHANGED
@@ -209,6 +209,7 @@ pnpm release patch --no-generate-notes
209
209
  - 执行 WebUI production build
210
210
  - 执行 GUI production build
211
211
  - 构建 public `scorel` npm package
212
+ - 将同版本 public `scorel` CLI runtime vendored 进 GUI app bundle,供 packaged GUI 用 bundle 内绝对路径启动本机 Host
212
213
  - 执行 `npm pack` 安装烟雾测试
213
214
  - bump 所有 package version
214
215
  - 构建 GUI macOS dmg / zip release assets,并生成 `latest-mac.yml` 与 blockmap 增量更新 metadata
@@ -94,7 +94,7 @@ running default -> follow_up queue
94
94
 
95
95
  ## 6. Source Reminder
96
96
 
97
- 每个 IM turn 会在用户消息前注入 hidden channel reminder:
97
+ 每个 IM turn 会在用户消息前注入 hidden `harness_item kind="channel_context"`。`buildContext()` 会把它转成 `system_reminder` block,provider adapter 最后 lower 成类似下面的模型输入文本:
98
98
 
99
99
  ```xml
100
100
  <system-reminder>
@@ -143,7 +143,22 @@ interface CompactEvent extends PersistentEventBase {
143
143
 
144
144
  构建 context 时:从 leaf 往 root 走,遇到 CompactEvent → 注入 summary,停止继续向上。旧事件仍在 JSONL 中(可查阅),但不进入 LLM context。
145
145
 
146
- ### 4.5 `channel_inject` — 外部来源元数据
146
+ ### 4.5 `context_control` — 控制上下文投影
147
+
148
+ ```typescript
149
+ interface ContextControlEvent extends PersistentEventBase {
150
+ type: "context_control";
151
+ operation: "hide_user_turn";
152
+ anchorUserEventId: EventId;
153
+ throughEventId: EventId;
154
+ actor: "agent" | "user" | "system";
155
+ reason?: string;
156
+ }
157
+ ```
158
+
159
+ `hide_user_turn` 隐藏一个已完成 user turn span:从 `anchorUserEventId` 开始,到同一路径下一条 `user_message` 前一条事件结束。它只影响未来 `buildContext()` 的 LLM 输入投影;原始 JSONL 事件仍保留并可用于 UI、审计和 resync。
160
+
161
+ ### 4.6 `channel_inject` — 外部来源元数据
147
162
 
148
163
  ```typescript
149
164
  interface ChannelInjectEvent extends PersistentEventBase {
@@ -325,7 +340,7 @@ interface EventTypeHandler<T extends PersistentEvent> {
325
340
  ```typescript
326
341
  type LlmAction =
327
342
  | { action: "include"; message: ScorelMessage } // 正常包含为一条消息
328
- | { action: "merge_prev"; content: string } // 合入前一条消息(<system-reminder> 包裹)
343
+ | { action: "merge_prev"; reminder: SystemReminderContentBlock } // 合入前一条 tool_result
329
344
  | { action: "skip" } // 不包含在 LLM context 中
330
345
  | { action: "barrier"; summary: ScorelMessage } // 替换上方所有消息,注入 summary,停止遍历
331
346
  ```
@@ -335,11 +350,12 @@ type LlmAction =
335
350
  | Event 类型 | convertToLlm | convertToDisplay |
336
351
  |---|---|---|
337
352
  | `message`(user/assistant/tool_result) | `include` — 原样包含 | 正常气泡 |
338
- | `message`(meta.source = "steer") | `merge_prev` — 合入前一条 tool_result 的 `<system-reminder>`;无 tool_result `include` 作为独立 user msg | 内联小字提示 |
353
+ | `harness_item`(steer / runtime guidance) | `merge_prev` — 合入前一条 tool_result 的 `system_reminder` block;无 tool_result 则作为独立 user msg | 内联小字提示 |
339
354
  | `message`(meta.source = "followUp") | 同 steer | 内联 "追加任务" 标记 |
340
355
  | `rewind` | `skip` | "回退到此处" 标记 |
341
356
  | `branch` | `skip` | "切换分支" 标记 |
342
357
  | `compact` | `barrier` — 注入 summary,停止向上遍历 | "已压缩" 折叠块 |
358
+ | `context_control` | `filter` — 从未来 LLM context 中排除指定 user turn span | "已 snip" 标记 |
343
359
  | `channel_inject` | `skip` | 来源 badge "from Telegram" |
344
360
  | `session_info` | `skip` | "模型切换为 X" 通知 |
345
361
  | `custom` | `skip` | Extension 自定义 |
@@ -381,23 +397,40 @@ function buildContext(tree: SessionTree, leafId: EventId): ScorelMessage[] {
381
397
 
382
398
  ---
383
399
 
384
- ## 7. `<system-reminder>` 通用 Harness 注入机制
400
+ ## 7. `system_reminder` 通用 Harness 注入机制
385
401
 
386
402
  ### 7.1 用途
387
403
 
388
- `<system-reminder>` 是 Scorel harness 向 LLM 传递旁路信息的统一格式。所有非用户直接输入、但需要 LLM 看到的系统级内容都用此标签包裹。
404
+ `system_reminder` 是 Scorel harness 向 LLM 传递旁路信息的结构化 content block。所有非用户直接输入、但需要 LLM 看到的系统级内容都先表达成结构化 block;`<system-reminder>` 只是 provider adapter 最后生成的传输 envelope。
389
405
 
390
406
  ### 7.2 使用场景
391
407
 
392
408
  | 场景 | 注入内容 | 注入位置 |
393
409
  |------|---------|---------|
394
410
  | Steer(用户中途插话) | 用户的引导文字 | merge 进前一条 tool_result |
395
- | Hook 上下文(UserPromptSubmit 等) | hook 产出 | user message / tool_result 末尾 |
411
+ | User message sidecar | 当前 turn 时间、用户输入引用、`snip.userMessageId` | 同一条 user message `content` |
412
+ | Hook 上下文(UserPromptSubmit 等) | hook 产出 | user message / tool_result |
396
413
  | Memory 召回 | 记忆内容 | tool_result 末尾 |
397
414
  | 系统提醒(超时、配额等) | 通知文本 | tool_result 末尾 |
398
415
  | Channel 来源标注 | 来自哪个群/频道 | user message 内 |
399
416
 
400
- ### 7.3 格式
417
+ ### 7.3 Canonical 格式
418
+
419
+ ```typescript
420
+ interface SystemReminderContentBlock {
421
+ type: "system_reminder";
422
+ kind: SystemReminderKind;
423
+ origin: "system" | "user" | "tool" | "skill";
424
+ text: string;
425
+ visibility: "model" | "display" | "compact";
426
+ scope: "message" | "turn" | "next_model_call" | "session";
427
+ data?: Record<string, unknown>;
428
+ }
429
+ ```
430
+
431
+ ### 7.4 Provider lowering
432
+
433
+ Provider adapter 把 canonical block lower 成当前模型输入格式。默认 text envelope 是:
401
434
 
402
435
  ```xml
403
436
  <system-reminder>
@@ -405,12 +438,13 @@ function buildContext(tree: SessionTree, leafId: EventId): ScorelMessage[] {
405
438
  </system-reminder>
406
439
  ```
407
440
 
408
- ### 7.4 注入规则
441
+ ### 7.5 注入规则
409
442
 
410
- - **工具循环中**:merge 进最近一条 tool_resultcontent 末尾
411
- - **无 tool_result 时(idle / turn 结束后)**:作为独立 user message(或附加到 user message 内)
443
+ - **跟随 user message**:作为同一条 `user_message.content` sidecar block,创建时固定,保持 prompt-cache 稳定。
444
+ - **工具循环中**:merge 进最近一条 tool_result content 末尾;provider 不支持时 fallback 为紧邻 tool result batch 的 user message
445
+ - **无 tool_result 时(idle / turn 结束后)**:作为独立 user message。
412
446
 
413
- ### 7.5 LLM System Prompt 声明
447
+ ### 7.6 LLM System Prompt 声明
414
448
 
415
449
  LLM 在 system prompt 中被告知:
416
450
 
@@ -174,15 +174,15 @@ Steer message persist 为**独立 PersistentEvent**(role = "user",`meta.sour
174
174
 
175
175
  | 前面有 tool_result | 行为 |
176
176
  |---|---|
177
- | ✅ 有 | `merge_prev` — 合入前一条 tool_result content 末尾,用 `<system-reminder>` 包裹 |
177
+ | ✅ 有 | `merge_prev` — 合入前一条 tool_result content 末尾,内容为结构化 `system_reminder` block |
178
178
  | ❌ 没有(idle 状态) | `include` — 作为独立 user message |
179
179
 
180
- LLM 最终看到的(工具循环中):
180
+ Provider lowering 后 LLM 最终看到的(工具循环中):
181
181
  ```
182
182
  tool_result: "文件内容...\n\n<system-reminder>\n别改了,直接跑测试\n</system-reminder>"
183
183
  ```
184
184
 
185
- LLM 最终看到的(idle 时):
185
+ Provider lowering 后 LLM 最终看到的(idle 时):
186
186
  ```
187
187
  user: "别改了,直接跑测试"
188
188
  ```
@@ -198,8 +198,8 @@ function buildContext(tree: SessionTree, leafId: EventId): ScorelMessage[] {
198
198
  messages.unshift(result.message);
199
199
  break;
200
200
  case "merge_prev":
201
- // 合入 messages 中最后一条 tool_result 的 content 末尾(<system-reminder> 包裹)
202
- mergeIntoPrevToolResult(messages, result.content);
201
+ // 合入 messages 中最后一条 tool_result 的 content 末尾(system_reminder block)
202
+ mergeIntoPrevToolResult(messages, result.reminder);
203
203
  break;
204
204
  case "skip":
205
205
  break;
@@ -216,7 +216,7 @@ function buildContext(tree: SessionTree, leafId: EventId): ScorelMessage[] {
216
216
 
217
217
  各事件类型的 LlmAction:
218
218
  - `message`(普通)→ `include`
219
- - `message`(meta.source = "steer"/"followUp")→ `merge_prev`(前面有 tool_result 时)或 `include`(没有时)
219
+ - `harness_item` / runtime guidance → `merge_prev`(前面有 tool_result 时)或独立 user message,内容为结构化 `system_reminder` block
220
220
  - `compact` → `barrier`(注入 summary,停止遍历)
221
221
  - `rewind` / `branch` / `channel_inject` / `session_info` / `custom` → `skip`
222
222
  - `custom_message` → `include`
@@ -345,6 +345,17 @@ if (estimateTokens(compactCandidates) > threshold) {
345
345
  - 在 compact 点之前分叉的其他分支不受影响
346
346
  - 旧事件仍在 JSONL 中,可供历史浏览
347
347
 
348
+ ### 6.3 Snip Context Control
349
+
350
+ `snip` 不删除 JSONL。它 append `context_control operation="hide_user_turn"`,让后续 `buildContext()` 在 active path 上过滤一个已完成 user turn span。
351
+
352
+ 安全边界:
353
+
354
+ - 目标必须是当前 active path 上的 `user_message`。
355
+ - 目标之后必须存在下一条 `user_message`,因此不能 snip 当前正在执行的 turn。
356
+ - 隐藏范围是目标 `user_message` 到下一条 `user_message` 前一条事件,保证 tool_call / tool_result 成对隐藏,不留下孤儿工具结果。
357
+ - 原始事件仍保留在 JSONL 中,UI 和审计可以继续查看。
358
+
348
359
  ---
349
360
 
350
361
  ## 7. 两层消息在本模块的落点
@@ -355,7 +366,8 @@ if (estimateTokens(compactCandidates) > threshold) {
355
366
  - buildContext 通用遍历时调用每个 event 的 handler,不 hardcode 任何类型
356
367
  - `rewind` / `branch` / `channel_inject` / `session_info` / `custom` → `skip`(不进入 LLM)
357
368
  - `compact` → `barrier`(注入 summary,停止向上)
358
- - `message`(meta.source = "steer")→ `merge_prev`(合入前一条 tool_result `<system-reminder>`)
369
+ - `context_control` `filter`(从未来 LLM context 排除指定 user turn span)
370
+ - `harness_item` / runtime guidance → `merge_prev` 或独立 user message,内容为结构化 `system_reminder` block
359
371
 
360
372
  换言之,应用层能玩的花样很多,LLM 始终只看到 handler 声明要暴露的内容。
361
373
 
@@ -0,0 +1,113 @@
1
+ # S0106: Snip Context Control
2
+
3
+ ## Goal
4
+
5
+ Let long-running sessions remove a completed user turn from future model context without deleting the underlying session record.
6
+
7
+ The business value is context hygiene: when an earlier user turn led the agent down a noisy or obsolete path, the user or agent can mark that whole turn as no longer relevant so future model calls stop paying attention to it. The original JSONL remains the evidence chain for UI, audit, resync, and debugging.
8
+
9
+ ## Scope
10
+
11
+ ### Stable User Turn References
12
+
13
+ Every persisted `user_message` already has a durable `EventId`. S0106 treats that event id as the source of truth for snip targeting. The model-facing id is a stable short alias derived from the real `EventId`; the alias resolves back to the real event id but is not a storage key.
14
+
15
+ When the Host creates a `user_message`, it appends a model-only text block to that same persisted message:
16
+
17
+ ```text
18
+ <system-reminder>
19
+ snip.userMessageId: u_...
20
+ </system-reminder>
21
+ ```
22
+
23
+ The block is persisted with the message so future `buildContext()` replays do not rewrite earlier user messages and do not invalidate prompt-cache prefixes. UI projectors hide model-only text blocks from the visible transcript.
24
+
25
+ ### Context Control Event
26
+
27
+ Add a persistent control event:
28
+
29
+ ```ts
30
+ type ContextControlEvent = {
31
+ type: "context_control";
32
+ operation: "hide_user_turn";
33
+ anchorUserEventId: EventId;
34
+ throughEventId: EventId;
35
+ actor: "agent" | "user" | "system";
36
+ reason?: string;
37
+ };
38
+ ```
39
+
40
+ The event is append-only and does not participate in the conversation tree. `buildContext()` folds these events as control state and filters the active path before producing LLM messages.
41
+
42
+ `hide_user_turn` semantics:
43
+
44
+ - `anchorUserEventId` must identify a `user_message` on the active session path.
45
+ - The hidden span starts at that user event.
46
+ - The hidden span ends at the event before the next `user_message` on the same path, or the active leaf when there is no later user message.
47
+ - The resolved end is stored as `throughEventId`.
48
+ - The span is hidden from future `buildContext()` calls after the control event is appended.
49
+ - Original events stay in JSONL and continue to be visible through session replay and UI projection.
50
+
51
+ ### `snip` Tool
52
+
53
+ Expose a lazily available `Snip` runtime tool that lets the agent request hiding a completed user turn from future context. The tool accepts:
54
+
55
+ ```json
56
+ { "userMessageId": "u_...", "reason": "optional short reason" }
57
+ ```
58
+
59
+ The Host validates the request, resolves the model-visible short alias to a target span, appends a `context_control` event, and returns a brief model-visible confirmation. Internal span details such as `anchorUserEventId`, `throughEventId`, and hidden event count may remain in structured tool result details for diagnostics, but provider context must only receive the concise confirmation. The tool result is still part of the current turn; the hidden span disappears on the next context build.
60
+
61
+ The tool is session-context control, not a generic coding tool. It must be registered by the Host with access to the current lane, not by `createCodingTools()`.
62
+
63
+ Tool parameter schemas are owned by `AgentTool` definitions. Provider adapters must pass through the tool's declared schema instead of hard-coding behavior for specific tool names such as `snip`.
64
+
65
+ ## Not In Scope
66
+
67
+ - Physically deleting, truncating, or rewriting historical JSONL.
68
+ - Arbitrary single-message deletion.
69
+ - Snipping across branches.
70
+ - User-facing GUI controls for browsing snipped spans.
71
+ - A CLI slash command.
72
+ - Automatic snip heuristics.
73
+ - Reusing `compact` for snip. Compact summarizes old context; snip excludes a specific user turn.
74
+
75
+ ## Acceptance Criteria
76
+
77
+ - `PersistentEvent` includes `context_control`.
78
+ - Session replay folds `hide_user_turn` events into control state.
79
+ - `buildContext()` excludes the hidden user-turn span from future LLM context.
80
+ - The excluded span can include assistant tool calls and tool results without leaving orphan tool results in context.
81
+ - JSONL remains append-only; original user/assistant/tool events remain loadable after snip.
82
+ - Repeated snip of the same target is idempotent enough to keep context stable.
83
+ - Invalid targets fail as tool errors and do not append control events.
84
+ - `snip` is registered by the Host and can append `context_control` from a real runtime tool call path.
85
+ - The model can discover snippable user turn ids from its normal context projection; tests must not rely on out-of-band `send_message` response data.
86
+ - User turn ids are persisted at user-message creation time, so replaying the same turn in later provider calls does not change that message and preserves prompt-cache stability.
87
+ - Model-only short id blocks are hidden from WebUI and GUI transcript projection.
88
+ - The `snip` `AgentTool` schema exposes `userMessageId` and optional `reason`, and provider adapters preserve tool-owned schemas without name-specific branches.
89
+ - Full validation passes with `pnpm typecheck && pnpm test`.
90
+
91
+ ## Testing Requirements
92
+
93
+ - Core session tests for context-control parsing, replay, and context filtering.
94
+ - Protocol tests for exhaustive event handling.
95
+ - Daemon embedded test proving `snip` appends `context_control` and the next provider call no longer receives the hidden turn.
96
+
97
+ ## Impacted Files
98
+
99
+ - `packages/protocol/src/events.ts`
100
+ - `packages/protocol/src/index.test.ts`
101
+ - `packages/core/src/session/index.ts`
102
+ - `packages/core/src/session/session.test.ts`
103
+ - `packages/core/src/tools/index.ts`
104
+ - `packages/daemon/src/index.ts`
105
+ - `packages/daemon/src/embedded/embedded.test.ts`
106
+ - `docs/ROADMAP.md`
107
+ - `README.md`
108
+
109
+ ## Risks And Boundaries
110
+
111
+ - Hiding the wrong turn is worse than compacting too much. Target resolution must use real `EventId`s and reject non-user targets.
112
+ - Tool-call/tool-result pairing can be broken by arbitrary deletion. This spec only hides full user-turn spans.
113
+ - Snip is context projection, not evidence deletion. UI and session replay must still show the original events unless a later explicit product decision adds a separate display filter.
@@ -0,0 +1,173 @@
1
+ # S0107: System Reminder Unification
2
+
3
+ ## Goal
4
+
5
+ Unify Scorel's runtime reminders as structured, model-facing context fragments.
6
+
7
+ The business value is prompt and transcript hygiene on a high-frequency path. Scorel will routinely attach reminders to user messages, inject reminders while a turn is running, and route reminders through different provider message formats. This must be one stable product contract, not ad-hoc `<system-reminder>` strings scattered across daemon, session replay, tool-result merge paths, UI projectors, or provider adapters.
8
+
9
+ ## Scope
10
+
11
+ ### Structured Reminder Block
12
+
13
+ Add a protocol-level content block:
14
+
15
+ ```typescript
16
+ type SystemReminderKind =
17
+ | "attachment"
18
+ | "time"
19
+ | "message_ref"
20
+ | "skill_listing"
21
+ | "skill_delta"
22
+ | "memory"
23
+ | "channel_context"
24
+ | "steer"
25
+ | "todo_nudge"
26
+ | "runtime_notice"
27
+ | "compact_summary";
28
+
29
+ type SystemReminderOrigin = "system" | "user" | "tool" | "skill";
30
+
31
+ type SystemReminderScope =
32
+ | "message" // travels with one persisted message whenever that message is in context
33
+ | "turn" // relevant to the user turn that created it
34
+ | "next_model_call" // runtime nudge, consumed by the next provider call
35
+ | "session"; // durable session context such as initial memory
36
+
37
+ type SystemReminderVisibility = "model" | "display" | "compact";
38
+
39
+ interface SystemReminderContentBlock {
40
+ type: "system_reminder";
41
+ kind: SystemReminderKind;
42
+ origin: SystemReminderOrigin;
43
+ text: string;
44
+ visibility: SystemReminderVisibility;
45
+ scope: SystemReminderScope;
46
+ data?: Record<string, unknown>;
47
+ }
48
+ ```
49
+
50
+ `<system-reminder>` remains the model-facing transport envelope, but it is no longer stored or hand-written by feature code. Callers create structured reminder blocks; core/provider projection renders the envelope.
51
+
52
+ ### Two Placement Modes
53
+
54
+ System reminders can appear in two product situations:
55
+
56
+ 1. **Message-attached reminders**: created together with a `user_message` and persisted in that message's `content`.
57
+ - Examples: current time for the submitted turn, `snip.userMessageId`, references to prior user messages, channel context that explains the submitted text.
58
+ - These are stable sidecars. They are created when the message is persisted, so replaying the same message later does not mutate historical content or break prompt-cache prefix stability.
59
+
60
+ 2. **Runtime injected reminders**: appended while a turn is running or between provider calls.
61
+ - Examples: steer, skill delta, a nudge that the model has not used `TodoWrite`, runtime notices.
62
+ - These remain standalone `harness_item` events because they are independent session facts. `buildContext()` lowers them into structured reminder blocks and then places them according to provider-safe rules.
63
+
64
+ ### Canonical Context And Provider Lowering
65
+
66
+ Scorel keeps a provider-neutral context:
67
+
68
+ - `ScorelMessage.content` may contain `system_reminder` blocks.
69
+ - UI/display projectors use block type and `visibility`; they must not parse `<system-reminder>` text.
70
+ - Provider adapters receive canonical `ScorelMessage[]` and lower `system_reminder` blocks to the provider's legal representation.
71
+
72
+ Default lowering keeps the current prompt contract:
73
+
74
+ ```xml
75
+ <system-reminder>
76
+ content
77
+ </system-reminder>
78
+ ```
79
+
80
+ Provider placement rules:
81
+
82
+ - User-message sidecars are rendered inside the same user message after visible user text.
83
+ - Runtime reminders prefer merge-after-tool-result when a valid previous tool result exists.
84
+ - If a provider cannot legally merge after a tool result, fallback to a standalone user message immediately after the tool result batch.
85
+ - Provider-level system/developer prompt is not used for runtime reminders.
86
+
87
+ ### Core Helper Surface
88
+
89
+ Core owns reminder construction and rendering:
90
+
91
+ - `createSystemReminderBlock(input)`
92
+ - `renderSystemReminder(block | text)`
93
+ - `renderSystemReminderText(text)`
94
+ - `appendSystemReminderToToolResult(message, block)`
95
+ - `systemReminderMessage(block, meta?)`
96
+
97
+ Feature code must pass semantic fields: `kind`, `origin`, `scope`, `visibility`, `text`, optional `data`. Feature code must not write `<system-reminder>` tags.
98
+
99
+ ### Existing Source Migration
100
+
101
+ Migrate these sources to structured reminder blocks:
102
+
103
+ - `snip.userMessageId` model-only block attached to every persisted user message.
104
+ - `harness_item` conversion for memory, channel context, skill listing, skill delta, steer, runtime notice, and future todo nudges.
105
+ - compact summary injection.
106
+ - GUI and WebUI transcript projection for model-only blocks.
107
+
108
+ `harness_item` remains the persistent event for standalone runtime/session injections. S0107 does not need a new event type unless the implementation proves `harness_item` cannot carry the contract.
109
+
110
+ ## Not In Scope
111
+
112
+ - Changing `snip` behavior from S0106.
113
+ - Adding UI controls for browsing hidden reminders.
114
+ - Backfilling or migrating old session JSONL files.
115
+ - Renaming `<system-reminder>` in the model-facing prompt.
116
+ - Moving runtime reminders into provider-level system/developer prompts.
117
+ - Replacing all event-type conversion with a full handler registry.
118
+
119
+ ## Acceptance Criteria
120
+
121
+ - Protocol supports `system_reminder` content blocks.
122
+ - No daemon or feature code hand-writes `<system-reminder>` strings.
123
+ - `buildContext()` uses shared core helpers for `harness_item` and compact summary conversion.
124
+ - `snip.userMessageId` is a message-attached `system_reminder` block with stable prompt-cache behavior across later turns.
125
+ - Provider adapters lower `system_reminder` blocks through the shared renderer, including reminders inside user messages and merged tool results.
126
+ - WebUI and GUI hide model-only reminder blocks without parsing reminder text.
127
+ - Existing harness visibility behavior stays intact:
128
+ - hidden harness items do not render as visible transcript turns;
129
+ - display/compact harness items can still render as lightweight transcript evidence.
130
+ - `pnpm typecheck && pnpm test` passes.
131
+
132
+ ## Testing Requirements
133
+
134
+ - Protocol tests for `system_reminder` content block round trip / exhaustiveness.
135
+ - Core session tests for:
136
+ - message-attached reminder blocks rendering to `<system-reminder>` in provider context;
137
+ - `harness_item` conversion producing structured reminder blocks;
138
+ - merge-after-tool-result behavior preserving tool result content;
139
+ - compact summary using the shared reminder renderer.
140
+ - Daemon embedded test proving snip's message-attached reminder remains stable across later turns.
141
+ - Provider adapter test proving `system_reminder` blocks are lowered to `<system-reminder>` text.
142
+ - WebUI and GUI projector tests proving model-only reminder blocks are hidden while display harness items remain visible.
143
+ - Static regression check that common runtime paths no longer hand-write `<system-reminder>` literals outside tests/docs and the shared renderer.
144
+
145
+ ## Impacted Files
146
+
147
+ - `packages/protocol/src/messages.ts`
148
+ - `packages/protocol/src/index.test.ts`
149
+ - `packages/core/src/session/index.ts`
150
+ - `packages/core/src/session/session.test.ts`
151
+ - `packages/core/src/provider/pi-ai.ts`
152
+ - `packages/core/src/provider/pi-ai.test.ts`
153
+ - `packages/daemon/src/index.ts`
154
+ - `packages/daemon/src/embedded/embedded.test.ts`
155
+ - `apps/webui/lib/events/projector.ts`
156
+ - `apps/webui/lib/events/projector.test.ts`
157
+ - `apps/gui/src/renderer/chatbox/projector.ts`
158
+ - `apps/gui/src/renderer/chatbox/projector.test.ts`
159
+ - `docs/spec/events.md`
160
+ - `docs/spec/session.md`
161
+ - `README.md`
162
+
163
+ ## Risks And Boundaries
164
+
165
+ - Reminder placement affects prompt-cache behavior. Do not move message-attached reminders into dynamic `buildContext()` injection.
166
+ - Tool-result merge behavior must preserve valid assistant tool-call / tool-result replay.
167
+ - Runtime reminders can be frequent. The data model must keep origin, kind, scope, and visibility explicit so future skill, time, todo, IM, and provider-specific rules do not become string parsing.
168
+ - UI must use explicit metadata, not text parsing.
169
+ - A full event handler registry remains a later refactor; S0107 should ship the stable reminder contract first.
170
+
171
+ ## Status
172
+
173
+ Done.