You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by "chinawch007 (via GitHub)" <gi...@apache.org> on 2026/10/02 08:28:12 UTC
[I] tracking(runtime-host): condition-driven Session waits and wake-ups [maka]
chinawch007 opened a new issue, #5926:
URL: https://github.com/apache/maka/issues/5926
This issue follows [Discussion #5920: condition-driven waiting and wake-up](https://github.com/apache/maka/discussions/5920) and tracks its seven implementation slices. The discussion explains the motivation, architecture, and boundaries; this issue turns them into separately reviewable, testable deliveries. It does not imply approval of every design detail.
## Motivation
Waiting for CI, background tests, PR collaboration, or external data can currently involve repeated model-driven status checks or an execution held open by a long-running tool call. Goal's timed waiting path can also start new check Turns while nothing changes, spending iterations intended for actual work.
The objective is to let the agent register an explicit condition, let Host observe it independently, and continue through existing admission only when it resolves, becomes invalid, expires, or produces an actionable exception. This reduces unnecessary model participation, releases execution occupied solely by waiting, and unifies cancellation, deadlines, verification, and result delivery.
## Target and boundaries
- Initially support **ShellRun, GitHub CI, PR/review events, and external-file readiness**.
- Support standard events and deterministic data predicates expressed through bounded templates—not arbitrary natural-language predicates, semantic watchers, or generated observer scripts.
- Reuse Goal continuation, root-Turn admission, and execution persistence authority. Do not add a second execution lifecycle or independent wake database.
- Preserve existing product rules for continuation, confirmation, and replay after interruption. Restoring a wait does not restart an old process or grant new permissions.
- Registration, observation, waking, and concrete actions remain separately authorized. Model arguments cannot attest that the user granted permission.
- Use the Goal path as the first registration/continuation consumer. Ordinary Session follow-ups require explicit ownership and admission contracts rather than assumed coverage.
## PR 1: wait contracts and persistence foundations
**Objective:** turn “which task is waiting for which condition” into identified, versioned, recoverable state rather than prompt text and in-memory intent.
Changes:
- Define provider-neutral records for Session/Goal control identity, resource, condition type/version, deadline, and result correlation.
- Add bounded validation, state transitions, CAS updates, and pagination rather than unrestricted JSON or unbounded scans.
- Add SQLite persistence, an independent memory reference implementation, and root-lease-scoped access inside the existing execution consistency domain.
- Initially allow one effective, undelivered wait per Session. Stable wait and delivery keys support later deduplication without creating a second continuation queue.
- Integrate archive/removal/operational-state cleanup and workflow schema compatibility tests.
Acceptance:
- Reopening retains records; duplicate creation and racing updates cannot overwrite newer state.
- Stale requests cannot reopen cancelled, expired, or resolved records.
- Providers agree on behavior; closed or invalid leases cannot continue accessing the store.
- No new watchers, tools, model calls, or Goal scheduling behavior.
A local implementation handoff is available in the [PR 1 plan](./event-wakeup-pr1-implementation-plan.zh-CN.md). Remove this relative link before posting, or replace it with the actual attached plan URL.
## PR 2: Host observation and condition management
**Objective:** observe valid waits and produce explicit results without invoking the work model.
Changes:
- Define adapter contracts for initial reads, notifications/polling, cancellation, and reconciliation.
- Implement a Host wait manager with TTL, frequency limits, backoff, and source-unavailability handling.
- Cover conditions satisfied before registration; notifications and current-state checks enter the same evaluation path.
- Persist observation progress, stable event identity, and bounded evidence, distinguishing pending, satisfied, invalidated, and unknown.
- Validate with fake providers, a controlled clock, and real persistence; do not introduce production GitHub credentials or start execution directly.
Acceptance:
- Unchanged observations do not call the work model; never-satisfied conditions expire.
- Duplicate notifications, read/subscribe races, restart, and throttling do not cause missed results or unbounded observation.
- Restart preserves absolute deadlines; query errors are not satisfaction.
## PR 3: execution bridge, tool yield, and Goal integration
**Objective:** make registration and result delivery safe boundaries for yielding and admitting follow-up work.
Changes:
- Add the high-level wait tool's runtime control path. Host binds trusted identity and permissions, then ends current execution correctly rather than merely asking the model to wait.
- Gate Goal continuation so legacy waiting timers and evaluator failures cannot bypass an explicit wait.
- Use existing durable continuation and root-Turn admission with typed provenance; handle busy admission, pause, cancellation, Goal replacement, and archive.
- Address deduplication and crash windows in one delivery/admission protocol. A recorded result must not be confused with an admitted execution.
- Integrate normal settlement and token budgets. When idle waits stop consuming work iterations, TTL, observation limits, and per-subscription wake bounds must already be effective.
- Reconcile existing execution after restart under current recovery policy; do not redispatch old operations with unknown effects.
Acceptance:
- Real admission plus a fake model proves: register → current execution ends → no model calls without change → result arrives → one effective successor.
- Pause, cancellation, busy admission, and crash injection do not cause uncontrolled or duplicate continuation.
- Keep the production tool hidden until a real source is ready; do not expand task authorization.
## PR 4: ShellRun terminal-state adapter
**Objective:** ship the first usable “start a long test and continue handling its result” flow.
Changes:
- Register waits on authorized background ShellRuns using stable task identity and durable terminal facts.
- Map completion, failure, cancellation, and `orphaned` state explicitly. A notification triggers verification rather than replacing it.
- Preserve existing cleanup callback responsibilities. Already-completed tasks can be read immediately without awaiting another notification.
- Expose the tool, minimal wait status, and cancellation; verify supported executor and Code Mode boundaries.
Acceptance:
- Results return to the correct Session; repeated completion does not duplicate execution.
- Tasks without registered waits do not automatically invoke models.
- Knowing a task ID does not expose user-owned terminals or other task scopes.
- Restart follows task facts and existing policy; commands with unknown effects are not automatically restarted.
## PR 5: GitHub CI
**Objective:** observe remote CI through the same protocol without requiring Webhook setup.
Changes:
- Add explicitly authorized GitHub connections and background API observation.
- Bind repository, head SHA, and run/attempt; define specific-run and declared-check-set conditions separately.
- Handle success, failure, cancellation, credential loss, throttling, and offline reconciliation.
- Filter stale versions and irrelevant changes while bounding observation frequency.
Acceptance:
- Deliver current-target results without waking on stale state, duplicate reads, or query errors.
- Adding GitHub as the second source does not add GitHub-specific core fields or another execution queue.
- “This run completed” cannot stand in for “all required PR checks are satisfied.”
## PR 6: PR/review collaboration events
**Objective:** reuse GitHub integration for merge, closure, and explicit review conditions.
Changes:
- Add merged, closed, review-arrival, and identity/revision-bound approval conditions.
- Handle closure without merge, dismissed approval, new commits, and late reviews.
- Deliver comments/review content as data for subsequent analysis, not watcher-level semantic judgments or new authorization.
Acceptance:
- Distinguish review arrival, receiving approval, and satisfying current merge requirements.
- Historical or dismissed reviews do not become current facts.
- Different GitHub notifications satisfying one one-shot wait still produce one effective successor.
## PR 7: external files and deterministic data readiness
**Objective:** continue after a user or another program delivers data, without guessed sleep intervals.
Changes:
- Combine filesystem notifications and scans for already-satisfied conditions and missed notifications.
- Initially support completion markers, manifest/checksum sets, and a small set of bounded assertions such as required columns in an explicit CSV set.
- Validate path scope, replacement, and resource identity; preserve pending, invalidated, and unknown.
- Let models fill supported template parameters, not submit arbitrary observer programs.
Acceptance:
- Partial writes, replacement, notification loss, and restart are reconciled against a defined readiness contract.
- File creation is not completed writing; directory stability is not complete delivery.
- Report uncertainty instead of substituting an approximate signal for the user's requirement.
## Merge order and shared acceptance
```text
PR 1 contracts/storage → PR 2 observation → PR 3 execution bridge → PR 4 ShellRun
├→ PR 5 CI → PR 6 PR/review
└→ PR 7 files
```
PRs 1–3 are reviewable separately but must not create separate execution authorities. PRs 3 and 4 may be combined based on size. Foundations can land before exposure; cancellation, deadlines, deduplication, permissions, and restart safety must accompany the first usable slice rather than become post-release patches.
Overall completion:
- [ ] Unchanged external state does not repeatedly invoke the work model.
- [ ] Waiting does not keep an execution occupied solely for querying.
- [ ] Matching, delivery, admission, and goal achievement are separately explainable.
- [ ] Duplicate, stale, cancelled, and invalid events cannot create incorrect successors.
- [ ] Interruption between result and admission follows existing recovery rules without silent loss or blind replay.
- [ ] All four sources use the common protocol with source-specific identity, permission, and fault tests.
- [ ] Work accounting and waiting bounds are separated while real model execution remains budgeted.
<details>
<summary>中文(点击展开)</summary>
本 issue 由 [Discussion #5920:条件驱动的等待与唤醒](https://github.com/apache/maka/discussions/5920) 引申而来,用于跟踪讨论中的七个实施 PR。讨论提供问题背景、架构及边界;本 issue 将其整理成可逐项评审、验证和交付的修改计划,不代表全部设计已经获得批准。
## 为什么做
等待 CI、后台测试、PR 协作状态或外部数据时,当前任务可能反复让模型查询状态,或让同一个执行长时间等待工具返回。Goal 的定时 waiting 路径也可能在外部没有变化时启动新的检查 Turn,消耗原本用于实际工作的轮次。
我们希望让 Agent 登记明确的等待条件,由 Host 独立观察,只有条件满足、失效、超时或出现需要处理的异常时,才经现有执行接纳继续原任务。这可以减少无效模型参与、释放纯等待占用的执行位置,并统一期限、取消、状态核验和结果交付。
## 目标与边界
- 短期支持 **ShellRun、GitHub CI、PR/review 事件和外部文件就绪**。
- 支持已有标准事件,以及受支持模板能够确定性验证的通用数据条件;不做任意自然语言条件、语义 watcher 或模型生成的观察脚本。
- 复用现有 Goal continuation、root-Turn admission 和执行存储权威,不新增第二套执行生命周期或独立 wake 数据库。
- 中断后是否继续、是否需要确认、是否允许重跑沿用现有业务逻辑。恢复等待不等于重启旧进程,也不增加原任务权限。
- 订阅、观察、唤醒和实际动作分别受权限约束。模型不能用参数声明“用户已经授权”。
- 首批登记和续跑消费者从 Goal 路径接入。普通 Session 的独立 follow-up 不自动视为已覆盖,需有明确所有权与接纳契约。
## PR 1:等待契约与持久化基础
**目标:**将“哪个任务正在等待哪个条件”变成有身份、有版本、可更新且可恢复的事实,而不是只留在提示词和内存中。
修改内容:
- 定义 provider-neutral 的等待记录:所属 Session/Goal 控制身份、来源和资源、条件类型与版本、截止时间及结果关联键。
- 定义有界的记录校验、等待状态转换、CAS 更新和有界分页,避免开放任意 JSON 或无上限全量扫描。
- 在现有执行存储一致性域中增加 SQLite 实现、内存参考实现和受 root lease 约束的访问 facade。
- 首版限制一个 Session 同时只有一个未交付的有效等待;稳定 wait ID 与交付关联键用于后续去重,不创建第二个 continuation 队列。
- 为归档、删除和 operational-state purge 接入清理;更新 workflow schema 和迁移兼容测试。
验收:
- 记录可重开读取,重复创建和并发更新不会覆盖较新状态。
- 取消、过期和已记录结果的状态不会被旧请求重新打开。
- 两种 provider 行为一致,关闭或失效 lease 不允许继续访问。
- 没有新增 watcher、工具、模型调用或 Goal 调度行为。
详细执行方案另附本地计划:[PR 1 实施计划](./event-wakeup-pr1-implementation-plan.zh-CN.md)。发布 issue 时可移除这一相对链接,或改为实际附上的计划地址。
## PR 2:Host 观测与条件管理
**目标:**在不调用工作模型的情况下,持续观察仍然有效的等待,并形成明确结果。
修改内容:
- 定义来源适配器的首次读取、通知/轮询、取消和重新校准契约。
- 实现 Host 等待管理器,对有效记录安排观测,处理 TTL、频率限制、退避和来源失联。
- 首次检查覆盖“登记前条件已经满足”;通知与当前事实核验进入同一个判断路径。
- 保存观测进度、稳定事件身份和有界证据,区分 pending、satisfied、invalidated 与 unknown。
- 使用 fake provider、可控时钟和真实存储验证行为,暂不接入生产 GitHub 凭据,也不直接创建执行。
验收:
- 无变化时不调用工作模型;永远不满足的条件会按期限结束。
- 重复通知、读/订阅竞态、重启和限流不会造成漏结果或无限观测。
- 重启不重置绝对截止时间,查询失败不被当作条件满足。
## PR 3:执行桥接、工具让出与 Goal 整合
**目标:**让“登记等待”和“结果到来”分别成为安全的执行让出与后续接纳边界。
修改内容:
- 提供高层等待工具的运行时控制接口。Host 绑定可信身份和权限;登记成功后正确结束当前执行,而不是只给模型一句“请等待”。
- 将有效等待与 Goal 控制状态连接,阻止旧 waiting timer 或 evaluator 失败路径绕过等待。
- 通过现有持久化 continuation 和 root-Turn admission 安排后续执行,携带事件来源身份,处理 busy、暂停、取消、Goal 替换和归档。
- 在同一份交付/接纳协议中处理去重和崩溃窗口,不将“结果已记录”误判为“执行已安排”。
- 接入正常结算与 token 预算。取消空等待对工作轮次的消耗时,同时启用 TTL、观测限制和每订阅唤醒上限。
- 重启后核验已有执行并沿用既有恢复策略,不盲目重派结果未知的旧操作。
验收:
- 通过真实接纳路径与 fake model 验证:登记 → 当前执行结束 → 无变化时无模型调用 → 结果到达 → 一次有效后继。
- 暂停、取消、繁忙和不同崩溃位置的测试均不产生失控续跑或重复执行。
- 未接入真实来源前不开放生产工具;不扩展原任务授权。
## PR 4:ShellRun 终态接入
**目标:**交付“启动长测试,完成后继续处理结果”的首个用户可用闭环。
修改内容:
- 允许等待有权访问的后台 ShellRun;使用稳定任务身份和持久化终态。
- 将完成、失败、取消和 `orphaned` 等事实转换为明确结果。完成通知只作为触发,不能代替对可靠记录的核验。
- 保留原有资源清理回调职责。已经完成的任务可立即读取,不依赖再收到一次通知。
- 开放等待工具、最小等待展示和取消入口,并验证支持的 executor / Code Mode 边界。
验收:
- 结果回到正确 Session,重复完成通知不重复执行。
- 没有登记等待的任务不会一律唤醒模型。
- 用户终端及其他任务范围不因知道 task ID 就可读取。
- 重启按任务事实与原有策略处理,不自动重启未知副作用的命令。
## PR 5:GitHub CI
**目标:**在不要求用户配置 Webhook 的情况下,用同一协议跟踪远端 CI。
修改内容:
- 增加明确授权的 GitHub 连接与后台 API 观测。
- 绑定 repository、head SHA、run/attempt;分别定义单 run 终态和明确检查集合的条件。
- 处理成功、失败、取消、凭据失效、限流和离线补查。
- 过滤旧 SHA、旧 attempt 和与订阅无关的变化,维护受控的观测频率。
验收:
- 当前目标结果可交付,旧状态、重复读取和查询失败不会误唤醒。
- GitHub 作为第二种来源,不要求向核心 schema 增加 GitHub 专属字段或新建执行队列。
- “run 完成”不能冒充“PR 所有 required checks 已满足”。
## PR 6:PR/review 协作事件
**目标:**复用 GitHub 接入,支持合并、关闭及明确的评审条件。
修改内容:
- 增加 merged、closed、review 到达及指定身份/版本下的审批条件。
- 处理关闭但未合并、审批撤销、PR 新提交和迟到 review。
- 将评论、review 正文作为后续分析的数据,不由 watcher 判断模糊语义,也不视为新授权。
验收:
- 区分 review 到达、收到 approval 和当前合并要求满足。
- 旧 review 或已撤销审批不能作为当前事实。
- 多种 GitHub 通知满足同一 one-shot 等待时只产生一个有效后继。
## PR 7:外部文件与确定性数据就绪
**目标:**支持用户或其他程序交付数据后继续处理,不依靠猜测式 sleep。
修改内容:
- 接入文件系统通知和当前状态扫描,覆盖登记前已完成与漏通知。
- 首批模板包括完成标记、manifest 文件清单/校验,以及少量有界数据断言,例如明确 CSV 集合具有指定列。
- 校验路径访问范围、文件替换和资源身份;保留 pending、invalidated 和 unknown 的区别。
- 模型只填写受支持模板参数,不提交任意观察程序。
验收:
- 部分写入、替换、通知丢失与重启后都能按明确就绪协议核验。
- 文件创建不等于写完,目录稳定不等于全部文件已交付。
- 无法确定时明确说明,不把近似信号当作用户要求已经满足。
## 合并顺序与共同验收
```text
PR 1 契约/存储 → PR 2 观测 → PR 3 执行桥接 → PR 4 ShellRun
├→ PR 5 CI → PR 6 PR/review
└→ PR 7 文件就绪
```
PR 1–3 可分别评审,但不能各自引入一套运行状态权威。PR 3 与 PR 4 可按体量合并。基础代码未开放前可以逐步建设;首个用户闭环必须同时具备取消、期限、去重、权限和恢复安全,不能将这些留作上线后补丁。
总体完成条件:
- [ ] 外部无变化时,不反复启动工作模型。
- [ ] 等待不会继续占用仅为查询而存在的执行。
- [ ] 条件匹配、等待交付、执行接纳和目标完成分别可解释。
- [ ] 重复、过期、取消或失效事件不能产生错误后继。
- [ ] 结果与接纳之间的中断按现有恢复规则处理,不静默遗忘或盲目重跑。
- [ ] 四类来源通过共同协议接入,且具备各自的身份、权限和故障测试。
- [ ] 工作轮次与等待限制合理分离,实际模型执行仍正常计量。
</details>
---
Drafted with assistance from Maka, based on the published discussion and code inspection. This is an implementation tracking proposal, not a completed feature or approval of every design choice.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [I] tracking(runtime-host): condition-driven Session waits and wake-ups [maka]
Posted by "chinawch007 (via GitHub)" <gi...@apache.org>.
chinawch007 commented on issue #5926:
URL: https://github.com/apache/maka/issues/5926#issuecomment-6229672833
# PR3 implementation plan: execution bridge, tool yield, and Goal integration
Following [Discussion #5920](https://github.com/apache/maka/discussions/5920), [tracking issue #5926](https://github.com/apache/maka/issues/5926), [PR1 #6057](https://github.com/apache/maka/pull/6057), and [PR2 #6113](https://github.com/apache/maka/pull/6113).
Inspected baseline: `db455a1661ec1bf83182c7a95110588994e3eec3`. Recheck main and the relevant contracts before implementation. This is an implementation proposal, not approval of every design detail. New field and API names below are provisional.
## 1. Deliverable and scope
Demonstrate this lifecycle with a fake adapter, a fake model, real persistence, and real Root admission:
```text
Register a wait from the current Turn
→ Persist the wait and its Goal association
→ Settle the current Turn and release execution resources
→ Observe without invoking the work model or Goal evaluator
→ Record a result or expiry
→ Persist an existing Goal continuation
→ Admit at most one effective successor through Root admission
```
This PR includes the internal high-level tool and runtime yield, explicit-wait gating of Goal scheduling, durable delivery/admission linkage, normal accounting, control operations, cleanup, and crash recovery.
Goal remains the first consumer. Each Session has at most one effective wait whose delivery remains outstanding. Generic Session follow-ups, multiple-wait groups, arbitrary condition expressions, production adapters, and product UI remain outside this PR. Keep the production tool hidden until the ShellRun slice in PR4.
## 2. Define ownership and durable contracts
Reuse the existing execution consistency domain and distinguish four facts:
| Fact | Authority and proposed extension |
|---|---|
| Wait target and observation outcome | Existing `eventWaitStore`, retaining resource, condition, deadline, and immutable results |
| Which wait currently gates a Goal | Proposed `GoalAuthorityRecord.activeEventWait` |
| A successor awaiting admission | Extended existing `GoalPendingContinuation`, without another execution queue |
| An admitted successor | Existing Root admission with durable wait-delivery linkage |
Requirements:
- Bind `activeEventWait` to the wait, source Turn, and Goal control generation. It may coexist with the source execution during registration.
- Preserve the existing exclusion between `currentExecution` and `pendingContinuation`.
- Persist the event-wait continuation’s `turnId`, `runId`, `userMessageId`, and frozen input once. Reuse them across busy admission and recovery.
- Reuse `deliveryKey = event-wait/<waitId>`. Add bounded typed provenance covering the wait, source Turn/tool call, Goal generation, outcome, and result version or digest.
- Keep observation outcome separate from delivery progress. `resolved` does not mean admitted; cancellation must not stand in for successful delivery.
- Reject reuse of a delivery key with a different Goal, result, or execution input.
- Represent `expired` explicitly. It has no ordinary `resolution`; do not fabricate a provider receipt or a satisfied outcome.
- Carry provenance through admission, Runtime input, events, projections, and recovery rather than encoding it only in prompt text.
Adding an internal origin must not expand the public `turn.start` input permissions or let clients manufacture automatic follow-up authority.
## 3. Implement trusted registration and runtime yield
Add an internal high-level tool, provisionally `WaitForEvent`.
The model supplies supported resource and condition parameters, a wait reason, and a timeout request. Host binds Session, source Turn/tool call, current Goal, control generation, and permissions from trusted context. Model arguments cannot establish authorization.
Registration should:
1. Verify that the current execution carries a valid Goal and that the backend supports this control operation.
2. Validate the adapter, condition version, resource scope, parameter bounds, and deadline limits.
3. Establish stable registration identity. Retrying the same tool call returns the same wait without extending its deadline; conflicting parameters are rejected.
4. Atomically validate Session availability, Goal revision/generation, source execution, and existing wait ownership, then create the wait and bind `activeEventWait`.
5. Return a trusted control result only after the commit is established, allowing Runtime to end the model loop.
A failed registration must neither leave an orphaned Goal gate nor falsely yield as though a wait exists. Reconcile an uncertain commit through the stable registration identity.
Use the existing tool declarations:
- `executionSemantics: 'exclusive_step'`
- `nesting: 'direct_only'`
Initially reject mixed tool steps and Code Mode nesting. Yield must be a Runtime control operation. Complete tool-result persistence, normal Turn termination, and cleanup; do not treat already-started effects as absent or replayable.
For already-satisfied conditions, use the same registration → yield → settlement → delivery path. An additional inline fast path is unnecessary for this slice.
## 4. Gate Goal scheduling and preserve settlement
Apply the explicit-wait gate at scheduling entry points, not only immediately before admission:
- Source Turn completion handling.
- Before evaluator invocation and after evaluator failure.
- Previously scheduled waiting timers.
- Pending-continuation recovery.
- Generic Goal recovery without a current execution.
- Admission retries after busy.
A valid wait or its outstanding delivery must prevent ordinary check Turns. Preserve legacy Goal waiting behavior when no explicit condition exists.
Treat wait yield as normal execution completion. Do not reuse the existing `suspended` outcome, which enters pause/failure handling.
Accounting requirements:
- Charge the actual registration Turn and result-processing Turn normally.
- Observation, backoff, and admission waiting do not increment work iterations or no-progress counters.
- Refresh usage from persisted facts even when skipping the evaluator. The current token cache is refreshed through evaluation-context reads, so its previous value is insufficient.
- Settle the source Turn exactly once. Registration alone proves neither progress nor Goal achievement.
- Enforce token and iteration limits before admitting a successor.
- Preserve token baselines, absolute deadlines, and consumed observation budgets.
Retain PR2’s TTL, attempt limits, throttling, backoff, and capacity limits. Allow at most one effective successor per subscription. The seven-day lifetime ceiling does not guarantee that the observation-attempt budget lasts seven days.
## 5. Deliver through two durable stages
In addition to registration, introduce two narrow typed operations. Names may change; their transaction boundaries should not.
| Stage | Required operation |
|---|---|
| Queue the continuation | Verify a valid durable source-Turn yield termination and completed settlement; jointly validate wait and Goal; atomically persist existing `pendingContinuation`, fixed execution identity, frozen input, and wait-delivery linkage |
| Admit the continuation | Use existing Session/Root admission; jointly validate Goal, wait, and pending state; atomically persist Root admission, transition pending to current, acknowledge delivery, and release wait ownership |
Constraints:
- A result arriving before source-Turn settlement remains stored without starting a successor.
- A resolved wait retains its active slot while queued or busy. Release it only after the admission transaction succeeds.
- Expired waits currently release the storage slot; `activeEventWait` must retain logical ownership until delivery or explicit suppression.
- Busy admission preserves the same pending entry, result, and execution IDs. Existing idle notifications and recovery provide progress.
- Select `previousRootTurnId` through the existing Root authority at admission time. User Turns may advance the chain while delivery is pending.
- Sequential commits to separate stores are not a joint transaction. Add narrow provider/facade operations without exposing SQL handles or generic transaction callbacks to Host.
- Implement matching behavior in SQLite and the independent Memory provider.
Outcome handling:
| Outcome | Behavior |
|---|---|
| `pending` or temporary `unknown` | Continue bounded observation without model calls |
| `satisfied`, `invalidated`, `source_unavailable` | Deliver once if current Goal, permissions, and budgets allow |
| `expired` | Deliver an explicit timeout fact once |
| User cancellation or invalidated control generation | Suppress unadmitted delivery without starting the model |
Condition satisfaction does not establish Goal achievement. Deliver external evidence as bounded untrusted data, without granting additional action permissions.
## 6. Specify control and recovery behavior
| Scenario | Initial behavior |
|---|---|
| Session busy | Retain delivery; revalidate identity, permissions, and budgets when admission becomes available |
| New user message | Preserve existing user-input admission; the old wait must not override new work or preempt execution |
| Goal pause | Existing pause renews the control generation; terminate the old generation’s wait and unadmitted delivery while retaining necessary facts and diagnostics |
| Goal resume | Follow existing continuation under the new generation; do not revive the old wait automatically |
| Wait cancellation | Stop observation and unadmitted delivery; do not cancel the underlying task or silently restart the legacy waiting timer |
| Goal cleared, replaced, or terminated | Invalidate old linkage; late events cannot affect the new Goal |
| Session archive, removal, or purge | Consistently clean wait, Goal linkage, and unadmitted delivery |
| Cancellation races admission | Durable commit order determines the winner; an admitted successor follows existing stop policy and retains its admission facts |
Recovery ordering:
1. Restore persistence and Goal control facts, including explicit-wait gates.
2. Reconcile the source execution and any admitted successor through existing execution authority.
3. Restore observation, outstanding results, and pending continuations with their original identities.
4. Allow ordinary Goal scheduling only after these checks.
Inject crashes at these boundaries:
| Window | Required recovery |
|---|---|
| Registration committed, before tool acknowledgement or source termination | Reconcile registration and source execution without blindly replaying old tools |
| Result committed, before continuation persistence | Materialize one continuation from authoritative facts |
| Continuation persisted, before admission | Retry the original execution identity |
| Admission committed, before acknowledgement | Find existing admission/delivery linkage instead of allocating another execution |
| Successor already started | Follow existing Runtime recovery, confirmation, and unknown-effect policies |
A commit error does not prove that nothing committed. Respect existing poisoned/fencing states and reconcile through supported authority paths. The guarantee is one effective successor admission, not exactly-once arbitrary external effects.
## 7. Complete composition, compatibility, and cleanup
- Integrate with existing Host composition and recovery phases.
- Keep PR2’s manager responsible for observation and result persistence. Notifications may prompt reconciliation; they must not directly start models.
- Use bounded delivery queries covering resolved and expired facts, without repeatedly treating acknowledged history as new work.
- Do not retain root execution or drain resources solely for waiting. Acquire them through existing admission and respect shutdown/storage lifetimes.
- Keep fake adapters out of production composition. Verify tool catalogs, backend capability checks, and Code Mode do not expose unsupported entry points.
- Cover old-record decoding, workflow migration, indexes, bounded serialization, and rejection of future versions.
- Validate the combined size of results, prompts, provenance, and cancellation metadata across every legal transition.
- Preserve consistent linkage in same-root backups. When portable Session copies omit waits, also handle active-wait and event-continuation references in Goal authority, preventing dangling gates or destination-root wakes.
- Apply existing protocol compatibility checks if public wire schemas change, even while the tool remains hidden.
Validate registration, source observation, successor admission, and concrete actions separately. The discussion’s optional `analyze-only` policy is not an assumed requirement of this PR.
## 8. Implementation sequence and code areas
| Order | Work |
|---|---|
| 1 | Fix transitions, outcome semantics, control generations, and crash boundaries; add contract tests |
| 2 | Implement registration, continuation persistence, joint admission, and suppression contracts with SQLite/Memory parity |
| 3 | Implement the internal tool and Runtime yield, including settlement and resource release |
| 4 | Integrate Goal gates, accounting without evaluator calls, and fixed-identity continuations |
| 5 | Connect Root admission, recovery, Host shutdown, and Session cleanup |
| 6 | Complete integration, fault-injection, migration/copy tests, then review the final diff |
Primary areas:
- [Core](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/core/src): `event-wait.ts`, `goal.ts`, `turn-origin.ts`, and related Runtime descriptor/event decoders.
- [Storage](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/storage/src): wait/Goal authorities, agent-run admission, execution facades, workflow schema, independent Memory implementation, and Session bundle/cleanup behavior.
- [Runtime](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/runtime/src): tool control interfaces, `ai-sdk-turn.ts`, Goal continuation/state/lifecycle, and accounting reads.
- [Runtime Host](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/runtime-host/src/server): wait bridge, Goal coordinators, Root admission, execution composition, and recovery.
Keep implementation internal where possible. Cross-package dependencies must use declared exports.
## 9. Acceptance and validation
Use controlled clocks and barriers, fake adapters/models, real Host admission, and SQLite process recovery.
| Test group | Required assertions |
|---|---|
| Complete lifecycle | Source execution ends after registration; unchanged observations add zero work-model/evaluator calls; one result admits one effective successor |
| Registration and permissions | Same-call retries are idempotent; conflicting parameters fail; concurrent registration has one winner; invalid Goal, unauthorized resources, forged origin, and unsupported backends create no wait |
| Ordering and duplication | Pre-satisfied conditions, results before settlement, duplicate notifications/scans, and lost acknowledgements do not duplicate delivery |
| Goal gating | Legacy timers, evaluator failures, generic recovery, and busy retries cannot bypass the wait |
| Progress | Busy delivery eventually proceeds; a later valid execution can register another wait after the previous delivery |
| Control races | Pause/resume, cancellation, replacement, archive, and admission ordering do not revive old generations |
| Outcomes and accounting | Cover every outcome, deadlines, observation limits, token/iteration limits, normal charging of actual Turns, and zero work-iteration cost for observation |
| Atomicity and crashes | Failures at transaction write points roll back fully; lost acknowledgements and process crashes preserve original identities |
| Lifecycle and compatibility | Cover invalid leases, shutdown, old/future records, backup, copies, deletion, and maximum combined record sizes |
| Tool boundaries | Enforce exclusive steps and direct-only use; reject forged control results; keep unsupported and production catalogs hidden |
Memory tests establish logical behavior and provider parity. Cross-process durability requires SQLite kill/reopen tests.
During development, rebuild dependencies before focused tests. Before delivery, run the repository checks:
```sh
npm test
npm run lint
npm run format:check
npm run build
npm run typecheck
npx knip --workspace apps/desktop
npx knip --workspace packages/ui
git diff --check
```
Report the actual baseline, final commit, validation commands/results, uncovered cases, and necessary deviations. Suggested PR title:
`feat(runtime-host): bridge event waits to Goal continuation`
Completion means the internal lifecycle and failure boundaries are demonstrated while the production tool remains hidden, with a defined integration seam for PR4’s ShellRun adapter.
*Drafted with assistance from OpenAI Codex based on source and proposal inspection. The validation listed here remains implementation work.*
<details>
<summary>中文(点击展开)</summary>
# PR3 开发计划:执行桥接、工具让出与 Goal 整合
承接 [Discussion #5920](https://github.com/apache/maka/discussions/5920)、[跟踪 issue #5926](https://github.com/apache/maka/issues/5926)、[PR1 #6057](https://github.com/apache/maka/pull/6057) 和 [PR2 #6113](https://github.com/apache/maka/pull/6113)。
代码核对基线:`db455a1661ec1bf83182c7a95110588994e3eec3`。实施前重新检查 main 漂移及相关契约。以下是实施提案,不表示所有设计细节已经获得批准。
## 1. 交付目标与范围
通过 fake adapter、fake model、真实持久化和真实 Root admission,完成以下闭环:
```text
当前 Turn 登记等待
→ 持久保存等待与 Goal 关联
→ 当前 Turn 正常结束并释放执行资源
→ Host 观测期间不调用工作模型或 Goal evaluator
→ 形成结果或超时事实
→ 保存既有 Goal continuation
→ 经 Root admission 接纳至多一个有效后继
```
本 PR 包含:
- 高层等待工具的内部控制路径和真正的 runtime yield。
- 显式等待对 Goal evaluator、旧 waiting timer 和恢复路径的门控。
- 等待结果与现有 `pendingContinuation`、Root admission 的可靠关联。
- 取消、暂停、繁忙、Goal 替换、预算、清理和崩溃恢复。
- 必需的契约、迁移、权限与行为测试。
保持 Goal 为首个消费者,每个 Session 同时至多一个有效、未完成交付的等待。暂不实现普通 Session 的通用 follow-up、多等待组、任意条件表达式、真实来源适配器或产品 UI。生产工具继续隐藏;ShellRun 的首次开放留给 PR4。
## 2. 明确状态所有权和持久契约
继续使用已有执行存储一致性域,区分四类事实:
| 事实 | 权威及建议扩展 |
|---|---|
| 等待目标与观测结果 | `eventWaitStore`,保留既有资源、条件、期限和不可变结果 |
| 当前 Goal 正被哪条等待阻挡 | 建议增加 `GoalAuthorityRecord.activeEventWait` |
| 后继尚待接纳 | 扩展既有 `GoalPendingContinuation`,不建立另一条执行队列 |
| 后继已经接纳 | 既有 Root admission,加上与等待交付的持久关联 |
具体要求:
- `activeEventWait` 绑定 wait、源 Turn 和 Goal 控制代际;登记期间可以与源执行共存。
- 保留 `currentExecution` 与 `pendingContinuation` 互斥的既有约束。
- event-wait continuation 保存一次分配后的 `turnId`、`runId`、`userMessageId` 和冻结后的输入,繁忙或恢复时复用。
- 复用 `deliveryKey = event-wait/<waitId>`,增加有界、类型化的来源信息:wait、源 Turn/tool call、Goal 代际、结果类别及结果版本或摘要。
- 观测终态与交付进度分别表达。`resolved` 不表示已经接纳,不能用 `cancelled` 冒充交付完成。
- 同一交付键对应不同 Goal、结果或执行输入时明确冲突,不能覆盖。
- `expired` 没有普通 `resolution`,交付内容必须明确表示期限到达,不伪造 provider receipt 或条件满足。
- 类型化来源信息必须贯穿 admission、Runtime 输入、事件、读取投影和恢复;不能只拼进 prompt。
公共客户端不能因为新增来源类型就获得伪造内部唤醒的能力。现有 `turn.start` 输入权限保持原边界。
## 3. 实现可信登记与真正让出执行
增加内部高层工具,名称可暂定为 `WaitForEvent`。
模型只提供受支持的资源、条件参数、等待原因和超时请求。Host 从可信调用上下文绑定 Session、源 Turn/tool call、当前 Goal、控制代际及权限;不接受模型提供的授权声明。
登记流程:
1. 确认当前执行正在携带有效 Goal,且 backend 支持该控制操作。
2. 校验 adapter、条件版本、资源访问范围、参数大小及期限上限。
3. 从首次登记意图生成稳定身份;同一 tool call 重试返回同一等待,不延长期限。相同身份但参数不同必须拒绝。
4. 通过窄类型存储操作,在同一事务中校验 Session、Goal revision/代际、源执行及现有等待,然后创建 wait 并绑定 `activeEventWait`。
5. 确认提交后返回可信控制结果,由 Runtime 结束当前模型循环。
登记失败不能留下孤立 Goal gate,也不能让当前执行错误地认为等待已建立。提交确认丢失时,先按稳定身份读取权威状态。
工具执行建议复用现有:
- `executionSemantics: 'exclusive_step'`
- `nesting: 'direct_only'`
首版拒绝与其他工具混合执行,也不从 Code Mode 嵌套调用。不能仅通过提示词要求模型停止。工具结果、Runtime 终止事实和清理必须正常完成;已有副作用不能因 yield 被当作未发生或重新执行。
条件登记前已经满足时,也先走统一的登记、yield、settlement、交付路径,暂不增加另一条 inline fast path。
## 4. 接入 Goal 门控与正常结算
显式等待必须在调度入口生效,不能只在最终 admission 处检查。
覆盖:
- 当前 Turn 完成后的 Goal 处理。
- evaluator 调用前及失败后的 fallback。
- 已安排的 waiting timer。
- pending continuation 恢复。
- 没有 current execution 时的 generic Goal recovery。
- busy 后重新尝试接纳。
存在有效等待或未完成的对应交付时,旧路径不得创建普通检查 Turn。没有显式条件的旧 Goal waiting 行为继续保留。
等待让出仍是一次正常完成的执行,不使用现有会进入暂停/失败处理的 `suspended` 来表达。
预算和结算要求:
- 登记等待的实际 Turn、处理结果的后继 Turn 均正常计量。
- Host 观测、退避、busy 等待不增加工作 iterations 或 no-progress 次数。
- 跳过 evaluator 时,仍须从持久化 usage 事实刷新 token 计数;当前 token cache 的刷新依赖读取评估上下文,不能直接沿用旧缓存。
- 源 Turn 只结算一次;登记等待本身不证明已经取得进展或完成 Goal。
- token budget、max iterations 等限制优先于后续接纳;达到限制不能被事件唤醒绕过。
- 不重置 token baseline、绝对 deadline 或已消耗的观测预算。
保留 PR2 的 TTL、观测次数、限流、退避及容量限制。每条订阅最多产生一个有效后继。七天是寿命上限,不意味着默认观测次数一定足以持续七天。
## 5. 使用两个持久化阶段完成交付与接纳
在登记事务之外,增加以下两个窄类型操作。名称可调整,事务边界必须保留。
| 阶段 | 必须完成的操作 |
|---|---|
| 保存 continuation | 确认源 Turn 已形成合法的 yield 终态并完成 settlement;联合校验 wait 与 Goal;原子保存既有 `pendingContinuation`、固定执行身份、冻结输入及 wait 交付关联 |
| 接纳 continuation | 通过既有 Session/Root 接纳入口,联合校验 Goal、wait 和 pending;原子写入 Root admission、执行 `pending → current`、确认交付并释放等待所有权 |
约束:
- 结果先于源 Turn 结束时只保存结果,不能抢先启动后继。
- `resolved` 在 queued/busy 阶段继续占用 active slot,到接纳事务成功后才释放。
- `expired` 当前不占存储 active slot,因此必须由 `activeEventWait` 保持逻辑门控,直到交付或显式终止。
- busy 时保留同一 pending、结果和执行 ID,使用既有 idle 通知及恢复机制继续推进。
- `previousRootTurnId` 在实际接纳时由现有 Root authority 选择;busy 期间用户可能已经推进执行链,不能提前冻结链头。
- 同一数据库中的多个独立 `await commit()` 不构成联合事务。扩展 provider/facade 的窄接口,不向 Host 暴露 SQL handle 或通用事务回调。
- SQLite 与独立 Memory provider 均实现相同契约。
结果处理规则:
| 结果 | 行为 |
|---|---|
| `pending`、暂时 `unknown` | 继续有界观测,不调用模型 |
| `satisfied`、`invalidated`、`source_unavailable` | 在当前 Goal、权限和预算允许时交付一次 |
| `expired` | 以明确超时事实交付一次 |
| 用户取消、旧控制代际失效 | 抑制未接纳交付,不因取消启动模型 |
`satisfied` 仅表示等待条件成立,不能直接将 Goal 标记为 achieved。外部证据作为有界的不可信数据进入后继上下文,不赋予新动作权限。
## 6. 固定控制操作和恢复语义
| 场景 | 首版处理 |
|---|---|
| Session 繁忙 | 保留交付,空闲后重新检查当前身份、权限和预算 |
| 用户发来新消息 | 沿既有用户输入接纳;旧等待不得覆盖新任务或抢占执行 |
| Goal 暂停 | 当前 pause 会更新控制代际;终止旧代等待及未接纳交付,保留必要事实和诊断 |
| Goal 恢复 | 沿既有新代际 continuation 推进;不自动复活旧 wait,需要时由合法执行重新登记 |
| 取消等待 | 停止观察和未接纳交付;不取消底层任务,也不悄悄重启旧 waiting timer |
| Goal 清除、替换或终止 | 旧关联失效,迟到事件不得作用于新 Goal |
| Session 归档、删除、purge | 一致清理 wait、Goal 关联及未接纳交付 |
| 接纳与取消竞争 | 以持久提交顺序确定胜者;已接纳执行沿现有停止策略处理,不抹除接纳事实 |
恢复按以下顺序处理:
1. 恢复存储和 Goal 控制事实,先识别显式等待 gate。
2. 由现有执行权威核验源执行和已经接纳的后继。
3. 恢复等待观测、未交付结果和固定身份的 pending continuation。
4. 完成上述核验后再允许普通 Goal 调度。
需要注入的崩溃窗口:
| 窗口 | 恢复要求 |
|---|---|
| 登记提交后、工具确认或源 Turn 结束前 | 核验已登记事实及源执行状态,不盲目重跑旧工具 |
| 结果提交后、continuation 保存前 | 从权威结果补建一次 continuation |
| continuation 保存后、接纳前 | 复用原执行身份重试 |
| 接纳提交后、确认返回前 | 读取已有 admission/交付关联,不能分配新执行 |
| 后继开始后 | 沿既有 Runtime 恢复、确认和副作用未知处理策略 |
提交异常不等于未提交。现有 admission 的 poisoned/fencing 状态必须被遵守,不能直接重复调用绕过。这里保证的是一次有效后继接纳,不承诺任意外部副作用 exactly once。
## 7. 完成 Host 装配、兼容与清理
- 在现有 Host composition 和恢复阶段中装配桥接,不建立第二套 Session 生命周期。
- PR2 manager 继续只负责观测和结果持久化;通知最多作为重新读取的提示,不直接调用模型。
- 交付扫描使用有界查询,能覆盖 resolved 和 expired,避免反复扫描已确认交付的历史记录。
- 纯等待不长期持有 root execution 或 drain 资源;接纳时沿既有机制获取。关闭时先停止新工作,再按存储生命周期释放资源。
- 生产不注册 fake adapter;工具仅在测试装配中开放。backend、Code Mode、工具目录均不能意外暴露不支持的入口。
- 更新旧记录解码、workflow schema、索引、大小限制及未来版本拒绝测试。
- 检查结果、prompt、provenance 和取消信息组合后的完整记录大小,保证后续合法转换仍可持久化。
- 同 Root 备份保留一致关联;Session 复制/导出排除 wait 时,也必须处理 Goal 中的 active-wait 和事件 continuation,避免悬空 gate 或在目标 Root 重新唤醒。
- 若变更触达公共 wire schema,按现有兼容检查处理,不能因为工具隐藏而跳过。
登记、来源观察、后继接纳和具体动作分别校验权限。`analyze-only` 是讨论中的可选政策,不在本 PR 中默认为已批准要求。
## 8. 实施顺序与代码落点
| 顺序 | 工作 |
|---|---|
| 1 | 固定状态转换、结果语义、控制代际和崩溃窗口,先补契约测试 |
| 2 | 实现登记、continuation 保存、联合接纳及终止交付的存储契约,完成 SQLite/Memory 一致性 |
| 3 | 实现内部工具及 runtime yield,验证终止、结算和资源释放 |
| 4 | 接入 Goal gate、无 evaluator 的计量刷新及固定身份 continuation |
| 5 | 接入 Root admission、恢复、Host 关闭和 Session 清理 |
| 6 | 完成端到端、故障注入、迁移/复制测试,再进行完整 diff 复审 |
主要代码范围:
- [Core](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/core/src):`event-wait.ts`、`goal.ts`、`turn-origin.ts`,以及相关 Runtime descriptor/event decoder。
- [Storage](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/storage/src):event-wait/Goal authority、agent-run admission、execution facade、workflow schema、独立 Memory 实现和 Session bundle/cleanup。
- [Runtime](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/runtime/src):工具控制接口、`ai-sdk-turn.ts`、Goal continuation/state/lifecycle 及计量读取。
- [Runtime Host](https://github.com/apache/maka/tree/db455a1661ec1bf83182c7a95110588994e3eec3/packages/runtime-host/src/server):等待桥接、Goal coordinators、Root admission、execution composition 和恢复装配。
尽量保持内部实现;跨 package 使用正式导出,不直接引用未导出的源码路径。
## 9. 验收与验证
使用可控时钟、可控暂停点、fake adapter/model、真实 Host admission 和 SQLite 进程恢复,至少覆盖:
| 测试组 | 核心断言 |
|---|---|
| 完整闭环 | 登记后源执行结束;无变化期间工作模型/evaluator 调用增量为零;结果到达后只有一个有效后继 |
| 登记与权限 | 同调用重试幂等、参数冲突拒绝、并发登记只有一个有效等待;无有效 Goal、越权、伪造来源和不支持 backend 不产生登记副作用 |
| 顺序与重复 | 登记前已满足、settlement 前已满足、重复通知、重复扫描和确认丢失均不重复交付 |
| Goal gate | 旧 timer、evaluator 失败、generic recovery 和 busy 重试不能穿透等待 |
| 正常进展 | busy 后最终能接纳;前一等待交付后,新合法执行可以登记下一次等待 |
| 控制竞争 | pause/resume、取消、替换、归档与接纳的不同先后顺序均不复活旧代等待 |
| 结果与预算 | 全部结果类别、期限、观测预算、token/max-iteration 限制;真实 Turn 不免计费,观测不增加工作轮次 |
| 原子性与崩溃 | 各事务写入点失败整体回滚;提交后确认丢失及进程被杀后仍复用原身份 |
| 生命周期与兼容 | lease 失效、关闭、旧记录、未来版本、备份、复制、删除和最大记录组合 |
| 工具边界 | exclusive-step、禁止嵌套、控制结果不能伪造;未支持入口和生产目录不暴露工具 |
Memory 测试证明逻辑与 provider 一致性;跨进程持久性必须由 SQLite 杀进程/重开测试证明。
开发期先按依赖顺序构建,再运行定向测试。交付前按仓库要求执行:
```sh
npm test
npm run lint
npm run format:check
npm run build
npm run typecheck
npx knip --workspace apps/desktop
npx knip --workspace packages/ui
git diff --check
```
PR 报告实际基线、最终提交、验证命令与结果、未覆盖项及必要设计偏差。建议标题:
`feat(runtime-host): bridge event waits to Goal continuation`
完成标准是内部闭环和上述故障边界可验证,同时保持生产工具未开放,为 PR4 接入 ShellRun 提供明确接口。
*由 OpenAI Codex 辅助起草,基于资料与源码核对;本文所列验证尚待实施阶段执行。*
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [I] tracking(runtime-host): condition-driven Session waits and wake-ups [maka]
Posted by "chinawch007 (via GitHub)" <gi...@apache.org>.
chinawch007 commented on issue #5926:
URL: https://github.com/apache/maka/issues/5926#issuecomment-6398370281
# Unified Host waiting for Goal mode: development plan
This document describes the branch's final design, implementation mapping and acceptance requirements.
## 1. Objective
**The objective of this change is to expand PR3's limited use cases to all waiting in Goal mode: use a unified Host observation mechanism while preserving execution, input, control, permission, accounting and recovery semantics.**
PR3 validated external event waiting in a deliberately narrow setting, where waiting ended the current Turn. Broader Goal use requires waiting to behave as an ordinary asynchronous operation: the original tool remains pending, the Host observes the external condition, and the model resumes the same Turn after receiving a result. For a Turn that has already ended naturally, Host observation drives the existing Goal continuation flow.
Compared with polling, condition detection, uninformative model calls and detection latency may change. Waiting must not itself end a Turn, create a continuation, insert user messages, change control scope, reset budgets or expand recovery authority. Real work retains normal accounting.
“All waiting in Goal mode” is the overall expansion objective. The table below defines the waiting forms, sources and execution entry points implemented by this branch; the objective does not imply that every external source or executor is already integrated. Exposing explicit event waiting to ordinary Sessions is a later stage.
## 2. Current delivery scope
| Dimension | Branch behavior |
| --- | --- |
| Explicit in-turn waits | WaitForEvent returns an ordinary result in the original Run, invocation and tool operation |
| Tool composition | Sequential, parallel, independent same-condition waits and mixed tool execution |
| Model entry points | Built-in AI SDK direct tools and Code Mode share the durable tool boundary |
| Natural Goal waiting | A verifiable structured evaluator condition lets Host observation drive the existing continuation |
| Production external source | Managed same-Session ShellRuns, with the terminal v1 condition |
| Child Run waiting | Two child Run join paths observe Runtime completion, then read the original durable result |
| Input and cancellation | current_turn steering, next_turn queuing, individual wait cancellation and Run Stop retain their scopes |
| Product entry points | Session-level multiple-wait query, presentation and precise cancellation in Desktop and CLI/TUI |
| General observation consumers | Neutral v1 and v2 consumers share source scheduling; existing Assistant Matter uses the neutral contract |
The current source registry does not include GitHub CI, PR/review, file readiness or computer text conditions. ACP executors use their existing tool surface, and computer text waiting keeps its existing implementation. Natural Goal waiting with no definite observable condition, an unsupported source or a definitive source-validation rejection uses existing backoff; temporary preparation unavailability retains the original outbox for retry. Arbitrary Bash polling scripts are not automatically parsed or rewritten.
These are current implementation boundaries, not additional work packages. Further sources, executors and ordinary Sessions can reuse these consumer and observation boundaries, with separate source-contract and authorization validation.
## 3. Behavioral contract
1. **Preserve execution.** Explicit waiting retains sessionId/turnId/runId/invocationId; the original operationId/toolCallId receives one durable result. The current execution retains its Root execution position while waiting.
2. **No uninformative model wakeups.** Pending, temporary unknown, duplicate notifications and throttling advance observation without periodically returning empty tool results for model rechecking.
3. **Normal settlement.** Explicit waiting triggers no evaluator or settlement for its Turn, adds or refunds no iterations, and resets no maxSteps, token baseline or existing execution deadline. The Turn settles once when it actually ends; valid settlement of earlier Turns must still complete.
4. **Separate control scopes.** Goal controls govern future Goal continuations; admitted tool waits belong to their Run. Cancelling one wait neither pauses Goal nor stops its background process. Stop still cancels the original Run.
5. **Exact ownership.** Equal-condition and different-condition waits have independent identities. A's late notifications, cancellation or acknowledgement cannot affect B.
6. **Revalidate authority.** Notifications are read hints. Registration, delivery, consumption and continuation each validate their identities, permissions, revision/CAS and durable facts.
7. **No repeated side effects.** Persisting an observation result, committing T2 and acknowledging consumption are separate stages. Lost acknowledgements are reconstructed from authority without replaying process startup or generating a second tool result.
8. **Resource bounds are not business failure.** Concurrency, backoff, subscription capacity and rolling counters bound Host work. An unbounded consumer must not fail early merely because an internal count or window reaches its limit.
9. **Recovery grants no extra authority.** A wait record is neither a cross-process JavaScript Promise nor permission to retry a tool or create a successor Turn.
## 4. Architecture and ownership
```text
AI SDK direct / Code Mode
→ Runtime T1: original tool dispatch
→ HostInvocationEventWaitCoordinator: registration and temporary receiver
→ HostEventWaitObserver: shared source scheduling
→ ShellRun adapter: notification followed by authoritative process read
→ durable observation result → original tool result → Runtime T2
→ original model loop continues; consumption ACK can reconcile asynchronously
Natural Turn completion → evaluator + normal settlement
→ exact-condition outbox → HostGoalWaitingCoordinator
→ the same HostEventWaitObserver
→ existing Goal continuation → existing Root admission
```
Each State Root's Runtime Host remains the execution and write authority. Runtime Event Log remains authoritative for model messages, T1/T2 and termination facts. Receivers, notifications and UI are derived state, not independent execution authorization.
The observer owns source concurrency, dispatch reservations, subscriptions, reconciliation reads, cooldown, Retry-After and absolute deadlines. Consumers own result attribution and delivery. Long pending waits hold no Goal mutation lane, Session admission lane or storage transaction.
Child Run joins reuse Runtime's active-run set and completion notifications, without creating another task lifecycle through the external source registry. Non-Goal callers can also use these shared join paths; their change is notification instead of repeated snapshot reads.
## 5. Data and persistence
### 5.1 Records and consumers
| Record | Identity and purpose | Completion evidence |
| --- | --- | --- |
| Neutral v1 EventWaitRecord | Existing source observation with its bounded observation policy | Existing consumer's observation result |
| v2 tool_invocation | Session/source Turn plus Run/invocation/operation/toolCall | Original operation's durable T2; consumed or retired delivery |
| v2 goal_waiting | Naturally completed Run, completion event, Goal control lease and revision | Existing natural continuation's Root admission; admitted or retired delivery |
AnyEventWaitRecord is their union. Consumer identity comes from trusted execution context, not model input. Version two permits a null deadlineAt and records a monotonic observation window; resolutions, including a resolution preceding cancellation, remain verifiable.
Natural Goal waiting stores the exact condition in pendingContinuation.observation, pending preparation in observationPreparation, and result digest/continuation identity in goalWaiting. These fields do not own an active tool's execution.
### 5.2 Storage boundaries
- registerInvocationWait validates real T1, tool name, arguments and original invocation. Another tool with identical arguments cannot register a wait.
- acknowledgeInvocationWaitResult validates original T2, operation, result digest and original resolution. Cancellation or revision changes after T2 require revalidation against immutable evidence, not denial of the completed tool fact.
- retireInvocationWait retires unreachable consumers based on legitimate error results or execution termination facts.
- Natural registration/admission validates original completion, Goal control, exact outbox and revision; a wait result cannot replace Root admission.
- Memory and SQLite independently implement coexistence, CAS, queries, consumption, archive/delete and backup boundaries, without wrapping one another or silently falling back.
- SQLite workflow schema is **16**. Session copies do not transfer observation execution authority; same-root backups retain consistent durable facts.
Admission uses strict decoders. PR3-specific historical data is outside this compatibility contract, with no conversion or fallback; State Roots containing it may be rejected during reading or recovery. Older binaries cannot bypass schema checks to open newer storage. This remains a current data boundary, not a promise of unconditional upgrade or downgrade.
## 6. Explicit waiting flow
### 6.1 Capability and discovery
WaitForEvent is a built-in Host tool. With an active/waiting Goal, the Host can bind capability to a specific Run. A successful GoalSet during the Run can also enable it, without changing model or creating another Session. Tool discovery resolves current capability dynamically.
Later Goal pause/clear/replace does not revoke that Run's acquired capability. The binding ends with the Run and is not inherited by other Runs. An ordinary Run without Goal capability cannot discover the tool. Manual checks must establish active status with GoalStatus/GoalSet before searching.
### 6.2 Request, result and delivery
Requests contain resource, versioned condition and reason, with optional timeoutMs and resumeWaitId. The current ShellRun shape is:
```json
{
"resource": {
"providerId": "shell_run",
"connectionId": null,
"resourceType": "background_task",
"resourceId": "<actual maka://runtime/background-tasks/... reference from Bash>"
},
"condition": { "typeId": "terminal", "version": 1, "parameters": {} },
"reason": "Wait for the background job to end before inspecting its result"
}
```
Execution sequence:
1. Runtime commits T1 in the original operation; the Host validates invocation, arguments and the supported condition template.
2. Register idempotently under original call identity, install a temporary receiver and reread authority to cover early results and lost notifications.
3. Keep the tool pending; the Host validates execution identity and source access before source reads. A Turn may hold multiple sequential or parallel independent waits.
4. Persist the source result, revalidate original call, source access and wait revision, then return one result to the original tool. Cancellation and expiry return their own durable terminal outcomes.
5. Runtime commits T2 and triggers asynchronous consumption acknowledgement. Failure does not block the model; reconciliation reconstructs ACK from that same T2.
EventWaitToolResult contains kind, waitId, outcome, observedAt, deadlineAt, evidenceRefs and optional reason. Outcomes are satisfied, expired, invalidated, source_unavailable, cancelled and interrupted.
ShellRun terminal means process termination, not success. Read the original reference after waiting to inspect exit code and output. Source access validates Session, visibility and the real background startup's Run and Bash T1/T2; a reference is not an access credential.
### 6.3 Input, cancellation and deadlines
| Event/action | Behavior |
| --- | --- |
| current_turn input | Notification returns interrupted; existing durable steering still reads and acknowledges the input |
| next_turn input | Remains queued and is not consumed by wait notification |
| Resume interrupted wait | A new call supplies returned waitId as resumeWaitId and the same resource/condition, inheriting the original absolute deadline; it still has its own tool operation identity |
| Individual wait cancellation | Returns cancelled to that operation without stopping ShellRun or pausing Goal |
| Goal pause/clear/replace | Existing rules govern future continuations; original Run's bound ordinary tool capability remains |
| Run Stop | Existing abort/termination and cleanup; late results cannot revive the Run |
| Explicit timeout | Uses the first wait's absolute deadline, unchanged by retries, steering or recovery |
| Omitted timeout | Adds no wait-specific deadline; execution lifecycle and outer limits still apply |
Code Mode uses the same tool boundary. Trusted steering interruption stops new nested dispatch while already-dispatched siblings complete required settlement and cleanup. Ordinary serialized JSON cannot forge that control signal.
## 7. Natural Goal waiting
This path starts after a Turn has naturally completed. It neither requires the original Run to stay alive nor invents a tool call.
1. The evaluator's waitingCondition supplies an explicit resource, condition and absolute deadline. The Host validates structure instead of inferring a source from prose.
2. The original Turn completes normal usage and Goal settlement; exact condition and natural-completion identity enter the pending continuation outbox.
3. Preparation, registration and recovery progress from that durable outbox. Temporary unavailability must not retain the finished Turn's Root position; retries reuse the original condition.
4. A result revalidates Goal control, revision, original completion and exact pending identity, updates waiting→active and its checkpoint, then uses existing Root admission.
5. Admission and consumption retain one continuation identity. Duplicate notifications or lost commit acknowledgements cannot produce multiple successors.
6. A new real Turn or Goal control change invalidates older pending work; old results cannot overwrite newer evaluation. Existing admission continues to enforce budgets and user-input priority.
Without a valid structured condition, retain existing waiting backoff and record why observation could not be established. Natural-wait cancellation uses Goal generation/revision and existing Goal control semantics, not ordinary tool cancellation semantics.
## 8. Recovery, shutdown and resource management
| Failure window | Authoritative handling |
| --- | --- |
| T1 committed, not registered | Existing tool recovery policy; arguments do not authorize replaying background startup |
| Registration committed, receipt/receiver absent | Recover the same operation's record, reread after listener installation, and assess delivery against real execution state |
| Resolution committed, no T2 | Retain result; deliver to the original live call or defer interrupted execution to existing Runtime recovery |
| T2 committed, ACK lost | Verify T2 and reconstruct ACK without repeating tool results or model requests |
| Invocation terminated | Retire receiver/consumer using termination facts; late callbacks do not revive execution |
| Restart during natural preparation | Recover exact outbox, revalidate current Goal/pending, preserve one legitimate existing continuation |
| Host shutdown or authority loss | Stop new work, cancel subscriptions/receivers, drain dispatched cleanup; long waits do not prevent shutdown |
| Session archive/delete/copy | Clean all Session waits; copies inherit no subscription, consumption or execution permission |
Startup reconciliation waits for trustworthy Runtime recovery facts. An empty initial receiver map is not proof of an orphan. Existing termination, original T2 and Goal outbox determine recovery separately for their respective consumers.
## 9. Protocol and product behavior
- session.event-waits.query exposes bounded, paginated Session waits. session.event-wait.cancel carries exact consumer identity and validates cancellation authority.
- Tool cancellation binds Run/invocation/operation; natural cancellation binds Goal/generation/revision. Stale UI requests cannot cancel replacements.
- Session continuity projects waits independently. Active tool waits remain visible after Goal clear, with source reference, observation state, deadline and cancellation control.
- Desktop main/preload/renderer and CLI/TUI share the Host protocol. Renderer uses the Goals feature and shared Session context, without a client-side source observation service.
- Runtime Host compatibility epoch is **231**. Client and Host match the current strict protocol and capability allowlists.
## 10. Work packages and implementation mapping
These work packages describe the current branch, not future source integrations. Paths are repository-relative.
| Work package | Main implementation | Exit condition |
| --- | --- | --- |
| A. Consumer contracts | packages/core/src/event-wait.ts, event-wait-delivery.ts, goal.ts, Runtime invocation/turn-origin contracts | Strict distinction between neutral observations and both consumers; consistent condition, identity, deadline and transitions |
| B. Independent persistence | packages/storage/src/event-wait-authority.ts, event-wait-consumer.ts, SQLite/Memory authority and bridge, workflow schema, execution stores, Session bundle | Registration/T2 ACK/natural admission use the same CAS and ownership boundary; coexistence, backups and recovery hold |
| C. Original asynchronous tool | packages/runtime/src/event-wait-tool.ts, tool-runtime.ts, ai-sdk-backend.ts, ai-sdk-turn.ts, code-mode.ts | Direct and Code Mode return in the original operation with complete input and durable-result wiring |
| D. Host tool consumer and source | packages/runtime-host/src/server/invocation-event-waits.ts, event-wait-manager.ts, event-wait-observation-store.ts, event-wait-sources.ts, ShellRun access/adapter, execution composition | Dynamic capability, source authorization, notification/reread, deadlines, cancellation and recovery form a complete path |
| E. Natural Goal consumer | Runtime goal-evaluator.ts and goal-continuation.ts; Host goal-waiting.ts, goal-coordinator.ts, goal-execution-coordinator.ts and Root/recovery wiring | Normal original settlement, recoverable exact outbox, single admission of existing continuation |
| F. Internal completion and input notifications | Runtime runtime-kernel.ts, session-manager.ts and message authority; Host message-coordinator.ts | Child join and steering observe original facts without consuming wrong input or creating execution |
| G. Public protocol and UX | Host event-wait/session-continuity protocols, Desktop Goals/IPC, CLI driver/TUI, UI Session context | Multiple visible waits, precise cancellation and consistent Goal/Run controls |
| H. Contract documentation and validation | docs/goal-event-wait-consumers.*, Runtime README, package contract/integration tests and Storybook | Source-aligned scope; separate behavioral, persistence, recovery and product evidence |
Implementation dependencies are A → B → C/D/E, followed by integrated validation of F/G/H. C, D and E share consumer contracts while explicit tools and natural continuations retain separate consumption authority.
## 11. Validation and acceptance
| Scenario | Required facts |
| --- | --- |
| Goal capability | New Runs without Goal cannot discover it; rediscovery after GoalSet succeeds; later controls do not revoke bound capability |
| Last Goal iteration | Resume and finish in the same Turn without extra/refunded iterations or reset model budget |
| Sequential/parallel/mixed/equal-condition waits | One T2 per operation; no cancellation or late-result cross-talk |
| Code Mode | Ordinary results, trusted steering and dispatched-sibling cleanup match direct semantics |
| Input/control races | Distinct current_turn/next_turn treatment; cancel-wait, Stop, Goal controls and result races follow the control table |
| Observation progress | Pending/unknown/duplicate or missing notifications do not wake the model; throttling/subscription shortage reconciles within policy; deadlines persist |
| Durable consumption | Real T1 required; other tools cannot impersonate; T2→cancel→ACK preserves results; lost ACK is recoverable |
| Natural waiting | Real completion and usage settle first; exact outbox recovers; new control/real Turn prevents stale continuation |
| Real ShellRun | Success and nonzero exits both satisfy terminal; Read determines success; cancelling the wait does not kill the job |
| Process recovery | Separate SQLite reopen and full Host SIGKILL evidence; waits do not authorize Bash replay or a new Turn |
| Data lifecycle | Independent providers, backups, archive/delete, Session copy and schema rejection preserve boundaries |
| Product/shared regressions | Desktop/CLI multiple waits and exact cancellation; no extra effects on Assistant Matter, ordinary Runs, existing computer and ACP behavior |
Behavioral tests use trusted T1/T2, controlled sources/clocks and authoritative synchronization points. Critical assertions cover both results and absent side effects while pending. Deterministic tests do not replace real-model tool-selection validation.
Manual validation first establishes active Goal with GoalStatus/GoalSet, then discovers the tool. Start two managed ShellRuns sequentially, call WaitForEvent once per job and Read once after each result; verify output and original Turn timeline. Discovery failure before Goal activation is not an event-wait fault. In-turn waiting also need not change Goal status to waiting.
Clean/build before testing current sources. Select targeted tests by impact and run repository checks:
```sh
npm run rebuild
npm run lint
npm run format:check
npm run typecheck
npx knip --workspace apps/desktop
npx knip --workspace packages/ui
npm run check:renderer-architecture
npm run check:e2e-budget
git diff --check
```
Record full npm test, targeted suites, visual checks and real-model experiments separately. Normal commits run Biome, ASF, epoch and whitespace hooks. Stale dist results are not evidence for current sources.
## 12. Current validation status and delivery limits
Existing evidence for fixed snapshot fb3b614d7: clean build, lint, typecheck, Desktop/UI Knip, renderer architecture, E2E budget and commit hooks passed. The selected 55 compiled test files yielded 1,912 passes, 3 platform skips and 1 failure. The failure is an existing macOS /var versus /private/var prefix assertion; the individual canonical-TMPDIR comparison passed, while the original suite remains failed. Full npm test was not run; whole-checkout formatting and task-scoped formatting must not be conflated.
See the [implementation and validation record](./goal-host-observation-implementation-v3-2026-10-24.en.md) for evidence and limits. This documentation consolidation is not a new build, behavioral test or user-instance verification. Current capabilities have explicit contracts; the broader all-Goal-waits objective still requires source-, entry-point- and condition-specific acceptance rather than treating intent as implemented coverage.
*Prepared with assistance from OpenAI Codex.*
<details>
<summary>中文(点击展开)</summary>
# Goal 模式统一 Host 等待:开发方案
## 1. 目标
**本次修改的目标,是把 PR3 的有限使用场景扩大到 Goal 模式下所有等待:以统一的 Host 观测方式承接等待,同时保持原有执行、输入、控制、权限、计量及恢复语义。**
PR3 先以较窄场景验证外部事件等待,其等待会结束当前 Turn。面向更广的 Goal 使用场景,等待应成为执行过程中的普通异步操作:原工具保持 pending,Host 观察外部条件,结果返回后模型继续原 Turn。对于已经自然结束的 Turn,Host 观测驱动原有 Goal 续行流程。
与原轮询方式比较,允许变化的是条件检测方式、无信息模型调用次数和检测延迟;等待本身不得额外结束 Turn、创建续行、插入用户消息、改变控制作用域、重置预算或扩大恢复权限。真实工作仍按原规则计量。
“Goal 模式下所有等待”是总体扩展目标。当前分支落实的等待形态、来源和执行入口以以下范围表为准;不能把总体目标当作全部外部来源或全部执行器已经接入的结论。普通 Session 的显式事件等待开放属于后续阶段。
## 2. 当前交付范围
| 维度 | 本分支行为 |
| --- | --- |
| Turn 内显式等待 | `WaitForEvent` 在原 Run、invocation 和工具操作中返回普通结果 |
| 工具组合 | 支持连续、并行、同条件独立等待,以及等待与其它工具混合执行 |
| 模型执行入口 | 内置 AI SDK 的直接工具和 Code Mode 使用相同持久工具边界 |
| 自然 Goal waiting | evaluator 提供可验证结构化条件后,由 Host 观测驱动既有 continuation |
| 生产外部来源 | 同 Session 的受管 ShellRun;条件为 `terminal` v1 |
| 子 Run 等待 | 两条 child Run join 路径通过 Runtime 完成通知等待原执行,再读取持久结果 |
| 输入与取消 | `current_turn` steering、`next_turn` 排队、单等待取消和 Run Stop 各保持原作用域 |
| 产品入口 | Session 多等待查询、展示及精确取消,覆盖 Desktop 与 CLI/TUI |
| 通用观测消费者 | 中立 v1 观测与 v2 消费者共享来源调度,现有 Assistant Matter 继续使用中立契约 |
当前来源表不包括 GitHub CI、PR/review、文件就绪或计算机文字条件;ACP 执行器使用其既有工具集合。计算机文字等待保持既有实现。缺少明确可观测条件、来源不受支持或来源校验明确拒绝的自然 Goal waiting,使用原有等待退避;准备阶段暂时不可用则保留原 outbox 重试。任意 Bash 脚本中的轮询不会被自动解析或改写。
这些是当前实现边界,不是新的开发工作包。覆盖更多来源、执行器和普通 Session 时,可复用本文消费者与观测边界,但须分别验证其来源契约和授权。
## 3. 行为契约
1. **保持原执行。** 显式等待前后保持同一 `sessionId/turnId/runId/invocationId`;原工具 `operationId/toolCallId` 对应一次持久结果。等待期间原 Root 执行位置仍归当前执行。
2. **无信息不唤醒工作模型。** pending、暂时 unknown、重复通知及限流仅推进 Host 观测,不周期性返回空工具结果让模型重新检查。
3. **正常结算。** 显式等待不触发本 Turn 的 evaluator 或结算,不增加、返还 iterations,不重置 maxSteps、token baseline 或已有执行期限。原 Turn 真正结束时结算一次;更早 Turn 的合法结算仍需完成。
4. **控制分层。** Goal 控制决定未来 Goal 续行;已接纳的工具等待归原 Run。取消一个等待不会暂停 Goal 或停止后台进程,Stop 仍取消原 Run。
5. **准确归属。** 同条件和不同条件的等待都有独立身份。A 的迟到通知、取消或确认不能作用于 B。
6. **权威重验。** 来源通知只是读取提示;登记、交付、消费和续行分别验证对应身份、权限、revision/CAS 与持久事实。
7. **不重复副作用。** 结果已持久化、工具 T2 已提交和消费已确认是三个阶段。确认丢失可从权威记录补齐,不能重放后台启动或生成第二条工具结果。
8. **有限资源不变成业务失败。** 观测并发、退避、订阅容量和滚动计数窗口约束 Host 工作;无期限消费者不因内部次数或窗口达到边界而被提前判失败。
9. **恢复不扩权。** wait 记录不是可跨进程恢复的 JavaScript Promise,也不是重试工具或创建后继 Turn 的许可。
## 4. 架构与所有权
```text
AI SDK direct / Code Mode
→ Runtime T1:原工具派发
→ HostInvocationEventWaitCoordinator:登记与临时接收器
→ HostEventWaitObserver:共享来源调度
→ ShellRun adapter:通知后读取权威进程状态
→ 持久观测结果 → 原工具返回 → Runtime T2
→ 原模型循环继续;消费确认可异步补齐
自然 Turn completion → evaluator + 正常结算
→ 精确条件 outbox → HostGoalWaitingCoordinator
→ 同一 HostEventWaitObserver
→ 既有 Goal continuation → 原 Root admission
```
每个 State Root 的 Runtime Host 仍是执行和写入权威。Runtime Event Log 是模型消息、T1/T2 和终止事实的权威。临时接收器、通知及 UI 都是派生状态,不能独立授权执行。
观测器负责来源并发、dispatch reservation、订阅、协调重读、cooldown、Retry-After 和绝对期限。消费者负责结果归属及交付。长时间 pending 不占有 Goal mutation lane、Session admission lane 或存储事务。
子 Run join 复用 Runtime 的 active-run 集合与完成通知,不通过外部 source registry 创建另一种任务生命周期。公共 join 路径也可被非 Goal 调用方使用,其变化仅是通知代替重复快照读取。
## 5. 数据与持久化
### 5.1 记录与消费者
| 记录 | 身份与用途 | 完成依据 |
| --- | --- | --- |
| 中立 v1 `EventWaitRecord` | 既有来源观测;保留有界观测策略 | 既有消费者的观测结果 |
| v2 `tool_invocation` | Session/source Turn + Run/invocation/operation/toolCall | 原操作的持久 T2;delivery 为 consumed 或 retired |
| v2 `goal_waiting` | 自然完成 Run、completion event、Goal control lease 与 revision | 既有自然 continuation 的 Root admission;delivery 为 admitted 或 retired |
`AnyEventWaitRecord` 表示上述联合。消费者身份来自可信执行上下文,不由模型传入。v2 的 `deadlineAt` 可为 null,观测检查点记录单调 window,结果和取消前已有 resolution 保持可核验。
Goal 自然等待使用 `pendingContinuation.observation` 保存精确条件,`observationPreparation` 表示待准备状态,`goalWaiting` 关联结果摘要与原续行身份。这些字段不构成活跃工具的执行所有权。
### 5.2 存储边界
- `registerInvocationWait` 验证真实 T1、工具名、参数和原 invocation;另一工具使用相同参数也不能登记等待。
- `acknowledgeInvocationWaitResult` 验证原 T2、operation、结果摘要与原 resolution。T2 后发生取消或 revision 更新时,按不可变结果重验,不否认已经完成的工具事实。
- `retireInvocationWait` 根据合法错误结果或执行终止事实退役不再可达的消费者。
- 自然等待的登记及接纳验证原 completion、Goal control、精确 outbox 和 revision;等待结果不能替代原 Root admission。
- Memory 与 SQLite 独立实现多等待共存、CAS、查询、消费、归档/删除及备份边界,不互相包装或隐式降级。
- 当前 SQLite workflow schema 为 **16**;Session 复制不携带观测执行权威,同根备份保留一致持久事实。
数据准入采用严格 decoder。PR3 专用历史数据不在本次兼容契约内,不提供转换或回退;包含这些数据的 State Root 可能在读取或恢复时被拒绝。较旧二进制也不能绕过 schema 检查打开较新的存储。这是仍然有效的数据边界,不承诺无条件升级或降级。
## 6. 显式等待流程
### 6.1 工具能力与发现
`WaitForEvent` 是 Host 内置工具。Goal 处于 active/waiting 时,Host 可将能力绑定到具体 Run;运行中成功 `GoalSet` 后也可开放,无需更换模型或新建会话。工具目录根据当前能力动态解析。
Goal 随后 pause/clear/replace 不撤销该 Run 已获得的能力;Run 结束后绑定释放,其他 Run 不继承。未获得 Goal 能力的普通 Run 搜索不到该工具。实测先用 `GoalStatus`/`GoalSet` 确认 active,再搜索工具。
### 6.2 请求、结果和交付
请求字段为 `resource`、版本化 `condition`、`reason`,以及可选 `timeoutMs`、`resumeWaitId`。当前 ShellRun 请求为:
```json
{
"resource": {
"providerId": "shell_run",
"connectionId": null,
"resourceType": "background_task",
"resourceId": "<Bash 返回的 maka://runtime/background-tasks/... 引用>"
},
"condition": { "typeId": "terminal", "version": 1, "parameters": {} },
"reason": "等待后台任务结束后检查结果"
}
```
执行顺序:
1. Runtime 在原操作提交 T1,Host 校验原 invocation、参数与受支持条件模板。
2. 按原调用身份幂等登记 wait,安装临时接收器并重读权威状态,覆盖结果早到或通知丢失。
3. 原工具保持 pending,Host 在来源读取前校验执行身份与来源访问权;同一 Turn 可连续或并行持有多个独立等待。
4. 来源结果先持久化,交付前重验原调用、来源访问权及 wait revision,再向原工具返回一次结果。取消和到期按其自身持久终态返回。
5. Runtime 提交 T2,触发异步消费确认。确认失败不阻塞模型,后续 reconciliation 从同一 T2 补确认。
`EventWaitToolResult` 包含 `kind`、`waitId`、`outcome`、`observedAt`、`deadlineAt`、`evidenceRefs` 和可选 `reason`。outcome 为 satisfied、expired、invalidated、source_unavailable、cancelled 或 interrupted。
ShellRun 的 terminal 表示进程进入终态,不代表成功。工具返回后用 `Read` 检查原引用的退出码和输出。来源授权核对同 Session、可见性、真实后台启动的 Run 和 Bash T1/T2;引用本身不是访问凭据。
### 6.3 输入、取消与期限
| 事件/操作 | 行为 |
| --- | --- |
| `current_turn` 输入 | 通知等待返回 interrupted;输入仍由既有持久 steering 边界读取和确认 |
| `next_turn` 输入 | 留在下一轮队列,不被等待通知取走 |
| 恢复中断等待 | 新调用携带返回的 waitId 作为 resumeWaitId,并保持同一资源/条件,继承原绝对期限;新调用仍有自己的工具操作身份 |
| 单等待取消 | 向该操作返回 cancelled;不停止 ShellRun,不暂停 Goal |
| Goal pause/clear/replace | 按原规则控制未来 Goal 续行;原 Run 已绑定的普通工具能力保留 |
| Run Stop | 走既有 abort/终止和清理流程;迟到来源结果不能复活 Run |
| 显式 timeout | 按首次等待的绝对期限结束;重试、steering、恢复均不重置期限 |
| 省略 timeout | 不增加等待专属期限;原执行生命周期及外层限制仍有效 |
Code Mode 使用相同工具边界。可信 steering 中断后停止新的嵌套派发,已派发的兄弟操作完成必要结算和清理;普通序列化 JSON 不能伪造该控制信号。
## 7. 自然 Goal waiting
此路径从已经自然完成的 Turn 开始,不要求原 Run 继续存活,也不伪造工具调用。
1. evaluator 的 `waitingCondition` 提供明确 resource、condition 和绝对 deadline;Host 校验结构化条件,不从自由文本猜测来源。
2. 原 Turn 完成正常用量及 Goal 结算,精确条件和自然完成身份写入 pending continuation outbox。
3. 来源准备、登记和恢复沿持久 outbox 推进。暂时不可用不得继续占有已结束 Turn 的 Root 执行位置;重试复用原条件。
4. 观测结果到达时重验 Goal control、revision、原 completion 和精确 pending 身份,更新 waiting→active 及对应 checkpoint,再走既有 Root admission。
5. 接纳与消费确认保留同一续行身份,重复通知和提交确认丢失不能产生多个 successor。
6. 新的真实 Turn 或 Goal 控制变化使旧 pending 失效;旧结果不能覆盖较新评估。预算及用户输入优先级继续由既有接纳边界执行。
没有有效结构化条件时维持原等待退避,并明确记录观测无法建立的原因。自然等待的取消使用 Goal control generation/revision 和既有 Goal 控制语义,不套用普通工具的单操作取消规则。
## 8. 恢复、关闭及资源管理
| 故障窗口 | 权威处理 |
| --- | --- |
| T1 后尚未登记 | 由原工具恢复策略处理,不凭参数重放后台启动 |
| 登记成功但回执/接收器缺失 | 查回相同 operation 的记录;通知安装后重读,依据真实执行状态判断可交付性 |
| resolution 已提交但无 T2 | 保留结果;活进程交给原调用,进程中断交由既有 Runtime recovery |
| T2 已提交但 ACK 丢失 | 核验原 T2 后补确认,不重复工具结果或模型请求 |
| 原 invocation 已终止 | 按终止事实退役接收器及消费者;迟到回调不复活执行 |
| 自然等待准备中重启 | 从精确 outbox 恢复,重验当前 Goal 与 pending,保留一次合法既有续行 |
| Host 关闭或执行权失效 | 停止新工作、取消订阅和接收器、收敛已派发清理;长等待不阻塞退出 |
| Session 归档、删除或复制 | 清理该 Session 的多等待;副本不继承来源订阅、消费或执行权限 |
启动 reconciliation 等待 Runtime 恢复事实可信,不能把初始化时接收器 map 为空当作孤儿证据。已有终止事实、原 T2 和 Goal outbox 分别决定各消费者的恢复动作。
## 9. 协议与产品
- `session.event-waits.query` 提供有界分页的 Session 等待集合;`session.event-wait.cancel` 携带精确消费者身份并校验取消权限。
- 工具等待取消绑定 Run/invocation/operation;自然等待绑定 Goal/代际/revision。过时界面请求不能取消替换后的对象。
- Session continuity 独立投影等待列表。Goal 清除后仍在运行的工具等待保持可见;界面显示来源引用、观测状态、期限和取消动作。
- Desktop main/preload/renderer 与 CLI/TUI 使用同一 Host 协议。renderer 通过 Goals feature 和共享 Session context 展示,不增加客户端来源观测服务。
- 当前 Runtime Host compatibility epoch 为 **231**。客户端与 Host 按现行严格协议及权限白名单匹配。
## 10. 实施工作包与代码落点
下列工作包共同构成当前分支的实现,不包含后续来源接入计划。路径以仓库根为起点。
| 工作包 | 核心落点 | 交付条件 |
| --- | --- | --- |
| A. 消费者契约 | `packages/core/src/event-wait.ts`、`event-wait-delivery.ts`、`goal.ts`、Runtime invocation/turn-origin 契约 | 中立观测与两类消费者可严格区分;条件、身份、期限和合法转换一致 |
| B. 独立持久化 | `packages/storage/src/event-wait-authority.ts`、`event-wait-consumer.ts`、SQLite/Memory authority 与 bridge、workflow schema、execution stores、Session bundle | 登记/T2 ACK/自然接纳遵守同一 CAS 和所有权边界;多等待、备份和恢复成立 |
| C. 原工具异步执行 | `packages/runtime/src/event-wait-tool.ts`、`tool-runtime.ts`、`ai-sdk-backend.ts`、`ai-sdk-turn.ts`、`code-mode.ts` | direct/Code Mode 都在原操作返回;输入中断和持久结果接线完整 |
| D. Host 工具消费者与来源 | `packages/runtime-host/src/server/invocation-event-waits.ts`、`event-wait-manager.ts`、`event-wait-observation-store.ts`、`event-wait-sources.ts`、ShellRun access/adapter、execution composition | 能力动态开放、来源授权、通知重读、期限、取消和恢复闭环 |
| E. 自然 Goal 消费者 | Runtime `goal-evaluator.ts`、`goal-continuation.ts`;Host `goal-waiting.ts`、`goal-coordinator.ts`、`goal-execution-coordinator.ts` 及 Root/recovery 接线 | 原 Turn 正常结算,精确 outbox 可恢复,既有续行一次接纳 |
| F. 内部完成与输入通知 | Runtime `runtime-kernel.ts`、`session-manager.ts`、message authority;Host `message-coordinator.ts` | child Run join 和 steering 通过原事实通知;不消费错误输入或创建新执行 |
| G. 公共协议和体验 | Host protocol/event-wait 与 session-continuity;Desktop Goals/IPC;CLI driver/TUI;UI Session context | 多等待可见,精确取消,Goal 和 Run 控制语义一致 |
| H. 契约文档与验证 | `docs/goal-event-wait-consumers.*`、Runtime README;各包契约/集成测试、Storybook | 文档与源码范围一致;行为、持久化、恢复、产品验证分别留证 |
实施依赖顺序为 A → B → C/D/E,随后统一验证 F/G/H。C、D、E 共享消费者契约,但显式工具与自然续行各保持独立的消费权威。
## 11. 验证与验收
| 场景 | 必须验证的事实 |
| --- | --- |
| Goal 能力开放 | 无 Goal 的新 Run 不暴露工具;GoalSet 后重新发现;后续控制变化不误撤销已绑定能力 |
| 最后一次迭代内等待 | 原 Turn 等待后继续完成,不额外新增或返还迭代,不重置模型预算 |
| 连续/并行/混合/同条件等待 | 每个 operation 一条 T2,取消与迟到结果互不串扰 |
| Code Mode | 普通结果、可信 steering、已派发兄弟操作清理与 direct 路径一致 |
| 输入与控制竞争 | current_turn/next_turn 分别处理;单等待取消、Stop、Goal 控制与结果竞态符合控制表 |
| 观测进展 | pending/unknown/重复或丢失通知不唤醒模型;限流/订阅不足可受控协调;绝对期限保持 |
| 持久消费 | 真 T1 必需,其它工具不能冒充;T2→取消→ACK 等交错保留原结果;ACK 丢失可补确认 |
| 自然 waiting | 真实完成和用量先结算;精确 outbox 恢复;新控制/新真实 Turn 阻止旧续行 |
| 真实 ShellRun | 正常退出与非零退出都能满足 terminal;Read 决定成功与否;取消等待不杀后台任务 |
| 进程恢复 | 分别验证 SQLite 重开与完整 Host SIGKILL;不把等待记录作为 Bash 重放或新 Turn 许可 |
| 数据生命周期 | Memory/SQLite、备份、归档/删除、Session copy 和 schema 拒绝保持一致边界 |
| 产品与公共回归 | Desktop/CLI 多等待和精确取消;Assistant Matter、普通 Run、既有计算机与 ACP 行为不受额外影响 |
行为测试使用可信 T1/T2、可控来源/时钟和权威同步点;关键断言同时检查结果与等待期间未发生的副作用。当前确定性测试不替代真实模型工具选择验证。
手工验证先通过 `GoalStatus`/`GoalSet` 确认 active,再发现工具。顺序启动两个受管 ShellRun,各调用一次 WaitForEvent,结束后各 Read 一次,核验真实结果和原 Turn 时间线。Goal 开启前搜索失败只说明能力未开放,不是事件等待故障;整体 Goal 状态也不必因为 in-turn 等待而变成 waiting。
验证当前源码前先 clean/build;根据改动范围运行定向测试和以下仓库检查:
```sh
npm run rebuild
npm run lint
npm run format:check
npm run typecheck
npx knip --workspace apps/desktop
npx knip --workspace packages/ui
npm run check:renderer-architecture
npm run check:e2e-budget
git diff --check
```
全仓 `npm test`、定向测试、视觉验证和真实模型实测分别记录,不相互代替。提交正常执行 Biome、ASF、epoch 和空白 hooks;不把旧 dist 通过当作当前源码证据。
## 12. 当前验证状态与交付界限
固定快照 `fb3b614d7` 的已有记录:clean build、lint、typecheck、Desktop/UI Knip、renderer architecture、E2E budget 和提交 hooks 已通过;55 个编译测试文件中 1,912 通过、3 个平台跳过、1 个失败。失败项为既有 macOS `/var` 与 `/private/var` 路径前缀断言,规范化 TMPDIR 的单项对照通过;原套件仍记为失败。未执行全仓 `npm test`,全目录 format 检查与任务范围检查不能混称。
实际证据与限制见[实现及验证记录](./goal-host-observation-implementation-v3-2026-10-24.zh-CN.md)。本次文档整合不构成新的构建、行为测试或用户实例验证。范围表中的现行能力有明确契约;更广的“Goal 模式下所有等待”目标仍以来源、执行入口及可表达条件逐项验收,不以目标表述代替未实现的覆盖。
*由 OpenAI Codex 辅助起草。*
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [I] tracking(runtime-host): condition-driven Session waits and wake-ups [maka]
Posted by "chinawch007 (via GitHub)" <gi...@apache.org>.
chinawch007 commented on issue #5926:
URL: https://github.com/apache/maka/issues/5926#issuecomment-6282816658
# PR4 Detailed Technical Plan: ShellRun Terminal-State Adapter and the First Usable Event-Wait Flow
## 0. Decision and References
PR4 builds on PR1's wait storage, PR2's Host observer, and PR3's Goal delivery bridge to consume terminal facts from ShellRuns created by managed background Bash. It exposes `WaitForEvent` on supported paths, a minimal status view, and an operation that cancels a specific wait. The intended flow is: the model starts background tests and registers a wait; the current Turn ends; the Host invokes neither the work model nor the Goal evaluator while no new result is available; once the tests finish, the result returns to the original Goal through existing Root admission.
This document is an implementation proposal for review. It does not claim that the proposed interfaces already exist or that every product choice has been approved. This task produces a plan only: it does not modify product code or publish to GitHub.
### 0.1 Exact Sources and Code Baseline
References checked on 2026-10-20. The online discussion, tracking issue, and all three implementation-plan comments were read. The links below identify the exact sources:
| Source | Role in this plan |
| --- | --- |
| PR4 section of [Discussion #5920](https://github.com/apache/maka/discussions/5920) | First usable ShellRun flow, terminal-state verification, minimal presentation, cancellation, and the prohibition on automatic command replay |
| [Tracking issue #5926](https://github.com/apache/maka/issues/5926) | Goal as the first consumer and the scope of the seven PRs |
| [PR1 plan comment](https://github.com/apache/maka/issues/5926#issuecomment-5951631569) | Provider-neutral records, CAS, leases, cleanup, and copy boundaries |
| [PR2 plan comment](https://github.com/apache/maka/issues/5926#issuecomment-6121096199) | Snapshot reconciliation, notification hints, deadlines, observation budgets, and error isolation |
| [PR3 plan comment](https://github.com/apache/maka/issues/5926#issuecomment-6229672833) | Trusted registration, actual yield, Goal gating, frozen continuations, joint admission, and recovery |
The inspected source baseline is local `main` at `b0ed5707ff3dc645c66253ed93e50936e3f9d45d`, whose latest commit is PR3 [#6147](https://github.com/apache/maka/pull/6147). This is an explicit local snapshot, not a claim that it equals the latest remote main at implementation time. Recheck the differences before implementation.
The preceding comments establish design intent; the merged interfaces determine how to connect the implementation. For example, PR1 originally proposed a 32 KiB record limit, whereas `EVENT_WAIT_LIMITS.recordBytes` is now 48 KiB. PR3 already provides `delivery` and joint bridge operations; PR4 must not implement another delivery state machine.
### 0.2 Explicit Choices Introduced by This Plan
The following restrictions make PR4 concrete enough to implement. They are proposals here, not claims that the discussion has already settled each point:
1. Register waits only for ShellRuns owned locally by the current Session in the same State Root, with a canonical background Bash result exposed to the model. Inherited, copied, and cross-Session resource references are unsupported.
2. Expose the tool only for AI SDK direct-tool mode with a valid Goal. Nested Code Mode waits, ACP, and plugin-hosted executors remain hidden and rejected.
3. Support only the single-task `terminal@1` condition. A task that has already finished follows the same registration → yield → settlement → delivery path; there is no separate inline-result path.
4. Preserve PR3's cancellation semantics: pause the Goal and suppress unadmitted delivery from the old generation, while the background process continues running.
5. Deliver `orphaned` as `source_unavailable`. If the task row independently disappears or its identity can no longer be verified, close the wait and pause the Goal with a diagnostic. Do not fabricate task failure or automatically start a diagnostic Turn.
## 1. Scope and Acceptance Rules
### 1.1 Included
- ShellRun adapter validation, authoritative reads, and releasable notification subscriptions.
- Background Bash provenance, same-Session model visibility, and current control-authority checks.
- Production Host composition, supported tool-catalog exposure, and access-controlled result-evidence reads.
- Minimal presentation of waiting, awaiting admission, and result processing, plus cancellation targeting a specific wait.
- Validation of pipes and managed background PTY, already-terminal tasks, failure, timeout, cancellation, lost management, restart, and resource cleanup.
- Necessary public protocol, client, decoder, and test updates.
### 1.2 Excluded
Independent automatic follow-ups for ordinary Sessions, multiple-wait aggregation, arbitrary observer code, GitHub/file adapters, takeover of external terminals or arbitrary remote jobs, process reattachment, and automatic command re-execution. PR4 does not redesign Goal budgets, message queues, Runtime recovery policy, or general permission modes.
### 1.3 Verifiable Rules
| ID | Rule |
| --- | --- |
| R1 Trusted source | A task ID, URI, model argument, PID, or storage lease cannot independently establish the model's authority to observe a task. |
| R2 Facts first | Only persisted ShellRun records may establish a condition result. Process-exit callbacks and UI updates are hints to reread them. |
| R3 One delivery | A wait admits at most one effective successor. Duplicate notifications, observations, or lost acknowledgements do not allocate another execution identity. |
| R4 Valid work progresses | Already-completed tasks, lost notifications, results preceding settlement, and temporarily busy Sessions can all continue through the existing paths. |
| R5 Control does not roll back | Late results cannot revive old-generation work after cancellation, pause, replacement, archival, or permission revocation. |
| R6 Bounded waiting | Absolute deadlines, observation-attempt limits, backoff, concurrency, and subscription limits remain effective; actual Turns are charged normally. |
| R7 Do not guess execution outcomes | `orphaned` does not prove that a process has died. Failure or timeout does not automatically authorize replay. |
| R8 Independent cleanup | Cancelling a wait releases observation resources only. Existing process-cleanup, sandbox, and Host-residency obligations must still complete. |
## 2. Existing Implementation and Required Additions
The links below are pinned to the inspected baseline. Suggested new files are listed in Section 13.
| Code | Existing behavior | PR4 work |
| --- | --- | --- |
| [event-wait-adapter.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/event-wait-adapter.ts) | Selects adapters by provider/resource/condition/version; supports snapshot, read, and subscribe | Implement the ShellRun adapter without source-specific core fields |
| [event-wait-manager.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/event-wait-manager.ts) | Durable observation progress, CAS, budgets, deadlines, notification coalescing, and polling | Reuse it; add only the necessary projection-invalidation hints |
| [goal-event-waits.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/goal-event-waits.ts) | Registration, post-yield settlement, result scanning, access revalidation, and continuations | Compose production sources; complete bounded delivery context and precise cancellation |
| [event-wait-tool.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime/src/event-wait-tool.ts) | `exclusive_step`, `direct_only`, `never_auto_retry`, and trusted in-memory receipts | Preserve control semantics; expose conditionally and improve the tool description |
| [shell-run.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/core/src/shell-run.ts) | Identity, provenance, visibility, state, and revision; no background flag | Verify background provenance from existing facts rather than inferring it from a missing field |
| [shell-run-manager.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime/src/shell-run-manager.ts) | Process management, terminal-state persistence, notifications, orphan recovery, and cleanup callbacks | Provide a narrow observation-only seam while preserving existing ownership |
| [execution-composition.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/execution-composition.ts) | The same Host already owns the ShellRun store, manager, runtime resources, and Goal coordinator; sources are currently optional | Compose the production source here using the existing instances |
| [goal.ts protocol](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/protocol/goal.ts) | The current Goal projection has no wait summary and uses strict decoding | Add the projection and cancellation protocol; update the compatibility epoch |
Two existing read surfaces cannot simply be reused: `readRuntimeResource` updates `observedAt` and may handle tasks that have lost their live handles; client surfaces such as `inspectResource` and `getSessionUpdate` are not equivalent to model-visibility checks. A background observer must not repeatedly invoke the model's `Read`, or bypass access checks by using a UI projection.
## 3. User Flow and Tool Request
In the typical flow, an existing Goal asks the agent to run tests and handle their results. The model calls `Bash` with `run_in_background=true`, receives a canonical `maka://runtime/background-tasks/<id>` reference, and calls `WaitForEvent` in the next exclusive tool step.
Keep the generic request structure already implemented, rather than reintroducing the `source` / `timeoutSeconds` fields from the discussion's early examples:
```json
{
"resource": {
"providerId": "shell_run",
"connectionId": null,
"resourceType": "background_task",
"resourceId": "maka://runtime/background-tasks/task_123"
},
"condition": {
"typeId": "terminal",
"version": 1,
"parameters": {}
},
"reason": "Wait for the full test run, inspect failures, and continue the current goal",
"timeoutMs": 3600000
}
```
The provider/resource identifiers above are this plan's proposed first production registration names. Establish shared constants and tests before fixing them during implementation. The request accepts no Session, Goal, generation, PID, command, authorization boolean, or arbitrary observation script. The Host binds control identity from the invocation context.
The tool description must state that registration ends the current Turn, condition satisfaction does not mean command success or Goal achievement, and wait expiry does not stop the background task. When no Goal is enabled, return a clear prerequisite error without creating one on the user's behalf. Require an explicit timeout; do not silently register waits for every background task.
## 4. ShellRun Identity, Provenance, and Authorization
### 4.1 Resource Addressing
- Use the canonical URI parser/formatter in `shell-run-contract.ts`. Reject other schemes/hosts, queries, fragments, additional path segments, and invalid IDs. Do not extract task IDs by taking a string suffix.
- Require `connectionId` to be `null`, `parameters` to be an empty object, and the condition to be exactly `terminal@1`.
- Address the resource by `(current Host root, wait.sessionId, shellRunId)`. The resource URI itself conveys no cross-Root authority.
- Allow registration for eligible tasks started by earlier Turns. The ShellRun's launch Turn need not equal the Turn registering the wait.
- `revision` numbers task-snapshot updates; it is not a process-execution generation. PIDs can be reused and `observedAt` can change; neither establishes identity.
### 4.2 Proving That the Resource Is Observable Background Bash
The first version adds neither a ShellRun background flag nor a whole-table migration. Verify it from existing immutable Runtime facts:
1. Read the task record for the current Session through the Host's composed store. Verify record identity and current visibility; always reject `visibility=user`.
2. Use the record's `sourceRunId`, `sourceTurnId`, and `sourceToolCallId` to identify the exact source invocation, tool call, dispatch, and final outcome.
3. The source facts must establish managed Bash execution, with a canonical outcome of `kind: shell_run` whose reference matches the target. Foreground Bash returns `kind: terminal` and is ineligible.
4. Verify call/dispatch/outcome linkage, the invocation's runtime/AI SDK route, and a Host tool composition supported by this PR. The current `assertUniqueToolNames` and `PluginToolService.resolveContributions` reject attempts to override Host-owned Bash; combine that constraint with the managed ShellRun's three source identifiers to establish builtin provenance. Existing T1 facts contain toolName/hash and related metadata, not a builtin-provider field that the reader can demand. Keep paths closed when name protection or source linkage cannot be established. Read archived large results through the existing canonical outcome/archive reader within bounds; reject if provenance cannot be proved.
5. Do not require `isError=false`. A background command that fails immediately, times out, or is cancelled may still have created a legitimate ShellRun. Registration after completion must work for these cases.
A default `visibility` of model is insufficient: some existing user-terminal launch paths can also use that default. The provenance checks above exclude those paths. When historical records lack provenance, reject new waits while preserving existing `Read` and terminal-view contracts. Do not scan the entire Session history to guess ownership.
To avoid rejecting valid provenance when a long Run exceeds the whole-Run read budget, add a narrow read-only provenance query. Its input is the Session/Run/tool-call identity; it returns the matching canonical call/dispatch/outcome and invocation linkage, with limits on record count and total bytes. Use the existing Runtime event authority and storage facade, with implementations in SQLite and the independent Memory provider. Do not create another authorization database or rely on the UI transcript. Reuse an existing query if it already satisfies the same bounded contract.
Code Mode can start background Bash, and nested tool events having `modelVisibility=hidden` does not mean that the resource is invisible. Provenance may accept nested Bash recorded through the normal ToolRuntime T1/T2 path. The wait itself must still execute later in supported direct-tool mode. Initially reject plugin paths that bypass the builtin without independently verifiable Bash facts.
### 4.3 Check Four Authorization Boundaries Separately
| Boundary | Required checks |
| --- | --- |
| Registration | Current Root execution, valid Goal/control lease, supported backend/mode, and the model's eligibility to observe this task |
| Observation | Session/Goal remains valid; the resource still belongs to this Session; visibility and provenance remain valid |
| Successor admission | Revalidate the Goal, user controls, resource access, and current budget; registration-time cache entries cannot grant permanent authority |
| Concrete actions | The successor follows existing tool permissions and confirmations; waking grants no additional authority to write files, push, merge, or replay commands |
If proofs are cached, cache immutable facts only. Current control and visibility must still be checked each time; a cache miss may affect cost only. A cancelled wait, an earlier `allowed` result, or a storage lease is not an authorization credential.
### 4.4 Missing Tasks and Storage Errors
- Task absent or provenance invalid at registration: reject without creating a wait or Goal gate.
- Confirmed missing local task row, inconsistent identity, or revoked visibility after registration: because there is currently no normal recovery protocol for independent task deletion, use the conservative policy of closing the wait and pausing the Goal with a bounded diagnostic. Do not start a model or guess the command outcome.
- Read timeout or temporary storage failure: remain `unknown`; do not translate an exception into “not found.” Shared storage or lease failures follow existing fail-closed/drain rules.
- `orphaned` row still present with valid access: authorization returns allowed, and the state read returns `source_unavailable`. Do not confuse an unknown process result with an absence of read authority, which would prevent delivery of the exceptional outcome.
Automatically delivering diagnostics after task-row loss would require a separately designed durable provenance binding. This PR does not allow the wait to prove ownership of its own original task.
## 5. Adapter Contract and Terminal-State Mapping
Implement `ShellRunEventWaitAdapter implements EventWaitSourceAdapter` with `reconciliation: 'snapshot'`. Its dependencies are limited to an authorized narrow state reader and a releasable change subscription. It receives no SQL handle, process launcher, model interface, or Root admission capability.
| Authoritative task fact | Adapter result | Meaning |
| --- | --- | --- |
| `starting` / `running` | `pending` | Continue observing without invoking a model |
| `completed` | `satisfied` | The terminal condition holds; the successor reads the exit code and result |
| `failed` | `satisfied` | The condition holds; command failure or an integrity fault needs analysis, without inferring overall Goal success or failure |
| `timed_out` | `satisfied` | The command's own timeout, distinct from the wait deadline |
| `cancelled` | `satisfied` | The underlying command was cancelled, distinct from the user cancelling the wait |
| `orphaned` | `source_unavailable` | No reliable live handle/result; explicitly report uncertainty without redispatching |
| Temporarily unreadable | `unknown` | Bounded diagnostics and backoff, without inventing a terminal state |
| Wait deadline reached | Manager writes `expired` | The adapter does not fabricate a ShellRun terminal state |
`failed` can include process-driver or output-persistence integrity failures. Do not always label it “test assertion failure”; retain the existing failure code/bounded explanation.
### 5.1 Stable Receipts and Evidence
Use a receipt such as `shell-run/terminal-v1/<taskId>/<digest>`. Compute the digest from normalized immutable task identity and terminal fields, for example Session/task/launch provenance/startedAt, status, completedAt, exitCode, and a failureMessage digest. Exclude `revision`, `updatedAt`, `observedAt`, output, and UI properties.
Model `Read` or output observation after termination may increase revision. These updates must not create another business terminal identity. Repeated reads of the same terminal state must return the same receipt/event key. Different waits may reference the same fact; delivery deduplication remains scoped to each wait's `deliveryKey` and PR3 joint admission.
Prefer the original canonical task reference in `evidenceRefs`, adding existing access-controlled log/archive references only when necessary. Do not duplicate complete stdout/stderr. The current bounds are 8 references, 1 KiB per reference, and the existing 48 KiB wait-record limit, including the combined bounds of every subsequent legal state.
The task reference resolves to the authorized current output projection; it does not pin a historical output version. The observed terminal identity is what remains immutable. Compute the digest in the Host's Node layer, keeping pure core contracts usable in browsers.
ShellRun terminal state and event-wait resolution may be persisted in two separate writes. The terminal fact is immutable and rereadable, so reconciliation repairs a crash after the first commit and before the second. No generic cross-source transaction is needed.
## 6. Subscription, Reads, and Resource Lifetime
```mermaid
sequenceDiagram
participant M as Model / Runtime
participant G as Goal wait bridge
participant O as Host observer
participant S as ShellRun manager / store
participant A as Root admission
M->>S: Background Bash; receive canonical ref
M->>G: WaitForEvent
G->>G: Verify provenance; atomically register wait + Goal gate
G-->>M: Trusted yield receipt
M->>G: Durable event_wait termination and normal settlement
O->>S: Read current durable facts after subscription is ready
S-->>O: pending
S->>S: Process exits; persist terminal state and output
S-->>O: change hint
O->>S: Reread and verify terminal state
O->>G: Persisted resolution discovered by bridge scan
G->>G: Freeze continuation after settlement completes
G->>A: Existing access, budget, busy handling, and Root admission
A-->>M: One effective successor with result references
```
The result may actually arrive before source-Turn settlement. That ordering stores the result but must not admit a successor early.
Implementation rules:
1. `subscribe()` returns after the listener is installed. The manager then performs its initial read, covering completion both before subscription and between subscription and reading.
2. Reuse the manager's existing update point after successful persistence, with Host-local listener fan-out filtered by Session/reference. Notifications call `changed()` only; they neither carry a “trusted result” nor start execution.
3. Reread durable records after a hint. Forged, duplicate, out-of-order, output-update, and stale notifications cannot directly resolve a wait.
4. Retain PR2 polling when subscription capacity is exhausted or notifications are lost. Restart orphan repair may not emit a notification, so the initial read and polling must determine the result independently.
5. Allow at most one in-flight read per wait and coalesce notifications. Both signal abort and `close()` remove listeners idempotently. The adapter introduces no unbounded timers, Promises, or process retention.
Listener exceptions are diagnostic only; they must not prevent existing ShellRun projections or other listeners from receiving hints.
6. Do not turn `onCompletion` into a wake callback. It retains its sandbox pinned-path cleanup and Host-residency release responsibilities, regardless of whether a wait was registered, cancelled, or delivered.
7. If terminal-state persistence fails, do not deliver success based on `onCompletion(false)`, `driverExit`, an in-memory snapshot, or an exit code. Cleanup follows existing manager behavior; observation retains uncertainty/failure.
Observation retains only bounded listeners/read requests, not a root execution or additional execution-drain resources. The active ShellRun's existing runtime-resource residency remains in effect; listeners after termination must not artificially prevent Host exit.
Retain PR2's current default policy: a 1-second minimum read interval, a 15-second base polling interval with jitter, 10,000 observations, a 7-day maximum lifetime, 8 globally in-flight reads, 1 per source, and 128 subscriptions. These are current settings, not an SLA. The seven-day ceiling does not guarantee that the observation budget lasts seven days. Notification-triggered reads must not bypass spacing or budgets.
## 7. Production Composition and Support Matrix
In `execution-composition.ts`, construct the production source with the already-open ShellRun store, the same manager, and the Runtime event authority, then supply it through the existing `HostGoalCoordinator.eventWaitSources`. Do not reopen a default Local store inside the adapter or create separate observers for CLI and Desktop. Preserve test injection, while keeping fake adapters out of production registration.
| Path | PR4 behavior |
| --- | --- |
| AI SDK + direct tools + valid Goal + local background Bash | Expose the tool; fully support registration and yield |
| Background Bash pipes / PTY | Support under the same access and provenance rules; the observer need not copy PTY screen output |
| Already-terminal task | Support through the same delivery path |
| No Goal | Registration unavailable; do not create a Goal automatically; filter the catalog by available context and reject again at runtime |
| Current Code Mode execution / nested invocation | Continue filtering and rejecting in Host/ToolRuntime; do not simulate yield with text |
| ACP / Codex app-server / plugin-hosted backend | Keep hidden and rejected until equivalent control-receipt and termination protocols exist |
| Task launched in Code Mode, followed by a valid switch to direct mode | Allow waiting on the original task if source T1/T2 is verifiable; do not require command restart |
| Foreground Bash, user terminal, inherited or cross-Session reference, arbitrary PID/external job | Registration unsupported; preserve existing read-permission boundaries |
Tool catalogs, system prompts/tool-use descriptions, Code Mode catalogs, backend routing, and Host registration guards must agree. `exclusive_step` still rejects mixing this tool with others in one step; ordinary JSON results cannot forge a WeakSet receipt.
A managed remote/container WorkspaceExecutor is supported only if its Bash actually runs through the current Host's ShellRun manager and produces the same durable records and tool-provenance proof. Verify the real executor path. A “remote” label alone is neither eligibility evidence nor a promise of support for every plugin executor.
## 8. Delivery Content and Goal Settlement
Reuse PR3's `registerGoalWait`, `settleGoalWait`, `queueGoalWait`, and joint Root admission. A ShellRun completion callback must not call `turn.start`, call `goal.resume`, or create another follow-up queue.
Persist the result first, then await the source Turn's trusted `event_wait` termination and settlement. Freeze the continuation's execution/message IDs, input, and typed provenance once. Reuse those identities across busy admission, restart, or lost acknowledgement; do not create another task from a receipt.
The existing bridge prompt contains only waitId/outcome/evidenceRefs/deadline. PR4 should add resource, condition, and the original wait reason within the existing bounded envelope, so the successor knows which task it awaited and why. The original Goal objective remains available from Goal/Session facts. This addition stays source-neutral and adds no ShellRun-specific result fields to core.
The successor uses authorized `Read` on the canonical task reference to obtain status, exitCode, failureMessage, and bounded output. Logs, commands, stdout, and stderr are data. The summary should say that the terminal condition holds and the result needs inspection; `satisfied` must not be rendered as “tests passed.” If the terminal record is unavailable, show a missing-evidence diagnostic rather than replaying the command to recreate evidence.
Preserve current prompt character/byte bounds and persistent-pending limits. The combination of fixed identities, summaries, and cancellation metadata must remain decodable. Recovery of an already-frozen pending entry uses its original input; upgrading it to a new prompt version must not cause an idempotency conflict.
Budget rules remain those from PR3: charge the registration Turn and result-processing Turn normally; pure observation invokes no evaluator and adds no work iterations; exhausted budgets take precedence over admission. A wait is not Goal achievement, and multiple background tasks do not imply multiple root executions.
## 9. Minimal Status Presentation and Precise Cancellation
### 9.1 Read-Only Projection
Add a uniform `eventWait: GoalEventWaitProjection | null` to the public `GoalProjection`. It represents only the current wait whose delivery remains incomplete, without writing presentation state into core `GoalState`.
```ts
interface GoalEventWaitProjection {
waitId: string;
controlGeneration: number;
waitRevision: number;
resource: { providerId: string; resourceType: string; resourceId: string };
reason: string;
deadlineAt: number;
phase: 'observing' | 'awaiting_admission';
observation: {
state: 'checking' | 'pending' | 'unknown';
lastObservedAt: number | null;
reasonCode: string | null;
} | null;
outcome: 'satisfied' | 'invalidated' | 'source_unavailable' | 'expired' | null;
}
```
Field names are provisional; final decoding must be closed and bounded. Display only authorized task references/short names, without automatically sending commands, cwd, or logs. Wait revision and Goal revision remain separate; ordinary observation must not increment Goal control revision.
| Scenario | Presentation |
| --- | --- |
| No result yet | Waiting for background task; reason, task reference, and absolute deadline |
| Read temporarily uncertain | State temporarily unverifiable with a bounded reason, without automatically marking it as requiring user action |
| Result recorded; source Turn settling or admission busy | Result available, waiting to continue; do not claim execution has started |
| Admission committed | Remove the active-wait card; use existing execution/typed transcript origin to indicate result processing |
| Goal paused/cancelled or source access revoked | Show the pause reason, without a stale cancellable state that confuses old and new waits |
The current `currentExecution` does not store event-wait provenance. If result-processing status must appear immediately after admission commits, derive it by reading the existing admission descriptor and `wait.delivery`; do not create another persistent run state for UI. Admission, Run start, Turn completion, and Goal achievement remain distinct facts.
When revoked access, a missing task row, or damaged provenance pauses the Goal, the bridge must map controlled reason codes to a useful pause reason instead of retaining only the current generic "observation was cancelled" message. After the wait card disappears, the Goal's pause reason and existing diagnostics must still explain the cause. Do not expose raw exceptions or information about another task.
### 9.2 Projection Propagation
- Rebuild initial subscriptions and reconnects from durable wait + Goal authority, without relying on complete delivery of in-memory broadcasts.
- Emit projection-invalidation hints for semantic changes such as initial registration, result/diagnostic changes, queuing, admission, and suppression; use the existing Session continuity canonical refresh.
- Do not notify users, refresh chat messages, or update global activity on every pending poll. `lastObservedAt` is the value in the latest projected snapshot; reconnect or explicit refresh can retrieve the newest value.
- If wait reads become asynchronous, revalidate Goal/wait binding inside the Session protection boundary. Adopt projection-cache updates monotonically by waitId + waitRevision; an older summary must not overwrite a newer wait.
- Desktop's `sameGoal` currently compares only Goal ID/revision and must include the wait summary, or observation changes will be lost. The field-by-field `toDesktopGoal` conversion must preserve the summary as well. Use a separate Goal view model through existing preload/GoalServices/feature ports.
- CLI's `runtime-host-session-channel.ts::sameGoalProjection` also compares only Goal ID/revision and must include the wait summary. Test observing → awaiting_admission notifications without a Goal revision change; updating presentation functions alone is insufficient.
- Integrate Renderer through the `features/goals` public entry and existing Goal UI. Add no state or bridge calls to the frozen AppShell family. CLI/TUI consumes the same Host projection and control operation rather than polling processes locally.
### 9.3 Cancellation Targeting a Specific Wait
Add a public operation, provisionally `goal.event-wait.cancel`, with this request:
```ts
{
sessionId: string;
goalId: string;
controlGeneration: number;
waitId: string;
expectedGoalRevision: number;
}
```
Follow existing client Session-access, Host admission, and response-error contracts. Reread and validate waitId, Goal ID, generation, and revision within the same serialized Session/Goal-mutation boundary, then reuse existing pause, generation renewal, and suppression of unadmitted delivery. Do not read the active wait, cross an await, and then invoke ordinary pause.
Retries always retain the original Goal/generation/waitId; they must not silently retarget the latest wait. Return distinguishable idempotent/conflict outcomes with the current projection when the wait is already cancelled, already admitted, or no longer the target. If admission committed first, preserve its facts; a stale card must not stop a newer execution. Already-admitted execution uses the existing stop entry point.
Recommended button explanation: **“Cancel the wait and pause the Goal. The background task keeps running.”** Stopping the underlying task still uses its existing resource-control operation; the two actions are not merged. Resuming a paused Goal continues in a new generation without reviving the old wait.
## 10. Recovery, Cleanup, and Failure Boundaries
Host resources recovery checks ShellRun first; the executions/domain phases then reconcile Run/Goal and wait state before scheduling is enabled. Do not change recovery into “restart the command when restoring a wait.”
| Interruption point or operation | Required behavior |
| --- | --- |
| Background command started but no wait registered successfully | Preserve existing ShellRun behavior; completion alone must not wake a model |
| Wait committed; yield acknowledgement/termination incomplete | Reuse PR3 Runtime recovery without blindly replaying tools; pause when existing policy requires it |
| ShellRun terminal state committed; wait not resolved | Reread the terminal fact and persist one resolution |
| ShellRun was active but no live handle exists after restart | Existing resources recovery marks it orphaned; the adapter delivers source uncertainty without killing by PID or replaying the command |
| Graceful Host shutdown already persisted process cancellation | Read the existing `cancelled` fact; do not indiscriminately rewrite it as post-crash `orphaned` |
| Resolution committed; no pending entry yet | Create one existing continuation once settlement and access checks allow it |
| Pending entry committed; not admitted yet | Reuse frozen identity, revalidate eligibility, and await Root admission |
| Admission committed; acknowledgement lost or no Run yet | Preserve PR3 drain/fence semantics; pause after restart under current recovery policy, without transparent replay when user resume is required |
| Crash after successor starts | Follow normal Runtime recovery and unknown-effect policy; do not redeliver the old wait |
| User pause, replacement, cancellation, archival, or deletion | End old-generation observation and unadmitted delivery; existing Session/resource lifecycle determines whether the ShellRun stops |
| Session copy/fork/import | Do not copy active waits; preserve existing Goal-link cleanup so source terminal facts cannot wake the copy |
| Consistent same-Root backup restoration | Preserve wait linkage; resource recovery decides whether the process state remains knowable |
| Host drain/close | First stop new observation/registration/delivery, release listeners and in-flight requests, then close stores/leases through the existing lifecycle |
Cancellation/replacement versus admission is decided by durable commit order, not by deleting history to hide execution that already occurred. SQLite reopen tests establish record reopening only. Full Host recovery must separately test resource recovery, Run termination, and scheduling order.
## 11. Compatibility and Security Boundaries
1. The adapter uses the existing provider-neutral wait schema. This plan requires neither new ShellRun business fields nor a second terminal-state table. If implementation does require persistent-field changes, separately document readers/writers, migration, old/future versions, and Memory parity. Do not smuggle fields into `parameters` to bypass semantic validation.
2. The Goal projection and cancellation operation are observable wire changes. Advance the compatibility epoch under the [protocol compatibility rules](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/protocol-compatible-changes/README.md). New optional fields are not eligible for a wire-invisible exemption.
3. Update the protocol barrel/operation set, client wrappers, Session snapshot decoding, worst-case size reservation, Desktop IPC/preload, CLI, fixtures, and compatibility checks. Do not overlook the current strict Goal-projection decoder.
4. The provenance query remains within existing execution-group/facade boundaries, with independent SQLite/Memory implementations. Do not silently open Local storage for a provider that lacks the capability.
5. Rereading terminal references still requires access checks. Arbitrary references, cross-Root or cross-Session resources, user-owned terminals, and inherited UI projections must not become bypasses.
6. Identity conflicts, forged provenance, and revoked access return bounded error codes, not the target terminal's command/output as error details. Logs and tool output cannot supply new instructions or action authority.
## 12. Validation Matrix
These checks are required during implementation. They were neither executed nor reported as passing while writing this plan. Prefer controlled clocks, barriers, and real fact signals to sleeps that guess intermediate state.
| Group | Scenario and synchronization point | Required assertions |
| --- | --- | --- |
| A Provenance/access | Invalid URI; cross-Session/root; user terminal; foreground; no Goal; missing/conflicting source events; similarly named plugin with forged result | Rejection creates no wait/gate side effects and leaks no sensitive data |
| A Valid controls | Same-Session background task from an earlier Turn; immediate failure; Code Mode creation with direct registration; long Run containing many unrelated events | Bounded provenance query can still prove eligibility and register; error results do not require isError=false |
| B State semantics | starting/running/completed/failed/timed_out/cancelled/orphaned | Exact mapping from Section 5; orphaned never replays, failed never means success |
| C Ordering | Completion before subscription, after subscription is ready but before initial read, during a read, or before source settlement | Eventually one effective delivery; no successor before settlement |
| C Deduplication | Duplicate/out-of-order notifications, output updates, Read increments revision, repeated scans, notifications after termination | Stable receipt; no duplicate resolve/admit; data revision is not execution identity |
| D No change | Real background task remains running while the clock advances repeatedly | Zero additional work-model/evaluator calls; bounded observation requests; source root execution released |
| D Normal progress | Another user Turn occupies the Session; the result arrives before that Turn releases it | The same pending/execution IDs can eventually be admitted; user input retains its existing priority |
| E Control races | Cancellation before/after resolve, queue, and admit; stale UI click after a new wait; pause/resume; Goal replacement/archival | An old target cannot affect a new wait; admitted facts remain; old unadmitted generations do not revive |
| E Two cancellations | Cancel the wait; Stop the underlying ShellRun | The former pauses the Goal while the process continues; the latter creates a task cancelled terminal fact deliverable to a still-valid wait |
| F Uncertainty/failure | Confirmed row loss, temporary read error, revoked access, terminal-persistence failure, lease invalidation | Distinguish missing/unknown/revoked; callbacks cannot impersonate durable terminal facts; failures do not leak access |
| F Resource limits | Notification storms, full subscription capacity, read timeout, close, exhausted observation budget, deadline | Coalesced hints, polling fallback, no unbounded tasks; no budget/deadline reset; wait expiry does not kill the task |
| G Process recovery | Kill and restart the Host at each durable boundary in Section 10 | Preserve original identities/results; follow existing pause/confirmation rules; no command redispatch |
| G Lifecycle | Session archive/delete/purge/copy, same-Root backup, old records, future versions | No dangling gate, no wake in a copy, no bypass of storage compatibility checks |
| H Cleanup | No wait, cancelled wait, delivered wait; process-start failure and terminal-write failure | Existing onCompletion responsibilities each complete once; sandbox/residency is neither released early nor retained forever |
| I Tool boundaries | Mixed step, Code Mode nesting, forged receipt, ACP/plugin backend | Catalog and invocation refusal agree; no state where registration succeeds but yield is unsupported |
| J Projection | Initial subscription, reconnect, lost hint, summary change without Goal-revision change, late old async read | Correct display, no stale-summary rollback, no notification storm from ordinary polling |
| J UI | waiting/unknown/awaiting admission/processing/paused and cancellation conflicts | Copy distinguishes terminal from successful; integration uses public feature entry points and includes visual evidence |
### 12.1 Minimal Usable-Flow Test
Use a real Host, Root admission, SQLite, builtin background Bash, and a fake model. The test process waits for an explicit release signal from the fixture. The fake model first calls Bash and then WaitForEvent. After confirming the source Turn's durable `event_wait` termination and settlement, advance observation and assert no additional work-model/evaluator calls. Release the process, await its real durable terminal fact, and confirm exactly one successor in the same Session that can obtain the exit code and output through Read.
Run success, nonzero exit, command timeout, Stop, and orphaned recovery separately. Use the same command without wait registration as a control, proving that not every background completion becomes an automatic model execution. Fake-provider tests cannot replace this real-source validation.
Recovery tests must also use an independent durable marker/start counter to prove that the original command did not execute again. Counting successors or model calls alone cannot rule out redispatch of the underlying command.
### 12.2 Diagnostics and Before/After Comparison
Use existing diagnostic channels to record waitId, Session, Goal generation, source task ID, outcome category, and delivery identity across registration, resolution, queue, admission, suppression, and recovery. Retain bounded reason codes only; do not add original commands, logs, or credentials to new diagnostics. Metrics explain existing facts and do not create another scheduling authority.
Using the same long-running test and a repeatable release point, compare existing model queries/Goal timer checks with structured waiting. Record work-model and evaluator call counts, tokens, Host state-read counts, latency from terminal persistence to admission, duplicate/missing deliveries, and user interventions. Report busy admission, storage failure, and normal operation separately. PR4 requires no additional model calls when nothing changes and no duplicate effective admissions; it prescribes neither a savings percentage nor a fixed admission deadline.
### 12.3 Build and Checks
First verify the toolchain in the root `package.json`, install dependencies only at the root, and build in dependency order to avoid running tests against stale dist output. Run focused tests first, then the checks required by the contribution guide:
```sh
npm --workspace @maka/core run build
npm --workspace @maka/storage run build
npm --workspace @maka/runtime run build
npm --workspace @maka/runtime-host run build
node --test packages/runtime/dist/__tests__/shell-run-manager.test.js
node --test packages/runtime-host/dist/__tests__/event-wait-manager.test.js
node --test packages/storage/dist/__tests__/event-wait-bridge.test.js
# Then run this PR's adapter, provenance, protocol, and real-Host flow tests.
npm run lint
npm run format:check
npm run build
npm run typecheck
npx knip --workspace apps/desktop
npx knip --workspace packages/ui
npm run check:renderer-architecture
git diff --check
```
Check public protocol changes using the repository's actual invocation of `scripts/protocol-epoch-check.mjs` and CI. When adding Electron E2E coverage, run budget checks under the applicable AGENTS instructions. Prefer unit/integration + Storybook for UI state; add Electron tests only for actual Electron/IPC boundaries. Run root `npm test` when necessary, and report the actual scope rather than calling focused validation a full-repository pass.
## 13. Implementation Sequence and Files
| Order | Scope | Independently verifiable result |
| --- | --- | --- |
| 1 | Fix source eligibility, terminal mapping, missing-task policy, and support matrix; add contract counterexamples | Positive/negative source cases and all state rules are testable |
| 2 | Narrow Runtime event provenance query with facade/SQLite/Memory; pure ShellRun observation reader | Proof requires neither UI, whole-history scans, nor model Read |
| 3 | Proposed `server/shell-run-event-wait-adapter.ts`, `server/shell-run-event-wait-access.ts`, and a controlled listener hub | subscribe → read → durable result, without model/admission calls |
| 4 | `execution-composition.ts`, `goal-event-waits.ts`, tool catalogs/descriptions | Production source composition; real background Bash → yield → one successor |
| 5 | Goal projection, precise cancellation, continuity, clients, and Desktop/CLI | Users can understand and cancel a specific wait without affecting a new Goal/wait |
| 6 | Failure/recovery/cleanup/compatibility tests and final full-diff review | Safety and progress evidence accompany the first exposed flow |
Primary code areas:
- `packages/core/src/shell-run.ts`, `event-wait.ts`: prefer existing contracts; place necessary shared read-only types behind declared exports.
- `packages/storage/src/`: Runtime event read contracts, SQLite/Memory implementations, and the `execution-stores` facade; the existing event-wait bridge remains authoritative.
- `packages/runtime/src/shell-run-manager.ts`, `shell-run-contract.ts`: narrow observation/subscription interfaces, preserving terminal writes, cleanup, and model Read semantics.
- `packages/runtime/src/event-wait-tool.ts`, `ai-sdk-turn.ts`, `tool-runtime.ts`, and Code Mode catalog: tool exposure and control-boundary regressions, without reimplementing yield.
- `packages/runtime-host/src/server/`: new adapter/access modules, composition, Goal bridge/coordinator/projection, and Session continuity.
- `packages/runtime-host/src/protocol/`, client: bounded projection, cancellation operation, strict decoding, epoch, and operation ownership.
- `apps/desktop/src/main/runtime-host-session-observer.ts`, `runtime-host-session-domains-ipc-main.ts`, preload/shared, and `renderer/features/goals/`: preserve the complete projection and reuse public feature entry points.
- Runtime Host session driver and Goal control/presentation in `packages/cli/src/`: consume the same protocol without introducing execution authority.
- Corresponding package `__tests__`, Desktop Storybook/necessary E2E, and updates to the runtime README's internal-only description.
Cross-package interfaces use declared `package.json` exports, not another package's internal src paths. New source files retain ASF headers. Update stable implementation contracts in the relevant README/architecture documents. Keep the implementation plan and progress in the issue/PR, and the local draft uncommitted under `docs/local/`.
## 14. Merge Criteria and Handoff
- [ ] PR1–PR3 persistence, Goal-gate, settlement, and joint-admission regressions pass; no second wake queue exists.
- [ ] A real ShellRun flow demonstrates R1–R8, including valid progress and negative controls.
- [ ] Every supported path has evidence through a real entry point; unsupported paths are controlled in both catalogs and the server.
- [ ] Minimal presentation, precise cancellation, reconnect projection, and public protocol compatibility are complete.
- [ ] orphaned, missing, unknown storage errors, and cancellation semantics follow this plan, with applicable tradeoffs retained.
- [ ] Host process recovery and storage-only reopen evidence are reported separately; arbitrary external effects are not claimed to be exactly-once.
- [ ] Report baseline/final HEAD, execution conditions, actual commands, uncovered cases, visual evidence, and independent-review results.
Suggested PR title: `feat(runtime-host): enable ShellRun event waits`.
Completion requires a usable flow and verified boundaries above, not merely an adapter that can return satisfied. If implementation cannot establish reliable background provenance, revalidate cancellation at the correct boundary, or produce trusted yield on an executor, keep that path closed and record the gap. Do not expose it by weakening permissions or simulating control results.
*Drafted with assistance from OpenAI Codex, based on the online plans and pinned source snapshot cited above. This document describes future implementation and acceptance work; it is not a test report for a completed implementation.*
<details>
<summary>中文(点击展开)</summary>
# PR4 详细技术方案:ShellRun 终态适配与首个可用等待闭环
## 0. 结论与依据
PR4 在 PR1 的等待存储、PR2 的 Host 观测器和 PR3 的 Goal 交付桥上,接入受管后台 Bash 的 ShellRun 终态,并开放受支持的 `WaitForEvent`、最小状态展示和精确取消入口。交付结果是:模型启动后台测试后登记等待,当前 Turn 结束;Host 在没有新结果时不调用工作模型或 Goal evaluator;测试结束后,经现有 Root admission 将结果交给原 Goal。
本方案为待评审的实施提案,不表示所列接口已实现或所有产品取舍已经批准。本次只编写方案,不修改产品代码、不向 GitHub 发布。
### 0.1 精确来源与代码基线
资料核对日期:2026-10-20。已读取在线 discussion、跟踪 issue 及其全部三条方案评论,以下链接指向具体来源:
| 来源 | 在本方案中的作用 |
| --- | --- |
| [Discussion #5920](https://github.com/apache/maka/discussions/5920) 的 PR4 段落 | 首个 ShellRun 用户闭环、终态核验、最小展示、取消及不自动重跑边界 |
| [跟踪 issue #5926](https://github.com/apache/maka/issues/5926) | Goal 为首个消费者,七个 PR 的交付范围 |
| [PR1 方案评论](https://github.com/apache/maka/issues/5926#issuecomment-5951631569) | provider-neutral 记录、CAS、租约、清理与复制边界 |
| [PR2 方案评论](https://github.com/apache/maka/issues/5926#issuecomment-6121096199) | snapshot reconciliation、通知提示、期限、观测预算、错误隔离 |
| [PR3 方案评论](https://github.com/apache/maka/issues/5926#issuecomment-6229672833) | 可信登记、真正 yield、Goal gate、冻结 continuation、联合接纳与恢复 |
源码核对基线为本地 `main`:`b0ed5707ff3dc645c66253ed93e50936e3f9d45d`,其最新提交是 PR3 的 [#6147](https://github.com/apache/maka/pull/6147)。这是明确的本地快照,不声称等于实施时最新远端 main。实施前须重新核对差异。
前序评论是设计依据,实际已合并接口用于确定接线方式。例如 PR1 原方案的记录上限为 32 KiB,当前 `EVENT_WAIT_LIMITS.recordBytes` 已是 48 KiB;PR3 已接入 `delivery` 和联合桥接操作,不应再次实现另一份交付状态机。
### 0.2 本方案新增的明确选择
这些是为使 PR4 可直接实施而提出的收窄,不冒充讨论已经逐项裁决:
1. 只登记同一 State Root、当前 Session 本地拥有、向模型暴露了正式后台 Bash 结果的 ShellRun;不支持继承、复制或跨 Session 的资源引用。
2. 只开放 AI SDK 的直接工具模式和有效 Goal。Code Mode 嵌套等待、ACP 及插件托管执行器继续隐藏并拒绝。
3. 只提供单任务 `terminal@1` 条件。已完成任务也走登记 → yield → settlement → 交付,不添加即时返回结果的第二条路径。
4. 取消等待采用现有 PR3 语义:暂停 Goal、撤销旧代未接纳交付,后台进程继续运行。
5. `orphaned` 交付 `source_unavailable`;独立丢失任务行或身份无法核实时关闭等待并暂停 Goal,提供诊断,不把它伪造为任务失败或自动启动诊断 Turn。
## 1. 范围与验收规则
### 1.1 包含
- ShellRun adapter 的验证、事实读取和可释放通知订阅。
- 后台 Bash 来源证明、同 Session 模型可见性和当前控制权限检查。
- 生产 Host 装配、受支持工具目录开放、受控结果证据读取。
- 等待、等待接纳、处理结果的最小展示,以及针对特定等待的取消。
- pipes 与受管后台 PTY、已终态、失败、超时、取消、失联、重启、资源清理等验证。
- 必需的公开协议、客户端、解码器和测试更新。
### 1.2 不包含
普通 Session 的独立自动 follow-up、多等待聚合、任意 observer 代码、GitHub/文件 adapter、外部终端和任意远程 job 的接管、进程重附着、自动重新执行命令。PR4 不重新设计 Goal 预算、消息队列、Runtime 恢复策略或通用权限模式。
### 1.3 可验证规则
| 编号 | 规则 |
| --- | --- |
| R1 来源可信 | task ID、URI、模型参数、PID 和存储 lease 均不能独立证明模型有权观察任务。 |
| R2 事实先行 | 只有持久 ShellRun 记录可形成条件结果;进程退出回调和 UI 更新只能提示重读。 |
| R3 一次交付 | 同一等待至多一个有效后继接纳;重复通知、观测、确认丢失不新建执行身份。 |
| R4 合法进展 | 登记前已完成、通知丢失、结果先于 settlement、Session 暂忙都能经已有路径继续。 |
| R5 控制不回退 | 取消、暂停、替换、归档或权限撤销后,迟到结果不能复活旧代工作。 |
| R6 等待有界 | 绝对 deadline、观测次数、退避、并发和订阅限制继续有效;真实 Turn 正常计量。 |
| R7 不猜执行结果 | `orphaned` 不证明进程死亡;失败或超时不自动授权重跑。 |
| R8 清理独立 | 取消等待只释放观察资源;原有进程清理、sandbox 和 Host residency 义务必须完成。 |
## 2. 已有实现与本次需要补齐的接口
下表链接固定到核对基线;新文件名在第 13 节列为建议。
| 代码 | 已有事实 | PR4 工作 |
| --- | --- | --- |
| [event-wait-adapter.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/event-wait-adapter.ts) | 按 provider/resource/condition/version 选 adapter;支持 snapshot、read、subscribe | 实现 ShellRun adapter,不增加来源专属核心字段 |
| [event-wait-manager.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/event-wait-manager.ts) | 持久观测进度、CAS、预算、期限、通知合并和轮询 | 复用;仅增加必要的投影失效提示 |
| [goal-event-waits.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/goal-event-waits.ts) | 登记、yield 后结算、结果扫描、权限复验与 continuation | 生产 sources 装配;补齐有界交付上下文和精确取消 |
| [event-wait-tool.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime/src/event-wait-tool.ts) | `exclusive_step`、`direct_only`、`never_auto_retry`、可信内存 receipt | 保留控制语义,条件化开放和补充工具说明 |
| [shell-run.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/core/src/shell-run.ts) | 身份、来源、visibility、状态和 revision;无后台标志 | 用现有事实核验后台来源,不凭字段缺省推断 |
| [shell-run-manager.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime/src/shell-run-manager.ts) | 进程管理、终态持久化、通知、恢复 orphan、清理回调 | 提供窄的纯观察接缝,保留已有所有权 |
| [execution-composition.ts](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/server/execution-composition.ts) | 同一 Host 已有 ShellRun store、manager、runtime resources 和 Goal coordinator;sources 目前可选 | 在此固定装配生产来源,复用原实例 |
| [goal.ts 协议](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/src/protocol/goal.ts) | 现有 Goal 投影未包含等待摘要,严格解码 | 新增投影与取消协议,更新 compatibility epoch |
特别注意两个不能直接复用的读入口:`readRuntimeResource` 会更新 `observedAt`,并可能处理失去 live handle 的任务;客户端 `inspectResource`、`getSessionUpdate` 等又不等价于模型可见性校验。后台 observer 不应每次调用模型的 `Read`,也不能绕过权限改用 UI 投影。
## 3. 用户路径与工具请求
典型路径:已有 Goal 要求运行测试并处理结果;模型调用 `Bash`,设置 `run_in_background=true`,取得规范 `maka://runtime/background-tasks/<id>` 引用;下一独占工具 step 调用 `WaitForEvent`。
建议沿用已实现的通用请求结构,而不是重新引入 discussion 早期示例中的 `source` / `timeoutSeconds`:
```json
{
"resource": {
"providerId": "shell_run",
"connectionId": null,
"resourceType": "background_task",
"resourceId": "maka://runtime/background-tasks/task_123"
},
"condition": {
"typeId": "terminal",
"version": 1,
"parameters": {}
},
"reason": "等待完整测试结束后检查失败项并继续当前目标",
"timeoutMs": 3600000
}
```
provider/resource 标识是本方案建议的首个正式注册名,实施时统一常量与测试后固定。请求不接受 Session、Goal、generation、PID、命令、授权布尔值或任意观察脚本。Host 从调用上下文绑定控制身份。
工具文案必须说明:登记会结束当前 Turn;条件成立不代表命令成功或 Goal 完成;等待超时不停止后台任务。未启用 Goal 时返回明确的前置条件错误,不替用户创建 Goal。请求明确携带超时,不暗中把所有后台任务登记为等待。
## 4. ShellRun 身份、来源与授权
### 4.1 资源定位
- 使用既有 `shell-run-contract.ts` 的规范 URI parser/formatter。拒绝其他 scheme/host、query、fragment、额外路径和非法 ID;不能靠字符串尾部提取 task ID。
- `connectionId` 必须为 `null`,`parameters` 必须为空对象,condition 必须精确为 `terminal@1`。
- 使用 `(当前 Host root, wait.sessionId, shellRunId)` 定位;resource URI 自身不携带跨 Root 权限。
- 登记允许等待较早 Turn 启动的合法任务,不要求 ShellRun 的启动 Turn 等于登记等待的 Turn。
- `revision` 是任务快照更新序号,不是进程执行代际。PID 可能复用,`observedAt` 可能更新,都不作为身份依据。
### 4.2 证明它是可观察的后台 Bash
首版不新增 ShellRun 后台标记或迁移整张表。通过已有不可变 Runtime 事实核验:
1. 从 Host 已装配的 store 按当前 Session 读取任务记录,确认记录身份、当前可见性;`visibility=user` 一律拒绝。
2. 使用记录中的 `sourceRunId`、`sourceTurnId`、`sourceToolCallId` 精确关联源 invocation、工具 call、dispatch 和最终 outcome。
3. 源事实必须证明受管 Bash 执行,且规范 outcome 为 `kind: shell_run`、ref 与目标一致。前台 Bash 返回 `kind: terminal`,不符合本条件。
4. 核验 call/dispatch/outcome 关联、invocation 的 runtime/AI SDK 路由,以及本 PR 支持的 Host 工具装配。当前 `assertUniqueToolNames` 与 `PluginToolService.resolveContributions` 会拒绝覆盖 Host-owned Bash;结合受管 ShellRun 的来源三元组,证明该操作来自 builtin。既有 T1 只有 toolName/hash 等信息,不能要求读取不存在的 builtin provider 字段。无法证明名称不可覆盖或来源关联的执行路径不开放。对归档大结果通过既有 canonical outcome/archive reader 有界读取;无法证明则拒绝。
5. 不要求 `isError=false`:立即失败、超时或被取消的后台命令仍可能已产生合法 ShellRun,必须支持“登记前已结束”。
`visibility` 缺省为 model 并不足够:现有用户终端的一些启动路径也可能使用缺省值。以上来源证明用于排除它们。历史记录缺少来源信息时,不开放新等待,既有 `Read`/终端查看能力保持原契约;不能全量扫描 Session 历史来猜测归属。
为避免长 Run 超过整 Run 读取预算而误拒合法来源,增加一个窄的只读来源证明查询:输入 Session/Run/tool-call 身份,返回匹配的 canonical call/dispatch/outcome 及 invocation 关联;限制结果条数和总字节数。使用现有 Runtime event authority 和存储 facade,在 SQLite 与独立 Memory provider 实现;不新建授权数据库,不用 UI transcript 作为依据。若已有查询可满足相同有界契约则直接复用。
Code Mode 可以启动后台 Bash,但嵌套工具事件的 `modelVisibility=hidden` 不等于资源不可见。来源证明可接受经正常 ToolRuntime T1/T2 写入的嵌套 Bash;实际等待仍必须在后续受支持的直接工具模式执行。插件绕行 builtin 而没有独立可核验 Bash 事实的路径首版拒绝。
### 4.3 分开检查四层权限
| 边界 | 必须核验 |
| --- | --- |
| 登记 | 当前 Root 执行、有效 Goal/control lease、支持的 backend/mode、模型对该任务的观察资格 |
| 观测 | 当前 Session/Goal 仍有效、资源仍属本 Session、可见性及来源证明仍成立 |
| 后继接纳 | 重新核验 Goal、用户控制、资源访问和当前预算;不能用登记时的缓存永久授权 |
| 具体动作 | 后继沿现有工具权限/确认处理,不因唤醒自动增加写文件、push、merge 或重跑权限 |
证明缓存若存在,只缓存不可变事实;每次仍检查当前控制和可见性,缓存缺失只影响成本。不得把被取消的 wait、过去的 `allowed` 或某个 storage lease 当作权限凭证。
### 4.4 任务丢失与存储错误
- 登记时任务不存在或无合法来源证明:拒绝,无 wait/Goal gate 副作用。
- 已登记后确认本地任务行缺失、身份矛盾或可见性撤销:当前实现没有正常的单任务删除恢复协议,采用关闭等待并暂停 Goal 的保守政策,保留有界诊断;不启动模型,也不猜命令结局。
- 读取超时、暂时存储故障:保持 `unknown`,不把异常转为“不存在”。共享存储或租约故障遵守既有 fail-closed/drain 规则。
- `orphaned` 行仍在且权限有效:授权结果是 allowed,状态读取返回 `source_unavailable`。不能把“进程结果未知”错误地当作“无权读取”,否则异常结果无法交付。
如果以后要求任务行丢失时仍自动交付诊断,需要另行设计持久来源绑定;本 PR 不让 wait 自证原任务归属。
## 5. 适配器契约与终态映射
实现 `ShellRunEventWaitAdapter implements EventWaitSourceAdapter`,选择 `reconciliation: 'snapshot'`。依赖仅包括已授权的窄状态 reader 和可释放 change subscription;不拿 SQL handle、进程启动器、模型接口或 Root admission。
| 可靠任务事实 | adapter 结果 | 后续含义 |
| --- | --- | --- |
| `starting` / `running` | `pending` | 继续观察,不调用模型 |
| `completed` | `satisfied` | terminal 条件成立;后继读取退出码和结果 |
| `failed` | `satisfied` | 条件成立,命令失败或完整性故障需要分析;不推断整体 Goal 成败 |
| `timed_out` | `satisfied` | 底层命令自己的 timeout;区别于等待 deadline |
| `cancelled` | `satisfied` | 底层命令已取消;区别于用户取消等待 |
| `orphaned` | `source_unavailable` | 无可靠 live handle/结果,明确表达未知,不重新 dispatch |
| 暂时无法读取 | `unknown` | 有界诊断与退避,不制造终态 |
| 等待 deadline 到达 | manager 写 `expired` | 不是 adapter 伪造的 ShellRun 终态 |
`failed` 可能包含进程驱动/输出持久化完整性问题,不能一律显示为“测试断言失败”;保留现有 failure code/有界说明。
### 5.1 稳定 receipt 与证据
建议 receipt 为 `shell-run/terminal-v1/<taskId>/<digest>`;digest 对规范化的不可变任务身份及终态字段计算,例如 Session/任务/启动来源/startedAt、status、completedAt、exitCode、failureMessage 摘要。排除 `revision`、`updatedAt`、`observedAt`、输出和 UI 属性。
终态后模型 Read 或输出观察可能增加 revision;它们不能产生新的业务终态 identity。重复读取同一终态必须得到相同 receipt/event key。不同 wait 可引用同一事实,交付去重仍由各自的 `deliveryKey` 与 PR3 联合接纳完成。
`evidenceRefs` 首选原规范 task ref,必要时增加已存在且受权限控制的日志/归档引用;不创建另一份完整 stdout/stderr。当前上限为 8 个引用、每个 1 KiB,整个 wait 仍遵守现有 48 KiB 校验和所有后续合法状态的组合上限。
task ref 指向授权后的当前输出投影,不承诺固定某个历史输出版本;不可变的是所观察的终态身份。摘要计算放在 Host 的 Node 层,纯 core 契约继续可在浏览器运行。
ShellRun 状态持久化与 event-wait resolution 分两次写入即可:终态不可变且可重读,前者提交后、后者提交前崩溃由 reconciliation 补齐。无需为此引入跨来源通用事务。
## 6. 订阅、读取与资源寿命
```mermaid
sequenceDiagram
participant M as 模型 / Runtime
participant G as Goal 等待桥
participant O as Host 观测器
participant S as ShellRun manager / store
participant A as Root admission
M->>S: 后台 Bash,获得规范 ref
M->>G: WaitForEvent
G->>G: 核验来源;原子登记 wait + Goal gate
G-->>M: 可信 yield receipt
M->>G: 持久 event_wait 终止并正常结算
O->>S: 订阅 ready 后读取当前持久事实
S-->>O: pending
S->>S: 进程结束,持久终态与输出
S-->>O: change hint
O->>S: 重读并核验终态
O->>G: 已持久 resolution,由桥接扫描发现
G->>G: settlement 完成后冻结 continuation
G->>A: 经现有权限、预算、busy 与 Root 接纳
A-->>M: 一个有效后继,携带结果引用
```
图中实际允许结果先于源 Turn settlement;这种顺序只保存结果,不能提前接纳。
实现规则:
1. `subscribe()` 在监听器已安装后返回;manager 再做首次读取,覆盖订阅前及订阅读取间发生的完成。
2. 复用 manager 已有持久成功后的更新出口,在 Host 内提供按 Session/ref 过滤的 listener fan-out。通知仅调用 `changed()`,不携带“可信结果”或启动执行。
3. 接收 hint 后依旧读取持久记录;伪造、重复、乱序、输出更新和过时通知均不能直接 resolve。
4. 订阅容量已满或通知丢失时继续用 PR2 轮询;重启的 orphan 修复未必发通知,首次读和轮询必须可独立完成判断。
5. 同 wait 最多一个 read 在途,通知合并;signal abort 与 `close()` 都幂等移除监听器。adapter 不自行增加无限 timer、Promise 或进程持有。
listener 的异常只进入诊断,不得阻止既有 ShellRun 投影或其他监听器收到提示。
6. 不改 `onCompletion` 成唤醒回调。它仍负责既有 sandbox pinned-path 清理和 Host residency 释放;等待有没有注册、被取消或交付都不影响这些义务。
7. 持久终态写入失败时不得依据 `onCompletion(false)`、`driverExit`、内存快照或退出码成功交付。清理继续按既有 manager 行为处理,观测保留未知/故障。
观察期仅持有有界 listener/读请求,不持有 root execution 或额外 execution drain 资源。活跃 ShellRun 本身已有的 runtime-resource residency 正常保留;终态后的 listener 不能人为阻止 Host 退出。
继续使用 PR2 默认策略:1 秒最小读取间隔、15 秒轮询基准和抖动、10,000 次观测、7 天最大寿命、8 个全局在途读取、每来源 1 个、128 个订阅。它们是现值而非 SLA;七天上限不保证观测预算能持续七天。PR4 不为通知触发读取绕过间隔或预算。
## 7. 生产装配与支持矩阵
在 `execution-composition.ts` 中使用已经打开的 ShellRun store、同一 manager 和 Runtime event authority 构造生产来源,再传给已有 `HostGoalCoordinator.eventWaitSources`。不得在 adapter 内重新打开默认 Local store,或为 CLI/Desktop 各建一个 observer。保留测试注入点,但生产不注册 fake adapter。
| 路径 | PR4 行为 |
| --- | --- |
| AI SDK + direct tools + 有效 Goal + 本地后台 Bash | 展示工具,登记与 yield 完整支持 |
| 后台 Bash pipes / PTY | 在同样权限与来源证明下支持;PTY 无需复制屏幕输出给 observer |
| 任务已终态 | 支持,仍走一次统一交付路径 |
| 没有 Goal | 不可登记,不自动创建 Goal;工具目录按可用上下文过滤,运行时再次拒绝 |
| Code Mode 当前执行/嵌套调用 | 继续过滤并在 Host/ToolRuntime 拒绝,不能用文字模拟 yield |
| ACP / Codex app-server / 插件托管 backend | 未实现同等控制 receipt/终止协议前继续隐藏并拒绝 |
| Code Mode 启动任务,随后已合法切换 direct 模式 | 若来源 T1/T2 可验证,可等待原任务;不要求重启命令 |
| 前台 Bash、用户终端、继承或跨 Session ref、任意 PID/外部 job | 不支持登记;不扩大原读取权限 |
工具目录、system prompt/工具使用说明、Code Mode catalog、backend routing 与 Host registration guard 必须一致。工具在同一 step 与其他工具混用仍由 `exclusive_step` 拒绝;普通结果 JSON 不能伪造 WeakSet receipt。
受管远程/container WorkspaceExecutor 只有在其 Bash 实际经当前 Host 的 ShellRun manager、形成同样持久记录和工具来源证明时才属于支持范围。按真实 executor 路径验证,不能用“远程”名称直接放行,也不能承诺所有插件执行器。
## 8. 交付内容与 Goal 结算
继续复用 PR3 的 `registerGoalWait`、`settleGoalWait`、`queueGoalWait` 和联合 Root admission。不得在 ShellRun 完成回调里调用 `turn.start`、`goal.resume` 或新建另一条 follow-up 队列。
结果先持久化,再等待源 Turn 的可信 `event_wait` 终止和 settlement;continuation 一次冻结 execution/message IDs、输入和 typed provenance。busy、重启或确认丢失时复用这些身份,不根据 receipt 重新创建任务。
现有桥接 prompt 只有 waitId/outcome/evidenceRefs/deadline。PR4 应在现有有界 envelope 中补足 resource、condition 和原等待 reason,让后继知道是哪一项任务及为何等待。原 Goal 目标仍来自 Goal/会话事实。此补充保持来源无关,不向 core 增加 ShellRun 专属结果字段。
后继通过已授权的 `Read` 读取规范 task ref,获得 status、exitCode、failureMessage 和有界输出;日志、命令及 stdout/stderr 均是数据。摘要应明确“terminal 已成立,请核查结果”,不能把 `satisfied` 渲染成“测试通过”。终态记录不可用时显示缺失诊断,不重跑来补证据。
保持当前 prompt 字符/字节限制及持久 pending 上限;固定身份、摘要、取消信息组合后仍可解码。已冻结的 pending 恢复时沿用旧输入,不能升级为新的 prompt 版本造成幂等冲突。
预算规则沿用 PR3:登记 Turn 和处理结果 Turn 正常计量;纯观测不调用 evaluator,不增加工作 iterations;预算耗尽优先于接纳。等待不是 Goal achieved,多个后台任务也不意味着多个 root execution。
## 9. 最小状态展示与精确取消
### 9.1 只读投影
建议在公开 `GoalProjection` 增加统一的 `eventWait: GoalEventWaitProjection | null`。它仅表示当前尚未完成交付的等待,不向核心 `GoalState` 写入展示状态。
```ts
interface GoalEventWaitProjection {
waitId: string;
controlGeneration: number;
waitRevision: number;
resource: { providerId: string; resourceType: string; resourceId: string };
reason: string;
deadlineAt: number;
phase: 'observing' | 'awaiting_admission';
observation: {
state: 'checking' | 'pending' | 'unknown';
lastObservedAt: number | null;
reasonCode: string | null;
} | null;
outcome: 'satisfied' | 'invalidated' | 'source_unavailable' | 'expired' | null;
}
```
字段名为建议;最终解码须封闭、有界。资源只显示已授权的 task ref/短名称,不自动下发 command、cwd 或日志。wait revision 与 Goal revision 分开;普通观测不能递增 Goal 控制 revision。
| 场景 | 展示 |
| --- | --- |
| 尚无结果 | 等待后台任务;原因、任务引用、绝对截止时间 |
| 读取暂时未知 | 状态暂不可确认及有界原因,不直接标成需要用户处理 |
| 结果已记录,源 Turn 收尾或接纳繁忙 | 结果已到,等待继续;不假称正在运行 |
| admission 已提交 | 移除 active wait 卡;用已有执行/typed transcript origin 表示正在处理等待结果 |
| Goal 暂停/取消/来源权限失效 | 展示暂停原因;不保留可取消的新旧等待混淆状态 |
当前 `currentExecution` 并不保存 event-wait provenance。需要 admission 刚提交就展示处理结果时,可只读关联既有 admission descriptor 与 `wait.delivery`;不为 UI 新增一份持久运行状态。接纳、Run 启动、Turn 完成、Goal 达成仍须分别表达。
权限撤销、任务行缺失或来源证明损坏导致暂停时,桥接层将受控原因码映射为可展示的暂停原因,不能只留下当前通用的“观察已取消”。等待卡移除后仍可通过 Goal 暂停原因及既有诊断查明原因;不要直接展示原始异常或别的任务信息。
### 9.2 投影传播
- 首次订阅与重连从 durable wait + Goal authority 重建,不依赖内存广播完整送达。
- 对首次登记、结果/诊断变化、排队、接纳、抑制等语义变化,发送投影失效 hint,走既有 Session continuity canonical refresh。
- 不为每次 pending 轮询弹通知、刷新聊天消息或更新全局活跃状态。`lastObservedAt` 是最近一次已投影快照值,重连/显式刷新可取最新值。
- 若增加异步 wait 读取,在 Session 保护范围内重验 Goal/wait 绑定;投影缓存按 waitId + waitRevision 单调采用,不能用旧摘要覆盖新等待。
- Desktop 的 `sameGoal` 目前仅比较 Goal ID/revision,需要同时检查等待摘要,否则观察变化被吞;`toDesktopGoal` 逐字段转换也必须保留摘要。使用独立 Goal view model,经现有 preload/GoalServices/feature ports 传播。
- CLI 的 `runtime-host-session-channel.ts::sameGoalProjection` 也只比较 Goal ID/revision,必须同步比较等待摘要;覆盖 Goal revision 不变时 observing → awaiting_admission 的通知,不能只改展示函数。
- Renderer 通过 `features/goals` public entry 及现有 Goal UI 接入;不在冻结的 AppShell 家族增加状态或 bridge 调用。CLI/TUI 使用同一 Host 投影和控制,不本地自行轮询进程。
### 9.3 精确取消操作
建议新增公开 operation `goal.event-wait.cancel`,请求包含:
```ts
{
sessionId: string;
goalId: string;
controlGeneration: number;
waitId: string;
expectedGoalRevision: number;
}
```
遵循既有客户端 Session access、Host admission 和响应错误契约。必须在同一 Session 序列化/Goal mutation 边界内重新读取并核对 waitId、Goal ID、generation 与 revision,然后复用现有 pause、换代和未接纳交付抑制;不能先读 active wait、跨 await 后再调用普通 pause。
重试始终固定原 Goal/generation/waitId;不得自动改为取消当前最新等待。已取消、已经接纳或目标改变时返回可区分的幂等/冲突结果,并附当前投影。若接纳先提交,不能抹掉接纳事实,也不能以旧卡片停止新的执行;已接纳执行使用原有停止入口。
推荐按钮说明:**“取消等待并暂停 Goal,后台任务继续运行。”** 底层任务的 Stop 仍走原资源控制操作;二者不合并。暂停后 resume 沿新代际继续,不复活旧 wait。
## 10. 恢复、清理与故障边界
Host 的 resources recovery 先核对 ShellRun,executions/domain 阶段再处理 Run/Goal 和等待,最后开放调度。不要为了新 adapter 改成“恢复 wait 就启动命令”。
| 中断位置或操作 | 预期处理 |
| --- | --- |
| 后台命令已启动但没有成功登记 wait | 维持原 ShellRun 行为;不能因命令结束自动唤醒模型 |
| wait 已提交,yield 回执/终止未完成 | 复用 PR3 Runtime 恢复,不盲目重放工具;必要时按既有策略暂停 |
| ShellRun 终态已提交,wait 尚未 resolve | 重读终态,补一次 resolution |
| ShellRun 原为 active,但重启无 live handle | 由原 resources recovery 标记 orphaned;adapter 交付未知来源结果,不按 PID 杀进程或重跑 |
| 正常关闭 Host 已持久取消进程 | 读取既有 `cancelled` 事实;不能一概重写为 crash 后的 `orphaned` |
| resolution 已提交,尚无 pending | settlement/权限满足后创建一次现有 continuation |
| pending 已提交,尚未接纳 | 复用冻结身份,重新核验资格并等待 Root admission |
| admission 提交,确认丢失或尚无 Run | 保持 PR3 drain/fence 语义;重启后按现行恢复暂停,需要用户 resume 时不得透明重放 |
| 后继已开始后崩溃 | 沿 Runtime 普通恢复与未知副作用政策,不重新交付旧 wait |
| 用户暂停、替换、取消、归档或删除 | 终止旧代观察与未接纳交付;ShellRun 是否终止由既有 Session/资源生命周期决定 |
| Session copy/fork/import | 不复制活动等待;保留已有 Goal 链接清理,不让源终态唤醒副本 |
| 同 Root 一致备份恢复 | 保留等待关联;进程是否仍可确定由资源恢复判断 |
| Host drain/close | 先停止新观察/登记/交付,释放 listener 和在途请求,再随原生命周期关闭 stores/lease |
取消/替换与接纳按持久提交顺序判定,不通过删除历史来掩盖已经发生的执行。SQLite 重开测试只能证明持久记录重开,完整 Host 恢复必须另测资源恢复、Run 终止和调度次序。
## 11. 兼容与安全边界
1. adapter 使用现有 provider-neutral wait schema;本方案不要求新增 ShellRun 业务字段或第二份终态表。若实现中确有持久字段变化,必须单独列出读写方、迁移、旧/未来版本和 Memory 一致性,不偷偷加入 `parameters` 绕过语义校验。
2. Goal 投影和取消 operation 是可观察 wire 变更,按[协议兼容约定](https://github.com/apache/maka/blob/b0ed5707ff3dc645c66253ed93e50936e3f9d45d/packages/runtime-host/protocol-compatible-changes/README.md)提升 compatibility epoch;新增 optional 字段也不能声明为 wire 不可见豁免。
3. 更新 protocol barrel/operation 集合、客户端封装、Session snapshot 解码、最坏大小预留、Desktop IPC/preload、CLI、fixtures 与兼容检查。当前 Goal 投影的严格 decoder 不能忽略。
4. 来源证明查询继续受当前 execution group/facade 约束,SQLite/Memory 独立实现;不要为缺少能力的 provider 隐式打开 Local storage。
5. 终态引用再次读取仍需访问校验;任意 ref、跨 Root、跨 Session、用户名义终端和继承 UI 投影均不能形成旁路。
6. 身份冲突、来源伪造或权限撤销返回有界错误码,不回传目标终端 command/output 作为错误详情。日志和工具输出不能提供新的指令或动作授权。
## 12. 验证矩阵
以下是实施阶段必须完成的验证,本次方案编写未执行或宣称通过这些行为测试。优先可控时钟、暂停点和真实事实信号,不以固定 sleep 猜测中间状态。
| 组 | 场景和同步点 | 核心断言 |
| --- | --- | --- |
| A 来源/权限 | 非法 URI;跨 Session/root;user terminal;foreground;无 Goal;来源事件缺失/冲突;同名插件伪结果 | 拒绝前无 wait/gate 副作用,无敏感数据泄漏 |
| A 合法对照 | 同 Session 旧 Turn 的后台任务、立即失败、Code Mode 创建但 direct 登记、有大量无关事件的长 Run | 有界来源查询仍能证明并登记,不要求错误结果 isError=false |
| B 状态语义 | starting/running/completed/failed/timed_out/cancelled/orphaned | 与第 5 节一一对应;orphaned 不重跑,failed 不标成成功 |
| C 顺序 | 订阅前结束、订阅 ready 后首次 read 前结束、read 中结束、result 早于 source settlement | 最终一次有效交付;settlement 前无后继 |
| C 去重 | 重复/乱序通知、输出更新、Read 增加 revision、重复扫描、终态后再通知 | 稳定 receipt;不重复 resolve/admit;数据 revision 不被当执行身份 |
| D 无变化 | 真后台任务保持 running,推进 clock 多次 | 工作模型/evaluator 增量为 0;观测请求有界;已释放源 root execution |
| D 正常进展 | 有其他用户 Turn 占用 Session,结果先到,随后释放 | 同一 pending/执行 ID 最终可接纳;用户输入仍有原优先级 |
| E 控制竞争 | 取消前/后 resolve、queue、admit;旧 UI 点击新 wait;pause/resume;Goal替换/归档 | 旧目标不能影响新等待;已接纳事实保留;未接纳旧代不复活 |
| E 两种取消 | 取消等待;Stop 底层 ShellRun | 前者暂停 Goal 且进程继续;后者产生任务 cancelled 终态,可交付仍有效等待 |
| F 未知/故障 | 确认丢行、临时读取错误、权限撤销、终态持久化失败、租约失效 | 区分 missing/unknown/revoked;无回调冒充持久终态;故障不泄漏权限 |
| F 资源限制 | 通知风暴、订阅满、读超时、关闭、观测预算耗尽、deadline | 合并通知、poll 兜底、无无限任务;不重置预算/期限;超时不 kill |
| G 进程恢复 | 在第 10 节各持久边界终止 Host 并重启 | 原身份与结果保留;遵守既有暂停/确认;无命令重派 |
| G 生命周期 | Session archive/delete/purge/copy、同 Root backup、旧记录与未来版本 | 无悬空 gate、无副本唤醒、无越过存储兼容检查 |
| H 清理 | 无 wait、已取消 wait、已交付 wait;进程启动失败及终态写失败 | 原 onCompletion 责任各完成一次;sandbox/residency 无提前或永久释放问题 |
| I 工具边界 | 混合 step、Code Mode 嵌套、伪造 receipt、ACP/插件 backend | 目录与调用拒绝一致,不能停在已登记但无法 yield 的状态 |
| J 投影 | 初始订阅、重连、hint 丢失、Goal revision 不变但摘要变化、旧异步读取迟到 | 展示正确,旧摘要不回退,ordinary polling 无通知风暴 |
| J 用户界面 | waiting/unknown/awaiting admission/processing/paused 及取消冲突 | 文案不混淆终态与成功;从 public feature 接入,具备视觉证据 |
### 12.1 最小用户闭环测试
使用真实 Host、Root admission、SQLite、内置后台 Bash 和 fake model。测试进程等待测试夹具的显式释放信号;fake model 先发 Bash,再发 WaitForEvent。确认 source Turn 的持久 `event_wait` 终止和 settlement 后推进观测,断言无工作模型/evaluator 新调用;释放进程,等待真实持久终态,确认同 Session 只有一个 successor,且可通过 Read 获得退出码和输出。
分别运行成功、非零退出、命令 timeout、Stop 与 orphaned 恢复。以“同样命令但不登记 wait”为对照,证明不会把所有后台完成都转换为自动模型执行。fake provider 测试不能替代这项真实来源验证。
恢复测试还应使用独立持久 marker/启动计数证明原命令没有再次执行;只统计 successor 或模型调用次数不足以排除底层命令被重派。
### 12.2 诊断与前后对照
沿既有诊断通路记录 waitId、Session、Goal generation、来源 task ID、结果类别与交付身份,覆盖 registration、resolution、queue、admission、suppression 和 recovery。只保留有界原因码,不把原命令、日志或凭据写入新诊断。指标用于解释现有事实,不构成另一套调度权威。
使用同一长测试与可重复释放点,对照现有模型查询/Goal 定时检查和结构化等待,记录工作模型及 evaluator 调用数、tokens、Host 状态读取次数、终态持久化到接纳的延迟、重复/遗漏交付和用户干预次数。busy、存储故障与正常运行分开记录。PR4 验收“无变化不新增模型调用”和“无重复有效接纳”,不预设节省百分比或固定接纳时限。
### 12.3 构建与检查
先确认根 `package.json` 工具链,在根安装依赖;按依赖顺序构建,避免对旧 dist 运行测试。建议先运行相关定向测试,再完成贡献指南要求:
```sh
npm --workspace @maka/core run build
npm --workspace @maka/storage run build
npm --workspace @maka/runtime run build
npm --workspace @maka/runtime-host run build
node --test packages/runtime/dist/__tests__/shell-run-manager.test.js
node --test packages/runtime-host/dist/__tests__/event-wait-manager.test.js
node --test packages/storage/dist/__tests__/event-wait-bridge.test.js
# 再运行本 PR 新增的 adapter、来源证明、协议、真实 Host 闭环测试。
npm run lint
npm run format:check
npm run build
npm run typecheck
npx knip --workspace apps/desktop
npx knip --workspace packages/ui
npm run check:renderer-architecture
git diff --check
```
公开协议按仓库 `scripts/protocol-epoch-check.mjs` 与 CI 的实际调用方式检查;新增 Electron E2E 时按对应 AGENTS 运行 budget 检查。UI 状态优先 unit/integration + Storybook,只有真实 Electron/IPC 边界才增加 Electron 测试。必要时运行根 `npm test`,最终报告实际范围,不能把定向验证称为全仓通过。
## 13. 实施顺序与文件清单
| 顺序 | 修改范围 | 可独立验收的结果 |
| --- | --- | --- |
| 1 | 固定 source eligibility、terminal 映射、missing 政策与支持矩阵;补契约反例 | 正反来源和全部状态规则可测试 |
| 2 | Runtime event 来源证明窄查询及 facade/SQLite/Memory;纯 ShellRun 观察 reader | 不靠 UI、整段历史扫描或模型 Read 获取证明 |
| 3 | 建议新增 `server/shell-run-event-wait-adapter.ts`、`server/shell-run-event-wait-access.ts` 及受控 listener hub | subscribe→read→持久 result;无模型/接纳调用 |
| 4 | `execution-composition.ts`、`goal-event-waits.ts`、工具目录/说明 | 生产来源装配,真实后台 Bash → yield → 一次 successor |
| 5 | Goal 投影、精确取消、continuity、客户端和 Desktop/CLI | 用户能理解和取消特定等待,不误伤新 Goal/wait |
| 6 | 故障/恢复/清理/兼容及完整 diff 复审 | 首次开放同时具备安全和进展证据 |
主要落点:
- `packages/core/src/shell-run.ts`、`event-wait.ts`:优先复用契约;必要的共享只读类型放正式 export。
- `packages/storage/src/`:Runtime event 读取契约、SQLite/Memory 实现和 `execution-stores` facade;已有 event-wait bridge 保持 authority。
- `packages/runtime/src/shell-run-manager.ts`、`shell-run-contract.ts`:窄观察/订阅接口;保留原终态写入、清理和模型 Read 语义。
- `packages/runtime/src/event-wait-tool.ts`、`ai-sdk-turn.ts`、`tool-runtime.ts`、Code Mode catalog:目录开放和控制边界回归,避免重新实现 yield。
- `packages/runtime-host/src/server/`:新增 adapter/access、composition、Goal bridge/coordinator/projection、Session continuity。
- `packages/runtime-host/src/protocol/`、client:有界投影、cancel operation、严格解码、epoch 与操作归属。
- `apps/desktop/src/main/runtime-host-session-observer.ts`、`runtime-host-session-domains-ipc-main.ts`、preload/shared 及 `renderer/features/goals/`:完整保留投影,复用公开 feature 入口。
- `packages/cli/src/` 的 Runtime Host session driver 与 Goal 控制/展示:消费同一协议,不增加执行权威。
- 各包对应 `__tests__`、Desktop Storybook/必要 E2E,以及 runtime README 的 internal-only 说明更新。
跨 package 接口走 `package.json` 声明导出,不从另一包内部 src 路径取实现。新增源码保留 ASF header。稳定实现契约更新到相关 README/架构文档;本实施计划和进度留在 issue/PR,本地稿放 `docs/local/` 不提交。
## 14. 合入条件与交接
- [ ] PR1–PR3 的持久化、Goal gate、结算及联合接纳回归通过;没有第二份 wake queue。
- [ ] 真实 ShellRun 闭环证明 R1–R8,含正常进展与非法路径对照。
- [ ] 所有支持路径都有真实入口证据;不支持路径在目录和服务端都受控。
- [ ] 最小展示、精确取消、重连投影和公开协议兼容完整落地。
- [ ] orphaned、missing、未知存储错误和取消语义已按本方案说明,并保留适用取舍。
- [ ] Host 进程恢复与纯存储重开证据分别报告;不宣称外部副作用 exactly-once。
- [ ] 报告基准/最终 HEAD、运行条件、实际命令、未覆盖项、视觉证据与独立复审结果。
建议 PR 标题:`feat(runtime-host): enable ShellRun event waits`。
完成标准是首个可用闭环及上述边界得到验证,不是 adapter 能返回一个 satisfied。若实施发现无法提供可靠后台来源证明、取消不能在正确边界重验、或某 executor 无法产生可信 yield,先保持该路径关闭并记录缺口,不通过弱化权限或模拟控制结果开放。
*由 OpenAI Codex 辅助起草,基于上述在线方案与固定源码快照核对。本文描述的是后续实施与验收计划,不是已完成实现的测试报告。*
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [I] tracking(runtime-host): condition-driven Session waits and wake-ups [maka]
Posted by "chinawch007 (via GitHub)" <gi...@apache.org>.
chinawch007 commented on issue #5926:
URL: https://github.com/apache/maka/issues/5926#issuecomment-5951631569
# PR 1 implementation plan: event-wait contracts and dormant persistence
- Source: [Discussion #5920](https://github.com/apache/maka/discussions/5920).
- Inspected baseline: `cae4a93ec842f053422aad615708ff18bbba02e2`.
- Status: a concrete handoff plan for an implementation Session. The new types, tables, and APIs below are design choices specified by this plan—not existing repository functionality or a claim of maintainer approval.
- Sole deliverable: wait records can be correctly created, read, concurrently updated, reopened, and cleaned up within existing execution persistence. Production runtime behavior remains unchanged.
## 0. Task brief to give directly to an implementation Session
> Implement PR 1 as defined here, not the entire event-wake system. First check the branch and changes on main, then use the file inventory to add core contracts, SQLite/memory persistence, and an execution-group facade. Cover contracts, migration, leases, cleanup, and provider parity with tests. Only tests invoke the new behavior: do not register tools or watchers, or change Goal accounting, waiting, automatic recovery, or model-call paths. Preserve existing untracked documents. Commit the implementation and report validation results; do not push or create a PR without separate authorization. If the design cannot fit the existing consistency boundary, report the concrete conflict rather than bypassing it with an independent database or execution queue.
Suggested branch: `feat/event-wait-persistence`.
Suggested PR title: `feat(storage): add dormant event-wait authority`.
## 1. Preflight and scope freeze
Before editing code, run:
```bash
git status --short
git branch --show-current
git rev-parse HEAD
git fetch --no-tags upstream main
git diff --stat cae4a93ec842f053422aad615708ff18bbba02e2..upstream/main -- \
packages/core/src/goal.ts \
packages/storage/src/execution-persistence-provider.ts \
packages/storage/src/execution-stores.ts \
packages/storage/src/goal-authority.ts \
packages/storage/src/sqlite-workflow-schema.ts \
packages/storage/src/test-only
```
Create a separate branch or worktree from the latest main at implementation time. Do not mix this feature into the earlier fix branch, and preserve existing tracked and untracked user changes. If the inspected baseline is unavailable, inspect the relevant files rather than guessing.
Record the actual baseline, Node/npm versions, dependency state, and workflow schema version. This plan's baseline uses workflow schema **12**, so the proposed next version is **13**. If main has advanced, use the next workflow version at that time; do not reuse an occupied number.
### In scope
- Core types, strict decoding, and state-transition validation.
- A provider-neutral wait record with a CAS revision.
- A stable result-delivery correlation key, without creating execution.
- Bounded queries and a storage constraint allowing one undelivered wait per Session.
- SQLite persistence within the existing consistency domain and an independent memory reference implementation.
- Execution-group/root-lease-scoped access, lifecycle management, and archive/removal cleanup.
- Migration, provider parity, fault, and no-side-effect tests.
### Explicitly out of scope
- No `WaitForEvent` tool, tool-catalog changes, or model-prompt changes.
- No timers, polling, filesystem watchers, or external connections.
- No GitHub authentication or Webhooks.
- No changes to `GoalAuthorityRecord`, GoalState statuses, waiting backoff, iterations, or token budgets.
- No new SessionStatus or wire-protocol fields.
- No creation of pending Goal continuations, Turns, Runs, or RuntimeEvents.
- No automatic execution recovery, command redispatch, subscription UI, or notifications.
- No generic receipt inbox, independent wake queue, or independent database.
Actual event-intake deduplication belongs to PR 2. Atomic linkage between a wait result and successor admission belongs to PR 3. This PR supplies stable identity, CAS, and the foundation for committing one immutable result to a wait; it must not claim exactly-once wake-up.
## 2. Current code facts and reuse points
| File / entry point | Current fact | Use in this PR |
| --- | --- | --- |
| `packages/core/src/goal.ts` | `GoalControlLease` carries Goal ID and generation; `pendingContinuation` already exists | Reuse control-identity types/validation rules without changing Goal storage |
| `packages/storage/src/goal-authority.ts` | Existing CAS authority, lease facade, and backend-close patterns | Use as a structural reference, but do not copy an access path with implicit Local fallback |
| `packages/storage/src/execution-persistence-provider.ts` | `ExecutionPersistence` is an indivisible consistency domain | Add `eventWaitStore`; do not open separate storage in Host |
| `packages/storage/src/local-execution-persistence.ts` | Opens/closes execution stores as one group | Add the SQLite backend using the same pattern, including partial-open cleanup |
| `packages/storage/src/execution-stores.ts` | The authenticated execution group wraps the provider and owns active operations, close, and leases | Add a controlled facade managed with the group |
| `packages/storage/src/sqlite-workflow-schema.ts` | Baseline workflow schema is 12; workflow tables are built declaratively | Add the table and advance to 13 using this module's migration pattern |
| `packages/storage/src/operational-state-store.ts` | Shared `runtime.sqlite` connection, transactions, and schema-scope registry | Reuse `acquireOperationalStateDatabase` and the workflow scope |
| `packages/storage/src/operational-target-schema.ts` | Builds and strictly validates the target structure from schema builders | Verify that the new table/indexes are covered by target validation and upgrade tests |
| `packages/storage/src/test-only/memory-execution-persistence.ts` | Independent memory reference provider, without SQLite | Add maps and transactions within the same memory authority |
| `sqlite-session-metadata-store.ts` / `conversation-operational-state.ts` | Archive, retirement, and purge already clean up Goal authority | Clean up waits within the same transaction boundaries |
**Do not use the `SQLITE_RUNTIME_SCHEMA_VERSION` numbering for this change.** The current runtime schema is 20, but this adds a workflow table. Unless implementation demonstrates a separate need, leave the runtime schema and its `PRAGMA user_version` numbering unchanged.
The core/storage package exports are explicit, not `./*`. Add the necessary exports for new modules without exposing every internal constructor as a product API.
## 3. Minimal v1 contract
Add `packages/core/src/event-wait.ts`. Follow the closed-object validation style in `record-schema.ts`: decode inputs from `unknown`, rather than accepting external data through type assertions.
Suggested exports:
```ts
export const EVENT_WAIT_SCHEMA_VERSION = 1 as const;
export type EventWaitRecord = EventWaitBase & EventWaitLifecycle;
export type EventWaitStatus = 'waiting' | 'resolved' | 'cancelled' | 'expired';
export function decodeEventWaitRecord(value: unknown): EventWaitRecord;
export function assertEventWaitTransition(
previous: EventWaitRecord,
next: EventWaitRecord,
): void;
export function eventWaitDeliveryKey(waitId: string): string;
```
Use the following record semantics. Types may be split to match repository style, but do not add executor, model-call, or arbitrary-action fields:
```ts
type EventWaitJsonValue =
| null
| boolean
| number
| string
| readonly EventWaitJsonValue[]
| { readonly [key: string]: EventWaitJsonValue };
interface EventWaitBase {
readonly schemaVersion: 1;
readonly waitId: string;
readonly sessionId: string;
readonly goalControlLease: GoalControlLease;
readonly sourceTurnId: string;
readonly sourceToolCallId: string;
readonly resource: {
readonly providerId: string;
readonly connectionId: string | null;
readonly resourceType: string;
readonly resourceId: string;
};
readonly condition: {
readonly typeId: string;
readonly version: number;
readonly parameters: Readonly<Record<string, EventWaitJsonValue>>;
};
readonly deliveryKey: string;
readonly createdAt: number;
readonly updatedAt: number;
readonly deadlineAt: number;
}
interface EventWaitResolution {
readonly outcome: 'satisfied' | 'invalidated' | 'source_unavailable';
readonly receiptKey: string;
readonly observedAt: number;
readonly evidenceRefs: readonly string[];
}
type EventWaitLifecycle =
| { readonly status: 'waiting' }
| {
readonly status: 'resolved';
readonly resolvedAt: number;
readonly resolution: EventWaitResolution;
}
| {
readonly status: 'cancelled';
readonly cancelledAt: number;
readonly reason: string;
readonly priorResolution?: {
readonly resolvedAt: number;
readonly resolution: EventWaitResolution;
};
}
| { readonly status: 'expired'; readonly expiredAt: number };
```
### 3.1 Reasons for this restricted shape
- **Goal is the only v1 consumer.** Use existing Goal control identity instead of adding unimplemented owner variants. Identity in the record is correlation, not self-authenticating permission; Host must later verify it against current control authority.
- Sources remain provider-neutral: no GitHub-, file-, or ShellRun-specific fields.
- `resource` and `condition` are persisted descriptions. PR 1 does not interpret their business semantics; PRs 2/3 must validate types and parameters through registered adapters.
- `deliveryKey` is fixed to `event-wait/${waitId}`: immutable and reconstructible, not evidence of existing execution. PR 3 will use it to link to existing continuation/admission rather than another execution queue.
- `resolved` means the result was recorded. **It does not mean a model was called or a successor was admitted.**
- Do not introduce `consumed`, `running`, or `delivered`. Without the actual admission transaction, a standalone `markDelivered()` must not pretend delivery completed. PR 3 will add linkage/release rules around the real cross-domain transaction.
- `receiptKey` identifies this wait's result. It is not a provider-wide receipt deduplication table; different subscriptions may observe the same external fact.
### 3.2 Validation rules and explicit limits
Export and test the following proposed limits. They are internal safety bounds for this plan, not default product TTLs:
| Field / value | Validation |
| --- | --- |
| Wait/Session/Goal/Turn/connection IDs | Follow existing ID constraints; suggested wait ID: 1–128 ASCII letters, digits, `_`, or `-`; connection may be null |
| provider/resourceType/condition typeId | Non-empty ASCII type identifier; allow dots, underscores, and hyphens; at most 128 bytes |
| Opaque resourceId | Non-empty UTF-8, at most 2 KiB; reject NUL/control characters |
| sourceToolCallId | Non-empty, at most 512 bytes; follow existing tool-call identity rules |
| parameters | Plain-object root; JSON values only; at most 8 KiB, depth 8, and 1024 nodes |
| resolution.receiptKey | Non-empty, at most 512 bytes, no control characters |
| evidenceRefs | At most 8 strings, each at most 1 KiB; validate bounds only, without resolving references |
| Cancellation reason | Non-empty, at most 1 KiB |
| Entire record | At most 32 KiB when JSON-serialized |
| Times, versions, generation | Finite safe integers; nonnegative times; validate versions/generation under existing identity conventions |
Additional rules:
- Reject unknown fields at the top level and in every fixed shape. Keys within `parameters` are bounded data, not arbitrary top-level extensions.
- Reject cycles, sparse arrays, `undefined`, NaN, Infinity, Date, custom prototypes, and other values outside the explicit JSON contract. Do not rely on `JSON.stringify` silently dropping fields.
- Require `createdAt <= updatedAt` and `deadlineAt > createdAt`.
- State-specific timestamps must be within `[createdAt, updatedAt]`; require `expiredAt >= deadlineAt`. `observedAt` is the Host observation time, not the provider's original event timestamp.
- `deliveryKey` must equal the derived value.
- Validate `goalControlLease` structurally, without claiming that the record proves current Goal authorization. PR 3 checks current validity in the trusted Host control path.
- Do not add credential, command, model-prompt, or `userAuthorized` fields. Parameter validation is not a secret scanner; later adapters must still prevent credentials being stored in parameters.
### 3.3 State-transition matrix
| Previous state | Permitted next state | Constraint |
| --- | --- | --- |
| Absent | waiting | The only creation path; direct insertion of resolved is forbidden |
| waiting | resolved | Commit one complete resolution; do not start execution |
| waiting | cancelled | Store an explicit cancellation reason |
| waiting | expired | The original deadline has been reached; caller-initiated, without a background timer |
| resolved | cancelled | Permit revocation before delivery, retaining the original resolution in `priorResolution` |
| Any existing state | Identical record | With the correct expected revision, return a no-op without incrementing revision |
| Any state | Change resource, condition, ownership, creation time, or deadline | Forbidden; a new condition requires a new wait ID |
| resolved/cancelled/expired | waiting or a different resolved record | Forbidden |
Apart from identical records, do not accept meaningless writes such as “waiting → waiting with only a heartbeat update.” PR 2 can add cursor/observation fields based on a real source requirement; do not prebuild a universal extension mechanism in PR 1.
Reject transitions not listed in the table. For example, do not edit the reason of a cancelled record or replace a resolved result while claiming it is the same delivery.
## 4. Storage API and CAS behavior
Add `packages/storage/src/event-wait-authority.ts`, following Goal authority's repository/facade layering. Add `eventWaitStore` to the execution storage group.
```ts
interface EventWaitSnapshot {
readonly authorityRevision: number; // Initially 0
readonly record: EventWaitRecord;
}
interface CommitEventWaitInput {
readonly sessionId: string;
readonly waitId: string;
readonly expectedAuthorityRevision: number | null; // null means create only
readonly record: EventWaitRecord; // No record:null deletion entry point
}
type CommitEventWaitResult =
| { readonly kind: 'committed'; readonly snapshot: EventWaitSnapshot }
| {
readonly kind: 'revision_conflict';
readonly actualAuthorityRevision: number | null;
}
| { readonly kind: 'active_wait_conflict'; readonly waitId: string }
| { readonly kind: 'session_unavailable' };
interface EventWaitPage {
readonly items: readonly EventWaitSnapshot[];
readonly nextCursor: string | null;
}
```
The repository provides `read`, `listSession`, `listPending`, `commit`, and `close`:
```text
read({sessionId, waitId})
listSession({sessionId, afterWaitId?, limit})
listPending({afterWaitId?, limit})
commit(input)
close()
```
- `limit` is required and must be 1–200. Use ascending ASCII `waitId` keyset pagination; read limit+1 to determine `nextCursor`. Do not implement an offset-based full scan.
- `listPending` returns only `waiting` and `resolved` for future restart reconciliation. It is not an execution queue or permission to start work.
- Pagination guarantees stable key ordering, not a database-wide snapshot across concurrent writes. The future observer must account for rescanning and deduplication.
- `read` matches both Session and wait ID. A wait ID alone must not expose another Session's record.
- Return isolated copies from reads; mutating a returned object must not alter committed state.
- The backend contract may return values or Promises. The authenticated facade returns Promises, matching existing provider style.
### 4.1 Commit sequence
1. Strictly decode input and verify that input IDs agree with the record.
2. In the backend's same write transaction, verify that the Session exists, is not archived, and has no tombstone.
3. Read the existing wait. If the wait ID belongs to another Session, reject the identity mismatch; do not transfer ownership.
4. Compare expected revision. On mismatch, return `revision_conflict` without writing.
5. Require waiting on creation; validate immutable fields and the transition matrix on update.
6. If the record is identical and expected revision is correct, return the current snapshot without incrementing revision.
7. Check whether another wait occupies this Session's active slot.
8. Atomically write state and revision. Roll back on failure and return an isolated snapshot on success.
Creation retries do not need a special “already-created” branch. A second `expected=null` for the same wait ID returns a revision conflict; the caller reads the existing record and decides what to do without creating another wait. Do not generate a new wait ID merely to retry.
The PR 1 active slot is `status IN ('waiting', 'resolved')`. A resolved wait retains the slot until cancellation or a real delivery transaction introduced by PR 3 releases it. With no production consumer in this PR, do not add a fake consumption API that releases the slot independently of actual admission.
Storage validates shape and Session liveness within the transaction. Whether the Goal generation is currently valid, the tool is authorized, and the adapter actually observed the condition belongs to PRs 3/2. Host must recheck these later; a low-level write lease is not model permission.
## 5. SQLite schema and migration
Add the table to `sqlite-workflow-schema.ts`, preserving its declarative schema-building style. The following DDL defines the target structure; apply repository formatting and validation conventions when implementing it:
```sql
CREATE TABLE IF NOT EXISTS workflow_event_waits (
wait_id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
authority_revision INTEGER NOT NULL CHECK (authority_revision >= 0),
status TEXT NOT NULL
CHECK (status IN ('waiting', 'resolved', 'cancelled', 'expired')),
delivery_key TEXT NOT NULL UNIQUE,
deadline_at INTEGER NOT NULL CHECK (deadline_at >= 0),
record_json TEXT NOT NULL,
FOREIGN KEY (session_id) REFERENCES session_metadata(session_id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS workflow_event_waits_by_session
ON workflow_event_waits(session_id, wait_id);
CREATE INDEX IF NOT EXISTS workflow_event_waits_pending
ON workflow_event_waits(wait_id)
WHERE status IN ('waiting', 'resolved');
CREATE UNIQUE INDEX IF NOT EXISTS workflow_event_waits_one_active_session
ON workflow_event_waits(session_id)
WHERE status IN ('waiting', 'resolved');
```
Implementation requirements:
- Acquire a lease on the existing `runtime.sqlite` via `acquireOperationalStateDatabase(root)`. Do not create another file or hold a `DatabaseSync` outside composition.
- Use existing `transaction('write', ...)` for Session validation, CAS, slot checking, and writing together. Unique indexes are the durable backstop; an in-process `Map` is not sufficient.
- Parameterize SQL. Do not interpolate opaque resource IDs or JSON.
- On read, strictly decode and verify scalar/JSON consistency for wait, Session, status, deliveryKey, and deadline. Do not duplicate `authority_revision` in record JSON; validate it separately as a nonnegative safe integer. Fail closed on corruption instead of repairing it into waiting or success.
- Initial revision is 0; each valid change increments it by 1. Do not mutate the caller's object to construct a result.
- Advance workflow scope from 12 to 13, or the next version at implementation time. Preserve existing runtime/core_execution/session_metadata and other scope versions.
- `operational-target-schema.ts` builds target DDL through schema builders. Check whether it already covers the new table, then change only necessary assertions. Do not weaken schema-integrity checks to make tests pass.
- The new build must open a workflow-12 root while retaining existing data. Older builds must reject workflow 13 through existing newer-scope handling rather than write a downgrade.
- Do not add a shared provider-wide receipt table. `resolution.receiptKey` and active-slot/CAS tests are not a complete event-deduplication system.
## 6. ExecutionPersistence wiring and memory reference implementation
### 6.1 Integration points
1. `execution-persistence-provider.ts`: add `readonly eventWaitStore: EventWaitAuthorityRepository`.
2. `local-execution-persistence.ts`: open the SQLite store, add it to the reverse-order close stack, and return it with the group. Partial-open failures must close it too.
3. `execution-stores.ts`: add `eventWaitStore` to `InteractiveExecutionStoresWriter`; wrap every operation through existing lease/active-operation handling.
4. `test-only/memory-execution-persistence.ts`: construct the memory wait store using the same `MemoryExecutionAuthority` and return it through the existing scoped proxy.
### 6.2 Facade requirements
- Add the `InteractiveEventWaitAuthorityWriter` brand and `authenticateInteractiveEventWaitAuthorityWriter`.
- Follow `goal-authority.ts` so fake objects fail authentication and invalid leases or access after group close are rejected.
- Facade construction must use a repository explicitly supplied by the execution group. **Do not add a product accessor that defaults to SQLite**, silently opening Local storage under a memory/custom provider.
- The group owns backend closure. Closing a facade only revokes that facade; do not close a shared backend twice.
- Reuse the execution group's in-flight waiting and close-failure semantics. Do not catch an error and silently continue using a partially closed store.
- A custom provider missing the new port should fail type checking or fail explicitly. Do not make the port optional or automatically supply a Local implementation.
### 6.3 Memory implementation
Add `test-only/memory-event-wait-authority.ts`, using `rows<EventWaitSnapshot>(state, 'eventWaits')`.
- `MemoryExecutionAuthority.read/write` supplies copies and copy-on-write transactions.
- Share core decoders/transition validators with SQLite, but implement Map operations independently without calling SQLite.
- Check existence and archive state using existing memory Session headers/tombstones.
- Use the same ordering as ASCII SQLite BINARY, not locale-sensitive ordering.
- Support existing `beforeCommit` / `afterCommit` fault hooks, using a stable operation name such as `eventWait.commit`.
- State explicitly that the memory provider retains facts only for the provider object's process lifetime, not across process loss.
## 7. Archive, removal, copy, and backup boundaries
### 7.1 SQLite
- `deleteGoalAuthorities` in `sqlite-session-metadata-store.ts` currently serves archive and batch retirement. Extend/rename it as an explicit Session workflow-authority cleanup helper, deleting waits in the same transaction.
- Preserve the `databaseLease` guard for standalone metadata stores without workflow schema so their existing tests do not break.
- Hard removal is covered by FK cascade and existing retirement paths. Test single-Session removal, batch removal, and mixed removal/archive.
- Add explicit wait-table cleanup to `conversation-operational-state.ts::purge`. This path may retain the Session header, so FK cascade alone is insufficient.
- Unarchive does not recreate removed waits or automatically start any work.
### 7.2 Memory
- In `memory-execution-session.ts`, delete waits by record.sessionId within remove, archive, and batch-retirement transactions.
- In `memory-execution-persistence.ts::purgeConversationOperationalState`, clean up by snapshot.record.sessionId. Do not accidentally use a generic loop that understands only flat records and misses nested snapshots.
- Cleanup and other Session changes belong to the same `authority.write` and roll back together on failure.
### 7.3 Moving data
- A full database backup of the same State Root retains wait records through existing operational backup/schema compatibility paths.
- Session copy, fork, and import must not automatically copy active subscriptions, which could make one event resume two tasks. Do not add fields to the Session transfer protocol in this PR.
- Check that these paths copy declared business data rather than wildcard-copying the new table. If active waits would be copied, fix that specific path and add a test.
- These are operational-state lifecycle rules. Do not alter conversation history or user files, and do not interpret deleting a wait as terminating the underlying task.
## 8. File inventory
### Add
| File | Responsibility |
| --- | --- |
| `packages/core/src/event-wait.ts` | Types, decoder, limits, deliveryKey, transition validation |
| `packages/core/src/__tests__/event-wait.test.ts` | Valid/invalid core contracts and transitions |
| `packages/storage/src/event-wait-authority.ts` | Repository types, SQLite implementation, controlled facade; split a private SQLite implementation if needed for size |
| `packages/storage/src/test-only/memory-event-wait-authority.ts` | Independent memory backend |
| `packages/storage/src/__tests__/event-wait-authority.test.ts` | Store, lease, reopen, CAS, and migration-related tests |
### Modify
| File | Change |
| --- | --- |
| `packages/core/package.json` | Export `./event-wait` using the existing format |
| `packages/storage/package.json` | Export `./event-wait-authority` if other workspaces need its types; do not expose raw DB handles |
| `execution-persistence-provider.ts` | Required `eventWaitStore` port |
| `local-execution-persistence.ts` | Open/close |
| `execution-stores.ts` | Authenticated group facade and close/error paths |
| `sqlite-workflow-schema.ts` | Table, indexes, workflow schema bump |
| `sqlite-session-metadata-store.ts` | Atomic archive/retirement cleanup |
| `conversation-operational-state.ts` | Purge cleanup |
| `test-only/memory-execution-persistence.ts` | Provider integration and purge |
| `test-only/memory-execution-session.ts` | Memory archive/retirement cleanup |
| `__tests__/execution-provider-conformance.test.ts` | Shared provider scenarios and close/fault boundaries |
| `__tests__/operational-state-store.test.ts` | Upgrade from old workflow scope, rejection of future versions, target schema |
| `__tests__/sqlite-workflow-store.test.ts` | New workflow structure and preservation of existing data |
| `__tests__/sqlite-session-metadata-store.test.ts` | Cleanup and standalone behavior |
| `__tests__/operational-state-backup.test.ts` | Wait readability after full-root backup/restore |
| `__tests__/session-copy-cleanup.test.ts` | Where fixtures allow, verify that copies do not carry active waits |
Paths without a directory prefix above are under `packages/storage/src/`. Also search the repository for every test fixture constructing `ExecutionPersistence` and supply the required port. Make type adaptations only; do not expand production functionality.
## 9. Required tests
### A. Core contract
- Valid waiting/resolved/cancelled/expired records.
- Unknown schemaVersion or fields, missing required fields, invalid IDs/generation/version.
- Negative or fractional times, a deadline not later than creation, premature expiry.
- Parameter depth/node/byte limits, NaN, undefined, sparse arrays, cycles, non-plain objects.
- Evidence/reason/resourceId bounds and a tampered deliveryKey.
- Attempts to change Session/Goal/resource/condition/source-call identity.
- Terminal records cannot reopen; resolved results cannot be replaced.
- resolved→cancelled retains the original result; waiting→cancelled does not invent one.
### B. Shared provider behavior
Run at least the following against both Local and memory, not just one backend:
- Write/read/list records belonging to a genuinely created active Session.
- Initial revision=0; valid changes increment it.
- Two writers using the same expected revision allow only one actual mutation.
- Duplicate create returns revision conflict rather than creating a second record.
- A second active wait in one Session returns active_wait_conflict; other Sessions are unaffected.
- cancelled/expired release the slot; resolved does not release it early.
- Identical commit with correct revision is a no-op; stale revision still conflicts.
- Pending pagination covers both states, filters terminal rows, does not repeat records across pages, and validates limit boundaries.
- Mutating read results or input objects does not change committed facts.
- Absent, archived, and removed Sessions reject writes.
- Test descriptions explicitly acknowledge that external truth and current Goal authorization are not verified here; storage tests are not authorization tests.
### C. Lease and provider boundaries
- Invalid leases, fake branded writers, and access after group close.
- Facade close does not close the shared backend again.
- Partial-open cleanup; close failure does not authorize a fresh Local fallback.
- Memory/custom providers do not open local SQLite, and the facade does not leak raw repository/SQL capabilities.
- Reads, lists, and writes all participate in existing in-flight lifecycle management.
### D. Durability, migration, and cleanup
- Local close/reopen retains records, revisions, and results exactly.
- Two store instances share operational authority; CAS does not depend on either instance's cache.
- Upgrade from a genuine workflow-12 structure/registry: the new table is initially absent, and existing Goal/Session data survives. Do not fake migration by changing only a version number after creating the new table.
- Repeated opens are idempotent; future workflow scope is rejected without modifying the database.
- Corrupt record_json or disagreement between indexed columns and JSON fails closed.
- Archive, batch retirement, removal, and purge clean waits without affecting another Session.
- Unarchive does not restore records; a stale commit after deletion cannot recreate a wait for an absent Session.
- Same-root backup/restore preserves waits; Session copies do not carry active subscriptions.
### E. Faults and unchanged runtime behavior
- A memory beforeCommit exception leaves state unchanged. An afterCommit exception represents a lost acknowledgement: rereading finds the single committed result, and retry does not duplicate creation.
- Cleanup transaction failure cannot leave partial results such as an archived Session with a still-effective wait.
- Fixtures call storage only, not a model, Host admission, or provider network.
- Existing Goal authority, Session retirement, provider conformance, and Runtime Host builds do not regress.
Full successor recovery across real process crashes belongs to PR 3. Passing store-reopen tests in this PR must not be presented as completed runtime automatic recovery.
## 10. Recommended implementation sequence
1. **Confirm the baseline and schema.** Record them in the PR work notes without editing existing user documents.
2. **Add core tests and types first.** Fix shapes, bounds, and transitions so backends do not interpret them differently.
3. **Add workflow DDL and Local store tests.** Verify CAS, slots, reopen, and transitions before completing the facade.
4. **Implement memory storage and run the same scenarios.** Do not share SQLite operations or use Local fallback to manufacture parity.
5. **Wire ExecutionPersistence and the authenticated group.** Update required ports/fixtures and verify close/partial-open failure behavior.
6. **Complete cleanup and migration coverage.** Archive, retirement, purge, backup/copy tests precede any production consumer.
7. **Run focused and workspace validation.** Preserve original failures and rerun results rather than silently dismissing environmental failures.
8. **Review the diff.** Confirm no watchers, tool registration, Goal behavior changes, or hidden permission modes.
9. **Commit.** Use the repository's required generative-tool attribution; do not push or create a PR without user authorization.
## 11. Validation commands
Use the Node/npm toolchain declared by `package.json` for the implementation environment. If dependencies are incomplete, use the standard repository installation process. Do not update unrelated lockfile dependencies for this PR.
During development:
```bash
npm --workspace @maka/core run build
npm --workspace @maka/storage run build
node --test packages/core/dist/__tests__/event-wait.test.js
node --test --test-concurrency=2 \
packages/storage/dist/__tests__/event-wait-authority.test.js \
packages/storage/dist/__tests__/execution-provider-conformance.test.js \
packages/storage/dist/__tests__/goal-authority.test.js \
packages/storage/dist/__tests__/operational-state-store.test.js \
packages/storage/dist/__tests__/sqlite-workflow-store.test.js \
packages/storage/dist/__tests__/sqlite-session-metadata-store.test.js \
packages/storage/dist/__tests__/operational-state-backup.test.js \
packages/storage/dist/__tests__/session-copy-cleanup.test.js
```
If tests require a package cwd, run equivalent commands from that workspace. Update commands when filenames change; do not quietly omit tests.
Before committing, at minimum:
```bash
npm run build:test
npm run typecheck
npm run lint
npm run format:check
git diff --check
```
Also run the full core/storage suites. If machine resources allow, use `npm run test:dist`. If known excessive-concurrency issues occur, rerun relevant workspaces with `node --test --test-concurrency=4 'dist/**/*.test.js'`, retaining both the initial failures and rerun results. Verify Runtime Host and Desktop consumers compile; a storage-only build is insufficient.
Follow repository contribution requirements for ASF headers. If pre-existing untracked user documents make the checkout-wide scan fail, do not edit them to remove the noise. Check this PR's changed/staged files and report the distinction.
Without Host wire-protocol changes, do not mechanically bump the compatibility epoch merely because functionality was added; workflow compatibility belongs to storage migration. If implementation touches the protocol, first explain why the scope expanded, then validate with the existing guard.
## 12. Acceptance checklist and handoff output
- [ ] The new contract is closed and bounded, accepting no arbitrary commands or authorization claims.
- [ ] Wait ID, ownership, condition, and deliveryKey are immutable; transitions have one validation implementation.
- [ ] SQLite and memory pass shared behavior tests covering CAS, slots, cleanup, and closure.
- [ ] Workflow migration preserves old-root data and fails closed on future versions.
- [ ] The new store is used through the execution group, without another database or implicit provider fallback.
- [ ] Archive/remove/purge/backup/copy boundaries are tested.
- [ ] Goal behavior, model calls, tool registration, SessionStatus, and the wire protocol are unchanged.
- [ ] `resolved` is not treated as execution already scheduled; actual delivery atomicity is explicitly left to PR 3.
- [ ] Existing edits/untracked documents are preserved and the diff contains only necessary PR files.
The implementation Session should finish by reporting:
1. Actual baseline and commit hash.
2. Changed files and any necessary deviations from this plan.
3. A short explanation of contracts, schema, API, and cleanup semantics.
4. Commands run and actual results, including failures, baseline reproduction, and checks not run.
5. The PR 2/3 handoff: how to list waits requiring reconciliation, submit results, and identify capabilities not yet implemented.
### Stop and explain these situations rather than expanding the PR
- Wait and Session cleanup cannot be kept consistent within the existing execution consistency domain.
- Storage tests require changing model execution, Goal accounting, or recovery policy.
- Provider or migration infrastructure has changed materially and this plan's versions/paths no longer apply.
- An independent “delivered” marker would conceal the absence of a real continuation-admission transaction.
- Tests pass only by bypassing permissions, weakening schema validation, or silently falling back to Local.
These indicate a boundary that needs discussion, not a reason to implement the entire event-wake system inside PR 1.
---
This plan is based on targeted code inspection. Its new design still requires code review. Preparing this document did not modify production code or establish that the proposed tests have run.
<details>
<summary>中文(点击展开)</summary>
# PR 1 执行计划:事件等待契约与未启用的持久化基础
- 来源:[Discussion #5920](https://github.com/apache/maka/discussions/5920)。
- 核查基线:`cae4a93ec842f053422aad615708ff18bbba02e2`。
- 性质:可交接给实现 Session 的具体计划。以下新增类型、表和 API 是本计划给出的实现选择,不是仓库中已经存在的功能,也不表示已获维护者批准。
- 唯一交付目标:等待记录能在既有执行存储中正确创建、读取、并发更新、重开和清理;生产运行行为不变。
## 0. 可直接交给实现 Session 的任务说明
> 实现本文定义的 PR 1,不实现整套事件唤醒。先确认分支及 main 漂移,再按文件清单新增核心契约、SQLite/内存存储和执行组 facade,补足契约、迁移、lease、清理和 provider parity 测试。所有行为只由测试调用,不注册新工具或 watcher,不修改 Goal 的计费、waiting、自动恢复和模型调用路径。不要覆盖工作区已有的未跟踪文档。完成后提交代码和验证结果;除非另有授权,不 push 或创建 PR。若发现设计无法在现有一致性边界中实现,报告具体冲突,不用独立数据库或另一个执行队列绕过。
建议分支名:`feat/event-wait-persistence`。
建议 PR 标题:`feat(storage): add dormant event-wait authority`。
## 1. 开始前检查与范围冻结
在开始改代码前执行:
```bash
git status --short
git branch --show-current
git rev-parse HEAD
git fetch --no-tags upstream main
git diff --stat cae4a93ec842f053422aad615708ff18bbba02e2..upstream/main -- \
packages/core/src/goal.ts \
packages/storage/src/execution-persistence-provider.ts \
packages/storage/src/execution-stores.ts \
packages/storage/src/goal-authority.ts \
packages/storage/src/sqlite-workflow-schema.ts \
packages/storage/src/test-only
```
从实施时最新 main 建立独立分支或 worktree,不在此前修复 PR 的分支上混入本功能;保留用户现有 tracked/untracked 修改。若基线已不可用,以文件级核查代替猜测。
记录实际基线、Node/npm 版本、依赖状态和 workflow schema 版本。本文基线为 workflow **12**,所以计划新增版本 **13**;若 main 已前进,使用当时的下一个 workflow 版本,不抢占已使用编号。
### 本 PR 做什么
- 核心类型、严格 decoder、状态转换校验。
- provider-neutral 的一条等待记录及 CAS revision。
- 稳定的结果交付关联键,不创建执行。
- 有界查询与一个 Session 一条未交付等待的存储约束。
- 现有 SQLite consistency domain 内的存储与独立 memory reference 实现。
- 经过执行组/root lease 的访问、生命周期管理、归档与删除清理。
- migration、provider parity、故障与无副作用测试。
### 本 PR 明确不做什么
- 不新增 `WaitForEvent` 工具,不改工具目录或模型提示词。
- 不启动计时器、轮询、文件监听或任何外部连接。
- 不接 GitHub 认证或 Webhook。
- 不改 `GoalAuthorityRecord`、GoalState 状态、waiting 退避、iterations、token 预算。
- 不增加 SessionStatus,不增加 wire protocol 字段。
- 不创建 pending Goal continuation、Turn、Run 或 RuntimeEvent。
- 不实现自动恢复执行、重派命令、订阅 UI 或通知。
- 不做通用 receipt 收件箱、独立 wake queue 或独立数据库。
实际“事件接收去重”留给 PR 2;“等待结果与后继接纳的原子关联”留给 PR 3。本 PR 只提供稳定身份、CAS 和同一条等待只能提交一个不可变结果的基础,不能宣称已实现 exactly-once 唤醒。
## 2. 当前代码事实与复用点
| 文件/入口 | 当前事实 | 本 PR 如何使用 |
| --- | --- | --- |
| `packages/core/src/goal.ts` | `GoalControlLease` 含 Goal ID 和 generation;已有 `pendingContinuation` | 引用控制身份的类型/校验规则,不改 Goal 存储结构 |
| `packages/storage/src/goal-authority.ts` | CAS authority、lease facade、backend close 的现有范式 | 参考结构,但不要复制一个带隐式 Local fallback 的新访问路径 |
| `packages/storage/src/execution-persistence-provider.ts` | `ExecutionPersistence` 是不可分的一致性域 | 增加 `eventWaitStore`,不在 Host 另开存储 |
| `packages/storage/src/local-execution-persistence.ts` | 统一打开/关闭各执行存储 | 按相同模式接入 SQLite backend,保持部分打开失败的清理 |
| `packages/storage/src/execution-stores.ts` | authenticated execution group 包装 provider,管理 active operations、close、lease | 增加受控 facade,与整个组共同管理 |
| `packages/storage/src/sqlite-workflow-schema.ts` | 基线 workflow schema 为 12;声明式建立 workflow 表 | 新增表并升到 13,沿用该模块迁移方式 |
| `packages/storage/src/operational-state-store.ts` | `runtime.sqlite` 共享连接、事务、schema scope registry | 复用 `acquireOperationalStateDatabase` 和 workflow scope |
| `packages/storage/src/operational-target-schema.ts` | 用各 schema 构建目标结构并严格校验 | 确认新增表/索引被目标结构和旧库升级测试覆盖 |
| `packages/storage/src/test-only/memory-execution-persistence.ts` | 独立内存参考 provider,不依赖 SQLite | 加入同一 memory authority 的 map 和事务 |
| `sqlite-session-metadata-store.ts` / `conversation-operational-state.ts` | 归档、退役和 purge 已清理 Goal authority | 在相同事务边界清理等待 |
**不要误用 `SQLITE_RUNTIME_SCHEMA_VERSION` 的编号。** 当前 runtime schema 为 20,但此次新增的是 workflow 表;除非实际实现证明另有必要,不修改 runtime schema 或 `PRAGMA user_version` 的 runtime 编号。
core/storage 的 package exports 是显式列举,并非 `./*`。新增模块需要对应 export;不必将所有内部构造函数暴露为产品 API。
## 3. 最小 v1 契约
新增 `packages/core/src/event-wait.ts`。使用现有 `record-schema.ts` 的封闭对象校验风格,输入按 `unknown` 解码,不靠类型断言接受外部数据。
建议导出:
```ts
export const EVENT_WAIT_SCHEMA_VERSION = 1 as const;
export type EventWaitRecord = EventWaitBase & EventWaitLifecycle;
export type EventWaitStatus = 'waiting' | 'resolved' | 'cancelled' | 'expired';
export function decodeEventWaitRecord(value: unknown): EventWaitRecord;
export function assertEventWaitTransition(
previous: EventWaitRecord,
next: EventWaitRecord,
): void;
export function eventWaitDeliveryKey(waitId: string): string;
```
记录结构采用下述语义;可以按仓库风格拆类型,但不擅自增加 executor、模型调用或任意动作字段:
```ts
type EventWaitJsonValue =
| null
| boolean
| number
| string
| readonly EventWaitJsonValue[]
| { readonly [key: string]: EventWaitJsonValue };
interface EventWaitBase {
readonly schemaVersion: 1;
readonly waitId: string;
readonly sessionId: string;
readonly goalControlLease: GoalControlLease;
readonly sourceTurnId: string;
readonly sourceToolCallId: string;
readonly resource: {
readonly providerId: string;
readonly connectionId: string | null;
readonly resourceType: string;
readonly resourceId: string;
};
readonly condition: {
readonly typeId: string;
readonly version: number;
readonly parameters: Readonly<Record<string, EventWaitJsonValue>>;
};
readonly deliveryKey: string;
readonly createdAt: number;
readonly updatedAt: number;
readonly deadlineAt: number;
}
interface EventWaitResolution {
readonly outcome: 'satisfied' | 'invalidated' | 'source_unavailable';
readonly receiptKey: string;
readonly observedAt: number;
readonly evidenceRefs: readonly string[];
}
type EventWaitLifecycle =
| { readonly status: 'waiting' }
| {
readonly status: 'resolved';
readonly resolvedAt: number;
readonly resolution: EventWaitResolution;
}
| {
readonly status: 'cancelled';
readonly cancelledAt: number;
readonly reason: string;
readonly priorResolution?: {
readonly resolvedAt: number;
readonly resolution: EventWaitResolution;
};
}
| { readonly status: 'expired'; readonly expiredAt: number };
```
### 3.1 为什么这样收窄
- v1 的**消费者先限定为 Goal**,使用现有 Goal 控制身份,不引入多个尚无实现的 owner variant。记录中的身份仅供关联,不能自证授权;后续 Host 必须核验它确实匹配当前控制权威。
- 来源仍是 provider-neutral:没有 GitHub、文件或 ShellRun 专属字段。
- `resource` 与 `condition` 是持久化描述,PR 1 不解释其业务语义;PR 2/3 必须通过注册的 adapter 校验类型与参数。
- `deliveryKey` 固定为 `event-wait/${waitId}`,不可变且可重建,不表示已有执行。它将供 PR 3 关联既有 continuation/admission,不另存一个运行队列。
- `resolved` 表示结果已记录,**不表示模型已被调用或后继已经接纳**。
- 不引入 `consumed`、`running`、`delivered` 状态;没有真实接纳事务时,不允许调用一个单独的 `markDelivered()` 假装交付完成。PR 3 根据实际跨域事务补充交付关联/释放规则。
- `receiptKey` 只关联这条等待的结果。它不是全 provider 的 receipt 去重表;不同订阅可以观察同一外部事实。
### 3.2 校验规则和明确上限
建议将以下上限导出并测试。它们是本计划的内部安全边界,不是产品默认 TTL:
| 内容 | 校验 |
| --- | --- |
| wait/session/Goal/Turn/connection 等 ID | 沿用现有 ID 约束;wait ID 建议 1–128 个 ASCII 字母、数字、`_`、`-`;connection 可为 null |
| provider/resourceType/condition typeId | 非空 ASCII 类型标识,允许点、下划线、连字符,最多 128 字节 |
| opaque resourceId | 非空 UTF-8,最多 2 KiB,拒绝 NUL/控制字符 |
| sourceToolCallId | 非空、最多 512 字节,遵循现有工具调用身份规则 |
| parameters | 根为普通对象;只允许 JSON 值,最多 8 KiB、深度 8、节点数 1024 |
| resolution.receiptKey | 非空、最多 512 字节,不含控制字符 |
| evidenceRefs | 最多 8 个字符串,每个最多 1 KiB;这里只校验边界,不读取引用 |
| 取消 reason | 非空、最多 1 KiB |
| 全记录 | JSON 序列化后最多 32 KiB |
| 时间、版本、generation | 有限、安全整数;时间非负;版本与 generation 按既有身份约定校验 |
额外规则:
- 顶层及每个固定形状拒绝未知字段;`parameters` 的 key 是有界数据,不是任意顶层扩展点。
- 拒绝循环、稀疏数组、`undefined`、NaN、Infinity、Date、自定义 prototype 等非明确 JSON 值;校验不能靠 `JSON.stringify` 静默删字段。
- `createdAt <= updatedAt`,`deadlineAt > createdAt`。
- 状态专属时间应在 `[createdAt, updatedAt]`;`expiredAt >= deadlineAt`。`observedAt` 是 Host 的观察时刻,不是外部事件原始发生时间。
- `deliveryKey` 必须等于派生函数结果。
- 核验 `goalControlLease` 结构,但不声称记录本身证明 Goal 当前仍授权;当前有效性由 PR 3 在可信 Host 控制路径核验。
- 不增加 credential、命令、模型 prompt 或 `userAuthorized` 字段。参数不是 secret scanner;后续 adapter 仍须防止把凭据写进参数。
### 3.3 状态转换矩阵
| 原状态 | 允许的新状态 | 约束 |
| --- | --- | --- |
| 不存在 | waiting | 只允许这样创建,不允许直接插入 resolved |
| waiting | resolved | 提交一个完整 resolution;不会启动执行 |
| waiting | cancelled | 保存明确取消原因 |
| waiting | expired | 时间达到原截止值;由调用方发起,不设后台 timer |
| resolved | cancelled | 允许交付前撤销,但必须保留原 resolution 到 `priorResolution` |
| 任意已存在状态 | 完全相同记录 | expected revision 正确时返回 no-op,不增加 revision |
| 任意状态 | 更改资源、条件、归属、创建时间或截止时间 | 禁止;新条件必须创建新 wait ID |
| resolved/cancelled/expired | waiting,或另一份 resolved | 禁止 |
除完全相同记录外,不允许“waiting → waiting 只更新心跳”之类无实际语义写入。游标与观测进度由 PR 2 按真实来源需求扩展,不在 PR 1 预建万能字段。
表中未列出的转换一律拒绝;例如不得修改已取消记录的原因,也不得更新已 resolved 的结果再宣称是同一份交付。
## 4. 存储 API 与 CAS 行为
新增 `packages/storage/src/event-wait-authority.ts`,参考 Goal authority 的 repository/facade 分层。新增 `eventWaitStore` 属性到执行存储组。
```ts
interface EventWaitSnapshot {
readonly authorityRevision: number; // 首次为 0
readonly record: EventWaitRecord;
}
interface CommitEventWaitInput {
readonly sessionId: string;
readonly waitId: string;
readonly expectedAuthorityRevision: number | null; // null 仅表示创建
readonly record: EventWaitRecord; // 不提供 record:null 删除入口
}
type CommitEventWaitResult =
| { readonly kind: 'committed'; readonly snapshot: EventWaitSnapshot }
| {
readonly kind: 'revision_conflict';
readonly actualAuthorityRevision: number | null;
}
| { readonly kind: 'active_wait_conflict'; readonly waitId: string }
| { readonly kind: 'session_unavailable' };
interface EventWaitPage {
readonly items: readonly EventWaitSnapshot[];
readonly nextCursor: string | null;
}
```
repository 提供 `read`、`listSession`、`listPending`、`commit`、`close`:
```text
read({sessionId, waitId})
listSession({sessionId, afterWaitId?, limit})
listPending({afterWaitId?, limit})
commit(input)
close()
```
- `limit` 必填,范围 1–200。按 ASCII `waitId` 升序 keyset 分页;查 limit+1 决定 `nextCursor`,不使用 offset 全量扫描。
- `listPending` 只返回 `waiting` 和 `resolved`,用于未来重启校准;这个查询不是执行队列或启动授权。
- 分页保证稳定键次序,不承诺跨并发写入的全库快照。未来观测管理器需要处理反复扫描和去重。
- `read` 使用 Session 和 wait ID 双条件,不能用一个 wait ID 泄漏其他 Session 的记录。
- 所有读返回隔离副本;修改读结果不得修改已提交状态。
- 外部契约可以返回同步值或 Promise,authenticated facade 统一为 Promise,保持现有 provider 风格。
### 4.1 一次 commit 的顺序
1. 严格解码并检查入参 ID 与 record 一致。
2. 在 backend 的同一个写事务中,检查 Session 存在、未归档、未被 tombstone 标记。
3. 读取当前 wait。wait ID 已属于别的 Session 时拒绝 identity mismatch,不迁移归属。
4. 比较 expected revision:不一致返回 `revision_conflict`,不写入。
5. 创建时要求 waiting;更新时核验不可变字段和状态矩阵。
6. 完全一致且 expected revision 正确时返回当前 snapshot,不增加 revision。
7. 检查该 Session 其他 wait 是否占用 active slot。
8. 原子写入新的状态与 revision;失败回滚,返回隔离 snapshot。
创建重试不要求特殊“already-created”分支:同一个 wait ID 的第二次 `expected=null` 返回 revision conflict,调用方读取已有记录再决定,不产生第二条等待。不要为重试生成新 wait ID。
PR 1 的 active slot 是 `status IN ('waiting', 'resolved')`。`resolved` 保持占用直到取消,或未来 PR 3 通过真正的交付事务释放。首 PR 无生产消费者,因此不添加一个会脱离真实接纳而提前释放 slot 的假消费 API。
存储只能核验结构和事务内的 Session 生存状态;Goal generation 是否仍有效、工具是否获授权及 adapter 是否真实观察到条件,是 PR 3/PR 2 的责任。后续 Host 必须重新核验,不能把低层 write lease 当作模型权限。
## 5. SQLite schema 与迁移
在 `sqlite-workflow-schema.ts` 增加表,保持已有声明式 schema 构建方式。以下 DDL 是目标结构,实施时按现有缩进/校验约定写入:
```sql
CREATE TABLE IF NOT EXISTS workflow_event_waits (
wait_id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
authority_revision INTEGER NOT NULL CHECK (authority_revision >= 0),
status TEXT NOT NULL
CHECK (status IN ('waiting', 'resolved', 'cancelled', 'expired')),
delivery_key TEXT NOT NULL UNIQUE,
deadline_at INTEGER NOT NULL CHECK (deadline_at >= 0),
record_json TEXT NOT NULL,
FOREIGN KEY (session_id) REFERENCES session_metadata(session_id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS workflow_event_waits_by_session
ON workflow_event_waits(session_id, wait_id);
CREATE INDEX IF NOT EXISTS workflow_event_waits_pending
ON workflow_event_waits(wait_id)
WHERE status IN ('waiting', 'resolved');
CREATE UNIQUE INDEX IF NOT EXISTS workflow_event_waits_one_active_session
ON workflow_event_waits(session_id)
WHERE status IN ('waiting', 'resolved');
```
实现要求:
- 用 `acquireOperationalStateDatabase(root)` 取得现有 `runtime.sqlite` 的 lease;不创建新文件、不直接持有绕开 composition 的 `DatabaseSync`。
- 写入使用既有 `transaction('write', ...)`,Session 校验、CAS、slot 检查和写入都在事务内。唯一索引是底线,不只靠进程内 `Map`。
- SQL 均使用参数,不拼接 opaque resource ID 或 JSON 内容。
- 读取时严格 decoder + scalar/JSON 一致性校验:wait/session/status/deliveryKey/deadline 不允许互相矛盾;`authority_revision` 不重复存入 record JSON,单独校验为非负安全整数。损坏记录应 fail closed,不补成 waiting 或 success。
- 首次创建记录 revision=0,之后合法变更加 1;不能修改 caller 对象作为返回结果。
- workflow scope 12→13(或实施时下一个版本)。保留 runtime/core_execution/session_metadata 等 scope 的既有版本。
- `operational-target-schema.ts` 通过 schema builder 构造目标 DDL,先验证是否自动覆盖新表,再只改真正需要的断言。不要放宽 schema 完整性检查来让测试通过。
- 新程序可打开旧 workflow 12 根并保留数据;旧版本面对 workflow 13 应按现有 newer-scope 机制拒绝,不能降级写入。
- 本 PR 不新增共享的全 provider receipt 表。`resolution.receiptKey` 与 active-slot/CAS 测试不能被宣传成完整事件去重系统。
## 6. ExecutionPersistence 接线与内存参考实现
### 6.1 接线位置
1. `execution-persistence-provider.ts`:增加 `readonly eventWaitStore: EventWaitAuthorityRepository`。
2. `local-execution-persistence.ts`:打开 SQLite store,加入统一逆序 close 栈,并随整个 group 返回;部分打开失败也会 close。
3. `execution-stores.ts`:在 `InteractiveExecutionStoresWriter` 增加 `eventWaitStore`;所有方法包装进既有 lease/active-operation 路径。
4. `test-only/memory-execution-persistence.ts`:用同一 `MemoryExecutionAuthority` 创建 memory wait store,经过现有 scoped proxy 返回。
### 6.2 Facade 要求
- 新建 `InteractiveEventWaitAuthorityWriter` 品牌和 `authenticateInteractiveEventWaitAuthorityWriter`。
- 参考 `goal-authority.ts`,保证 fake object 无法通过 authenticate,失效 lease 与 group close 后的调用被拒绝。
- facade 创建必须使用执行组显式传入的 repository。**不提供默认回落到 SQLite 的新产品 accessor**,避免 memory/custom provider 下偷偷打开 Local 存储。
- group close 拥有 backend,facade close 只撤销该 facade;不能把共享 backend 重复关闭。
- 复用执行组的 in-flight 等待和关闭失败语义;不要 catch 后静默继续使用部分关闭的 store。
- 自定义 provider 缺少新增端口时应通过类型检查或明确失败发现,不做可选端口和自动补 Local 实现。
### 6.3 内存实现
建议新增 `test-only/memory-event-wait-authority.ts`,使用 `rows<EventWaitSnapshot>(state, 'eventWaits')`。
- `MemoryExecutionAuthority.read/write` 提供副本与 copy-on-write 事务。
- `commit` 与 SQLite 复用 core decoder/transition validator,但独立实现 Map 操作,不调用 SQLite。
- 用现有 memory Session headers/tombstones 检查存在和归档状态。
- 字符串排序采用与 ASCII SQLite BINARY 相同的次序,不依赖 locale 排序。
- 支持现有 `beforeCommit` / `afterCommit` 故障钩子,操作名固定如 `eventWait.commit`。
- 明确 memory provider 仅在 provider 对象进程生命周期内保留状态,不声称跨进程 durable。
## 7. 归档、删除、复制与备份边界
### 7.1 SQLite
- `sqlite-session-metadata-store.ts` 中当前 `deleteGoalAuthorities` 用于归档和批量退役。将其扩展/重命名为明确的 Session workflow authority 清理 helper,在同一事务删除等待记录。
- 保留 standalone metadata store 没有 workflow schema 时的 `databaseLease` guard,不使原独立存储测试失败。
- 硬删除由 FK cascade 和现有退役路径保证清理;补测试覆盖单 Session remove、批量删除及“部分删除、部分归档”。
- `conversation-operational-state.ts::purge` 增加显式等待表清理;该路径可能保留 Session header,不能只靠 FK。
- 解除归档不重新创建被清理的等待,不自动启动任何工作。
### 7.2 Memory
- 在 `memory-execution-session.ts` 的 remove、archive 和批量 retirement 事务中,按 record.sessionId 删除 wait。
- 在 `memory-execution-persistence.ts::purgeConversationOperationalState` 中按 snapshot.record.sessionId 清理;不要误用只适用于扁平 record 的通用循环而漏删。
- 清理与其他 Session 状态变更属于同一次 `authority.write`,故障时共同回滚。
### 7.3 数据移动
- 同一 State Root 的完整数据库备份应保留等待记录,沿用已有 operational backup/schema 兼容路径。
- Session 复制、fork、导入不自动复制活跃订阅,避免一个事件恢复两个不同任务。本 PR 不给现有 Session transfer 协议添加新字段。
- 核对这些路径只复制已声明的业务数据,而不是新增表后被通配复制。若发现活跃等待会被复制,修正该具体路径并增加测试。
- 这是操作态的生命周期规则,不改聊天历史或用户文件,也不把删除等待误当成终止底层任务。
## 8. 文件清单
### 新增
| 文件 | 职责 |
| --- | --- |
| `packages/core/src/event-wait.ts` | 类型、decoder、上限、deliveryKey、转换校验 |
| `packages/core/src/__tests__/event-wait.test.ts` | 核心合法/非法契约与转换测试 |
| `packages/storage/src/event-wait-authority.ts` | repository types、SQLite 实现、受控 facade;如过大可拆 SQLite 私有实现 |
| `packages/storage/src/test-only/memory-event-wait-authority.ts` | 独立 memory backend |
| `packages/storage/src/__tests__/event-wait-authority.test.ts` | store、lease、重开、CAS、迁移相关测试 |
### 修改
| 文件 | 修改内容 |
| --- | --- |
| `packages/core/package.json` | 增加 `./event-wait` 导出,沿用现有 export 格式 |
| `packages/storage/package.json` | 如其他 workspace 需要类型,增加 `./event-wait-authority`;不公开原始 DB handles |
| `execution-persistence-provider.ts` | required `eventWaitStore` port |
| `local-execution-persistence.ts` | open/close |
| `execution-stores.ts` | authenticated group facade 与 close/error paths |
| `sqlite-workflow-schema.ts` | 表、索引、workflow schema bump |
| `sqlite-session-metadata-store.ts` | 归档/退役的原子清理 |
| `conversation-operational-state.ts` | purge 清理 |
| `test-only/memory-execution-persistence.ts` | provider 接入、purge |
| `test-only/memory-execution-session.ts` | memory 归档/退役清理 |
| `__tests__/execution-provider-conformance.test.ts` | 两种 provider 共用场景与关闭/故障边界 |
| `__tests__/operational-state-store.test.ts` | 旧 workflow scope 升级、新版本拒绝及目标 schema |
| `__tests__/sqlite-workflow-store.test.ts` | 新 workflow 结构及原数据不受影响 |
| `__tests__/sqlite-session-metadata-store.test.ts` | 清理与 standalone 行为 |
| `__tests__/operational-state-backup.test.ts` | 全根备份与恢复记录可读 |
| `__tests__/session-copy-cleanup.test.ts` | 如现有夹具可覆盖,验证复制不带活跃等待 |
以上省略目录前缀的文件位于 `packages/storage/src/`。此外全仓搜索所有构造 `ExecutionPersistence` 的测试夹具,补齐 required port;只做类型适配,不扩展生产功能。
## 9. 必须完成的测试
### A. Core contract
- waiting/resolved/cancelled/expired 各状态合法样例。
- 未知 schemaVersion、未知字段、缺失必填、错误 ID/generation/version。
- 时间负数、非整数、deadline 不晚于创建、提前 expired。
- parameters 深度/节点/字节边界,NaN、undefined、稀疏数组、循环与非普通对象。
- evidence/reason/resourceId 边界和 deliveryKey 篡改。
- 不允许替换 wait 所属 Session/Goal/资源/条件/源调用身份。
- terminal 不可重开;resolved 不能更换结果。
- resolved→cancelled 必须保留原结果,waiting→cancelled 不伪造结果。
### B. Provider 共同行为
至少把以下用例分别运行于 Local 和 memory,而不是只测一个 backend:
- 在真实创建的 active Session 下写入、read、按 Session 列表。
- 初始 revision=0,合法更新递增。
- 两个 writer 使用同一 expected revision,只允许一个实际变更。
- 重复 create 返回 revision conflict;不生成第二份记录。
- 一个 Session 的第二条 active wait 返回 active_wait_conflict;其他 Session 不受影响。
- cancelled/expired 释放 slot;resolved 不提前释放。
- 正确 revision 下同值提交是 no-op,旧 revision 仍冲突。
- 两种 pending 状态分页、有终态混入时过滤,跨页不重复,边界 limit 校验。
- 读结果与写入对象的外部修改不影响已提交事实。
- 不存在、archived、removed Session 不接受写入。
- 不验证外部条件真假或现时 Goal 授权的测试说明必须明确,避免误把 store 测试当授权测试。
### C. Lease 与 provider 边界
- invalid lease、fake branded writer、group close 后访问。
- facade close 不重复关闭 shared backend。
- partial open failure 清理;close failure 不授权新 Local fallback。
- memory/custom provider 不打开本地 SQLite,且新增 facade 不泄漏 raw repository/SQL 能力。
- read/list/write 均通过既有 in-flight 生命周期。
### D. 持久性、迁移、清理
- Local 关闭后重开,记录、revision、结果完全一致。
- 两个 store instance 使用同一 operational authority,CAS 不依赖各自内存 cache。
- 从真正的 workflow 12 结构/registry 升级:没有新表、保留原 Goal/Session 数据;不要仅修改版本号却预先创建新表的伪迁移测试。
- 重复打开幂等;future workflow scope 拒绝且不修改原库。
- 手动破坏 record_json 或索引列/JSON 对应关系时 fail closed。
- archive、批量退役、remove、purge 都清理等待且不影响其他 Session。
- 解除归档不恢复记录;删除后 stale commit 不能在不存在 Session 下重建等待。
- 同根 backup/restore 保留等待;Session copy 不附带活跃订阅。
### E. 故障与无行为变化
- memory beforeCommit 抛错,状态不变;afterCommit 抛错代表 lost acknowledgement,重读能找到唯一提交,重试不重复创建。
- 清理事务失败时不留下“Session 已归档,但等待仍有效”等部分结果。
- fixture 只调用存储,未调用 model、Host admission 或 provider network。
- 现有 Goal authority、Session retirement、provider conformance 与 Runtime Host build 不回归。
其中真实进程 crash 的完整后继恢复测试属于 PR 3;本 PR 不以“重开 store 测试通过”冒称运行时自动恢复已完成。
## 10. 建议执行顺序
1. **确认基线和 schema。** 写入 PR 工作记录,不动现有用户文档。
2. **先加 core 测试及类型。** 固定形状、边界和状态矩阵,避免 backend 各自解释。
3. **加入 workflow DDL 与 Local store 测试。** 先验证 CAS、slot、重开和合法转换,再补 facade。
4. **实现 memory store 并跑同一组场景。** 不共享 SQLite 操作或以 Local fallback 让 parity 假通过。
5. **接入 ExecutionPersistence 与 authenticated group。** 更新 required ports 和夹具,检查 close/部分打开失败。
6. **补清理与迁移。** 归档、退役、purge、backup/copy 测试要在开放任何生产消费者前完成。
7. **运行聚焦与 workspace 验证。** 保留失败原文和重跑结果,不把环境失败简单吞掉。
8. **审查 diff。** 确认没有 watcher、工具注册、Goal 行为修改或隐藏权限模式。
9. **提交。** 使用项目要求的生成式工具 attribution;未经用户授权不 push 或创建 PR。
## 11. 验证命令
根据实施环境使用 `package.json` 声明的 Node/npm 工具链。已有依赖若不完整,先执行仓库标准安装;不要为了本 PR 更新锁文件中的无关依赖。
开发时:
```bash
npm --workspace @maka/core run build
npm --workspace @maka/storage run build
node --test packages/core/dist/__tests__/event-wait.test.js
node --test --test-concurrency=2 \
packages/storage/dist/__tests__/event-wait-authority.test.js \
packages/storage/dist/__tests__/execution-provider-conformance.test.js \
packages/storage/dist/__tests__/goal-authority.test.js \
packages/storage/dist/__tests__/operational-state-store.test.js \
packages/storage/dist/__tests__/sqlite-workflow-store.test.js \
packages/storage/dist/__tests__/sqlite-session-metadata-store.test.js \
packages/storage/dist/__tests__/operational-state-backup.test.js \
packages/storage/dist/__tests__/session-copy-cleanup.test.js
```
如测试依赖 package cwd,从对应 workspace 执行等价命令。新增文件名改变时同步调整命令,不悄悄省略测试。
提交前至少:
```bash
npm run build:test
npm run typecheck
npm run lint
npm run format:check
git diff --check
```
并运行 core/storage 完整 suite。机器资源允许时使用 `npm run test:dist`;若遇到已知过高并发问题,可以对相关 workspace 用 `node --test --test-concurrency=4 'dist/**/*.test.js'` 重跑,并保留首轮失败与重跑事实。确认 Runtime Host 及 Desktop 消费方编译,不能只通过 storage 自己的 build。
ASF header 检查遵循仓库贡献要求。若 checkout 中存在用户未跟踪的旧文档导致全目录检查失败,不修改那些文件来消除噪声;检查本 PR changed/staged 文件并如实说明。
不修改 Host wire protocol 的情况下,本 PR 不应为了“新增功能”机械 bump compatibility epoch;workflow schema 的兼容性由 storage migration 管理。若实际改动碰到协议,先解释为何越界,再按现有 guard 验证。
## 12. 验收清单与交接输出
- [ ] 新契约封闭且有界,不接收任意命令或授权声明。
- [ ] wait ID、归属、条件和 deliveryKey 不可变;状态转换有唯一实现。
- [ ] SQLite 与 memory 有同一套行为测试,包括 CAS、slot、清理和关闭。
- [ ] workflow migration 正确,旧根数据保留,未来版本 fail closed。
- [ ] 新 store 只能随执行组使用,未引入独立数据库或隐式 provider fallback。
- [ ] archive/remove/purge/backup/copy 边界已测试。
- [ ] 没有改变 Goal 行为、模型调用、工具注册、SessionStatus 或 wire protocol。
- [ ] 记录“resolved”未被当作已经安排执行;实际交付原子性明确留给 PR 3。
- [ ] 现有修改/未跟踪文档未被覆盖,diff 只含本 PR 必要文件。
实现 Session 最终应输出:
1. 实际基线与提交 hash。
2. 文件变更列表及任何对本计划的必要偏离。
3. 契约、schema、API、清理语义的简短说明。
4. 执行过的验证命令与真实结果,包含失败、基线复现或未运行项。
5. PR 2/3 接口交接:如何列出待校准等待、提交结果,以及哪些能力尚未实现。
### 遇到以下情况应先停下来说明,而不是扩大 PR
- 无法在现有 execution consistency domain 内保证等待与 Session 清理一致性。
- 需要修改实际模型执行、Goal 计费或恢复策略才能让存储测试成立。
- 发现 provider 或迁移主干发生实质变化,本文版本/文件路径不再适用。
- 需要用一个独立“已交付”标记掩盖不存在的 continuation 接纳事务。
- 测试只能靠跳过权限、放宽 schema 校验或隐式落回 Local 才能通过。
这些问题意味着边界需要重新讨论,而不是应在 PR 1 内实现整套事件唤醒。
---
计划依据为定向代码核查,新增设计仍需代码评审。该文档没有修改生产代码,也没有声明相关测试已经运行。
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [I] tracking(runtime-host): condition-driven Session waits and wake-ups [maka]
Posted by "chinawch007 (via GitHub)" <gi...@apache.org>.
chinawch007 commented on issue #5926:
URL: https://github.com/apache/maka/issues/5926#issuecomment-6121096199
<p><strong><span>PR 2 implementation plan: Host observation and condition management</span></strong></p><p><span>Following </span><a href="https://github.com/apache/maka/discussions/5920"><span><span><span>Discussion #5920</span></span></span></a><span> and </span><a href="https://github.com/apache/maka/pull/6057"><span><span><span>PR1 #6057</span></span></span></a><span>, add Host-owned condition observation on top of the existing wait contracts, CAS updates, and persistence.</span></p><p><strong><span>Deliverable:</span></strong><span> demonstrate “discover a wait → verify the condition → continue observation → persist the result → recover after restart” using fake adapters, a controlled clock, and real persistence, without invoking the work model.</span></p><p><span>This PR owns observation and result persistence. Tool yielding, Goal scheduling, and successor admission belong to PR3. Production ShellRun, GitHub, and file adapters belong to subsequent PRs.</span></p>
<p><strong><span>1. Define the source adapter contract</span></strong></p><p><span>Introduce an internal adapter interface covering condition validation, current-state reads, optional notifications, reconciliation, and cancellation. Select adapters by </span><code dir="ltr">providerId</code><span>, condition type, and version; reject unsupported combinations explicitly.</span></p><p><span>Adapters own source semantics: resource-version matching, condition evaluation, and stable event identities or recovery cursors. The manager owns scheduling, deadlines, observation budgets, and persistence. Source access must come from trusted Host context; neither a storage lease nor model-supplied arguments establish authorization.</span></p><p><span>Keep observation outcomes separate from the wait lifecycle:</span></p><div><div><div>
Observation | Handling
-- | --
pending: the read succeeded, but the condition does not hold | Keep waiting; schedule another observation
unknown: network failure, throttling, or another inconclusive read | Keep waiting; record the reason and back off
satisfied: the declared condition holds | Commit resolved with stable result identity and evidence references
invalidated: the resource or condition is no longer valid | Commit the corresponding resolved outcome
Explicitly unrecoverable source failure, or exhausted observation budget | Commit source_unavailable under a defined policy
Absolute deadline reached | Commit expired
</div></div></div><p><span>Assert zero calls to the work model, Goal evaluator, and execution admission. These tests establish observation correctness and durable recovery; they do not establish execution delivery.</span></p><p><strong><span>Suggested implementation order:</span></strong><span> finalize adapter and observation-state contracts → extend persistence and compatibility tests → implement the manager and scheduler → add cancellation and recovery fault tests → validate Host lifecycle composition and document the PR3 handoff.</span></p><p><span>Before submission, run the relevant core, storage, and runtime-host tests, plus build, typecheck, lint, and formatting checks.</span></p>PR 2 implementation plan: Host observation and condition management
Following [Discussion #5920](https://github.com/apache/maka/discussions/5920) and [PR1 #6057](https://github.com/apache/maka/pull/6057), add Host-owned condition observation on top of the existing wait contracts, CAS updates, and persistence.
Deliverable: demonstrate “discover a wait → verify the condition → continue observation → persist the result → recover after restart” using fake adapters, a controlled clock, and real persistence, without invoking the work model.
This PR owns observation and result persistence. Tool yielding, Goal scheduling, and successor admission belong to PR3. Production ShellRun, GitHub, and file adapters belong to subsequent PRs.
1. Define the source adapter contract
Introduce an internal adapter interface covering condition validation, current-state reads, optional notifications, reconciliation, and cancellation. Select adapters by providerId, condition type, and version; reject unsupported combinations explicitly.
Adapters own source semantics: resource-version matching, condition evaluation, and stable event identities or recovery cursors. The manager owns scheduling, deadlines, observation budgets, and persistence. Source access must come from trusted Host context; neither a storage lease nor model-supplied arguments establish authorization.
Keep observation outcomes separate from the wait lifecycle:
Observation Handling
pending: the read succeeded, but the condition does not hold Keep waiting; schedule another observation
unknown: network failure, throttling, or another inconclusive read Keep waiting; record the reason and back off
satisfied: the declared condition holds Commit resolved with stable result identity and evidence references
invalidated: the resource or condition is no longer valid Commit the corresponding resolved outcome
Explicitly unrecoverable source failure, or exhausted observation budget Commit source_unavailable under a defined policy
Absolute deadline reached Commit expired
Transient read failures must not become satisfaction or permanent source failure. satisfied means the declared condition holds; it does not establish overall task success.
2. Persist minimal observation progress
PR1 currently supports lifecycle transitions but does not persist ongoing observation progress. Add bounded, validated state for:
- Last observation time, next permitted observation time, and latest observation category.
- Observations consumed, consecutive errors, and bounded diagnostic information.
- Recovery cursors, stable event identities, or version markers where supported by the source.
Persist this through the existing eventWaitStore and execution consistency domain. Allow constrained waiting → waiting observation updates while keeping ownership, resource, condition, and absolute deadline immutable.
Maintain SQLite/memory parity and cover existing-record compatibility, any required migration, and cleanup. Preserve PR1’s immutable-result and active-slot rules: resolved means a result is stored, and must not be treated as already delivered.
3. Implement one observation path and close notification races
Read current facts when first taking ownership of a wait, including conditions satisfied before registration. Notifications, polling, and restart reconciliation must use the same validation and commit path. A notification triggers verification; it does not replace verification.
Each adapter must define how it closes the read/subscribe race—for example, “establish subscription readiness, then read a snapshot,” or “read, subscribe, then reconcile again.” Event-oriented sources whose facts cannot be reconstructed from current state need replayable history or cursors; a snapshot alone cannot guarantee that events are not missed.
Allow at most one verification in flight per wait. Coalesce notifications arriving during verification into a subsequent check, avoiding overlapping reads without losing changes.
Intake deduplication reduces redundant work. CAS provides the final single-result boundary: after a conflict, reread the record rather than overwriting cancellation or an existing result.
4. Add bounded scheduling and failure handling
Inject the clock, timer, and randomness dependencies. Define Host policy for polling intervals, capped exponential backoff, jitter, per-read timeout, observation limits, and concurrency. Honor source-provided retry timing and limit requests by provider/connection.
Notification bursts remain subject to coalescing and rate limits. Durably account for observation budget before issuing reads so restart cannot reset consumption. Recover waits in batches to avoid a startup request burst.
Proposed deadline rule: preserve results already committed; once a still-waiting record reaches its deadline, expire it before accepting a late read result. A stalled source request must not prevent expiry or resource cleanup.
5. Handle cancellation, invalidation, and restart
Persist cancellation before stopping timers, subscriptions, and in-flight reads. Late callbacks must recheck the record revision and manager-instance identity; they cannot revive cancelled, expired, or removed waits. Cancelling observation does not cancel the underlying task.
Recover records with bounded pagination:
- Resume observation only for waiting records.
- Preserve resolved records for later consumption.
- Expire overdue waits without resetting their deadlines.
- Retain observation budgets and backoff state, and revalidate access eligibility.
- Stop observation when the Session is archived or removed, or its control identity becomes invalid.
A failure affecting one source must not block recovery of other waits. Host shutdown must release subscriptions and in-flight work within a bounded period and prevent callbacks from writing after storage closes.
6. Define Host composition and the PR3 handoff
Implement an internal wait manager depending on the existing storage facade, adapter registry, clock, and trusted eligibility checks. Exercise startup, newly registered waits, cancellation, and shutdown through test composition. This PR supplies fake adapters only; production source registration and public model tools remain in later slices.
Address one existing query boundary explicitly: PR1’s listPending() returns only waiting/resolved. Provide bounded access to expiry and other terminal facts, and document how PR3 discovers them, so a persisted timeout cannot become invisible to the delivery path.
Hand off stable wait IDs, delivery keys, result snapshots, terminal-state queries, and cancellation interfaces. Atomic linkage to continuation, delivery acknowledgement, and one effective successor execution remain PR3 responsibilities.
Acceptance and testing
Use fake adapters and a controlled clock, covering both SQLite reopen recovery and the independent memory provider:
Scenario Required evidence
Condition already holds; registration races a source event Initial reconciliation finds the result without a notification gap
Unchanged state; duplicate or out-of-order notifications Reads remain bounded; results are not committed twice
Another notification arrives during verification The subsequent change is still checked
Network failure, throttling, or a stalled read Unknown state is preserved; backoff and read timeouts apply
Condition never holds; observation budget is exhausted The wait ends under the declared policy without indefinite renewal
Cancellation, expiry, or cleanup races a result Late callbacks cannot overwrite committed state
Exit after reading a result but before committing it Restart can reconcile and commit the result
Commit succeeds but acknowledgement is lost Restart finds the existing result without creating another
Many waits recover together Deadlines and budgets survive; requests do not burst
Host closes or its lease becomes invalid Resources are released; subsequent access is rejected
Assert zero calls to the work model, Goal evaluator, and execution admission. These tests establish observation correctness and durable recovery; they do not establish execution delivery.
Suggested implementation order: finalize adapter and observation-state contracts → extend persistence and compatibility tests → implement the manager and scheduler → add cancellation and recovery fault tests → validate Host lifecycle composition and document the PR3 handoff.
Before submission, run the relevant core, storage, and runtime-host tests, plus build, typecheck, lint, and formatting checks.
<details>
<summary>中文(点击展开)</summary>
<p><strong><span>PR 2 开发计划:Host 观测与条件管理</span></strong></p><p><span>承接 </span><a href="https://github.com/apache/maka/discussions/5920"><span><span><span>Discussion #5920</span></span></span></a><span> 和 </span><a href="https://github.com/apache/maka/pull/6057"><span><span><span>PR1 #6057</span></span></span></a><span>,在已有等待契约、CAS 和持久化基础上,实现 Host 独立观测条件的能力。</span></p><p><span>**交付目标:**通过 fake adapter、可控时钟和真实存储,跑通“发现等待 → 核验条件 → 持续观测 → 保存结果 → 重启后恢复”的闭环,全程不调用工作模型。</span></p><p><span>本 PR 负责观测及结果持久化。工具让出、Goal 调度与后继执行接纳属于 PR3;ShellRun、GitHub 和文件等真实来源属于后续 PR。</span></p><p><strong><span>1. 定义来源适配器契约</span></strong></p><p><span>增加 Host 内部的 adapter 接口,覆盖条件�
�数校验、当前事实读取、可选通知订阅、断线补查和取消。按 </span><code dir="ltr">providerId</code><span>、条件类型及版本选择实现;不支持的组合明确拒绝。</span></p><p><span>适配器负责来源语义,例如资源版本是否匹配、条件是否成立,以及稳定事件身份或恢复游标。管理器统一负责调度、期限、预算和状态提交。权限从可信 Host 上下文取得,不能将存储 lease 或模型参数当作来源访问授权。</span></p><p><span>观测结果与等待生命周期保持区分:</span></p><div><div><div>
观测情况 | 处理方式
-- | --
pending:已成功读取,条件尚未满足 | 保持 waiting,安排下次观测
unknown:网络错误、限流等导致无法判断 | 保持 waiting,保存原因并退避
satisfied:目标条件成立 | 提交 resolved,保存稳定结果身份和证据引用
invalidated:资源或条件已失效 | 提交对应的 resolved 结果
明确不可恢复的来源错误,或达到观测上限 | 按明确策略提交 source_unavailable
到达绝对截止时间 | 提交 expired
</div></div></div><p><span>测试同时断言工作模型、Goal evaluator 和执行接纳调用均为零。验证重点是观测正确性及持久恢复,不将本 PR 的结果去重描述为已经完成执行交付。</span></p><p><span>**建议实施顺序:**固定 adapter 与观测状态契约 → 扩展存储和兼容测试 → 实现管理器与调度 → 补取消及恢复故障测试 → 完成 Host 生命周期验证和 PR3 接口交接。提交前运行 core、storage、runtime-host 相关测试,以及构建、类型、lint 和格式检查。</span></p>PR 2 开发计划:Host 观测与条件管理
承接 [Discussion #5920](https://github.com/apache/maka/discussions/5920) 和 [PR1 #6057](https://github.com/apache/maka/pull/6057),在已有等待契约、CAS 和持久化基础上,实现 Host 独立观测条件的能力。
**交付目标:**通过 fake adapter、可控时钟和真实存储,跑通“发现等待 → 核验条件 → 持续观测 → 保存结果 → 重启后恢复”的闭环,全程不调用工作模型。
本 PR 负责观测及结果持久化。工具让出、Goal 调度与后继执行接纳属于 PR3;ShellRun、GitHub 和文件等真实来源属于后续 PR。
1. 定义来源适配器契约
增加 Host 内部的 adapter 接口,覆盖条件参数校验、当前事实读取、可选通知订阅、断线补查和取消。按 providerId、条件类型及版本选择实现;不支持的组合明确拒绝。
适配器负责来源语义,例如资源版本是否匹配、条件是否成立,以及稳定事件身份或恢复游标。管理器统一负责调度、期限、预算和状态提交。权限从可信 Host 上下文取得,不能将存储 lease 或模型参数当作来源访问授权。
观测结果与等待生命周期保持区分:
观测情况 处理方式
pending:已成功读取,条件尚未满足 保持 waiting,安排下次观测
unknown:网络错误、限流等导致无法判断 保持 waiting,保存原因并退避
satisfied:目标条件成立 提交 resolved,保存稳定结果身份和证据引用
invalidated:资源或条件已失效 提交对应的 resolved 结果
明确不可恢复的来源错误,或达到观测上限 按明确策略提交 source_unavailable
到达绝对截止时间 提交 expired
临时查询失败不能直接变成条件满足或永久来源失败。satisfied 表示约定条件成立,不代表整个任务成功。
2. 扩展最小观测持久状态
PR1 当前只支持等待生命周期转换,尚不能保存持续观测进度。为此增加有界、可校验的观测状态,主要包括:
- 最近观测时间、下一次允许观测时间及观测结果类别。
- 已使用的观测次数、连续错误次数及有限长度的错误信息。
- 来源支持时使用的恢复游标、稳定事件身份或版本标识。
通过现有 eventWaitStore 和执行存储一致性域保存;允许受约束的 waiting → waiting 观测更新,继续禁止修改所属任务、资源、条件和绝对截止时间。SQLite 与 memory 实现保持一致,补齐旧记录兼容、必要迁移及清理测试。
沿用 PR1 的不可变结果和 active-slot 规则:resolved 仍表示结果已保存,不能提前释放为“已经交付”。
3. 实现统一观测流程,闭合通知竞态
首次接管等待时读取当前事实,覆盖“登记前条件已经满足”。通知、定时轮询和重启补查进入同一条校验与提交路径;通知本身只触发核验。
读与订阅之间采用 adapter 明确支持的协议,例如“订阅就绪后读取快照”,或“读取 → 建立订阅 → 再次补查”。事件型来源如果无法从当前状态重建事实,必须提供可回放历史或游标,不能宣称仅靠快照保证不漏事件。
每个等待同时最多执行一次核验。核验期间到达的新通知合并为后续检查,避免并发查询和丢失变化。接收去重用于减少重复工作;最终结果通过 CAS 保证只能提交一次,冲突后重新读取状态,不覆盖取消或已有结果。
4. 实现有界调度与故障处理
管理器注入时钟、定时器和随机源,统一配置轮询间隔、指数退避上限、抖动、单次读取超时、观测次数上限及并发限制。支持来源返回的重试时间,并按 provider/connection 限制请求频率。
通知风暴同样受合并和频率限制约束。观测预算在发起读取前可靠记账,重启不能恢复为零;恢复大量等待时分批调度,避免集中请求。
建议明确截止时间规则:已有持久化结果保持有效;仍处于 waiting 的记录到期后优先过期,迟到读取不得将其改回 resolved。即使查询卡住,也必须能结束等待并释放资源。
5. 处理取消、失效和重启恢复
取消等待时,先持久化取消状态,再停止计时器、订阅和在途读取。迟到回调必须重新检查记录版本与管理器实例身份,不能复活已取消、过期或被清理的等待;取消观察不等于取消底层任务。
启动时分页恢复记录:仅为 waiting 恢复观测;保留 resolved 供后续消费;到期记录直接过期。恢复时保留原截止时间、预算和退避状态,并重新核验访问资格。Session 已归档、删除或控制身份失效时停止观测;单个来源异常不能阻断其他等待恢复。
Host 关闭时有界释放订阅和在途任务,不允许旧实例在存储关闭后继续提交。
6. 明确 Host 接线及 PR3 交接
新增内部等待管理器,依赖现有存储 facade、adapter registry、时钟和可信的有效性校验接口。通过测试装配验证启动、接管新增等待、取消和关闭;本 PR 只提供 fake adapter,不注册生产来源或公开模型工具。
需要特别处理 PR1 的查询边界:listPending() 当前只返回 waiting/resolved。为超时等终态提供有界查询方式,明确 PR3 如何发现对应事实,避免超时已落库却无法被后续交付流程发现。
向 PR3 交接稳定的 wait ID、delivery key、结果快照、终态查询及取消接口。结果与 continuation 的原子关联、交付确认和一次有效后继执行,由 PR3 实现。
验收与测试
使用 fake adapter、可控时钟,同时覆盖 SQLite 重开恢复和 memory provider:
场景 必须证明
条件预先成立、读取与订阅并发 能发现结果,注册窗口不漏事件
状态不变、通知重复或乱序 查询受限,不重复提交结果
核验期间再次收到通知 后续变化仍会被检查
网络失败、限流、读取卡住 保留未知状态,退避和读取超时生效
永远不满足、观测预算耗尽 按策略明确结束,不无限续期
取消、过期、清理与结果竞争 迟到回调不能覆盖已提交状态
读取完成后、结果提交前退出 重启能重新核验并提交
提交成功但确认丢失后重启 找回已有结果,不生成第二份
多个等待同时恢复 保留期限与预算,不形成请求风暴
Host 关闭、lease 失效 资源释放,后续读写被拒绝
测试同时断言工作模型、Goal evaluator 和执行接纳调用均为零。验证重点是观测正确性及持久恢复,不将本 PR 的结果去重描述为已经完成执行交付。
**建议实施顺序:**固定 adapter 与观测状态契约 → 扩展存储和兼容测试 → 实现管理器与调度 → 补取消及恢复故障测试 → 完成 Host 生命周期验证和 PR3 接口交接。提交前运行 core、storage、runtime-host 相关测试,以及构建、类型、lint 和格式检查。
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org