| 1 | # DSH runtime alignment |
| 2 | |
| 3 | This document fixes the executable contracts for the Reasonix runtime |
| 4 | alignment. It is normative for new sessions; legacy fields are display-only. |
| 5 | |
| 6 | ## Todo contract |
| 7 | |
| 8 | `todo_write` accepts one object containing a required `todos` array. Every item |
| 9 | contains exactly `content` and `status`. Content is trimmed and must be non-empty |
| 10 | and unique within the replacement list. Status is one of `pending`, |
| 11 | `in_progress`, or `completed`. The complete array replaces the previous array; |
| 12 | `[]` is a valid clear. Ordering, deletion, replanning, pending-only lists, and |
| 13 | multiple in-progress items are valid. |
| 14 | |
| 15 | The successful result is canonical JSON containing the normalized full list and |
| 16 | counts. Validation failure leaves the prior state unchanged. Result truncation |
| 17 | or duplicate presentation must never replace this state-bearing result. |
| 18 | |
| 19 | Todo lifetime is one real host turn. A committed top-level turn start clears the |
| 20 | current list. Tool rounds, compaction, steering, approval answers, and Ask |
| 21 | answers stay in the same turn and do not clear it. A completed, failed, or |
| 22 | cancelled turn retains its last successful list for display until the next turn |
| 23 | starts. Goal continuation is a new turn and starts empty. |
| 24 | |
| 25 | `complete_step`, hierarchical levels, `activeForm`, `step_id`, host advancement, |
| 26 | approved-plan seeding, Goal restoration, and completed-prefix protection are |
| 27 | retired execution behavior. The hidden `complete_step` tombstone only returns an |
| 28 | actionable retirement error. Old transcript records remain readable. |
| 29 | |
| 30 | ## Runtime ownership |
| 31 | |
| 32 | The session-scoped turn-loop is the execution authority: it uniquely owns |
| 33 | foreground admission, cancellation, the active turn identity, FIFO pending |
| 34 | input, and the level-triggered wake. `session.Runtime` is the persistence |
| 35 | authority: session identity, the single-writer lease, in-memory event receive, |
| 36 | and background write-behind. Durable turn events record facts and never |
| 37 | recreate a live executor after process restart. Runtime phases are `idle`, |
| 38 | `executing`, `cancelling`, `finishing`, `recovery_required`, and `closed`; |
| 39 | compatibility booleans are derived from the phase and current owners. There is |
| 40 | no parallel Activity permit and no second admission gate on `session.Runtime`. |
| 41 | |
| 42 | Stop is session-scoped. A supplied UI turn id is diagnostic only and cannot be a |
| 43 | precondition for cancellation. Cancellation signals the bound turn-loop before |
| 44 | storage, notification, or callback cleanup, and each turn uses a fresh cancel |
| 45 | context so Stop cannot poison the next turn. The existing 15-second tool |
| 46 | straggler grace applies to owned work. A turn whose owned work cannot converge |
| 47 | is represented as `recovery_required`; late work cannot resume that turn or |
| 48 | commit a newer runtime generation. The Runtime binds the turn-loop with an |
| 49 | exact generation so an old Controller unbind cannot clear a replacement's |
| 50 | control. |
| 51 | |
| 52 | Ask, approval, Plan, recovery, and MCP decisions use `PendingPromptOwner` as one |
| 53 | registry. Every identity binds prompt id, kind, turn, and runtime epoch. Resolve |
| 54 | is single-winner and rejects stale runtime or turn identities. Callbacks run |
| 55 | outside the registry lock. Cancellation drains every registered prompt, and the |
| 56 | runtime snapshot derives `pendingPrompt` from this registry rather than an |
| 57 | approval-only side channel. |
| 58 | Resolution events preserve `answered`, `rejected`, or `cancelled`. A Plan |
| 59 | resolution and its Plan state are one logical batch. Turn termination closes any |
| 60 | remaining requests, so a restart cannot recreate answerable authority. |
| 61 | |
| 62 | ## Capability catalog |
| 63 | |
| 64 | `skill.Store.Snapshot` is the discovery boundary. A generation has immutable, |
| 65 | stable-order candidates and an O(1) name index. Concurrent cold callers share a |
| 66 | single discovery scan; cancelled waiters do not cancel a scan needed by other |
| 67 | callers. Invalidation retains the previous complete generation until the new |
| 68 | one is complete, and refresh retries are bounded to two generations. Create, |
| 69 | update, delete, and detected root changes invalidate the generation. |
| 70 | Windows uses a cancellable bounded polling generation because |
| 71 | `ReadDirectoryChangesW` registration can block teardown; other platforms keep |
| 72 | fsnotify. Both paths cover external edits and missing-root creation. |
| 73 | |
| 74 | `use_capability search` consumes one catalog and one MCP schema snapshot. Skill |
| 75 | argument lookup uses the store index and cannot rescan roots per result. List is |
| 76 | paged at 50 entries by default and 100 maximum. Its cursor binds to the catalog |
| 77 | fingerprint; a changed catalog rejects the cursor and requires a restart. |
| 78 | Discovery never connects an MCP server or calls `tools/list`. |
| 79 | |
| 80 | ## Compatibility and cache boundary |
| 81 | |
| 82 | The changed todo schema and capability pagination are one intentional stable |
| 83 | prefix revision. Within the new contract, tool order, JSON schema bytes, and |
| 84 | the delivery marker stay deterministic; runtime state, timestamps, catalog |
| 85 | generations, and todo contents never enter the system prompt. |
| 86 | |
| 87 | Legacy todo fields, completion declarations, Goal todo payloads, and dismissal |
| 88 | records are retained only so existing history can be shown. Continuing work |
| 89 | does not activate them. New Goal state writes no todo payload, approved Plans do |
| 90 | not generate todo calls, and the frontend keeps dismissal only as an in-memory |
| 91 | view preference. |
| 92 | |
| 93 | ## v3 session boundary |
| 94 | |
| 95 | `internal/session` defines the ownership-cutover codec |
| 96 | `reasonix.session.linear/v3.1`. The retired `reasonix.session.events/v3` |
| 97 | prototype and `reasonix.session.linear/v3` preview cannot be opened for |
| 98 | execution and must pass through the restricted importer. Unknown required |
| 99 | events, damaged complete records, and unexplained projection operations remain |
| 100 | read-only and cannot resume execution. A session has one active write handle. Fork and edit-resend create an |
| 101 | independent child session instead of adding writable heads to one log. One |
| 102 | physical JSONL record contains one complete logical batch with contiguous event |
| 103 | sequences. Cold reads omit an unterminated tail. After acquiring the exclusive |
| 104 | lease, a writer preserves its original bytes and truncates back to the last |
| 105 | complete batch before restart recovery. Complete malformed records, gaps, |
| 106 | unknown codecs, and unknown required events fail closed. |
| 107 | |
| 108 | Three layers own distinct facts. The in-memory `Session` owns the typed event |
| 109 | log, sequence allocation, the operation-idempotency table, and the projection. |
| 110 | `PersistenceBinding` owns the write-behind queue, the durable watermark, and the |
| 111 | single drain chain. The physical `Store` owns the JSONL bytes, the writer lease, |
| 112 | and the rebuildable offset index. The handle exposes no projection, operation |
| 113 | table, or accepted commit list, so business state cannot be reconstructed from |
| 114 | disk layout. `Session.PrepareBatch` copies the payload, validates the schema, and |
| 115 | computes the operation digest before the commit lock is taken; the digest |
| 116 | covers only caller-supplied fields, so retrying one logical batch stays |
| 117 | idempotent. `Session.CommitPrepared` then performs the idempotency check, |
| 118 | sequence assignment, whole-batch append, and projection swap under one short |
| 119 | memory lock. A batch reaches the binding's queue after the commit lock is |
| 120 | released, and that enqueue never performs file I/O. |
| 121 | |
| 122 | Create returns only after the in-memory Session, writer handle, and immutable |
| 123 | session ID have been published. `session/title` and `session/config` are the |
| 124 | authoritative title and model-selection facts; the latter includes the |
| 125 | connection revision. Rebuilding an Agent replaces model context and appends |
| 126 | configuration without creating another Controller that competes for the same |
| 127 | writer. Directory indexes and legacy model sidecars are not v3 state sources. |
| 128 | |
| 129 | The host owns each published Runtime through a `RuntimeOwner`, which is the only |
| 130 | authority that can terminate an exact instance. `Service.Open` attaches a client |
| 131 | by returning a `ClientBinding` and never hands out close authority, so a failed |
| 132 | attach can only withdraw the caller's own grant. Controllers receive a |
| 133 | `ClientBinding` that may send and observe work, but releasing a tab or connection |
| 134 | cannot close the shared writer or cancel an active turn. The last binding marks an idle |
| 135 | Runtime for retirement; an active Runtime finishes under host ownership and is |
| 136 | retired afterward. Prepare failures and stale cleanup callbacks can discard |
| 137 | only the exact candidate or instance they own. Cancellation reaches the bound |
| 138 | turn-loop without first taking the Runtime or persistence lock, so its receipt |
| 139 | does not wait for a commit, observer, or disk operation. Session event |
| 140 | commits require only that the current Controller still holds the write lease; |
| 141 | Stop does not revoke `history/replace`, `turn/end`, interaction wrap-up, or |
| 142 | diagnostic writes. Session accept is first; the compatibility ledger and |
| 143 | frontend projection advance only after Session receives the batch. |
| 144 | |
| 145 | `Append` means the live session accepted a fact. It validates the whole batch, |
| 146 | assigns sequences, retains an immutable copy, updates the in-memory projection, |
| 147 | and publishes to observers. It does not imply durability. The first pending |
| 148 | event starts a fixed 200 ms write-behind window; later appends do not extend the |
| 149 | deadline, and each handle has one drain chain. A background write failure keeps |
| 150 | the original ordered batch and pauses automatic retry. The next explicit |
| 151 | `Flush` retries safely. An uncertain write or fsync result becomes an explicit |
| 152 | uncertain persistence state and never causes a tool rerun. |
| 153 | |
| 154 | The agent flushes before every model adapter call and before entering a |
| 155 | top-level tool body. A failed checkpoint prevents the downstream call. Todo, |
| 156 | approval, assistant-message, and `turn/end` appends do not force individual |
| 157 | flushes. Idle is not a durability guarantee. Export, cold-disk verification, |
| 158 | writer handoff, and clean shutdown wait for an explicit flush. Live snapshots |
| 159 | carry both event and durable sequences, and the former may be newer. |
| 160 | |
| 161 | The new root is `sessions-v4`. Legacy continuation resolves the transcript and |
| 162 | its paired preview event directory as one import decision, and it classifies |
| 163 | before it publishes anything. It freezes the legacy transcript under its write |
| 164 | lease and the paired preview under directory and writer ownership locks, then |
| 165 | parses both from the frozen copies only. A structured preview wins when the |
| 166 | frozen legacy messages equal its messages or are a strict prefix of them; the |
| 167 | transcript wins when the preview is the strict prefix; divergent work remains |
| 168 | read-only and produces no target at all. Only after the source decision is final |
| 169 | does migration build and validate one v3 session in a same-filesystem temporary |
| 170 | directory and publish it by atomic rename. The canonical source path, head, digest, and target codec determine |
| 171 | the target ID, so identical input is idempotent and changed input creates a new |
| 172 | target. Original artifacts are copied byte-for-byte under `legacy/`; unknown |
| 173 | content is never decoded and re-encoded. Goal import excludes todos and |
| 174 | automatic continuation. Old unfinished runtime and approval records remain |
| 175 | history and never recreate live authority. |
| 176 | |
| 177 | Each reachable head in a legacy schema-2 log migrates to a separate linear |
| 178 | session. `legacyHeadId` participates in both the deterministic target ID and the |
| 179 | migration-map key. Catalog listing reads the manifest head, the log revision (stat plus a bounded |
| 180 | head/tail identity sample), and rebuildable scalar metadata only. It never |
| 181 | builds the offset index. Cache entries are pinned to the exact log revision, so |
| 182 | an appended byte invalidates the entry without replaying the log. Missing |
| 183 | metadata is reported as pending and rebuilt with at most two concurrent |
| 184 | streaming reducers. Warm listing reads no event bodies. Cold |
| 185 | history pages validate records as a stream and stop at the requested page |
| 186 | boundary rather than materializing the complete log. |
| 187 | |
| 188 | At the final coordinated cutover, Desktop host RPC moves to protocol version 5. |
| 189 | The Electron shell sends and validates the version from its embedded command |
| 190 | contract, so shell and service cannot drift through separately maintained |
| 191 | constants. Serve advertises `execution-v2`, `session-history-v1`, and |
| 192 | `session-identity-v1`, and `session-ownership-v1`. New Desktop builds reject execution control against a |
| 193 | remote missing any capability instead of emulating the new state machine over |
| 194 | old RPCs or path identities. |
| 195 | |
| 196 | ## Regression matrix |
| 197 | |
| 198 | - Whole-list tests cover empty, pending-only, multiple in-progress, out-of-order |
| 199 | completion, trimming, duplicate content, unknown fields, and invalid status. |
| 200 | - Replay tests must cover equal counts with different statuses, duplicate output |
| 201 | presentation, compaction, branch isolation, and a new-turn clear. |
| 202 | - Catalog tests use 1,154 and 10,000 candidates and assert scan/read counts: |
| 203 | exactly one shared cold scan, zero warm scans, and O(1) indexed skill lookup. |
| 204 | - Cancellation tests use channels as ordering barriers for model streams, |
| 205 | serial and parallel tools, prompt publication, answer/cancel races, hooks, |
| 206 | discovery, and compaction. Time-based sleeps are not correctness evidence. |
| 207 | - Runtime tests assert prompt registry projection, stale answer rejection, |
| 208 | session-level Stop without a turn id, and explicit recovery-required state. |
| 209 |