Repository navigation
tracking(runtime-host): condition-driven Session waits and wake-ups #5926
Description
Activity
- addedenhancementNew feature or requestNew feature or requesttrackingTracking or umbrella issueTracking or umbrella issue
on Oct 2, 2026 PR 1 implementation plan: event-wait contracts and dormant persistence
- Source: Discussion #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:
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
WaitForEventtool, 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.tsGoalControlLeasecarries Goal ID and generation;pendingContinuationalready existsReuse control-identity types/validation rules without changing Goal storage packages/storage/src/goal-authority.tsExisting 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.tsExecutionPersistenceis an indivisible consistency domainAdd eventWaitStore; do not open separate storage in Hostpackages/storage/src/local-execution-persistence.tsOpens/closes execution stores as one group Add the SQLite backend using the same pattern, including partial-open cleanup packages/storage/src/execution-stores.tsThe 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.tsBaseline 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.tsShared runtime.sqliteconnection, transactions, and schema-scope registryReuse acquireOperationalStateDatabaseand the workflow scopepackages/storage/src/operational-target-schema.tsBuilds 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.tsIndependent memory reference provider, without SQLite Add maps and transactions within the same memory authority sqlite-session-metadata-store.ts/conversation-operational-state.tsArchive, retirement, and purge already clean up Goal authority Clean up waits within the same transaction boundaries Do not use the
SQLITE_RUNTIME_SCHEMA_VERSIONnumbering 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 itsPRAGMA user_versionnumbering 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 inrecord-schema.ts: decode inputs fromunknown, rather than accepting external data through type assertions.Suggested exports:
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:
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.
resourceandconditionare persisted descriptions. PR 1 does not interpret their business semantics; PRs 2/3 must validate types and parameters through registered adapters.deliveryKeyis fixed toevent-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.resolvedmeans the result was recorded. It does not mean a model was called or a successor was admitted.- Do not introduce
consumed,running, ordelivered. Without the actual admission transaction, a standalonemarkDelivered()must not pretend delivery completed. PR 3 will add linkage/release rules around the real cross-domain transaction. receiptKeyidentifies 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 nullprovider/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
parametersare 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 onJSON.stringifysilently dropping fields. - Require
createdAt <= updatedAtanddeadlineAt > createdAt. - State-specific timestamps must be within
[createdAt, updatedAt]; requireexpiredAt >= deadlineAt.observedAtis the Host observation time, not the provider's original event timestamp. deliveryKeymust equal the derived value.- Validate
goalControlLeasestructurally, 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
userAuthorizedfields. 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 priorResolutionAny 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. AddeventWaitStoreto the execution storage group.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, andclose:read({sessionId, waitId}) listSession({sessionId, afterWaitId?, limit}) listPending({afterWaitId?, limit}) commit(input) close()limitis required and must be 1–200. Use ascending ASCIIwaitIdkeyset pagination; read limit+1 to determinenextCursor. Do not implement an offset-based full scan.listPendingreturns onlywaitingandresolvedfor 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.
readmatches 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
- Strictly decode input and verify that input IDs agree with the record.
- In the backend's same write transaction, verify that the Session exists, is not archived, and has no tombstone.
- Read the existing wait. If the wait ID belongs to another Session, reject the identity mismatch; do not transfer ownership.
- Compare expected revision. On mismatch, return
revision_conflictwithout writing. - Require waiting on creation; validate immutable fields and the transition matrix on update.
- If the record is identical and expected revision is correct, return the current snapshot without incrementing revision.
- Check whether another wait occupies this Session's active slot.
- 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=nullfor 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: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.sqliteviaacquireOperationalStateDatabase(root). Do not create another file or hold aDatabaseSyncoutside composition. - Use existing
transaction('write', ...)for Session validation, CAS, slot checking, and writing together. Unique indexes are the durable backstop; an in-processMapis 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_revisionin 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.tsbuilds 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.receiptKeyand active-slot/CAS tests are not a complete event-deduplication system.
6. ExecutionPersistence wiring and memory reference implementation
6.1 Integration points
execution-persistence-provider.ts: addreadonly eventWaitStore: EventWaitAuthorityRepository.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.execution-stores.ts: addeventWaitStoretoInteractiveExecutionStoresWriter; wrap every operation through existing lease/active-operation handling.test-only/memory-execution-persistence.ts: construct the memory wait store using the sameMemoryExecutionAuthorityand return it through the existing scoped proxy.
6.2 Facade requirements
- Add the
InteractiveEventWaitAuthorityWriterbrand andauthenticateInteractiveEventWaitAuthorityWriter. - Follow
goal-authority.tsso 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, usingrows<EventWaitSnapshot>(state, 'eventWaits').MemoryExecutionAuthority.read/writesupplies 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/afterCommitfault hooks, using a stable operation name such aseventWait.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
deleteGoalAuthoritiesinsqlite-session-metadata-store.tscurrently serves archive and batch retirement. Extend/rename it as an explicit Session workflow-authority cleanup helper, deleting waits in the same transaction.- Preserve the
databaseLeaseguard 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.writeand 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.tsTypes, decoder, limits, deliveryKey, transition validation packages/core/src/__tests__/event-wait.test.tsValid/invalid core contracts and transitions packages/storage/src/event-wait-authority.tsRepository types, SQLite implementation, controlled facade; split a private SQLite implementation if needed for size packages/storage/src/test-only/memory-event-wait-authority.tsIndependent memory backend packages/storage/src/__tests__/event-wait-authority.test.tsStore, lease, reopen, CAS, and migration-related tests Modify
File Change packages/core/package.jsonExport ./event-waitusing the existing formatpackages/storage/package.jsonExport ./event-wait-authorityif other workspaces need its types; do not expose raw DB handlesexecution-persistence-provider.tsRequired eventWaitStoreportlocal-execution-persistence.tsOpen/close execution-stores.tsAuthenticated group facade and close/error paths sqlite-workflow-schema.tsTable, indexes, workflow schema bump sqlite-session-metadata-store.tsAtomic archive/retirement cleanup conversation-operational-state.tsPurge cleanup test-only/memory-execution-persistence.tsProvider integration and purge test-only/memory-execution-session.tsMemory archive/retirement cleanup __tests__/execution-provider-conformance.test.tsShared provider scenarios and close/fault boundaries __tests__/operational-state-store.test.tsUpgrade from old workflow scope, rejection of future versions, target schema __tests__/sqlite-workflow-store.test.tsNew workflow structure and preservation of existing data __tests__/sqlite-session-metadata-store.test.tsCleanup and standalone behavior __tests__/operational-state-backup.test.tsWait readability after full-root backup/restore __tests__/session-copy-cleanup.test.tsWhere 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 constructingExecutionPersistenceand 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
- Confirm the baseline and schema. Record them in the PR work notes without editing existing user documents.
- Add core tests and types first. Fix shapes, bounds, and transitions so backends do not interpret them differently.
- Add workflow DDL and Local store tests. Verify CAS, slots, reopen, and transitions before completing the facade.
- Implement memory storage and run the same scenarios. Do not share SQLite operations or use Local fallback to manufacture parity.
- Wire ExecutionPersistence and the authenticated group. Update required ports/fixtures and verify close/partial-open failure behavior.
- Complete cleanup and migration coverage. Archive, retirement, purge, backup/copy tests precede any production consumer.
- Run focused and workspace validation. Preserve original failures and rerun results rather than silently dismissing environmental failures.
- Review the diff. Confirm no watchers, tool registration, Goal behavior changes, or hidden permission modes.
- 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.jsonfor the implementation environment. If dependencies are incomplete, use the standard repository installation process. Do not update unrelated lockfile dependencies for this PR.During development:
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:
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 withnode --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.
-
resolvedis 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:
- Actual baseline and commit hash.
- Changed files and any necessary deviations from this plan.
- A short explanation of contracts, schema, API, and cleanup semantics.
- Commands run and actual results, including failures, baseline reproduction, and checks not run.
- 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.
中文(点击展开)
# PR 1 执行计划:事件等待契约与未启用的持久化基础- 来源:Discussion #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. 开始前检查与范围冻结
在开始改代码前执行:
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.tsGoalControlLease含 Goal ID 和 generation;已有pendingContinuation引用控制身份的类型/校验规则,不改 Goal 存储结构 packages/storage/src/goal-authority.tsCAS authority、lease facade、backend close 的现有范式 参考结构,但不要复制一个带隐式 Local fallback 的新访问路径 packages/storage/src/execution-persistence-provider.tsExecutionPersistence是不可分的一致性域增加 eventWaitStore,不在 Host 另开存储packages/storage/src/local-execution-persistence.ts统一打开/关闭各执行存储 按相同模式接入 SQLite backend,保持部分打开失败的清理 packages/storage/src/execution-stores.tsauthenticated 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.tsruntime.sqlite共享连接、事务、schema scope registry复用 acquireOperationalStateDatabase和 workflow scopepackages/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解码,不靠类型断言接受外部数据。建议导出:
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、模型调用或任意动作字段:
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 可为 nullprovider/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属性到执行存储组。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:read({sessionId, waitId}) listSession({sessionId, afterWaitId?, limit}) listPending({afterWaitId?, limit}) commit(input) close()limit必填,范围 1–200。按 ASCIIwaitId升序 keyset 分页;查 limit+1 决定nextCursor,不使用 offset 全量扫描。listPending只返回waiting和resolved,用于未来重启校准;这个查询不是执行队列或启动授权。- 分页保证稳定键次序,不承诺跨并发写入的全库快照。未来观测管理器需要处理反复扫描和去重。
read使用 Session 和 wait ID 双条件,不能用一个 wait ID 泄漏其他 Session 的记录。- 所有读返回隔离副本;修改读结果不得修改已提交状态。
- 外部契约可以返回同步值或 Promise,authenticated facade 统一为 Promise,保持现有 provider 风格。
4.1 一次 commit 的顺序
- 严格解码并检查入参 ID 与 record 一致。
- 在 backend 的同一个写事务中,检查 Session 存在、未归档、未被 tombstone 标记。
- 读取当前 wait。wait ID 已属于别的 Session 时拒绝 identity mismatch,不迁移归属。
- 比较 expected revision:不一致返回
revision_conflict,不写入。 - 创建时要求 waiting;更新时核验不可变字段和状态矩阵。
- 完全一致且 expected revision 正确时返回当前 snapshot,不增加 revision。
- 检查该 Session 其他 wait 是否占用 active slot。
- 原子写入新的状态与 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 是目标结构,实施时按现有缩进/校验约定写入: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 接线位置
execution-persistence-provider.ts:增加readonly eventWaitStore: EventWaitAuthorityRepository。local-execution-persistence.ts:打开 SQLite store,加入统一逆序 close 栈,并随整个 group 返回;部分打开失败也会 close。execution-stores.ts:在InteractiveExecutionStoresWriter增加eventWaitStore;所有方法包装进既有 lease/active-operation 路径。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 时的
databaseLeaseguard,不使原独立存储测试失败。 - 硬删除由 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.tsrepository types、SQLite 实现、受控 facade;如过大可拆 SQLite 私有实现 packages/storage/src/test-only/memory-event-wait-authority.ts独立 memory backend packages/storage/src/__tests__/event-wait-authority.test.tsstore、lease、重开、CAS、迁移相关测试 修改
文件 修改内容 packages/core/package.json增加 ./event-wait导出,沿用现有 export 格式packages/storage/package.json如其他 workspace 需要类型,增加 ./event-wait-authority;不公开原始 DB handlesexecution-persistence-provider.tsrequired eventWaitStoreportlocal-execution-persistence.tsopen/close execution-stores.tsauthenticated group facade 与 close/error paths sqlite-workflow-schema.ts表、索引、workflow schema bump sqlite-session-metadata-store.ts归档/退役的原子清理 conversation-operational-state.tspurge 清理 test-only/memory-execution-persistence.tsprovider 接入、purge test-only/memory-execution-session.tsmemory 归档/退役清理 __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. 建议执行顺序
- 确认基线和 schema。 写入 PR 工作记录,不动现有用户文档。
- 先加 core 测试及类型。 固定形状、边界和状态矩阵,避免 backend 各自解释。
- 加入 workflow DDL 与 Local store 测试。 先验证 CAS、slot、重开和合法转换,再补 facade。
- 实现 memory store 并跑同一组场景。 不共享 SQLite 操作或以 Local fallback 让 parity 假通过。
- 接入 ExecutionPersistence 与 authenticated group。 更新 required ports 和夹具,检查 close/部分打开失败。
- 补清理与迁移。 归档、退役、purge、backup/copy 测试要在开放任何生产消费者前完成。
- 运行聚焦与 workspace 验证。 保留失败原文和重跑结果,不把环境失败简单吞掉。
- 审查 diff。 确认没有 watcher、工具注册、Goal 行为修改或隐藏权限模式。
- 提交。 使用项目要求的生成式工具 attribution;未经用户授权不 push 或创建 PR。
11. 验证命令
根据实施环境使用
package.json声明的 Node/npm 工具链。已有依赖若不完整,先执行仓库标准安装;不要为了本 PR 更新锁文件中的无关依赖。开发时:
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 执行等价命令。新增文件名改变时同步调整命令,不悄悄省略测试。
提交前至少:
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 最终应输出:
- 实际基线与提交 hash。
- 文件变更列表及任何对本计划的必要偏离。
- 契约、schema、API、清理语义的简短说明。
- 执行过的验证命令与真实结果,包含失败、基线复现或未运行项。
- PR 2/3 接口交接:如何列出待校准等待、提交结果,以及哪些能力尚未实现。
遇到以下情况应先停下来说明,而不是扩大 PR
- 无法在现有 execution consistency domain 内保证等待与 Session 清理一致性。
- 需要修改实际模型执行、Goal 计费或恢复策略才能让存储测试成立。
- 发现 provider 或迁移主干发生实质变化,本文版本/文件路径不再适用。
- 需要用一个独立“已交付”标记掩盖不存在的 continuation 接纳事务。
- 测试只能靠跳过权限、放宽 schema 校验或隐式落回 Local 才能通过。
这些问题意味着边界需要重新讨论,而不是应在 PR 1 内实现整套事件唤醒。
计划依据为定向代码核查,新增设计仍需代码评审。该文档没有修改生产代码,也没有声明相关测试已经运行。
This issue follows Discussion #5920: condition-driven waiting and wake-up 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
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:
Acceptance:
A local implementation handoff is available in the PR 1 plan. 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:
Acceptance:
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:
Acceptance:
PR 4: ShellRun terminal-state adapter
Objective: ship the first usable “start a long test and continue handling its result” flow.
Changes:
orphanedstate explicitly. A notification triggers verification rather than replacing it.Acceptance:
PR 5: GitHub CI
Objective: observe remote CI through the same protocol without requiring Webhook setup.
Changes:
Acceptance:
PR 6: PR/review collaboration events
Objective: reuse GitHub integration for merge, closure, and explicit review conditions.
Changes:
Acceptance:
PR 7: external files and deterministic data readiness
Objective: continue after a user or another program delivers data, without guessed sleep intervals.
Changes:
Acceptance:
Merge order and shared acceptance
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:
中文(点击展开)
本 issue 由 [Discussion #5920:条件驱动的等待与唤醒](https://github.com//discussions/5920) 引申而来,用于跟踪讨论中的七个实施 PR。讨论提供问题背景、架构及边界;本 issue 将其整理成可逐项评审、验证和交付的修改计划,不代表全部设计已经获得批准。为什么做
等待 CI、后台测试、PR 协作状态或外部数据时,当前任务可能反复让模型查询状态,或让同一个执行长时间等待工具返回。Goal 的定时 waiting 路径也可能在外部没有变化时启动新的检查 Turn,消耗原本用于实际工作的轮次。
我们希望让 Agent 登记明确的等待条件,由 Host 独立观察,只有条件满足、失效、超时或出现需要处理的异常时,才经现有执行接纳继续原任务。这可以减少无效模型参与、释放纯等待占用的执行位置,并统一期限、取消、状态核验和结果交付。
目标与边界
PR 1:等待契约与持久化基础
**目标:**将“哪个任务正在等待哪个条件”变成有身份、有版本、可更新且可恢复的事实,而不是只留在提示词和内存中。
修改内容:
验收:
详细执行方案另附本地计划:PR 1 实施计划。发布 issue 时可移除这一相对链接,或改为实际附上的计划地址。
PR 2:Host 观测与条件管理
**目标:**在不调用工作模型的情况下,持续观察仍然有效的等待,并形成明确结果。
修改内容:
验收:
PR 3:执行桥接、工具让出与 Goal 整合
**目标:**让“登记等待”和“结果到来”分别成为安全的执行让出与后续接纳边界。
修改内容:
验收:
PR 4:ShellRun 终态接入
**目标:**交付“启动长测试,完成后继续处理结果”的首个用户可用闭环。
修改内容:
orphaned等事实转换为明确结果。完成通知只作为触发,不能代替对可靠记录的核验。验收:
PR 5:GitHub CI
**目标:**在不要求用户配置 Webhook 的情况下,用同一协议跟踪远端 CI。
修改内容:
验收:
PR 6:PR/review 协作事件
**目标:**复用 GitHub 接入,支持合并、关闭及明确的评审条件。
修改内容:
验收:
PR 7:外部文件与确定性数据就绪
**目标:**支持用户或其他程序交付数据后继续处理,不依靠猜测式 sleep。
修改内容:
验收:
合并顺序与共同验收
PR 1–3 可分别评审,但不能各自引入一套运行状态权威。PR 3 与 PR 4 可按体量合并。基础代码未开放前可以逐步建设;首个用户闭环必须同时具备取消、期限、去重、权限和恢复安全,不能将这些留作上线后补丁。
总体完成条件:
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.