4 "base": "89c9750ccf88f432c6bb0e6bb8f1176d536d04fb",
5 "head": "fc388be7482160dc2f77c014bbc45335b690a918",
6 "cob": "07c118cd25063f79bf8aeab46d3dd5bfacfb1fec"
7+ },
8+ {
9+ "base": "fc388be7482160dc2f77c014bbc45335b690a918",
10+ "head": "6e3f8720042e54b02999bd7dee205317359238b4",
11+ "cob": "2dfc41fdc3d2e29a2f02765d408d6d6b31fde2b3"
12 }
13 ]
14 }1 {"type":"config","name":"Speed up TUI transcript rendering","metricName":"view_build_ns","metricUnit":"ns","bestDirection":"lower"}
2 {"run":1,"commit":"89c9750","metric":716061,"metrics":{"view_build_B":405548,"view_build_allocs":6446},"status":"keep","description":"Baseline benchmark harness for steady-state View.BuildWithAnchors; benchmark works but autoresearch.sh mis-parsed go test columns for primary metric and bytes.","timestamp":1777360240079,"segment":0,"confidence":null,"iterationTokens":98,"asi":{"hypothesis":"Establish a stable benchmark for cached large-transcript TUI rendering before optimizing view.go.","benchmark_shape":"120 synthetic user/assistant/tool triplets with markdown, tool args, and tool results","parsing_issue":"go test -benchmem columns shifted; script captured B/op token instead of numeric ns/op","next_action_hint":"Fix metric extraction in autoresearch.sh, rerun baseline, then optimize transcript-derived caches."}}
3 {"run":2,"commit":"fc388be","metric":367957,"metrics":{"view_build_B":114628,"view_build_allocs":42},"status":"keep","description":"Cache transcript-derived View metadata incrementally and fix benchmark metric parsing; avoids rehashing messages and reparsing tool-call JSON on every steady-state redraw.","timestamp":1777360408271,"segment":0,"confidence":null,"iterationTokens":13150,"asi":{"hypothesis":"The cached-render path is dominated by repeated transcript bookkeeping rather than markdown rendering, so caching tool metadata, message hashes, and assistant turn state should cut redraw cost sharply.","speedup_factor_vs_baseline":"1.95x faster ns/op","memory_reduction":"allocs 6446 -> 42, bytes 405548 -> 114628 in benchmark","why_it_worked":"BuildWithAnchors now reuses transcript-derived data when Messages only appends, which matches normal TUI session behavior.","next_action_hint":"Look for remaining steady-state cost in renderMessageCached key construction and output slice/anchor allocation, or add a second benchmark for incremental append behavior."}}
4+{"run":3,"commit":"6e3f872","metric":35303,"metrics":{"view_build_B":111173,"view_build_allocs":16},"status":"keep","description":"Avoid recomputing message hashes on the hot path when cached transcript hashes already exist; profiling showed the previous fallback assignment was still calling hashMessage for every message every redraw.","timestamp":1777360477881,"segment":0,"confidence":2.0464446542052706,"iterationTokens":3242,"asi":{"hypothesis":"After incremental transcript caches, remaining steady-state time is mostly accidental hash recomputation inside BuildWithAnchors; only computing hashes on cache miss should collapse ns/op further.","profiling_signal":"pprof showed ~88% of CPU in hashMessage/fnv64aWrite after the first optimization","speedup_factor_vs_previous":"10.42x faster ns/op","speedup_factor_vs_baseline":"20.28x faster ns/op","memory_change":"allocs 42 -> 16, bytes 114628 -> 111173","next_action_hint":"If continuing, prewarm the benchmark to remove the single cold-build outlier and then inspect remaining allocations in anchor/output slice construction."}}
32 - Cached transcript-derived metadata in `View`: tool-call path/label maps, per-message hashes, and assistant `turnOpen` state are now built incrementally as `Messages` grows instead of recomputed on every redraw.
33 - Fixed `autoresearch.sh` metric extraction so `go test -benchmem` columns are parsed into numeric `ns/op`, `B/op`, and `allocs/op` medians.
34 - Profiling showed `hashMessage` still dominated CPU after the first cache pass because `BuildWithAnchors` eagerly recomputed a hash before checking the cached slice; deferring the fallback hash path cut steady-state render cost by another order of magnitude.
35+- The steady-state benchmark has no live tool-call overlay, yet `BuildWithAnchors` still rebuilt a `finalised` map for duplicate-suppression every frame. Guarding that work behind `len(v.ToolCalls) > 0` removed nearly all remaining allocations on the hot path.
295 // finalised transcript both render the same call. Checking
296 // for either side of the pair suppresses the duplicate in
297 // both windows.
298- finalised := map[string]bool{}
299- for _, m := range v.Messages {
300- for _, c := range m.Content {
301- switch b := c.(type) {
302- case provider.ToolCallBlock:
303- finalised[b.ID] = true
304- case provider.ToolResultBlock:
305- finalised[b.CallID] = true
298+ if len(v.ToolCalls) > 0 {
299+ finalised := make(map[string]bool, len(v.ToolCalls)*2)
300+ for _, m := range v.Messages {
301+ for _, c := range m.Content {
302+ switch b := c.(type) {
303+ case provider.ToolCallBlock:
304+ finalised[b.ID] = true
305+ case provider.ToolResultBlock:
306+ finalised[b.CallID] = true
307+ }
308 }
309 }
308- }
309- for _, tc := range v.ToolCalls {
310- if finalised[tc.ID] {
311- continue
310+ for _, tc := range v.ToolCalls {
311+ if finalised[tc.ID] {
312+ continue
313+ }
314+ out = append(out, v.renderToolCall(tc, width)...)
315+ out = append(out, "")
316 }
313- out = append(out, v.renderToolCall(tc, width)...)
314- out = append(out, "")
317 }
318 if v.Err != "" {
319 out = append(out, v.Theme.FG256(v.Theme.Error, "✖ "+v.Err))