Repository navigation
cmd/codeaf: the model warm stops when the process closes - #1179
Merged
santoshkumarradha merged 4 commits intoSep 18, 2026
Merged
Conversation
The model catalog warm was started through guard.Go, which joins nothing at shutdown, and it writes the picker cache through tui3.WriteModelCache, which resolves CODEAF_HOME at the moment it writes. A warm that outlived the process that asked for it landed in whichever state root was current when the rows arrived — the next test's own TempDir, whose clean-up then failed with "directory not empty". It is now seated on the profile's start-up errand tracker (the #1124 machinery) and observes the tracker's context, so v3Process.closeAll cancels its wait and joins it before the conversations and stores close. cmd/codeaf/chatv3_modelswarm_test.go holds the catalog fetch open, moves CODEAF_HOME, closes the process, releases the fetch, and fails if the write lands in the moved root (deterministic: fails without the seating, passes with). Co-Authored-By: codeaf <[email protected]>
…this tree A review of #1178 caught its own summary claiming that warmV3Models "no longer resolves the catalog via ContextLength". It does — chatv3.go:2320 still asks ContextLength for the window. The sentence was wrong and the code is right, so the note now says what the function actually does and why the obvious way to make the sentence true would be a regression: the non-blocking v3ContextWindow seam matches an id with EqualFold where ContextLength normalizes it through catalog's own index, so a conversation started on model:high would lose the window it exists to learn; and FetchedAt on the next line reads through the same waiting door anyway. What moved is the WAIT, to Catalog.Warmed(ctx), and after it answers the blocking door cannot block, because LoadLazy closes its channel inside the future's own body. The note also carries the evidence the brief asked for and it was missing: the command, and the deterministic failure against the pre-fix fire-and-forget shape (0.11s, the write landing in the root CODEAF_HOME had already moved to), beside the pass with the seating in place. Co-Authored-By: codeaf <[email protected]>
…e catalog The reported sentence that warmV3Models "no longer resolves the catalog via ContextLength" was false on the first commit: it still asked models.ContextLength(started) for the window, and the pull-request check that reads the sentence held it. The naive fix — read through v3ContextWindow, the launch's non-blocking seam — is a regression: it matches ids with EqualFold instead of normalizeID, so a session started on `model:high` answers a zero window, and it leaves the FetchedAt read on the resolving door behind it. The warm now reads through two new never-waiting doors on the catalog, ContextLengthNow and FetchedAtNow, which read rowsNow (the ModelsNow door), answer zero/nil while a lazy catalog is still warming, and match ids exactly like the blocking twins. After Warmed(ctx) answers true the rows are always in hand, so the twins answer the same numbers without counting a blocking question; the warm's only wait is the context-observing one the close ends. Co-Authored-By: codeaf <[email protected]>
Co-Authored-By: Claude Opus 5 <[email protected]>
santoshkumarradha
force-pushed
the
task/c224-model-warm-stops-at-close
branch
from
September 18, 2026 07:30
7f4748e to
c438a8f
Compare
AbirAbbas
added a commit
that referenced
this pull request
Sep 18, 2026
… custom connections (#1194) Lands the integration branch santos/dev (#1108, head 6db5125, 152 commits over dev) on dev as one squash, plus two fixes that kept its touched-packages job red: the pool start-up test empties the CI word it is not about (649bb4b), and a zombie leader no longer keeps a job's process group alive on Linux, which had delayed every finished background job by the two-second termination grace since #1155 (053e6a9). #1108 carries the full account; the 94 change entries under docs/changes/unreleased/ are the per-change record. The commits folded in, oldest first: 5c413ef Keep archived evidence readable while tasks finish 3705a0c Document recovery evidence retrieval change 7834cc7 tui3, manual: a question's own answer box answers to the whole caret vocabulary 99d66e9 task: Add open/all source toggle to crew config c1e4cb5 docs: the change entry for the crew model family toggle e63b439 crew: round-1 review fixes 304a5a2 crew: round-2 review fix 65477c0 task: Add open/all selector to /crew TUI picker 9189ef7 Merge branch 'task/add-open-all-selector-to-crew-tu-9e8cc0' into feat/crew-open-all-toggle ba9c12d crew: reconcile the picker onto config's source helpers ea05bc0 crew: a chosen family counts as an answered crew e1a037e Merge remote-tracking branch 'origin/dev' into feat/crew-open-all-toggle f6c2924 crew: the open table takes the picks we landed on eff44b5 docs: the change entry carries the open-table move and the family gate 71ce9be crew: the family reaches the fallback, the settings skin, and the row's line b8ca5f5 crew: round-1 review fixes on the new table 8db5708 crew: round-2 review fixes ce9759e crew: round-3 review fixes 956e1f5 crew: round-4 review fixes 37ad27f crew: fixes from the first Opus round 6ffb958 crew: the fixes from the Opus confirmation round 72b0274 crew: the comments the atomic write left behind 70d0e20 crew: round-four fixes from the Opus review 234300e Merge branch 'dev' into feat/crew-open-all-toggle 894292f crew: round-five fixes from the Opus review fba77f9 An answered landing carries its answer's fate when it is raised again, and an in-flight settle holds the question down (#1077) e694450 Changelog entry for the landing-answer fate (#1086) 64ab7ea crew: round-six fixes from the Opus review 0700164 crew: round-seven fixes from the Opus review af09aa8 The review round's four findings: claim ownership, decider changes, recordable answers, plain words c24af02 crew: round-eight fixes from the Opus review 289d372 The tests the landing's answer carries: fate, hold-down, redraw, claim doors 7e38346 The take-down comes before every mint, and the tui3 walk reads the newest card dbe1be0 The review rounds' last word: the release owns its claim, and the docs say what the code does 4cc4945 The Opus review: a release owns its generation, and every failed round still says its word a5cce0d The done card says who is deciding, so the clause is a duplication, not the fact 6bd276e Merge fix/1077-landing-reraise into santos/dev 2710169 Merge feat/crew-open-all-toggle into santos/dev b459399 Merge fix/question-answer-box-caret-keys into santos/dev a384920 Merge codex/recovery-evidence into santos/dev dc22e64 cmd/codeaf: the role-door tests read the shipped mastermind instead of a copy of it 39f528f release: the pin-example check reads the guide the examples moved to 6439d3c session, tui3: gofmt the three files santos/dev merged unformatted 8540379 tui3: a question's answer box kills to the end of the line on ctrl+k d93ec61 session: the late-attach proposal test holds the work until the replay is read a81ead4 session: the landing-fate tests read today's clock instead of a fixed date 8fe4fff provider: the retry-avoid list rides the call's context into the knobs a266da3 provider: the retry-avoid veto narrows the wire through one field law 197d893 exec: a node retry asks the router to avoid the lanes that failed it 2d02cc0 provider: the set-servable law's doc comment keeps its paragraph break e1d0e75 exec: the retry test names its helper and spells its wall in time units e7d65a5 docs: the change note for the retry that avoids the failed upstream 11decbd Merge fix/provider-failover into santos/dev c099a51 Merge dev into santos/dev e47d92b provider, exec: the lane a failure names is a wire fact provider reads (#1105) 396be97 Merge dev into santos/dev 3b97f50 Merge dev into santos/dev ea3b5c9 telemetry: the send path drops events that cannot name their version 573b288 cmd/codeaf: keep smoke runs from sending usage counts e8d627c docs: the change note for the usage-count audit fixes 11bec8d chat: a hosted session's usage counts are read off the events it receives b9d75b3 chat: the hosted-count tee is tested against the tally it must produce 39fa08d telemetry: stamp a version in the lifecycle tests 681e2ad chat: count the hosted boot agent once, and conversations opened beside 825ef2c connect, settings: named, multiple, switchable custom connections (#1092) f4d3f9a Merge branch 'dev' into fix/telemetry-audit 76fec37 ci: the Model Pool mirror workflow and the signature check it runs 2fb296b crew: Pareto crewing, the learned picker and the Model Pool (#1093) a065ebb Merge remote-tracking branch 'origin/dev' into santos/dev 80ac250 Merge remote-tracking branch 'origin/feat/pool-mirror-workflow' into santos/dev 6092393 Merge remote-tracking branch 'origin/fix/telemetry-audit' into santos/dev ef808dd session: a decision card under --yolo takes its default (#1117) 70a9e1f session: a task's thread stops its parts before it closes, and a machinery cut says so (#1118) c4df8f3 crew: an auto seat's rung names the pick that ran, once, for both doors (#1119) a9fcf9b crew: a bare auto row under picked from = catalog reads the catalog's figures alone (#1120) 13be0dc relay: accept the exec and run doors, not only task and do (#1121) 90f29ae changes: name the #1120 note by its PR and stamp the pr field 0b77926 do: seat from the settings the run will use (#1122) 32148d8 changes: stamp pr: 1120 on the #1120 note 113ed62 pool: a launch's start-up errands stop when the process closes (#1124) 6c98ddc verify, codeaf: a launch compiles fewer patterns and a surface holds fewer Ps (#1125) 50fe6a3 pool: the min_installs floor counts installs on the client (#1127) 076cde8 relay: fold a resubmitted row once by its nonce (#1128) 8339ae7 relay: carry the acceptable metric beside role_quality (#1129) 0367c44 outbox: a refused row leaves the outbox and the rest of its batch is sent (#1130) 09aecd2 relay: the index holds only the cells that meet the min_installs floor (#1131) ae7c05d outbox: a Send compacts the file to the rows it still holds (#1133) 9e24f72 pool: the errand tracker's critical sections unlock from a defer (#1134) 358a569 relay: fold a submit batch with one read and one write per distinct cell and day (#1136) a1d2eb1 pool: judge landed-but-unjudged runs on the next chat start (#1132) 3c67228 verify: take the finished-tree check at a project cloned into a subdirectory (#1135) 263625b exec: a headless run leaves a pending judge record at its tail (#1137) 8be373d run: the headless task door leaves a pending judge record (#1138) a336b24 do: leave a pending landing for the Model Pool's judge (#1139) 8820a56 cmd/codeaf: one readPendingRows test helper, not two (#1140) 49760fb pool: show and verify print both metrics the index carries (#1141) 6eaeb25 pool: status shows the pending judge records and the last sweep (#1142) 7c00b5f codeaf: a test's crash fixture stays in a profile the test owns (#1145) fc2eb35 manual: the status row's ledger is the tree's, not the conversation's (#1143) a06b53a test: pin the delivery gate over a grown job root (#1147) 7231347 resident: the wall refuses a first replan round it cannot hold (#1144) 5e25755 cmd/codeaf: the package's test floor clears a profile the environment named 4dd1c56 Revert "cmd/codeaf: the package's test floor clears a profile the environment named" 9075f86 pool: the seed index is regenerated from the relay by seedgen (#1148) a5c44f3 pool: show --cells lists the held index's cells with installs and dims (#1149) 8259967 cmd/codeaf: the package test floor clears a profile the environment named (#1150) 81568aa session: report gitignored build products a run leaves, without gating on them (#1152) da49105 internal/tui3: the opening hint test owns its profile and pins both doors 72de3bc docs/changes: #1151 — the opening hint test owns its profile 499083d Revert "docs/changes: #1151 — the opening hint test owns its profile" beb31ec Revert "internal/tui3: the opening hint test owns its profile and pins both doors" 25b8b7d version: an unstamped binary names the condition, not a Makefile target (#1151) c833b42 pool: a dropped row keeps the relay's reason and status shows it (#1154) cbb7d6d processgroup: verify a job group's identity before signalling it (#1155) efe498e internal/tui3: the opening hint test owns its profile and pins both doors (#1153) 17b3b0e do: a continuation continues the plan it has instead of re-planning (#1156) 22cf3ce changes: correct the #1155 and #1156 notes' pr fields (#1157) 8e85b0f cmd/codeaf: a surface runs under a soft memory limit drawn from the machine (#1158) ef1e927 task: status row accounting (#1159) 44f572e pool: status's index line says what this run's fetch did to the cache (#1161) 3600fc9 pool: the pool's copy of a seat's id is the bare vendor/id (#1163) a2ebd13 cmd/codeaf: no test can push pool rows to the default relay (#1164) 4fb7f05 docs: the road a chat door bash push took under --yolo (#1160) 69219a1 cmd/codeaf: the soft memory limit is the smallest real bound, not the host s memory (#1162) 2b155b4 relay: bound judge severity, clamp adjusted scores, publish mean and sd on one scale (#1165) 2fb5532 relay: purge fixture vendors and refuse them at the door (#1167) 5750418 exec: a job's shell cannot reach the tmux server hosting codeaf (#1166) eb7b465 internal/session: an unattended run may not move a branch it did not create (#1168) 80f7706 pool: status says the identity, the judged total and that nothing has landed (#1169) 1cb7333 exec: an exec run's worker spend reaches the usage ledger (#1170) 96bb940 changes: the #1169 note's title parses and reads as one line (#1172) 627236d config: the task door honors a stored crew word, and a pin survives the learned pick (#1171) 593092b internal/tui3: a test may not read a profile it did not create (#1173) ad8be1e session: a landing card under --yolo takes its default instead of parking the run (#1174) 03d49a6 gitignore: the .furrow ids the engine writes into a workspace (#1175) 46edbdd internal/tui3: gofmt the profile-law helpers from #1173 (#1176) 3179e36 relay: ALLOWED_VENDORS names the catalog's vendors and codeaf (#1177) 397a145 docs: why a settled /task leaves commits on task/<slug>, not the worktree branch (#1178) 915ff3a pool: regenerate the seed index from the relay's first clean publish (#1180) d9b4243 cmd/codeaf: the model warm stops when the process closes (#1179) bd2dcf3 session: the auditor's calls say they are the auditor's and which node they check (#1181) c6ed47c task: json unverified branch (#1182) 360352a session: a settle turn runs under the checker's own bound, not the run's wall (#1183) 814e1af session: the tasks text and record line name a failed/unverified node's kept branch and verdict (#1184) 7c25a26 internal/session: the identical-effects test stops riding the turn guard (#1185) 6e7d0ed cmd/codeaf: exec and run envelopes name a non-verified node's kept branch and verdict (#1186) f4079d7 session: bound a worker's park on a promoted command (#1187) 994689f docs: the model pool runbook names the Workers Paid plan and what it costs (#1188) c315be1 docs: the runbook's plan line says metered, not capped, and nothing more (#1189) 2e80c60 internal/tui3: the status word says working while a task subtree turns (#1191) 99363ca session: bound a job subtree's CPU and process count (#1190) 6db5125 docs: what the audit check seat can execute, and where that boundary is set (#1192) 649bb4b cmd/codeaf: the pool start-up test empties the CI word it is not about 053e6a9 processgroup: a zombie leader does not keep a job's group alive b4e66f5 docs/changes: the entry for #1194 Co-Authored-By: Santosh kumar <[email protected]> Co-Authored-By: Claude Fable 5.1 <[email protected]>
santoshkumarradha
pushed a commit
that referenced
this pull request
Sep 19, 2026
… joins The change note takes the PR number (1264 named a PR that has already landed). With the catalog warm owned, cancelled and joined through Models.Close, the chatv3/models and engine/models rows in the guard.Go inventory law no longer carry an open writer one package down: each names the waiter joined since #1179 and the internal/catalog warm that v3Process.closeAll now cancels and joins. chatv3/sweep-home is the one known-open row left.
AbirAbbas
added a commit
that referenced
this pull request
Sep 27, 2026
* fix(chatv3): seal the place sweep note at close instead of joining the walk (#1227) * test(provider): order the late-first-token rescue by a signal, not elapsed slack (#1229) * remote, tui3: a hosted conversation reads and steers its run (#1230) * tui3: a task's page opens with the task, and a run's rows on the rail open their pages (#1231) * fix(run): a self-finished codeaf do run seats its review before it ends (#1233) * run: declared checks are the review contract, judged by observable properties, with the verdict basis recorded (#1220) The worker declares its checks, and a holds verdict rests on a recorded, auditable, zero-exit run of every declared command. The worker records each command's own exit status, an absent status reads as unknown and earns nothing, and a record opens with a line that marks it as one that records exits, so a declared check that was never observed is refused, including when the record is missing or empty. A record written before exits were recorded still holds by reading and names the checks it did not observe. A does-not-hold verdict is ungated, a task with no declaration gets a reading verdict, and the verdict basis is persisted. One quote-aware reader of "is this one command" lives in internal/approval and is shared by the proposal door, the checker's runner gate and the store. * test(connect): the second-door tests use dynamic ports, not the fixed callback ports (#1237) * session: a run works in a copy of its own, and its work comes home when it ends (#1232) * tui3: a wait whose stream has stopped arriving paints at the spinner cadence, and the harness that measured it (#1235) * tui3: a task's page says only what is true of it (#1240) * chat: name unread config.json keys once before defaults apply (#1239) * test(exec): identify a leaf-end survivor by process identity, not a bare PID (#1238) * config: concurrent profile writes in one process compose (#1242) * run: regression test for seating the check after a self-finished worker returns (#1243) * session: every run a conversation has made stays readable (#1234) * scripts: a repeatable hosted drive of a run on the real binary (#1241) * config: unread notice skips keys the product itself retired (#1245) * tui3: what is typed after a press on a run's row goes to its page, never the conversation (#1244) * session: make the claim-unspent revision test deterministic by forcing the ordering (#1246) * fix(connect): a reconnect reuses its registered identity instead of introducing itself again (#1236) * test(connect): stop the second-door Slack test racing on a freed port (#1247) * config: two codeaf processes writing the profile at once keep each other's keys (#1248) A profile write now takes a cross-process lock on a stable config.json.lock for the whole read, copy and rename, inside the in-process write mutex. The lock rides internal/filelock, so it holds on every platform the repo builds for. A write waits at most two seconds for another process and then fails with a plain timeout error. The lock file is never unlinked: a crashed holder is released by the operating system closing its file, so there is no stale lock to reclaim. Tests run two real processes forced to contend and show both keys survive, a bounded timeout, and recovery after a holder exits without unlocking. Those forced tests are unix-only; their clocks are sanity bounds, not load bounds. * exec: the job shell tmux tests read one marker line, not a full env dump (#1249) Both tests dumped the whole environment through the capped output collector and read variables out of it. On a machine with a large ambient environment the dump is cut before the TMUX_TMPDIR line, so the test read it absent while the job shell had it, and the two checks for stripped variables passed without proving anything. The shell now prints the three variables on one short line with an explicit word for unset, through the same login shell road the product uses, and the test fails if that line is missing from the captured output. Test only; the product was already correct on every platform. * tui3: a press never waits behind a run's summary, and an open page follows on a beat (#1251) The run's summary is a read nobody pressed for, and its refresh can wait on a model for seconds. It was asked through the one ordered line that carries a person's gestures to the engine, so a press on a run's row, or a stop, waited behind it: 2.4 to 10.1 seconds on a real screen, 0.09 to 0.18 after. The summary now goes beside the line. What may leave the line is decided by property: the ask was not a gesture and nothing a person does next depends on the engine having seen it first. A law lists every door that stands in the line and fails when one is added without a reason. An open page on a task that can still move was re-read on every paint tick once the last read answered, 509 wire reads in ninety seconds for one page. It now follows on the rail's own beat, counted from the last read for any reason, and a page on an ended or held task is never read again. The follow stays in the line because its fold replaces the page and must not overtake a note. * session: a person's stop reaches a run (#1250) A run under the task belt was started on a context nothing could cut, and no cancel was kept. Its rows wear task numbers, but a stop by number went only to the task graph, which has never held a run's rows. Measured on the real binary in hosted mode: the stop card answered that there was no such task, the run's own page answered with the store's sentence about who owns the root, ctrl+c and /quit closed the window, and the run carried on to its own landing every time. The id a surface already sends is now resolved to whoever owns the row, so an older window stops a run through a newer engine. A stop of a run ends the run's task and everything open under it in one store write, then cuts the run's context so workers and their calls end at once, then commits what was made on the run's own branch and gives the copy back. Nothing goes into the person's folder, no model turn is bought, and the person is told where the work is and how to bring it in or drop it. A hand-off that joined a run is stopped alone. A stopped run buys no further summary. On the surface: x on the run's own page raises the same stop card, x typed while that page is still loading reaches it too, the page no longer offers a hold the store would refuse, and ctrl+c is read above both the loading page and the open page. A law lists every place that publishes a running row and fails until each has a test that a stop on that row ends its owner. What ctrl+c, /quit and a closed window do to a live run is unchanged here. * repo: remove a cell's findings note that rode in with #1239 FINDINGS.md at the repository root is a working note from the cell behind the unread config key notice. It is not product documentation and nothing reads it. A cell's findings belong in its run folder, outside the repository. * exec: the skill path shell test reads one marker line (#1252) The test printed the whole PATH through the capped output collector and read its first entry. On a machine with a long PATH the capture can be cut, and a cut capture could read as a pass or as a false failure. The shell now prints the first PATH entry on one short marker line, and the test fails when that line is missing from the capture. Test only. * build: make check cross-compiles every platform the release ships (#1254) make check built and tested only the host platform, so a symbol defined for one platform family passed the gate and would have broken another platform's build; only a nightly would have caught it. A new build-cross step reads the shipped platform list from the release workflow, builds ./... for each with the shared build cache, names the target that failed, and prints its own duration: about 11 seconds on a warm box, at most about 111 on a cold one. * chatv3: the standing ticker stops and is joined when the process closes (#1256) The standing ticker was a package-level goroutine started once and never stopped, so it could write under the home's v3/standing folder after Close returned; one of the temp directory cleanup failures in the full check named that folder. The ticker now belongs to the process: Close stops it and waits for its loop, and a second process in the same binary starts its own. Quitting never waits out background work: the ticker holds one context that the stop cancels, and a pass derives its 120 second ceiling from it, so a pass in flight ends at once when the process closes. A cancelled pass leaves no half-written file, because standing writes are temp then rename or single short appends and a pass checks its context between steps. Tests: no write under v3/standing after Close on the real close path, a second process starts its own ticker, and Close returns promptly with a pass held in flight, ordered by channels. * tui3: no frame reads a run's rows, whatever made the read due (#1255) The side list read a run's rows from the engine inside the frame: on the first reading, when one of the window's own rows moved, and when the beat came due. Over a connection that read is a call to another process, so a slow link froze the frame for as long as the read took. Measured on Spark with 250 ms injected on the read, real remote client and engine: 254 to 258 ms per frame on trunk, 0.1 to 0.7 ms after, with no read inside the frame. The rows are now asked for from the update loop, held on the surface per conversation, and the frame draws what is held; a verb that lands while a read is out is answered by one more read. The law that lists every door still on the loop could not see a door reached through the plan reader; it now can, so every plan door is under it. The hosted drive script opens the side list with its own key when the copied profile starts with it hidden. * tui3: a run's step rows show the work, cut from the commands as they ran (#1257) A run's page listed every step a worker ran, including the run's own bookkeeping (recording its plan) and the change into the run's own copy, so the work a person wanted to read was buried. The session now says, for each part of a recorded command, whether it is the run's bookkeeping or a change into the run's copy, and the surface leaves those parts out. The recorded command is never retyped. internal/approval's one reader of a command line now also reports each part's byte span, and the surface cuts the left-out spans from the line as it ran, so spacing, operators and closing brackets stay as typed; approval verdicts read the same trimmed segments as before. A part is marked only in sequence, never inside a substitution or a group; a pipeline headed by a record command is left out whole; a run's copy is recognised under the task belt and after it is given back; the display facts cross the wire, so a hosted conversation sees the same page. Rows keep their recorded numbers. * tui3: a task you stopped reads stopped wherever its state word is drawn (#1258) After a person stopped a run, the side column said stopped while the run's own page said incomplete, the word for an ending nobody judged, and the page offered its verbs again. Measured on the real binary in hosted mode: the stopped run's page read incomplete twelve times and stopped never; after, it reads stopped everywhere and incomplete nowhere. Whether a task was stopped by a person is now established once, in the session, from the store's own record of the ending, and crosses the wire as row data; every place that draws a state word reads that one fact, so the page, the side column and the rail cannot disagree. A part a stop took down reads stopped too when it was cancelled in the same instant as a stopped ancestor, which the store's cascade guarantees with one timestamp. A stopped task counts as ended, so its page no longer offers verbs the store would refuse. * session: usage writers have an owner and are closed when the process exits (#1259) A usage ledger writer was started on first use and never stopped, so it could outlive the process close that started it; under repeated runs of one test binary the leaked writers piled up until the run timed out. The process now owns its writers: CloseUsage detaches every writer and closes its queue under the same lock rows are enqueued through, the writer drains what it holds so the last row is on disk, and every exit door that used to flush now closes, including the engine host and the fallback that serves a conversation on a pipe. Closing keeps the bargain the flush already made: one ceiling covers the whole call, so a writer stalled on a disk that is not answering is left behind rather than holding the exit open. A flush hands its markers to the writers under the same lock, so it can never send on a queue a close has just closed. A usage writer holds the path it was started with, so it is not one of the writers that land files in a later test's home. * test(session): the claim-unspent revision is typed through Submit, the door a person uses (#1263) The test pushed its mid-turn revision straight into the agent's steering queue and needed a forced lock ordering to see the second ending. It now submits the revision the way a surface does and drains that revision's own stream, so it asks for exactly what the product promises: a direction typed while a turn runs earns its own ending on a normal completion. Test only. * tui3: a call that did not run has no row, and a refused action is one plain line (#1260) A task's page drew every harness-made answer as a numbered step, so a correction addressed to the worker and an action a door refused both read as work that ran. The engine now records two facts where the event is known: the call did not run, and whether it was an action the worker attempted and a door refused. A correction about the form of a reply stays in the record and draws nothing. A refused action draws as one dim line in the step's place, led by the word the permissions page already uses, with what was tried and no number, and the rows around it keep the numbers the record gave them. The surface never reads the answer's words, the facts cross the wire so a hosted conversation sees the same page, and a record written before the fields existed draws as it did. * scripts: the heavy-suite lock is a kernel file lock, so one pid namespace cannot misread another (#1264) The lock was a file holding a pid that a contender checked under /proc. A cell's worker runs in its own pid namespace with /tmp shared, so each side read the other's pid as stale, removed the lock and started a second heavy suite, which is how the box reached a load of thirty with the lock in place. A small helper now holds an advisory lock on an open descriptor through internal/filelock while the suite runs. The kernel arbitrates it whatever namespace the contender is in, it is released when the holder dies, and there is no staleness rule left to get wrong. The file keeps the holder's pid and start time only so a refusal can name them. The helper builds for every platform the release ships. The acceptance script covers a cell against a host in both orders, and a second host run after a cell's refused attempt. * chatv3: the pool judge sweep is stopped and joined at close (#1267) The start-up sweep that judges finished work ran on a goroutine nobody waited on. It outlived the process close that started it, and in one test binary a survivor resolved its home from the environment at write time and wrote pool files into a later test's temporary home, which is one source of the directory-not-empty cleanup failures. The sweep now starts on the profile's errand tracker, so closing cancels it and waits for it, and it observes the cancel between landings and inside a judge call. A landing the close interrupted is not a verdict: it is left unjudged and unmarked and its claim stays in place, so the next start judges it. A close waits for at most one in-flight judge call observing the cancel. A new law lists every goroutine a v3 process or launch starts and requires each to name its stop and its join at close, or to name the later change that owns it. Three are listed as still open. * test(chatv3): the goroutine law says truthfully what the two model warms are (#1269) The rows for the two model warms read as known open. Their waiter has been joined at close since #1179; what is still open is the catalog's own warm, one package down, which writes its cache with nothing to stop or wait for it. The rows now say exactly that and name the change that owns it. Test only. * run: a time limit a person sets ends a run by the same road as the cost limit (#1262) A conversation's time limit never reached a run the worker harness started, so a run kept working long after the limit its person had set. A run is now handed what is left of the conversation's time when it starts. When that passes, no new worker starts, the workers in flight are ended, and their endings are absorbed the way the caller's own wall absorbs them, so no task is left claimed on a run that is over and what a cut worker spent is still counted. The run then ends on the same limit word the cost limit uses, and the conversation is told which limit it was when the run has no result of its own to report. On the real binary in hosted mode, a run under a 108 second limit ended at 105 seconds; before this change the same run was still working at 261. A dollar limit typed on the command line does not reach a run yet. That is a following change. * tui3: the dim line under a step row is only ever the row's own (#1265) A step's row can leave out a part of the command, the run's own bookkeeping or its change into its own copy, but the record holds one answer for the whole line, so the dim line under the row could be the left-out part's print drawn as though the shown command had said it. The engine now says when that can happen: the head of what came back is drawn only when every part left out of the row cannot have written it and stops everything after it if it fails. A part addressed to the run's record always withholds the head; a leading change into the run's copy keeps it only when joined so that its failure ends the line. The fact is the negative on purpose, so a record from an older engine draws as it did, and the page reads the fact and never the words that came back. Measured over 25 recorded runs, 280 drawn rows: 242 keep their line, 38 lose it, every one to a left-out record part. Three day-old test expectations that asserted a head the engine cannot attribute were changed as one decision. * run: a task's step numbers continue across a wake (#1261) A task that waited and was woken numbered its steps from one again, so its record held two steps called 1 and the page drew the woken work under numbers already used. A worker now starts from the last number its task's record holds, the live step is announced under that same count, and every child still numbers from one. The per-wake step cap counts as before. On the real binary in hosted mode, the same split-and-wait task recorded 1, 2, 3, 4, the wait, then 1, 2 before this change, and 1, 2, 3, the wait, then 4 after it. * run: a dollar limit holds while a worker is still working (#1268) A run's dollar limit was only read when a worker came home, so one long worker could pass the limit many times over before anything counted it, and a worker that never came home was never counted at all. A worker now tells its run what it has banked as each call is paid for, as a cumulative figure it writes without ever waiting on the run, and the run counts it while the work is going. When the sum reaches the limit the work in flight is ended and its endings are absorbed, the same road the time limit takes. A return reconciles with what was counted on the way, so a dollar is counted once. What a run counts as spent never goes down and includes every paid call whatever way its task ended: a task the store cancelled under its worker, and a worker the run outlived, no longer hand back their dollars uncounted. The call that reaches the limit is already paid for, so a run can end a little over. * plandb: a parked task is finished only by a worker woken for it (#1271) A task that parks to wait releases its claim, but a round its worker had already started still ends its tool, and when that tool was the task's own finish the store admitted it. On a run's own task that closed the run over a child nobody had reviewed, and it left a pair nothing legitimate produces: done and still waiting. The store now refuses an ending on a parked task on every road that writes one, from one statement of the law. The task stays parked and is woken the ordinary way when its wait is over. This is the store's half. That no run answers done before a review is a following change. * enginehost: a launch that lands while a host is coming up reaches it (#1272) A host takes its lock, removes the old socket, then listens. A headless message or an opened task row that dialled inside that window was refused and went on without the host. Both now dial through one function that retries a refused or briefly missing connection only while the host's lock is held, for a quarter of a second at most. With the lock free it answers at once, so the ordinary state after a host crashed and left a stale socket costs a launch nothing. Asking whether a host is there still creates nothing on disk. The test helper that waited for a host now proves a real accept where it used to trust the socket file and the lock, which is what made one host test fail under load. On Windows a refused connection is not recognised, so the first answer stands there as before. * scripts: the heavy-suite lock follows the suite, not the helper that started it (#1270) The helper held the lock, so a helper that was killed released it while the suite it had started kept loading the box, and a second suite could start on top. The suite now inherits the lock's descriptor, so the lock lasts as long as the suite does, and the lock file names the suite's process. The cost is stated and handled: anything the suite starts inherits the descriptor too, so a leaked process can keep the lock after the suite is gone. A refusal therefore never calls the holder gone. When the recorded process is not visible from the contender it says so, gives both readings, another process namespace or a holder that exited and left the lock to something it started, and names how to find the real holder where every process is visible. The refusal's first line is unchanged. Windows keeps the old behaviour. * run: a worker that has stopped making progress is told once, then ended (#1266) A worker that alternated one action with one short reply never reached the ending that counts replies with no action, so only the 200 step cap stopped it; a real pair of workers made about 820 calls that way in half an hour. The belt now reads the record: the same command coming back with the same answer, with nothing moving under the worker's own task between the looks, is work that is not moving, whatever the tool and whatever the cause. After three such steps the belt says so once, in its own voice, with what the worker can do: try something else, wait in one longer action and how long one action may run, or park when the plan names what it waits on. Three more identical steps after that end the task with a reason a person can read. A changed action, a changed answer, or a move under the task resets the count, the note included. What other tasks in the run do is not this worker's progress. A task the store says is blocked on another task is parked, not ended. * catalog: the model catalog's own warm is cancelled and joined at close (#1274) Opening a conversation starts a background fetch of the model catalog that writes a cache when it lands. Nothing stopped it or waited for it: every caller handed it a context that never ends, so it could write after the process had closed, and in one test binary it wrote into a later test's home, which is one source of the directory-not-empty cleanup failures. An earlier change had joined only the code that waits on the warm. The process now owns a lifetime context, the catalog's warm runs under it, closing cancels and joins it, and a fetch that succeeds after the cancel no longer writes. The engine process closes the same way when its serving road returns. One-shot commands that exit at once keep their plain context. The goroutine law's two model warm rows now say the warm is joined. * run: no run answers done while a finished piece of work has had no review (#1275) A landing is a return the run has absorbed, not a store row. Reviews are seated through one store seam, AddReviewCheck, which moves every done ancestor back to waiting and keeps what each earned. A review the store will not seat ends the run as incomplete. The root is read after queued returns are absorbed. A worker that writes done in the middle of a command holds the run open until that command ends, at most 600 seconds. * approval: a backslash pair inside double quotes is text, read by one scanner (#1273) One quote scanner sits under both the composition reader and the bar reader. A backslash pair inside double quotes is stepped over as text. Every shape the scanner cannot settle is refused, never waved through. * session: a woken turn's fixture holds its script like every other turn (#1278) A woken turn has no caller, so a fixture that pins an order through its turn's context could not reach it. One unexported field, nil in the product, lets a fixture supply that base. Nothing a person sees changes. * chat: closing a process stops its place sweep before it can change a later home (#1276) Each process owns one place sweep. Close cancels the walk and joins it. Cancellation is checked between entries and before every rename or removal, so the join waits only for the one file operation in flight. * session, run: a run's dollars reach the conversation's own total (#1280) The run hands its owner its reconciled cumulative spend whenever it rises, with or without a dollar limit. The conversation folds each rise through the door that writes no ledger row, and closes on the run's final figure before the stop road and the landing road split, so every ending counts once. * run, session: an ending a person caused is not a fault, and it names the limit (#1279) A run ended by a limit carries which limit as a typed fact beside its outcome word. The row reads 'a time limit you set stopped it' or 'a dollar limit you set stopped it' and is not drawn as a fault. The first limit reached names the ending. Tasks the ending cut read the same way; a task that failed by itself keeps its own reason. * suite lock: the lock follows the suite, not what the suite leaves behind (#1284) The locked descriptor goes to a small holder beside the suite, in its own session, which exits when the suite's pid is gone. The suite and its children never inherit the lock. After the suite ends the wrapper waits for its holder and reports one that is still running; it ends nothing. * exec: a cancelled command is ended by the call that ran it (#1282) The cancel arm of a foreground command sends the group kill itself, unless the call was adopted or the process was already reaped. A cancel that arrived as the command started could leave it running under no parent; the watcher alone did not order the kill. * session: a run is handed what is left of every dollar limit (#1281) A run's dollar ceiling is the smaller of the conversation's dollar limits, less what the conversation has already spent, read through the one place that decides which limit is smaller. A second run gets the remainder after the first. * session: a long declared check says why it was refused (#1283) A declared check over 1000 bytes is refused with its cause and its own measured length. The limit guards against a program pasted in as a check; it governs admission only, so a check already recorded stays on the checker's door. Both doors measure the same trimmed text. * session: a usage flush says whether it drained (#1285) FlushUsage answers true when everything in front of it reached the disk and false when its ceiling fired. StopUsageWriter detaches one path's writer and joins it under the same ceiling. The stalled-path test stops its writer and asserts the drain instead of trusting a timer. * standing: a row that needs you names what it needed and opens the item (#1288) A watch that stopped because a call needed permission and nobody was there to give it left the engine's own refusal on the item, verbatim. That line is written for the worker that must act on it. It reached home under the mark that says a person is needed, and pressing the key on it opened an idle conversation with nothing in it to answer. The firing now writes the sentence the surface already says, in the past tense, naming what the call actually wanted: the command for a shell call, read through the package's own table of which argument says what a call is doing, and the tool's name otherwise. It never claims how much was refused, because it fires on the first matching failure and knows nothing about the calls that succeeded before it. One predicate decides what a permission stop is, asked by the store when a person changes an item and by the surface when it picks a row's door, so a line cannot be a permission in one place and a question in another. It matches on what a line says rather than its exact words, because three doors in this build write such a sentence and they share no spelling. It requires the refusal word alongside the absence, so an ordinary question that happens to carry both other parts is never mistaken for one. It knows the spelling older builds wrote, which is what the items stuck on disk today carry. A row stopped on a permission takes the item's own page, where pausing it, stopping it and letting it go are. A question keeps the conversation that asked for it, which is the owner's ruling of 2026-09-15. Changing an item puts down a permission line and never a question. * tui3: a run task's page draws its brief through the reader the transcript already uses (#1289) A plan task's description can be the work order a run hands its workers. The page drew it raw, so a person opening one task of a run met the machinery addressed to the model first: the shouted scaffold heading, the rule under it, and only then their own ask. The brief section now draws through the same reader the conversation's transcript already applies to that text. Plain headings, this task's own work first. A description that is not the generated document passes through unchanged, so a brief a person typed themselves draws exactly as it always did. The stored text is never touched; only what is drawn changes. The key that folds the brief open counted the raw wrap while the block drew the reshaped one, so the count and the fold could disagree about how many lines there were. Both now read the same function. * session: a run is work, and its life is the conversation's (#1291) A run's rows are kept beside the task graph rather than in it, so the walk over the graph's own nodes never saw one. A conversation whose only live work was a run answered that it was working on nothing. The engine reads that answer to decide whether to retire a conversation, so a run made its own conversation invisible and was retired out from under itself at thirty minutes, mid-run. The tree now carries the run's row, under the number every surface already knows it by, so the engine stays alive while a run lives. No engine-side rule was added; the retirement and the host's own quiet both hang off this one reading. A run also drops the proposing turn's cancellation on purpose, and nothing else could reach it, so its life belonged to the process rather than the room. A conversation that ended while a run was going left workers spending against a room nobody could read or stop. Closing the conversation now cuts it, in the same place and for the same reason as the adaptive runs it already cut. It writes nothing on the run's record, because a room closing is not a person's stop, and a test holds that line. * session: a standing firing runs behind the gate a task node runs behind (#1290) A firing copied the conversation's approval policy, which on a fresh install prompts a person, while also being told it was inside a task where no person exists. Every decision came back a refusal. What survived was the read-only lift, so a watch could not run one shell command, not even one the person had explicitly allowed. A real watch fired thirty-nine times over four days, spent real money and did nothing. A firing now carries the policy a task node carries: allow everything except approval's critical floor. The floor still turns an allow into a prompt for the shapes that destroy a disk or drop the machine, and a prompt with nobody to ask is a refusal the firing can read and report. The calls that act in the person's name outside this machine stay refused, and a bash call whose arguments cannot be read stays refused. The owner decided this on 2026-09-20, after being shown the shape and what it widens. The probe road is deliberately untouched: it is a different function with a different risk, and a watch's probe command never meets this gate at all. * task: Trace settings to permission gate wiring Co-Authored-By: codeaf <[email protected]> * task: Inventory every settings row for the UX review Co-Authored-By: codeaf <[email protected]> * session: a run stopped on its own question says so (#1293) A conversation whose sub-harness had stopped on a question reported that it was working. The question was registered and open, but the reading that decides whether a person is needed never looked at that lane, so no row appeared on home, no mark was drawn, and nothing moved until somebody happened to open the conversation. Work stopped in silence. Two halves, and either alone is worse than neither. The reading now counts that lane beside the others. And the lane banks its question at the desk the sentence is read from, rather than only raising it, so the row carries what is being asked instead of an attention mark with nothing under it. Three sibling lanes share the shape and are deliberately left: sign-in, the sub-harness offer and the harness design card all raise without banking, so each draws a mark with no sentence today. They belong to a change about that desk, and a fix that quietly repaired four lanes while claiming one would be a fix nobody could review. The reading also split along the seam its own comments had drawn. One part reads every lane under the lock and hands back what the answer is made of; the other decides which answer a person should read first. The lock discipline is unchanged and no lane's behaviour moved. * task: Implement fzf v2 fuzzy matcher universally Co-Authored-By: codeaf <[email protected]> * session: work nothing is driving reads as interrupted, not failed (#1296) A row that was live when the process went away came back stamped failed and stopped, and the run's row said it had ended when codeaf closed. Two untrue things in one line: that the work went wrong, and that somebody ended it. It now reads interrupted, which means nothing is driving it and everything it did is kept. The word lives on the reading rather than in the surfaces, so one rung on the state ladder and one on the presence ladder give the tier, the mark, the two answers and the sentence, and no surface grows a table of its own. Continuing sits in the person's tier and is never the model's answer, because continuing spends money. The ask and its two answers are carried here rather than in the cell that draws them, because a your-call row is required by its own type to carry an ask, and splitting it would ship a card with no answers for one gate cycle. The old sentence is deleted rather than left unused, and the manual page that quoted it is corrected in the same change. A known window while the piece that continues work is still to come: a conversation that genuinely ends cuts its run, and a reopen still stamps that run failed. That is named rather than left to be discovered. The const block is reformatted here because the new sentences widened it. It reached a gate unformatted because the check that should have caught it read an exit code from a tool that reports by printing. * session: a run's working copy is written down, so it can be found again (#1298) A run's stored row carried its number, title, state, cost and timings, and nothing about where its work is. No folder, no branch, no ground. After a reopen the product knew a run had existed and had no idea where to look for what it did. The row now carries the copy, and the road that makes a copy is never asked twice. That road mints a new branch name with a random suffix on every call and clears the directory it is handed, which is safe only because the sole thing that can be sitting there is that run's own wreckage. Called a second time for a run being resumed it would delete the work and hand back an empty branch that looks correct. A run with no copy recorded is refused, in words naming what is missing, and nothing on that road may carve a directory, cut a branch or derive a path. Every row on disk today predates this field, so that refusal is the answer those runs get forever rather than a stopgap. A copy gone from disk is refused too, naming the branch, because the branch is the one thing a person can still act on. The directory is derived from the run's number. The branch is not: it is random and nothing else writes it down. That asymmetry is the whole reason this record exists, and a reader who believes both are reconstructible will delete this change. Every publish replaces the row and only the first knows where the work is, so the carry-forward lives inside the one function every publisher goes through rather than in its callers. A notice naming a copy of its own still wins, as the more recent reading. * manual: the interrupted page no longer offers two answers nobody can give (#1304) The page said an interrupted row offers two answers, continue it or leave it. Nothing draws them. There are three readers of a task's two answers in the tree and a run's row reaches none of them: it never enters the index the needs panel reads, it never holds the state the landing question requires, and it never matches the question the record card looks for. What is true stays. The row does wear the asking mark and does wait for a person, through the surface's own node rather than through the record, so that clause is kept rather than softened away with the rest. In place of the promise the page states the limit: nothing picks an interrupted run up yet, no key, command or background pass does, and nothing ever will without being asked. That is the sentence a person came to the page for. * fuzzy: land the remaining surface cutover (autonomy, connections, memory, folder search, rewind, registry) and manual pages Co-Authored-By: codeaf <[email protected]> * fuzzy: state the gap price the code and tests charge fzf's own algo.go charges scoreGapStart (3) for the first skipped byte and scoreGapExtension (1) for each byte after it in the same gap; the const-block comment claimed the first byte paid extension too, which the alignment, the gap-price test, and fzf's source all contradict. Co-Authored-By: codeaf <[email protected]> * docs: a footprint benchmark, its method, and one day's numbers (#1126) * task: benchmark methodology Co-Authored-By: codeaf <[email protected]> * readme: the one-binary claim is measured, not asserted Startup, on-disk footprint and idle memory against six other agent CLIs, placed next to the sentence they substantiate. Memory is PSS because the two-process design shares one binary's text; the metric the fastest competitor wins is in the table rather than only in the linked write-up. Co-Authored-By: codeaf <[email protected]> * readme: cut an opening sentence the window section already makes, and a badge The second sentence of the opening paragraph restated the one-window-and-tasks framing that "One window for every project" and "What a factory is" carry in full. The model-pool badge repeated a link the section's own prose and the design document already give. Kept as its own commit so the cuts can be dropped without losing the measurements above. Co-Authored-By: codeaf <[email protected]> * docs, readme: the footprint method, the full named table, and a five-row summary The cell wrote measure-cli.sh but no methodology, and a three-row table that omitted the two strongest honest rows while keeping two we lose. This adds docs/benchmarks/README.md (method, the four rules the numbers depend on, and the known limits including the wakeup figure being unusable), docs/benchmarks/results-2026-09-17.md (the full table with every CLI named, including the three rows where another binary beats this one and the row where it could not be measured at all), and replaces the README summary with five rows. Co-Authored-By: Claude Opus 5 <[email protected]> * changes: note the footprint table entry for #1126 Co-Authored-By: Claude Opus 5 <[email protected]> * readme: restore the hero block the footprint section displaced My own edit script matched from the new heading to the next ## and so removed everything between, which is the hero: the early-preview notice with its Discord and issue links, the overview screenshot and its alt text, the demo video and its caption. They are restored untouched and the footprint section now sits between them and Install, which is where it belongs anyway. The em dash in the closing sentence is gone, and the table ranges read x to y rather than using a dash. The two listed README cuts are unchanged and remain in their own commit (cbc1d0d5). Co-Authored-By: Claude Opus 5 <[email protected]> * readme: leave the top-level README as it was; the footprint table stays under docs/benchmarks Santosh reviewed the README hunk and asked for it to go. The method, the script and the full named table remain in docs/benchmarks/ and the change note now says so. * docs/benchmarks: the results file, not the README, is the script's output * changes: name the #1126 note for what it notes * docs/benchmarks: record the machine's load average around every timed phase load1/5/15 land in phase=meta, and again immediately before and after the startup and idle windows, read from /proc/loadavg with a builtin read. Telemetry only: no default changes, no new dependency. Co-Authored-By: codeaf <[email protected]> * docs/benchmarks: the README now says what the script does and how to invoke it The example invocation was flags the script does not have (--name/--home parse as NAME and HOME), cold start was documented as best-of-N with no word of the discarded warm-ups or the warm cache behind it, and the README claimed every table in the results file was this script's output when the on-disk and one-turn tables are not. Load recording documented. Co-Authored-By: codeaf <[email protected]> * docs/benchmarks: the method review — per-column verdicts against the competitor test Three columns fail the read (version rows contradicted by the box, on-disk rule stated nowhere and unreproducible, one-turn sampler uncommitted); the startup, frame, idle and strace columns hold with caveats. Facts 3 and 4 caveated, not invalidating. Evidence named for everything still open. Co-Authored-By: codeaf <[email protected]> * docs/benchmarks: the rerun that tests the review, and what it settles The method review above was taken by reading. This adds the measuring: all seven CLIs again in one session on a quiet box, at ONE build for every column (4f002ca2), with the one-minute load recorded beside every figure and sampled six times across a full minute before the run began. Startup and first frame stand and are published with the load beside them. Several figures move a long way from 2026-09-17 — claude's first frame 572 to 429 ms, cursor-agent's 1,298 to 592, pi's startup 330-360 to 146, omp's first frame 400 to 1,254 the other way — which is the load finding demonstrated rather than argued. The idle memory column's verdict MOVES, from "fair with caveat" to "cannot decide, and not reproducible as it stands". Every one of the seven measured procs=1, where the table has CodeAF at 2 and cursor-agent at 5. It cuts both ways and is nobody's win: CodeAF's PSS came out 38.7 MB against 66.4, and cursor-agent's 194.4 against 412.7. Three explanations were tested and killed — not the sweep or the choice of HOME (sweep_home is teardown only, and a controlled pair under a dedicated home and under the operator's own both gave procs=1), not a product change (cursor-agent is the same version string), and not a different script (the script was committed before the results). What is left is the invocations, which were never recorded. Also records what each CLI actually resolves to. opencode is not on a login PATH at all and exists as two different products; pi is a JavaScript bundle; codex and cursor-agent resolve to different binaries depending on how the measuring shell started. The header's own two-build split needs no caveat: the package-init changes have landed, so measuring at one build removes it. The run's log and the rig it was taken with are committed beside the note, so the next person to ask this question starts from a recorded rig rather than from a reconstruction. Co-Authored-By: Claude Opus 5 <[email protected]> * benchmarks: make CLI teardown workspace-specific * docs: correct defective memory measurement * benchmarks: split teardown survivor accounting * docs: repair benchmark method review prose * docs: record c245 teardown accounting checks * benchmarks: retain launched process ancestry * benchmarks: state which processes the idle figure sums The script sums the processes of the launched tree that are alive at the last sample of the idle window, and the README did not say so. A reader comparing two CLIs could not tell whether a helper that exited during the window was charged or not. The rule is stated with its reason: a steady-state figure must not depend on how long the window was, which is what summing every process ever seen would produce. The transient cost is reported in peak RSS instead, and procs_seen is named beside procs so a CLI that churns helpers is visible rather than hidden behind a single count. Co-Authored-By: Claude Opus 5 <[email protected]> * docs: the 2026-09-20 footprint tables, their drivers, and the hero figure Co-Authored-By: codeaf <[email protected]> * docs: classify benchmarks under docs/benchmarks/performance Co-Authored-By: codeaf <[email protected]> * docs: sharper performance benchmark gist Co-Authored-By: codeaf <[email protected]> * docs: neutral review voice, explicit multiples in the gist Co-Authored-By: codeaf <[email protected]> * docs: gist as bullets Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> Co-authored-by: Claude Opus 5 <[email protected]> * docs: resume cost joins the footprint benchmark (#1317) * docs: resume cost joins the footprint benchmark Co-Authored-By: codeaf <[email protected]> * docs: change note for the resume table Co-Authored-By: codeaf <[email protected]> * docs: the quick table joins the gist Co-Authored-By: codeaf <[email protected]> * docs: table outside the list, blank lines around it Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * feat(session): a run nothing is driving can be carried on, in the copy its work is already in An interrupted run's store holds every step it took and, since the copy record landed, its row knows where that work is. This is the one door that starts work on it again: it adopts the recorded working copy and the store already on disk, hands both to the same engine under the same seats, and lets the supervisor's ordinary next pass release the claims whose owner is gone. No resume machinery, no second scheduler. IT HAS NO CALLER, ON PURPOSE. Continuing seats workers and spends money, and a door that spends money reachable before anything means to reach it is not a smaller version of the feature but a worse thing than no door. The card that offers the choice is the next change; until it lands a person cannot reach this from any surface. The refusals name what is in the way: a run that never stopped, a number with no run behind it, a run that already said its last word, and a copy that was never written down or is gone. The refusal for a run with no copy is the SAME sentence the row shows where the offer would otherwise be, taken from one constant, so the reading and the answer cannot drift into two spellings of one fact. The reading gains that sentence as a sentence rather than as a flag, and the run spec is now built in one place so the start road and this door cannot diverge the day a cap moves. Every refusal is proved twice: that it said no in the person's words, and that nothing started. The test that matters puts a worker's file in a run's copy, carries the run on, and reads the file back: the road this door could have taken instead clears that directory (#1298 shows it doing so). Fourth cell of the settled close-window design (#1224). Co-Authored-By: Claude Opus 5 <[email protected]> * chore(scripts): three proof steps that could not report failure, in one tested form Three shapes of proof step were each written more than once and each was incapable of reporting the failure it was checking for. gofmt -l prints the files it would reformat and exits 0 whether it printed any or none, so a step reading its exit code reports green on every tree that has ever existed. A pipeline ending in a reader hands back the reader's status, so `cmd 2>&1 | tail -25; echo "exit=$?"` printed exit=0 underneath a screen of FAIL, which does not merely hide a red but manufactures a green directly below the evidence. And a -run pattern is a second copy of the list of tests in a file: when the two drift, go test answers a filter matching nothing with ok, because from its side nothing failed, so a named test that never ran reads exactly like one that passed. scripts/proof.sh is the one written form of all three. THE ACCEPTANCE IS THE FAILING ARM: internal/ci/prooflib_test.go runs each function twice, once where the thing it checks is sound and once where it is broken, and each broken arm also pins what the plain command says about the same situation, because in all three cases the obvious command reports success. A guard against invisible failure that has only ever been seen passing is the thing it was written to prevent, wearing its own name. No caller is converted here, on purpose. If the library is right the conversions are mechanical; if it is wrong, that is better learned on one caller than on all of them. Co-Authored-By: Claude Opus 5 <[email protected]> * tui3: a standing row stopped for permission says the line, not asks: it (#1314) * session: a sign-in and a harness ask say what they are waiting for (#1315) * tui3: the front tab and the tab beside it agree about one conversation (#1316) * task: Fix the terminal cursor park for the settings search box Co-Authored-By: codeaf <[email protected]> * One fuzzy matcher for every picker, subtle match highlighting, and the caret parks where you type (#1321) * task: Implement fzf v2 fuzzy matcher universally Co-Authored-By: codeaf <[email protected]> * fuzzy: land the remaining surface cutover (autonomy, connections, memory, folder search, rewind, registry) and manual pages Co-Authored-By: codeaf <[email protected]> * fuzzy: state the gap price the code and tests charge fzf's own algo.go charges scoreGapStart (3) for the first skipped byte and scoreGapExtension (1) for each byte after it in the same gap; the const-block comment claimed the first byte paid extension too, which the alignment, the gap-price test, and fzf's source all contradict. Co-Authored-By: codeaf <[email protected]> * task: Fuzzy match highlighting across the search surfaces Co-Authored-By: codeaf <[email protected]> * task: Fix the terminal cursor park for the settings search box Co-Authored-By: codeaf <[email protected]> * changelog: the 1321 change entries, renumbered from the task placeholders, with the caret entry Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * session: a landing already accepted stops saying your call (#1322) * The commit co-author links to the CodeAF account, and assisted-by names the model (#1325) * exec, session: the commit co-author links to the CodeAF account, and assisted-by names the model The co-author address gains the account's numeric id, 267109073+, the form GitHub renders as a link with the account's avatar, and a new Assisted-by line above it names the model that wrote the commit, so git interpret-trailers can answer who typed it beyond the account. The Assisted-by line is filled at the belt render, the one surface that knows the session's configured model; the leaf loop's standing contract carries the co-author alone, as before, because exec is handed facts about the model and never its name. The manual pages show both lines, and the fixed arm's prefix waiver rises by the 162 bytes the wider belt fact costs. Assisted-by: CodeAF (z-ai/glm-5.3) Co-Authored-By: CodeAF <[email protected]> * changelog: the co-author links to the CodeAF account, and assisted-by names the model --------- Co-authored-by: CodeAF <[email protected]> * scripts: a heavy suite takes both locks so a stale tree can see it (#1324) * docs: the trailer entry names its pull request and takes the required filename * task: Skills: per-node attachment in briefs Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (deepseek/deepseek-v4-flash) * task: Verify standing-tasks findings Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (deepseek/deepseek-v4-flash) * task: Finish skills store record: continue the cut-off branch Co-Authored-By: codeaf <[email protected]> * docs: changelog entry for skills tranche 1 Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (moonshotai/kimi-k3) * task: Finish skills shelf and use_skill: run the right suite, land the branch Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (z-ai/glm-5.3-flash) * session: fix ActivateSkill test callers after the skills merge Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (moonshotai/kimi-k3) * exec, manual: the drafted-with footer links to the codeaf repository (#1329) agentfield.ai/github redirects to Agent-Field/agentfield, so every pull request, issue and comment codeaf drafted pointed a reader at the wrong repository. agentfield.ai/github/codeaf redirects to Agent-Field/CodeAF and is already serving, so the three attribution constants, their fixtures and the two manual pages take the longer path. The utm parameters and the rest of each line are unchanged. * docs: the change entry for the drafted-with link (#1329) The squash of #1329 carried the code and left its entry behind. * skills: compose per-node skill attachments at brief time and render them NodeBrief.Skills was plumbed but nothing in live flow populated it. The brief pass now composes each leaf's attachment from the shelf the caller hands the build frozen: skills the goal names outright first, then deterministic cue/scope retrieval behind them (function words never count as cues, a single shared word is coincidence, three candidates at most). The same order is written onto the node, journaled on the node brief and rendered into the worker's instruction beside the working method; a leaf with nothing attached renders byte for byte what it rendered before. RenderSkillsBlock and SkillEntry move from orchestrate to plan, beside ComposeSkills: the executor cannot import orchestrate (it imports the subharness, which imports the executor), and a render the worker brief cannot call is a render that never runs. Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (z-ai/glm-5.3-flash) * cmd: wire the active shelf into the chat engine's plan builds Both brief-journaling builds (the main plan and the remainder replan) now read the active shelf once and hand it to plan.Build frozen, and each dispatched leaf carries its plan node's attachment onto the task that runs it. A store with no shelf composes nothing and changes no prompt. Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (z-ai/glm-5.3-flash) * task: Skills tranche 1: integrate, wire live flow, prepare draft PR Co-Authored-By: CodeAF <[email protected]> Assisted-by: CodeAF (z-ai/glm-5.3-flash) * docs: drop pr-draft.md now that the PR body carries it Co-Authored-By: codeaf <[email protected]> Assisted-by: CodeAF (moonshotai/kimi-k3) * session: a held landing offers its answer and stops demanding one (#1331) A landing question is raised for unverified and retired for every other state, so a failed node has no question object and never had one. The reading was inventing one out of the ending's shape alone, and a row nobody was waiting on stood in the needs-you group for the rest of the session. Attention goes; the tier, the word, the reason and the take-it-anyway answers stay. * task: Fix round 1: address skills review findings Co-Authored-By: CodeAF <[email protected]> * audit-notes: move the standing-visibility investigation out of the PR diff Co-Authored-By: codeaf <[email protected]> * session: align the catalog header test with the reviewed rewording Co-Authored-By: codeaf <[email protected]> * task: Fix round 3: final fixes for skills tranche 1 Co-Authored-By: CodeAF <[email protected]> * task: Make imported SKILL.md skills readable through codeaf's skill doors Co-Authored-By: CodeAF <[email protected]> * skills: import foreign SKILL.md folders in place internal/skills discovers skills installed for other harnesses (.claude/.codex/.cursor/.gemini/.agents/.codeaf skills roots, project then user) without copying them; the resident sync registers each original directory as an imported-provisional skill fact, idempotently, and supersedes on digest change or removal. Root .gitignore re-includes the testdata fixture folders that must carry the real harness folder names. Assisted-by: CodeAF (z-ai/glm-5.3) Co-Authored-By: CodeAF <[email protected]> * import foreign skills before the first message on every v3 launch The v3 chat door claims no residency, so the import pass the resident reconciler runs on its own clock never reached the surface people actually open: a live session saw an empty shelf and a worker went around it, reading SKILL.md files it happened to find. The pass is now one store-bound function, ReconcileImportedSkills, that the reconciler calls behind its gate as before and every v3 launch calls directly, between v3Memory's graph and the first prompt — idempotent, so an open costs one scan and journals nothing on an unchanged disk. Proven live: fresh graph, nine real skills installed by npx into an isolated home, deepseek-v4.1-flash one-shot — facts committed at launch, use_skill list names them, get resolves the SKILL.md path. Co-Authored-By: CodeAF <[email protected]> * skills: preserve user scope without a project root Assisted-by: CodeAF (deepseek/deepseek-v4.1-flash) Co-Authored-By: CodeAF <[email protected]> * the world this task started from: Resolve skills tranche merge into santos/dev * merge skills tranche and existing-skill import Assisted-by: CodeAF (deepseek/deepseek-v4.1-flash) Co-Authored-By: CodeAF <[email protected]> * session, cmd, bench: the worker harness is the belt a task runs on (#1335) (#1335) CODEAF_TASK_BELT kept its name, its one reader and the sense of every caller. What moved is the answer it gives when nobody has spoken: bashBeltAsked was a match against "bash" and is now a lookup in a closed list of the words that mean the older road — node, legacy, off. Both engines are still in one binary. The list is closed on purpose. An unrecognised value leaves a person on the harness rather than moving them off it, because a typo in an environment variable must not be able to change which engine does the work and the failure it would cause is silent. An exported but blank value reads as unset, which is the default, which is bash. The two refusals that named the way in now name the way out. The manual pages that told a person the harness was not the default tell them it is, and name the three words that turn it off. The bench is the one place this could have been wrong without saying so: arm A ran with the variable unset, so after the flip both arms would have ridden the bash belt and the driver would have reported a comparison it never made. Both arms now name a word, and the branch that handled an absent one is gone. A hundred and forty-one tests in internal/session and cmd/codeaf were written against the node engine and said so by saying nothing. They did not become wrong when the default moved, they became silent about which road they meant. Each package's TestMain names that road once, rather than each test naming it, and a test that means the harness still sets "bash" for itself and wins. That pin has a cost, so the truth table is asserted directly in a new file: with the variable unset the belt is bash, the three words turn it off, case and surrounding space do not matter, and an unknown word moves nobody. A second test asserts the pin itself, because without it every other test in the package would quietly change what it tests. Co-authored-by: Claude Opus 5 <[email protected]> * docs: the bash-belt entry names only surfaces the checker knows `cli` is not one of the six words docs/rules/changelog.md lists, so the change check refuses the entry. The two that are left, `engine` and `chat`, are the surfaces the change actually moved. * manual: the use_skill page says what happened instead of naming it The chat corpus may not spell the machinery's own words, and the manual law fails on `verified`. The page now says the shelf holds skills this project saved after watching each one run, which is the same fact in the person's vocabulary. * docs: the bash-belt entry names only surfaces the checker knows `cli` is not one of the six words docs/rules/changelog.md lists, so the change check refuses the entry. The two that are left, `engine` and `chat`, are the surfaces the change actually moved. * session: a person can put a skill in front of this conversation by hand Retrieval guesses which skills suit the work and is allowed to be wrong. An attachment is a person saying which one to use, so it is held apart, by name, and the doors that will render it put it ahead of anything a score found. Names and not facts: the shelf is read where the skills are rendered, so a name attached before its skill is installed starts being carried the moment it exists, and one deleted from the shelf stops without anybody tidying the list. * The node belt is the default again, on a measured comparison (#1336) (#1340) * Revert "session, cmd, bench: the worker harness is the belt a task runs on (#1335) (#1335)" This reverts commit 619860cc65c6cb442dfdbf244fc7167fbf6e2ce3. * bench, session: keep the belt-naming fix and pin the default (#1340) Two things the revert of #1335 would otherwise have taken with it. bench/bashloop names a belt on BOTH arms. Arm A ran with CODEAF_TASK_BELT unset, which was correct only while unset meant the node belt; the moment a default moves, an arm that relies on absence becomes a copy of the arm it is being compared against, and the driver reports a difference of zero as a measurement. That is true whichever belt is the default, which is why it does not travel…
santoshkumarradha
pushed a commit
that referenced
this pull request
Oct 24, 2026
…warm one package down Since #1179 the chatv3/models and engine/models warm waiters are seated on the profile errand tracker and joined by stopPoolErrands in v3Process.closeAll, so the guard.Go inventory law now records them as joined rather than known-open. The real remaining survivor is one package down: the internal/catalog catalog/warm goroutine that LoadLazy starts, which runs on context.Background(), is cancelled by nothing at close, and writes cachePath under home.Dir() at warm time. Each model-warm row names that open writer and the cell that owns closing it, and the only known-open row left in this file is chatv3/sweep-home.
santoshkumarradha
pushed a commit
that referenced
this pull request
Oct 24, 2026
… joins The change note takes the PR number (1264 named a PR that has already landed). With the catalog warm owned, cancelled and joined through Models.Close, the chatv3/models and engine/models rows in the guard.Go inventory law no longer carry an open writer one package down: each names the waiter joined since #1179 and the internal/catalog warm that v3Process.closeAll now cancels and joins. chatv3/sweep-home is the one known-open row left.
santoshkumarradha
added a commit
that referenced
this pull request
Oct 25, 2026
* tui3, config: the gate row lands on the conversation you are in, and /status names the gate (#1063) The three Safety rows — `ask before running`, the tool exceptions and the shell command rules — wrote the profile and left the running gate alone, while the YOLO badge read that profile live. So the badge and the gate disagreed in both directions: `allow` lit the badge over a gate that kept asking, and `prompt` put it out over a gate that was still wide open, which is the false-safety claim #322 and #325 exist to prevent. `applySetting` now pushes those three rows through `ApplyApprovals` — the seam /permissions already drops a banked rule through — and refreshes the badge on the same keystroke, on the branch where the push landed. A rebuild that could not be done is said out loud on the panel's foot line in /permissions' own words, `saved · from the next session`, and the badge stays with the gate in force rather than following the file. And /status carries the posture it never carried: `approvals prompt`, `allow` or `deny`, in the `ask before running` row's own words, in the note, in --json and on the phone's status sheet, from the one posture reading the badge uses. The status row keeps its negative-space badge and the segment is no longer written into the page twice. The note's label column is measured from the widest label it carries, so three crew assertions that spelled `"\ncrew"` plus exactly five spaces were assertions about every other row on the page. They read the crew line by its label now. Co-authored-by: Claude Fable 5.1 <[email protected]> * attribution: the footer names CodeAF, and still links to agentfield.ai (#1075) * attribution: the footer names CodeAF, and still links to agentfield.ai The pull request, issue and comment footers read "Drafted with agentfield ai". They now read "Drafted with CodeAF"; the link, its utm parameters and the "reviewed and owned by the author" tail are unchanged. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy * docs: the change entry for the CodeAF footer Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy --------- Co-authored-by: Claude Opus 5 <[email protected]> * session, prompt, tui3: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1) (#1067) * session, prompt: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1) The end-of-turn reader saw 600 bytes of the last message, so a long table read as cut off; the carry-on told the model to explain itself, and the explanation became the only answer left in view under the fold. The reader now sees up to 8 KB of the last words, and a cut it still has to make ends with `[clipped by codeaf: N of M bytes]`, which its ask says is never evidence of a cut-off answer. The carry-on lead says who wrote the note and offers one exact reply, `[no change]`: it ends carrying on for the ask without another reading, the transcript keeps it for the model, and neither replay nor the live feed draws it — so the settled answer before it is what the fold finds. system.md trades `# Tone` and `# Delivery` for `# The answer`, `# Words or work`, `# When corrected` and `# Messages from codeaf`, and the two lines that pushed action on questions are qualified. The lean page keeps all four; the prefix waivers rise by the 1,705 bytes that cost, dated and named. Co-Authored-By: Claude Opus 5 <[email protected]> * docs: the change entry takes the pull request's own number Co-Authored-By: Claude Opus 5 <[email protected]> * session: a quiet landing is asked about the notes it was handed, not the page that names them The worker's system page now names [silent] and [stuck] under `# Messages from codeaf`, so a search of the whole request found the tag on the page. childWasHanded reads the conversation and leaves the page out. Co-Authored-By: Claude Opus 5 <[email protected]> * session, prompt: [no change] answers the note it follows, and a long ask leaves the last words cut by name (#1065 round 1) Self-review of #1067 found the token honoured on the wrong replies. The gate read the turn's carry-on count, so a model that made calls in answer to the note and then said [no change] ended the turn unread on an answer those calls may have made stale, and a token after a load or ask nudge counted too. It now reads the transcript: the token must be the very next reply to the reader's own lead (answeredNoteUnchanged), and the dead declined flag is gone. An ask up to the whole digest rides in its head, so eight kilobytes of last words behind a long ask reached the backstop clip and ended in a bare `…`. checkpointClipSaid takes the room that is left. A turn that ends on the token no longer starts the route judge or hands the token to the memory reflex, and a later reading's last words skip it. `# Messages from codeaf` named `[still asked]`, which only the reader ever receives, and left out `[stuck]` and `[held]`, which the conversation does; it now names the tags that arrive and says any other opening, such as `[image #1]`, is the person's. The waivers sit on the measured 1,779 bytes, and the manual and the change entry say what the gate actually honours. Co-Authored-By: Claude Opus 5 <[email protected]> * prompt: the new sections keep the rules the old two carried, and say what stays in view (#1065 round 1) Owner review of #1067. Replacing `# Tone` and `# Delivery` had dropped five rules; each is back inside the new sections: match the person's task and vocabulary, choose the safe option under uncertainty, evidence and blocking details complete, never a compiling scaffold or a narrowed test, never half-solved work. `# The answer` said only the last message reaches the person, which `# Planning` contradicts: the plan and the per-batch sentence are shown while a turn runs. It now says that once a turn ends only its last message stays in view. `# Words or work` sat beside `## Work or words`, which spends the same two words on hand-off sizing; it is `# Answer or change`, and it says an answer asked for in chat is written in the reply, not a file, unless the person asks for one. The waivers sit on the new measurement, 2,035 bytes over dev on both arms. Co-Authored-By: Claude Opus 5 <[email protected]> * prompt: the messages-from-codeaf list names every note codeaf writes The system prompt now distinguishes conversation user-role notes from private reader and tool-result markers. A structural test follows the production constants, and the byte waivers record the exact fixed and lean measurements. Co-Authored-By: Claude Fable 5.1 <[email protected]> * session: no change skips judgment only when it answers the reader The completion loop now uses the transcript context before suppressing the route judge or substituting the prior answer for memory. Tests cover the withdrawn and ordinary-token memory paths, and the manual separates counting from display. Co-Authored-By: Claude Fable 5.1 <[email protected]> * test: the model stub replays the no-change fold The OpenAI-compatible stub can now drive the real binary through eleven read rounds, a long table, a rejecting reader, and the exact token reply. The recipe records the tmux setup and the expected log and screen evidence. Co-Authored-By: Claude Fable 5.1 <[email protected]> * prompt: the standing-news tag stays with the event, not on the page The messages-from-codeaf list had named [something you set up fired], and TestTheSteeringLineReadsAsNewsAndNotAsARequest refused it: that line says under the news itself what it is and what not to do about it, so the page's copy is one law bought on every request of every turn for a turn most sessions never have. The list keeps the compaction tags, the structural test now files the standing frame with the private voices the page must not name, and the waivers fall to the new measurement (fixed 55,280, lean 47,055). Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Opus 5 <[email protected]> Co-authored-by: Abir Abbas <[email protected]> * Clean internal experiment records and revive test/ux for codeaf (#1068) * chore: clean internal experiment records and revive test/ux for codeaf Public-repository cleanup ahead of opening the docs to outside readers. - Remove four internal DOE/experiment record directories that reference private issue and pull-request numbers and are not current-product documentation: docs/design/pin-doe, plan-gate-doe, turn-wall-share-doe, and workspace-foundation. - Neutralize internal machine references in benchmark and design docs: the bench host ("spark"), its secrets.env path, and a personal working branch name, all now generic placeholders. - Revive test/ux/, the user-facing journey suite, for the renamed product: collapse the removed v1/v2 dual-surface logic into the single v3 surface, repoint the binary to cmd/codeaf and state to ~/.codeaf/CODEAF_*, and delete gate.sh whose premise was the v1-vs-v2 parity table. Co-Authored-By: codeaf <[email protected]> * chore: drop internal conversation-runtime wave records, keep the measurement contract The conversation-runtime directory was a local development wave: day-by-day status, /private/tmp paths, "owner authorized" process notes, draft PR numbers, and local change logs. None of it is current-product documentation a public reader needs. Keep PARETO.md, which is the benchmark's cited measurement contract, trimmed of its dangling calibration link and owner language. Co-Authored-By: codeaf <[email protected]> * chore: neutralize the remaining bench-host and work-machine references A wider sweep found the internal bench host ("Spark"), its secrets path (fleet/secrets.env), and a scratch ops directory still named in live docs, bench READMEs and bench scripts that the earlier redaction had not touched. Rename the host to "benchhost"/"the bench host", drop the secrets path, and replace "the laptop" as the owner's own machine with a neutral "work machine" where it meant the internal one. The manual's first-person "the laptop you are sitting at" is public-facing and left alone. Co-Authored-By: codeaf <[email protected]> * chore: fix review findings in the public-cleanup diff - PERF.md: drop a private tracker issue reference (#846). - PARETO.md: restore a complete sentence dropped when the calibration link was removed, and align tense with the remaining present-tense claim. - home-mission-control/DESIGN.md: drop the internal "fleet" system name and an owner-framing phrase, consistent with the secrets-path scrub elsewhere. Co-Authored-By: codeaf <[email protected]> * docs/design: the two records live code still cites stay in the tree plan-gate-doe/REPORT.md is the measured evidence behind the plan-gate default that twelve comments in cmd/codeaf, internal/config, internal/session and internal/splitgate point at, and turn-wall-share-doe is the run internal/session/turnwall.go says it was added from. CLAUDE.md keeps the record on purpose for exactly this reason, and neither folder names a machine, a secret or a private address. Co-Authored-By: Claude Fable 5.1 <[email protected]> * test/ux: the revived journey suite goes back out #991 removed it because it drove the v1 and v2 surfaces, and the revival did not change that: its journal verb reads graph.db tables (messages, nodes, usage, commands) that the v3 chat never writes, lib.sh still waits for the resident, nothing runs it from the Makefile or a workflow, and its only evidence was bash -n. docs/JOURNEY.md says internal/e2e is the one suite that drives the binary, and that stays true. A suite that has been run green against bin/codeaf can come back on its own pull request with a target that runs it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: entry for #1068 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: the #1068 entry says why the journey suite is out, correctly The first draft said the v3 chat never writes the graph.db tables the suite reads. Running the suite showed both message rows landing, so that sentence was wrong; what is true is that nothing runs the suite, it had never been run, and its first journey fails on its first screen check (#1072). Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: codeaf <[email protected]> Co-authored-by: Abir Abbas <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]> * session: task commits are authored by the bot account, not codeaf@localhost The author codeaf stamped on the commits it lands was `codeaf <codeaf@localhost>`, which no GitHub account can link, so every branch codeaf landed failed the CLA check. It now writes `codeaf <[email protected]>` - the identity the Co-Authored-By trailer already used - and keeps `codeaf@localhost` and `aforge@localhost` recognised as legacy by taskCommitIdentity. GLM 5.3's review caught an unmarked retired name in permissions.md that reddened the namelaw gate, and seven stale test fixtures; both fixed. Laws gate, session, exec and cmd are green. Co-Authored-By: codeaf <[email protected]> * README: show the pinned-version install line the release test requires (#1103) * README: show the pinned-version install line the release test requires The Install rewrite dropped the VERSION=<tag> pin example, and TestPublishedPinExamplesGiveVersionToBash reads every published install surface for `| VERSION=` because `VERSION=<tag> curl ...` is an easy mistake to copy — it sets the variable for curl instead of the install script. The pin goes back inline on the same host the section already uses, with the releases page still linked for tags. Co-Authored-By: codeaf <[email protected]> * docs: the change entry for the README pin example Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * telemetry: anonymous usage counts, off with CODEAF_TELEMETRY=off (#1095) * telemetry: the package behind anonymous usage counting, per the wire contract Four typed events (first_run, session_started, session_ended, fault) with the contract's allowlisted, bucketed props; the opt-out ladder with its off reasons; a hashed install identity at 0700/0600; a fault fingerprint over codeaf function names only; a JSON-lines spool with a deadline-bounded flush of up to 50 events per POST, 7-day age drops and a 1000-line cap; the notice constant with NoticeShown/MarkNoticeShown gating the flush; and Show() for a future codeaf telemetry show. A library only: nothing in cmd/codeaf, internal/session or internal/config reads it yet. Co-Authored-By: codeaf <[email protected]> * telemetry: tests, doc, and contract fixes for the usage-count package Adds the package's tests — bucketing edges, the opt-out ladder, install identity, the notice, fingerprinting, the spool against an httptest relay (cap, age drop, 50-per-POST splitting, a hard deadline against a hanging server, silence on failure) and the privacy law that no sentinel string reaches the wire. docs/TELEMETRY.md documents the notice, what is sent and how to turn it off, with a test that fails if the doc's property table drifts from the Go allowlist. Fixes four drifts from the wire contract found running those tests: the <1m duration bucket was missing, the fingerprint's argument stripping cut too much, the rc channel suffix must be a single integer, and under go test a config-off rung outranks the test rung in the reason reported. Co-Authored-By: codeaf <[email protected]> * telemetry: fix review defects 1-8 with tests Fingerprint stability (last-( cut, .go:/main./created-by frame rules), counted-batch flush, guarded and multi-process-safe spool, BucketDuration edges, Configure-gated write paths, O_EXCL install id, install.json preservation, buildinfo.Dirty accessor. Co-Authored-By: codeaf <[email protected]> * installer: record the install locally and print the telemetry notice Following the telemetry wire contract: a successful install now writes telemetry/install.json under the state root (dir 0700, file 0600) with the install method, channel and UTC timestamp, leaves any existing install_id alone, and makes no request the download steps did not already make. At the very end the installer prints the contract's notice to stderr after one blank line, or one line saying telemetry is off when CODEAF_TELEMETRY/DO_NOT_TRACK opts out. README gains a plain Telemetry section pointing at docs/TELEMETRY.md, and a new test/installer-telemetry.sh proves the marker, the notice and the opt-out without a network. Co-Authored-By: codeaf <[email protected]> * installer: four review corrections to the telemetry commit The review of 71ff95a6b found four things. Revert the one line that changed `if [[ -z "${TAG:-}" ]]; then` back to `${TAG:-}`: under set -u an unset TAG must reach api_problem, not crash. In write_install_marker the channel lands in JSON verbatim and CHANNEL can come from the environment, so only stable, rc, staging or dev is recorded and anything else becomes "unknown" — with test cases for a valid channel and for we"ird, both asserting the marker parses (python3 -m json.tool, or jq, else a strict channel check). Fence the README Telemetry notice in a ```text block so Markdown keeps its alignment, wording byte for byte. Clear the two shellcheck findings in test/installer-telemetry.sh with one block disable for SC2016/SC2034 — ok() evals those strings, so the quoting is deliberate — and export CODEAF_TELEMETRY/DO_NOT_TRACK where a subshell needs them, without weakening any assertion. Co-Authored-By: codeaf <[email protected]> * release-downloads: daily PostHog report of public GitHub release download counts A scheduled workflow reads the repository's releases from the public GitHub API and sends one codeaf:release_downloads event per binary asset (codeaf-<os>-<arch>[.exe]) — sidecars like checksums.txt are not reported. Each event carries release_tag, channel (stable or the prerelease suffix), prerelease, os, arch, the cumulative download_count and the UTC snapshot_date; the event id is derived from tag, asset and date, so a same-day rerun deduplicates instead of counting twice. No code in the binary; counts come only from public release data. Co-Authored-By: codeaf <[email protected]> * release-downloads: tests cover the workflow, docs and pagination The dry-run unittests now also assert the identifiers the wire spec spells out where the script and the workflow spell them: the schedule, dispatch, contents-read permissions, built-in token, project-key secret and host variable in .github/workflows/release-downloads.yml, the public-data paragraph in docs/rules/promotion.md, stdlib-only imports, pagination across API pages, the host override and the /batch/ endpoint, sidecar skipping, draft skipping, and the channel words stable, rc, dev and staging. Co-Authored-By: codeaf <[email protected]> * release-downloads: four review corrections to the script Channel detection now follows the tag grammar in cmd/codeaf-release/version.go: stable, rc, dev and staging tags match their four patterns and anything else reports "unknown", so dev-20260917-09299019bcdc is no longer mislabelled "stable". --dry-run works without CODEAF_POSTHOG_PROJECT_KEY, the local and test path: the batch prints with api_key "<unset>" and exits 0, and only the sending path stops with the notice. Every event carries "$geoip_disable": true next to "$process_person_profile": false, since the sender is a CI runner whose location is meaningless. The module docstring now names release-downloads.yml, the file that exists. The fixture holds one tag of each shape plus an unrecognised one, and the tests assert the channel for each, including the two real dev and staging tags. Co-Authored-By: codeaf <[email protected]> * release-downloads: test runs on Python 3.9 too ScriptSourceTest read sys.stdlib_module_names unguarded; the attribute arrived in Python 3.10, so the suite errored on macOS stock python3 (3.9.6). Guarded with getattr so the pinned import-set check stands on its own where the attribute is absent; 32 tests pass on 3.9, 3.10 and 3.14. Co-Authored-By: codeaf <[email protected]> * installer telemetry: the notice is read from docs/TELEMETRY.md The test read .telemetry-contract.md, a working note that is not in the repository and never will be, so it aborted with "telemetry contract is missing" in a clean checkout. It now extracts the fenced block under "## The notice" in docs/TELEMETRY.md — the same bytes as the Notice constant in internal/telemetry/notice.go — and holds the installer's TELEMETRY_NOTICE to it as before; a new assertion holds the README's telemetry fence to it too, byte for byte, so the invariant the suite existed to guard now has its third leg. Comments in scripts/install.sh, internal/telemetry/telemetry.go and this test now name docs/TELEMETRY.md as the source of the notice. No Go code changed and no Go tests were run. shellcheck is clean; the suite's 33 passing assertions include the two new notice checks, and the same two stat(1)-related assertions that already failed on this GNU machine before the change still fail with identical output. Co-Authored-By: codeaf <[email protected]> * release-downloads: the download-counts paragraph lives in docs/TELEMETRY.md docs/rules/promotion.md was the only home the paragraph had, and it came with a fallback pinning the branch to the state before the telemetry doc existed. The paragraph moves, byte for byte, into docs/TELEMETRY.md as a final "## Download counts" section beside the rest of the telemetry story, and promotion.md returns to its origin/dev content, so this branch now touches no release docs. test_no_telemetry_doc_on_this_branch asserted docs/TELEMETRY.md did not exist and is deleted. The two tests that read the paragraph read it where it now lives: both require a "## Download counts" section in docs/TELEMETRY.md naming .github/workflows/release-downloads.yml, scripts/release_downloads.py and the checksums.txt sidecar, and the promotion.md fallback is gone. No Go code changed and no Go tests were run; the property table in docs/TELEMETRY.md is untouched. The suite passes: 31 tests, OK. Co-Authored-By: codeaf <[email protected]> * installer telemetry: the mode check asks GNU stat first On Linux `stat -f` succeeds and reports the filesystem, so the BSD-first order never reached the GNU form and both permission checks failed. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01CqfhxLrrBNQxwPNvEgoBGR * telemetry: refuse to send to the production relay from a test binary Co-Authored-By: codeaf <[email protected]> * telemetry: testHome points the endpoint at a dead loopback Co-Authored-By: codeaf <[email protected]> * telemetry: the orphan-adoption test names the dead relay it now gets Co-Authored-By: codeaf <[email protected]> * telemetry: a test pins that no test binary reaches the production relay Co-Authored-By: codeaf <[email protected]> * telemetry: three tests pin the test-binary relay refusal Each asserts through real calls, not a re-implemented effect: the newRelay override lands after testHome and a flush reaches that recorder; a default-endpoint flush is refused like a dead relay (zero round trips, event still spooled); forcing the ladder on cannot reopen the path. Co-Authored-By: codeaf <[email protected]> * telemetry: allocation-free session counters Snapshot returns the SessionStats fields the SessionEnded constructor takes, so the wiring hands the struct straight over. Cost is stored as integer micro-dollars and divided back once, in Snapshot. Count* never ask whether telemetry is enabled and never allocate. Co-Authored-By: codeaf <[email protected]> * telemetry: gofmt the counters test One alignment fix in the many-goroutine test; no behaviour change. Co-Authored-By: codeaf <[email protected]> * config: telemetry setting row (CODEAF_TELEMETRY) Key "telemetry", on|off, default on, env pin CODEAF_TELEMETRY, in the interface group beside the history row. TelemetryAtIn walks the ladder — pin, project file, profile config, default — and TelemetryOffReason answers whether config turned the pipe off and names which rung did. The row sits in ProjectKeys so a repository may answer for itself. Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the telemetry command, and the setting it writes Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: session lifecycle events at execute (work in progress) Configure, notice, first_run and session_started at the start of every session command, session_ended and a one-second flush at its exit. The end event does not yet carry the session counters. * provider: count answered model calls and their spend at the log door * session: count sealed turns and executed tool calls * cmd/codeaf: the session-ended event carries the run's counters telemetryEnd builds the end event from telemetry.Snapshot so turns, model calls, tool calls and cost reach the spool; the stop-reason switch becomes a map over the exit ladder; and both lifecycle constructors are gated on the ladder being on so a disabled run touches no state. Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the counters test says the tally is process-wide The exact turn, call and cost assertions hold for one run of the package; a repeated run counts into the same atomic tally again. Co-Authored-By: codeaf <[email protected]> * guard: a fault hook Recover calls, for a reporter above this package Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the fault event at both crash sites, off the current session Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: a test that the crash site spools a fault and not the panic text Co-Authored-By: codeaf <[email protected]> * docs: finish the anonymous usage counts contract and add the 1095 changelog entry * guard: the fault hook sits behind an atomic pointer A goroutine recovering a panic read the hook while a test on another goroutine wrote it, which the race detector reported. SetOnFault stores it once; Recover loads it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: a task's interrupt word comes from the signal, not the cancelled context stopSignals cancels the signal context at the end of every errand, so the flag read from its Err said interrupted for a run that finished cleanly. Co-Authored-By: Claude Fable 5.1 <[email protected]> * tui3, installer: the telemetry row has a tab, and a custom install writes no marker TestEverySettingRowHasATab wants every registry row on the settings panel. TestCustomInstallOutsideTheStateRootDoesNotRunAdoption wants an install outside the state root to leave the login's state folders alone, so the install marker is written only when the root exists or the install is in it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: the counters test zeroes the process tally first Other tests in the package drive model and tool calls through the doors that count, so the exact bands held only when the test ran alone. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: the version test drives the telemetry lifecycle, not execute execute starts writers under the state root that outlive the test, and the temp dir's cleanup raced them. The lifecycle alone is what the test pins. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: gofmt the lifecycle test Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: codeaf <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]> * session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler (#1104) * session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler TestASuccessfulHandoffJournalsTheRungThatSuppliedTheBrief failed once in a full make check at line 118: the worker's brief was the running model's draft, not the mastermind's written handoff. THE CAUSE IS NOT A STOLEN STEP. Reproduced under load (60 CPU burners, the fixture run 1500 times), every request the fixture makes is either the turn's own or one the aside answers: the queue carries only `round`, `draft` and `writer`, and the errands beside the turn (title, namer, the mark's drawing) never touch it. What moves is the ROUND COUNT — 41 to 54 across the runs — because the mark's drawing is a sidecar (sidecar.go) whose landing the scheduler places, and the turn is never held for it. On a busy machine the turn runs its rounds off faster than the drawing's goroutine is scheduled onto a core, the gap between the net firing and the drawing landing outruns the slack the script carries ([checkpointSlack] says so in its own words), the writer's ask lands PAST the end of the 53-step script, the scripted completer answers it with its past-the-end line `(unscripted)` — which is not prose — the handoff rung reads degenerate, and the carry ladder falls onto the draft. That is #392 exactly, one mechanism along. WHAT FIXES IT IS WAITING FOR THE READING. checkpointAgent now puts a watch on the turn's own context ([watchReadings]) and the handover fixtures submit through [watchedContext], so [answerWhenQuiet] holds each request that carries the belt until every reading beside it has landed — the order a real turn has, where a real model spends seconds on a step and the small readings land inside it. The gap is then one boundary whatever else the machine is doing, and a script long enough to cross the mark is long enough. THE REGRESSION TEST holds the drawing back by a fixed wall time — standing in for a scheduler that does not reach it in time — and insists on the behaviour: the turn does not spend its script racing the drawing, and the writer's rung still supplies the brief. It fails without the watch (the turn makes 54 rounds) and passes with it. Co-Authored-By: codeaf <[email protected]> * docs: the change entry for the carry-ladder drawing race Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * docs: the install proxy is live, and the manual says so (#1076) * docs: the install proxy is live, and the manual says so `https://agentfield.ai/get/codeaf` has served `scripts/install.sh` since 2026-09-16, the day the repository went public, so the compiled-in manual and docs/GUIDE.md stop saying it is "not serving yet" and stop calling the repository private. The proxied line goes first, as the README already has it; the raw GitHub line is described as the script under it. Both pages say to pipe into `bash`, not `sh`, because the script uses pipefail and `[[ ]]` and dash rejects them, and both state the channel path (`/dev`, `/staging`, `/rc`) beside the flags. The manual also says what the proxy refuses: a script without its channel line exactly once, or a body that is not a shell script, is a 502 rather than the wrong thing. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: entry for #1076 Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> * A launch says a newer codeaf is out, and /update installs it and restarts (#1060) * The release-tag grammar has one home, and it is internal/update Stable, rc, dev and staging tags are parsed, compared and bumped in exactly one place now. cmd/codeaf-release keeps its next, kind and prune behaviour and its tests unchanged; it reads the grammar from the new package rather than owning a second copy of it. A second parser is the thing that drifts: the updater about to land needs the same answer to "is this tag newer than that one" that the release tool gives, and one source of truth is the only way they cannot disagree. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * internal/update: look up a release, check it, and replace this binary with it The launch check answers only for a build that could sensibly be behind: a stable tag, or a release candidate whose stable line has since been published. A dev or staging build, a make build sha and an unstamped binary make no request at all, because a channel build is deliberately newer than stable and a source build belongs to whoever built it. The answer is cached for a day beside the profile, and every failure is silence. The install downloads into the target's own directory, matches the sha256 from checksums.txt, and only then renames over the running file, so a refused download, a wrong checksum or an unwritable directory all leave the original binary exactly where it was and no temp file behind. The API token never leaves the API host: the redirect policy strips it the moment a redirect changes host, which Go does not do on its own for another port or a subdomain. Windows has no atomic rename over a running image, so it goes through ReplaceFileW and puts the original back when Windows reports the one failure that moves it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * tui3: a launch says a newer codeaf is out, and /update installs it The launch line is one dim transcript note carrying both versions, /update, and the curl line, because the command is what the owner asked to be given. It arrives as a command rather than an opening read, so the first frame is drawn before the network is asked anything and a slow or refused answer draws nothing. /update — /upgrade is the same road — names the tag, then the checksum and where it landed, then quits with a restart plan for the door to carry out. Already newest says so and stays. It refuses while a turn or a task is running, and once it has started, one shared guard keeps a second /update and a new turn from beginning underneath it: the restart must never quit on top of live work, and the home page's Enter roads go through the same guard chat's does. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * codeaf update, and the door that restarts the same conversation codeaf update --check prints one line and exits 0 when this release is newest, 3 when a newer one exists, 1 when it could not ask. Without --check it installs stable, or whatever --rc, --dev, --staging or --version names, choosing a channel build by its own publish time the way the installer does. A binary built from source refuses in place and names its path rather than replacing something a developer is holding. The restart happens here and not inside Bubble Tea: the surface fills a slot, the door reads it once the frame is down and the terminal is back, and then execs the new binary with this launch's own arguments plus --session pointing at this transcript, so the conversation reopens where it was. A linked-local engine is asked to retire only after the surface has detached, and it still refuses while another window or any live work is there. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * remote: a window that says it is leaving needs no torn-link grace The twenty-second grace exists for a terminal that was killed without saying so, and a deliberate detach is the opposite of that — the window has just announced its departure. Carrying the grace anyway left a cleanly closed window looking attached for two more call deadlines, which kept a freshly installed codeaf waiting behind the engine the old one had already left. It clears only once the last view is gone. Turns, tasks, unanswered questions, other views and live work all still refuse retirement, and a refusal still never stops the work it protects. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * The installer and the updater agree on what a channel tag looks like Two roads now install codeaf, and they were reading the same release list by different rules: the shell accepted any dev-* or staging-* tag and semver parts with leading zeroes, while internal/update accepts only the shapes the release workflow actually publishes. A malformed tag newer than a real one would send the two roads to different builds. One helper carries the grammar to both channel selection and the line that names the channel, and an unrecognised tag for the asked-for channel now says so instead of installing whatever came back. The publish-time ordering #1056 added is untouched: only which candidates are eligible changed. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * The manual stops saying that nothing self-updates That sentence was true until this change and is now the opposite of true, so it goes, and a section written in the words people bring takes its place: is there a newer version, how do I update codeaf, why does it say this every time I start. It states who gets the launch line and who never does, the opt-out variable, the day the cached answer lasts, the shell exit codes, and both refusals — a build made from source and an executable nobody may write. The command table names /update and /upgrade with the argument they take. Four probes reach the new section, and the remote page keeps its own asker's words in its heading so the new section cannot take that question from it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * docs/changes: the update check and /update, for #1060 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: --check answers for the channel it was asked about, and a newer build is never downgraded Two things a hand verification caught. --check parsed whatever tag it had selected with the stable grammar alone, so --dev, --staging and --rc could never be "newer" and every one of them answered "you are on the newest codeaf" and exited 0 while a newer build sat in the list. The check now keeps its channel all the way through the decision and always names the tag it is talking about: stable and rc compare as semver, a dev or staging tag is the same tag or a different one, and --version is equal or it is not. And a build AHEAD of the newest stable was quietly installed backwards — the check said newest, the install downgraded it. Both roads refuse now, naming both versions and the deliberate way past: this codeaf is v0.3.0, ahead of the newest stable v0.2.0 — pass --version v0.2.0 to install it anyway An explicit tag IS that deliberate road, so --version and /update <tag> still install exactly what was named. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: a failure names the asset and the status, and ends with the curl line A download that failed printed the whole fallback URL, which put the retired product name on somebody's screen. The line names the asset it asked for and the status it got back, and nothing about which repository answered — the compatibility attempts still happen, they are just not the person's business. Every terminal failure now ends with the curl line, which D9 asked for and the download path had been missing. The / list declared no argument for /update while the command page documented one, so the list gains the second row the way /cache has one. The manual section now says what the code does about a build ahead of the newest stable, where it had said such a build "gets nothing" — true of the launch line, and false of the install until this wave. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: the curl line is agentfield.ai/get/codeaf, the supported road The launch line, the refusals and the terminal door offered the raw GitHub address of scripts/install.sh. The repository is public and agentfield.ai/get/codeaf has served that script from main since 2026-09-16, so the constant now spells the supported line, the manual's update section names it in place of "the curl line below", and the change entry says so. Asked for on the pull request by Santosh. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]> * a run's own scratch note no longer fails the delivery gate (#1096) * codeaf: a run's own note is offered outside the tree it is judged in leafOutputHint invited a job root to write its overflow document at a workspace-relative name (exec.SuggestPathFor's task-2-x1-toolchain-checks.md shape), so a run working in a person's directory left that note untracked at the root of their working tree. The delivery gate holds the run's record — the artifact registry, joined by jobArtifacts, plus the delivered tree — against the request, read the note as part of what was handed over, and refused the delivery; the run then ended incomplete over complete work. There is no separate mechanical clean-tree check to exempt, so the fix is the writer's: where the workspace belongs to the person, the root's file invitation points at the run's scratch beside its recorders and job logs, and the message keeps carrying the deliverable. * codeaf: a person's own file still joins the record the gate reads TestARootLeafInAPersonsDirectoryPutsItsNotesInScratch baselined the tree after the note was already written, so its landing read could never have observed anything: the record stayed empty because of the ordering, not because of the address. The baseline now happens first, in the executor's own order, and a hint that ever points into the tree again fails on the observation, not only on the spelling. And the other half of the law is pinned down: a file that turns up in the person's tree while the leaf runs — even at a name wearing the run's own task-<n>-<slug>.md shape — is still observed and still lands in the record the delivery gate is held to. Only the run's own note moved; nothing else about what the tree says was loosened. Co-Authored-By: codeaf <[email protected]> * docs: the change note for the root's overflow file moving out of the tree --------- Co-authored-by: codeaf <[email protected]> * chat: the wordmark is drawn CodeAF, four rows, capitals on the d's cap line (#1106) `┌─┐ / ├─┤ / └─┘` is not an `a`, it is two closed bowls of equal size stacked on each other, and two equal closed bowls are an `8`. That was the second-to-last glyph of the product's own name on the first screen of every fresh install. The owner's answer is to have the two letters at the end stand up out of the word as capitals: `A` is an apex, a crossbar and two legs; `F` is the one letter whose case is told by its arms alone, so its top arm is the longer one. A CAPITAL'S TOP IS THE ASCENDER'S TOP, and three rows of box-drawing could not give it that. A bar is drawn at the middle of its cell and a stem reaches the cell's edge, so the `d` stood half a row over any capital however it was formed — and a stem in the bottom row hung half a row under the bowls' baseline the same way. The fourth row is that half a row made whole: the x-height letters live in the lower three and leave the top row blank; the capitals and the `d`'s ascender start in the top row, the ascender as the half-stroke `╷` so its top is the capitals' bar; and every stem that ends on the baseline ends as `╵`, which stops where `└─┘` does. A `│` in the bottom row is a descender and nothing else. THE WORD IS SPELLED `codeaf` AND DRAWN `CodeAF`. `product` is unchanged, the glyph table is still keyed by its lowercase letters, and a terminal that cannot draw boxes still gets the word `codeaf`. The greeting's centring test read the unit's left edge off the wordmark's first row, which is now the cap line the x-height letters leave blank; it reads the second row. The one manual page that draws the wordmark had been drawing the retired `aforge` letterforms under the label "the codeaf wordmark" — the name law walks text and cannot see a name spelled in box-drawing — and draws the shipped forms now. Co-authored-by: Claude Fable 5.1 <[email protected]> * A real release download finishes: the 3-second check clock no longer covers the asset (#1110) * update: a release download gets its own clock, not the check's The release client was built as http.Client{Timeout: 3s} because three seconds is right for the launch check, and Go's Client.Timeout covers the WHOLE exchange including every body byte. The same client then downloaded the release asset, so a 57 MB download on a healthy link — about eight seconds here — was cut off three seconds in and a person read `read the release response: context deadline exceeded`. Client.Timeout is gone. The transport bounds only what has made no progress: a 5 s dial, a 5 s TLS handshake, a 10 s wait for response headers. Every call now takes its deadline from its context. The launch notice and `--check` share the one CheckTimeout constant, install metadata gets 10 s per request, and the asset gets a 30-second no-byte stall window plus a 15-minute ceiling — a dead link fails with a sentence, a slow one finishes. No Go deadline text reaches a person. A stall says `downloading codeaf-linux-amd64 stalled — no bytes for 30 s`, the ceiling says `took longer than 15 minutes`, and late headers say `did not start arriving within 10 s`. The stall window, the ceiling and the metadata window are injectable, so the tests prove the clocks with real slow, silent and trickling servers rather than waiting out the shipped ones. Co-Authored-By: Claude Fable 5.1 <[email protected]> * update: both doors read the one check constant `codeaf update` and the chat surface each spelled `3*time.Second` when they built the release client, which is how a figure meant for one small request came to govern an install. Both now pass update.CheckTimeout, and the three seconds live in exactly one place. The terminal's `--check` takes the client's Check, which is the whole-exchange check deadline; a plain `codeaf update` keeps Select, so the metadata requests an install makes get the ten seconds they need. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: the update download's own clock (#1110) Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> * docs/changes: v0.2.1 rolled up into CHANGELOG.md (#1113) The 33 entries that landed on dev since the v0.2.0 roll-up are the ## v0.2.1 section of CHANGELOG.md now; docs/changes/unreleased/ keeps only this pull request's own entry. Stable v0.2.1 takes its notes from that section. Co-Authored-By: Claude Fable 5.1 <[email protected]> * README: telemetry folds under Docs (#1115) The 23-line Telemetry section between Install and the product tour becomes one collapsed block at the end of Docs: a plain summary line, the notice verbatim, one sentence and the link to docs/TELEMETRY.md. The notice block is unchanged, so it still matches the binary, the installer and docs/TELEMETRY.md. Replaces #1112. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01NoeSVN8nqmwNVRXExgoUNN * santos/dev lands on dev: crew family, Pareto crewing, the Model Pool, custom connections (#1194) Lands the integration branch santos/dev (#1108, head 6db5125b2, 152 commits over dev) on dev as one squash, plus two fixes that kept its touched-packages job red: the pool start-up test empties the CI word it is not about (649bb4bfd), and a zombie leader no longer keeps a job's process group alive on Linux, which had delayed every finished background job by the two-second termination grace since #1155 (053e6a90f). #1108 carries the full account; the 94 change entries under docs/changes/unreleased/ are the per-change record. The commits folded in, oldest first: 5c413ef7b Keep archived evidence readable while tasks finish 3705a0cfe Document recovery evidence retrieval change 7834cc7d0 tui3, manual: a question's own answer box answers to the whole caret vocabulary 99d66e98c task: Add open/all source toggle to crew config c1e4cb57f docs: the change entry for the crew model family toggle e63b43927 crew: round-1 review fixes 304a5a223 crew: round-2 review fix 65477c0c5 task: Add open/all selector to /crew TUI picker 9189ef764 Merge branch 'task/add-open-all-selector-to-crew-tu-9e8cc0' into feat/crew-open-all-toggle ba9c12d61 crew: reconcile the picker onto config's source helpers ea05bc0f5 crew: a chosen family counts as an answered crew e1a037e0a Merge remote-tracking branch 'origin/dev' into feat/crew-open-all-toggle f6c2924a7 crew: the open table takes the picks we landed on eff44b542 docs: the change entry carries the open-table move and the family gate 71ce9bec0 crew: the family reaches the fallback, the settings skin, and the row's line b8ca5f513 crew: round-1 review fixes on the new table 8db5708ef crew: round-2 review fixes ce9759ef4 crew: round-3 review fixes 956e1f514 crew: round-4 review fixes 37ad27f4e crew: fixes from the first Opus round 6ffb95813 crew: the fixes from the Opus confirmation round 72b027436 crew: the comments the atomic write left behind 70d0e205b crew: round-four fixes from the Opus review 234300e37 Merge branch 'dev' into feat/crew-open-all-toggle 894292f28 crew: round-five fixes from the Opus review fba77f92d An answered landing carries its answer's fate when it is raised again, and an in-flight settle holds the question down (#1077) e69445058 Changelog entry for the landing-answer fate (#1086) 64ab7eae0 crew: round-six fixes from the Opus review 070016419 crew: round-seven fixes from the Opus review af09aa852 The review round's four findings: claim ownership, decider changes, recordable answers, plain words c24af0248 crew: round-eight fixes from the Opus review 289d372b9 The tests the landing's answer carries: fate, hold-down, redraw, claim doors 7e38346a9 The take-down comes before every mint, and the tui3 walk reads the newest card dbe1be03e The review rounds' last word: the release owns its claim, and the docs say what the code does 4cc4945a7 The Opus review: a release owns its generation, and every failed round still says its word a5cce0dde The done card says who is deciding, so the clause is a duplication, not the fact 6bd276e6c Merge fix/1077-landing-reraise into santos/dev 271016938 Merge feat/crew-open-all-toggle into santos/dev b459399d3 Merge fix/question-answer-box-caret-keys into santos/dev a38492026 Merge codex/recovery-evidence into santos/dev dc22e6461 cmd/codeaf: the role-door tests read the shipped mastermind instead of a copy of it 39f528f70 release: the pin-example check reads the guide the examples moved to 6439d3cb8 session, tui3: gofmt the three files santos/dev merged unformatted 854037967 tui3: a question's answer box kills to the end of the line on ctrl+k d93ec61ea session: the late-attach proposal test holds the work until the replay is read a81ead4de session: the landing-fate tests read today's clock instead of a fixed date 8fe4fff38 provider: the retry-avoid list rides the call's context into the knobs a266da3e2 provider: the retry-avoid veto narrows the wire through one field law 197d89334 exec: a node retry asks the router to avoid the lanes that failed it 2d02cc0b9 provider: the set-servable law's doc comment keeps its paragraph break e1d0e7524 exec: the retry test names its helper and spells its wall in time units e7d65a517 docs: the change note for the retry that avoids the failed upstream 11decbd92 Merge fix/provider-failover into santos/dev c099a51c2 Merge dev into santos/dev e47d92bde provider, exec: the lane a failure names is a wire fact provider reads (#1105) 396be97be Merge dev into santos/dev 3b97f50c3 Merge dev into santos/dev ea3b5c9a0 telemetry: the send path drops events that cannot name their version 573b288e4 cmd/codeaf: keep smoke runs from sending usage counts e8d627c6b docs: the change note for the usage-count audit fixes 11bec8d80 chat: a hosted session's usage counts are read off the events it receives b9d75b39e chat: the hosted-count tee is tested against the tally it must produce 39fa08d3d telemetry: stamp a version in the lifecycle tests 681e2adf1 chat: count the hosted boot agent once, and conversations opened beside 825ef2c41 connect, settings: named, multiple, switchable custom connections (#1092) f4d3f9a1a Merge branch 'dev' into fix/telemetry-audit 76fec37e3 ci: the Model Pool mirror workflow and the signature check it runs 2fb296bf1 crew: Pareto crewing, the learned picker and the Model Pool (#1093) a065ebb1b Merge remote-tracking branch 'origin/dev' into santos/dev 80ac250a0 Merge remote-tracking branch 'origin/feat/pool-mirror-workflow' into santos/dev 6092393ab Merge remote-tracking branch 'origin/fix/telemetry-audit' into santos/dev ef808dda4 session: a decision card under --yolo takes its default (#1117) 70a9e1f6e session: a task's thread stops its parts before it closes, and a machinery cut says so (#1118) c4df8f364 crew: an auto seat's rung names the pick that ran, once, for both doors (#1119) a9fcf9bc8 crew: a bare auto row under picked from = catalog reads the catalog's figures alone (#1120) 13be0dce4 relay: accept the exec and run doors, not only task and do (#1121) 90f29aec1 changes: name the #1120 note by its PR and stamp the pr field 0b77926ca do: seat from the settings the run will use (#1122) 32148d87d changes: stamp pr: 1120 on the #1120 note 113ed628e pool: a launch's start-up errands stop when the process closes (#1124) 6c98ddca3 verify, codeaf: a launch compiles fewer patterns and a surface holds fewer Ps (#1125) 50fe6a3e4 pool: the min_installs floor counts installs on the client (#1127) 076cde8ae relay: fold a resubmitted row once by its nonce (#1128) 8339ae71b relay: carry the acceptable metric beside role_quality (#1129) 0367c441b outbox: a refused row leaves the outbox and the rest of its batch is sent (#1130) 09aecd2f1 relay: the index holds only the cells that meet the min_installs floor (#1131) ae7c05dcd outbox: a Send compacts the file to the rows it still holds (#1133) 9e24f72d8 pool: the errand tracker's critical sections unlock from a defer (#1134) 358a569aa relay: fold a submit batch with one read and one write per distinct cell and day (#1136) a1d2eb1f7 pool: judge landed-but-unjudged runs on the next chat start (#1132) 3c6722859 verify: take the finished-tree check at a project cloned into a subdirectory (#1135) 263625b63 exec: a headless run leaves a pending judge record at its tail (#1137) 8be373d39 run: the headless task door leaves a pending judge record (#1138) a336b246e do: leave a pending landing for the Model Pool's judge (#1139) 8820a5659 cmd/codeaf: one readPendingRows test helper, not two (#1140) 49760fb45 pool: show and verify print both metrics the index carries (#1141) 6eaeb25c9 pool: status shows the pending judge records and the last sweep (#1142) 7c00b5f2c codeaf: a test's crash fixture stays in a profile the test owns (#1145) fc2eb357a manual: the status row's ledger is the tree's, not the conversation's (#1143) a06b53a91 test: pin the delivery gate over a grown job root (#1147) 7231347eb resident: the wall refuses a first replan round it cannot hold (#1144) 5e257557f cmd/codeaf: the package's test floor clears a profile the environment named 4dd1c562f Revert "cmd/codeaf: the package's test floor clears a profile the environment named" 9075f863c pool: the seed index is regenerated from the relay by seedgen (#1148) a5c44f3d2 pool: show --cells lists the held index's cells with installs and dims (#1149) 825996739 cmd/codeaf: the package test floor clears a profile the environment named (#1150) 81568aa82 session: report gitignored build products a run leaves, without gating on them (#1152) da4910593 internal/tui3: the opening hint test owns its profile and pins both doors 72de3bca7 docs/changes: #1151 — the opening hint test owns its profile 499083d1c Revert "docs/changes: #1151 — the opening hint test owns its profile" beb31ecf7 Revert "internal/tui3: the opening hint test owns its profile and pins both doors" 25b8b7dea version: an unstamped binary names the condition, not a Makefile target (#1151) c833b423a pool: a dropped row keeps the relay's reason and status shows it (#1154) cbb7d6d9e processgroup: verify a job group's identity before signalling it (#1155) efe498ed0 internal/tui3: the opening hint test owns its profile and pins both doors (#1153) 17b3b0e40 do: a continuation continues the plan it has instead of re-planning (#1156) 22cf3ce17 changes: correct the #1155 and #1156 notes' pr fields (#1157) 8e85b0fe7 cmd/codeaf: a surface runs under a soft memory limit drawn from the machine (#1158) ef1e92756 task: status row accounting (#1159) 44f572e26 pool: status's index line says what this run's fetch did to the cache (#1161) 3600fc9a5 pool: the pool's copy of a seat's id is the bare vendor/id (#1163) a2ebd1397 cmd/codeaf: no test can push pool rows to the default relay (#1164) 4fb7f0546 docs: the road a chat door bash push took under --yolo (#1160) 69219a1e1 cmd/codeaf: the soft memory limit is the smallest real bound, not the host s memory (#1162) 2b155b4b4 relay: bound judge severity, clamp adjusted scores, publish mean and sd on one scale (#1165) 2fb55324a relay: purge fixture vendors and refuse them at the door (#1167) 57504187a exec: a job's shell cannot reach the tmux server hosting codeaf (#1166) eb7b46559 internal/session: an unattended run may not move a branch it did not create (#1168) 80f770639 pool: status says the identity, the judged total and that nothing has landed (#1169) 1cb7333fa exec: an exec run's worker spend reaches the usage ledger (#1170) 96bb9402e changes: the #1169 note's title parses and reads as one line (#1172) 627236d95 config: the task door honors a stored crew word, and a pin survives the learned pick (#1171) 593092be6 internal/tui3: a test may not read a profile it did not create (#1173) ad8be1ed7 session: a landing card under --yolo takes its default instead of parking the run (#1174) 03d49a678 gitignore: the .furrow ids the engine writes into a workspace (#1175) 46edbdd32 internal/tui3: gofmt the profile-law helpers from #1173 (#1176) 3179e3611 relay: ALLOWED_VENDORS names the catalog's vendors and codeaf (#1177) 397a145a6 docs: why a settled /task leaves commits on task/<slug>, not the worktree branch (#1178) 915ff3a76 pool: regenerate the seed index from the relay's first clean publish (#1180) d9b42430f cmd/codeaf: the model warm stops when the process closes (#1179) bd2dcf334 session: the auditor's calls say they are the auditor's and which node they check (#1181) c6ed47c5a task: json unverified branch (#1182) 360352ad4 session: a settle turn runs under the checker's own bound, not the run's wall (#1183) 814e1af0e session: the tasks text and record line name a failed/unverified node's kept branch and verdict (#1184) 7c25a26d9 internal/session: the identical-effects test stops riding the turn guard (#1185) 6e7d0ed44 cmd/codeaf: exec and run envelopes name a non-verified node's kept branch and verdict (#1186) f4079d716 session: bound a worker's park on a promoted command (#1187) 994689fe7 docs: the model pool runbook names the Workers Paid plan and what it costs (#1188) c315be13e docs: the runbook's plan line says metered, not capped, and nothing more (#1189) 2e80c60d8 internal/tui3: the status word says working while a task subtree turns (#1191) 99363ca8f session: bound a job subtree's CPU and process count (#1190) 6db5125b2 docs: what the audit check seat can execute, and where that boundary is set (#1192) 649bb4bfd cmd/codeaf: the pool start-up test empties the CI word it is not about 053e6a90f processgroup: a zombie leader does not keep a job's group alive b4e66f515 docs/changes: the entry for #1194 Co-Authored-By: Santosh kumar <[email protected]> Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci: Model Pool mirror workflow (signed index onto the model-pool branch) (#1116) * ci: the Model Pool mirror workflow and the signature check it runs Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs: the change entry for the Model Pool mirror workflow --------- Co-authored-by: Claude Fable 5.1 <[email protected]> Co-authored-by: santoshkumarradha <[email protected]> * chat: the unconnected custom row on /connect keeps its vendored name (#1195) modelConnectionStatus renamed every custom-id row to the connection's Written word, and the catalog template's Written is the bare id, so a person who had connected nothing yet saw `custom` where the manual, the README and #1107 say `Custom OpenAI-compatible API`. Only a connected instance is called what the person called it now; the contract test draws both rows and is red without the fix. Found verifying the staging build of #1194. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: v0.3.0 rolled up into CHANGELOG.md (#1196) 95 loose entries — the crew family and Pareto crewing, the Model Pool and its relay, custom connections, the telemetry audit, the update door's download clock, and the fixes that followed — become the `## v0.3.0` section the stable release reads its notes from. Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci: a changed directory in a nested module is not a package of the root module (#1332) The touched-packages job and the Makefile's test-touched target both derived the test set as every directory holding a changed .go file, and handed a directory under its own go.mod to go test, which answers "main module does not contain package" and fails the job as a setup error. Both walks now stop at the first go.mod above the directory and skip nested modules, so the bench fixtures under bench/bashloop/fixtures are left alone and make pr-ready derives the set the gate will. Verified on the 621-file diff that surfaced it: 43 directories to 36, the seven dropped are exactly the fixtures, and the two halves print the same list. Co-Authored-By: Claude Fable 5.1 <[email protected]> * santos/dev lands on dev: the worker harness, stopping and limits, the task page and rail, goroutine ownership at close (#1108) Integration branch santos/dev, held as a closed slice at d39a4e308 plus the empty-prompt guard for generate_image (3a1f80f9c). Everything through #1192 had already reached dev as #1194; this carries #1193 and #1203 through #1331. The worker harness: one loop under /task and codeaf do with a plan store per run, a plan seat, a work seat, a check seat and a review round after each piece of work, off unless CODEAF_TASK_BELT=bash is set, so the default road does not move. Stopping and limits: a person's stop reaches a run, a time or dollar limit ends a run and names itself, a run is handed what is left of the conversation's limits, a worker that stops making progress is told once and then ended. The task page and the rail: the plan as a tree, a run's row with its parts under it, step rows cut from the commands as they ran, a press on a run's row opens its page in under 0.2 seconds. The engine host and sign-in, the profile's composed writes across two processes, unread config keys said once. Nothing keeps writing after the process closes: the place sweep, the standing ticker, the usage writers, the pool judge sweep and the catalog warm are each owned, stopped and joined, and a law lists every goroutine a conversation process starts. The heavy-suite lock is a kernel file lock that follows the suite. The lazy lexer registry, one fuzzy matcher for every picker, the drafted-with footer on the CodeAF repository. Reviewed 2026-09-21: gates green locally and in CI once #1332 taught the touched-packages walk about nested modules; the default road verified live on both roads against a dev baseline; the one regression the slice introduced (generate_image had lost its empty-prompt guard) fixed on the slice with a contract test. Co-authored-by: Santosh kumar <[email protected]> Co-Authored-By: Claude Fable 5.1 <[email protected]> * a dev build installs beside codeaf as devaf and follows the dev channel (#1333) * install: --name installs the binary under another file name beside codeaf The installer takes --name WORD and CODEAF_INS…
santoshkumarradha
added a commit
that referenced
this pull request
Oct 25, 2026
* Honor chat-requested models for task workers and new conversations (#1935) Preserve explicit worker model selection for joined tasks and descendants. Add model-selected independent and teammate chats, atomic launch claims, updated tool guidance, and regression coverage. Validated all five live OpenRouter launch scenarios with DeepSeek v4.1 Flash and Kimi K3. All PR checks passed. Refs #1839. * Answer and verify the four skill capability questions (#1938) Add direct skill definition, capability and troubleshooting answers, including memory-independent behavior. Pin the four original queries and verify tool-result delivery through real hosted terminal chats with memory saving on and off. All eight live cases passed using exact deepseek/deepseek-v4.1-flash. All PR checks passed. Closes #1937. * Make All show the current conversation tabs (#1939) * Scope All to the current conversation tab strip * Keep screenshot launches guarded and use rendered count grammar * Guard the screenshot terminal option command * Expect current-tab scope in the All navigation hint * Scope the All wall's copy and empty state to open tabs The in-wall All chip now says the grid holds the tabs in this view rather than saved conversations, and an empty scoped grid says no conversations are open here instead of promising saved ones will appear. wallEmptyText is shared by the live grid and the currently unused wallEmptyRow, so the two stay in step. The dock test now proves the label and the mark each open the wall scoped to open tabs, and the render test pins the scoped empty line. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> * Make open tabs a property of the open wall closeWall now clears wall.openTabs with wall.on, so no reader after a close can mistake a closed scoped wall for an open one. wallOpen still reads the scope before it closes the wall, which is where the team is kept. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> * Open an overview unpicked A pick outlived a close that was not esc, so a card picked in the wall left the next overview in selection mode - and in the All scope it could be a card nobody had picked there. Every overview now opens with nothing picked. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> * Name the All door as the tabs in view in the manual places.md still promised all saved conversations from ▦ All, and keys.md listed alt+v, /wall and the strip door as one thing. They are two views since this change. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> --------- Co-authored-by: CodeAF <[email protected]> * Run the same program when connecting to another machine (#1940) * tui3, config: the gate row lands on the conversation you are in, and /status names the gate (#1063) The three Safety rows — `ask before running`, the tool exceptions and the shell command rules — wrote the profile and left the running gate alone, while the YOLO badge read that profile live. So the badge and the gate disagreed in both directions: `allow` lit the badge over a gate that kept asking, and `prompt` put it out over a gate that was still wide open, which is the false-safety claim #322 and #325 exist to prevent. `applySetting` now pushes those three rows through `ApplyApprovals` — the seam /permissions already drops a banked rule through — and refreshes the badge on the same keystroke, on the branch where the push landed. A rebuild that could not be done is said out loud on the panel's foot line in /permissions' own words, `saved · from the next session`, and the badge stays with the gate in force rather than following the file. And /status carries the posture it never carried: `approvals prompt`, `allow` or `deny`, in the `ask before running` row's own words, in the note, in --json and on the phone's status sheet, from the one posture reading the badge uses. The status row keeps its negative-space badge and the segment is no longer written into the page twice. The note's label column is measured from the widest label it carries, so three crew assertions that spelled `"\ncrew"` plus exactly five spaces were assertions about every other row on the page. They read the crew line by its label now. Co-authored-by: Claude Fable 5.1 <[email protected]> * attribution: the footer names CodeAF, and still links to agentfield.ai (#1075) * attribution: the footer names CodeAF, and still links to agentfield.ai The pull request, issue and comment footers read "Drafted with agentfield ai". They now read "Drafted with CodeAF"; the link, its utm parameters and the "reviewed and owned by the author" tail are unchanged. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy * docs: the change entry for the CodeAF footer Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy --------- Co-authored-by: Claude Opus 5 <[email protected]> * session, prompt, tui3: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1) (#1067) * session, prompt: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1) The end-of-turn reader saw 600 bytes of the last message, so a long table read as cut off; the carry-on told the model to explain itself, and the explanation became the only answer left in view under the fold. The reader now sees up to 8 KB of the last words, and a cut it still has to make ends with `[clipped by codeaf: N of M bytes]`, which its ask says is never evidence of a cut-off answer. The carry-on lead says who wrote the note and offers one exact reply, `[no change]`: it ends carrying on for the ask without another reading, the transcript keeps it for the model, and neither replay nor the live feed draws it — so the settled answer before it is what the fold finds. system.md trades `# Tone` and `# Delivery` for `# The answer`, `# Words or work`, `# When corrected` and `# Messages from codeaf`, and the two lines that pushed action on questions are qualified. The lean page keeps all four; the prefix waivers rise by the 1,705 bytes that cost, dated and named. Co-Authored-By: Claude Opus 5 <[email protected]> * docs: the change entry takes the pull request's own number Co-Authored-By: Claude Opus 5 <[email protected]> * session: a quiet landing is asked about the notes it was handed, not the page that names them The worker's system page now names [silent] and [stuck] under `# Messages from codeaf`, so a search of the whole request found the tag on the page. childWasHanded reads the conversation and leaves the page out. Co-Authored-By: Claude Opus 5 <[email protected]> * session, prompt: [no change] answers the note it follows, and a long ask leaves the last words cut by name (#1065 round 1) Self-review of #1067 found the token honoured on the wrong replies. The gate read the turn's carry-on count, so a model that made calls in answer to the note and then said [no change] ended the turn unread on an answer those calls may have made stale, and a token after a load or ask nudge counted too. It now reads the transcript: the token must be the very next reply to the reader's own lead (answeredNoteUnchanged), and the dead declined flag is gone. An ask up to the whole digest rides in its head, so eight kilobytes of last words behind a long ask reached the backstop clip and ended in a bare `…`. checkpointClipSaid takes the room that is left. A turn that ends on the token no longer starts the route judge or hands the token to the memory reflex, and a later reading's last words skip it. `# Messages from codeaf` named `[still asked]`, which only the reader ever receives, and left out `[stuck]` and `[held]`, which the conversation does; it now names the tags that arrive and says any other opening, such as `[image #1]`, is the person's. The waivers sit on the measured 1,779 bytes, and the manual and the change entry say what the gate actually honours. Co-Authored-By: Claude Opus 5 <[email protected]> * prompt: the new sections keep the rules the old two carried, and say what stays in view (#1065 round 1) Owner review of #1067. Replacing `# Tone` and `# Delivery` had dropped five rules; each is back inside the new sections: match the person's task and vocabulary, choose the safe option under uncertainty, evidence and blocking details complete, never a compiling scaffold or a narrowed test, never half-solved work. `# The answer` said only the last message reaches the person, which `# Planning` contradicts: the plan and the per-batch sentence are shown while a turn runs. It now says that once a turn ends only its last message stays in view. `# Words or work` sat beside `## Work or words`, which spends the same two words on hand-off sizing; it is `# Answer or change`, and it says an answer asked for in chat is written in the reply, not a file, unless the person asks for one. The waivers sit on the new measurement, 2,035 bytes over dev on both arms. Co-Authored-By: Claude Opus 5 <[email protected]> * prompt: the messages-from-codeaf list names every note codeaf writes The system prompt now distinguishes conversation user-role notes from private reader and tool-result markers. A structural test follows the production constants, and the byte waivers record the exact fixed and lean measurements. Co-Authored-By: Claude Fable 5.1 <[email protected]> * session: no change skips judgment only when it answers the reader The completion loop now uses the transcript context before suppressing the route judge or substituting the prior answer for memory. Tests cover the withdrawn and ordinary-token memory paths, and the manual separates counting from display. Co-Authored-By: Claude Fable 5.1 <[email protected]> * test: the model stub replays the no-change fold The OpenAI-compatible stub can now drive the real binary through eleven read rounds, a long table, a rejecting reader, and the exact token reply. The recipe records the tmux setup and the expected log and screen evidence. Co-Authored-By: Claude Fable 5.1 <[email protected]> * prompt: the standing-news tag stays with the event, not on the page The messages-from-codeaf list had named [something you set up fired], and TestTheSteeringLineReadsAsNewsAndNotAsARequest refused it: that line says under the news itself what it is and what not to do about it, so the page's copy is one law bought on every request of every turn for a turn most sessions never have. The list keeps the compaction tags, the structural test now files the standing frame with the private voices the page must not name, and the waivers fall to the new measurement (fixed 55,280, lean 47,055). Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Opus 5 <[email protected]> Co-authored-by: Abir Abbas <[email protected]> * Clean internal experiment records and revive test/ux for codeaf (#1068) * chore: clean internal experiment records and revive test/ux for codeaf Public-repository cleanup ahead of opening the docs to outside readers. - Remove four internal DOE/experiment record directories that reference private issue and pull-request numbers and are not current-product documentation: docs/design/pin-doe, plan-gate-doe, turn-wall-share-doe, and workspace-foundation. - Neutralize internal machine references in benchmark and design docs: the bench host ("spark"), its secrets.env path, and a personal working branch name, all now generic placeholders. - Revive test/ux/, the user-facing journey suite, for the renamed product: collapse the removed v1/v2 dual-surface logic into the single v3 surface, repoint the binary to cmd/codeaf and state to ~/.codeaf/CODEAF_*, and delete gate.sh whose premise was the v1-vs-v2 parity table. Co-Authored-By: codeaf <[email protected]> * chore: drop internal conversation-runtime wave records, keep the measurement contract The conversation-runtime directory was a local development wave: day-by-day status, /private/tmp paths, "owner authorized" process notes, draft PR numbers, and local change logs. None of it is current-product documentation a public reader needs. Keep PARETO.md, which is the benchmark's cited measurement contract, trimmed of its dangling calibration link and owner language. Co-Authored-By: codeaf <[email protected]> * chore: neutralize the remaining bench-host and work-machine references A wider sweep found the internal bench host ("Spark"), its secrets path (fleet/secrets.env), and a scratch ops directory still named in live docs, bench READMEs and bench scripts that the earlier redaction had not touched. Rename the host to "benchhost"/"the bench host", drop the secrets path, and replace "the laptop" as the owner's own machine with a neutral "work machine" where it meant the internal one. The manual's first-person "the laptop you are sitting at" is public-facing and left alone. Co-Authored-By: codeaf <[email protected]> * chore: fix review findings in the public-cleanup diff - PERF.md: drop a private tracker issue reference (#846). - PARETO.md: restore a complete sentence dropped when the calibration link was removed, and align tense with the remaining present-tense claim. - home-mission-control/DESIGN.md: drop the internal "fleet" system name and an owner-framing phrase, consistent with the secrets-path scrub elsewhere. Co-Authored-By: codeaf <[email protected]> * docs/design: the two records live code still cites stay in the tree plan-gate-doe/REPORT.md is the measured evidence behind the plan-gate default that twelve comments in cmd/codeaf, internal/config, internal/session and internal/splitgate point at, and turn-wall-share-doe is the run internal/session/turnwall.go says it was added from. CLAUDE.md keeps the record on purpose for exactly this reason, and neither folder names a machine, a secret or a private address. Co-Authored-By: Claude Fable 5.1 <[email protected]> * test/ux: the revived journey suite goes back out #991 removed it because it drove the v1 and v2 surfaces, and the revival did not change that: its journal verb reads graph.db tables (messages, nodes, usage, commands) that the v3 chat never writes, lib.sh still waits for the resident, nothing runs it from the Makefile or a workflow, and its only evidence was bash -n. docs/JOURNEY.md says internal/e2e is the one suite that drives the binary, and that stays true. A suite that has been run green against bin/codeaf can come back on its own pull request with a target that runs it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: entry for #1068 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: the #1068 entry says why the journey suite is out, correctly The first draft said the v3 chat never writes the graph.db tables the suite reads. Running the suite showed both message rows landing, so that sentence was wrong; what is true is that nothing runs the suite, it had never been run, and its first journey fails on its first screen check (#1072). Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: codeaf <[email protected]> Co-authored-by: Abir Abbas <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]> * session: task commits are authored by the bot account, not codeaf@localhost The author codeaf stamped on the commits it lands was `codeaf <codeaf@localhost>`, which no GitHub account can link, so every branch codeaf landed failed the CLA check. It now writes `codeaf <[email protected]>` - the identity the Co-Authored-By trailer already used - and keeps `codeaf@localhost` and `aforge@localhost` recognised as legacy by taskCommitIdentity. GLM 5.3's review caught an unmarked retired name in permissions.md that reddened the namelaw gate, and seven stale test fixtures; both fixed. Laws gate, session, exec and cmd are green. Co-Authored-By: codeaf <[email protected]> * README: show the pinned-version install line the release test requires (#1103) * README: show the pinned-version install line the release test requires The Install rewrite dropped the VERSION=<tag> pin example, and TestPublishedPinExamplesGiveVersionToBash reads every published install surface for `| VERSION=` because `VERSION=<tag> curl ...` is an easy mistake to copy — it sets the variable for curl instead of the install script. The pin goes back inline on the same host the section already uses, with the releases page still linked for tags. Co-Authored-By: codeaf <[email protected]> * docs: the change entry for the README pin example Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * telemetry: anonymous usage counts, off with CODEAF_TELEMETRY=off (#1095) * telemetry: the package behind anonymous usage counting, per the wire contract Four typed events (first_run, session_started, session_ended, fault) with the contract's allowlisted, bucketed props; the opt-out ladder with its off reasons; a hashed install identity at 0700/0600; a fault fingerprint over codeaf function names only; a JSON-lines spool with a deadline-bounded flush of up to 50 events per POST, 7-day age drops and a 1000-line cap; the notice constant with NoticeShown/MarkNoticeShown gating the flush; and Show() for a future codeaf telemetry show. A library only: nothing in cmd/codeaf, internal/session or internal/config reads it yet. Co-Authored-By: codeaf <[email protected]> * telemetry: tests, doc, and contract fixes for the usage-count package Adds the package's tests — bucketing edges, the opt-out ladder, install identity, the notice, fingerprinting, the spool against an httptest relay (cap, age drop, 50-per-POST splitting, a hard deadline against a hanging server, silence on failure) and the privacy law that no sentinel string reaches the wire. docs/TELEMETRY.md documents the notice, what is sent and how to turn it off, with a test that fails if the doc's property table drifts from the Go allowlist. Fixes four drifts from the wire contract found running those tests: the <1m duration bucket was missing, the fingerprint's argument stripping cut too much, the rc channel suffix must be a single integer, and under go test a config-off rung outranks the test rung in the reason reported. Co-Authored-By: codeaf <[email protected]> * telemetry: fix review defects 1-8 with tests Fingerprint stability (last-( cut, .go:/main./created-by frame rules), counted-batch flush, guarded and multi-process-safe spool, BucketDuration edges, Configure-gated write paths, O_EXCL install id, install.json preservation, buildinfo.Dirty accessor. Co-Authored-By: codeaf <[email protected]> * installer: record the install locally and print the telemetry notice Following the telemetry wire contract: a successful install now writes telemetry/install.json under the state root (dir 0700, file 0600) with the install method, channel and UTC timestamp, leaves any existing install_id alone, and makes no request the download steps did not already make. At the very end the installer prints the contract's notice to stderr after one blank line, or one line saying telemetry is off when CODEAF_TELEMETRY/DO_NOT_TRACK opts out. README gains a plain Telemetry section pointing at docs/TELEMETRY.md, and a new test/installer-telemetry.sh proves the marker, the notice and the opt-out without a network. Co-Authored-By: codeaf <[email protected]> * installer: four review corrections to the telemetry commit The review of 71ff95a6b found four things. Revert the one line that changed `if [[ -z "${TAG:-}" ]]; then` back to `${TAG:-}`: under set -u an unset TAG must reach api_problem, not crash. In write_install_marker the channel lands in JSON verbatim and CHANNEL can come from the environment, so only stable, rc, staging or dev is recorded and anything else becomes "unknown" — with test cases for a valid channel and for we"ird, both asserting the marker parses (python3 -m json.tool, or jq, else a strict channel check). Fence the README Telemetry notice in a ```text block so Markdown keeps its alignment, wording byte for byte. Clear the two shellcheck findings in test/installer-telemetry.sh with one block disable for SC2016/SC2034 — ok() evals those strings, so the quoting is deliberate — and export CODEAF_TELEMETRY/DO_NOT_TRACK where a subshell needs them, without weakening any assertion. Co-Authored-By: codeaf <[email protected]> * release-downloads: daily PostHog report of public GitHub release download counts A scheduled workflow reads the repository's releases from the public GitHub API and sends one codeaf:release_downloads event per binary asset (codeaf-<os>-<arch>[.exe]) — sidecars like checksums.txt are not reported. Each event carries release_tag, channel (stable or the prerelease suffix), prerelease, os, arch, the cumulative download_count and the UTC snapshot_date; the event id is derived from tag, asset and date, so a same-day rerun deduplicates instead of counting twice. No code in the binary; counts come only from public release data. Co-Authored-By: codeaf <[email protected]> * release-downloads: tests cover the workflow, docs and pagination The dry-run unittests now also assert the identifiers the wire spec spells out where the script and the workflow spell them: the schedule, dispatch, contents-read permissions, built-in token, project-key secret and host variable in .github/workflows/release-downloads.yml, the public-data paragraph in docs/rules/promotion.md, stdlib-only imports, pagination across API pages, the host override and the /batch/ endpoint, sidecar skipping, draft skipping, and the channel words stable, rc, dev and staging. Co-Authored-By: codeaf <[email protected]> * release-downloads: four review corrections to the script Channel detection now follows the tag grammar in cmd/codeaf-release/version.go: stable, rc, dev and staging tags match their four patterns and anything else reports "unknown", so dev-20260917-09299019bcdc is no longer mislabelled "stable". --dry-run works without CODEAF_POSTHOG_PROJECT_KEY, the local and test path: the batch prints with api_key "<unset>" and exits 0, and only the sending path stops with the notice. Every event carries "$geoip_disable": true next to "$process_person_profile": false, since the sender is a CI runner whose location is meaningless. The module docstring now names release-downloads.yml, the file that exists. The fixture holds one tag of each shape plus an unrecognised one, and the tests assert the channel for each, including the two real dev and staging tags. Co-Authored-By: codeaf <[email protected]> * release-downloads: test runs on Python 3.9 too ScriptSourceTest read sys.stdlib_module_names unguarded; the attribute arrived in Python 3.10, so the suite errored on macOS stock python3 (3.9.6). Guarded with getattr so the pinned import-set check stands on its own where the attribute is absent; 32 tests pass on 3.9, 3.10 and 3.14. Co-Authored-By: codeaf <[email protected]> * installer telemetry: the notice is read from docs/TELEMETRY.md The test read .telemetry-contract.md, a working note that is not in the repository and never will be, so it aborted with "telemetry contract is missing" in a clean checkout. It now extracts the fenced block under "## The notice" in docs/TELEMETRY.md — the same bytes as the Notice constant in internal/telemetry/notice.go — and holds the installer's TELEMETRY_NOTICE to it as before; a new assertion holds the README's telemetry fence to it too, byte for byte, so the invariant the suite existed to guard now has its third leg. Comments in scripts/install.sh, internal/telemetry/telemetry.go and this test now name docs/TELEMETRY.md as the source of the notice. No Go code changed and no Go tests were run. shellcheck is clean; the suite's 33 passing assertions include the two new notice checks, and the same two stat(1)-related assertions that already failed on this GNU machine before the change still fail with identical output. Co-Authored-By: codeaf <[email protected]> * release-downloads: the download-counts paragraph lives in docs/TELEMETRY.md docs/rules/promotion.md was the only home the paragraph had, and it came with a fallback pinning the branch to the state before the telemetry doc existed. The paragraph moves, byte for byte, into docs/TELEMETRY.md as a final "## Download counts" section beside the rest of the telemetry story, and promotion.md returns to its origin/dev content, so this branch now touches no release docs. test_no_telemetry_doc_on_this_branch asserted docs/TELEMETRY.md did not exist and is deleted. The two tests that read the paragraph read it where it now lives: both require a "## Download counts" section in docs/TELEMETRY.md naming .github/workflows/release-downloads.yml, scripts/release_downloads.py and the checksums.txt sidecar, and the promotion.md fallback is gone. No Go code changed and no Go tests were run; the property table in docs/TELEMETRY.md is untouched. The suite passes: 31 tests, OK. Co-Authored-By: codeaf <[email protected]> * installer telemetry: the mode check asks GNU stat first On Linux `stat -f` succeeds and reports the filesystem, so the BSD-first order never reached the GNU form and both permission checks failed. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01CqfhxLrrBNQxwPNvEgoBGR * telemetry: refuse to send to the production relay from a test binary Co-Authored-By: codeaf <[email protected]> * telemetry: testHome points the endpoint at a dead loopback Co-Authored-By: codeaf <[email protected]> * telemetry: the orphan-adoption test names the dead relay it now gets Co-Authored-By: codeaf <[email protected]> * telemetry: a test pins that no test binary reaches the production relay Co-Authored-By: codeaf <[email protected]> * telemetry: three tests pin the test-binary relay refusal Each asserts through real calls, not a re-implemented effect: the newRelay override lands after testHome and a flush reaches that recorder; a default-endpoint flush is refused like a dead relay (zero round trips, event still spooled); forcing the ladder on cannot reopen the path. Co-Authored-By: codeaf <[email protected]> * telemetry: allocation-free session counters Snapshot returns the SessionStats fields the SessionEnded constructor takes, so the wiring hands the struct straight over. Cost is stored as integer micro-dollars and divided back once, in Snapshot. Count* never ask whether telemetry is enabled and never allocate. Co-Authored-By: codeaf <[email protected]> * telemetry: gofmt the counters test One alignment fix in the many-goroutine test; no behaviour change. Co-Authored-By: codeaf <[email protected]> * config: telemetry setting row (CODEAF_TELEMETRY) Key "telemetry", on|off, default on, env pin CODEAF_TELEMETRY, in the interface group beside the history row. TelemetryAtIn walks the ladder — pin, project file, profile config, default — and TelemetryOffReason answers whether config turned the pipe off and names which rung did. The row sits in ProjectKeys so a repository may answer for itself. Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the telemetry command, and the setting it writes Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: session lifecycle events at execute (work in progress) Configure, notice, first_run and session_started at the start of every session command, session_ended and a one-second flush at its exit. The end event does not yet carry the session counters. * provider: count answered model calls and their spend at the log door * session: count sealed turns and executed tool calls * cmd/codeaf: the session-ended event carries the run's counters telemetryEnd builds the end event from telemetry.Snapshot so turns, model calls, tool calls and cost reach the spool; the stop-reason switch becomes a map over the exit ladder; and both lifecycle constructors are gated on the ladder being on so a disabled run touches no state. Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the counters test says the tally is process-wide The exact turn, call and cost assertions hold for one run of the package; a repeated run counts into the same atomic tally again. Co-Authored-By: codeaf <[email protected]> * guard: a fault hook Recover calls, for a reporter above this package Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the fault event at both crash sites, off the current session Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: a test that the crash site spools a fault and not the panic text Co-Authored-By: codeaf <[email protected]> * docs: finish the anonymous usage counts contract and add the 1095 changelog entry * guard: the fault hook sits behind an atomic pointer A goroutine recovering a panic read the hook while a test on another goroutine wrote it, which the race detector reported. SetOnFault stores it once; Recover loads it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: a task's interrupt word comes from the signal, not the cancelled context stopSignals cancels the signal context at the end of every errand, so the flag read from its Err said interrupted for a run that finished cleanly. Co-Authored-By: Claude Fable 5.1 <[email protected]> * tui3, installer: the telemetry row has a tab, and a custom install writes no marker TestEverySettingRowHasATab wants every registry row on the settings panel. TestCustomInstallOutsideTheStateRootDoesNotRunAdoption wants an install outside the state root to leave the login's state folders alone, so the install marker is written only when the root exists or the install is in it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: the counters test zeroes the process tally first Other tests in the package drive model and tool calls through the doors that count, so the exact bands held only when the test ran alone. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: the version test drives the telemetry lifecycle, not execute execute starts writers under the state root that outlive the test, and the temp dir's cleanup raced them. The lifecycle alone is what the test pins. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: gofmt the lifecycle test Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: codeaf <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]> * session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler (#1104) * session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler TestASuccessfulHandoffJournalsTheRungThatSuppliedTheBrief failed once in a full make check at line 118: the worker's brief was the running model's draft, not the mastermind's written handoff. THE CAUSE IS NOT A STOLEN STEP. Reproduced under load (60 CPU burners, the fixture run 1500 times), every request the fixture makes is either the turn's own or one the aside answers: the queue carries only `round`, `draft` and `writer`, and the errands beside the turn (title, namer, the mark's drawing) never touch it. What moves is the ROUND COUNT — 41 to 54 across the runs — because the mark's drawing is a sidecar (sidecar.go) whose landing the scheduler places, and the turn is never held for it. On a busy machine the turn runs its rounds off faster than the drawing's goroutine is scheduled onto a core, the gap between the net firing and the drawing landing outruns the slack the script carries ([checkpointSlack] says so in its own words), the writer's ask lands PAST the end of the 53-step script, the scripted completer answers it with its past-the-end line `(unscripted)` — which is not prose — the handoff rung reads degenerate, and the carry ladder falls onto the draft. That is #392 exactly, one mechanism along. WHAT FIXES IT IS WAITING FOR THE READING. checkpointAgent now puts a watch on the turn's own context ([watchReadings]) and the handover fixtures submit through [watchedContext], so [answerWhenQuiet] holds each request that carries the belt until every reading beside it has landed — the order a real turn has, where a real model spends seconds on a step and the small readings land inside it. The gap is then one boundary whatever else the machine is doing, and a script long enough to cross the mark is long enough. THE REGRESSION TEST holds the drawing back by a fixed wall time — standing in for a scheduler that does not reach it in time — and insists on the behaviour: the turn does not spend its script racing the drawing, and the writer's rung still supplies the brief. It fails without the watch (the turn makes 54 rounds) and passes with it. Co-Authored-By: codeaf <[email protected]> * docs: the change entry for the carry-ladder drawing race Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * docs: the install proxy is live, and the manual says so (#1076) * docs: the install proxy is live, and the manual says so `https://agentfield.ai/get/codeaf` has served `scripts/install.sh` since 2026-09-16, the day the repository went public, so the compiled-in manual and docs/GUIDE.md stop saying it is "not serving yet" and stop calling the repository private. The proxied line goes first, as the README already has it; the raw GitHub line is described as the script under it. Both pages say to pipe into `bash`, not `sh`, because the script uses pipefail and `[[ ]]` and dash rejects them, and both state the channel path (`/dev`, `/staging`, `/rc`) beside the flags. The manual also says what the proxy refuses: a script without its channel line exactly once, or a body that is not a shell script, is a 502 rather than the wrong thing. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: entry for #1076 Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> * A launch says a newer codeaf is out, and /update installs it and restarts (#1060) * The release-tag grammar has one home, and it is internal/update Stable, rc, dev and staging tags are parsed, compared and bumped in exactly one place now. cmd/codeaf-release keeps its next, kind and prune behaviour and its tests unchanged; it reads the grammar from the new package rather than owning a second copy of it. A second parser is the thing that drifts: the updater about to land needs the same answer to "is this tag newer than that one" that the release tool gives, and one source of truth is the only way they cannot disagree. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * internal/update: look up a release, check it, and replace this binary with it The launch check answers only for a build that could sensibly be behind: a stable tag, or a release candidate whose stable line has since been published. A dev or staging build, a make build sha and an unstamped binary make no request at all, because a channel build is deliberately newer than stable and a source build belongs to whoever built it. The answer is cached for a day beside the profile, and every failure is silence. The install downloads into the target's own directory, matches the sha256 from checksums.txt, and only then renames over the running file, so a refused download, a wrong checksum or an unwritable directory all leave the original binary exactly where it was and no temp file behind. The API token never leaves the API host: the redirect policy strips it the moment a redirect changes host, which Go does not do on its own for another port or a subdomain. Windows has no atomic rename over a running image, so it goes through ReplaceFileW and puts the original back when Windows reports the one failure that moves it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * tui3: a launch says a newer codeaf is out, and /update installs it The launch line is one dim transcript note carrying both versions, /update, and the curl line, because the command is what the owner asked to be given. It arrives as a command rather than an opening read, so the first frame is drawn before the network is asked anything and a slow or refused answer draws nothing. /update — /upgrade is the same road — names the tag, then the checksum and where it landed, then quits with a restart plan for the door to carry out. Already newest says so and stays. It refuses while a turn or a task is running, and once it has started, one shared guard keeps a second /update and a new turn from beginning underneath it: the restart must never quit on top of live work, and the home page's Enter roads go through the same guard chat's does. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * codeaf update, and the door that restarts the same conversation codeaf update --check prints one line and exits 0 when this release is newest, 3 when a newer one exists, 1 when it could not ask. Without --check it installs stable, or whatever --rc, --dev, --staging or --version names, choosing a channel build by its own publish time the way the installer does. A binary built from source refuses in place and names its path rather than replacing something a developer is holding. The restart happens here and not inside Bubble Tea: the surface fills a slot, the door reads it once the frame is down and the terminal is back, and then execs the new binary with this launch's own arguments plus --session pointing at this transcript, so the conversation reopens where it was. A linked-local engine is asked to retire only after the surface has detached, and it still refuses while another window or any live work is there. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * remote: a window that says it is leaving needs no torn-link grace The twenty-second grace exists for a terminal that was killed without saying so, and a deliberate detach is the opposite of that — the window has just announced its departure. Carrying the grace anyway left a cleanly closed window looking attached for two more call deadlines, which kept a freshly installed codeaf waiting behind the engine the old one had already left. It clears only once the last view is gone. Turns, tasks, unanswered questions, other views and live work all still refuse retirement, and a refusal still never stops the work it protects. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * The installer and the updater agree on what a channel tag looks like Two roads now install codeaf, and they were reading the same release list by different rules: the shell accepted any dev-* or staging-* tag and semver parts with leading zeroes, while internal/update accepts only the shapes the release workflow actually publishes. A malformed tag newer than a real one would send the two roads to different builds. One helper carries the grammar to both channel selection and the line that names the channel, and an unrecognised tag for the asked-for channel now says so instead of installing whatever came back. The publish-time ordering #1056 added is untouched: only which candidates are eligible changed. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * The manual stops saying that nothing self-updates That sentence was true until this change and is now the opposite of true, so it goes, and a section written in the words people bring takes its place: is there a newer version, how do I update codeaf, why does it say this every time I start. It states who gets the launch line and who never does, the opt-out variable, the day the cached answer lasts, the shell exit codes, and both refusals — a build made from source and an executable nobody may write. The command table names /update and /upgrade with the argument they take. Four probes reach the new section, and the remote page keeps its own asker's words in its heading so the new section cannot take that question from it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * docs/changes: the update check and /update, for #1060 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: --check answers for the channel it was asked about, and a newer build is never downgraded Two things a hand verification caught. --check parsed whatever tag it had selected with the stable grammar alone, so --dev, --staging and --rc could never be "newer" and every one of them answered "you are on the newest codeaf" and exited 0 while a newer build sat in the list. The check now keeps its channel all the way through the decision and always names the tag it is talking about: stable and rc compare as semver, a dev or staging tag is the same tag or a different one, and --version is equal or it is not. And a build AHEAD of the newest stable was quietly installed backwards — the check said newest, the install downgraded it. Both roads refuse now, naming both versions and the deliberate way past: this codeaf is v0.3.0, ahead of the newest stable v0.2.0 — pass --version v0.2.0 to install it anyway An explicit tag IS that deliberate road, so --version and /update <tag> still install exactly what was named. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: a failure names the asset and the status, and ends with the curl line A download that failed printed the whole fallback URL, which put the retired product name on somebody's screen. The line names the asset it asked for and the status it got back, and nothing about which repository answered — the compatibility attempts still happen, they are just not the person's business. Every terminal failure now ends with the curl line, which D9 asked for and the download path had been missing. The / list declared no argument for /update while the command page documented one, so the list gains the second row the way /cache has one. The manual section now says what the code does about a build ahead of the newest stable, where it had said such a build "gets nothing" — true of the launch line, and false of the install until this wave. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: the curl line is agentfield.ai/get/codeaf, the supported road The launch line, the refusals and the terminal door offered the raw GitHub address of scripts/install.sh. The repository is public and agentfield.ai/get/codeaf has served that script from main since 2026-09-16, so the constant now spells the supported line, the manual's update section names it in place of "the curl line below", and the change entry says so. Asked for on the pull request by Santosh. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]> * a run's own scratch note no longer fails the delivery gate (#1096) * codeaf: a run's own note is offered outside the tree it is judged in leafOutputHint invited a job root to write its overflow document at a workspace-relative name (exec.SuggestPathFor's task-2-x1-toolchain-checks.md shape), so a run working in a person's directory left that note untracked at the root of their working tree. The delivery gate holds the run's record — the artifact registry, joined by jobArtifacts, plus the delivered tree — against the request, read the note as part of what was handed over, and refused the delivery; the run then ended incomplete over complete work. There is no separate mechanical clean-tree check to exempt, so the fix is the writer's: where the workspace belongs to the person, the root's file invitation points at the run's scratch beside its recorders and job logs, and the message keeps carrying the deliverable. * codeaf: a person's own file still joins the record the gate reads TestARootLeafInAPersonsDirectoryPutsItsNotesInScratch baselined the tree after the note was already written, so its landing read could never have observed anything: the record stayed empty because of the ordering, not because of the address. The baseline now happens first, in the executor's own order, and a hint that ever points into the tree again fails on the observation, not only on the spelling. And the other half of the law is pinned down: a file that turns up in the person's tree while the leaf runs — even at a name wearing the run's own task-<n>-<slug>.md shape — is still observed and still lands in the record the delivery gate is held to. Only the run's own note moved; nothing else about what the tree says was loosened. Co-Authored-By: codeaf <[email protected]> * docs: the change note for the root's overflow file moving out of the tree --------- Co-authored-by: codeaf <[email protected]> * chat: the wordmark is drawn CodeAF, four rows, capitals on the d's cap line (#1106) `┌─┐ / ├─┤ / └─┘` is not an `a`, it is two closed bowls of equal size stacked on each other, and two equal closed bowls are an `8`. That was the second-to-last glyph of the product's own name on the first screen of every fresh install. The owner's answer is to have the two letters at the end stand up out of the word as capitals: `A` is an apex, a crossbar and two legs; `F` is the one letter whose case is told by its arms alone, so its top arm is the longer one. A CAPITAL'S TOP IS THE ASCENDER'S TOP, and three rows of box-drawing could not give it that. A bar is drawn at the middle of its cell and a stem reaches the cell's edge, so the `d` stood half a row over any capital however it was formed — and a stem in the bottom row hung half a row under the bowls' baseline the same way. The fourth row is that half a row made whole: the x-height letters live in the lower three and leave the top row blank; the capitals and the `d`'s ascender start in the top row, the ascender as the half-stroke `╷` so its top is the capitals' bar; and every stem that ends on the baseline ends as `╵`, which stops where `└─┘` does. A `│` in the bottom row is a descender and nothing else. THE WORD IS SPELLED `codeaf` AND DRAWN `CodeAF`. `product` is unchanged, the glyph table is still keyed by its lowercase letters, and a terminal that cannot draw boxes still gets the word `codeaf`. The greeting's centring test read the unit's left edge off the wordmark's first row, which is now the cap line the x-height letters leave blank; it reads the second row. The one manual page that draws the wordmark had been drawing the retired `aforge` letterforms under the label "the codeaf wordmark" — the name law walks text and cannot see a name spelled in box-drawing — and draws the shipped forms now. Co-authored-by: Claude Fable 5.1 <[email protected]> * A real release download finishes: the 3-second check clock no longer covers the asset (#1110) * update: a release download gets its own clock, not the check's The release client was built as http.Client{Timeout: 3s} because three seconds is right for the launch check, and Go's Client.Timeout covers the WHOLE exchange including every body byte. The same client then downloaded the release asset, so a 57 MB download on a healthy link — about eight seconds here — was cut off three seconds in and a person read `read the release response: context deadline exceeded`. Client.Timeout is gone. The transport bounds only what has made no progress: a 5 s dial, a 5 s TLS handshake, a 10 s wait for response headers. Every call now takes its deadline from its context. The launch notice and `--check` share the one CheckTimeout constant, install metadata gets 10 s per request, and the asset gets a 30-second no-byte stall window plus a 15-minute ceiling — a dead link fails with a sentence, a slow one finishes. No Go deadline text reaches a person. A stall says `downloading codeaf-linux-amd64 stalled — no bytes for 30 s`, the ceiling says `took longer than 15 minutes`, and late headers say `did not start arriving within 10 s`. The stall window, the ceiling and the metadata window are injectable, so the tests prove the clocks with real slow, silent and trickling servers rather than waiting out the shipped ones. Co-Authored-By: Claude Fable 5.1 <[email protected]> * update: both doors read the one check constant `codeaf update` and the chat surface each spelled `3*time.Second` when they built the release client, which is how a figure meant for one small request came to govern an install. Both now pass update.CheckTimeout, and the three seconds live in exactly one place. The terminal's `--check` takes the client's Check, which is the whole-exchange check deadline; a plain `codeaf update` keeps Select, so the metadata requests an install makes get the ten seconds they need. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: the update download's own clock (#1110) Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> * docs/changes: v0.2.1 rolled up into CHANGELOG.md (#1113) The 33 entries that landed on dev since the v0.2.0 roll-up are the ## v0.2.1 section of CHANGELOG.md now; docs/changes/unreleased/ keeps only this pull request's own entry. Stable v0.2.1 takes its notes from that section. Co-Authored-By: Claude Fable 5.1 <[email protected]> * README: telemetry folds under Docs (#1115) The 23-line Telemetry section between Install and the product tour becomes one collapsed block at the end of Docs: a plain summary line, the notice verbatim, one sentence and the link to docs/TELEMETRY.md. The notice block is unchanged, so it still matches the binary, the installer and docs/TELEMETRY.md. Replaces #1112. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01NoeSVN8nqmwNVRXExgoUNN * santos/dev lands on dev: crew family, Pareto crewing, the Model Pool, custom connections (#1194) Lands the integration branch santos/dev (#1108, head 6db5125b2, 152 commits over dev) on dev as one squash, plus two fixes that kept its touched-packages job red: the pool start-up test empties the CI word it is not about (649bb4bfd), and a zombie leader no longer keeps a job's process group alive on Linux, which had delayed every finished background job by the two-second termination grace since #1155 (053e6a90f). #1108 carries the full account; the 94 change entries under docs/changes/unreleased/ are the per-change record. The commits folded in, oldest first: 5c413ef7b Keep archived evidence readable while tasks finish 3705a0cfe Document recovery evidence retrieval change 7834cc7d0 tui3, manual: a question's own answer box answers to the whole caret vocabulary 99d66e98c task: Add open/all source toggle to crew config c1e4cb57f docs: the change entry for the crew model family toggle e63b43927 crew: round-1 review fixes 304a5a223 crew: round-2 review fix 65477c0c5 task: Add open/all selector to /crew TUI picker 9189ef764 Merge branch 'task/add-open-all-selector-to-crew-tu-9e8cc0' into feat/crew-open-all-toggle ba9c12d61 crew: reconcile the picker onto config's source helpers ea05bc0f5 crew: a chosen family counts as an answered crew e1a037e0a Merge remote-tracking branch 'origin/dev' into feat/crew-open-all-toggle f6c2924a7 crew: the open table takes the picks we landed on eff44b542 docs: the change entry carries the open-table move and the family gate 71ce9bec0 crew: the family reaches the fallback, the settings skin, and the row's line b8ca5f513 crew: round-1 review fixes on the new table 8db5708ef crew: round-2 review fixes ce9759ef4 crew: round-3 review fixes 956e1f514 crew: round-4 review fixes 37ad27f4e crew: fixes from the first Opus round 6ffb95813 crew: the fixes from the Opus confirmation round 72b027436 crew: the comments the atomic write left behind 70d0e205b crew: round-four fixes from the Opus review 234300e37 Merge branch 'dev' into feat/crew-open-all-toggle 894292f28 crew: round-five fixes from the Opus review fba77f92d An answered landing carries its answer's fate when it is raised again, and an in-flight settle holds the question down (#1077) e69445058 Changelog entry for the landing-answer fate (#1086) 64ab7eae0 crew: round-six fixes from the Opus review 070016419 crew: round-seven fixes from the Opus review af09aa852 The review round's four findings: claim ownership, decider changes, recordable answers, plain words c24af0248 crew: round-eight fixes from the Opus review 289d372b9 The tests the landing's answer carries: fate, hold-down, redraw, claim doors 7e38346a9 The take-down comes before every mint, and the tui3 walk reads the newest card dbe1be03e The review rounds' last word: the release owns its claim, and the docs say what the code does 4cc4945a7 The Opus review: a release owns its generation, and every failed round still says its word a5cce0dde The done card says who is deciding, so the clause is a duplication, not the fact 6bd276e6c Merge fix/1077-landing-reraise into santos/dev 271016938 Merge feat/crew-open-all-toggle into santos/dev b459399d3 Merge fix/question-answer-box-caret-keys into santos/dev a38492026 Merge codex/recovery-evidence into santos/dev dc22e6461 cmd/codeaf: the role-door tests read the shipped mastermind instead of a copy of it 39f528f70 release: the pin-example check reads the guide the examples moved to 6439d3cb8 session, tui3: gofmt the three files santos/dev merged unformatted 854037967 tui3: a question's answer box kills to the end of the line on ctrl+k d93ec61ea session: the late-attach proposal test holds the work until the replay is read a81ead4de session: the landing-fate tests read today's clock instead of a fixed date 8fe4fff38 provider: the retry-avoid list rides the call's context into the knobs a266da3e2 provider: the retry-avoid veto narrows the wire through one field law 197d89334 exec: a node retry asks the router to avoid the lanes that failed it 2d02cc0b9 provider: the set-servable law's doc comment keeps its paragraph break e1d0e7524 exec: the retry test names its helper and spells its wall in time units e7d65a517 docs: the change note for the retry that avoids the failed upstream 11decbd92 Merge fix/provider-failover into santos/dev c099a51c2 Merge dev into santos/dev e47d92bde provider, exec: the lane a failure names is a wire fact provider reads (#1105) 396be97be Merge dev into santos/dev 3b97f50c3 Merge dev into santos/dev ea3b5c9a0 telemetry: the send path drops events that cannot name their version 573b288e4 cmd/codeaf: keep smoke runs from sending usage counts e8d627c6b docs: the change note for the usage-count audit fixes 11bec8d80 chat: a hosted session's usage counts are read off the events it receives b9d75b39e chat: the hosted-count tee is tested against the tally it must produce 39fa08d3d telemetry: stamp a version in the lifecycle tests 681e2adf1 chat: count the hosted boot agent once, and conversations opened beside 825ef2c41 connect, settings: named, multiple, switchable custom connections (#1092) f4d3f9a1a Merge branch 'dev' into fix/telemetry-audit 76fec37e3 ci: the Model Pool mirror workflow and the signature check it runs 2fb296bf1 crew: Pareto crewing, the learned picker and the Model Pool (#1093) a065ebb1b Merge remote-tracking branch 'origin/dev' into santos/dev 80ac250a0 Merge remote-tracking branch 'origin/feat/pool-mirror-workflow' into santos/dev 6092393ab Merge remote-tracking branch 'origin/fix/telemetry-audit' into santos/dev ef808dda4 session: a decision card under --yolo takes its default (#1117) 70a9e1f6e session: a task's thread stops its parts before it closes, and a machinery cut says so (#1118) c4df8f364 crew: an auto seat's rung names the pick that ran, once, for both doors (#1119) a9fcf9bc8 crew: a bare auto row under picked from = catalog reads the catalog's figures alone (#1120) 13be0dce4 relay: accept the exec and run doors, not only task and do (#1121) 90f29aec1 changes: name the #1120 note by its PR and stamp the pr field 0b77926ca do: seat from the settings the run will use (#1122) 32148d87d changes: stamp pr: 1120 on the #1120 note 113ed628e pool: a launch's start-up errands stop when the process closes (#1124) 6c98ddca3 verify, codeaf: a launch compiles fewer patterns and a surface holds fewer Ps (#1125) 50fe6a3e4 pool: the min_installs floor counts installs on the client (#1127) 076cde8ae relay: fold a resubmitted row once by its nonce (#1128) 8339ae71b relay: carry the acceptable metric beside role_quality (#1129) 0367c441b outbox: a refused row leaves the outbox and the rest of its batch is sent (#1130) 09aecd2f1 relay: the index holds only the cells that meet the min_installs floor (#1131) ae7c05dcd outbox: a Send compacts the file to the rows it still holds (#1133) 9e24f72d8 pool: the errand tracker's critical sections unlock from a defer (#1134) 358a569aa relay: fold a submit batch with one read and one write per distinct cell and day (#1136) a1d2eb1f7 pool: judge landed-but-unjudged runs on the next chat start (#1132) 3c6722859 verify: take the finished-tree check at a project cloned into a subdirectory (#1135) 263625b63 exec: a headless run leaves a pending judge record at its tail (#1137) 8be373d39 run: the headless task door leaves a pending judge record (#1138) a336b246e do: leave a pending landing for the Model Pool's judge (#1139) 8820a5659 cmd/codeaf: one readPendingRows test helper, not two (#1140) 49760fb45 pool: show and verify print both metrics the index carries (#1141) 6eaeb25c9 pool: status shows the pending judge records and the last sweep (#1142) 7c00b5f2c codeaf: a test's crash fixture stays in a profile the test owns (#1145) fc2eb357a manual: the status row's ledger is the tree's, not the conversation's (#1143) a06b53a91 test: pin the delivery gate over a grown job root (#1147) 7231347eb resident: the wall refuses a first replan round it cannot hold (#1144) 5e257557f cmd/codeaf: the package's test floor clears a profile the environment named 4dd1c562f Revert "cmd/codeaf: the package's test floor clears a profile the environment named" 9075f863c pool: the seed index is regenerated from the relay by seedgen (#1148) a5c44f3d2 pool: show --cells lists the held index's cells with installs and dims (#1149) 825996739 cmd/codeaf: the package test floor clears a profile the environment named (#1150) 81568aa82 session: report gitignored build products a run leaves, without gating on them (#1152) da4910593 internal/tui3: the opening hint test owns its profile and pins both doors 72de3bca7 docs/changes: #1151 — the opening hint test owns its profile 499083d1c Revert "docs/changes: #1151 — the opening hint test owns its profile" beb31ecf7 Revert "internal/tui3: the opening hint test owns its profile and pins both doors" 25b8b7dea version: an unstamped binary names the condition, not a Makefile target (#1151) c833b423a pool: a dropped row keeps the relay's reason and status shows it (#1154) cbb7d6d9e processgroup: verify a job group's identity before signalling it (#1155) efe498ed0 internal/tui3: the opening hint test owns its profile and pins both doors (#1153) 17b3b0e40 do: a continuation continues the plan it has instead of re-planning (#1156) 22cf3ce17 changes: correct the #1155 and #1156 notes' pr fields (#1157) 8e85b0fe7 cmd/codeaf: a surface runs under a soft memory limit drawn from the machine (#1158) ef1e92756 task: status row accounting (#1159) 44f572e26 pool: status's index line says what this run's fetch did to the cache (#1161) 3600fc9a5 pool: the pool's copy of a seat's id is the bare vendor/id (#1163) a2ebd1397 cmd/codeaf: no test can push pool rows to the default relay (#1164) 4fb7f0546 docs: the road a chat door bash push took under --yolo (#1160) 69219a1e1 cmd/codeaf: the soft memory limit is the smallest real bound, not the host s memory (#1162) 2b155b4b4 relay: bound judge severity, clamp adjusted scores, publish mean and sd on one scale (#1165) 2fb55324a relay: purge fixture vendors and refuse them at the door (#1167) 57504187a exec: a job's shell cannot reach the tmux server hosting codeaf (#1166) eb7b46559 internal/session: an unattended run may not move a branch it did not create (#1168) 80f770639 pool: status says the identity, the judged total and that nothing has landed (#1169) 1cb7333fa exec: an exec run's worker spend reaches the usage ledger (#1170) 96bb9402e changes: the #1169 note's title parses and reads as one line (#1172) 627236d95 config: the task door honors a stored crew word, and a pin survives the learned pick (#1171) 593092be6 internal/tui3: a test may not read a profile it did not create (#1173) ad8be1ed7 session: a landing card under --yolo takes its default instead of parking the run (#1174) 03d49a678 gitignore: the .furrow ids the engine writes into a workspace (#1175) 46edbdd32 internal/tui3: gofmt the profile-law helpers from #1173 (#1176) 3179e3611 relay: ALLOWED_VENDORS names the catalog's vendors and codeaf (#1177) 397a145a6 docs: why a settled /task leaves commits on task/<slug>, not the worktree branch (#1178) 915ff3a76 pool: regenerate the seed index from the relay's first clean publish (#1180) d9b42430f cmd/codeaf: the model warm stops when the process closes (#1179) bd2dcf334 session: the auditor's calls say they are the auditor's and which node they check (#1181) c6ed47c5a task: json unverified branch (#1182) 360352ad4 session: a settle turn runs under the checker's own bound, not the run's wall (#1183) 814e1af0e session: the tasks text and record line name a failed/unverified node's kept branch and verdict (#1184) 7c25a26d9 internal/session: the identical-effects test stops riding the turn guard (#1185) 6e7d0ed44 cmd/codeaf: exec and run envelopes name a non-verified node's kept branch and verdict (#1186) f4079d716 session: bound a worker's park on a promoted command (#1187) 994689fe7 docs: the model pool runbook names the Workers Paid plan and what it costs (#1188) c315be13e docs: the runbook's plan line says metered, not capped, and nothing more (#1189) 2e80c60d8 internal/tui3: the status word says working while a task subtree turns (#1191) 99363ca8f session: bound a job subtree's CPU and process count (#1190) 6db5125b2 docs: what the audit check seat can execute, and where that boundary is set (#1192) 649bb4bfd cmd/codeaf: the pool start-up test empties the CI word it is not about 053e6a90f processgroup: a zombie leader does not keep a job's group alive b4e66f515 docs/changes: the entry for #1194 Co-Authored-By: Santosh kumar <[email protected]> Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci: Model Pool mirror workflow (signed index onto the model-pool branch) (#1116) * ci: the Model Pool mirror workflow and the signature check it runs Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs: the change entry for the Model Pool mirror workflow --------- Co-authored-by: Claude Fable 5.1 <[email protected]> Co-authored-by: santoshkumarradha <[email protected]> * chat: the unconnected custom row on /connect keeps its vendored name (#1195) modelConnectionStatus renamed every custom-id row to the connection's Written word, and the catalog template's Written is the bare id, so a person who had connected nothing yet saw `custom` where the manual, the README and #1107 say `Custom OpenAI-compatible API`. Only a connected instance is called what the person called it now; the contract test draws both rows and is red without the fix. Found verifying the staging build of #1194. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: v0.3.0 rolled up into CHANGELOG.md (#1196) 95 loose entries — the crew family and Pareto crewing, the Model Pool and its relay, custom connections, the telemetry audit, the update door's download clock, and the fixes that followed — become the `## v0.3.0` section the stable release reads its notes from. Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci: a changed directory in a nested modu…
AbirAbbas
added a commit
that referenced
this pull request
Oct 28, 2026
…t saving (#2009) * Honor chat-requested models for task workers and new conversations (#1935) Preserve explicit worker model selection for joined tasks and descendants. Add model-selected independent and teammate chats, atomic launch claims, updated tool guidance, and regression coverage. Validated all five live OpenRouter launch scenarios with DeepSeek v4.1 Flash and Kimi K3. All PR checks passed. Refs #1839. * Answer and verify the four skill capability questions (#1938) Add direct skill definition, capability and troubleshooting answers, including memory-independent behavior. Pin the four original queries and verify tool-result delivery through real hosted terminal chats with memory saving on and off. All eight live cases passed using exact deepseek/deepseek-v4.1-flash. All PR checks passed. Closes #1937. * Make All show the current conversation tabs (#1939) * Scope All to the current conversation tab strip * Keep screenshot launches guarded and use rendered count grammar * Guard the screenshot terminal option command * Expect current-tab scope in the All navigation hint * Scope the All wall's copy and empty state to open tabs The in-wall All chip now says the grid holds the tabs in this view rather than saved conversations, and an empty scoped grid says no conversations are open here instead of promising saved ones will appear. wallEmptyText is shared by the live grid and the currently unused wallEmptyRow, so the two stay in step. The dock test now proves the label and the mark each open the wall scoped to open tabs, and the render test pins the scoped empty line. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> * Make open tabs a property of the open wall closeWall now clears wall.openTabs with wall.on, so no reader after a close can mistake a closed scoped wall for an open one. wallOpen still reads the scope before it closes the wall, which is where the team is kept. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> * Open an overview unpicked A pick outlived a close that was not esc, so a card picked in the wall left the next overview in selection mode - and in the All scope it could be a card nobody had picked there. Every overview now opens with nothing picked. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> * Name the All door as the tabs in view in the manual places.md still promised all saved conversations from ▦ All, and keys.md listed alt+v, /wall and the strip door as one thing. They are two views since this change. Assisted-by: CodeAF (deepseek-v4.1-flash-1017) Co-Authored-By: CodeAF <[email protected]> --------- Co-authored-by: CodeAF <[email protected]> * Run the same program when connecting to another machine (#1940) * tui3, config: the gate row lands on the conversation you are in, and /status names the gate (#1063) The three Safety rows — `ask before running`, the tool exceptions and the shell command rules — wrote the profile and left the running gate alone, while the YOLO badge read that profile live. So the badge and the gate disagreed in both directions: `allow` lit the badge over a gate that kept asking, and `prompt` put it out over a gate that was still wide open, which is the false-safety claim #322 and #325 exist to prevent. `applySetting` now pushes those three rows through `ApplyApprovals` — the seam /permissions already drops a banked rule through — and refreshes the badge on the same keystroke, on the branch where the push landed. A rebuild that could not be done is said out loud on the panel's foot line in /permissions' own words, `saved · from the next session`, and the badge stays with the gate in force rather than following the file. And /status carries the posture it never carried: `approvals prompt`, `allow` or `deny`, in the `ask before running` row's own words, in the note, in --json and on the phone's status sheet, from the one posture reading the badge uses. The status row keeps its negative-space badge and the segment is no longer written into the page twice. The note's label column is measured from the widest label it carries, so three crew assertions that spelled `"\ncrew"` plus exactly five spaces were assertions about every other row on the page. They read the crew line by its label now. Co-authored-by: Claude Fable 5.1 <[email protected]> * attribution: the footer names CodeAF, and still links to agentfield.ai (#1075) * attribution: the footer names CodeAF, and still links to agentfield.ai The pull request, issue and comment footers read "Drafted with agentfield ai". They now read "Drafted with CodeAF"; the link, its utm parameters and the "reviewed and owned by the author" tail are unchanged. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy * docs: the change entry for the CodeAF footer Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy --------- Co-authored-by: Claude Opus 5 <[email protected]> * session, prompt, tui3: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1) (#1067) * session, prompt: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1) The end-of-turn reader saw 600 bytes of the last message, so a long table read as cut off; the carry-on told the model to explain itself, and the explanation became the only answer left in view under the fold. The reader now sees up to 8 KB of the last words, and a cut it still has to make ends with `[clipped by codeaf: N of M bytes]`, which its ask says is never evidence of a cut-off answer. The carry-on lead says who wrote the note and offers one exact reply, `[no change]`: it ends carrying on for the ask without another reading, the transcript keeps it for the model, and neither replay nor the live feed draws it — so the settled answer before it is what the fold finds. system.md trades `# Tone` and `# Delivery` for `# The answer`, `# Words or work`, `# When corrected` and `# Messages from codeaf`, and the two lines that pushed action on questions are qualified. The lean page keeps all four; the prefix waivers rise by the 1,705 bytes that cost, dated and named. Co-Authored-By: Claude Opus 5 <[email protected]> * docs: the change entry takes the pull request's own number Co-Authored-By: Claude Opus 5 <[email protected]> * session: a quiet landing is asked about the notes it was handed, not the page that names them The worker's system page now names [silent] and [stuck] under `# Messages from codeaf`, so a search of the whole request found the tag on the page. childWasHanded reads the conversation and leaves the page out. Co-Authored-By: Claude Opus 5 <[email protected]> * session, prompt: [no change] answers the note it follows, and a long ask leaves the last words cut by name (#1065 round 1) Self-review of #1067 found the token honoured on the wrong replies. The gate read the turn's carry-on count, so a model that made calls in answer to the note and then said [no change] ended the turn unread on an answer those calls may have made stale, and a token after a load or ask nudge counted too. It now reads the transcript: the token must be the very next reply to the reader's own lead (answeredNoteUnchanged), and the dead declined flag is gone. An ask up to the whole digest rides in its head, so eight kilobytes of last words behind a long ask reached the backstop clip and ended in a bare `…`. checkpointClipSaid takes the room that is left. A turn that ends on the token no longer starts the route judge or hands the token to the memory reflex, and a later reading's last words skip it. `# Messages from codeaf` named `[still asked]`, which only the reader ever receives, and left out `[stuck]` and `[held]`, which the conversation does; it now names the tags that arrive and says any other opening, such as `[image #1]`, is the person's. The waivers sit on the measured 1,779 bytes, and the manual and the change entry say what the gate actually honours. Co-Authored-By: Claude Opus 5 <[email protected]> * prompt: the new sections keep the rules the old two carried, and say what stays in view (#1065 round 1) Owner review of #1067. Replacing `# Tone` and `# Delivery` had dropped five rules; each is back inside the new sections: match the person's task and vocabulary, choose the safe option under uncertainty, evidence and blocking details complete, never a compiling scaffold or a narrowed test, never half-solved work. `# The answer` said only the last message reaches the person, which `# Planning` contradicts: the plan and the per-batch sentence are shown while a turn runs. It now says that once a turn ends only its last message stays in view. `# Words or work` sat beside `## Work or words`, which spends the same two words on hand-off sizing; it is `# Answer or change`, and it says an answer asked for in chat is written in the reply, not a file, unless the person asks for one. The waivers sit on the new measurement, 2,035 bytes over dev on both arms. Co-Authored-By: Claude Opus 5 <[email protected]> * prompt: the messages-from-codeaf list names every note codeaf writes The system prompt now distinguishes conversation user-role notes from private reader and tool-result markers. A structural test follows the production constants, and the byte waivers record the exact fixed and lean measurements. Co-Authored-By: Claude Fable 5.1 <[email protected]> * session: no change skips judgment only when it answers the reader The completion loop now uses the transcript context before suppressing the route judge or substituting the prior answer for memory. Tests cover the withdrawn and ordinary-token memory paths, and the manual separates counting from display. Co-Authored-By: Claude Fable 5.1 <[email protected]> * test: the model stub replays the no-change fold The OpenAI-compatible stub can now drive the real binary through eleven read rounds, a long table, a rejecting reader, and the exact token reply. The recipe records the tmux setup and the expected log and screen evidence. Co-Authored-By: Claude Fable 5.1 <[email protected]> * prompt: the standing-news tag stays with the event, not on the page The messages-from-codeaf list had named [something you set up fired], and TestTheSteeringLineReadsAsNewsAndNotAsARequest refused it: that line says under the news itself what it is and what not to do about it, so the page's copy is one law bought on every request of every turn for a turn most sessions never have. The list keeps the compaction tags, the structural test now files the standing frame with the private voices the page must not name, and the waivers fall to the new measurement (fixed 55,280, lean 47,055). Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Opus 5 <[email protected]> Co-authored-by: Abir Abbas <[email protected]> * Clean internal experiment records and revive test/ux for codeaf (#1068) * chore: clean internal experiment records and revive test/ux for codeaf Public-repository cleanup ahead of opening the docs to outside readers. - Remove four internal DOE/experiment record directories that reference private issue and pull-request numbers and are not current-product documentation: docs/design/pin-doe, plan-gate-doe, turn-wall-share-doe, and workspace-foundation. - Neutralize internal machine references in benchmark and design docs: the bench host ("spark"), its secrets.env path, and a personal working branch name, all now generic placeholders. - Revive test/ux/, the user-facing journey suite, for the renamed product: collapse the removed v1/v2 dual-surface logic into the single v3 surface, repoint the binary to cmd/codeaf and state to ~/.codeaf/CODEAF_*, and delete gate.sh whose premise was the v1-vs-v2 parity table. Co-Authored-By: codeaf <[email protected]> * chore: drop internal conversation-runtime wave records, keep the measurement contract The conversation-runtime directory was a local development wave: day-by-day status, /private/tmp paths, "owner authorized" process notes, draft PR numbers, and local change logs. None of it is current-product documentation a public reader needs. Keep PARETO.md, which is the benchmark's cited measurement contract, trimmed of its dangling calibration link and owner language. Co-Authored-By: codeaf <[email protected]> * chore: neutralize the remaining bench-host and work-machine references A wider sweep found the internal bench host ("Spark"), its secrets path (fleet/secrets.env), and a scratch ops directory still named in live docs, bench READMEs and bench scripts that the earlier redaction had not touched. Rename the host to "benchhost"/"the bench host", drop the secrets path, and replace "the laptop" as the owner's own machine with a neutral "work machine" where it meant the internal one. The manual's first-person "the laptop you are sitting at" is public-facing and left alone. Co-Authored-By: codeaf <[email protected]> * chore: fix review findings in the public-cleanup diff - PERF.md: drop a private tracker issue reference (#846). - PARETO.md: restore a complete sentence dropped when the calibration link was removed, and align tense with the remaining present-tense claim. - home-mission-control/DESIGN.md: drop the internal "fleet" system name and an owner-framing phrase, consistent with the secrets-path scrub elsewhere. Co-Authored-By: codeaf <[email protected]> * docs/design: the two records live code still cites stay in the tree plan-gate-doe/REPORT.md is the measured evidence behind the plan-gate default that twelve comments in cmd/codeaf, internal/config, internal/session and internal/splitgate point at, and turn-wall-share-doe is the run internal/session/turnwall.go says it was added from. CLAUDE.md keeps the record on purpose for exactly this reason, and neither folder names a machine, a secret or a private address. Co-Authored-By: Claude Fable 5.1 <[email protected]> * test/ux: the revived journey suite goes back out #991 removed it because it drove the v1 and v2 surfaces, and the revival did not change that: its journal verb reads graph.db tables (messages, nodes, usage, commands) that the v3 chat never writes, lib.sh still waits for the resident, nothing runs it from the Makefile or a workflow, and its only evidence was bash -n. docs/JOURNEY.md says internal/e2e is the one suite that drives the binary, and that stays true. A suite that has been run green against bin/codeaf can come back on its own pull request with a target that runs it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: entry for #1068 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: the #1068 entry says why the journey suite is out, correctly The first draft said the v3 chat never writes the graph.db tables the suite reads. Running the suite showed both message rows landing, so that sentence was wrong; what is true is that nothing runs the suite, it had never been run, and its first journey fails on its first screen check (#1072). Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: codeaf <[email protected]> Co-authored-by: Abir Abbas <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]> * session: task commits are authored by the bot account, not codeaf@localhost The author codeaf stamped on the commits it lands was `codeaf <codeaf@localhost>`, which no GitHub account can link, so every branch codeaf landed failed the CLA check. It now writes `codeaf <[email protected]>` - the identity the Co-Authored-By trailer already used - and keeps `codeaf@localhost` and `aforge@localhost` recognised as legacy by taskCommitIdentity. GLM 5.3's review caught an unmarked retired name in permissions.md that reddened the namelaw gate, and seven stale test fixtures; both fixed. Laws gate, session, exec and cmd are green. Co-Authored-By: codeaf <[email protected]> * README: show the pinned-version install line the release test requires (#1103) * README: show the pinned-version install line the release test requires The Install rewrite dropped the VERSION=<tag> pin example, and TestPublishedPinExamplesGiveVersionToBash reads every published install surface for `| VERSION=` because `VERSION=<tag> curl ...` is an easy mistake to copy — it sets the variable for curl instead of the install script. The pin goes back inline on the same host the section already uses, with the releases page still linked for tags. Co-Authored-By: codeaf <[email protected]> * docs: the change entry for the README pin example Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * telemetry: anonymous usage counts, off with CODEAF_TELEMETRY=off (#1095) * telemetry: the package behind anonymous usage counting, per the wire contract Four typed events (first_run, session_started, session_ended, fault) with the contract's allowlisted, bucketed props; the opt-out ladder with its off reasons; a hashed install identity at 0700/0600; a fault fingerprint over codeaf function names only; a JSON-lines spool with a deadline-bounded flush of up to 50 events per POST, 7-day age drops and a 1000-line cap; the notice constant with NoticeShown/MarkNoticeShown gating the flush; and Show() for a future codeaf telemetry show. A library only: nothing in cmd/codeaf, internal/session or internal/config reads it yet. Co-Authored-By: codeaf <[email protected]> * telemetry: tests, doc, and contract fixes for the usage-count package Adds the package's tests — bucketing edges, the opt-out ladder, install identity, the notice, fingerprinting, the spool against an httptest relay (cap, age drop, 50-per-POST splitting, a hard deadline against a hanging server, silence on failure) and the privacy law that no sentinel string reaches the wire. docs/TELEMETRY.md documents the notice, what is sent and how to turn it off, with a test that fails if the doc's property table drifts from the Go allowlist. Fixes four drifts from the wire contract found running those tests: the <1m duration bucket was missing, the fingerprint's argument stripping cut too much, the rc channel suffix must be a single integer, and under go test a config-off rung outranks the test rung in the reason reported. Co-Authored-By: codeaf <[email protected]> * telemetry: fix review defects 1-8 with tests Fingerprint stability (last-( cut, .go:/main./created-by frame rules), counted-batch flush, guarded and multi-process-safe spool, BucketDuration edges, Configure-gated write paths, O_EXCL install id, install.json preservation, buildinfo.Dirty accessor. Co-Authored-By: codeaf <[email protected]> * installer: record the install locally and print the telemetry notice Following the telemetry wire contract: a successful install now writes telemetry/install.json under the state root (dir 0700, file 0600) with the install method, channel and UTC timestamp, leaves any existing install_id alone, and makes no request the download steps did not already make. At the very end the installer prints the contract's notice to stderr after one blank line, or one line saying telemetry is off when CODEAF_TELEMETRY/DO_NOT_TRACK opts out. README gains a plain Telemetry section pointing at docs/TELEMETRY.md, and a new test/installer-telemetry.sh proves the marker, the notice and the opt-out without a network. Co-Authored-By: codeaf <[email protected]> * installer: four review corrections to the telemetry commit The review of 71ff95a6b found four things. Revert the one line that changed `if [[ -z "${TAG:-}" ]]; then` back to `${TAG:-}`: under set -u an unset TAG must reach api_problem, not crash. In write_install_marker the channel lands in JSON verbatim and CHANNEL can come from the environment, so only stable, rc, staging or dev is recorded and anything else becomes "unknown" — with test cases for a valid channel and for we"ird, both asserting the marker parses (python3 -m json.tool, or jq, else a strict channel check). Fence the README Telemetry notice in a ```text block so Markdown keeps its alignment, wording byte for byte. Clear the two shellcheck findings in test/installer-telemetry.sh with one block disable for SC2016/SC2034 — ok() evals those strings, so the quoting is deliberate — and export CODEAF_TELEMETRY/DO_NOT_TRACK where a subshell needs them, without weakening any assertion. Co-Authored-By: codeaf <[email protected]> * release-downloads: daily PostHog report of public GitHub release download counts A scheduled workflow reads the repository's releases from the public GitHub API and sends one codeaf:release_downloads event per binary asset (codeaf-<os>-<arch>[.exe]) — sidecars like checksums.txt are not reported. Each event carries release_tag, channel (stable or the prerelease suffix), prerelease, os, arch, the cumulative download_count and the UTC snapshot_date; the event id is derived from tag, asset and date, so a same-day rerun deduplicates instead of counting twice. No code in the binary; counts come only from public release data. Co-Authored-By: codeaf <[email protected]> * release-downloads: tests cover the workflow, docs and pagination The dry-run unittests now also assert the identifiers the wire spec spells out where the script and the workflow spell them: the schedule, dispatch, contents-read permissions, built-in token, project-key secret and host variable in .github/workflows/release-downloads.yml, the public-data paragraph in docs/rules/promotion.md, stdlib-only imports, pagination across API pages, the host override and the /batch/ endpoint, sidecar skipping, draft skipping, and the channel words stable, rc, dev and staging. Co-Authored-By: codeaf <[email protected]> * release-downloads: four review corrections to the script Channel detection now follows the tag grammar in cmd/codeaf-release/version.go: stable, rc, dev and staging tags match their four patterns and anything else reports "unknown", so dev-20260917-09299019bcdc is no longer mislabelled "stable". --dry-run works without CODEAF_POSTHOG_PROJECT_KEY, the local and test path: the batch prints with api_key "<unset>" and exits 0, and only the sending path stops with the notice. Every event carries "$geoip_disable": true next to "$process_person_profile": false, since the sender is a CI runner whose location is meaningless. The module docstring now names release-downloads.yml, the file that exists. The fixture holds one tag of each shape plus an unrecognised one, and the tests assert the channel for each, including the two real dev and staging tags. Co-Authored-By: codeaf <[email protected]> * release-downloads: test runs on Python 3.9 too ScriptSourceTest read sys.stdlib_module_names unguarded; the attribute arrived in Python 3.10, so the suite errored on macOS stock python3 (3.9.6). Guarded with getattr so the pinned import-set check stands on its own where the attribute is absent; 32 tests pass on 3.9, 3.10 and 3.14. Co-Authored-By: codeaf <[email protected]> * installer telemetry: the notice is read from docs/TELEMETRY.md The test read .telemetry-contract.md, a working note that is not in the repository and never will be, so it aborted with "telemetry contract is missing" in a clean checkout. It now extracts the fenced block under "## The notice" in docs/TELEMETRY.md — the same bytes as the Notice constant in internal/telemetry/notice.go — and holds the installer's TELEMETRY_NOTICE to it as before; a new assertion holds the README's telemetry fence to it too, byte for byte, so the invariant the suite existed to guard now has its third leg. Comments in scripts/install.sh, internal/telemetry/telemetry.go and this test now name docs/TELEMETRY.md as the source of the notice. No Go code changed and no Go tests were run. shellcheck is clean; the suite's 33 passing assertions include the two new notice checks, and the same two stat(1)-related assertions that already failed on this GNU machine before the change still fail with identical output. Co-Authored-By: codeaf <[email protected]> * release-downloads: the download-counts paragraph lives in docs/TELEMETRY.md docs/rules/promotion.md was the only home the paragraph had, and it came with a fallback pinning the branch to the state before the telemetry doc existed. The paragraph moves, byte for byte, into docs/TELEMETRY.md as a final "## Download counts" section beside the rest of the telemetry story, and promotion.md returns to its origin/dev content, so this branch now touches no release docs. test_no_telemetry_doc_on_this_branch asserted docs/TELEMETRY.md did not exist and is deleted. The two tests that read the paragraph read it where it now lives: both require a "## Download counts" section in docs/TELEMETRY.md naming .github/workflows/release-downloads.yml, scripts/release_downloads.py and the checksums.txt sidecar, and the promotion.md fallback is gone. No Go code changed and no Go tests were run; the property table in docs/TELEMETRY.md is untouched. The suite passes: 31 tests, OK. Co-Authored-By: codeaf <[email protected]> * installer telemetry: the mode check asks GNU stat first On Linux `stat -f` succeeds and reports the filesystem, so the BSD-first order never reached the GNU form and both permission checks failed. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01CqfhxLrrBNQxwPNvEgoBGR * telemetry: refuse to send to the production relay from a test binary Co-Authored-By: codeaf <[email protected]> * telemetry: testHome points the endpoint at a dead loopback Co-Authored-By: codeaf <[email protected]> * telemetry: the orphan-adoption test names the dead relay it now gets Co-Authored-By: codeaf <[email protected]> * telemetry: a test pins that no test binary reaches the production relay Co-Authored-By: codeaf <[email protected]> * telemetry: three tests pin the test-binary relay refusal Each asserts through real calls, not a re-implemented effect: the newRelay override lands after testHome and a flush reaches that recorder; a default-endpoint flush is refused like a dead relay (zero round trips, event still spooled); forcing the ladder on cannot reopen the path. Co-Authored-By: codeaf <[email protected]> * telemetry: allocation-free session counters Snapshot returns the SessionStats fields the SessionEnded constructor takes, so the wiring hands the struct straight over. Cost is stored as integer micro-dollars and divided back once, in Snapshot. Count* never ask whether telemetry is enabled and never allocate. Co-Authored-By: codeaf <[email protected]> * telemetry: gofmt the counters test One alignment fix in the many-goroutine test; no behaviour change. Co-Authored-By: codeaf <[email protected]> * config: telemetry setting row (CODEAF_TELEMETRY) Key "telemetry", on|off, default on, env pin CODEAF_TELEMETRY, in the interface group beside the history row. TelemetryAtIn walks the ladder — pin, project file, profile config, default — and TelemetryOffReason answers whether config turned the pipe off and names which rung did. The row sits in ProjectKeys so a repository may answer for itself. Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the telemetry command, and the setting it writes Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: session lifecycle events at execute (work in progress) Configure, notice, first_run and session_started at the start of every session command, session_ended and a one-second flush at its exit. The end event does not yet carry the session counters. * provider: count answered model calls and their spend at the log door * session: count sealed turns and executed tool calls * cmd/codeaf: the session-ended event carries the run's counters telemetryEnd builds the end event from telemetry.Snapshot so turns, model calls, tool calls and cost reach the spool; the stop-reason switch becomes a map over the exit ladder; and both lifecycle constructors are gated on the ladder being on so a disabled run touches no state. Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the counters test says the tally is process-wide The exact turn, call and cost assertions hold for one run of the package; a repeated run counts into the same atomic tally again. Co-Authored-By: codeaf <[email protected]> * guard: a fault hook Recover calls, for a reporter above this package Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: the fault event at both crash sites, off the current session Co-Authored-By: codeaf <[email protected]> * cmd/codeaf: a test that the crash site spools a fault and not the panic text Co-Authored-By: codeaf <[email protected]> * docs: finish the anonymous usage counts contract and add the 1095 changelog entry * guard: the fault hook sits behind an atomic pointer A goroutine recovering a panic read the hook while a test on another goroutine wrote it, which the race detector reported. SetOnFault stores it once; Recover loads it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: a task's interrupt word comes from the signal, not the cancelled context stopSignals cancels the signal context at the end of every errand, so the flag read from its Err said interrupted for a run that finished cleanly. Co-Authored-By: Claude Fable 5.1 <[email protected]> * tui3, installer: the telemetry row has a tab, and a custom install writes no marker TestEverySettingRowHasATab wants every registry row on the settings panel. TestCustomInstallOutsideTheStateRootDoesNotRunAdoption wants an install outside the state root to leave the login's state folders alone, so the install marker is written only when the root exists or the install is in it. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: the counters test zeroes the process tally first Other tests in the package drive model and tool calls through the doors that count, so the exact bands held only when the test ran alone. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: the version test drives the telemetry lifecycle, not execute execute starts writers under the state root that outlive the test, and the temp dir's cleanup raced them. The lifecycle alone is what the test pins. Co-Authored-By: Claude Fable 5.1 <[email protected]> * cmd/codeaf: gofmt the lifecycle test Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: codeaf <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]> * session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler (#1104) * session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler TestASuccessfulHandoffJournalsTheRungThatSuppliedTheBrief failed once in a full make check at line 118: the worker's brief was the running model's draft, not the mastermind's written handoff. THE CAUSE IS NOT A STOLEN STEP. Reproduced under load (60 CPU burners, the fixture run 1500 times), every request the fixture makes is either the turn's own or one the aside answers: the queue carries only `round`, `draft` and `writer`, and the errands beside the turn (title, namer, the mark's drawing) never touch it. What moves is the ROUND COUNT — 41 to 54 across the runs — because the mark's drawing is a sidecar (sidecar.go) whose landing the scheduler places, and the turn is never held for it. On a busy machine the turn runs its rounds off faster than the drawing's goroutine is scheduled onto a core, the gap between the net firing and the drawing landing outruns the slack the script carries ([checkpointSlack] says so in its own words), the writer's ask lands PAST the end of the 53-step script, the scripted completer answers it with its past-the-end line `(unscripted)` — which is not prose — the handoff rung reads degenerate, and the carry ladder falls onto the draft. That is #392 exactly, one mechanism along. WHAT FIXES IT IS WAITING FOR THE READING. checkpointAgent now puts a watch on the turn's own context ([watchReadings]) and the handover fixtures submit through [watchedContext], so [answerWhenQuiet] holds each request that carries the belt until every reading beside it has landed — the order a real turn has, where a real model spends seconds on a step and the small readings land inside it. The gap is then one boundary whatever else the machine is doing, and a script long enough to cross the mark is long enough. THE REGRESSION TEST holds the drawing back by a fixed wall time — standing in for a scheduler that does not reach it in time — and insists on the behaviour: the turn does not spend its script racing the drawing, and the writer's rung still supplies the brief. It fails without the watch (the turn makes 54 rounds) and passes with it. Co-Authored-By: codeaf <[email protected]> * docs: the change entry for the carry-ladder drawing race Co-Authored-By: codeaf <[email protected]> --------- Co-authored-by: codeaf <[email protected]> * docs: the install proxy is live, and the manual says so (#1076) * docs: the install proxy is live, and the manual says so `https://agentfield.ai/get/codeaf` has served `scripts/install.sh` since 2026-09-16, the day the repository went public, so the compiled-in manual and docs/GUIDE.md stop saying it is "not serving yet" and stop calling the repository private. The proxied line goes first, as the README already has it; the raw GitHub line is described as the script under it. Both pages say to pipe into `bash`, not `sh`, because the script uses pipefail and `[[ ]]` and dash rejects them, and both state the channel path (`/dev`, `/staging`, `/rc`) beside the flags. The manual also says what the proxy refuses: a script without its channel line exactly once, or a body that is not a shell script, is a 502 rather than the wrong thing. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: entry for #1076 Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> * A launch says a newer codeaf is out, and /update installs it and restarts (#1060) * The release-tag grammar has one home, and it is internal/update Stable, rc, dev and staging tags are parsed, compared and bumped in exactly one place now. cmd/codeaf-release keeps its next, kind and prune behaviour and its tests unchanged; it reads the grammar from the new package rather than owning a second copy of it. A second parser is the thing that drifts: the updater about to land needs the same answer to "is this tag newer than that one" that the release tool gives, and one source of truth is the only way they cannot disagree. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * internal/update: look up a release, check it, and replace this binary with it The launch check answers only for a build that could sensibly be behind: a stable tag, or a release candidate whose stable line has since been published. A dev or staging build, a make build sha and an unstamped binary make no request at all, because a channel build is deliberately newer than stable and a source build belongs to whoever built it. The answer is cached for a day beside the profile, and every failure is silence. The install downloads into the target's own directory, matches the sha256 from checksums.txt, and only then renames over the running file, so a refused download, a wrong checksum or an unwritable directory all leave the original binary exactly where it was and no temp file behind. The API token never leaves the API host: the redirect policy strips it the moment a redirect changes host, which Go does not do on its own for another port or a subdomain. Windows has no atomic rename over a running image, so it goes through ReplaceFileW and puts the original back when Windows reports the one failure that moves it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * tui3: a launch says a newer codeaf is out, and /update installs it The launch line is one dim transcript note carrying both versions, /update, and the curl line, because the command is what the owner asked to be given. It arrives as a command rather than an opening read, so the first frame is drawn before the network is asked anything and a slow or refused answer draws nothing. /update — /upgrade is the same road — names the tag, then the checksum and where it landed, then quits with a restart plan for the door to carry out. Already newest says so and stays. It refuses while a turn or a task is running, and once it has started, one shared guard keeps a second /update and a new turn from beginning underneath it: the restart must never quit on top of live work, and the home page's Enter roads go through the same guard chat's does. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * codeaf update, and the door that restarts the same conversation codeaf update --check prints one line and exits 0 when this release is newest, 3 when a newer one exists, 1 when it could not ask. Without --check it installs stable, or whatever --rc, --dev, --staging or --version names, choosing a channel build by its own publish time the way the installer does. A binary built from source refuses in place and names its path rather than replacing something a developer is holding. The restart happens here and not inside Bubble Tea: the surface fills a slot, the door reads it once the frame is down and the terminal is back, and then execs the new binary with this launch's own arguments plus --session pointing at this transcript, so the conversation reopens where it was. A linked-local engine is asked to retire only after the surface has detached, and it still refuses while another window or any live work is there. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * remote: a window that says it is leaving needs no torn-link grace The twenty-second grace exists for a terminal that was killed without saying so, and a deliberate detach is the opposite of that — the window has just announced its departure. Carrying the grace anyway left a cleanly closed window looking attached for two more call deadlines, which kept a freshly installed codeaf waiting behind the engine the old one had already left. It clears only once the last view is gone. Turns, tasks, unanswered questions, other views and live work all still refuse retirement, and a refusal still never stops the work it protects. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * The installer and the updater agree on what a channel tag looks like Two roads now install codeaf, and they were reading the same release list by different rules: the shell accepted any dev-* or staging-* tag and semver parts with leading zeroes, while internal/update accepts only the shapes the release workflow actually publishes. A malformed tag newer than a real one would send the two roads to different builds. One helper carries the grammar to both channel selection and the line that names the channel, and an unrecognised tag for the asked-for channel now says so instead of installing whatever came back. The publish-time ordering #1056 added is untouched: only which candidates are eligible changed. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * The manual stops saying that nothing self-updates That sentence was true until this change and is now the opposite of true, so it goes, and a section written in the words people bring takes its place: is there a newer version, how do I update codeaf, why does it say this every time I start. It states who gets the launch line and who never does, the opt-out variable, the day the cached answer lasts, the shell exit codes, and both refusals — a build made from source and an executable nobody may write. The command table names /update and /upgrade with the argument they take. Four probes reach the new section, and the remote page keeps its own asker's words in its heading so the new section cannot take that question from it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * docs/changes: the update check and /update, for #1060 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: --check answers for the channel it was asked about, and a newer build is never downgraded Two things a hand verification caught. --check parsed whatever tag it had selected with the stable grammar alone, so --dev, --staging and --rc could never be "newer" and every one of them answered "you are on the newest codeaf" and exited 0 while a newer build sat in the list. The check now keeps its channel all the way through the decision and always names the tag it is talking about: stable and rc compare as semver, a dev or staging tag is the same tag or a different one, and --version is equal or it is not. And a build AHEAD of the newest stable was quietly installed backwards — the check said newest, the install downgraded it. Both roads refuse now, naming both versions and the deliberate way past: this codeaf is v0.3.0, ahead of the newest stable v0.2.0 — pass --version v0.2.0 to install it anyway An explicit tag IS that deliberate road, so --version and /update <tag> still install exactly what was named. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: a failure names the asset and the status, and ends with the curl line A download that failed printed the whole fallback URL, which put the retired product name on somebody's screen. The line names the asset it asked for and the status it got back, and nothing about which repository answered — the compatibility attempts still happen, they are just not the person's business. Every terminal failure now ends with the curl line, which D9 asked for and the download path had been missing. The / list declared no argument for /update while the command page documented one, so the list gains the second row the way /cache has one. The manual section now says what the code does about a build ahead of the newest stable, where it had said such a build "gets nothing" — true of the launch line, and false of the install until this wave. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * update: the curl line is agentfield.ai/get/codeaf, the supported road The launch line, the refusals and the terminal door offered the raw GitHub address of scripts/install.sh. The repository is public and agentfield.ai/get/codeaf has served that script from main since 2026-09-16, so the constant now spells the supported line, the manual's update section names it in place of "the curl line below", and the change entry says so. Asked for on the pull request by Santosh. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]> * a run's own scratch note no longer fails the delivery gate (#1096) * codeaf: a run's own note is offered outside the tree it is judged in leafOutputHint invited a job root to write its overflow document at a workspace-relative name (exec.SuggestPathFor's task-2-x1-toolchain-checks.md shape), so a run working in a person's directory left that note untracked at the root of their working tree. The delivery gate holds the run's record — the artifact registry, joined by jobArtifacts, plus the delivered tree — against the request, read the note as part of what was handed over, and refused the delivery; the run then ended incomplete over complete work. There is no separate mechanical clean-tree check to exempt, so the fix is the writer's: where the workspace belongs to the person, the root's file invitation points at the run's scratch beside its recorders and job logs, and the message keeps carrying the deliverable. * codeaf: a person's own file still joins the record the gate reads TestARootLeafInAPersonsDirectoryPutsItsNotesInScratch baselined the tree after the note was already written, so its landing read could never have observed anything: the record stayed empty because of the ordering, not because of the address. The baseline now happens first, in the executor's own order, and a hint that ever points into the tree again fails on the observation, not only on the spelling. And the other half of the law is pinned down: a file that turns up in the person's tree while the leaf runs — even at a name wearing the run's own task-<n>-<slug>.md shape — is still observed and still lands in the record the delivery gate is held to. Only the run's own note moved; nothing else about what the tree says was loosened. Co-Authored-By: codeaf <[email protected]> * docs: the change note for the root's overflow file moving out of the tree --------- Co-authored-by: codeaf <[email protected]> * chat: the wordmark is drawn CodeAF, four rows, capitals on the d's cap line (#1106) `┌─┐ / ├─┤ / └─┘` is not an `a`, it is two closed bowls of equal size stacked on each other, and two equal closed bowls are an `8`. That was the second-to-last glyph of the product's own name on the first screen of every fresh install. The owner's answer is to have the two letters at the end stand up out of the word as capitals: `A` is an apex, a crossbar and two legs; `F` is the one letter whose case is told by its arms alone, so its top arm is the longer one. A CAPITAL'S TOP IS THE ASCENDER'S TOP, and three rows of box-drawing could not give it that. A bar is drawn at the middle of its cell and a stem reaches the cell's edge, so the `d` stood half a row over any capital however it was formed — and a stem in the bottom row hung half a row under the bowls' baseline the same way. The fourth row is that half a row made whole: the x-height letters live in the lower three and leave the top row blank; the capitals and the `d`'s ascender start in the top row, the ascender as the half-stroke `╷` so its top is the capitals' bar; and every stem that ends on the baseline ends as `╵`, which stops where `└─┘` does. A `│` in the bottom row is a descender and nothing else. THE WORD IS SPELLED `codeaf` AND DRAWN `CodeAF`. `product` is unchanged, the glyph table is still keyed by its lowercase letters, and a terminal that cannot draw boxes still gets the word `codeaf`. The greeting's centring test read the unit's left edge off the wordmark's first row, which is now the cap line the x-height letters leave blank; it reads the second row. The one manual page that draws the wordmark had been drawing the retired `aforge` letterforms under the label "the codeaf wordmark" — the name law walks text and cannot see a name spelled in box-drawing — and draws the shipped forms now. Co-authored-by: Claude Fable 5.1 <[email protected]> * A real release download finishes: the 3-second check clock no longer covers the asset (#1110) * update: a release download gets its own clock, not the check's The release client was built as http.Client{Timeout: 3s} because three seconds is right for the launch check, and Go's Client.Timeout covers the WHOLE exchange including every body byte. The same client then downloaded the release asset, so a 57 MB download on a healthy link — about eight seconds here — was cut off three seconds in and a person read `read the release response: context deadline exceeded`. Client.Timeout is gone. The transport bounds only what has made no progress: a 5 s dial, a 5 s TLS handshake, a 10 s wait for response headers. Every call now takes its deadline from its context. The launch notice and `--check` share the one CheckTimeout constant, install metadata gets 10 s per request, and the asset gets a 30-second no-byte stall window plus a 15-minute ceiling — a dead link fails with a sentence, a slow one finishes. No Go deadline text reaches a person. A stall says `downloading codeaf-linux-amd64 stalled — no bytes for 30 s`, the ceiling says `took longer than 15 minutes`, and late headers say `did not start arriving within 10 s`. The stall window, the ceiling and the metadata window are injectable, so the tests prove the clocks with real slow, silent and trickling servers rather than waiting out the shipped ones. Co-Authored-By: Claude Fable 5.1 <[email protected]> * update: both doors read the one check constant `codeaf update` and the chat surface each spelled `3*time.Second` when they built the release client, which is how a figure meant for one small request came to govern an install. Both now pass update.CheckTimeout, and the three seconds live in exactly one place. The terminal's `--check` takes the client's Check, which is the whole-exchange check deadline; a plain `codeaf update` keeps Select, so the metadata requests an install makes get the ten seconds they need. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: the update download's own clock (#1110) Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> * docs/changes: v0.2.1 rolled up into CHANGELOG.md (#1113) The 33 entries that landed on dev since the v0.2.0 roll-up are the ## v0.2.1 section of CHANGELOG.md now; docs/changes/unreleased/ keeps only this pull request's own entry. Stable v0.2.1 takes its notes from that section. Co-Authored-By: Claude Fable 5.1 <[email protected]> * README: telemetry folds under Docs (#1115) The 23-line Telemetry section between Install and the product tour becomes one collapsed block at the end of Docs: a plain summary line, the notice verbatim, one sentence and the link to docs/TELEMETRY.md. The notice block is unchanged, so it still matches the binary, the installer and docs/TELEMETRY.md. Replaces #1112. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01NoeSVN8nqmwNVRXExgoUNN * santos/dev lands on dev: crew family, Pareto crewing, the Model Pool, custom connections (#1194) Lands the integration branch santos/dev (#1108, head 6db5125b2, 152 commits over dev) on dev as one squash, plus two fixes that kept its touched-packages job red: the pool start-up test empties the CI word it is not about (649bb4bfd), and a zombie leader no longer keeps a job's process group alive on Linux, which had delayed every finished background job by the two-second termination grace since #1155 (053e6a90f). #1108 carries the full account; the 94 change entries under docs/changes/unreleased/ are the per-change record. The commits folded in, oldest first: 5c413ef7b Keep archived evidence readable while tasks finish 3705a0cfe Document recovery evidence retrieval change 7834cc7d0 tui3, manual: a question's own answer box answers to the whole caret vocabulary 99d66e98c task: Add open/all source toggle to crew config c1e4cb57f docs: the change entry for the crew model family toggle e63b43927 crew: round-1 review fixes 304a5a223 crew: round-2 review fix 65477c0c5 task: Add open/all selector to /crew TUI picker 9189ef764 Merge branch 'task/add-open-all-selector-to-crew-tu-9e8cc0' into feat/crew-open-all-toggle ba9c12d61 crew: reconcile the picker onto config's source helpers ea05bc0f5 crew: a chosen family counts as an answered crew e1a037e0a Merge remote-tracking branch 'origin/dev' into feat/crew-open-all-toggle f6c2924a7 crew: the open table takes the picks we landed on eff44b542 docs: the change entry carries the open-table move and the family gate 71ce9bec0 crew: the family reaches the fallback, the settings skin, and the row's line b8ca5f513 crew: round-1 review fixes on the new table 8db5708ef crew: round-2 review fixes ce9759ef4 crew: round-3 review fixes 956e1f514 crew: round-4 review fixes 37ad27f4e crew: fixes from the first Opus round 6ffb95813 crew: the fixes from the Opus confirmation round 72b027436 crew: the comments the atomic write left behind 70d0e205b crew: round-four fixes from the Opus review 234300e37 Merge branch 'dev' into feat/crew-open-all-toggle 894292f28 crew: round-five fixes from the Opus review fba77f92d An answered landing carries its answer's fate when it is raised again, and an in-flight settle holds the question down (#1077) e69445058 Changelog entry for the landing-answer fate (#1086) 64ab7eae0 crew: round-six fixes from the Opus review 070016419 crew: round-seven fixes from the Opus review af09aa852 The review round's four findings: claim ownership, decider changes, recordable answers, plain words c24af0248 crew: round-eight fixes from the Opus review 289d372b9 The tests the landing's answer carries: fate, hold-down, redraw, claim doors 7e38346a9 The take-down comes before every mint, and the tui3 walk reads the newest card dbe1be03e The review rounds' last word: the release owns its claim, and the docs say what the code does 4cc4945a7 The Opus review: a release owns its generation, and every failed round still says its word a5cce0dde The done card says who is deciding, so the clause is a duplication, not the fact 6bd276e6c Merge fix/1077-landing-reraise into santos/dev 271016938 Merge feat/crew-open-all-toggle into santos/dev b459399d3 Merge fix/question-answer-box-caret-keys into santos/dev a38492026 Merge codex/recovery-evidence into santos/dev dc22e6461 cmd/codeaf: the role-door tests read the shipped mastermind instead of a copy of it 39f528f70 release: the pin-example check reads the guide the examples moved to 6439d3cb8 session, tui3: gofmt the three files santos/dev merged unformatted 854037967 tui3: a question's answer box kills to the end of the line on ctrl+k d93ec61ea session: the late-attach proposal test holds the work until the replay is read a81ead4de session: the landing-fate tests read today's clock instead of a fixed date 8fe4fff38 provider: the retry-avoid list rides the call's context into the knobs a266da3e2 provider: the retry-avoid veto narrows the wire through one field law 197d89334 exec: a node retry asks the router to avoid the lanes that failed it 2d02cc0b9 provider: the set-servable law's doc comment keeps its paragraph break e1d0e7524 exec: the retry test names its helper and spells its wall in time units e7d65a517 docs: the change note for the retry that avoids the failed upstream 11decbd92 Merge fix/provider-failover into santos/dev c099a51c2 Merge dev into santos/dev e47d92bde provider, exec: the lane a failure names is a wire fact provider reads (#1105) 396be97be Merge dev into santos/dev 3b97f50c3 Merge dev into santos/dev ea3b5c9a0 telemetry: the send path drops events that cannot name their version 573b288e4 cmd/codeaf: keep smoke runs from sending usage counts e8d627c6b docs: the change note for the usage-count audit fixes 11bec8d80 chat: a hosted session's usage counts are read off the events it receives b9d75b39e chat: the hosted-count tee is tested against the tally it must produce 39fa08d3d telemetry: stamp a version in the lifecycle tests 681e2adf1 chat: count the hosted boot agent once, and conversations opened beside 825ef2c41 connect, settings: named, multiple, switchable custom connections (#1092) f4d3f9a1a Merge branch 'dev' into fix/telemetry-audit 76fec37e3 ci: the Model Pool mirror workflow and the signature check it runs 2fb296bf1 crew: Pareto crewing, the learned picker and the Model Pool (#1093) a065ebb1b Merge remote-tracking branch 'origin/dev' into santos/dev 80ac250a0 Merge remote-tracking branch 'origin/feat/pool-mirror-workflow' into santos/dev 6092393ab Merge remote-tracking branch 'origin/fix/telemetry-audit' into santos/dev ef808dda4 session: a decision card under --yolo takes its default (#1117) 70a9e1f6e session: a task's thread stops its parts before it closes, and a machinery cut says so (#1118) c4df8f364 crew: an auto seat's rung names the pick that ran, once, for both doors (#1119) a9fcf9bc8 crew: a bare auto row under picked from = catalog reads the catalog's figures alone (#1120) 13be0dce4 relay: accept the exec and run doors, not only task and do (#1121) 90f29aec1 changes: name the #1120 note by its PR and stamp the pr field 0b77926ca do: seat from the settings the run will use (#1122) 32148d87d changes: stamp pr: 1120 on the #1120 note 113ed628e pool: a launch's start-up errands stop when the process closes (#1124) 6c98ddca3 verify, codeaf: a launch compiles fewer patterns and a surface holds fewer Ps (#1125) 50fe6a3e4 pool: the min_installs floor counts installs on the client (#1127) 076cde8ae relay: fold a resubmitted row once by its nonce (#1128) 8339ae71b relay: carry the acceptable metric beside role_quality (#1129) 0367c441b outbox: a refused row leaves the outbox and the rest of its batch is sent (#1130) 09aecd2f1 relay: the index holds only the cells that meet the min_installs floor (#1131) ae7c05dcd outbox: a Send compacts the file to the rows it still holds (#1133) 9e24f72d8 pool: the errand tracker's critical sections unlock from a defer (#1134) 358a569aa relay: fold a submit batch with one read and one write per distinct cell and day (#1136) a1d2eb1f7 pool: judge landed-but-unjudged runs on the next chat start (#1132) 3c6722859 verify: take the finished-tree check at a project cloned into a subdirectory (#1135) 263625b63 exec: a headless run leaves a pending judge record at its tail (#1137) 8be373d39 run: the headless task door leaves a pending judge record (#1138) a336b246e do: leave a pending landing for the Model Pool's judge (#1139) 8820a5659 cmd/codeaf: one readPendingRows test helper, not two (#1140) 49760fb45 pool: show and verify print both metrics the index carries (#1141) 6eaeb25c9 pool: status shows the pending judge records and the last sweep (#1142) 7c00b5f2c codeaf: a test's crash fixture stays in a profile the test owns (#1145) fc2eb357a manual: the status row's ledger is the tree's, not the conversation's (#1143) a06b53a91 test: pin the delivery gate over a grown job root (#1147) 7231347eb resident: the wall refuses a first replan round it cannot hold (#1144) 5e257557f cmd/codeaf: the package's test floor clears a profile the environment named 4dd1c562f Revert "cmd/codeaf: the package's test floor clears a profile the environment named" 9075f863c pool: the seed index is regenerated from the relay by seedgen (#1148) a5c44f3d2 pool: show --cells lists the held index's cells with installs and dims (#1149) 825996739 cmd/codeaf: the package test floor clears a profile the environment named (#1150) 81568aa82 session: report gitignored build products a run leaves, without gating on them (#1152) da4910593 internal/tui3: the opening hint test owns its profile and pins both doors 72de3bca7 docs/changes: #1151 — the opening hint test owns its profile 499083d1c Revert "docs/changes: #1151 — the opening hint test owns its profile" beb31ecf7 Revert "internal/tui3: the opening hint test owns its profile and pins both doors" 25b8b7dea version: an unstamped binary names the condition, not a Makefile target (#1151) c833b423a pool: a dropped row keeps the relay's reason and status shows it (#1154) cbb7d6d9e processgroup: verify a job group's identity before signalling it (#1155) efe498ed0 internal/tui3: the opening hint test owns its profile and pins both doors (#1153) 17b3b0e40 do: a continuation continues the plan it has instead of re-planning (#1156) 22cf3ce17 changes: correct the #1155 and #1156 notes' pr fields (#1157) 8e85b0fe7 cmd/codeaf: a surface runs under a soft memory limit drawn from the machine (#1158) ef1e92756 task: status row accounting (#1159) 44f572e26 pool: status's index line says what this run's fetch did to the cache (#1161) 3600fc9a5 pool: the pool's copy of a seat's id is the bare vendor/id (#1163) a2ebd1397 cmd/codeaf: no test can push pool rows to the default relay (#1164) 4fb7f0546 docs: the road a chat door bash push took under --yolo (#1160) 69219a1e1 cmd/codeaf: the soft memory limit is the smallest real bound, not the host s memory (#1162) 2b155b4b4 relay: bound judge severity, clamp adjusted scores, publish mean and sd on one scale (#1165) 2fb55324a relay: purge fixture vendors and refuse them at the door (#1167) 57504187a exec: a job's shell cannot reach the tmux server hosting codeaf (#1166) eb7b46559 internal/session: an unattended run may not move a branch it did not create (#1168) 80f770639 pool: status says the identity, the judged total and that nothing has landed (#1169) 1cb7333fa exec: an exec run's worker spend reaches the usage ledger (#1170) 96bb9402e changes: the #1169 note's title parses and reads as one line (#1172) 627236d95 config: the task door honors a stored crew word, and a pin survives the learned pick (#1171) 593092be6 internal/tui3: a test may not read a profile it did not create (#1173) ad8be1ed7 session: a landing card under --yolo takes its default instead of parking the run (#1174) 03d49a678 gitignore: the .furrow ids the engine writes into a workspace (#1175) 46edbdd32 internal/tui3: gofmt the profile-law helpers from #1173 (#1176) 3179e3611 relay: ALLOWED_VENDORS names the catalog's vendors and codeaf (#1177) 397a145a6 docs: why a settled /task leaves commits on task/<slug>, not the worktree branch (#1178) 915ff3a76 pool: regenerate the seed index from the relay's first clean publish (#1180) d9b42430f cmd/codeaf: the model warm stops when the process closes (#1179) bd2dcf334 session: the auditor's calls say they are the auditor's and which node they check (#1181) c6ed47c5a task: json unverified branch (#1182) 360352ad4 session: a settle turn runs under the checker's own bound, not the run's wall (#1183) 814e1af0e session: the tasks text and record line name a failed/unverified node's kept branch and verdict (#1184) 7c25a26d9 internal/session: the identical-effects test stops riding the turn guard (#1185) 6e7d0ed44 cmd/codeaf: exec and run envelopes name a non-verified node's kept branch and verdict (#1186) f4079d716 session: bound a worker's park on a promoted command (#1187) 994689fe7 docs: the model pool runbook names the Workers Paid plan and what it costs (#1188) c315be13e docs: the runbook's plan line says metered, not capped, and nothing more (#1189) 2e80c60d8 internal/tui3: the status word says working while a task subtree turns (#1191) 99363ca8f session: bound a job subtree's CPU and process count (#1190) 6db5125b2 docs: what the audit check seat can execute, and where that boundary is set (#1192) 649bb4bfd cmd/codeaf: the pool start-up test empties the CI word it is not about 053e6a90f processgroup: a zombie leader does not keep a job's group alive b4e66f515 docs/changes: the entry for #1194 Co-Authored-By: Santosh kumar <[email protected]> Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci: Model Pool mirror workflow (signed index onto the model-pool branch) (#1116) * ci: the Model Pool mirror workflow and the signature check it runs Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs: the change entry for the Model Pool mirror workflow --------- Co-authored-by: Claude Fable 5.1 <[email protected]> Co-authored-by: santoshkumarradha <[email protected]> * chat: the unconnected custom row on /connect keeps its vendored name (#1195) modelConnectionStatus renamed every custom-id row to the connection's Written word, and the catalog template's Written is the bare id, so a person who had connected nothing yet saw `custom` where the manual, the README and #1107 say `Custom OpenAI-compatible API`. Only a connected instance is called what the person called it now; the contract test draws both rows and is red without the fix. Found verifying the staging build of #1194. Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs/changes: v0.3.0 rolled up into CHANGELOG.md (#1196) 95 loose entries — the crew family and Pareto crewing, the Model Pool and its relay, custom connections, the telemetry audit, the update door's download clock, and the fixes that followed — become the `## v0.3.0` section the stable release reads its notes from. Co-Authored-By: Claude Fable 5.1 <[email protected]> * ci: a…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two
cmd/codeaftests failed at cleanup withTempDir RemoveAll cleanup: unlinkat …: directory not empty, only under load, never alone. Neither test starts a goroutine of its own — the first one'swhole body is a
t.Setenv("CODEAF_HOME", t.TempDir()), a cache write and a read-only gate — so thewriter was never the test's.
The writer, named
warmV3Modelsis started fire-and-forget byguard.Go, which joins nothing at shutdown, and itfinishes by writing the picker's cache through
tui3.WriteModelCache— which resolvesCODEAF_HOMEat the moment it writes, not when the warm started. On a cold cache the warm is a network
round-trip, so a warm can outlive the conversation that asked for it and land its write in whichever
state root is current when the rows finally arrive: in a package run, the next test's
t.TempDir(),whose clean-up then fails with
directory not empty. That is both symptoms at once — only under load(slow enough to cross a test boundary) and never alone (no earlier test to leak one).
v3Process.closeAll's own header promises that nothing this process started is still writing underits profile once it closes. For this writer the promise was not true.
The fix, in two parts
Seat the warm on the tracker it should always have been on.
poolErrandGoCtxispoolErrandGofor an errand that must observe cancellation rather than only be waited for;
v3Process.warmModelsstarts the warm through it, so
closeAllcancels the wait and joins the goroutine, exactly as #1124did for the pool errands. Both call sites move: the chat door (
chatv3.go:419) and the engine(
engine.go:708).Give the warm reads that cannot block. The wait now belongs to
Catalog.Warmed(ctx), the onecontext-observing door the close can end; after it answers true the rows are in hand, so the two
reads behind it — the context window and
FetchedAt— must not ask blocking questions of a catalogthe errand has just stopped waiting on.
ContextLengthNowandFetchedAtNowreadrowsNow(theModelsNowdoor) and answer zero / the zero time while a lazy catalog is still warming.ContextLengthNowis byte-for-byte the id matching of its blocking twin —byID[normalizeID(id)],the same normalisation of the reasoning-effort suffix and a leading
~. That matters: the obviouscheaper route, reusing the launch's existing non-blocking
v3ContextWindowseam, matches withEqualFold, so a conversation started onmodel:highwould answer a zero window — the exactnumber the warm exists to learn. The cell caught that in its own first draft and said so.
Consequence, stated rather than buried
A process that closes before its catalog resolves now writes no picker cache for the next launch,
where before the orphaned warm eventually wrote one. That is the intended trade: the old write was
the bug — it landed in a profile the process no longer owned. The warm runs again at the next launch.
Proof the test bites
A test for a race can pass by asserting nothing, so I checked it by mutation rather than by reading.
In a clone of this branch, unseating the warm back to its old shape —
The test makes the race certain rather than likely: its endpoint holds the catalog fetch open until
the test releases it, so an unseated warm is certainly still waiting at the close, and the test has
already moved
CODEAF_HOMEto a directory it owns before releasing it. Deterministic, at-count=3.Evidence
From a fresh clone of this head (
c438a8f75b079f7f068d97a55dd289660ee23792) on the Sparkcluster — log
~/src/trees/check-c438a8f75b079f7f068d97a55dd289660ee23792.log, HEAD on its first line:gofmt -l ./internal ./cmdgo vet ./internal/... ./cmd/...go test ./internal/guard ./internal/namelawgo run ./cmd/codeaf-changes checkgo test ./cmd/codeaf -count=1go test ./internal/catalog -count=1The catalog package is in the list because this adds two doors to it.
🤖 Generated with Claude Code