santos/dev: crew family, Pareto crewing and the Model Pool, custom connections - #1108
Draft
santoshkumarradha wants to merge 693 commits into
Draft
santoshkumarradha wants to merge 693 commits into
santoshkumarradha wants to merge 693 commits into
Conversation
…tree branch (#1178) Diagnosis: the merge-home is correct and targets the driver's own worktree, but runs only on a verified verdict. A driver 'settled' (its quiet rule) is not the engine's 'verified'; unverified/failed nodes keep the deliverable on task/<slug> by design. Traces the branch-creation, commit, and merge-home paths with file:line, proves the pattern across c193-c221, and argues the smallest driver-side recovery. Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…1180) * pool: regenerate the seed index from the relay (version 1789715873) seedgen against the relay's 07:17Z publish: 18 cells, min_installs 3, the relay's judge list; the paper's role_quality cells beside it. seedgen -check exits 0 against the two files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: change note for the regenerated seed index Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * pool: the seed count test reads the score metric's own cells The regenerated seed carries the graded shares beside the judge's scores, so the role_quality line counts its own cells, not the seed's total. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: stamp the seed note with its PR number Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* cmd/codeaf: the model warm stops when the process closes 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 <agentfield-bot@users.noreply.github.com> * docs/notes: what the model warm still reads, and the test failing on 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 <agentfield-bot@users.noreply.github.com> * internal/catalog, cmd/codeaf: the model warm's reads never resolve the 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 <agentfield-bot@users.noreply.github.com> * docs/changes: the entry carries the pull request number it landed as Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…e they check (#1181) * session: a failing test says the auditor's call is the auditor's The auditor's provider call reached the model-call log tagged `turn` with no node, and its usage row named no role and no task. This test drives a real provider boundary from a checker built by newAuditAgent and reads the call log, expecting `auditor` and the id of the node it checked; the usage-row test now expects `role: auditor`, the checked node's `task` and the high seat. * session: the auditor's calls say they are the auditor's An agent built to check a node answers for a crew role and is not that node, so its calls wear the role's own word and name the node they check rather than falling through to `turn` with no node. Config gains checksNode for the node a checker CHECKS, set by newAuditAgent from the node it was handed; loop.go stamps callPurpose(crewRole) and WithCallNode(checksNode) for a crew role, and banks the turn with that role word so TagUsage writes it beside the high seat the ledger already billed. usageNode files the row under the checked node, which taskID deliberately leaves empty. * docs: change note for the auditor's calls naming themselves * docs: the change entry says what the pool's record actually carries It asserted the pool row's contents from the brief and left a reader unable to tell whether `internal/pool/record` was inspected or skipped. The row is one per judged seat score — metric, seat, model, score, judge, door, size, day — written by Recorder.Record from judge scores at the pool's own record command, never from a provider call, with no call count and no cost in it. The entry now says that, and says plainly that no file there changed. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * docs: stamp the auditor-purpose note with its PR number Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
…n's wall (#1183) * docs: change note for the auditor's calls naming themselves * docs: the change entry says what the pool's record actually carries It asserted the pool row's contents from the brief and left a reader unable to tell whether `internal/pool/record` was inspected or skipped. The row is one per judged seat score — metric, seat, model, score, judge, door, size, day — written by Recorder.Record from judge scores at the pool's own record command, never from a provider call, with no call count and no cost in it. The entry now says that, and says plainly that no file there changed. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * docs: stamp the auditor-purpose note with its PR number Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * session: the settle turn runs under a bound, not the run's wall A landed-but-unverified task under `task.settle = auto` wakes the conversation's own turn and tells it to read the work and settle it. That turn was measured running 28 minutes of 49 tool-call rounds on the high-tier model, stopped only by the run's wall, over a tree that was already clean. These tests hold the settle turn to the checker's own contract: a call ceiling, a share of the run's money when a Steward is armed, the bound and the count on the hand-back, one call for a clean tree with no declared check, and no ceiling for an ordinary turn. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * session: bound the settle turn at the wake, and say why it came back The turn a landing note wakes under `task.settle = auto` is the one that has to read the work and settle it, and it ran with no bound of its own. The wake is now marked a settle wake where the note is minted, and the turn it starts opens the checker's own window on its context and stops at `settleCallCeiling` provider calls — or at its share of the run's money when a Steward is armed. When a bound trips, the node is handed back with the reason and the count on its report (a new reason beside `taskAskTimeReason`), not a silent stop. A node whose check saw a clean tree with no declared check carries both facts onto the node and settles after one call. A conversation's ordinary turn keeps no ceiling. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * docs: the change entry for the settle turn's bound Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * session: the settle turn tests build against the bound Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * session: the settle bound's count says one call in the singular Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: stamp the settle-turn-bound note with its PR number Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…s was Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…'s kept branch and verdict (#1184) The chat-side counterpart to #1182: a /task node that ended failed or unverified kept its deliverable on its own task/<slug> branch (keptWork) and did not merge home. The tasks tool text, the recovered-graph note, and the TaskIndexEntry record now name that kept branch and the verdict word (session.TaskFailed/TaskUnverified) — the same vocabulary #1182 put on the headless envelope. keptBranchOf gates on the merge word (mergeAborted/Conflicted/Kept) so only genuinely-kept work names a branch. tui3 rendering is untouched; the rail/page read the words off the record. Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ard (#1185) TestARunOfIdenticalEffectsEndsNothingByItself failed on its len(rounds) < 2 assertion in a full-package run under load and passed alone. It was not the leash: the run's allowance is 60 minutes and the run lasts 0.21s, so the deadline branch above the repeat branch in childRun.trip never fires, the step budget is 200 against seven steps, and every save resets r.idle. What ended the turn was the turn's OWN repetition guard. Forty byte-identical write calls are a loop by looped.go's rule, which keys on the call signature (callSignature hashes the raw argument text), so the guard hands the turn over at its seventh write, and whether the drain reached a second repeat checkpoint before that turn closed was already a race without any load at all. The test now leaves one identical EFFECT and forty distinct call SIGNATURES: the saves write an empty file, so the leash's fingerprint of it cannot race the next truncating write, and the JSON key order turns over between calls, so no two signatures match and the guard cannot fire. Measured on this tree, the round count moves from 3 against a floor of 2 to 19. It also holds the behaviour effects.pardon exists for, which nothing pinned before: the reader is asked once per run of identical effects and never on every step. Removing that reset gives 38 rounds against a ceiling of 21. Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…anch and verdict (#1186) * cmd/codeaf: the exec and run envelopes must name a non-verified node's kept branch and verdict (failing tests) The tests for the last gap in the #1182/#1184 vocabulary: an exec or run run that ends non-verified carries kept_branch (the workspace's own branch) and verdict (the record's word) on its --json envelope. They name the shared seam (buildResultEnvelope/runResult) as the place one change covers both doors, pin the failed-broke-with-nothing and no-repository/detached cases, and an end-to-end smoke run proves the door passes its workspace through. Red on purpose: buildExecEnvelope takes no workspace and subharnessRun has no workspace field. Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> * cmd/codeaf: the exec and run envelopes name a non-verified node's kept branch and verdict The last gap in the #1182/#1184 vocabulary. An exec run leaves its landing for the pool's judge with State session.TaskUnverified (exec.go) and a run door run does the same on every ending that ran (subharness_run.go), but neither door's --json envelope said where the run's work stood or what verdict left it there. Both now say the two facts do --json has said since #1182, at the shared buildResultEnvelope/runResult seam (envelope.go) so one merge covers all three verbs and do's own emission moves onto it unchanged. execVerdict (exec.go) reads the word off the same condition the pending landing uses: a run the landing refuses — never started, or broke with no text and no artifacts — says session.TaskFailed; everything else that ran says session.TaskUnverified. The run door's sayEnvelope is unverified on both its endings (the landing is written whatever the stop) and sayFailedEnvelope is failed: that road writes no landing at all. kept_branch is the workspace's own branch via keptBranchIn (extracted from errandKeptBranch, do.go), empty for a detached HEAD or a non-repo, and absent with the key when no verdict stands. The run door's workspace rides subharnessRun, resolved at the door from space.Root() exactly as do resolves its own. exec passes space.Root() at the buildExecEnvelope call site; the end-to-end smoke pins the door wiring. Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> * docs: the envelope's kept_branch and verdict cover all three headless doors HEADLESS.md's two rows said the keys ride codeaf do alone; they now say what exec and run publish — unverified on every run that left a pending landing, failed on one that broke with nothing to show — and the change entry records what moved (#1185). Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
* docs: what a --yolo task turn waited on when a bash job backgrounded Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * session: bound a worker's park on a promoted command Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * docs: change note for the park's own bound Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * docs: stamp park-bound change note (#1187) Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
Four tests for the plan drawn as a tree in the tasks place: a child indented under its parent, a held row under the task it waits on, the live step at the node's own column, and a note on a child still landing through PlanNote. Each fails today but the guard; the code follows. PlanTaskRow carries `Waits` — the hard dependencies that are not its parent — so a row can say what holds it besides its parent. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>
A plan row is now nested on the parent the store wrote: planItem answers the tree walk's Parent from planAnchor — the store's own parent, or, for a row kept pending, the hard dependency holding it that is not its parent. The place's existing walk does the rest, so a child sits under its parent and a held row sits under what it waits on, each wearing the tasks place's own connector (tasksKin). A plan row is a node of the plan's tree and not a fold: its children always follow it, so the graph is on screen without a keypress. A record row keeps the fold it had. PlanTaskRow.Waits carries the hard dependencies a row is held behind, and planWaits names the anchor — the waiting-on task or the parent — so `queued · waits: <the work>` is one answer, not two. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>
PlanTaskPage carries the task's own children, read one level deep in store order, and the page draws them under the steps the way the rail draws a family: the tasks place's connector, each child's state word and title, and its live step under it while one is in flight. The note composer and its receipt are untouched. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>
Two answers in the asker's words: the plan is drawn as a tree, a task sits under the task that requested it, and a task held behind named work sits under what it waits on, still wearing `queued · waits: <that task>`. tasks.md carries it after the tasks place, worker-harness.md after its own pane page. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#1191) * tui3: a door at rest with a turning task subtree says idle Failing test first, before the render-site fix: the status row's state word must say `working` while the conversation's own turn is over and a task it started is still turning. It currently says `idle`. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * tui3: the status word says working while a task subtree turns A door at rest whose work outlived its turn now says `working` from [app.stateWord], reading the surface's own frame-safe signal ([app.frontSignal]) rather than `a.state`, whose behavioural reads are unchanged. The word matches the tab strip's; the figures are untouched. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * tui3: assertions, manual and note for the working word roomstatus_test.go's fixture is a door at rest whose node is still turning, so the conversation's own reading after leaving the room is the working word, not a bare idle. The manual gains a row for the at-rest `working` and loses the sentence that called the conversation idle while a node worked; a note and a changelog entry record the word, the render site and why the count is app.frontSignal and not runningTasks. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
* session: a failing test for a job subtree over its CPU bound The test states a spinner storm and an honest parallel verify as injected readings — 16 loops then 64, against 34 processes and 8.5 cores — and demands the storm be cut with a record while the honest peak survives. Nothing bounds a job subtree's CPU or process count today, so it fails. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * session: bound a job subtree's CPU and process count A run whose model spawns busy loops as bash jobs spends no tokens, so its dollar cap never fires while the box burns. #1158 bounded a run's memory and nothing bounded a job subtree's CPU or process count. The subtree #1155 records at launch (a process group with its leader's identity) now has a bound: a portable Go-side watch over the subtree's cumulative CPU and live process count. The CPU RATE is the signal that separates an honest peak (34 processes, 8.5 cores) from the storm (16 loops, then 64 — fewer processes but far more CPU); a much higher process ceiling catches the fork storm the rate alone would miss. A subtree over the ceiling for three consecutive readings is cut and the run is handed the record 'the job subtree was not settled within its bound', the family #1183 and #1187 speak in. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * docs: stamp cpu-bound change note (#1190) Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * session: draw the CPU ceiling from the effective cores, not the machine Under a cgroup CPU quota (where cells run) or an affinity mask a subtree tops out at a few cores, so max(0.6*NumCPU, 12) could never trip: the 64-loop storm passed the CPU rule and, at 64 processes, the 256-process backstop too. Draw the ceiling from the effective cores (min of NumCPU, cgroup cpu.max/cfs quota walked from the process's own cgroup as #1162 does for memory, and sched_getaffinity), share 0.6, floor 1; lower the process backstop to 128 (about 4x the honest peak). New internal/processgroup effective-cores reader, injectable for the seam test. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * session: the CPU rule applies only when no quota or affinity binds A share of a cell's four-core quota (2.4 cores) would cut every honest go test inside the cell: under a binding quota the quota is already the ceiling and #1187's park bound returns a wedged turn, so the CPU rate rule is off there and only the 128-process backstop applies. When nothing binds, the ceiling is 0.6 x NumCPU as before. quotaBinds() decides it (effective cores < machine cores). Widen the trip window to 15 readings (30 s) so an honest build burst is never cut. Remove the now unused core floor. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
…is set (#1192) Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
#1193) * internal/tui2/prose: a curated lexer set that parses nothing at init Importing the dependency's lexers package runs a package initializer that builds a lexer from every one of its 279 embedded XML definitions, before main and whether or not a block is ever drawn. On the cold-start benchmark that parse is about seven of the twelve milliseconds `codeaf --version` costs, and every command pays it. So the set becomes ours. embedded/ carries 40 of those definitions — 39 languages plus go_template.xml, which the Go lexer delegates a raw string to — copied byte-for-byte from the dependency's own copies (all 40 verified identical with cmp against v2.27.0 in the module cache; NOTICE says do not edit them here, so a re-copy after an upgrade is a diff with nothing to reconcile). lexers.go embeds them (`//go:embed embedded/*.xml` at line 44 into curatedFS at 45) behind one sync.Once (curatedRegistry, line 129) and builds the whole set on first use (curated, line 135), because a definition may delegate to another BY NAME — html.xml leans on CSS and Javascript, docker.xml on Bash and JSON, makefile.xml on Bash — and one parsed alone whose delegate was never registered would tokenise the delegated region as text. Nothing is read at package init. loadCuratedXML (line 118) is the only XML parse, and curatedParses (line 114) counts every one so the laziness can be pinned by a test rather than asserted in a comment. curatedEntries (line 67) is the set, and its order matters in exactly one place: hcl.xml and terraform.xml both claim the alias `hcl`, and registration leaves the later holding it, so Terraform follows HCL and a ```hcl fence reaches what it reached before. There is deliberately no second table of names — curatedGet (line 158) and curatedMatch (line 165) are the dependency's own lookups over the curated registry, so names, aliases and bare extensions come from each definition's own Config and cannot drift. Go and markdown are the two languages the dependency ships as Go source rather than XML, so there is nothing to copy. lexers_builtin.go rebuilds both out of the dependency's own rule tables (goRules line 50, markdownRules line 161), lazily and registered with the rest (newGoLexer line 40, newMarkdownLexer line 95): goTemplate (line 28) is a sync.OnceValue over the one XML the Go lexer needs, and markdownLexer.Tokenise (line 112) keeps the frontmatter delegation to YAML via splitFrontmatter (line 134). Without them the most common fence in this repository would lose its colour, which is the one thing the set must not do. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * internal/tui2/prose/code.go: the four registry call sites go through the curated set The import of the dependency's lexers package (line 8) is what ran the 279 definitions' initializer, and this file was the only place in the tree that imported it. It is gone; only chroma itself remains (line 7). All four call sites move onto the curated set: - LexerName (line 318) matched a filename with lexers.Match; it now uses curatedMatch (line 330), the same globs and priority ordering over the curated registry. It still answers "" for a filename nothing claims, and the doc comment now says an uncurated filename is exactly that case, so a file preview falls back to what it drew before instead of being painted as if it were code. The memoisation stays: Match walks every file pattern of every lexer, and the callers draw terminal rows at thirty frames a second. - highlightPieces (line 349) resolved a fence's language with lexers.Get, then lexers.Analyse on the source when the label missed, then lexers.Fallback when that missed too. It now uses curatedGet (line 359) and, on a miss, draws the body at the ground tier — the same unstyled pieces a block under a profile with no ramp already produced. Analyse is dropped rather than replaced: an unlabelled or uncurated block is plain text, because a wrong guess is a claim about somebody's code. Fallback is dropped with it, since the nil-lexer degradation the callers already made IS the plain-text answer. No behaviour change for a curated language; the miss path raises nothing. Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> * internal/tui2/prose: pin the laziness, and the change note TestMain (lexers_test.go line 22) is the pin, and it runs before any test can build the set for its own reasons: curatedParses must be 0 and curatedRegistry.reg must be nil at that point, so an eager parse — a package variable that calls the loader, a registry built in a var — exits non-zero before a single test reports. That is a run, not a claim. Adding `var _ = curatedGet("go")` to the package makes it fail: the curated lexer set parsed 39 definition(s) from XML before any use; the registry is eager FAIL github.com/Agent-Field/codeaf/internal/tui2/prose 0.006s and deleting that one line returns the package to green (ok, 0.420s). 39 is the count of XML definitions in the set; go_template.xml is parsed through goTemplate rather than loadCuratedXML, so it is not one of the 39. TestCuratedSetBuildsOnceAndAnswersNames (line 37) pins the other half: first use builds the set, a later use re-parses nothing, names and aliases and bare extensions all resolve (go/golang/python/py/rust/ts/yaml/yml/bash/shell/hcl/ terraform/markdown/md/text), filenames resolve (main.go, script.py, styles.css, Dockerfile, x.yml, notes.md), and a miss is nil for both a language and a filename — plain text, no error. TestCuratedLanguagesHighlight (line 78) is the no-regression half: the set carries real lexers and not non-nil placeholders, so python, rust, typescript and markdown fenced blocks still emit code-slot colours. The change note carries the measured cold start (three best-of-7 rounds of the benchmark's own startup phase, before 15/13/14 ms and after 10/11/10 ms) and the measured binary sizes (53,739,785 to 51,511,561 bytes, both `make build` of this tree). Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com> --------- Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
…ot on the wall clock (#1221) The scenario where the default lane goes slow and the router moves compared measured wall times, so on a busy machine a healthy router could miss the design's gate. Each request is now scored on the scripted first token of the lane that answered it, and both of the design's clauses stand on that figure: a p90 first-token improvement of at least thirty percent, and the moving arm's p90 below the pinned arm's median. A router that does not move fails by construction. Measured wall times stay in the log as diagnostics. Test-only.
A config file whose keys the reader does not know, including a nested object written where a dotted key was meant, read the same as no config at all, with no word to the person. Loading a profile now names every top-level key that nothing consumes, once per load, as a log line and never as an error, so a session still starts. A correctly shaped config says nothing.
Asked about work it had just handed off on the new road, the conversation's tasks tool answered that no task had run, because it read only the old task graph. It now lists a run's tasks from the run's own store, named the way the rail names them, a task by its number and a part by its place under it, each with its state and the first line of its result, and an id reads one of them. The shipped listing of earlier tasks is kept beside it. The manual no longer describes an ask box that no screen opens.
… open their pages (#1231)
…operties, 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.
… callback ports (#1237)
…cadence, and the harness that measured it (#1235)
…ver the conversation (#1244)
…ntroducing itself again (#1236)
…her'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.
…ump (#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.
…llows 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.
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.
Integration branch
santos/devintodev. Everything below through #1192 landed ondevas #1194 on 2026-09-18 (merged by Abir with his own fixes on top: a zombie-aware process-group liveness check and a CI-mode guard in the pool test), anddevwas merged back intosantos/devat 631237c, again at 2ac2360 (the Model Pool mirror workflow, #1116) and at c8d0046 (the /connect custom row fix, #1195), and at ae07e93 (the v0.3.0 changelog roll-up, #1196), so the branch is level withdev. #1203 makes two planner tests count their fake client's calls atomically, removing a test-only data race that failed one full check under load. #1109 brings the worker harness: one loop under/taskandcodeaf do, with a plan store per run, a review round after each piece of work, and the task tree on the rail. It is off unlessCODEAF_TASK_BELT=bashis set, so the default road does not move; today a/taskthe person types andcodeaf dotake the new road, and a task the chat starts by itself joined it in #1205. #1207 seats a review check on the crew's checking seat instead of the thinking seat, so the model a person picks to check is the one that checks; the door resolves that seat from--check-model, thenCODEAF_CHECK_MODEL, then a pinned plan seat, then the profile's careful row. The two harness waves landed at 164548f (full check on Spark green, 114 packages): #1204 draws the plan as a tree on the chat rail with a dot row, a line saying what is happening now, a bounded read-only question about a run, and a work tab that lists runs; #1205 gives the chat one way to put work out, a landing that wakes no model turn, and a reply only when an answer is owed, and a task the chat starts now takes the new road too. #1210 reads a run's result from the store when the root's worker returns after the run ended, which removes a race that lost the answer under load. #1209 shapes answers to lead with the action, number their steps and end on one next thing. #1211 joins a test's background writer before its state directory is torn down (test-only, landed at 7cda67c, full check green). Three fixes from a first real session on the new road landed together at b94df33 (full check green, 114 packages): #1212 counts a landing's report as evidence that the ask is finished, so a correct answer is not followed by an empty turn, and a check refused for a leading directory change is told the form that passes; #1213 keeps a reopened conversation's tasks, by reading back a task born from a run's plan, never overwriting a checkpoint it could not read, never reusing an id, and saving a run's ending with its row; #1214 tells a refused proposal every problem at once and settles where a task works from the folders its brief names before asking the person. #1215 reads a declared check as one command the way a shell reads its quotes, so a search for one of several words is admitted, run and judged by one shared reader, and a refusal names the character and the form that passes; #1221 judges the router scenario on scripted first tokens so its verdict no longer depends on how busy the machine is (both landed at 39ffbc5, full check green). Seven small PRs landed together at fe9eb62 (full check on Spark green, 114 packages): #1222 lets the conversation's tasks tool read a run's tasks from the run's own record, so a finished hand-off is asked about and not redone; #1223 says once, at load, which top-level profile config keys nothing reads, so a nested object written where a dotted key was meant is no longer silent; #1225 stops a dispatch pass from reclaiming the leaf it just released, which ran the same piece of work twice and spent its budget twice; #1227 seals the place sweep's note at close, so nothing is written under state after the process closes and quitting never waits on the folder walk; #1226, #1228 and #1229 make three tests independent of machine load (a wait helper that checks its condition before the clock, a usage writer joined before cleanup, a first token ordered by a signal). Three more landed together at 4e176a5 (full check on Spark green, 114 packages, first run): #1230 lets a conversation whose engine runs in another process, which is the default, read and steer its run: the rail's rows, a task's page and the steering verbs now cross the wire, a compile-time line fails the build if a later method is not carried, and the plan is re-read on a beat only while a row is queued or running, so a conversation at rest reads nothing; #1231 opens a task's page from its row, gives a run a row of its own with its parts under it, and answers a click from the same drawn view the frame used; #1233 seats the review of acodeaf dorun whose root finished itself before its worker returned, which used to end the run with no check and failed the full check under load. Five more landed together at 608c459 (full check on Spark green, 114 packages): #1220 makes a worker's declared checks the review contract: a holds verdict rests on a recorded, zero-exit run of every declared command, the worker records each command's own exit, a record opens with a line marking it as one that records exits, so a declared check that was never observed is refused, and the basis of every verdict is kept with the task (replaces #1206); #1232 gives every run a copy of its own, so a hand-off no longer works in the person's folder, and brings the work home as one commit when the run ends; #1235 paints a wait whose stream has stopped arriving at the spinner's own pace, measured at about 0.6 points of a core less on the wait path, with its method and harness under bench/idle-surface; #1240 stops a letter typed into a note on a running task's page from raising the stop card, and a finished task's page no longer offers verbs the store would refuse or shows a store id; #1237 takes the connect tests off the fixed callback ports, so nothing else on a machine can fail them. Two more landed at 1cbcb4b (full check on Spark green, 114 packages, first run): #1239 tells the person once, in the conversation, which config.json keys nothing reads, in hosted and unhosted launches alike, again only when that set of keys changes, and never for a key the product itself writes; #1238 identifies a surviving process in a test by its identity and start time, since a bare process number can be reused by an unrelated process. Four more landed at 12840ee (full check on Spark green, 114 packages, first run): #1234 keeps every run a conversation has made readable, and closes a lock the plan reader could leave held when a conversation had no run yet, which stopped the engine; a new guard law now fails the build if a reader of that shape can return while still holding its lock; #1242 makes two profile writes in one process compose, so a second write no longer drops the first one's key; #1243 adds the regression test for seating a check after a worker whose root finished itself returns; #1241 adds a repeatable drive of a run on the real binary in hosted mode under scripts/, outside the full check. Five more landed at 443c661 (full check on Spark green, 114 packages, first run): #1244 gives what a person types after pressing a run's row to that task's page, so a note meant for a task is never sent to the model as a message; #1245 keeps the unread-key notice quiet about keys the product itself retired, with a ledger law so a key cannot be retired silently; #1236 lets a reconnect reuse its registered identity, reports a finished sign-in only after its listener is released, and hands the product a held listener in tests; #1247 and #1246 make two tests independent of machine load. Two more landed at 8ed67b7 (full check on Spark green, 114 packages, on the second run; the first run failed one temp directory cleanup in a package the stack does not touch): #1248 keeps both keys when two codeaf processes write the profile at once, with a lock that holds on every platform the repo builds for, a wait bounded at two seconds, and no stale lock to reclaim after a crash; #1249 makes the job shell tmux tests read one marker line, so a large environment can no longer cut the line they assert on or let an absence pass unproven. Still to come as small PRs: the task's own page and its reader over the wire, a copy of its own for every run, and the review contract where a worker declares its own proof command and the engine judges it by what ran (replaces #1206). This PR now carries what came after #1194: the lazy lexer registry (#1193) and whatever lands next. This PR is the running index of what the branch carries at the product level. It stays a draft until every feature below has landed onsantos/dev, the independent end-to-end pass on Spark is green, and Santosh has e2e-tested it.What a person gets
models.crew.source = open|all(defaultall), or the open/all switch in/crew.santos/dev(#1073)auto./crew catalog, ormodels.crew.pick = catalog.santos/dev(#1093, squash 2fb296b)table,catalog,learn/crew table|catalog|learn,models.crew.pick.santos/dev(#1093, squash 2fb296b)/taskrun an independent judge scores every seat (billed to ajudgeseat). Scores go to your own sheet and feed the picker next time, blended with the pool by observation count, so your own runs improve your own picks first. Starts from a seed built from 145 real runs, so learning works on day one. A seat you type by hand is judged too.pick = learn.codeaf pool showlists what the picker knows.santos/dev(#1093, squash 2fb296b)model_pool on|read|off(defaulton, notice shown once).codeaf pool show|status|verify [--json].santos/dev(#1093, squash 2fb296b)relay/with a runbook; it is live athttps://codeaf.agentfield.ai/pool. The signed index is mirrored to themodel-poolbranch of this repo so anyone can watch it update. A team can run its own private relay with its own key (CODEAF_MODEL_POOL_RELAY_URL,_MIRROR_URL,_PUBLIC_KEY).santos/dev(#1093, squash 2fb296b)docs/design/model-pool/pareto-crewing.pdf("Pareto Crewing"): the objective, estimator and picker, with figures from real runs.santos/dev(#1093, squash 2fb296b)/modelgroups models under each connection's name. Switching onto a keyless default service is refused with a reason./connect→ Custom OpenAI-compatible API;/settings→ Providers → add / edit / active connection.santos/dev(#1092, squash 825ef2c; Santosh e2e-tested locally against a local OpenAI-compatible proxy)/taskanddo/taskdoor andcodeaf do.Bug fixes on the branch (question answer-box keys, landing answer fate, retry avoiding the failed upstream, recovery evidence, taxonomy lane read, and, with #1092, mid-session
/connectnow reaching the live conversation on the engine road, a pre-existing hole) are not listed above; each has its own change note underdocs/changes/unreleased/.Known limits, stated on purpose
/taskdoor only;codeaf do,askand remote sessions do not feed the pool yet.codeaf pool verifypulls a fresh copy, read at the next start.Relay status
Live at
https://codeaf.agentfield.ai/pool(healthz 200, index signed,codeaf pool verifygood). Themodel-poolbranch exists and carries the current signed index (refreshed hourly by a stand-in job until #1116 puts the workflow ondev). The pool is primed with the same measured runs the embedded seed encodes, so the published index carries real cells from day one.End-to-end pass on Spark (independent session, done)
All 12 steps pass on #1093's head (cf4ea35; final fd29d79 differs by a runbook sentence). Report:
spark:~/src/e2e-learn-2026-09-17.mdwithKNOWN-LIMITS.mdandevidence/INDEX.mdbeside it; 18 capped runs, about $26.learn, judged tasks, own sheet + outbox flushedreadandoffsend nothing/modeloverride underlearnis still judgedauto/catalog/learn seats resolve on headless doors/crewchooser and/settingspool row with noticecodeaf models,--refresh, daily cachedo/exec under a learn crewFollow-ups from the e2e, now on
santos/dev: a task's thread stops its parts before closing and a machinery cut records its reason (#1118); a decision card under--yolotakes its default (#1117); the auto crew row shows the rung it actually resolved to (#1119); a bareautorow underpicked from = catalogreads the catalog's figures alone (#1120).codeaf doseats from the settings the run will use, so a fresh profile with the key in the environment reads the catalog, not the table (#1122); the relay accepts the exec and run doors (#1121); a launch's pool errands stop when the process closes instead of writing into a closed profile (#1124); a launch compilesinternal/verify's patterns on first use and the engine host runs on a capped scheduler (#1125); the client'smin_installsfloor counts installs, as the relay's does (#1127); the relay folds a retried batch once by its nonce (#1128), carries anacceptablemetric beside the judge score (#1129), publishes only cells that meet the floor (#1131) and writes once per cell per batch (#1136); a refused row leaves the outbox and the file is compacted (#1130, #1133); the pool errand tracker's critical sections unlock from a defer (#1134); a run a process death left unjudged, and any headless door's landing, is judged on the next chat start (#1132). the finished-tree check reads a project cloned into a subdirectory (#1135); the exec, run and do doors leave a pending record for the sweep (#1137, #1138, #1139); the wall admits a first round it cannot cost and refuses an overrun it cannot hold, so a longcodeaf dolands instead of re-planning to the wall (#1144), with the delivery gate over a grown job root pinned by a test (#1147);pool show,pool verifyandpool statusprint both metrics, the pending judge records and the last sweep (#1141, #1142); a test's crash fixture stays in a profile the test owns, and the package's test floor clears any profile the environment named, and the opening-hint test reads a profile it made and pins the greeting contract both ways (#1145, #1150, #1153), and tui3's package law makes every surface-building helper name a profile of its own (#1173); the manual's status row says whose ledger it is (#1143); the seed index is regenerated from the live relay by one in-repo command, so a fresh install's fallback is a verified copy of the pool (#1148);pool show --cellslists the held index's cells with their installs and dims (#1149); a landing that leaves gitignored build products behind stays complete and the reconciled row names them (#1152);codeaf versionon an unstamped binary names the condition, no.gitdirectory for the toolchain to read, instead of blaming a Makefile target (#1151).pool statussays when the relay dropped a row and why, instead of the row vanishing silently (#1154); a job teardown records the group leader's identity at launch and never signals a process group the kernel has since handed to someone else (#1155); acodeaf docontinuation after an overrun continues the plan it has from the recorded turns instead of re-planning from scratch (#1156); the process sets a soft memory limit of half the machine's memory, nothing below a 512 MiB floor, an explicitGOMEMLIMITuntouched, carried intoengine --daemon(#1158), and the bound is the smallest finite one of physical memory and the cgroup v2/v1 limits walked from the process's own level to the root (#1162);pool statussays when this run cached the index, so the seed-to-cache flip is visible in the run that caused it (#1161). The pool's copy of a seat's id is the bare vendor/id, so a fresh install on the default model (whose seat carries codeaf's alias marker) no longer has its first rows refused by the relay, and the judge picker's same-vendor exclusion works on it (#1163); no cmd/codeaf test can push rows to the default relay, the package floor pins a dead address (#1164); the relay bounds each judge's fitted severity to ±10, clamps every adjusted score to the rubric, publishes mean and sd on one scale and falls back to the median when spread is zero, which is what had published a cell at 103.5 (#1165); the relay can purge stored rows from a fixture vendor and refuse vendors not inALLOWED_VENDORS(#1167), withALLOWED_VENDORSset to the catalog's vendors plus codeaf (#1177, Worker version 47102a26; 624 fixture keys purged from the live store); the built-in seed is regenerated from the relay's first clean publish after the purge, 18 cells, no fixture rows (#1180); a job's shell no longer inherits the host's TMUX handle and gets a profile-owned TMUX_TMPDIR, so a command the model runs cannot kill the terminal hosting codeaf (#1166); an unattended run (--yolo, nobody to answer) is refused agit push,mergeorrebasethat would move a branch it did not create, including the branch the checkout began on, while its own task branch stays pushable and attended sessions are untouched (#1168, the road it took is in #1160's note);pool statussays the install's identity, the judged total across all doors, and "none yet" when no landing has been judged (#1169); an exec run's worker spend is minted into the usage ledger at the door, one row per run, so the status row, the cap and the pool row count it (#1170); the task door honours a storedmodels.crewword (every arm had resolved to balanced) and a pinned tier row survivespick=learninstead of being recomputed (#1171); a landing card raised on the check road under--yolotakes its default and records that the dial answered and that the check ran out of time, instead of parking a headless run forever, while conflict, shift and ground landings still park and now say so in the record (#1174); the model catalog warm is seated on the profile's start-up errand tracker so a process close joins it, instead of a warm outliving the process and writing its cache into whatever state root was current (#1179); the auditor's calls tag themselvesauditorwith the node they check, and its usage rows carry the auditor role and the task, so a ledger tells an audit call from a session turn (#1181); a headlessdo --jsonwhose node ends failed or unverified names the kept branch and the verdict in its envelope, so nobody digs through tasks.json for kept work (#1182); the settle turn a landing note wakes undertask.settle autoruns under the checker's own bound, a call ceiling and a share of the run's cap, one call for a clean tree with no declared check, and records "not settled within its bound" instead of running to the wall, which had been a one-to-two-dollar tax on most headless runs (#1183); the chat-sidetaskstext and a resumed session's record line name a failed or unverified node's kepttask/<slug>branch and its verdict word, the same two words the headless envelope carries (#1184); the identical-effects test is deterministic, its forty saves carrying distinct call signatures so the turn's repetition guard cannot end the turn and the leash is the only thing the assertion is about, which closes a margin-of-one race that had read as a load flake since #284 (#1185); a non-verifiedcodeaf execorcodeaf runnames its kept branch and verdict in its envelope through the one result-envelope seam the three headless doors share, so do, exec and run answer alike (#1186); a task worker parked on a command it started (a bash call the 30-second background clock promoted to a job) waited the node's whole allowance when the job never ended, which is what held a DOE run silent for 53 minutes under a live cap; the park now has a bound of its own, a third of the allowance, posts "the park was not settled within its bound" and hands the turn back while the command keeps running, with the diagnosis at docs/notes/yolo-task-worker-parked-on-promoted-bash.md (#1187); the model pool runbook's plan line says metered, not capped (#1188, #1189, docs only); the status row saysworkingwhile the door's task subtree turns, read from the same signal the tab strip draws from so the two cannot disagree, with the manual's screen and task-page sections moved to match (#1191); a job subtree is cut when it passes a CPU or process bound, 60% of the machine sustained for 30 s or 128 processes when nothing else bounds it, the process backstop alone under a cgroup or affinity quota, and the run learns it as "the job subtree was not settled within its bound", which is what a model that spawns infinite loops under a dollar cap had escaped (#1190); a note records what a task's audit step can execute and where that boundary is set, the declared checks plus read-only git and coreutils, which is why a brief that declares no check leaves a cautious auditor nothing it may run (#1192, docs only); the prose lexer registry loads a curated set of forty languages on first use instead of parsing all 279 of chroma's lexers at init, plain text for the rest, which takes about 3 ms off cold start and 2 MB off the binary, with chroma's files and licence carried beside the copies (#1193). Still open, PRs in progress: the footprint benchmark method and table under docs/benchmarks (#1126); Pareto crewing v2 with a model-free grader (#1123, Santosh reviews). The "task-branch merge fails 1 in 3" observation was the test driver killing runs mid-check, not a product defect.Related, not on this branch
devdirectly (must ride staging and the next main release; the website relay fix is already live). Also merged intosantos/devso the e2e covers it.devdirectly (workflow file + verifier only), so themodel-poolbranch is refreshed by Actions; until then a stand-in job on Spark refreshes it hourly. Also onsantos/dev.Before this leaves draft
santos/dev;dev, telemetry: audit fixes — hosted chat counters, no unversioned sends, silent smoke tests #1111's and ci: Model Pool mirror workflow (signed index onto the model-pool branch) #1116's branches merged on top (6092393).santos/dev.make checkon the finalsantos/devhead, on Spark.