Repository navigation
Description
Summary
Could the Pi integration expose acp_delegate notifications as persisted, typed custom messages with structured run identity and launch provenance, rather than only ordinary user messages containing notification text?
This would let extensions implement turn folding, task summaries, failure indicators, and other UI integrations without matching billion-context's human-readable wording with regular expressions. This is a request for a small producer-side interoperability contract, not for billion-context to implement a particular extension's UI.
Concrete consumer: pi-tidy
- Repository: https://github.com/Andy8647/pi-tidy
- Related request: [Feature] Configurable assistant frame colors/date-time headers and acp_delegate notifications in owning turns Andy8647/pi-tidy#59
- Maintainer's response: [Feature] Configurable assistant frame colors/date-time headers and acp_delegate notifications in owning turns Andy8647/pi-tidy#59 (comment)
The pi-tidy maintainer explicitly deferred delegate-notification folding because identifying another extension's messages by their text is brittle:
If billion-context sent them as a dedicated custom message type carrying the run id, tidy could tie them to the turn that started them reliably.
The desired UI is to associate a completion/failure notice with the turn that launched the delegate, show counts such as 2 completed · ⚠ 1 failed, and expose the full notice when expanded. A late result must not become a new human turn or be attributed to whichever turn happens to be active when it arrives.
Current limitation
In the Pi delegate implementation inspected at daa082ea, notification delivery uses pi.sendUserMessage(text, { deliverAs: "steer" }). The producer already knows the run ID and terminal status, and launch tool-result details already include structured delegate information, but the notification transport does not expose an equivalent typed envelope to consumers.
Recognizing headers such as [acp_delegate completed] or [acp_delegate FAILED ⚠️] is fragile: wording changes, localized text, recovery/batched notices, and users quoting an actual notice all complicate identification. A metadata contract would avoid those ambiguities.
Suggested contract (field names are illustrative)
Pi already supports pi.sendMessage() with a dedicated customType, readable content, and persisted details. For example:
{
customType: "billion-context:delegate-notification",
content: originalNotificationText,
display: true,
details: {
version: 1,
runs: [
{
runId: "del_example",
status: "completed",
sourceSessionId: "...",
originToolCallId: "...",
// Optional alternative/additional persisted launch anchor:
originEntryId: "..."
}
]
}
}The important properties are:
- Dedicated type and versioned metadata. Consumers should not need to inspect
contentto decide whether a message is a delegate notification. - Stable run identity and explicit status. Enough to deduplicate notices and distinguish completed/failed/cancelled outcomes. Auxiliary usage summaries should be distinguishable from terminal notifications so the same run is not counted twice.
- Resolvable launch ownership. Preserve a tool-call or persisted parent-message anchor captured at launch, together with session provenance.
runIdalone works only if the launch-to-turn mapping is also structurally available. Nested/codemode launches should have a root-call or equivalent persisted anchor, not require parsing a script or formatted acknowledgement. - Persistence. Metadata should survive reload/resume and remain available alongside the original notification, rather than existing only on a transient event bus.
- Batches/recovery. If one notification covers several runs, expose every run explicitly. For batches spanning different owning turns, per-run display content or stable structured segments would let a consumer project each notice without parsing prose. Exact batching/schema design is up to the maintainer.
A simple initial contract would already be useful; consumers can leave unsupported or unresolvable cases visible rather than guessing ownership or silently hiding them.
Preserve existing agent behavior
Please keep the original model-visible notification text, persistence, steering/idle wake-up semantics, read/wait suppression, and failure visibility. This should not become a UI-only notification that the main agent no longer receives, nor request-local context injection.
Pi's custom-message conversion can pass content to the model while leaving details for extensions, so structured UI integration need not remove the existing notification from the model context. The sendMessage delivery options would still need to be chosen/tested to preserve current behavior; this is not a suggestion to mechanically swap the send call.
Useful acceptance checks
- Run launched in turn A completes during turn B: provenance still identifies A.
- Live delivery and reload/resume expose the same metadata, including batched/recovery notifications.
- Completion/failure and usage summaries for one run can be distinguished and deduplicated.
- Nested launches remain traceable to a persisted main-session origin.
- Missing/compacted origins and unknown schema versions can safely remain visible.
- A human pasting the same notification text is not classified as automation.
- Main-agent notification content and wake-up behavior remain intact.
pi-tidy would own the consumer-side folding/rendering and conservative fallback behavior. A documented contract here would also benefit other transcript, task-panel, and UI extensions without each maintaining its own text parser.
Activity
ranxianglei commentedon Nov 5, 2026
🔶 需要决策
(ai: vllm-qwen/qwen3.8-27b)
查重:不重复。GitHub 全库搜「acp_delegate notification structured customType runId provenance」无命中;镜像在途 40+ 个 [PR] 无一针对本主题(最接近的 delegate 相关 PR 各管别的:占位符来源归属 / Pi 扩展列表识别 / user-scope 降级)。标签:
pi+scope/wiring+p3(互操作缺口、非缺陷)+needs-decision——因为这是功能请求且在定一个对外契约,按「功能类看到就实现禁止」先请你拍板方向再动手。八问
是什么问题:Pi 的
acp_delegate子任务完成/失败通知,目前只能发成一条纯文字的 user 消息(措辞如[acp_delegate completed] **reviewer** (runId \del_..`, exit 0)`)。第三方扩展(具体是 pi-tidy release v0.1.24 #59)想可靠地折叠/归属这些通知,只能正则去匹配措辞——措辞一改就失效。缺的是一个稳定的、结构化的「运行身份 + 启动来源锚点」信封。什么原因导致的:通知的投递通道(
sendUserMessage)本身只承载人类可读文本,没有 runId/status/来源锚点的类型化字段。根因是投递层没有结构化信封,不是措辞写得不好——所以修法在投递层加信封,不是继续调措辞。影响范围与严重程度(按选项):
怎么修复的(推荐方案思路):把 4 处
sendUserMessage(text,{deliverAs:"steer"})换成sendMessage({customType:"billion-context:delegate-notification", content:<同一份 text>, display:true, details:{version:1, runs:[{runId,status,sourceSessionId,originToolCallId}]}},{deliverAs:"steer"})。安全锚点:①
content逐字复用同一份构建好的 text +deliverAs仍为steer→ 模型看到的字节完全一致、idle 唤醒语义不变;details 只在扩展侧(pi 的convertToLlm已实测把role:"custom"转成role:"user"且只带 content,details 不进模型线);② 在execute(toolCallId,...)启动时把 toolCallId + 宿主 session id 记进DelegateRun,作为 originToolCallId/sourceSessionId(嵌套/codemode 场景锚到持久化的根调用锚点,不做脚本反解);③ host 无sendMessage能力时能力门控回退到sendUserMessage。不改压缩引擎、不改代理 wire、不加新配置面。是否有回归问题(按选项):
是否破坏缓存:不涉及。custom 消息在 pi 进程内就被转成 role:user 才出网,bili 代理看到的仍是 user 消息,前缀缓存字节不受影响。
是否改了设计方向:不涉及架构转向。纯增量:pi-subagents 边界内加一个对外互操作信封,压缩引擎/代理/协议都不动。
是否引入了新的配置:B 方案不引入任何新配置(零配置始终开启)。C 方案会新增
delegate.notifyStructured(布尔 opt-in)——这本身按 Config Surface Discipline 需你拍板才能加。待决问题
要不要做这个对外互操作信封,以及做成哪种形态(A/B/C)。这是公开契约:一旦发布,pi-tidy 及后续扩展都会依赖字段名与 versioning,所以先定形再实现。
选项对比表
我的推荐
B。理由:已对照 pi 0.83.0(devDep 钉死版本)的类型与
convertToLlm实现验证过——sendMessage({customType,content,display,details},{deliverAs:"steer"})能让模型看到与现在逐字相同的内容、同时把 details 留在扩展侧,是真正低风险的纯增益;且不引入配置面,对所有实例一致生效。若你想更保守,选 C(但多一个配置项要一并拍板)。不决定的默认行为
停在这里不动代码,issue 挂
needs-decision;pi-tidy 侧继续它的保守文本回退,不影响 bili 现有行为。等你选 A/B/C 后我按选定形态实现并出 PR。Pasitearko commentedon Nov 6, 2026
如果c方案可以完整达成我的需求,那就使用c方案,开关默认为off,用户有需求可以自己开启
ranxianglei commentedon Nov 6, 2026
🔶 需要决策
(ai: vllm-qwen/qwen3.8-27b)
@Pasitearko 谢谢明确方向。先回答你的条件句:是的,C 方案(开关默认 off)能完整达成你列的全部验收项——前提是「开启者」受益,默认 off 的实例行为零变化。逐条对一下:
originToolCallId+sourceSessionId,折叠归属到 A,与当前活跃 turn 无关 ✓role:"custom"+details)持久化进会话 JSONL,重载读回;批量/恢复路径走同一发送点,details.runs[] 覆盖该通知里的每个 run ✓kind(terminal/usage)+runs[].status区分 ✓version字段 + 消费端把缺 details/未知版本当合法态,正文仍可见 ✓deliverAs:"steer"不变 ✓精确规格(供 owner 一行拍板)
delegate.notifyStructured(布尔),写在~/.pi/acp.json(global+project),默认false。落在 pi-subagents 自己的delegate.*命名空间,完全套用现有delegate.notifyIfRead/forceEnable的模式;不加新的BILI_*env、不动 billion-context 主配置。sendUserMessage(text,{deliverAs:"steer"}),字节级零变化。sendMessage({customType:"billion-context:delegate-notification", content:<同一份 text>, display:true, details:{version:1, kind:"terminal"|"usage", runs:[{runId,status,sourceSessionId,originToolCallId,agent?}]}},{deliverAs:"steer"})。execute(toolCallId,...))把 toolCallId + 宿主 session id 记进DelegateRun(on/off 都捕获,成本可忽略);仅 on 时写进 details。sendMessage时即使 on 也回退 sendUserMessage。八问(C-default-off 口径)
delegate.notifyStructured(布尔,默认 off,acp.json)——这是唯一的新配置面,按规则需 owner 签核后才能实现。待决 & 路由
@Pasitearko 的 C-default-off 是明确的强推荐,我完全采纳为实施形态。但按本仓规则,最终 green-light + 这个新配置键的签核只能来自 owner @ranxianglei(功能类需 owner 拍板后才实现;新增配置面需 owner 事前批准)。@ranxianglei 若认可上面「精确规格」,回一句即可,我立即按此开分支出 PR(
Closes #5750)。不决定的默认行为:停在决策门不动代码,issue 维持
needs-decision;pi-tidy 侧继续保守文本回退,不影响任何现有用户。ranxianglei commentedon Nov 6, 2026
c
ranxianglei commentedon Nov 6, 2026
收到,按 C 方案 实施:
delegate.notifyStructured(默认false),on 时 4 处通知点发类型化信封(customType + content 逐字不变 +deliverAs:"steer"+ details{version:1, kind, runs[]}),启动期捕获 originToolCallId/sourceSessionId,host 无 sendMessage 时能力门控回退。开分支出 PR(Closes #5750),完成后回带八问结论。(ai: vllm-qwen/qwen3.8-27b)
ranxianglei commentedon Nov 6, 2026
(ai: vllm-qwen/qwen3.8-27b)
已按你拍板的 C 方案(开关默认 off,用户有需求可自行开启)实现并提交 PR:#5790 (Closes #5750)
结构化八问
acp_delegate完成/失败通知只以「纯文本 steering user message」形式到达宿主,第三方 UI(如 pi-tidy release v0.1.24 #59)只能靠正则解析措辞,才能把通知归到发起它的那一轮——脆弱,且迟到的结果会误挂到当前活跃轮次。delegate.notifyStructured开启且宿主支持ExtensionAPI.sendMessage时,4 个通知点改为投递 custom message,其版本化details携带 runId/status/agent + 启动溯源(originToolCallId在启动时捕获、sourceSessionIdbest-effort);content与旧消息逐字节一致、投递仍为 steer;宿主无sendMessage时自动回退纯文本路径(能力门)。convertToLlm丢弃 details → 线上不变)、失败即未送达的恢复语义保留、injectResult 的 usage 副作用次序保留。convertToLlm()在请求离开 pi 之前就剥掉 details,代理/上游看到的仍是 role:user 且 content 一致,前缀缓存不受影响。delegate.notifyStructured(boolean,默认false),位于~/.pi/acp.json(全局+项目)。迁移:无需迁移(默认 false = 旧行为);显式开启才启用。行为变更披露:默认 off 时与现状逐字节一致,无任何回归面;开启后投递信道由
sendUserMessage切换为sendMessage(typed custom message),但模型可见字节不变(同一 content + steer)——这是有意为之的 opt-in 变更。验证:
tsc --noEmit干净、tsup构建成功;本地定向套件 129/129 绿(新增tests/delegate-structured-notify.test.ts覆盖:默认-off 一致性、结构化分发+内容逐字节一致、能力回退、batch 列全 run、溯源在场/缺省皆安全、idle 与 injectResult 两条路径;既有 3 个通知套件不变)。全量回归以 CI 为准。判定:可合入,等 CI 绿 + 你 review。合并权在你。
fix(#5790): keep idle-host wake parity on the structured notification…
docs(#5790): document delegate.notifyStructured in pi-subagents READMEs
feat(pi): opt-in structured acp_delegate notification envelope
fix(#5790): keep idle-host wake parity on the structured notification…
docs(#5790): document delegate.notifyStructured in pi-subagents READMEs