Commit 815f5aa
authored
fix(coding-agents): write back only the new turns (append + idempotent retain) (#3336)
* feat(coding-agents): write back only the new turns (append + idempotent retain)
The live write-back re-uploaded the WHOLE conversation on every Stop, and every
N turns under the persistent-plugin runtime. A long session therefore re-sent its
entire transcript each time, which is what turns a large session into an
unretainable one rather than merely a slow one.
Retains now carry a per-session cursor. A session that has already been written
appends only the turns added since the last successful write, using the server's
`update_mode: "append"` (supported since #932); the server concatenates them onto
the stored document with "\n", which is exactly why the transcript is JSONL.
Append is only correct while our view of the document matches the server's, so
every uncertain case falls back to the full REPLACE this always did - no cursor,
a transcript that was rewritten rather than extended (compaction, a truncated
rollout), or a previous write whose outcome is unknown. Replace is idempotent by
construction and so is always the safe recovery.
Two supporting changes:
- Conversation retains carry a deterministic v5 `operation_id`, so a resubmitted
write is collapsed into the original operation instead of being applied twice.
That is what makes append safe: the client aborts at 15s, and a server that
committed the write anyway would otherwise get the same turns again. The field
landed in v0.8.6 (#2937/#2947) and is silently IGNORED by anything older, so
the append path is gated on a cached GET /version probe and older servers keep
replacing.
- `RetainOpts.async` is gone. Nothing ever passed `false`; retains are always
async, and nothing in this plugin can afford to block a hook on extraction.
Backfill, git, knowledge and survey retains are untouched: they keep replacing,
and deliberately do not take a deterministic operation id, so re-retaining
identical content after a document is deleted still restores it.
* fix(coding-agents): serialise a session's write-backs so appends cannot overlap
Found reviewing the append cursor: the runtime fires retains without awaiting
them, and reading the cursor was not atomic with claiming it — the capability
probe awaits in between. Two overlapping write-backs therefore both planned an
append from the SAME position and submitted overlapping slices, duplicating turns
inside the document:
replace(REF-ID + turns 0-4), append(turns 5-7), append(turns 5-8)
Serialising only the claim would not have fixed it either: that leaves an append
racing a replace on the wire, where the order they land in decides the outcome.
The whole read-plan-send-confirm cycle is now chained per session, so each
write-back plans against the previous one's CONFIRMED cursor.
The runtime's idle test now waits a tick before asserting on the fire-and-forget
retain, as its sibling assertions already did — one extra microtask hop.
* fix(coding-agents): key the write-back cursor to the bank it wrote to
The cursor is keyed by (harness, session id), but the bank is re-derived from
each hook event's cwd — so a session that moves between repos (#3133) keeps its
id and changes bank. The new bank holds no document for that session, and the
cursor still claimed a position in it:
bank repo-a: replace(REF-ID + turns 0-4)
bank repo-b: append(turns 5-7) <- turns 0-4 never existed here
The cursor now records the bank it wrote to, and a mismatch replaces. Same
reasoning as the fingerprint and dirty checks: anything that makes our view of
the document unreliable falls back to the full write.
Also covers a config change (mapPathToBank, an explicit bankId) that re-points a
live session at a different bank.
* fix(coding-agents): re-sync the whole document every 20 appends
Review follow-up. Retains are async: the server acknowledges the submission and
extracts later, so a write can be confirmed to us and still fail afterwards — and
_resolve_retain_replay returns a prior operation whatever its status, so
resubmitting the same payload will not redo it. Replacing everything used to be
self-healing precisely because each write re-sent the whole document; appending
gives that up, and a single lost write would otherwise cost the rest of the
session.
A full write every MAX_APPENDS_BEFORE_RESYNC appends bounds that to the turns
since the last re-sync. A replace of any kind resets the count.
Also from the review:
- drop a session's chain entry once it settles, so a host that outlives many
sessions (opencode runs for days) does not keep one resolved promise per
session id forever
- pin the version test to MIN_IDEMPOTENT_RETAIN_VERSION rather than repeating
the literal, which also gives the exported constant a consumer1 parent f8e588c commit 815f5aa
21 files changed
Lines changed: 910 additions & 38 deletions
File tree
- hindsight-integrations/coding-agents/src
- core
Lines changed: 210 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
68 | 69 | | |
69 | 70 | | |
70 | 71 | | |
71 | | - | |
| 72 | + | |
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
75 | 76 | | |
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
| 9 | + | |
8 | 10 | | |
9 | 11 | | |
10 | 12 | | |
| |||
86 | 88 | | |
87 | 89 | | |
88 | 90 | | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
89 | 134 | | |
90 | | - | |
91 | | - | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
92 | 143 | | |
93 | 144 | | |
94 | 145 | | |
95 | | - | |
| 146 | + | |
96 | 147 | | |
97 | 148 | | |
98 | 149 | | |
99 | 150 | | |
100 | 151 | | |
101 | 152 | | |
102 | 153 | | |
103 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
104 | 172 | | |
105 | 173 | | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
106 | 198 | | |
107 | | - | |
| 199 | + | |
108 | 200 | | |
109 | 201 | | |
110 | 202 | | |
111 | 203 | | |
112 | 204 | | |
113 | 205 | | |
114 | | - | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
115 | 210 | | |
116 | 211 | | |
117 | 212 | | |
| |||
120 | 215 | | |
121 | 216 | | |
122 | 217 | | |
| 218 | + | |
123 | 219 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
193 | 193 | | |
194 | 194 | | |
195 | 195 | | |
196 | | - | |
197 | | - | |
| 196 | + | |
198 | 197 | | |
199 | 198 | | |
200 | 199 | | |
| |||
0 commit comments