b8b8af9c
docs: M9 prediction — the record
a73x 2026-08-08 17:23
Commit message
README.md
| Old | New | ||
|---|---|---|---|
| @@ -110,7 +110,7 @@ Design: `docs/handoff.md`. Every decision and measurement: | |||
| 110 | 110 | ||
| 111 | ## Status | 111 | ## Status |
| 112 | 112 | ||
| 113 | Prototype, eight milestones in, first trial cut tagged `v0.0.1-1`. All | 113 | Prototype, nine milestones in, first trial cut tagged `v0.0.1-1`. All |
| 114 | founding kill criteria cleared, measured on real networks rather than | 114 | founding kill criteria cleared, measured on real networks rather than |
| 115 | loopback: reattach into a live full-screen `nvim` in ~5ms from one | 115 | loopback: reattach into a live full-screen `nvim` in ~5ms from one |
| 116 | snapshot; steady-state delta traffic ~1% of snapshot-equivalent cost; the | 116 | snapshot; steady-state delta traffic ~1% of snapshot-equivalent cost; the |
| @@ -118,5 +118,9 @@ protocol adds ~4ms to a raw ssh round trip (constant, not a multiple of | |||
| 118 | RTT); ten consecutive real network tears resumed hands-off, all | 118 | RTT); ten consecutive real network tears resumed hands-off, all |
| 119 | delta-served, on both ssh and QUIC transports; QUIC cold attach beats | 119 | delta-served, on both ssh and QUIC transports; QUIC cold attach beats |
| 120 | ssh-via (6.9ms vs 9.8ms clean, 234.1ms vs 235.7ms with 75ms emulated | 120 | ssh-via (6.9ms vs 9.8ms clean, 234.1ms vs 235.7ms with 75ms emulated |
| 121 | delay on a real path). Not yet here: daemon auto-start on attach, | 121 | delay on a real path); and a predicted keystroke paints in **0.1ms** on a |
| 122 | predictive local echo for high-RTT links, reconnect backoff tuning. | 122 | 150ms path where the same keystroke's authoritative echo takes **154ms** — |
| 123 | typing stops waiting for the round trip, and the two numbers come from one | ||
| 124 | keystroke on one connection so the gap is not an artefact of comparing | ||
| 125 | runs. Not yet here: daemon auto-start on attach, reconnect backoff tuning, | ||
| 126 | prediction for backspace and for multi-byte input. | ||
docs/decisions.md
| Old | New | ||
|---|---|---|---|
| @@ -942,6 +942,304 @@ loop. | |||
| 942 | and possibly worth a dedup pass across the three arms now that there are | 942 | and possibly worth a dedup pass across the three arms now that there are |
| 943 | three. | 943 | three. |
| 944 | 944 | ||
| 945 | ## 2026-08-08 (M9) | ||
| 946 | |||
| 947 | Speculative local echo. The client paints a predicted glyph for a printable | ||
| 948 | keystroke immediately, underlined, and reconciles it against the daemon's | ||
| 949 | authoritative delta when that arrives. Echo latency stops being a function | ||
| 950 | of the round trip where prediction applies — and is provably absent where it | ||
| 951 | must not. | ||
| 952 | |||
| 953 | ### The verdict, per leg | ||
| 954 | |||
| 955 | Measured on the LAN box with `netem delay 150ms` on the interface facing the | ||
| 956 | client, round trip measured at **150.5ms** (not assumed — the baseline is | ||
| 957 | printed beside the result). | ||
| 958 | |||
| 959 | | | min | med | max | n | | ||
| 960 | |---|---|---|---|---| | ||
| 961 | | baseline (the path) | 150.4 | 150.5 | 150.5 | 10 | | ||
| 962 | | predicted paint | 0.1 | **0.1** | 0.3 | 20 | | ||
| 963 | | unpredicted input | 153.8 | **154.2** | 154.8 | 10 | | ||
| 964 | | same keystroke's authoritative echo | 0.3 | 154.2 | 154.6 | 20 | | ||
| 965 | | burst convergence | 233.4 | 233.8 | 234.2 | 10 | | ||
| 966 | |||
| 967 | - **Leg 1 (latency) — cleared.** 0.1ms median against a 30ms threshold, at a | ||
| 968 | round trip three orders of magnitude larger. | ||
| 969 | - **Leg 2 (convergence) — cleared, in the narrowed form below.** Ten | ||
| 970 | adversarial bursts: `made=54 confirmed=54 contradicted=0 expired=0 | ||
| 971 | abandoned=0 pending=0`, last burst present in the daemon's grid. | ||
| 972 | - **Leg 3 (safety) — cleared by the e2e suite**, not by the WAN harness. A | ||
| 973 | session that is canonical with echo off from its first instruction makes | ||
| 974 | **nothing**: `made=0 displayed=0`, the typed secret appears nowhere in the | ||
| 975 | bytes the client emits, and `pw-len-7` in the daemon's grid proves the | ||
| 976 | shell received all seven characters so the absence is prediction declining | ||
| 977 | rather than nothing having been typed. | ||
| 978 | - **Leg 4 (fallback completeness) — cleared by unit and e2e tests.** | ||
| 979 | Multi-byte input, last column, scroll mode, pending resize and unknown | ||
| 980 | mode bits each suppress, each with its own test. | ||
| 981 | |||
| 982 | ### The control is the measurement | ||
| 983 | |||
| 984 | Leg 1's number means nothing on its own — a fast local paint is what a | ||
| 985 | terminal does anyway. What makes it evidence is that the **unpredicted** | ||
| 986 | control was taken on the same connection in the same run, from the same | ||
| 987 | kind of keystroke, and came back at 154.2ms. One keystroke, two clocks: no | ||
| 988 | argument about warm caches, differing conditions or hardware can be made | ||
| 989 | about the gap between them. | ||
| 990 | |||
| 991 | Two things about that control are worth keeping. | ||
| 992 | |||
| 993 | - **The criterion's wording was unmeasurable as written.** It asked for an | ||
| 994 | "echo-off context" as the control. With echo off *nothing is painted*, so | ||
| 995 | there is no arrival to put a clock on. The measurable form of the same | ||
| 996 | claim is input prediction **refuses**: two characters in one write reach | ||
| 997 | the client as one chunk, are refused for being multi-byte, and therefore | ||
| 998 | cannot appear until the daemon answers. A criterion can be unmeasurable | ||
| 999 | while sounding precise, and the time to find that out is while writing the | ||
| 1000 | harness, not while reading the results. | ||
| 1001 | - **A control that cannot fail proves nothing**, so the harness fails leg 1 | ||
| 1002 | if the control comes back fast. A quick "unpredicted" number would mean | ||
| 1003 | the input was being predicted after all and the comparison was empty — | ||
| 1004 | which is the floor-form philosophy applied to a control rather than to a | ||
| 1005 | threshold. | ||
| 1006 | |||
| 1007 | ### Floor-form thresholds: the new standard | ||
| 1008 | |||
| 1009 | This discharges the rewrite banked by M8. Every threshold from here is | ||
| 1010 | stated in **absolute units with an explicit validity floor**, never as a | ||
| 1011 | multiple of RTT: | ||
| 1012 | |||
| 1013 | - Each leg names the minimum RTT at which it is meaningful. Below that, the | ||
| 1014 | harness prints **NOT EXERCISED** — never PASS. Leg 1's floor is 50ms and | ||
| 1015 | `wan.sh` implements the check rather than describing it. | ||
| 1016 | - The failure this prevents is not hypothetical: two milestones' criteria | ||
| 1017 | (M6's reattach gate, M8's leg 2) failed on fast hardware for the same | ||
| 1018 | structural reason, and a criterion that fails because the link is *good* | ||
| 1019 | is measuring the wrong thing. | ||
| 1020 | |||
| 1021 | **M6's reattach gate still fails structurally on a fast link**, and did so | ||
| 1022 | again in every M9 run: "2× round-trip" is 0.4ms on a LAN, which nothing can | ||
| 1023 | meet. That is the pre-existing ruling, unrelated to M9, and it is now the | ||
| 1024 | clearest argument for applying the floor form **retroactively** to M6's | ||
| 1025 | criterion. Banked below. | ||
| 1026 | |||
| 1027 | ### The re-entrancy defect: found by a leaked temp directory | ||
| 1028 | |||
| 1029 | The most valuable thing this milestone produced was not prediction. | ||
| 1030 | |||
| 1031 | A `make test` run came back 171/172 with one test binary failed. The run | ||
| 1032 | immediately before it, of the same binaries, passed; the change between them | ||
| 1033 | was a comment. **The failing test's name was not captured** — the | ||
| 1034 | verification command grepped only the summary line — and 22 further runs, | ||
| 1035 | six of them under 8-way CPU load, never reproduced it. | ||
| 1036 | |||
| 1037 | What the hunt did turn up: two M8-era QUIC tests intermittently leaked their | ||
| 1038 | `TmpDir`. A leaked temp directory means the cleanup `defer` never ran, which | ||
| 1039 | points at an **abort** rather than a clean test failure. An audit followed | ||
| 1040 | that scent to a real defect: | ||
| 1041 | |||
| 1042 | - ngtcp2 is not re-entrant, and we re-entered it. `read_pkt` → | ||
| 1043 | `recv_stream_data` callback → the daemon's frame handling → a reply queued | ||
| 1044 | → `send` → `drain` → `writev_stream` **on the same connection**, with | ||
| 1045 | `read_pkt` still on the stack below. | ||
| 1046 | - Monotonic timestamps move backwards within one `read_pkt`, quietly | ||
| 1047 | corrupting loss detection. When a datagram carries a STREAM frame ahead of | ||
| 1048 | an ACK, the nested write mutates the retransmission buffer the outer ack | ||
| 1049 | walk is about to traverse. | ||
| 1050 | - `ngtcp2_unreachable()` calls `abort()` **unconditionally, even under | ||
| 1051 | NDEBUG**. The symptom is a bare SIGABRT with no Zig panic banner and no | ||
| 1052 | defers run — which is exactly what a leaked `TmpDir` looks like. | ||
| 1053 | |||
| 1054 | **The fix is an invariant, not a patch:** `Listener.send` queues and never | ||
| 1055 | drains. Draining happens only where the stack is ours — after `read_pkt` | ||
| 1056 | returns, in `tick`, in `reapClosing`, and in the daemon's explicit | ||
| 1057 | `drainAll` at the end of each pump. An `assert(ngtcp2_depth == 0)` at the | ||
| 1058 | top of `drain` makes a regression a deterministic Debug failure instead of a | ||
| 1059 | one-in-a-few-hundred abort somewhere else entirely. | ||
| 1060 | |||
| 1061 | **The mutation check revised the theory.** Reverting queue-only with the | ||
| 1062 | assert in place fires it *immediately* — in the unit tests and again in | ||
| 1063 | e2e, on the first QUIC exchange. The re-entrancy was not rare; it happened | ||
| 1064 | on essentially every QUIC session. Only its *consequence* was rare, | ||
| 1065 | depending on whether a datagram carried the frame ordering that turns it | ||
| 1066 | fatal. Every QUIC session before this fix was corrupting loss-detection | ||
| 1067 | state silently. | ||
| 1068 | |||
| 1069 | **Honesty about the causal link.** The original 171/172 transient was never | ||
| 1070 | reproduced and its test name was never captured, so "this defect caused that | ||
| 1071 | failure" is **strong inference, not proof**. What is proven: the defect was | ||
| 1072 | real, constant, and capable of producing exactly that signature. What is | ||
| 1073 | not: that it produced *that* one. | ||
| 1074 | |||
| 1075 | Two things queue-only broke that had to be fixed with it, neither in the | ||
| 1076 | audit's list — latency (a reply would have waited for the next poll cycle, | ||
| 1077 | since `tick` only services connections whose timer is due) and the | ||
| 1078 | session-full refusal (queued, then the connection reaped before it could | ||
| 1079 | leave; `reapClosing` now drains once before freeing). | ||
| 1080 | |||
| 1081 | **Three hardening items in that batch are reasoned, not mutation-pinned**, | ||
| 1082 | and are recorded as such because the batch would otherwise read as | ||
| 1083 | uniformly verified: `closed`-recomputed-after-reap needs a handler that | ||
| 1084 | closes a *different* connection mid-reap; took-before-error-check needs | ||
| 1085 | ngtcp2 to commit a stream offset and then fail; the two `deinit` orderings | ||
| 1086 | need a use-after-free detector. All three are corrections to paths the tests | ||
| 1087 | do not reach. | ||
| 1088 | |||
| 1089 | ### Reconcile v2: judge evidence, not arrival order | ||
| 1090 | |||
| 1091 | The first design read any cell that did not already hold the prediction as a | ||
| 1092 | refutation. That is wrong about the **ordinary** case, not an edge one: type | ||
| 1093 | `hello` faster than the round trip and the first frame back was built when | ||
| 1094 | the daemon had seen only `h`, so `e,l,l,o` are judged against a screen that | ||
| 1095 | predates them, all four read as contradictions, and the queue flushes. | ||
| 1096 | Prediction would have erased itself once per RTT, in every tier — the exact | ||
| 1097 | opposite of the feature. | ||
| 1098 | |||
| 1099 | So a prediction carries what the cell held when it was made, and the three | ||
| 1100 | answers are distinguished: our character confirms; **the character that was | ||
| 1101 | already there means the frame has said nothing yet**, so the prediction | ||
| 1102 | waits; anything else means somebody wrote that cell, which is the only thing | ||
| 1103 | that refutes us. A refutation still flushes the whole queue — everything | ||
| 1104 | typed after a wrong prediction was typed into a screen that never existed. | ||
| 1105 | |||
| 1106 | Waiting is bounded, or "no evidence yet" becomes a state a prediction sits | ||
| 1107 | in forever: eight judging frames, or a second of wall time. That is the | ||
| 1108 | phantom guard — nvim swallowing a normal-mode `j` repaints some other row | ||
| 1109 | and leaves the predicted cell untouched. The frame bound cannot catch the | ||
| 1110 | version where the application simply goes quiet (no frame ever comes back to | ||
| 1111 | trigger it), so `expire` does the same job off the client's idle path. | ||
| 1112 | |||
| 1113 | The WAN run settles the risk this was banked against: `contradicted=0` and | ||
| 1114 | `expired=0` across ten adversarial bursts at 150ms RTT. | ||
| 1115 | |||
| 1116 | ### The burst/expiry ceiling | ||
| 1117 | |||
| 1118 | A prediction is retired unanswered after `expire_after_ms` (1000ms). What it | ||
| 1119 | must survive is **the round trip plus however long a burst's later | ||
| 1120 | keystrokes queue behind its earlier ones**. Cross that and predictions | ||
| 1121 | expire mid-burst and the counters collapse. | ||
| 1122 | |||
| 1123 | This is measured, not theorised: at 400ms each way the e2e burst scenario | ||
| 1124 | dropped to `confirmed=1`, because `delaypipe` delays each chunk serially and | ||
| 1125 | the later keystrokes aged past the bound while queued. At the WAN's 150ms | ||
| 1126 | the burst converges in 233.8ms against the 1000ms bound. `wan.sh` prints | ||
| 1127 | that headroom beside the burst numbers, because it is what decides whether | ||
| 1128 | the numbers reproduce on a slower path. | ||
| 1129 | |||
| 1130 | ### The readline finding: the tiers describe termios, not UX | ||
| 1131 | |||
| 1132 | An interactive bash or zsh prompt runs at **icanon=0, echo=0** — readline | ||
| 1133 | turns both off and echoes for itself. So the everyday shell prompt is the | ||
| 1134 | `.adaptive` tier, where display must be earned; `.always` covers only | ||
| 1135 | genuinely canonical readers (`cat`, a shell's `read` builtin, dash without | ||
| 1136 | line editing). | ||
| 1137 | |||
| 1138 | Two consequences. The mode bits move once or twice **per command** as | ||
| 1139 | readline hands the terminal back and forth, and since any move in the bits | ||
| 1140 | flushes the queue and un-earns display, the first two keystrokes after each | ||
| 1141 | prompt are invisible predictions. That is the conservative trade taken | ||
| 1142 | deliberately; per-context confidence memory is the banked polish. | ||
| 1143 | |||
| 1144 | It also decided the test design: `/bin/cat` is the session shell wherever | ||
| 1145 | `.always` is under test, in e2e and in the WAN harness alike. A suite that | ||
| 1146 | used `/bin/sh` there would have been testing a different tier than it | ||
| 1147 | claimed. | ||
| 1148 | |||
| 1149 | ### Named trap: "a write is not a keystroke" | ||
| 1150 | |||
| 1151 | Three sightings this milestone, all the same root cause — a `printf` or a | ||
| 1152 | loop of them arrives at the client as **one read**, which is a multi-byte | ||
| 1153 | chunk, which prediction refuses: | ||
| 1154 | |||
| 1155 | 1. **e2e password scenario.** `printf 'hunter2\n'` is one write, so the | ||
| 1156 | chunk was refused for being multi-byte long before the tier was | ||
| 1157 | consulted. `made=0` held for a reason that had nothing to do with the | ||
| 1158 | password tier — the scenario passed with echo-off canonical mapped to the | ||
| 1159 | always-predict tier, and would have shipped guarding nothing. | ||
| 1160 | 2. **e2e multi-byte test.** All three chunks had non-printable lead bytes, | ||
| 1161 | which `predictAt` refuses anyway. The untested shape was a paste of plain | ||
| 1162 | ASCII, whose lead byte is printable — the one case the length guard is | ||
| 1163 | the only thing catching. | ||
| 1164 | 3. **wan.sh burst.** Characters sent back to back gave `made=5` for 50 | ||
| 1165 | keystrokes: the bursts contained no predictions at all, and leg 2 would | ||
| 1166 | have "passed" on an empty measurement. | ||
| 1167 | |||
| 1168 | The fix is always the same — pace the keystrokes so each is its own read — | ||
| 1169 | and the lesson is that **a test which types must prove what it typed became | ||
| 1170 | separate keystrokes**, by asserting `made`. | ||
| 1171 | |||
| 1172 | ### Named trap, sixth sighting: `pgrep -f` matching itself | ||
| 1173 | |||
| 1174 | Recorded five times before this milestone. The sixth: checking the LAN box | ||
| 1175 | for leftovers with `pgrep -f "muxd-wan-"` reported two matches, both of | ||
| 1176 | which were the diagnostic's **own** shell — its command line contained the | ||
| 1177 | literal path glob `/tmp/muxd-wan-*`. It happened *while checking for the | ||
| 1178 | very hazard it is*, which is the detail worth keeping: knowing about a trap | ||
| 1179 | is not the same as not being in it. | ||
| 1180 | |||
| 1181 | The authoritative check is by `comm` plus a file listing — `pgrep -x muxd` | ||
| 1182 | and `ls` — which showed the box clean. The earlier "box clean" reports were | ||
| 1183 | correct, but rested on weaker evidence than was claimed for them. | ||
| 1184 | |||
| 1185 | ### Leg 2's convergence, narrowed — and why | ||
| 1186 | |||
| 1187 | The plan's leg 2 says the client grid converges **byte-identical** to `muxd | ||
| 1188 | dump`. The WAN harness does not check that. It asserts convergence through | ||
| 1189 | the daemon's grid plus attribution: every prediction accounted for | ||
| 1190 | (`made = confirmed + abandoned + pending`, with `pending = 0`), and the last | ||
| 1191 | burst present in the daemon's grid. | ||
| 1192 | |||
| 1193 | This is the **same narrowing, for the same reason, as M7's**: the client | ||
| 1194 | emits a stream of paints, not a grid, so comparing it to a dump means | ||
| 1195 | parsing VT inside the harness — a second terminal emulator whose own bugs | ||
| 1196 | would be indistinguishable from the ones it is meant to catch. `client.zig` | ||
| 1197 | and `server.zig` tests do make grid comparisons against a real replica | ||
| 1198 | Engine, over a socket. The full render-vs-dump harness stays banked. | ||
| 1199 | |||
| 1200 | ### Method notes | ||
| 1201 | |||
| 1202 | - **Write the mutation first.** Five assertions across this milestone passed | ||
| 1203 | for reasons other than the mechanism they named, and every one was caught | ||
| 1204 | by asking "what change should break this?" *before* trusting the green. | ||
| 1205 | Three were test weaknesses found on a mutation's first run (the | ||
| 1206 | `paintOverlay` confidence guard, multi-byte chunks, the client's | ||
| 1207 | `markPainted` call site); two were e2e scenarios that survived a mutation | ||
| 1208 | of the thing they existed to protect. | ||
| 1209 | - **Mutation-check the call site separately from the rule.** A module's own | ||
| 1210 | test cannot see whether its caller ever calls it: removing the client's | ||
| 1211 | `markPainted` call survived until a client-side test existed for it. | ||
| 1212 | - **`predict.zig` is engine-free**, so the whole policy — tiers, promotion, | ||
| 1213 | refutation, expiry — is exercised with no terminal, pty or daemon in the | ||
| 1214 | picture. `reconcile` takes its grid duck-typed; `PlainGrid` adapts the | ||
| 1215 | plain dump a client already has. The debt that created (no real-Engine | ||
| 1216 | reconcile test) was discharged at the client wiring point, where the | ||
| 1217 | `prev_ch` read is pinned by a test that fails twice over if it reads the | ||
| 1218 | wrong cell. | ||
| 1219 | |||
| 1220 | ### Banked by M9 | ||
| 1221 | |||
| 1222 | - **Backspace prediction.** M9 predicts printable ASCII only; backspace is | ||
| 1223 | the most-missed omission in ordinary typing. | ||
| 1224 | - **Underline only when late.** Underlining every prediction is honest but | ||
| 1225 | noisy on a fast link, where the authoritative echo replaces it within | ||
| 1226 | milliseconds. Show the distinction only once a prediction has been | ||
| 1227 | outstanding long enough to matter. | ||
| 1228 | - **Per-context confidence memory.** Confidence is re-earned from scratch on | ||
| 1229 | every mode change, which at a readline prompt is once or twice per | ||
| 1230 | command. Remembering it per context would return the first two keystrokes | ||
| 1231 | after each prompt. | ||
| 1232 | - **An input ack in the protocol.** The proper fix if spurious contradiction | ||
| 1233 | ever reappears: the daemon telling the client which input it has seen | ||
| 1234 | removes the guesswork from judging entirely. Not needed today — | ||
| 1235 | `contradicted=0` at 150ms — and a protocol change is not worth spending | ||
| 1236 | before it is. | ||
| 1237 | - **Full render-vs-dump convergence harness** (shared with M7's identical | ||
| 1238 | banked item). | ||
| 1239 | - **Floor-form rewrite applied retroactively to M6's reattach criterion.** | ||
| 1240 | It still fails structurally on fast links; the form that fixes it is now | ||
| 1241 | standard and written down. | ||
| 1242 | |||
| 945 | ## Open (owed by later milestones) | 1243 | ## Open (owed by later milestones) |
| 946 | 1244 | ||
| 947 | - Scrollback retention *tuning*. The policy itself was decided in M1 and | 1245 | - Scrollback retention *tuning*. The policy itself was decided in M1 and |
docs/roadmap.md
| Old | New | ||
|---|---|---|---|
| @@ -5,35 +5,33 @@ The forward view, one item per line, ranked. History and evidence live in | |||
| 5 | this file at each milestone close and whenever the queue reorders; the | 5 | this file at each milestone close and whenever the queue reorders; the |
| 6 | queue's order is set by the user, not by this file. | 6 | queue's order is set by the user, not by this file. |
| 7 | 7 | ||
| 8 | **Now:** `v0.0.1-1` tagged; M1–M8 complete; in field trial on real VMs. | 8 | **Now:** `v0.0.1-1` tagged; M1–M9 complete; in field trial on real VMs. |
| 9 | Trial feedback outranks everything below — what actually hurts in use is | 9 | Trial feedback outranks everything below — what actually hurts in use is |
| 10 | better data than any of this ranking. | 10 | better data than any of this ranking. |
| 11 | 11 | ||
| 12 | ## M9 — prediction (speculative local echo) — queued | 12 | ## M9 — prediction (speculative local echo) — complete |
| 13 | 13 | ||
| 14 | The client paints predicted echo for printable keystrokes immediately and | 14 | **Verdict:** cleared. A predicted keystroke paints in **0.1ms** while the |
| 15 | reconciles against the daemon's authoritative delta when it arrives. | 15 | same keystroke's authoritative echo — and unpredicted input on the same |
| 16 | Sequencing set 2026-08-07: reconnect → QUIC → prediction. Only pays on | 16 | connection in the same run — takes **154ms** over a 150ms path. Echo |
| 17 | high-RTT links — the protocol itself costs ~4ms over baseline, so LAN | 17 | latency has stopped being a function of the round trip where prediction |
| 18 | users will never see the difference (decisions.md, M6 verdict). | 18 | applies, and is provably absent where it must not (decisions.md, M9). |
| 19 | 19 | ||
| 20 | Scope per handoff §5: conservative, line-mode only, printable chars, | 20 | Prediction is an *overlay* on the replica, never fed into it; the daemon |
| 21 | unconfirmed text visually distinguished, instant fallback on anything | 21 | ships the pty's mode bits so disabled contexts are known rather than |
| 22 | unpredictable. Design sketch discussed 2026-08-08: prediction is an | 22 | guessed. The blocking pre-requisite — the RTT-multiple criterion form — was |
| 23 | *overlay* on the replica, never fed into it; daemon ships pty mode bits | 23 | discharged: thresholds are now stated in absolute units with an explicit |
| 24 | (echo/icanon) so disabled contexts are known, not guessed. | 24 | validity floor, and a harness prints NOT EXERCISED below it rather than |
| 25 | PASS. | ||
| 25 | 26 | ||
| 26 | **Pre-requisite, blocking:** rewrite the RTT-multiple kill-criterion form | 27 | Two findings outlived the milestone. The everyday shell prompt is |
| 27 | — two milestones' criteria (M6 reattach gate, M8 leg 2) failed on fast | 28 | icanon=0/echo=0 (readline echoes for itself), so it is the adaptive tier |
| 28 | hardware for the same structural reason; a threshold needs a floor term or | 29 | and not the always-predict one. And the hunt for a one-in-hundreds test |
| 29 | a stated minimum RTT before M9 writes another one (decisions.md, "Banked | 30 | failure found ngtcp2 being re-entered on **every** QUIC exchange — |
| 30 | by M8"). | 31 | constant, with a rare consequence — which is fixed and, unusually, was |
| 32 | proven so by a mutation that fired immediately. | ||
| 31 | 33 | ||
| 32 | **Hazard carried in:** speculative buffers held across callbacks are the | 34 | ## Trial friction, likely next — and still outranked by trial feedback |
| 33 | habitat of the UAF-that-never-crashes (decisions.md, M8 egress/teardown | ||
| 34 | records). | ||
| 35 | |||
| 36 | ## Trial friction, likely next after (or before) M9 | ||
| 37 | 35 | ||
| 38 | - **Daemon lifecycle**: attach does not auto-start `muxd run`; no install | 36 | - **Daemon lifecycle**: attach does not auto-start `muxd run`; no install |
| 39 | script; systemd user units exist in `contrib/` but are not the deployed | 37 | script; systemd user units exist in `contrib/` but are not the deployed |
| @@ -44,6 +42,12 @@ records). | |||
| 44 | 42 | ||
| 45 | ## Banked, ranked by expected win | 43 | ## Banked, ranked by expected win |
| 46 | 44 | ||
| 45 | 0. **Prediction polish, as a group** — backspace prediction (the | ||
| 46 | most-missed omission in ordinary typing); underline only once a | ||
| 47 | prediction has been outstanding long enough to matter, rather than | ||
| 48 | always; per-context confidence memory, which would return the first two | ||
| 49 | keystrokes after every readline prompt. All three are small and all | ||
| 50 | three are felt on every keystroke (decisions.md, "Banked by M9"). | ||
| 47 | 1. **Retry-skip via NEW_TOKEN** — ~1 RTT off QUIC cold attach, which is | 51 | 1. **Retry-skip via NEW_TOKEN** — ~1 RTT off QUIC cold attach, which is |
| 48 | most of QUIC's remaining margin over ssh-via. Not "skip Retry": token | 52 | most of QUIC's remaining margin over ssh-via. Not "skip Retry": token |
| 49 | for validated clients, RFC 9000 3x anti-amplification for the rest — | 53 | for validated clients, RFC 9000 3x anti-amplification for the rest — |