Skip to content

Commit dee4dca

Browse files
FUDCoclaude
andauthored
docs: Updated Ken protocol assessment (#805)
The previous analysis incorrectly identified gaps on the send side. With crank buffering, output validity and deferred transmission are achieved: messages only reach RemoteHandle via the run queue, which means the originating crank has already committed. The remaining gaps are on the receive side: - Done table for exactly-once delivery (deduplication) - FIFO enforcement for ordering <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only change that reclassifies protocol property coverage; no code paths or runtime behavior are modified, but it may influence engineering priorities if misinterpreted. > > **Overview** > Updates `docs/ken-protocol-assessment.md` to state that crank buffering *fully achieves* send-side **output validity** and **deferred transmission**, since messages reach `RemoteHandle` only after the originating crank commits via the run queue. > > Reframes the remaining work as *receive-side* gaps only, adding concrete failure scenarios and outlining needed mechanisms for **exactly-once deduplication** (a `Done` table / processed-seq tracking) and **FIFO ordering** (buffer/reorder out-of-order messages), and refreshes the summary/progress tables accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit a7446e5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b8fdf8a commit dee4dca

File tree

1 file changed

+97
-112
lines changed

1 file changed

+97
-112
lines changed

docs/ken-protocol-assessment.md

Lines changed: 97 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ Key aspects:
4747
| Crash-safe persistence || Write message first, then update nextSendSeq |
4848
| Local recovery || Restore seq state, restart ACK timeout |
4949
| **Transactional turns** || Crank buffering defers outputs until crank commit |
50-
| **Deferred transmission** | **Partial** | Buffered within kernel, but RemoteHandle transmits immediately on flush |
50+
| **Deferred transmission** || Outputs reach RemoteHandle only after originating crank commits |
51+
| **Output validity** || Crank buffering ensures outputs escape only after commit |
52+
| **Atomic checkpoint** || Database savepoints make crank state changes atomic |
5153

52-
### Recent Improvements: Crank Buffering
54+
### Crank Buffering (Issue #786)
5355

54-
The crank buffering feature (issue #786) significantly improves our alignment with Ken:
56+
The crank buffering feature achieves Ken's core send-side properties:
5557

56-
**Our new crank model:**
58+
**Our crank model:**
5759
```
5860
crank_start(deliver one item from run queue)
5961
→ create database savepoint
@@ -64,163 +66,146 @@ crank_end:
6466
→ if failure: rollback to savepoint, discard buffer
6567
```
6668

67-
This achieves Ken's core property that **outputs are only externalized after successful turn completion**. Within the kernel:
69+
This achieves Ken's property that **outputs are only externalized after successful turn completion**:
70+
6871
- `enqueueSend(target, message, immediate=false)` buffers sends
6972
- `enqueueNotify(endpoint, kpid, immediate=false)` buffers notifications
7073
- `resolvePromises(endpoint, resolutions, immediate=false)` buffers all resolution effects
71-
- On successful crank: `#flushCrankBuffer()` moves items to run queue
74+
- On successful crank: `#flushCrankBuffer()` moves items to persistent run queue
7275
- On rollback: buffer is discarded along with database changes
7376

74-
### What We're Still Missing or Differs
75-
76-
#### 1. Deferred Network Transmission (Gap)
77-
78-
**Ken's model:**
79-
```
80-
persist checkpoint → THEN transmit to network
81-
```
82-
83-
**Our model:**
84-
```
85-
crank completes → flush to run queue → eventually delivered to RemoteHandle
86-
RemoteHandle: persist message → transmit immediately
87-
```
88-
89-
While crank outputs are now buffered until crank commit, when a message reaches `RemoteHandle` for remote transmission, it is persisted and transmitted in quick succession. A crash between persist and transmit could result in the message being retransmitted on recovery (which is fine due to idempotency), but more critically, there's no coordination ensuring the kernel's crank commit happens before network transmission.
90-
91-
**Impact**: If RemoteHandle transmits a message and then the kernel crashes before its crank fully commits, the remote has received a message that the local kernel will "forget" on recovery. This violates output validity.
77+
**Why output validity is achieved**: When a message destined for a remote reaches `RemoteHandle`, it arrives via the run queue. Items only reach the run queue after the originating crank commits. Therefore, by the time `RemoteHandle` persists and transmits a message, the crank that produced it has already committed. The transmitted message corresponds to committed local state.
9278

93-
**Mitigation needed**: RemoteHandle should only transmit messages after the originating crank has been fully committed. This requires coordination between the kernel's crank lifecycle and RemoteHandle's transmission timing.
79+
`RemoteHandle` persists messages to `remotePending` before transmitting for a different reason: to enable retransmission on recovery if the transmission or ACK is lost. This is part of the at-least-once delivery mechanism, not the output validity mechanism.
9480

95-
#### 2. Done Table / Duplicate Detection (Gap)
81+
### Remaining Gaps (Receive Side)
9682

97-
Ken maintains a `Done` table ensuring:
98-
- Each message delivered to application **at most once**
99-
- FIFO ordering enforced via `next_ready()` considering seq + sender ID
83+
The remaining gaps are on the **receive side** of remote messaging:
10084

101-
We track `highestReceivedSeq` but only for ACK purposes. We don't have explicit duplicate detection for incoming messages. If the remote retransmits a message we already processed (but before we ACKed), we could deliver it twice.
85+
#### 1. Done Table / Duplicate Detection (Gap)
10286

103-
#### 3. Output Validity (Improved, but Partial)
87+
Ken maintains a `Done` table ensuring each message is delivered to the application **at most once**. The `Done` table is updated atomically with the application state at crank commit.
10488

105-
Ken guarantees outputs could have resulted from failure-free execution because:
106-
- Outputs are buffered during a turn
107-
- A crash during processing loses all outputs from that turn
108-
- Only committed outputs escape to the outside world
89+
We track `highestReceivedSeq` per remote, but there's a gap in how it interacts with delivery:
10990

110-
**Improvement**: With crank buffering, kernel-internal outputs (sends to local vats, notifications) are now properly buffered and discarded on rollback. A crash mid-crank no longer results in partial kernel state.
91+
**Scenario A - Update on receive, before delivery:**
92+
1. Receive message seq=5 from remote R
93+
2. Update `highestReceivedSeq` to 5
94+
3. Add message to run queue for delivery to local vat
95+
4. Crash before delivery crank commits
96+
5. On recovery: `highestReceivedSeq=5` suggests we processed it
97+
6. Remote retransmits seq=5, we ignore it
98+
7. **Message lost** - vat never received it
11199

112-
**Remaining gap**: For remote messages, the gap described in #1 above means network transmissions could still escape before the crank is fully committed.
100+
**Scenario B - Update after delivery:**
101+
1. Receive message seq=5, add to run queue
102+
2. Delivery crank runs, vat processes message, crank commits
103+
3. Crash before `highestReceivedSeq` is updated
104+
4. On recovery: `highestReceivedSeq < 5`
105+
5. Remote retransmits seq=5, we deliver again
106+
6. **Duplicate delivery** - vat receives it twice
113107

114-
#### 4. Atomic Checkpoint (Improved)
108+
**What's needed**: A `Done` table (or equivalent) that is updated atomically with the delivery crank commit, and checked before delivering incoming messages.
115109

116-
Ken atomically checkpoints `(turn, app_state, Q_out, Done)` together at end of turn.
110+
#### 2. FIFO Enforcement on Receive (Gap)
117111

118-
**Improvement**: The kernel now uses database savepoints to make crank state changes atomic. The `CrankBuffer` contents are flushed atomically with the crank commit.
112+
Ken enforces per-sender FIFO ordering via `next_ready()` which only delivers the next expected sequence number.
119113

120-
**Remaining gap**: RemoteHandle's message persistence is separate from the kernel's crank commit. These two persistence operations are not atomic with respect to each other.
114+
If messages arrive out of order from the network (e.g., seq 1, 3, 2):
115+
- We should deliver seq=1
116+
- Buffer seq=3 until seq=2 arrives
117+
- Deliver seq=2, then seq=3
121118

122-
#### 5. FIFO Enforcement on Receive (Gap)
119+
We don't currently enforce this ordering on the receive side. Out-of-order network delivery could result in out-of-order application delivery.
123120

124-
Hold out-of-order messages until predecessors processed:
125-
- Track expected next seq per sender
126-
- Buffer messages that arrive out of order
127-
- Deliver in sequence order only
128-
129-
We don't currently enforce FIFO delivery order on the receive side.
121+
**What's needed**: Track expected next seq per remote sender, buffer out-of-order messages, deliver in sequence order only.
130122

131123
### Summary Table
132124

133125
| Ken Property | Our System | Notes |
134126
|--------------|------------|-------|
135-
| Exactly-once delivery | **Partial** | At-least-once with no duplicate detection |
136-
| Output validity | **Partial** | ✓ for kernel-internal, gap for remote transmission |
137127
| Transactional turns | **Yes** | Crank buffering provides turn boundaries |
138-
| Consistent frontier | **Partial** | Kernel state atomic, but not coordinated with RemoteHandle |
128+
| Output validity | **Yes** | Outputs escape only after originating crank commits |
129+
| Deferred transmission | **Yes** | Run queue staging ensures this |
130+
| Atomic checkpoint | **Yes** | Database savepoints for kernel state |
131+
| Consistent frontier | **Yes** | Each kernel's checkpoint is independent |
139132
| Local recovery | **Yes** | Crashes don't affect other processes |
140-
| Sender-based logging | **Yes** | Messages persisted until ACKed |
141-
| Deferred transmission | **Partial** | ✓ within kernel, gap at network boundary |
142-
| FIFO ordering | **Partial** | Per-sender seq, but no enforcement on receive side |
143-
144-
## What Would Be Needed to Achieve Full Ken Properties
133+
| Sender-based logging | **Yes** | Messages persisted in remotePending until ACKed |
134+
| Exactly-once delivery | **Partial** | At-least-once; missing Done table for deduplication |
135+
| FIFO ordering | **Partial** | Per-sender seq on send; no enforcement on receive |
145136

146-
### 1. Coordinate RemoteHandle with Crank Commit (Critical)
137+
## What Would Be Needed for Full Ken Properties
147138

148-
The most important remaining gap. Options:
139+
### 1. Add Done Table for Exactly-Once Delivery
149140

150-
**Option A: Two-phase approach**
151-
- During crank: RemoteHandle persists message but does NOT transmit
152-
- After crank commit: Signal RemoteHandle to transmit persisted messages
153-
- Requires: Crank commit notification mechanism to RemoteHandle
141+
Track processed messages and deduplicate on receive:
154142

155-
**Option B: Defer to run queue delivery**
156-
- RemoteHandle only transmits when it receives a "transmit" item from run queue
157-
- Crank buffers "transmit" items along with other outputs
158-
- Flush adds transmit items to run queue
159-
- RemoteHandle processes transmit items after crank commit
143+
**Option A: Per-remote processed sequence tracking**
144+
- Store `highestProcessedSeq.${remoteId}` updated atomically with delivery crank
145+
- On receive: if `seq <= highestProcessedSeq`, ACK but don't deliver
146+
- Simple but requires in-order processing
160147

161-
### 2. Add Done Table
148+
**Option B: Explicit Done table**
149+
- Store `done.${remoteId}.${seq} = true` for each processed message
150+
- On receive: check Done table before delivering
151+
- Supports out-of-order processing
152+
- Requires garbage collection of old entries (after ACK confirms sender discarded)
162153

163-
Track processed message IDs, deduplicate on receive:
164-
- Persist `Done` table entries for processed messages
165-
- On receive, check if message already in `Done` before delivering
166-
- ACK messages in `Done` without re-delivering
154+
Either approach requires the processed-message record to be updated **atomically with the delivery crank commit**, so that crash recovery sees consistent state.
167155

168-
### 3. FIFO Enforcement on Receive
156+
### 2. FIFO Enforcement on Receive
169157

170-
Hold out-of-order messages until predecessors processed:
171-
- Track expected next seq per sender
172-
- Buffer messages that arrive out of order
173-
- Deliver in sequence order only
158+
Buffer and reorder incoming messages:
174159

175-
## Architectural Implications
160+
- Track `expectedNextSeq.${remoteId}` per sender
161+
- On receive: if `seq > expectedNextSeq`, buffer the message
162+
- When `expectedNextSeq` message arrives, deliver it and any buffered successors
163+
- Update `expectedNextSeq` as messages are delivered
176164

177-
The crank buffering work has brought us significantly closer to Ken's model:
165+
This interacts with the Done table: we need to handle the case where we've already processed some messages (from Done table) when determining what's "expected next."
178166

179-
**Before crank buffering:**
180-
```
181-
Kernel Crank:
182-
process message → syscalls immediately enqueue to run queue
183-
184-
RemoteHandle (independent):
185-
persist each outgoing message → transmit immediately
186-
```
167+
## Architectural Summary
187168

188-
**After crank buffering:**
169+
**Send side (achieved with crank buffering):**
189170
```
190-
Kernel Crank:
191-
process message → syscalls buffer outputs
171+
Vat Crank:
172+
vat processes message → syscalls buffer outputs
192173
193174
Crank Commit (atomic):
194-
persist(kernel_state) + flush(buffered_outputs to run queue)
175+
persist(vat_state) + flush(buffered_outputs to run queue)
195176
196-
RemoteHandle (still independent):
197-
receive from run queue → persist → transmit immediately
177+
Later (separate operation):
178+
run queue delivers to RemoteHandle → persist to remotePending → transmit
198179
```
199180

200-
**Ken-style architecture (goal):**
201-
```
202-
Kernel Crank:
203-
process message → syscalls buffer outputs
204-
205-
Crank Commit (atomic):
206-
persist(kernel_state, buffered_outputs, done_table)
181+
The key insight: by the time RemoteHandle sees a message, the originating crank has already committed. Output validity is achieved.
207182

208-
Post-Commit:
209-
signal RemoteHandle to transmit persisted messages
183+
**Receive side (gaps remain):**
184+
```
185+
Current:
186+
receive from network → add to run queue → deliver to vat
187+
(no deduplication, no ordering enforcement)
188+
189+
Needed:
190+
receive from network
191+
→ check Done table (skip if already processed)
192+
→ check sequence (buffer if out of order)
193+
→ add to run queue
194+
→ deliver to vat
195+
→ atomically update Done table with delivery crank commit
210196
```
211-
212-
The key remaining work is ensuring that network transmission only happens after the crank that produced the message has been fully committed.
213197

214198
## Progress Summary
215199

216-
| Area | Before | After Crank Buffering |
217-
|------|--------|----------------------|
218-
| Kernel-internal output buffering | No | **Yes** |
219-
| Rollback discards uncommitted outputs | No | **Yes** |
220-
| Atomic kernel state + output queue | No | **Yes** |
221-
| Network transmission deferred to commit | No | No (still needed) |
222-
| Done table for deduplication | No | No (still needed) |
223-
| FIFO enforcement on receive | No | No (still needed) |
200+
| Area | Status |
201+
|------|--------|
202+
| Kernel-internal output buffering | **Achieved** |
203+
| Rollback discards uncommitted outputs | **Achieved** |
204+
| Atomic kernel state + output queue | **Achieved** |
205+
| Output validity (send side) | **Achieved** |
206+
| Deferred transmission (send side) | **Achieved** |
207+
| Done table for deduplication (receive side) | Gap |
208+
| FIFO enforcement (receive side) | Gap |
224209

225210
## References
226211

0 commit comments

Comments
 (0)