jab put html/todo/*/*.html (733 sub-crossing args, journal root authority) took 10s; the identical files staged as FLAT args in a scratch clone take 0.18s (55x). PUT-007 hoisted the engine prep + made ONE wtlog write per batch — but only for flat args: stageInSub (be/verbs/put/put.js:513) still runs the whole cycle per arg. Fix = the same PUT-007 treatment, repo-wise: group args by their resolved repo (parent or sub), prep ONE engine and commit ONE wtlog write per repo.
Incident 2026-07-17, :put html/todo/*/*.html from the journal tree, 10s wall-clock, 13ms/arg. Per sub-crossing arg, stageInSub does:
store.open(subRepo.storePath, ...) — fresh reader per arg (maintain memoized per instance only).stage.prep(subRepo, wtlog.open(subRepo), subK) — the whole-wt rescan PUT-007 hoisted for flat args, re-done per arg.commitOps → appendAndAssign (put.js:216) — ulog.each reads the whole wtlog, ulog.write REWRITES it whole + unlinks the idx sidecar; the log grows 1 row per arg → quadratic bytes.putOne already returns flat-arg rows for ONE batch commit; sub-crossing args bypass that return path ("commit to the sub's own wtlog and return no parent ops").733 sub-crossing puts cost ~1 flat batch (sub-linear in args): one stage.prep, one store.open, one wtlog append per DISTINCT repo touched by the arg batch.
put: banner).put <sub>#<tip>, arg names the sub itself) and SUBS-051 nested descent keep exact semantics; bump stays ONE row per sub per batch.appendAndAssign's ts policy (nowAfter(tail), +1 per row, restamp to row ts) is unchanged — one call per repo instead of per arg.verbs/put/put.js (+ shared/stage.js only if prep sharing needs it); no other verbs touched.ts+1n
cohort stepping stays (POST-029/GET-049 keep it in ulog.append).stage.prep + ONE wtlog write per repo (hook-based, RED on current code).{subRepo, wtlogReader, storeReader, engine, ops[]} across args in putRun; flush commits repo-by-repo after the arg loop..be rows vs a pre-fix run.