senior-dev: a coding agent built into codeaf takes a whole task, in the folder itself - #1488
Open
ZeroPoint95 wants to merge 165 commits into
Open
ZeroPoint95 wants to merge 165 commits into
ZeroPoint95 wants to merge 165 commits into
Conversation
A draft for discussion: an outside program such as swe-pro becomes one more worker kind behind the run supervisor rather than a slash command of its own. Names the seams it rides, the contract it asks of a program, what swe-pro would have to change, and the waves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ands drawn beside it Owner feedback 2026-09-21: the UX is /swe-pro <brief>, not /task via. The row is generated from the manifest and rides the task door. The control-plane change and the spend-record question are handed to the swe-pro side. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s spend Records what landed on the swe-pro side (f3b9716): no plane needed, a cumulative `stage: spend` record the live limit reads, and the rule never to sum message.updated costs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oad time Owner's call 2026-09-21: delegates are added at a person's discretion, so the /<name> row cannot be a build-time list. The manifest ships its own manual page, the corpus layers installed pages over the packed one, and a page that does not spell its command refuses the manifest. codeaf-as-delegate moves to later. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Owner's call 2026-09-21: one commit per delegated task, subject the task's title, body the terminal record's claim and observation, then the ordinary merge home. Eager commits stay on inside the copy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Decisions up front in one table, short numbered sections, every "already exists" seam in a table, no content removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ext-landing delegates Owner's call 2026-09-21: no per-program readers. stage, spend, step and terminal on stdout, everything else ignored; swe-pro already conforms bar two optional asks, and a delegate may land text instead of a tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d and a step record swe-pro 5793499 landed both optional asks, output-only. The reader needs no compatibility spelling and the task page gets steps. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The interface a program meets to be a codeaf delegate, standalone: launch, the four stdout records, the terminal, SIGTERM, tree or text, the manifest and its manual page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ch, and the run's delegate worker Wave 1 of docs/design/delegate/DESIGN.md. internal/delegate is a leaf: a manifest beside its manual page, loaded from ~/.codeaf/delegates with the manual law checked at load; a reader over the four stdout records that ignores everything else; and a launch under its own process group that SIGTERMs on the context, waits a grace, then kills, keeping a terminal written on the way out. internal/run seats it as one more worker kind: stages feed the live step, step records enter the trajectory, spend reaches the run's bank, and the terminal record is the ending. Proven against a scripted program and a recorded swe-pro stream. No door reaches it yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…/delegate lists them; via on propose_task Wave 2 of docs/design/delegate/DESIGN.md. The conversation's door: a delegated run rides the run road whatever the belt switch says, the program is seated on the root task, nothing joins it, and its commits are squashed into one task commit that comes home the way every run's copy does. The surface generates one command row per installed delegate at launch, appended to the live table and never to the literal; /delegate lists what is here, what is absent and what was not added. The model is told the delegates this launch has, by name, and names one with `via`. The manual gets its Delegates page and the probes that reach it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… over the packed corpus Wave 3 of docs/design/delegate/DESIGN.md. A delegate ships its page beside its manifest; the loader keeps the text and the conversation's manual tool answers from the packed corpus with those pages layered under `delegate-<name>`, so "what does /swe-pro do" is answered from swe-pro's page and nowhere else. The overlay adds and never replaces. A ready-to-copy swe-pro manifest and page sit under docs/delegates. Wave 4 needs no wire: a hosted surface installs no rows and /delegate says whose delegates they are. The design doc records the decisions taken while building. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s row startKnownTaskRun became a wrapper over startKnownTaskRunVia when the delegate door landed, so the row's publisher is the via variant. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the loop, and the laws hold Wave 4 of docs/design/delegate/DESIGN.md. Delegate.List and Delegate.Start ride wire version 18, so a --host surface generates its rows from the far machine's registry and a delegate runs there; the surface asks for the list beside the line at the launch and on a switch, and starts one in the line. The two roads the door lengthened are split along their phases (joinBeltRun, commitProposalToRun), the stop-law ledger names both publishers, and the manual pages name no retired worker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dir it before installing one Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…its own repository swe-pro-go renamed its binary and namespace to senior-dev at b43daaf: the binary and cmd/senior-dev, the .senior-dev/ run folder, refs/senior-dev/* and every SENIOR_DEV_* variable. Its command line, its four stdout records and its terminal data spellings are unchanged, so the reader needs nothing new; the manifest, the page, the fixture, the tests and both documents take the new name, and the docs pin senior-dev at 6103488 with one line saying what it used to be called. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a placeholder
fill substituted a brief into argv and then kept rewriting it in map order,
and checked the RESULT for unknown placeholders. So a review of a Go template
or a Helm chart ({{ .Name }}) refused the launch every time, and a brief that
said {{key}} or {{workspace}} could be rewritten after insertion, splicing the
person's API key into a command line. Each element is now substituted in one
pass by a strings.Replacer, which never rescans what it inserted, and the
unknown-placeholder check reads the manifest's own text. Reported by the pr-af
session with a reproduction; both faults are pinned by a test through Run that
fails on the old code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What was true: a delegate was an outside program installed by hand — a
manifest and a page in ~/.codeaf/delegates, a binary on PATH, `{{placeholders}}`
filled at launch with the person's key among them — listed by `/delegate` and
documented in the public docs/DELEGATE-PROTOCOL.md.
What is true now (the owner's direction of 2026-09-23): the programs codeaf
can hand a whole task to are compiled into it. internal/delegate is the v2
contract — a Delegate is a Go value in internal/delegate/builtin's list, with
its own commands and flags; it runs as a child of codeaf's own executable
(`codeaf <name> run --json --dir … -- <brief>`); it writes `hello`, `stage`,
`step` and `terminal` on stdout; and it reaches a model only through the model
API codeaf will serve each run, whose address and token are the only model
names in its environment. ChildEnv takes every provider key and redirection
out, so no key reaches a program or a command its model runs. A `hello` of
another protocol stops the run: the engine outlived a rebuild. The
conversation log record (delegate.Turn) is fixed here for the API to write and
the task page to read.
Gone: the manifest, its validator and fill, the loader and its folder,
`/delegate`, the manual overlay, docs/delegates and the public protocol page
(kept on the tag delegate-manifest-v1). The chat's `/<name>` rows, the door,
`propose_task`'s `via`, the run road and the squashed landing carry over.
"Delegate" is a working title, so every sentence a person or the model reads
names the program itself. docs/design/delegate/PROTOCOL.md is the internal page.
The build carries no program yet; senior-dev joins the list with its engine.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… lanes Two pieces three lanes will build on at once, fixed here so none of them invents its own: internal/provider/modelapi.ChatURL, the one place a program in codeaf's tree gets its model route from (the funnel law lets only internal/provider spell it), and delegate.ProgramFile, the record of which program a run handed its task to and the stages its hello named, written by the worker and read by the task page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What was true: a task a run handed to a program (senior-dev) read like any other task. Its page carried the store row, the brief and the steps its worker recorded, and nothing of the program: not its name, not the stages it said it would move through, not the calls it made to a model. PlanTaskPage never set the page's own Live, so the stage the worker publishes as the live step was drawn nowhere on the page, and a row named no program at all. What is true now: a row names the program its task was handed to and the stage it is in (PlanTaskRow.Program, .Stage), read off the program record in the task's own folder, or off the conversation's live run in the moment before the program's hello. A page carries PlanProgram for a task whose folder holds a program record or a conversation log: the name and stages, the newest 200 calls in the order they started with every text cut to its first line and the run's own copy taken out of paths, how many earlier calls it leaves out, and how many calls reached a model. The page's Live is set. The spend a page shows is still the store's spend rows, which is the figure that moves while the run goes once the model API banks one row per call. The run's ceiling has a field and no source yet: it is worked out when the run starts and written down nowhere a page can read. Every read is inside the page and row reads the surface already makes off its loop, and the remote wire carries the new fields unchanged on the page's own call (client_test.go proves the round trip). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What was true: a run handed to a program (senior-dev) opened on the page every task opens on — a telemetry line, the brief, the steps the program reported — with a note box and the sentence "the worker reads a note at its next step", neither of which a program ever honoured. What the program was doing, the calls it made to a model, was drawn nowhere; the rail row wore only its clock, and a plan row drew the program's stage behind the shell's `$` as if it were a command. What is true now: a program's page is the conversation between the program and the model that answered it, drawn from the page the surface already holds (taskconversation.go). The brief opens it under the program's name; each call is the program's side (a tool's result as `<tool>: <line>`, its own words, or `summarized its history so far`) and the model's (its short name, the first line of its answer, one dim row per tool it asked for behind that tool's action mark); a refused call is codeaf's one line, a failed call the model's; the call in flight is the last line, the running mark, the model and its seconds, and it goes when the call returns. The line under the title is pinned — stage (or the state word), spend so far, calls, age — so it survives the page following its bottom edge; the head and the foot are now counted by the frame, the window and the scroll alike. A program's page, and its run's tab, have no box and take no note: the foot is the rule and the keys, and typed letters are nothing. The rail row of a program's run says its stage and its spend so far; a program's plan row draws its stage and never a `$` command. Nothing drawn is zero or unknown, every row is fitted to its width, every text is one clean row, and every mark comes through the glyph door. The manual's worker-harness page says what the page shows, and three probes reach it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot all of it What was true: the new section on a program's task page said the whole of every call is kept in the task's own record. The conversation log keeps each message cut at a cap and the newest messages of a long call, so that claimed more than the record holds. What is true now: the section says the task's own record keeps more of every call than the page draws, which is what the log does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an show it What was true: a program's task page had a place for the run's dollar ceiling beside its spend, but nothing wrote the ceiling down where a page could read it after the run started, so the page never drew it. What is true now: delegate.ProgramRecord carries CeilingUSD, written with the hello, and the page reads it into PlanProgram.CeilingUSD; zero still draws nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dapted senior-dev lived in its own repository (swe-pro-go at the tag codeaf-absorb, 6103488) and ran as its own binary with its own key. Its packages are now under internal/seniordev at the same sub-paths, and the product half of its command (the pipeline, the solo run, verification, the workspace recorders, the engine adapters, the durable sessions and their tests) is the package internal/seniordev/app. This commit is the copy and only the copy, so the adaptation that follows can be read as a diff: the module path is rewritten, `package main` is `package app`, and every Go file carries a !windows constraint (the two stat files keep their darwin and linux ones). Left out entirely: internal/afield (the control plane), internal/attribution (OpenRouter's attribution headers), the tree-sitter marker file, and the command's CLI glue (main, args, the parser, cli_run, serve, cpbridge, trace) and their tests. It does not build on its own yet; the next commit makes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n folder stays out of a worktree's git Four of senior-dev's tests failed on macOS, all on the same fact: every temporary folder there is /var/folders/…, a link to /private/var/folders/…. Three were tests comparing two spellings of one folder. The fourth was real: the per-file commit measured the written file against git's resolved top level, so in any workspace reached through a link the path walked out of the repository, `git add` refused it, and every `wip(write)`/`wip(edit)` commit stopped without a word. Both sides are now resolved before they are compared, and the three tests compare resolved folders. senior-dev also wrote its `.senior-dev/` exclude into <git-dir>/info/exclude. In a linked worktree, which is what codeaf cuts for a task, git ignores that folder in favour of the common dir's, so `.senior-dev/` stayed untracked and a landing that stages the tree's status would have committed senior-dev's database and spec into the person's branch. The exclude now goes where `git rev-parse --git-path info/exclude` says git reads it. Its commit identity came from the AgentField attribution package, which is not carried; senior-dev's own commits now carry `senior-dev <senior-dev@localhost>` as -c overrides (GIT_AUTHOR_*/GIT_COMMITTER_* still win), and no AgentField trailer. Those commits never reach a person's branch as they are: codeaf squashes a run into one commit under its own identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rts only through the host senior-dev was a binary of its own. It read OPENROUTER_API_KEY and OPENROUTER_BASE_URL (or a config's apiKey/baseURL), called OpenRouter directly with attribution headers and an optional provider-routing block, printed every bus payload and a running `spend` on stdout as NDJSON, mirrored itself onto an AgentField control plane, and ended with a terminal event its CLI layer wrote. Now it is the program internal/seniordev.Program: one command, `run`, whose body takes a delegate.Host. Every model call goes to modelapi.ChatURL(host.Models().BaseURL) and leaves by one door, the backend's fetch, which puts the run's token on it over the one http.Client. No key or base URL is read anywhere, and a senior-dev.json that sets a service's apiKey, baseURL or any providerRouting block is refused by name; an ad-hoc `provider` option is dropped. prompt_cache_key, x-session-affinity, reasoning, usage.include and the tools are kept. The one "openrouter" spelling it needs is modelsource.DefaultID, read through orclient.Service. On stdout it writes only the protocol's records, through the host: a hello naming its thirteen stages in the order a run reaches them (a law in stages_test.go holds the list to the source), a stage per phase change, a step per finished tool call, and exactly one terminal, written by the body so a panic is an ending too. The terminal keeps the model's claim (its submission reason) and what senior-dev itself observed of the project's build and tests apart, in words a person reads. Bus payloads and spend stay in the process; each stage's data goes to stderr, which codeaf keeps beside the task. A stop is the truth, not a crash: SIGTERM ends the run's context, it starts nothing new, ships what it has, and says `stopped before it finished` (or budget-exhausted past a ceiling, or pass for a frozen candidate). A check cut by the stop is recorded as incomplete rather than failed, so it neither fails a submitted candidate nor restores an unsubmitted tree. Every file carries a !windows constraint, and a test holds the tree to it. Left behind with the command line: --format, --tui, serve, the control plane and the stderr trace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The build's list of carried programs was empty on every platform, so there
was no /senior-dev row and `codeaf senior-dev` was an unknown command.
Now the unix list carries internal/seniordev.Program: `/senior-dev <brief>` in
the chat and `codeaf senior-dev <brief>` at a shell, `run` its one command,
landing a tree. Windows still carries nothing.
In the same change, the chat's manual gains internal/manual/chat/senior-dev.md,
written from what the code does: what it does and when to use it, how to write
the brief, what it cannot do (it cannot ask you anything, has no step cap,
reaches a model only through codeaf, needs git unless --in-place, is absent on
Windows), where its work lands (one squashed commit, the claim and what it
observed kept apart, .senior-dev/ never lands), what a run costs (every call
through codeaf, the dollar ceiling refuses the call that would cross it, its
landing reserve), its flags, and how a run ends. Six questions in a person's
words hold it reachable; two older questions that the first draft crowded out
("what flags does codeaf do take", "is my task stuck while it waits for its
test suite") were fixed in the page, not the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Was: internal/provider/modelapi held only the route (ChatURL). A program codeaf carries had an address and a token in its environment and nothing answering at them, so no call it made could reach a model. Now: modelapi.Open serves one run's OpenAI-style chat-completions API on 127.0.0.1, opened by one minted token and closed with the run, so the token dies with it. Every call: - is refused 401 without the token, and 402 in OpenRouter's error shape before it is made once the run's metered spend has reached its ceiling, written down as a refused turn and counted (RefusedAtCeiling); - is decoded whole into the funnel's types — messages of every role with assistant tool calls and tool results, tools and the program's own tool_choice, max_tokens/max_completion_tokens, temperature, response_format, the reasoning depth (as a configured effort), the prompt_cache_key (or the x-session-affinity header) as the cache key, and working handed back on assistant messages — while the program's provider-routing fields are dropped for codeaf's router to decide; - goes out through the completer the run hands it, armed with provider.WithBilling and WithReconcile so every billed answer and every late receipt is metered once, as it happens, to Config.Bank; - is answered in OpenRouter's own shape: one chat.completion, or an event stream ending in a usage chunk carrying cost and cached tokens and then [DONE], with `: keepalive` comments every 15 s while the funnel thinks (whitespace on a whole body), and model failures as the router's error envelope with a status that means the same thing; - is written to the task's conversation log twice under one number — at its start and at its end — with only what the thread had not sent before (Restarted when the program rewrote its history), per thread. Which model answers is one rule, Resolve: the model the program asked for when one of this person's services can take a call on it, the run's work seat when none can (Served names it), and never a refusal only because this machine does not know the id; a call the funnel itself cannot serve (no key, a withdrawn model) goes out once more on the seat. Two small seams in internal/session carry it: ServesModel exposes the account pool's own "can a service answer this model" test (and RunSpec.Serves hands it to a run, read live), so the API and the pool cannot disagree; and WithOwnCacheLineage lets a marked call keep the program's own prompt_cache_key instead of the conversation's stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y call Was: the delegate worker started the program with an empty model API, so nothing it asked a model could be answered; it banked the dollars the program reported about itself in v1 `spend` records, trusted the terminal's cost_usd when it was higher, and wrote one spend row for the whole run at the end. A program refused at the ceiling ended as whatever it said it was, `crashed` included. Now: before the program starts, the worker opens the run's model API (modelapi.Open) over the conversation's own completer, with the run's dollar ceiling, the services the conversation can reach and the run's work seat (WorkSeat, the crew's own seatModel answer), and hands the child its address and token through delegate.ChildEnv; it closes the API the moment the program exits. Every metered call reaches three books as it happens: the run's live bank (bankSpend), one spend row per call under `delegate/<name>`, and this machine's spending ledger, one row per call, written here and nowhere else — the conversation folds the run's total without a ledger row of its own. Report.USD is the API's metered total; the terminal's own cost is never banked and there is no end-of-run row. On the child's hello the program record (delegate.WriteProgram) is written beside the conversation log in the task's own folder. A program the API refused at the ceiling is reported in the ceiling's words — "<name> reached the run's dollar ceiling of $X" — whatever it said it was, and the run ends on its cost limit. The v1 `spend` record is gone from the protocol: RecordSpend, Sink.Spend and Reading.SpendUSD are removed, and a spend line a program still writes is one more line the reader ignores and counts. The tests run the worker against real children: this test binary started as the program (delegate_child_test.go), asking the real API over a real socket, and a shell script that curls it; they pin the three books, the turns, the dead token, the ceiling's refusal and the cost limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sts it Was: `codeaf <name> <brief>` from a shell refused with "runs from a shell once its model API is in this build"; `codeaf --help` never named a carried program, and a typo of one was answered with nothing. Now: a shell run is the same two processes a chat's run is. The host reads the person's own profile the way `codeaf exec` does (config.Load, the crew's work seat, one adapter per model with the service's own id put on the wire, so an `openrouter/` spelling reaches the router bare), opens a model API, and starts this executable as the program's child with the API's address and token and no key, handing it the person's own line with --json added so a command they named and its own flags arrive intact. It prints the stage as it changes, each step, each model call as `model · N in · N out · $X`, and the ending — or, with --json, passes the records through as records. Every call is written to this machine's spending ledger once; the conversation, the program record and its stderr are kept under ~/.codeaf/v3/carried/<name>/<run>/. --max-cost and --max-hours stop the program from outside (the limit rung), Ctrl-C sends SIGTERM with its grace (the incomplete rung), a run with no brief starts nothing, and the exit is carriedExit's. `codeaf --help` lists every carried program in a group of its own after the work you hand it — one synopsis line and a one-line summary each, inside the eighty-cell law, the page's cap moving by exactly that (carriedPageLines) — and prints no group in a build that carries none. A program's name joins the typo suggester (commandWords), and a test fails the build when a program's name would shadow any word run() answers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd where its ending is kept PrepareProgramFolder's comment still said a run that went away in the folder is finished first, and that nothing is changed by a refusal; it is settled without a commit, and only codeaf's own notes move. The record folder's comment now names the ending written there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it, and an earlier-messages question still reaches its page dev's tab bar now names the home tab with the places bar's word, `home`; the program room's narrow test and the manual lines that quoted `Home` read it that way. The senior-dev pages' new words outranked compacting-over-and-over for "what happened to the earlier messages", so that page's heading now carries the asker's words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…task in the folder itself Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… one for a program's ending endInterruptedProgramRun called a.graph(), which builds a graph, so every conversation opened got one. An interrupted row only exists where recoverTasks read a checkpoint back, which already built the graph; read it with tasker(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… enough to fit Under macOS's $TMPDIR the control path came to 104 bytes, one over the 103-byte socket limit, so the multiplexing options were rightly dropped and the test failed on a Mac while passing on Linux CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eRun say so in one sentence ContinueRun would have rebuilt a program's run on codeaf's own workers, and an interrupted program row blamed a working copy it never had. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its card The @ block told the model to steer running senior-dev work, which reads no messages; it now names the stop. The task-record card offered enter retry on a failed program task, which the engine refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its landed card and the tasks tool A settled run's own row now carries the run's total, as its index row already did, so the landed card shows the price; a reopen settling a run reads it off the store's spend rows. The tasks tool's reader of the run's store prints the price beside the time. No book is summed from rows, so nothing is charged twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ends_on, and any task may name a program's finished run A program starts the moment it is approved and has nothing to wait in, so its depends_on was read and dropped. It is now refused before the card while what it names has not landed. An ordinary proposal naming a program's run was refused as naming no task; a run that ended done is now a dependency met, and one still going is refused in its own sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… read-only A program's task writes no worker journal, so the guest page another window opened onto one read the owner's journal, found nothing, and drew the [senior-dev] badge over an empty body. The owner view now carries a TaskPage reader (remote.Agent.ReadPlanTaskPage, which keeps the engine's refusal), the guest page asks the owner's store once whether the task is a program's, and if it is the same room gains the program's body: actions under their steps, the pinned facts line, ctrl+y for the raw calls. It stays a guest page: no stop, no steer (enter says it is reading), the owner's lanes and trail kept, and a replaced conversation is its final answer on the page read as on the journal. The journal beat stops for a program's page, and the owner's landing notice reads the page once more. MethodPlanTaskPage joins the watcher allow-list; the page's verbs do not. Ordinary guest pages are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s price is on its card and in the tasks tool Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… so their rows open The tasks place drew another conversation's running work only from a presence reading it asked of the agent, and only the in-process agent could answer. Bare `codeaf` holds a connection to its engine, so on every ordinary launch no such row was drawn (another window's senior-dev task read `enter go inside it`) and the reading page behind `enter read it as it runs` could not be reached from any real window; --no-host drew the row but has no engine to attach to. The engine launch now binds Options.Elsewhere to session.ElsewhereOf, which reads the presence files beside this window's transcript on this machine's disk with this conversation left out, and refreshElsewhere falls back to it when the agent cannot answer. --host binds nothing and keeps the card. Proven live with two windows on one engine and the stub model: the row reads `another window`, enter opens the program's actions read-only, ctrl+y turns to the raw calls, and the landing reads when the run ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… into senior-dev Resolved against dev's run road: the start lock and join-or-wait now carry the program road (a program never joins a run, and nothing joins a program's run), dev's set-aside ends an abandoned program run in its own words first, a closed conversation leaves an ordinary run interrupted while a program's run is ended and its folder finished on its branch, commits carry dev's gitSignature, the task page takes dev's trail-and-facts head with the program's pinned line in the facts row, and the prefix waivers sit on the merged measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s the program's badge, the ratchet holds The task page's trail (dev's new head) draws the program's badge after the task's crumb. The program's join refusal and the carried program move out of joinOrWait and publishRunRow, back under the complexity ceiling. Tests that meant the switch off now say off, since an empty switch is the bash belt; a limit-ended ordinary run is archived as it ended (dev's one road). The manual says a /task in a conversation where senior-dev works is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the snapshot recorder, flag or no flag senior-dev chose its git recorder unless told --in-place and ended at once with "workspace is not a git repository" otherwise. codeaf passed the flag for plain folders, but that made two readers of one question, and any launch that missed the flag (an older build, a hand-typed run) met an ending the chat had no lever for. newWorkspaceRecorder now picks git only for a work tree whose HEAD is a commit, and the snapshot recorder for everything short of that. --in-place still forces the snapshot recorder over a real repository, which codeaf keeps doing under a home-folder repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed, sent back at most twice, one branch A program's ending was a sentence on a row, read by the chat only if the person asked something next. Every program run's landing now wakes the conversation (programLandingNote) with a verdict — passed, unverified, failed, limit, crashed — which run in its line it was, its cost and one next step, under a playbook role page (prompts/program-outcome.md) and a wider bound than a settle turn (16 calls, 15 minutes). The chat checks, fixes on the branch, or hands the work back with a sharper brief. Two bounds are code: a hand-off made in the turn an ending woke is refused past programAutoRetries (2), and after a dollar or time limit, which waits for the person's word. A hand-off the person asks for starts a new line. The program's finished verdict (pass / pass-unverified) now reaches the session (Report.Verdict -> Summary.Verdict -> RunSummary.ProgramVerdict). A run handed a folder the last run of the same program left on its branch carries on on that branch (ProgramFolder.carryOn) with that run's home and start: the ending names the person's real branch, and a run that adds nothing never deletes what an earlier one committed. The landed card says `ended` and "senior-dev's ending went to the chat" instead of the program's status, and is never painted as a fault. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s its brief behind a dropdown, and opens every step The program's room opened under three titles: the trail's crumb (the conversation's name, which a conversation named after its work spells the same), the bold title row, and `Reading: <title>` over the box. The head is now the title row alone — title, badge, a `▸ brief` dropdown, the pinned facts — and the dropdown (or ctrl+o) draws the whole brief in the dim ink between the head's rules, capped at half the frame. The body no longer opens on a clipped brief, and the Reading label is gone. Every action with more to show is a press (hitAction): it opens the whole step under its line — the command or argument and what came back (delegate.Shown.Detail, filled by senior-dev's presenter from the log's command and observation) — and the same press folds it. The page's notes no longer carry the program's status sentence, claim and observation: a program run keeps `<program>'s ending went to the chat` and where the work is (programPageNote). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on senior-dev's page senior-dev's page said `wrote a.go` and `edited b.go` with nothing about how much each moved. The file tools already counted lines for edit and apply_patch; write now counts its own (writeLineCounts, metadata only — the model reads nothing new). senior-dev's step record reads the counts off the tool's metadata (lineCounts, apply_patch summed over its files), the protocol's step record carries them as `added` and `removed` — the emitter wrote a fixed list of a step's fields and dropped them until it named them — and the action log keeps them. The presenter marks a change to the work (write, edit, apply_patch, never senior-dev's own spec, pinned check or checklist) with its lines, and the page draws `+N,-M` at the action's right edge, the added lines in the diff's green and the removed in its red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZeroPoint95
marked this pull request as ready for review
September 25, 2026 14:38
Resolves the two conflicts #1495 made: Land takes the run's base and the copy record carries CheckBase beside Continues. The clock test's slow landing takes the base too, and places.md says where the here ~/codeaf chip sat, so the manual's search still reaches it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #1429 (conversation overview) and #1485 (one task page for every task). #1485 retired the page senior-dev's stored page was drawn on, so a program's task now opens its program room from every door — its row, its card, the tasks place and the run's tab. A program room draws no transcript/work tabs, the run's own tab stays the one selected tab, and the branch's guards for the retired overlays are dropped or pointed at the room. Tests that drove the old page drive the room. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Benchmarks section now carries the ten-harness DeepSWE comparison: the chart, a table of solved, cost per task, cost per solved issue and time, the limits of a one-seed run, and the V4.1 Flash and Kimi K3 runs. The per-harness numbers move into docs/benchmarks/deepswe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7ShhY74oyWjYGB3SougdE
The README keeps the chart and one paragraph; the table, the later V4.1 Flash and Kimi K3 runs, the setup and the limits live in docs/benchmarks/deepswe. The chart's footer now says only "same model". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7ShhY74oyWjYGB3SougdE
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7ShhY74oyWjYGB3SougdE
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7ShhY74oyWjYGB3SougdE
The installer printed a three-line telemetry notice after the receipt. It prints none now; the binary's full notice still arrives before the first session's events are sent. The local install marker stays. The test asserts the installer prints no notice, and docs/TELEMETRY.md loses the installer's block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7ShhY74oyWjYGB3SougdE
Nothing the installer prints mentions telemetry now; the marker is still written when it can be. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7ShhY74oyWjYGB3SougdE
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.
Draft: not ready to merge. The test of the chat's routing against a real model has not been run yet.
How
/senior-devworkscodeafbinary, so there is nothing to install and no separate key to set. That is why the diff is so large: about 69k of the ~99k added lines areinternal/seniordev, and about 37k are tests. Its solution process is unchanged; codeaf hosts it and meters it./senior-dev <brief>.via: "senior-dev". The chat prefers senior-dev for complex, multi-part coding work, like an issue in a mature codebase, and nearly always uses it when asked by name.codeaf senior-dev <brief>runs it without the TUI.[senior-dev]badge ([sd]when narrow), beside ordinary/tasks.ctrl+yshows the raw model calls.task/<title>-<id>. The person's own branch never moves. At the end, the work is committed on that branch and the branch stays checked out. A run that changed nothing switches back and deletes its branch. A checkout with uncommitted changes is refused before the card./costand home.Added
/senior-devcommand, theviafield onpropose_task, andcodeaf senior-devin the shell.internal/seniordev(the program),internal/delegate(the protocol a built-in program uses to talk to codeaf; spec indocs/design/delegate/PROTOCOL.md), andinternal/run/delegateworker.go(the host).[senior-dev]) wherever a task is named. Ordinary tasks wear none.ctrl+yfor the raw calls.internal/manual/chat/senior-dev.mdanddelegates.md.scripts/clean-run.sh(themake clean-runtarget), which opensbin/codeafon a fresh state root.Changed
via, it is sent back once.write,editand file-generating tools refuse paths inside it, and so does every ordinary task road.bashis not fenced.SIZE-BUDGETrises from 54,600,000 to 57,400,000, the size the engine adds; PERF.md shows the figures.devsantos/dev2 lands on dev: the worker harness as default, runs as work, skills from other harnesses, the chat as manager #1410 are 57,124 bytes (full) and 49,590 (lean):dev's 56,146 and 48,814 plus this branch's 978 and 776.prompts/program-outcome.md). The chat checks, fixes or re-hands the work. A third automatic hand-off, or one after a limit, is refused in code. The landed card saysendedand "senior-dev's ending went to the chat" instead of the status.▸ briefdropdown that shows the whole brief in grey (alsoctrl+o), and the pinned facts. The trail crumb and theReading:label are gone. Every action opens to its whole step on a click. The page's notes say the ending went to the chat, not the program's status.+N,-Mon senior-dev's page, the added lines in green and the removed in red. The file tools count them (writenow counts its own, aseditandapply_patchdid), and the protocol'ssteprecord carries them asaddedandremoved.bind().Fixed
runningfor ever. It now reads ended, with its time stopped.taskstool show what the run cost. Its settled row now carries the run's total, as its index row already did. It's a display figure only, so nothing is charged twice.depends_onnames unfinished work is refused before its card; before, it started at once and dropped the dependency. Any task may name a senior-dev run that ended done, and one still going is refused in its own sentence.@block or a retry on the task card.ContinueRunand the task row refuse to carry a senior-dev run on, in one sentence.session.ElsewhereOf). This also switches on the "another window is already in these files" warning for/taskon a normal launch.--in-placein a folder with no git history. It now chooses git only for a work tree with a commit, and its snapshot recorder otherwise.endInterruptedProgramRuncalledgraph()rather thantasker().Known gaps (not in this change)
incomplete, instead of its actions view./stopon a page opened from another window does nothing and says nothing.tasks.mdquotes the stale-status line differently from the code (current status unavailable — showing the last known state)./taskdefaults to the run road (santos/dev2 lands on dev: the worker harness as default, runs as work, skills from other harnesses, the chat as manager #1410). A conversation drives one run, and senior-dev runs alone in its folder. So while senior-dev works, a/taskor proposal in the same conversation is refused ("task N did not start: … senior-dev runs alone …"), where it used to run beside it on the old engine. Other conversations can still run tasks on other folders.How it was checked
make pr-ready, run in its parts, on2f2dc745d, andtest-touchedagain after the gap fixes (rebased ontodevf101ad2e7):test-quick,shard-test_test.sh, andtest-touchedall pass.test-touchedcoveredinternal/session(8 shards),internal/tui3(8 shards),cmd/codeafand every other package this branch touches.make test-lawspasses in all 28 packages.devon this Mac too:scripts/one-suite_test.sh, because this Mac has noflock;TestTheWarmTranscriptEncodeCostsTheSameAtEightyTurnsAsAtEight.endInterruptedProgramRuncalledgraph()rather thantasker(). That was the first CI failure, inTestTheSessionAcceptanceDoesNotMintChecksFromProse.$TMPDIR.devsantos/dev2 lands on dev: the worker harness as default, runs as work, skills from other harnesses, the chat as manager #1410 (the worker harness as default, runs as work) atfff90333c:dev's start lock and join-or-wait, and adds senior-dev's road (its folder, program and requested models; it never joins a run).dev's set-aside ends an abandoned senior-dev run in its own words first.interrupted; a senior-dev run is ended and its folder finished on its own branch.dev'sgitSignature, and the task page usesdev's trail and facts head with senior-dev's badge and pinned line.test-touchedpasses for session (8 shards), tui3 (8 shards) and every other touched package except three tests that also fail on a cleandevon this Mac: twocmd/codeaf/varvs/private/varpath tests, and the provider allocation test./taskrefused while it runs, the landed card at$0.05, and the stub's $0.1353 matching the conversation's $0.14.$0.05for a run whose row records $0.0492. The stub charged $0.2583 across two conversations showing $0.14 + $0.12, so nothing is counted twice;enter read it as it runson another conversation's senior-dev run. It opens the actions view read-only,ctrl+yshows the raw calls and back, typing is refused in a sentence, there is no stop, and the final page shows once the run ends;[sd]row at 110 columns, the actions view andctrl+y;/taskrefused while senior-dev holds the folder;Checklist
docs/changes/unreleased/1488-senior-dev.md.internal/manual/chat/is updated in this change..github/known-red.txt.git add -A.🤖 Generated with Claude Code