Skip to content

Merge dev (#2020–#2092) into app/v3 - #2099

Merged
santoshkumarradha merged 51 commits into
app/v3from
merge/dev-into-app-v3
Nov 1, 2026
Merged

santoshkumarradha merged 51 commits into
app/v3from
merge/dev-into-app-v3

Conversation

@santoshkumarradha

Copy link
Copy Markdown
Member

Merges dev (#2020 through #2092) into app/v3 with a merge commit. Every dev fix is kept. Where dev and app/v3 changed the same behaviour in different ways, the table says which one won and cites the commit.

Summary

app/v3 f172fcdcc ──┐
                   ├── merge commit, merge base dev@#2002 (3bf6072fd)
dev    20eb6952d ──┘
  └── follow-up commit: test and code fixes the merge needed

The earlier dev merge into app/v3 (#2009) was squashed, so git's own merge base is older than it and gives 194 conflicts. With dev@#2002 (3bf6072fd) as the base, 61 files conflict.

What lands from dev

Where app/v3 wins on purpose

Behaviour dev app/v3 (kept) Cited commit
Pairing a computer that has its own chats refuses it and counts the chats; --replace gives them up (#2087) joins, and the chats are kept and merged 1c4ec8a25 (#2031)
Teams store teams.json SQLite teams.db 3de72a4c3 (#2066)
Window displaced from the same computer — lets go at once, and the chat becomes "available to continue here" bd98788a9 (#2034)
Model that refuses a thinking effort only task clients repair it, each with its own memo any client repairs it once; the memo lasts for the process f9c0f1a1c (landed in #2009)

Conflict resolutions (61 files)

File Resolution Why
cmd/codeaf/chatv3_layout.go app/v3's v3MintSession/session directories + dev's pendingDriveOf with the sync report both changes are needed (#2085, #2091)
cmd/codeaf/chatv3_orchestrate.go union: both ExternalTasks and TaskComputers defaults dev #2085 adds task computers
cmd/codeaf/chatv3_pair.go, pair.go, pairlink.go, pairroute.go app/v3's join flow (kept chats, Paired with <name> + sharing line); kept dev's PairCommand, offer UI, unreachable-relay wording, pairingRelay(dir) 1c4ec8a25 (#2031); dev wording #2087 where it does not contradict
cmd/codeaf/engine.go one opener for every door; a failed open is tried again dev 152134cdd (#2081) keeps only a successful load, for every door. app/v3 (#1843) did this for the app door only
cmd/codeaf/syncdrive.go, synctake.go union: dev's sync-off guard (syncsetup.Resolve(...).On()) + app/v3's continuer, ReleaseUnused, relay host #2082, #2078
cmd/codeaf/pair_test.go, pairlink_test.go expect app/v3's Paired with <name>. Your chats are kept… plus the sharing line 1c4ec8a25 (#2031)
cmd/codeaf/team_launch_test.go app/v3 version dev did not change the behaviour this test checks
internal/cellstore/guard.go, seat.go, homecapture_test.go both hooks: dev's Notify (nil → log) and app/v3's Kept (secret files → log) dev b8f62f622 (#2030), app/v3 53118b3ef (#2013)
internal/directory/conditional_take_test.go both tests kept independent
internal/exec/bare/tools.go dev's payload check + app/v3's writeFileObserved 66fa77eab (#2028)
internal/keys/merge.go app/v3's metadata tie rule, then dev's team-scope ordering both apply
internal/manual/chat/*.md (8 pages), chat_test.go union of both sides' sections; pairing pages describe kept chats 1c4ec8a25 (#2031)
internal/pair/errors.go, lines.go, linkjoin.go, join.go app/v3's kept-chats join; dev's solo refusal removed; ErrDifferentChats kept for a different set of chats 1c4ec8a25 (#2031)
internal/pair/state.go dev's replace list + vault.enc.next; teams store set aside with new teams.MoveAside (SQLite store + WAL) #2080 on the #2066 store
internal/provider/endpoints.go dev's fallbacksWritten() af030384b (#2020)
internal/provider/wire.go resolveEffort: process memo → task memo (DropRefusedEffort) → catalog both repairs kept; see the effort row above
internal/remote/callclass.go, client.go, server.go, wire.go union of both sides' methods, fields, refusals (WorkRefusal, UploadFailure, SyncPending) #2085, #2089 + app/v3 app doors
internal/run/bashworker.go dev's stalledAfter + app/v3's notes both
internal/session/loop.go, session.go, sessionfile.go, usage_ledger.go, task_contract.go, task_store.go, task_run_belt.go union of fields and calls #2085 remote tasks + app/v3 tasks
internal/session/task.go, task_person.go dev's task placement placed into app/v3's split hand-off (startApprovedHandoff → placeHandoff) 23f7edd19 (#2085)
internal/session/team_nest.go, team_wakewatch.go, tools_team.go dev's delivery refusals + app/v3's provenance argument #2080 + app/v3 4efe0c70a
internal/session/stoplaw_test.go union, then removed app/v3's startHeldBeltRun/preparePendingBeltRun entries dev 23f7edd19 renamed the first to startDeferredBeltRun (in the ledger) and moved the second's publish to finishPendingCopy. The app/v3 tests they named still run
internal/syncsetup/drive.go, drive_watch.go, sendlease.go, confirm_work.go app/v3's Viewer/availableAfter/same-computer path + dev's lost hold, Extends head check, kept unsent turns on an ownership check that finds another computer app/v3 bd98788a9 (#2034); dev 9be6706b3 (#2091), 9a0322641 (#2075)
internal/syncsetup/drive_refusal_safety_test.go dev version #2091
internal/syncsetup/firstrun.go, state.go, syncsetup.go union; teams sharing state on the vault #2080
internal/teams/member_json.go, teams.go, traffic.go union of fields (ReportsTo, SyncID, …) on app/v3's store #2080 on #2066

Follow-up commit: what the merge needed

Each test change follows one rule. If app/v3 changed the behaviour on purpose, the test is adapted and keeps its intent, with the commit cited. Otherwise dev's behaviour wins and the code is fixed. No test is skipped or deleted.

File Change Why
internal/keys/lock.go code: LockHome no longer recreates a removed home (home.MkdirAllUnder) dev 96381125e (#2086): a deleted home is never rewritten. Red: TestGuardLeavesRemovedHomeAbsent
internal/provider/client.go code: learn a thinking refusal only when the effort was actually sent dev #2085 intent. Red: TestAnAlreadyRemovedEffortDoesNotTeachTheTaskClient, kept as dev wrote it
internal/provider/refused_effort_test.go TestATaskChildResendsWithoutEffort…: the task memo stays per client; an ordinary client now also repairs once. TestWithoutTheFlag… → …IsRepairedOnceForTheProcess app/v3 f9c0f1a1c (in #2009)
internal/syncsetup/drive.go + new TestOwnershipCheckThatFindsAnotherHolderKeepsUnsentTurns code: CheckLease retires the hold, waits for seals and keeps unsent turns before naming the new holder; only for another computer dev 9be6706b3 (#2091). The same-computer path stays app/v3's bd98788a9 (#2034). This test also passes without the fix (a later flush keeps the turn), so it is coverage, not a red guard
internal/session/tools_write.go code: the "content is required" refusal goes through rejectToolArguments app/v3's refusal law (TestEveryHandlerArgumentRefusalIsMarked) on dev's 66fa77eab (#2028) check
internal/identity/startup_database.go code: first-start automation rows include app/v3's run_asked/origin_revision dev's TestL4Fix2FirstStartCanJoin on app/v3's automation table
internal/config/settings.go code: register CODEAF_APP_LOCAL_GUI fixes TestRegistryCoversEveryUserFacingEnvironmentPin, failing before this merge (64ac8d622)
internal/syncsetup/teams_test.go (2), internal/remote/teams_history_test.go, internal/tui3/teamsrelay_test.go ported to the SQLite store with teamstest.Write/JSON/Break app/v3 3de72a4c3 (#2066)
internal/pair/teams_relay_test.go legacy-shape check kept, read from the store's document 3de72a4c3 (#2066)
internal/pair/teams_replace_test.go the set-aside store is read back and must equal the local teams 3de72a4c3 (#2066) + teams.MoveAside
internal/pair/l4_join_test.go TestL4PairCountsChats/OneChat → TestL4PairKeepsAComputersOwnChats (1 and 3 chats may join, and the line says they are kept) 1c4ec8a25 (#2031)
internal/pair/l4fix4_preflight_test.go TestL4Fix4SoloJoinRefusesBeforeContactingMailbox → TestSoloJoinWithChatsReachesTheMailboxAndChangesNothing 1c4ec8a25 (#2031)
internal/pair/l4_words_test.go, lines_test.go, cmd/codeaf/l4_pair_test.go, internal/manual/chat/use-this-on-another-computer.md expect Paired with <name>. Your chats are kept… 1c4ec8a25 (#2031)
cmd/codeaf/appkeylate_test.go TestOnlyTheAppsDoorOpensAFailedProcessHalfAgain → TestEveryDoorOpensAFailedProcessHalfAgain dev 152134cdd (#2081) wins over app/v3 5c5e66c7f (#1843)
cmd/codeaf/app_chat_rows_test.go the chat this computer made reads idle, not off, after its own lease lapses dev 9a0322641 (#2075, #2072) wins
cmd/codeaf/beltdoors_test.go help cap 121 → 123 dev's codeaf devbox door (152134cdd, #2081) adds 2 lines; the cap rule moves only by a door's lines
internal/session/task_stability_baseline_test.go the original-schema fixture lists app/v3's notice fields (Retryable, Source, Dismissed, Irreversible) and usage row id app/v3 6f0fc49b9, 50bc6ed3b, 86918bc1c, 1dcc1ebb1 (all before this merge)
internal/session/artifact_append_law_test.go dev's remote usage and step ledgers listed as their own stores dev 23f7edd19 (#2085)
internal/cellstore/guard_test.go, internal/syncsetup/home_removed_test.go set both hooks (Notify, Kept) both hooks kept, see guard row
internal/session/team_relay_test.go pass teams.Provenance{} app/v3's provenance argument
internal/manual/chat/keys.md the 10-second answer leads with "what happens if it will not stop" ranking moved when dev's pages joined the manual

Evidence

  • go build ./..., go vet ./... and gofmt are clean.
  • Every test named above passes.
  • Full suite (go test -timeout 60m ./cmd/codeaf/... ./internal/...): 42 tests fail on this branch, against 53 on app/v3 before the merge. Every failure on this branch also fails on app/v3, except two that passed 3 of 3 when run alone (TestAppReadsSurviveAReopenedRecord, TestARewriteOfIdenticalContentLandsWellUnderTheStepCap). 13 tests that failed on app/v3 now pass. The rest fail on app/v3 too; this PR does not change them.
  • Live run: the merged engine answered a real codeaf chat --once task on deepseek/deepseek-v4.1-flash through OpenRouter. The reply was correct and used the read tool. Every transcript and usage row recorded deepseek/deepseek-v4.1-flash.

Merge Danger

Door: two-way. This is a merge commit, so it can be reverted. No data migration.

Blast Radius: wide. Pairing, two-computer sync, teams sharing and task placement all change together.

  • A computer with its own chats now pairs and keeps them (app/v3), not dev's refusal. Dev's --replace still gives them up on request.
  • The pairing manual pages and lines follow app/v3's wording.

🤖 Generated with Claude Code

santoshkumarradha and others added 30 commits October 25, 2026 09:35
…1935)

Preserve explicit worker model selection for joined tasks and descendants. Add model-selected independent and teammate chats, atomic launch claims, updated tool guidance, and regression coverage.

Validated all five live OpenRouter launch scenarios with DeepSeek v4.1 Flash and Kimi K3. All PR checks passed.

Refs #1839.
Add direct skill definition, capability and troubleshooting answers, including memory-independent behavior. Pin the four original queries and verify tool-result delivery through real hosted terminal chats with memory saving on and off.

All eight live cases passed using exact deepseek/deepseek-v4.1-flash. All PR checks passed.

Closes #1937.
* Scope All to the current conversation tab strip

* Keep screenshot launches guarded and use rendered count grammar

* Guard the screenshot terminal option command

* Expect current-tab scope in the All navigation hint

* Scope the All wall's copy and empty state to open tabs

The in-wall All chip now says the grid holds the tabs in this view
rather than saved conversations, and an empty scoped grid says no
conversations are open here instead of promising saved ones will
appear. wallEmptyText is shared by the live grid and the currently
unused wallEmptyRow, so the two stay in step. The dock test now proves
the label and the mark each open the wall scoped to open tabs, and the
render test pins the scoped empty line.

Assisted-by: CodeAF (deepseek-v4.1-flash-1017)
Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>

* Make open tabs a property of the open wall

closeWall now clears wall.openTabs with wall.on, so no reader after a
close can mistake a closed scoped wall for an open one. wallOpen still
reads the scope before it closes the wall, which is where the team is
kept.

Assisted-by: CodeAF (deepseek-v4.1-flash-1017)
Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>

* Open an overview unpicked

A pick outlived a close that was not esc, so a card picked in the wall left
the next overview in selection mode - and in the All scope it could be a card
nobody had picked there. Every overview now opens with nothing picked.

Assisted-by: CodeAF (deepseek-v4.1-flash-1017)
Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>

* Name the All door as the tabs in view in the manual

places.md still promised all saved conversations from ▦ All, and keys.md
listed alt+v, /wall and the strip door as one thing. They are two views
since this change.

Assisted-by: CodeAF (deepseek-v4.1-flash-1017)
Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>

---------

Co-authored-by: CodeAF <267109073+agentfield-bot@users.noreply.github.com>
* tui3, config: the gate row lands on the conversation you are in, and /status names the gate (#1063)

The three Safety rows — `ask before running`, the tool exceptions and the shell
command rules — wrote the profile and left the running gate alone, while the
YOLO badge read that profile live. So the badge and the gate disagreed in both
directions: `allow` lit the badge over a gate that kept asking, and `prompt` put
it out over a gate that was still wide open, which is the false-safety claim
#322 and #325 exist to prevent.

`applySetting` now pushes those three rows through `ApplyApprovals` — the seam
/permissions already drops a banked rule through — and refreshes the badge on
the same keystroke, on the branch where the push landed. A rebuild that could
not be done is said out loud on the panel's foot line in /permissions' own
words, `saved · from the next session`, and the badge stays with the gate in
force rather than following the file.

And /status carries the posture it never carried: `approvals prompt`, `allow` or
`deny`, in the `ask before running` row's own words, in the note, in --json and
on the phone's status sheet, from the one posture reading the badge uses. The
status row keeps its negative-space badge and the segment is no longer written
into the page twice.

The note's label column is measured from the widest label it carries, so three
crew assertions that spelled `"\ncrew"` plus exactly five spaces were
assertions about every other row on the page. They read the crew line by its
label now.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* attribution: the footer names CodeAF, and still links to agentfield.ai (#1075)

* attribution: the footer names CodeAF, and still links to agentfield.ai

The pull request, issue and comment footers read "Drafted with agentfield ai".
They now read "Drafted with CodeAF"; the link, its utm parameters and the
"reviewed and owned by the author" tail are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy

* docs: the change entry for the CodeAF footer

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNBScbyNAbbEQ5jn9G6auy

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* session, prompt, tui3: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1) (#1067)

* session, prompt: a wrong completion check is answered with [no change], and the answer stands (#1065 round 1)

The end-of-turn reader saw 600 bytes of the last message, so a long table read
as cut off; the carry-on told the model to explain itself, and the explanation
became the only answer left in view under the fold.

The reader now sees up to 8 KB of the last words, and a cut it still has to
make ends with `[clipped by codeaf: N of M bytes]`, which its ask says is never
evidence of a cut-off answer. The carry-on lead says who wrote the note and
offers one exact reply, `[no change]`: it ends carrying on for the ask without
another reading, the transcript keeps it for the model, and neither replay nor
the live feed draws it — so the settled answer before it is what the fold finds.

system.md trades `# Tone` and `# Delivery` for `# The answer`, `# Words or
work`, `# When corrected` and `# Messages from codeaf`, and the two lines that
pushed action on questions are qualified. The lean page keeps all four; the
prefix waivers rise by the 1,705 bytes that cost, dated and named.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: the change entry takes the pull request's own number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* session: a quiet landing is asked about the notes it was handed, not the page that names them

The worker's system page now names [silent] and [stuck] under
`# Messages from codeaf`, so a search of the whole request found the tag on
the page. childWasHanded reads the conversation and leaves the page out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* session, prompt: [no change] answers the note it follows, and a long ask leaves the last words cut by name (#1065 round 1)

Self-review of #1067 found the token honoured on the wrong replies. The gate
read the turn's carry-on count, so a model that made calls in answer to the
note and then said [no change] ended the turn unread on an answer those calls
may have made stale, and a token after a load or ask nudge counted too. It now
reads the transcript: the token must be the very next reply to the reader's
own lead (answeredNoteUnchanged), and the dead declined flag is gone.

An ask up to the whole digest rides in its head, so eight kilobytes of last
words behind a long ask reached the backstop clip and ended in a bare `…`.
checkpointClipSaid takes the room that is left. A turn that ends on the token
no longer starts the route judge or hands the token to the memory reflex, and
a later reading's last words skip it.

`# Messages from codeaf` named `[still asked]`, which only the reader ever
receives, and left out `[stuck]` and `[held]`, which the conversation does; it
now names the tags that arrive and says any other opening, such as
`[image #1]`, is the person's. The waivers sit on the measured 1,779 bytes, and
the manual and the change entry say what the gate actually honours.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* prompt: the new sections keep the rules the old two carried, and say what stays in view (#1065 round 1)

Owner review of #1067. Replacing `# Tone` and `# Delivery` had dropped five
rules; each is back inside the new sections: match the person's task and
vocabulary, choose the safe option under uncertainty, evidence and blocking
details complete, never a compiling scaffold or a narrowed test, never
half-solved work.

`# The answer` said only the last message reaches the person, which `# Planning`
contradicts: the plan and the per-batch sentence are shown while a turn runs. It
now says that once a turn ends only its last message stays in view.

`# Words or work` sat beside `## Work or words`, which spends the same two words
on hand-off sizing; it is `# Answer or change`, and it says an answer asked for
in chat is written in the reply, not a file, unless the person asks for one.

The waivers sit on the new measurement, 2,035 bytes over dev on both arms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* prompt: the messages-from-codeaf list names every note codeaf writes

The system prompt now distinguishes conversation user-role notes from private reader and tool-result markers. A structural test follows the production constants, and the byte waivers record the exact fixed and lean measurements.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* session: no change skips judgment only when it answers the reader

The completion loop now uses the transcript context before suppressing the route judge or substituting the prior answer for memory. Tests cover the withdrawn and ordinary-token memory paths, and the manual separates counting from display.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: the model stub replays the no-change fold

The OpenAI-compatible stub can now drive the real binary through eleven read rounds, a long table, a rejecting reader, and the exact token reply. The recipe records the tmux setup and the expected log and screen evidence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* prompt: the standing-news tag stays with the event, not on the page

The messages-from-codeaf list had named [something you set up fired], and
TestTheSteeringLineReadsAsNewsAndNotAsARequest refused it: that line says
under the news itself what it is and what not to do about it, so the page's
copy is one law bought on every request of every turn for a turn most
sessions never have. The list keeps the compaction tags, the structural test
now files the standing frame with the private voices the page must not name,
and the waivers fall to the new measurement (fixed 55,280, lean 47,055).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Abir Abbas <abirabbas1998@gmail.com>

* Clean internal experiment records and revive test/ux for codeaf (#1068)

* chore: clean internal experiment records and revive test/ux for codeaf

Public-repository cleanup ahead of opening the docs to outside readers.

- Remove four internal DOE/experiment record directories that reference
  private issue and pull-request numbers and are not current-product
  documentation: docs/design/pin-doe, plan-gate-doe, turn-wall-share-doe,
  and workspace-foundation.
- Neutralize internal machine references in benchmark and design docs:
  the bench host ("spark"), its secrets.env path, and a personal working
  branch name, all now generic placeholders.
- Revive test/ux/, the user-facing journey suite, for the renamed product:
  collapse the removed v1/v2 dual-surface logic into the single v3 surface,
  repoint the binary to cmd/codeaf and state to ~/.codeaf/CODEAF_*, and
  delete gate.sh whose premise was the v1-vs-v2 parity table.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* chore: drop internal conversation-runtime wave records, keep the measurement contract

The conversation-runtime directory was a local development wave: day-by-day
status, /private/tmp paths, "owner authorized" process notes, draft PR numbers,
and local change logs. None of it is current-product documentation a public
reader needs. Keep PARETO.md, which is the benchmark's cited measurement
contract, trimmed of its dangling calibration link and owner language.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* chore: neutralize the remaining bench-host and work-machine references

A wider sweep found the internal bench host ("Spark"), its secrets path
(fleet/secrets.env), and a scratch ops directory still named in live docs,
bench READMEs and bench scripts that the earlier redaction had not touched.
Rename the host to "benchhost"/"the bench host", drop the secrets path, and
replace "the laptop" as the owner's own machine with a neutral "work machine"
where it meant the internal one. The manual's first-person "the laptop you are
sitting at" is public-facing and left alone.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* chore: fix review findings in the public-cleanup diff

- PERF.md: drop a private tracker issue reference (#846).
- PARETO.md: restore a complete sentence dropped when the calibration link was
  removed, and align tense with the remaining present-tense claim.
- home-mission-control/DESIGN.md: drop the internal "fleet" system name and
  an owner-framing phrase, consistent with the secrets-path scrub elsewhere.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* docs/design: the two records live code still cites stay in the tree

plan-gate-doe/REPORT.md is the measured evidence behind the plan-gate default
that twelve comments in cmd/codeaf, internal/config, internal/session and
internal/splitgate point at, and turn-wall-share-doe is the run
internal/session/turnwall.go says it was added from. CLAUDE.md keeps the
record on purpose for exactly this reason, and neither folder names a machine,
a secret or a private address.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test/ux: the revived journey suite goes back out

#991 removed it because it drove the v1 and v2 surfaces, and the revival did
not change that: its journal verb reads graph.db tables (messages, nodes,
usage, commands) that the v3 chat never writes, lib.sh still waits for the
resident, nothing runs it from the Makefile or a workflow, and its only
evidence was bash -n. docs/JOURNEY.md says internal/e2e is the one suite that
drives the binary, and that stays true. A suite that has been run green
against bin/codeaf can come back on its own pull request with a target that
runs it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs/changes: entry for #1068

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs/changes: the #1068 entry says why the journey suite is out, correctly

The first draft said the v3 chat never writes the graph.db tables the suite
reads. Running the suite showed both message rows landing, so that sentence
was wrong; what is true is that nothing runs the suite, it had never been run,
and its first journey fails on its first screen check (#1072).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
Co-authored-by: Abir Abbas <abirabbas1998@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* session: task commits are authored by the bot account, not codeaf@localhost

The author codeaf stamped on the commits it lands was `codeaf <codeaf@localhost>`, which no GitHub account can link, so every branch codeaf landed failed the CLA check. It now writes `codeaf <agentfield-bot@users.noreply.github.com>` - the identity the Co-Authored-By trailer already used - and keeps `codeaf@localhost` and `aforge@localhost` recognised as legacy by taskCommitIdentity.

GLM 5.3's review caught an unmarked retired name in permissions.md that reddened the namelaw gate, and seven stale test fixtures; both fixed. Laws gate, session, exec and cmd are green.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* README: show the pinned-version install line the release test requires (#1103)

* README: show the pinned-version install line the release test requires

The Install rewrite dropped the VERSION=<tag> pin example, and
TestPublishedPinExamplesGiveVersionToBash reads every published install
surface for `| VERSION=` because `VERSION=<tag> curl ...` is an easy
mistake to copy — it sets the variable for curl instead of the install
script. The pin goes back inline on the same host the section already
uses, with the releases page still linked for tags.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* docs: the change entry for the README pin example

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

---------

Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: anonymous usage counts, off with CODEAF_TELEMETRY=off (#1095)

* telemetry: the package behind anonymous usage counting, per the wire contract

Four typed events (first_run, session_started, session_ended, fault) with the
contract's allowlisted, bucketed props; the opt-out ladder with its off
reasons; a hashed install identity at 0700/0600; a fault fingerprint over
codeaf function names only; a JSON-lines spool with a deadline-bounded flush
of up to 50 events per POST, 7-day age drops and a 1000-line cap; the notice
constant with NoticeShown/MarkNoticeShown gating the flush; and Show() for a
future codeaf telemetry show.

A library only: nothing in cmd/codeaf, internal/session or internal/config
reads it yet.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: tests, doc, and contract fixes for the usage-count package

Adds the package's tests — bucketing edges, the opt-out ladder, install
identity, the notice, fingerprinting, the spool against an httptest relay
(cap, age drop, 50-per-POST splitting, a hard deadline against a hanging
server, silence on failure) and the privacy law that no sentinel string
reaches the wire. docs/TELEMETRY.md documents the notice, what is sent and
how to turn it off, with a test that fails if the doc's property table
drifts from the Go allowlist.

Fixes four drifts from the wire contract found running those tests: the
<1m duration bucket was missing, the fingerprint's argument stripping cut
too much, the rc channel suffix must be a single integer, and under go
test a config-off rung outranks the test rung in the reason reported.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: fix review defects 1-8 with tests

Fingerprint stability (last-( cut, .go:/main./created-by frame rules),
counted-batch flush, guarded and multi-process-safe spool, BucketDuration
edges, Configure-gated write paths, O_EXCL install id, install.json
preservation, buildinfo.Dirty accessor.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* installer: record the install locally and print the telemetry notice

Following the telemetry wire contract: a successful install now writes
telemetry/install.json under the state root (dir 0700, file 0600) with
the install method, channel and UTC timestamp, leaves any existing
install_id alone, and makes no request the download steps did not
already make. At the very end the installer prints the contract's
notice to stderr after one blank line, or one line saying telemetry is
off when CODEAF_TELEMETRY/DO_NOT_TRACK opts out. README gains a plain
Telemetry section pointing at docs/TELEMETRY.md, and a new
test/installer-telemetry.sh proves the marker, the notice and the
opt-out without a network.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* installer: four review corrections to the telemetry commit

The review of 71ff95a6b found four things. Revert the one line that
changed `if [[ -z "${TAG:-}" ]]; then` back to `${TAG:-}`: under
set -u an unset TAG must reach api_problem, not crash. In
write_install_marker the channel lands in JSON verbatim and CHANNEL
can come from the environment, so only stable, rc, staging or dev is
recorded and anything else becomes "unknown" — with test cases for a
valid channel and for we"ird, both asserting the marker parses
(python3 -m json.tool, or jq, else a strict channel check). Fence the
README Telemetry notice in a ```text block so Markdown keeps its
alignment, wording byte for byte. Clear the two shellcheck findings in
test/installer-telemetry.sh with one block disable for SC2016/SC2034 —
ok() evals those strings, so the quoting is deliberate — and export
CODEAF_TELEMETRY/DO_NOT_TRACK where a subshell needs them, without
weakening any assertion.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* release-downloads: daily PostHog report of public GitHub release download counts

A scheduled workflow reads the repository's releases from the public GitHub
API and sends one codeaf:release_downloads event per binary asset
(codeaf-<os>-<arch>[.exe]) — sidecars like checksums.txt are not reported.
Each event carries release_tag, channel (stable or the prerelease suffix),
prerelease, os, arch, the cumulative download_count and the UTC
snapshot_date; the event id is derived from tag, asset and date, so a
same-day rerun deduplicates instead of counting twice. No code in the
binary; counts come only from public release data.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* release-downloads: tests cover the workflow, docs and pagination

The dry-run unittests now also assert the identifiers the wire spec spells
out where the script and the workflow spell them: the schedule, dispatch,
contents-read permissions, built-in token, project-key secret and host
variable in .github/workflows/release-downloads.yml, the public-data
paragraph in docs/rules/promotion.md, stdlib-only imports, pagination
across API pages, the host override and the /batch/ endpoint, sidecar
skipping, draft skipping, and the channel words stable, rc, dev and
staging.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* release-downloads: four review corrections to the script

Channel detection now follows the tag grammar in
cmd/codeaf-release/version.go: stable, rc, dev and staging tags match
their four patterns and anything else reports "unknown", so
dev-20260917-09299019bcdc is no longer mislabelled "stable".

--dry-run works without CODEAF_POSTHOG_PROJECT_KEY, the local and test
path: the batch prints with api_key "<unset>" and exits 0, and only the
sending path stops with the notice.

Every event carries "$geoip_disable": true next to
"$process_person_profile": false, since the sender is a CI runner whose
location is meaningless. The module docstring now names
release-downloads.yml, the file that exists.

The fixture holds one tag of each shape plus an unrecognised one, and
the tests assert the channel for each, including the two real dev and
staging tags.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* release-downloads: test runs on Python 3.9 too

ScriptSourceTest read sys.stdlib_module_names unguarded; the attribute
arrived in Python 3.10, so the suite errored on macOS stock python3
(3.9.6). Guarded with getattr so the pinned import-set check stands on
its own where the attribute is absent; 32 tests pass on 3.9, 3.10 and
3.14.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* installer telemetry: the notice is read from docs/TELEMETRY.md

The test read .telemetry-contract.md, a working note that is not in the
repository and never will be, so it aborted with "telemetry contract is
missing" in a clean checkout. It now extracts the fenced block under
"## The notice" in docs/TELEMETRY.md — the same bytes as the Notice
constant in internal/telemetry/notice.go — and holds the installer's
TELEMETRY_NOTICE to it as before; a new assertion holds the README's
telemetry fence to it too, byte for byte, so the invariant the suite
existed to guard now has its third leg.

Comments in scripts/install.sh, internal/telemetry/telemetry.go and this
test now name docs/TELEMETRY.md as the source of the notice.

No Go code changed and no Go tests were run. shellcheck is clean; the
suite's 33 passing assertions include the two new notice checks, and the
same two stat(1)-related assertions that already failed on this GNU
machine before the change still fail with identical output.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* release-downloads: the download-counts paragraph lives in docs/TELEMETRY.md

docs/rules/promotion.md was the only home the paragraph had, and it came
with a fallback pinning the branch to the state before the telemetry
doc existed. The paragraph moves, byte for byte, into docs/TELEMETRY.md
as a final "## Download counts" section beside the rest of the telemetry
story, and promotion.md returns to its origin/dev content, so this
branch now touches no release docs.

test_no_telemetry_doc_on_this_branch asserted docs/TELEMETRY.md did not
exist and is deleted. The two tests that read the paragraph read it
where it now lives: both require a "## Download counts" section in
docs/TELEMETRY.md naming .github/workflows/release-downloads.yml,
scripts/release_downloads.py and the checksums.txt sidecar, and the
promotion.md fallback is gone.

No Go code changed and no Go tests were run; the property table in
docs/TELEMETRY.md is untouched. The suite passes: 31 tests, OK.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* installer telemetry: the mode check asks GNU stat first

On Linux `stat -f` succeeds and reports the filesystem, so the BSD-first
order never reached the GNU form and both permission checks failed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqfhxLrrBNQxwPNvEgoBGR

* telemetry: refuse to send to the production relay from a test binary

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: testHome points the endpoint at a dead loopback

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: the orphan-adoption test names the dead relay it now gets

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: a test pins that no test binary reaches the production relay

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: three tests pin the test-binary relay refusal

Each asserts through real calls, not a re-implemented effect: the
newRelay override lands after testHome and a flush reaches that
recorder; a default-endpoint flush is refused like a dead relay
(zero round trips, event still spooled); forcing the ladder on
cannot reopen the path.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: allocation-free session counters

Snapshot returns the SessionStats fields the SessionEnded constructor
takes, so the wiring hands the struct straight over. Cost is stored as
integer micro-dollars and divided back once, in Snapshot. Count* never
ask whether telemetry is enabled and never allocate.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* telemetry: gofmt the counters test

One alignment fix in the many-goroutine test; no behaviour change.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* config: telemetry setting row (CODEAF_TELEMETRY)

Key "telemetry", on|off, default on, env pin CODEAF_TELEMETRY, in the
interface group beside the history row. TelemetryAtIn walks the ladder —
pin, project file, profile config, default — and TelemetryOffReason
answers whether config turned the pipe off and names which rung did.
The row sits in ProjectKeys so a repository may answer for itself.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* cmd/codeaf: the telemetry command, and the setting it writes

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* cmd/codeaf: session lifecycle events at execute (work in progress)

Configure, notice, first_run and session_started at the start of every session
command, session_ended and a one-second flush at its exit. The end event does
not yet carry the session counters.

* provider: count answered model calls and their spend at the log door

* session: count sealed turns and executed tool calls

* cmd/codeaf: the session-ended event carries the run's counters

telemetryEnd builds the end event from telemetry.Snapshot so turns, model
calls, tool calls and cost reach the spool; the stop-reason switch becomes
a map over the exit ladder; and both lifecycle constructors are gated on
the ladder being on so a disabled run touches no state.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* cmd/codeaf: the counters test says the tally is process-wide

The exact turn, call and cost assertions hold for one run of the package; a
repeated run counts into the same atomic tally again.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* guard: a fault hook Recover calls, for a reporter above this package

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* cmd/codeaf: the fault event at both crash sites, off the current session

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* cmd/codeaf: a test that the crash site spools a fault and not the panic text

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* docs: finish the anonymous usage counts contract and add the 1095 changelog entry

* guard: the fault hook sits behind an atomic pointer

A goroutine recovering a panic read the hook while a test on another
goroutine wrote it, which the race detector reported. SetOnFault stores it
once; Recover loads it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cmd/codeaf: a task's interrupt word comes from the signal, not the cancelled context

stopSignals cancels the signal context at the end of every errand, so the
flag read from its Err said interrupted for a run that finished cleanly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* tui3, installer: the telemetry row has a tab, and a custom install writes no marker

TestEverySettingRowHasATab wants every registry row on the settings panel.
TestCustomInstallOutsideTheStateRootDoesNotRunAdoption wants an install
outside the state root to leave the login's state folders alone, so the
install marker is written only when the root exists or the install is in it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cmd/codeaf: the counters test zeroes the process tally first

Other tests in the package drive model and tool calls through the doors
that count, so the exact bands held only when the test ran alone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cmd/codeaf: the version test drives the telemetry lifecycle, not execute

execute starts writers under the state root that outlive the test, and the
temp dir's cleanup raced them. The lifecycle alone is what the test pins.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* cmd/codeaf: gofmt the lifecycle test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler (#1104)

* session: the carry-ladder fixtures wait for the mark's drawing, not race the scheduler

TestASuccessfulHandoffJournalsTheRungThatSuppliedTheBrief failed once in a full
make check at line 118: the worker's brief was the running model's draft, not
the mastermind's written handoff.

THE CAUSE IS NOT A STOLEN STEP. Reproduced under load (60 CPU burners, the
fixture run 1500 times), every request the fixture makes is either the turn's
own or one the aside answers: the queue carries only `round`, `draft` and
`writer`, and the errands beside the turn (title, namer, the mark's drawing)
never touch it. What moves is the ROUND COUNT — 41 to 54 across the runs —
because the mark's drawing is a sidecar (sidecar.go) whose landing the scheduler
places, and the turn is never held for it. On a busy machine the turn runs its
rounds off faster than the drawing's goroutine is scheduled onto a core, the gap
between the net firing and the drawing landing outruns the slack the script
carries ([checkpointSlack] says so in its own words), the writer's ask lands
PAST the end of the 53-step script, the scripted completer answers it with its
past-the-end line `(unscripted)` — which is not prose — the handoff rung reads
degenerate, and the carry ladder falls onto the draft. That is #392 exactly, one
mechanism along.

WHAT FIXES IT IS WAITING FOR THE READING. checkpointAgent now puts a watch on
the turn's own context ([watchReadings]) and the handover fixtures submit
through [watchedContext], so [answerWhenQuiet] holds each request that carries
the belt until every reading beside it has landed — the order a real turn has,
where a real model spends seconds on a step and the small readings land inside
it. The gap is then one boundary whatever else the machine is doing, and a
script long enough to cross the mark is long enough.

THE REGRESSION TEST holds the drawing back by a fixed wall time — standing in
for a scheduler that does not reach it in time — and insists on the behaviour:
the turn does not spend its script racing the drawing, and the writer's rung
still supplies the brief. It fails without the watch (the turn makes 54 rounds)
and passes with it.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* docs: the change entry for the carry-ladder drawing race

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

---------

Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>

* docs: the install proxy is live, and the manual says so (#1076)

* docs: the install proxy is live, and the manual says so

`https://agentfield.ai/get/codeaf` has served `scripts/install.sh` since
2026-09-16, the day the repository went public, so the compiled-in manual
and docs/GUIDE.md stop saying it is "not serving yet" and stop calling the
repository private. The proxied line goes first, as the README already has
it; the raw GitHub line is described as the script under it. Both pages say
to pipe into `bash`, not `sh`, because the script uses pipefail and `[[ ]]`
and dash rejects them, and both state the channel path (`/dev`, `/staging`,
`/rc`) beside the flags. The manual also says what the proxy refuses: a
script without its channel line exactly once, or a body that is not a shell
script, is a 502 rather than the wrong thing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs/changes: entry for #1076

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* A launch says a newer codeaf is out, and /update installs it and restarts (#1060)

* The release-tag grammar has one home, and it is internal/update

Stable, rc, dev and staging tags are parsed, compared and bumped in exactly one
place now. cmd/codeaf-release keeps its next, kind and prune behaviour and its
tests unchanged; it reads the grammar from the new package rather than owning a
second copy of it.

A second parser is the thing that drifts: the updater about to land needs the
same answer to "is this tag newer than that one" that the release tool gives,
and one source of truth is the only way they cannot disagree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* internal/update: look up a release, check it, and replace this binary with it

The launch check answers only for a build that could sensibly be behind: a
stable tag, or a release candidate whose stable line has since been published.
A dev or staging build, a make build sha and an unstamped binary make no request
at all, because a channel build is deliberately newer than stable and a source
build belongs to whoever built it. The answer is cached for a day beside the
profile, and every failure is silence.

The install downloads into the target's own directory, matches the sha256 from
checksums.txt, and only then renames over the running file, so a refused
download, a wrong checksum or an unwritable directory all leave the original
binary exactly where it was and no temp file behind. The API token never leaves
the API host: the redirect policy strips it the moment a redirect changes host,
which Go does not do on its own for another port or a subdomain. Windows has no
atomic rename over a running image, so it goes through ReplaceFileW and puts the
original back when Windows reports the one failure that moves it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* tui3: a launch says a newer codeaf is out, and /update installs it

The launch line is one dim transcript note carrying both versions, /update, and
the curl line, because the command is what the owner asked to be given. It
arrives as a command rather than an opening read, so the first frame is drawn
before the network is asked anything and a slow or refused answer draws nothing.

/update — /upgrade is the same road — names the tag, then the checksum and where
it landed, then quits with a restart plan for the door to carry out. Already
newest says so and stays. It refuses while a turn or a task is running, and once
it has started, one shared guard keeps a second /update and a new turn from
beginning underneath it: the restart must never quit on top of live work, and
the home page's Enter roads go through the same guard chat's does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* codeaf update, and the door that restarts the same conversation

codeaf update --check prints one line and exits 0 when this release is newest,
3 when a newer one exists, 1 when it could not ask. Without --check it installs
stable, or whatever --rc, --dev, --staging or --version names, choosing a
channel build by its own publish time the way the installer does. A binary built
from source refuses in place and names its path rather than replacing something
a developer is holding.

The restart happens here and not inside Bubble Tea: the surface fills a slot,
the door reads it once the frame is down and the terminal is back, and then
execs the new binary with this launch's own arguments plus --session pointing at
this transcript, so the conversation reopens where it was. A linked-local engine
is asked to retire only after the surface has detached, and it still refuses
while another window or any live work is there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* remote: a window that says it is leaving needs no torn-link grace

The twenty-second grace exists for a terminal that was killed without saying so,
and a deliberate detach is the opposite of that — the window has just announced
its departure. Carrying the grace anyway left a cleanly closed window looking
attached for two more call deadlines, which kept a freshly installed codeaf
waiting behind the engine the old one had already left.

It clears only once the last view is gone. Turns, tasks, unanswered questions,
other views and live work all still refuse retirement, and a refusal still never
stops the work it protects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* The installer and the updater agree on what a channel tag looks like

Two roads now install codeaf, and they were reading the same release list by
different rules: the shell accepted any dev-* or staging-* tag and semver parts
with leading zeroes, while internal/update accepts only the shapes the release
workflow actually publishes. A malformed tag newer than a real one would send
the two roads to different builds.

One helper carries the grammar to both channel selection and the line that names
the channel, and an unrecognised tag for the asked-for channel now says so
instead of installing whatever came back. The publish-time ordering #1056 added
is untouched: only which candidates are eligible changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* The manual stops saying that nothing self-updates

That sentence was true until this change and is now the opposite of true, so it
goes, and a section written in the words people bring takes its place: is there
a newer version, how do I update codeaf, why does it say this every time I
start. It states who gets the launch line and who never does, the opt-out
variable, the day the cached answer lasts, the shell exit codes, and both
refusals — a build made from source and an executable nobody may write.

The command table names /update and /upgrade with the argument they take. Four
probes reach the new section, and the remote page keeps its own asker's words in
its heading so the new section cannot take that question from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs/changes: the update check and /update, for #1060

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* update: --check answers for the channel it was asked about, and a newer build is never downgraded

Two things a hand verification caught. --check parsed whatever tag it had
selected with the stable grammar alone, so --dev, --staging and --rc could
never be "newer" and every one of them answered "you are on the newest codeaf"
and exited 0 while a newer build sat in the list. The check now keeps its
channel all the way through the decision and always names the tag it is talking
about: stable and rc compare as semver, a dev or staging tag is the same tag or
a different one, and --version is equal or it is not.

And a build AHEAD of the newest stable was quietly installed backwards — the
check said newest, the install downgraded it. Both roads refuse now, naming both
versions and the deliberate way past:

  this codeaf is v0.3.0, ahead of the newest stable v0.2.0 — pass --version
  v0.2.0 to install it anyway

An explicit tag IS that deliberate road, so --version and /update <tag> still
install exactly what was named.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* update: a failure names the asset and the status, and ends with the curl line

A download that failed printed the whole fallback URL, which put the retired
product name on somebody's screen. The line names the asset it asked for and the
status it got back, and nothing about which repository answered — the
compatibility attempts still happen, they are just not the person's business.
Every terminal failure now ends with the curl line, which D9 asked for and the
download path had been missing.

The / list declared no argument for /update while the command page documented
one, so the list gains the second row the way /cache has one. The manual section
now says what the code does about a build ahead of the newest stable, where it
had said such a build "gets nothing" — true of the launch line, and false of the
install until this wave.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* update: the curl line is agentfield.ai/get/codeaf, the supported road

The launch line, the refusals and the terminal door offered the raw
GitHub address of scripts/install.sh. The repository is public and
agentfield.ai/get/codeaf has served that script from main since
2026-09-16, so the constant now spells the supported line, the manual's
update section names it in place of "the curl line below", and the
change entry says so. Asked for on the pull request by Santosh.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* a run's own scratch note no longer fails the delivery gate (#1096)

* codeaf: a run's own note is offered outside the tree it is judged in

leafOutputHint invited a job root to write its overflow document at a
workspace-relative name (exec.SuggestPathFor's task-2-x1-toolchain-checks.md
shape), so a run working in a person's directory left that note untracked at
the root of their working tree. The delivery gate holds the run's record —
the artifact registry, joined by jobArtifacts, plus the delivered tree —
against the request, read the note as part of what was handed over, and
refused the delivery; the run then ended incomplete over complete work.
There is no separate mechanical clean-tree check to exempt, so the fix is
the writer's: where the workspace belongs to the person, the root's file
invitation points at the run's scratch beside its recorders and job logs,
and the message keeps carrying the deliverable.

* codeaf: a person's own file still joins the record the gate reads

TestARootLeafInAPersonsDirectoryPutsItsNotesInScratch baselined the tree
after the note was already written, so its landing read could never have
observed anything: the record stayed empty because of the ordering, not
because of the address. The baseline now happens first, in the executor's
own order, and a hint that ever points into the tree again fails on the
observation, not only on the spelling.

And the other half of the law is pinned down: a file that turns up in the
person's tree while the leaf runs — even at a name wearing the run's own
task-<n>-<slug>.md shape — is still observed and still lands in the record
the delivery gate is held to. Only the run's own note moved; nothing else
about what the tree says was loosened.

Co-Authored-By: codeaf <agentfield-bot@users.noreply.github.com>

* docs: the change note for the root's overflow file moving out of the tree

---------

Co-authored-by: codeaf <agentfield-bot@users.noreply.github.com>

* chat: the wordmark is drawn CodeAF, four rows, capitals on the d's cap line (#1106)

`┌─┐ / ├─┤ / └─┘` is not an `a`, it is two closed bowls of equal size
stacked on each other, and two equal closed bowls are an `8`. That was
the second-to-last glyph of the product's own name on the first screen
of every fresh install. The owner's answer is to have the two letters
at the end stand up out of the word as capitals: `A` is an apex, a
crossbar and two legs; `F` is the one letter whose case is told by its
arms alone, so its top arm is the longer one.

A CAPITAL'S TOP IS THE ASCENDER'S TOP, and three rows of box-drawing
could not give it that. A bar is drawn at the middle of its cell and a
stem reaches the cell's edge, so the `d` stood half a row over any
capital however it was formed — and a stem in the bottom row hung half
a row under the bowls' baseline the same way. The fourth row is that
half a row made whole: the x-height letters live in the lower three and
leave the top row blank; the capitals and the `d`'s ascender start in
the top row, the ascender as the half-stroke `╷` so its top is the
capitals' bar; and every stem that ends on the baseline ends as `╵`,
which stops where `└─┘` does. A `│` in the bottom row is a descender
and nothing else.

THE WORD IS SPELLED `codeaf` AND DRAWN `CodeAF`. `product` is unchanged,
the glyph table is still keyed by its lowercase letters, and a terminal
that cannot draw boxes still gets the word `codeaf`.

The greeting's centring test read the unit's left edge off the
wordmark's first row, which is now the cap line the x-height letters
leave blank; it reads the second row. The one manual page that draws
the wordmark had been drawing the retired `aforge` letterforms under
the label "the codeaf wordmark" — the name law walks text and cannot
see a name spelled in box-drawing — and draws the shipped forms now.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* A real release download finishes: the 3-second check clock no longer covers the asset (#1110)

* update: a release download gets its own clock, not the check's

The release client was built as http.Client{Timeout: 3s} because three seconds
is right for the launch check, and Go's Client.Timeout covers the WHOLE
exchange including every body byte. The same client then downloaded the
release asset, so a 57 MB download on a healthy link — about eight seconds
here — was cut off three seconds in and a person read `read the release
response: context deadline exceeded`.

Client.Timeout is gone. The transport bounds only what has made no progress: a
5 s dial, a 5 s TLS handshake, a 10 s wait for response headers. Every call now
takes its deadline from its context. The launch notice and `--check` share the
one CheckTimeout constant, install metadata gets 10 s per request, and the
asset gets a 30-second no-byte stall window plus a 15-minute ceiling — a dead
link fails with a sentence, a slow one finishes.

No Go deadline text reaches a person. A stall says `downloading
codeaf-linux-amd64 stalled — no bytes for 30 s`, the ceiling says `took longer
than 15 minutes`, and late headers say `did not start arriving within 10 s`.
The stall window, the ceiling and the metadata window are injectable, so the
tests prove the clocks with real slow, silent and trickling servers rather than
waiting out the shipped ones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* update: both doors read the one check constant

`codeaf update` and the chat surface each spelled `3*time.Second` when they
built the release client, which is how a figure meant for one small request
came to govern an install. Both now pass update.CheckTimeout, and the three
seconds live in exactly one place.

The terminal's `--check` takes the client's Check, which is the whole-exchange
check deadline; a plain `codeaf update` keeps Select, so the metadata requests
an install makes get the ten seconds they need.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs/changes: the update download's own clock (#1110)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* docs/changes: v0.2.1 rolled up into CHANGELOG.md (#1113)

The 33 entries that landed on dev since the v0.2.0 roll-up are the ## v0.2.1 section of CHANGELOG.md now; docs/changes/unreleased/ keeps only this pull request's own entry. Stable v0.2.1 takes its notes from that section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* README: telemetry folds under Docs (#1115)

The 23-line Telemetry section between Install and the product tour becomes one collapsed block at the end of Docs: a plain summary line, the notice verbatim, one sentence and the link to docs/TELEMETRY.md. The notice block is unchanged, so it still matches the binary, the installer and docs/TELEMETRY.md. Replaces #1112.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NoeSVN8nqmwNVRXExgoUNN

* santos/dev lands on dev: crew family, Pareto crewing, the Model Pool, custom connections (#1194)

Lands the integration branch santos/dev (#1108, head 6db5125b2, 152 commits over dev) on dev as one squash, plus two fixes that kept its touched-packages job red: the pool start-up test empties the CI word it is not about (649bb4bfd), and a zombie leader no longer keeps a job's process group alive on Linux, which had delayed every finished background job by the two-second termination grace since #1155 (053e6a90f). #1108 carries the full account; the 94 change entries under docs/changes/unreleased/ are the per-change record.

The commits folded in, oldest first:

5c413ef7b Keep archived evidence readable while tasks finish
3705a0cfe Document recovery evidence retrieval change
7834cc7d0 tui3, manual: a question's own answer box answers to the whole caret vocabulary
99d66e98c task: Add open/all source toggle to crew config
c1e4cb57f docs: the change entry for the crew model family toggle
e63b43927 crew: round-1 review fixes
304a5a223 crew: round-2 review fix
65477c0c5 task: Add open/all selector to /crew TUI picker
9189ef764 Merge branch 'task/add-open-all-selector-to-crew-tu-9e8cc0' into feat/crew-open-all-toggle
ba9c12d61 crew: reconcile the picker onto config's source helpers
ea05bc0f5 crew: a chosen family counts as an answered crew
e1a037e0a Merge remote-tracking branch 'origin/dev' into feat/crew-open-all-toggle
f6c2924a7 crew: the open table takes the picks we landed on
eff44b542 docs: the change entry carries the open-table move and the family gate
71ce9bec0 crew: the family reaches the fallback, the settings skin, and the row's line
b8ca5f513 crew: round-1 review fixes on the new table
8db5708ef crew: round-2 review fixes
ce9759ef4 crew: round-3 review fixes
956e1f514 crew: round-4 review fixes
37ad27f4e crew: fixes from the first Opus round
6ffb95813 crew: the fixes from the Opus confirmation round
72b027436 crew: the comments the atomic write left behind
70d0e205b crew: round-four fixes from the Opus review
234300e37 Merge branch 'dev' into feat/crew-open-all-toggle
894292f28 crew: round-five fixes from the Opus review
fba77f92d An answered landing carries its answer's fate when it is raised again, and an in-flight settle holds the question down (#1077)
e69445058 Changelog entry for the landing-answer fate (#1086)
64ab7eae0 crew: round-six fixes from the Opus review
070016419 crew: round-seven fixes from the Opus review
af09aa852 The review round's four findings: claim ownership, decider changes, recordable answers, plain words
c24af0248 crew: round-eight fixes from the Opus review
289d372b9 The tests the landing's answer carries: fate, hold-down, redraw, claim doors
7e38346a9 The take-down comes before every mint, and the tui3 walk reads the newest card
dbe1be03e The review rounds' last word: the release owns its claim, and the docs say what the code does
4cc4945a7 The Opus review: a release owns its generation, and every failed round still says its word
a5cce0dde The done card says who is deciding, so the clause is a duplication, not the fact
6bd276e6c Merge fix/1077-landing-reraise into santos/dev
271016938 Merge feat/crew-open-all-toggle into santos/dev
b459399d3 Merge fix/question-answer-box-caret-keys into santos/dev
a38492026 Merge codex/recovery-evidence into santos/dev
dc22e6461 cmd/codeaf: the role-door tests read the shipped mastermind instead of a copy of it
39f528f70 release: the pin-example check reads the guide the examples moved to
6439d3cb8 session, tui3: gofmt the three files santos/dev merged unformatted
854037967 tui3: a question's answer box kills to the end of the line on ctrl+k
d93ec61ea session: the late-attach proposal test holds the work until the replay is read
a81ead4de session: the landing-fate tests read today's clock instead of a fixed date
8fe4fff38 provider: the retry-avoid list rides the call's context into the knobs
a266da3e2 provider: the retry-avoid veto narrows the wire through one field law
197d89334 exec: a node retry asks the router to avoid the lanes that failed it
2d02cc0b9 provider: the set-servable law's doc comment keeps its paragraph break
e1d0e7524 exec: the retry test names its helper and spells its wall in time units
e7d65a517 docs: the change note for the retry that avoids the failed upstream
11decbd92 Merge fix/provider-failover into santos/dev
c099a51c2 Merge dev into santos/dev
e47d92bde provider, exec: the lane a failure names is a wire fact provider reads (#1105)
396be97be Merge dev into santos/dev
3b97f50c3 Merge dev into santos/dev
ea3b5c9a0 telemetry: the send path drops events that cannot name their version
573b288e4 cmd/codeaf: keep smoke runs from sending usage counts
e8d627c6b docs: the change note for the usage-count audit fixes
11bec8d80 chat: a hosted session's usage counts are read off the events it receives
b9d75b39e chat: the hosted-count tee is tested against the tally it must produce
39fa08d3d telemetry: stamp a version in the lifecycle tests
681e2adf1 chat: count the hosted boot agent once, and conversations opened beside
825ef2c41 connect, settings: named, multiple, switchable custom connections (#1092)
f4d3f9a1a Merge branch 'dev' into fix/telemetry-audit
76fec37e3 ci: the Model Pool mirror workflow and the signature check it runs
2fb296bf1 crew: Pareto crewing, the learned picker and the Model Pool (#1093)
a065ebb1b Merge remote-tracking branch 'origin/dev' into santos/dev
80ac250a0 Merge remote-tracking branch 'origin/feat/pool-mirror-workflow' into santos/dev
6092393ab Merge remote-tracking branch 'origin/fix/telemetry-audit' into santos/dev
ef808dda4 session: a decision card under --yolo takes its default (#1117)
70a9e1f6e session: a task's thread stops its parts before it closes, and a machinery cut says so (#1118)
c4df8f364 crew: an auto seat's rung names the pick that ran, once, for both doors (#1119)
a9fcf9bc8 crew: a bare auto row under picked from = catalog reads the catalog's figures alone (#1120)
13be0dce4 relay: accept the exec and run doors, not only task and do (#1121)
90f29aec1 changes: name the #1120 note by its PR and stamp the pr field
0b77926ca do: seat from the settings the run will use (#1122)
32148d87d changes: stamp pr: 1120 on the #1120 note
113ed628e pool: a launch's start-up errands stop when the process closes (#1124)
6c98ddca3 verify, codeaf: a launch compiles fewer patterns and a surface holds fewer Ps (#1125)
50fe6a3e4 pool: the min_installs floor counts installs on the client (#1127)
076cde8ae relay: fold a resubmitted row once by its nonce (#1128)
8339ae71b relay: carry the acceptable metric beside role_quality (#1129)
0367c441b outbox: a refused row leaves the outbox and the rest of its batch is sent (#1130)
09aecd2f1 relay: the index holds only the cells that meet the min_installs floor (#1131)
ae7c05dcd outbox: a Send compacts the file to the rows it still holds (#1133)
9e24f72d8 pool: the errand tracker's critical sections unlock from a defer (#1134)
358a569aa relay: fold a submit batch with one read and one write per distinct cell and day (#1136)
a1d2eb1f7 pool: judge landed-but-unjudged runs on the next chat start (#1132)
3c6722859 verify: take the finished-tree check at a project cloned into a subdirectory (#1135)
263625b63 exec: a headless run leaves a pending judge record at its tail (#1137)
8be373d39 run: the headless task door leaves a pending judge record (#1138)
a336b246e do: leave a pending landing for the Model Pool's judge (#1139)
8820a5659 cmd/codeaf: one readPendingRows test helper, not two (#1140)
49760fb45 pool: show and verify print both metrics the index carries (#1141)
6eaeb25c9 pool: status shows the pending judge records and the last sweep (#1142)
7c00b5f2c codeaf: a test's crash fixture stays in a profile the test owns (#1145)
fc2eb357a manual: the status row's ledger is the tree's, not the conversation's (#1143)
a06b53a91 test: pin the delivery gate over a grown job root (#1147)
7231347eb resident: the wall refuses a first replan round it cannot hold (#1144)
5e257557f cmd/codeaf: the package's test floor clears a profile the environment named
4dd1c562f Revert "cmd/codeaf: the package's test floor clears a profile the environment named"
9075f863c pool: the seed index is regenerated from the relay by seedgen (#1148)
a5c44f3d2 pool: show --cells lists the held index's cells with installs and dims (#1149)
825996739 cmd/codeaf: the package test floor clears a profile the environment named (#1150)
81568aa82 session: report gitignored build products a run leaves, without gating on them (#1152)
da4910593 internal/tui3: the opening hint test owns its profile and pins both doors
72de3bca7 docs/changes: #1151 — the opening hint test owns its profile
499083d1c Revert "docs/changes: #1151 — the opening hint test owns its profile"
beb31ecf7 Revert "internal/tui3: the opening hint test owns its profile and pins both doors"
25b8b7dea version: an unstamped binary names the condition, not a Makefile target (#1151)
c833b423a pool: a dropped row keeps the relay's reason and status shows it (#1154)
cbb7d6d9e processgroup: verify a job group's identity before signalling it (#1155)
efe498ed0 internal/tui3: the opening hint test owns its profile and pins both doors (#1153)
17b3b0e40 do: a continuation continues the plan it has instead of re-planning (#1156)
22cf3ce17 changes: correct the #1155 and #1156 notes' pr fields (#1157)
8e85b0fe7 cmd/codeaf: a surface runs under a soft memory limit drawn from the machine (#1158)
ef1e92756 task: status row accounting (#1159)
44f572e26 pool: status's index line says what this run's fetch did to the cache (#1161)
3600fc9a5 pool: the pool's copy of a seat's id is the bare vendor/id (#1163)
a2ebd1397 cmd/codeaf: no test can push pool rows to the default relay (#1164)
4fb7f0546 docs: the road a chat door bash push took under --yolo (#1160)
69219a1e1 cmd/codeaf: the soft memory limit is the smallest real bound, not the host s memory (#1162)
2b155b4b4 relay: bound judge severity, clamp adjusted scores, publish mean and sd on one scale (#1165)
2fb55324a relay: purge fixture vendors and refuse them at the door (#1167)
57504187a exec: a job's shell cannot reach the tmux server hosting codeaf (#1166)
eb7b46559 internal/session: an unattended run may not move a branch it did not create (#1168)
80f770639 pool: status says the identity, the judged total and that nothing has landed (#1169)
1cb7333fa exec: an exec run's worker spend reaches the usage ledger (#1170)
96bb9402e changes: the #1169 note's title parses and reads as one line (#1172)
627236d95 config: the task door honors a stored crew word, and a pin survives the learned pick (#1171)
593092be6 internal/tui3: a test may not read a profile it did not create (#1173)
ad8be1ed7 session: a landing card under --yolo takes its default instead of parking the run (#1174)
03d49a678 gitignore: the .furrow ids the engine writes into a workspace (#1175)
46edbdd32 internal/tui3: gofmt the profile-law helpers from #1173 (#1176)
3179e3611 relay: ALLOWED_VENDORS names the catalog's vendors and codeaf (#1177)
397a145a6 docs: why a settled /task leaves commits on task/<slug>, not the worktree branch (#1178)
915ff3a76 pool: regenerate the seed index from the relay's first clean publish (#1180)
d9b42430f cmd/codeaf: the model warm stops when the process closes (#1179)
bd2dcf334 session: the auditor's calls say they are the auditor's and which node they check (#1181)
c6ed47c5a task: json unverified branch (#1182)
360352ad4 session: a settle turn runs under the checker's own bound, not the run's wall (#1183)
814e1af0e session: the tasks text and record line name a failed/unverified node's kept branch and verdict (#1184)
7c25a26d9 internal/session: the identical-effects test stops riding the turn guard (#1185)
6e7d0ed44 cmd/codeaf: exec and run envelopes name a non-verified node's kept branch and verdict (#1186)
f4079d716 session: bound a worker's park on a promoted command (#1187)
994689fe7 docs: the model pool runbook names the Workers Paid plan and what it costs (#1188)
c315be13e docs: the runbook's plan line says metered, not capped, and nothing more (#1189)
2e80c60d8 internal/tui3: the status word says working while a task subtree turns (#1191)
99363ca8f session: bound a job subtree's CPU and process count (#1190)
6db5125b2 docs: what the audit check seat can execute, and where that boundary is set (#1192)
649bb4bfd cmd/codeaf: the pool start-up test empties the CI word it is not about
053e6a90f processgroup: a zombie leader does not keep a job's group alive
b4e66f515 docs/changes: the entry for #1194

Co-Authored-By: Santosh kumar <29346072+santoshkumarradha@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: Model Pool mirror workflow (signed index onto the model-pool branch) (#1116)

* ci: the Model Pool mirror workflow and the signature check it runs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: the change entry for the Model Pool mirror workflow

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: santoshkumarradha <eng@agentfield.ai>

* chat: the unconnected custom row on /connect keeps its vendored name (#1195)

modelConnectionStatus renamed every custom-id row to the connection's Written word, and the catalog template's Written is the bare id, so a person who had connected nothing yet saw `custom` where the manual, the README and #1107 say `Custom OpenAI-compatible API`. Only a connected instance is called what the person called it now; the contract test draws both rows and is red without the fix. Found verifying the staging build of #1194.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs/changes: v0.3.0 rolled up into CHANGELOG.md (#1196)

95 loose entries — the crew family and Pareto crewing, the Model Pool and its relay, custom connections, the telemetry audit, the update door's download clock, and the fixes that followed — become the `## v0.3.0` section the stable release reads its notes from.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: a changed directory in a nested module is not a package of the root module (#1332)

The touched-packages job and the Makefile's test-touched target both derived the test set as every directory holding a changed .go file, and handed a directory under its own go.mod to go test, which answers "main module does not contain package" and fails the job as a setup error. Both walks now stop at the first go.mod above the directory and skip nested modules, so the bench fixtures under bench/bashloop/fixtures are left alone and make pr-ready derives the set the gate will.

Verified on the 621-file diff that surfaced it: 43 directories to 36, the seven dropped are exactly the fixtures, and the two halves print the same list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* santos/dev lands on dev: the worker harness, stopping and limits, the task page and rail, goroutine ownership at close (#1108)

Integration branch santos/dev, held as a closed slice at d39a4e308 plus the empty-prompt guard for generate_image (3a1f80f9c). Everything through #1192 had already reached dev as #1194; this carries #1193 and #1203 through #1331.

The worker harness: one loop under /task and codeaf do with a plan store per run, a plan seat, a work seat, a check seat and a review round after each piece of work, off unless CODEAF_TASK_BELT=bash is set, so the default road does not move. Stopping and limits: a person's stop reaches a run, a time or dollar limit ends a run and names itself, a run is handed what is left of the conversation's limits, a worker that stops making progress is told once and then ended. The task page and the rail: the plan as a tree, a run's row with its parts under it, step rows cut from the commands as they ran, a press on a run's row opens its page in under 0.2 seconds. The engine host and sign-in, the profile's composed writes across two processes, unread config keys said once. Nothing keeps writing after the process closes: the place sweep, the standing ticker, the usage writers, the pool judge sweep and the catalog warm are each owned, stopped and joined, and a law lists every goroutine a conversation process starts. The heavy-suite lock is a kernel file lock that follows the suite. The lazy lexer registry, one fuzzy matcher for every picker, the drafted-with footer on the CodeAF repository.

Reviewed 2026-09-21: gates green locally and in CI once #1332 taught the touched-packages walk about nested modules; the default road verified live on both roads against a dev baseline; the one regression the slice introduced (generate_image had lost its empty-prompt guard) fixed on the slice with a contract test.

Co-authored-by: Santosh kumar <29346072+santoshkumarradha@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* a dev build installs beside codeaf as devaf and follows the dev channel (#1333)

* install: --name installs the binary under another file name beside codeaf

The installer takes --name WORD and CODEAF_INS…
* experiment: add sourced knowledge articles and local wiki lab

* experiment: learn emergent subjects and audit memory costs

* experiment: compose sourced guides with bounded knowledge learners

* experiment: tighten knowledge support and reference usefulness

* experiment: add incremental knowledge workflow and validation

* experiment: add canonical inline knowledge links and backlinks

* experiment: connect project knowledge to Memory and local chats

* Polish knowledge reader links, sources and large-vault navigation

* Complete knowledge reader hover feedback and shared selection

* Fix knowledge sidebar scrolling and add folder controls

* Organize knowledge sidebar, format sources and configure its model

* Add durable knowledge page editing and deletion

* Document project knowledge in release notes

* Align shared UI assertions with Memory navigation and switch

* Explain automatic project knowledge capture in internal manual
* docs: organize product and developer knowledge by audience

* docs: record documentation organization in change history
* Fix worker first-turn wake after delayed team membership

* Document delayed worker startup fix

* Keep new chats and returning tab history visible during setup
* fix(tui): keep navigation usable while opening a chat

* docs: record opening chat navigation fix

* docs: clarify corrected opening status
…1948)

* Keep chat startup and navigation responsive during background work

* Tighten latency audit synchronization and cancellation evidence

* Document responsive startup and resume behavior
…1951)

* fix(tui): keep cold team tab opens off the input loop

* docs: record responsive cold team tab opening

* fix(tui): settle team selection after a cold tab opens
* Make add-device card clickable with an editable pairing field

* Exercise pairing card and input clicks in terminal journey

* Document clickable add-device pairing field
…1950)

* Share pure directory records and canonical coordination schemas.

Keep legacy lease decisions and JSON behavior intact while making the shared records usable without HTTP or SQLite. Define a strict typed v2 boundary with exact uint64 and sealed-byte round trips across Go and JavaScript before consensus code depends on it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Start an independent sequential coordination oracle.

Give the fault simulator an ownership, vault and revocation model that cannot inherit defects from production apply functions. Keep candidate history states independent and pin observed-take and displaced-write behavior before the history checker is added.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Pin the shared Raft source with per-node election randomness.

Native and WASI members must use the same reviewed election seam and protobuf wire implementation. Retain upstream source and notices, record the narrow patch and source hashes, and expose only these dependency directories through the existing ignore rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Make consensus effects wait for explicit durable completions.

Wrap RawNode in a seeded single-threaded input and effect contract so hosts cannot release dependent messages or authority reads before persistence and application. Bound read contexts, reject peer-injected control messages, stop on storage or schema failures, and pin election replay with focused quorum and transfer smoke tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Define a shared reactor ABI so native and hosted members use the same Go core.

Keep consensus synchronous behind owned binary buffers and explicit durability completions. Bound frames and handles, audit the WASI runtime imports, and exercise real lease rules over immutable paged rows without adding a production writer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prove the Worker boundary locally before building the hosted cluster on it.

Compare native and wasm effects through real commit and ReadIndex exchanges, restart disk-backed workerd storage, and measure the complete package and a grandfathered-size fixture. Run consensus and bulk memory phases serially so the proof fits the shared laptop budget without changing the Worker workload.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Persist native Raft members before releasing dependent effects.

Keep each member in its own FULL-synchronous SQLite WAL store so votes, conflicting suffixes and application results survive restart. Refuse corrupt authoritative state and commit rows, dedup results, configuration and the applied index together.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prove native recovery across persistence and application failures.

Cut subprocesses at every transaction boundary and observe actual WAL flushes before acknowledgment. Exercise production cores with three real stores so leader loss, joint configuration and dropped application completions recover without losing committed results.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep durable coordination checkpoints without retaining learner history.

Stream complete application snapshots, retain two checked generations and atomically install their state before acknowledging Raft persistence. Compact only covered applied history so a disconnected learner catches up without pinning the log.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Carry portable snapshot maintenance across the reactor boundary.

Preserve ABI-1 effects while adding checkpoint completion and maintenance queries. Pin the snapshot schema and source hash in the Worker bundle so native and hosted adapters share the same format.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Add an independent bounded checker for coordination histories.

Keep timed-out operations as unknown outcomes and retain session results and tombstones so the oracle can detect stale reads, double takes and duplicate execution without sharing production rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Exercise the production Raft core with deterministic durability and network faults.

Drive explicit write, flush, application and reply boundaries through seeded virtual-time schedules, preserve portable snapshots across recovery and compare the disk model with native SQLite. Keep replayable counterexamples and require broken fence, read and flush controls to fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Wait for durable results and repeated election attempts in fault campaigns.

A lost proposal can still be uncommitted after draining messages, and randomized elections can split votes beyond a fixed 25-tick wait. Preserve exact replay regressions, check each election timeout separately, and run 500 seeds by default with an explicit larger-soak count.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Authenticate and bound native peer delivery so learner backlog cannot displace voter traffic.

Bind relay exchanges to committed identity and member generations, preserve signed opaque bytes across reconnects, and reserve bounded inbox capacity for voters and snapshots. Publish a shared native and JavaScript wire vector.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prove native quorum progress through outbound relay exchanges before production host assembly.

Mount the peer handler through the relay host boundary and drive three durable native members through election, committed application, ReadIndex, restart and absent-learner progress without inbound member listeners.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep hosted storage framing on the shared Go core.

Expose bounded protobuf inspection, streaming hashes and portable snapshot pointers so hosted storage can persist consensus without a second application writer. Exercise the same protocol through native SQLite proof peers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Persist hosted members before their coordination effects escape.

Serialize each actor across durability waits, authenticate bounded peer inboxes and stream retained snapshots through the shared reactor. Add the local compiled-module entry and independent object binding while leaving the directory cutover to a later change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prove hosted recovery beside real native peers.

Drive signed mixed-runtime commits, quorum reads, both snapshot directions and joint-state restarts in local workerd. Bound admission, retained state and co-resident memory before the directory adapter uses these actors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Decide directory mutations and retry results in the shared Go state machine.

Verify committed enrollment and signed requests at application, retain exact sequence results, and expose bounded dependency evaluation through the same native and WASI ABI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Commit native directory responses only after quorum and durable application.

Adapt every Client verb to the consensus boundary, assemble immutable native read-sets and atomic write-sets, and prove the public directory and HTTP doors with real stores and the independent ownership model.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Route hosted member application through the shared Go evaluator.

Keep JavaScript limited to dependency bytes and durable writes, expose the migration-ready facade, and prove signed hosted decisions with native ABI parity while preserving the earlier workerd mechanism fixture.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Require committed upload evidence and install pairing authority before delivering grants.

Keep effect signing separate from personal authority, bind approvals to authenticated request keys, and retain exact outbox outcomes across crashes. Preserve original v1 upload ordering while requiring evidence for v2 publication.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Make hosted blob and pairing effects durable and safe to replay.

Reserve each public request for one cluster, commit its device authorization before exposing the sealed grant, and repair interrupted uploads and delivery acknowledgements without deleting content.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Freeze legacy namespaces before importing one durable coordination history.

Keep the source irrevocably gated, preserve its complete rows and watch evidence, and resume the same three-voter genesis after interruption so old and new writers cannot overlap.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Resume hosted imports through bounded durable row staging.

Drain admitted legacy effects before freezing, fence incomplete member activation, and compare the hosted genesis with native storage so eviction and retries retain the original authority and content.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Record durable learner intents so pairing cannot enlarge the voting quorum.

Keep admission separate from applied configuration, retain every retired node ID, and complete authorized joins after the requester sleeps. Replay configuration at each log position so follower batching cannot change policy. Prove bounded churn, snapshot catch-up, exact retries and restart with the production core and native stores.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Derive hosted peer authority from committed membership so stale sockets cannot restore retired nodes.

Page registry policy through the shared Go reactor and use the applied join barrier before reporting readiness. Preserve the source-provisioned three-anchor bootstrap, isolate the old mechanism seam in its test fixture, and exercise real workerd restarts with a dead learner. Document enrollment limits and refusals with the implementation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep leadership policy and ranked election timing durable across fallback.

Use committed owner intents and explicit joint consensus for personal leadership, require a fresh quorum read for completion and retain it to prevent automatic regain. Rank randomized election windows from the immutable provisioned anchor manifest while preserving quorum and vote rules and exact versioned replay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Carry leadership and anchor rank through native and hosted control doors.

Recover the committed anchor order through registry and import snapshots in both runtimes. Serialize readiness and control effects, recheck current authorization, persist original request retries and document the actual commands, refusals and fallback limits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prove ranked fallback through conditional writes and both runtimes.

Retain every original split-vote seed and all joint crash cases, measure every seeded conditional acquire and preserve old exact replay. Exercise native process death, WASI promotion, snapshot restoration and minority refusal under the existing laptop resource ceilings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Commit fresh hold evidence and durable idle barriers.

Keep renewal authority in the shared state machine because a socket cannot extend ownership outside quorum. Require every voter to apply a generation before timer suppression, and invalidate it across wake and recovery. Prove the policy with native persistence and seeded partial-delivery histories.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Require committed lease receipts before suppressing heartbeats.

Keep presence advisory because pongs cannot prove durable renewal. Recheck quiet mode for retained clients and watch streams so a later sync-off decision closes every sending path. Document the ownership and idle limits beside the continuation journey.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Drive hosted renewal and idle scheduling through the shared Go policy.

Serialize timer decisions with durable dispatch because a concurrent wake must retain its alarm. Bound evidence pages and transport retention, and require exact batch acknowledgments across restart. Exercise the production actor, alarms and native replay locally.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Delegate selected chats without exporting personal identity authority.

Bind expiring worker certificates to exact chat scopes and retain sealed grants. Commit registration, availability, drain and revocation rules with incarnation fencing so replacement processes cannot reuse old authority.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep native and hosted worker access behind committed policy.

Return only selected chat grants after the host quorum barrier and derive learner routing from current worker scope, expiry and incarnation. Exercise real native recovery and byte-identical WASI policy to preserve the same authorization boundary in both runtimes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Explain delegated worker trust and its current limits.

Describe selected-chat access, explicit learner metadata disclosure and draining in the embedded manual while keeping worker setup and assignment execution absent until their client lifecycle is implemented.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Commit explicit worker assignments with conditional chat ownership.

Keep scheduling decisions and chat fences in one durable application transaction so stale continuations, revoked workers and replaced processes cannot claim the same observed work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep worker execution behind staged handoff and durable local intent.

Reuse selected content, ordinary upload and handoff dependencies while retaining original signed retries and refusing automatic tool replay after interruption or reassignment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Describe delegated claims and interrupted worker recovery.

Keep the manual and adapter contracts accurate about conditional claims, child results, recovery limits and the remaining public setup work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Retain original device proofs when adapting v1 requests to committed commands.

Check the original HTTP certificate, route, body and signature at application so forwarding cannot manufacture personal authority or a new observed holder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Pin the production compatibility door and explain older-client refusals.

Retain a failing real-relay contract gate until namespace migration and member assembly replace the legacy writer, and keep the manual accurate about safe continuation and incomplete saves.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Preserve certified coordination and portable snapshots at durable boundaries.

Keep returning holders usable at their imported capacity without enrolling extra rows. Bind operator policy and anchor keys to the source manifest, retain retryable authority failures, and preserve the exact portable snapshot encoding after the library fills default fields. These shared boundaries let the production relay use the same deterministic policy without weakening its storage checks. Give committed read callbacks the actor context so losing a client response cannot cancel durability.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Replace the persistent relay writer with three durable local anchors.

Freeze and preserve the original namespace before one resumable three-store import, then require quorum commit and fresh reads for directory, rotation and blob authority. Keep pairing and watches working through committed effects, recover returning stores through checked snapshot chunks, and refuse to recreate lost voter identities or re-import a published voter without its own receipt. Keep disconnected clients from poisoning shared actors. Document the native topology and limits, include the modified Raft notices, and record the measured runtime cost in the size budget.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep original v1 proofs stable across typed hosted boundaries.

Normalize empty frame hints and accept the original safe rotation schema marker so canonical forwarding retains the signed operation. Prepare restricted hosted effects, typed legacy projections and committed authority checks in the shared Go ABI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Route hosted identities through the same durable three-anchor namespace.

Freeze and resume first-use imports with one manifest before publishing quorum-backed ownership. Keep blob I/O outside the coordination queue, recheck authority after it, exclude unclaimed replacement stores and retain resumable all-replica retirement.

Distinguish the local platform name table from unknown retained application data, and finish KV transport cleanup after an interrupted retirement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prove the public hosted compatibility and recovery doors.

Exercise migration cuts, majority loss, original v1 takes and pairing, retained wide numbers, stale snapshots, result provenance and rotation through the exported Worker. Reuse the real two-home Go journey and pin the actual lost-race screen sentence while measuring local runtime budgets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Document the irreversible hosted directory upgrade.

Describe the exact v3-raft-members migration, preserved source backups, quorum refusals, older-client behavior and all-replica retirement in the hosted relay README, the reactor notes and the shipped manual.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Bind scoped cluster ingress to original credentials and committed authority.

Authenticate selected requests before activation and repeat scope/time checks at the quorum-backed view. Retain original personal certificates without overriding committed roles or revocation, and initialize imported anchor authority through the original manifest instead of rewriting frozen rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Connect native scoped requests and watch receipts to durable coordination.

Serialize current-time commit, fresh ReadIndex and selected reads. Carry only committed hold receipts, replay bounded outboxes and stop production timers through the shared all-voter sleep protocol. Exercise actual HTTP workers, delayed renewals and ten minutes of virtual idle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Connect hosted scoped ingress and idle maintenance to the shared Go policy.

Use additive ABI doors rather than JavaScript authorization or lease rules. Preserve hibernating certificate evidence, original request bytes and durable results, and prove production worker claims and idle scheduling through the local Worker.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep the missing personal-member journey visible before release.

The production personal learner transport and leadership door remain incomplete. Retain an end-to-end failing acceptance test and describe the actual limit in the manual so adapter proofs cannot be mistaken for a finished journey.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Wait for owned journey engines to exit before removing their homes.

The keyless journey can finish its assertions while an engine daemon still writes the temporary store. Wait for the same owned processes after killing them so cleanup records real process termination before TempDir removal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Expose committed native cluster status while refusing unfinished leadership.

A signed personal status request must cross a fresh majority barrier and current revocation checks. Report only committed membership and refuse leadership before saving or proposing an intent that the production host cannot complete. Keep the missing learner lifecycle visible in its unchanged acceptance test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Reuse hosted fixture bundles so runtime restarts preserve their program.

Cache each normal or injected-crash bundle once per fixture. Restart Miniflare over the same persisted state without rebundling, retaining every scoped-flow assertion and its original timeout. Pin the absent hosted control route across restart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Serve durable personal learners through the native relay's committed membership.

Preserve each paired device's original signed admission, bind node authority permanently to its store, and reconcile separate learner configuration after a restart. Route authenticated exchange and socket traffic through the native member queue without waiting on learners, stream bounded snapshots independently of compaction, and require signed durable application evidence for readiness. Keep the provision immutable while fencing restart cursors, and preserve legal maximum command writes through snapshot chunks. Keep three anchor voters and all-voter sleep with fresh quorum wake, and pin the hosted member doors absent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep a completed device removal visible when a later list read fails.

Retain the action result independently of refresh errors so a delayed devices-list failure cannot erase removal confirmation. Preserve initial-load error behavior and test both callback orders without altering the retained assertion or its predicate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep passive learner transport authenticated without waking an idle quorum.

Learner replies that arrive after the all-voter sleep barrier must not restart periodic coordination. Keep the existing response deadline while deliveries await acknowledgment, and let empty subscriptions wake passively. Device signing callbacks retain the canonical wire without exporting private seeds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Run durable personal learners inside the existing home sync lifetime.

Persist store identity before authenticated admission and finish real snapshot and log application before reporting readiness. Reuse authority on reopen, replace it after store loss, and check current quiet policy at every HTTP, socket and delayed callback. Share local applied display and follower hints across Home handles so idle native homes stop recurring requests while ownership actions retain fresh majority reads and hosted sync keeps its existing polling. Expose committed learner identity and readiness in cluster status.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep device guidance readable while documenting native learner sync.

Move coordination and worker machinery into its own manual page while preserving device sentences and every retained retrieval probe. Explain native durable learners, the local Home follower, the actual healthy backstop, hosted polling and the continuing leadership refusal. Keep delegate and remote-session answers reachable through headings in the askers' words.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Check that the public relay config declares the Raft entry, build, binding and migration.

The private deployment configs and the test that checked them are no longer in the repository. The base config still has to send fresh installs and upgrades to the Raft reactor and the three anchor stores, and it must only append the anchor migration so that existing namespace IDs stay in place. This test keeps those assertions for wrangler.toml alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Describe the coordination code by its behaviour rather than how it was built.

Comments and package notes cited internal planning ids, development steps and private documents that mean nothing to a reader of the public tree. State what each component does, what it leaves to its host and what is not available yet, keeping every limit, invariant and refusal intact. Test comments keep the contract item they prove. The hosted relay notes now say plainly that personal-member learners and leadership requests are not offered there yet, and source backups are described as lasting until the identity is retired.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Let joined personal computers coordinate the native cluster safely.

Forward original application requests over the enrolled member connection while peer persistence stays live. Complete explicit joint leadership transfer with fresh receipts, retain saved requests across retries, and restore the anchors after personal departure. Preserve quorum safety, bounded snapshot flow and quiet idle homes, and document the native capability with production lifecycle proofs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Move the size cap by the coordination core's measured weight on the current dev base.

On linux/amd64 the stripped binary built from this branch weighs 78,332,169 bytes and the dev base it sits on weighs 75,071,753, so the coordination core adds 3,260,416 bytes. The earlier cap was set from a smaller measurement on an older base and later coordination work used up its headroom, which left the branch 1,281 bytes over. The cap now moves from dev's 75,811,848 by the measured amount, keeping the 740,095 bytes of headroom dev has, and PERF.md states the new measurement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* State the personal-member lifecycle for the default hosted relay.

The real codeaf client, pointed at the hosted relay running in the local
Worker runtime, must join its identity's cluster as a durable learner beside
the three hosted anchors and then lead it with an elected, committed and
freshly read receipt. The Go test skips outside the local hosted proof runner;
its launcher compiles the client tests before starting the Worker so linking
cannot spend the journey's request deadlines. It fails at the status door
until the hosted relay offers personal members.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Share committed personal membership policy with the hosted core.

Use the same admission stages and status projection for native and hosted relays so enrollment retries retain their node and signed proof. Authenticate native peer frames and encode client responses inside the Go core while preserving the WASI network boundary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Serve durable personal learners through the hosted relay.

Expose enrollment, peer sockets, snapshot catch-up and authorized status while keeping learners outside quorum and hosted leadership unavailable. Fence recovered routing and outbound sequences durably, and cover real home lifetimes, compaction and bounded delivery through the production Worker.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep hosted presence independent of sleeping coordination.

Persist outstanding maintenance separately from advisory presence so idle connections survive coordinator reconstruction without waking voters. Preserve racing peer wakes and require fresh committed authority for retirement. Document quiet home traffic and the presence sweep cost.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Exercise quiet hosted homes and retain useful local failure diagnostics.

Cover real home lifetimes, observers, compatibility pings, sleeping alarms, cold wake and prompt state delivery. Retain bounded redacted runtime output and exact failing proxy routes. Fresh upstream connections on the counting hop prevent a local idle close from masquerading as a directory refusal, while real clients keep their ordinary transport.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Allow joined computers to lead the hosted relay safely.

Keep public coordination available through signed forwarding and fresh quorum reads while a personal computer leads. Reuse the native membership planner and application wire, and preserve quiet learner homes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Complete hosted fallback and idle personal leadership.

Advance surviving voters before authority reads and use the shared leadership planner to restore the standby after election. Preserve quiet scheduling with pending work accounted for, durable sleep cursors and a continuous monotonic clock across coordinator activation.

Cover process death, sync-off, cold wake, minority refusal and returning learners through the real client and local Worker. Document the fallback windows and quiet behavior on both relays. Keep background-job and self-hosting questions reaching their explanatory pages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep large signed metadata within the existing command limits.

Older clients retain their original body limit through bounded authenticated body pages and one atomic directory change. Lossless physical records and bounded projection preserve opaque fields without increasing the command or reactor envelopes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Commit older requests through native and personal leaders.

The native ingress waits for durable staged completion and resolves large forwarded views from the exact applied result. Archived clients retain their signed bytes, safe refusals and complete saved metadata after recovery.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Preserve large requests across hosted member recovery.

Hosted members apply the shared paged representation in their existing atomic SQL transaction. Exclusive durable-object replacement fences stale actors, while bounded temporary accounting and portable snapshots preserve retained capacity and complete metadata. Earlier automatic collection keeps repeated large views within the reactor budget.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Document complete metadata saves and retained capacity.

Older builds can save metadata up to the existing request limit. An interrupted save exposes no partial metadata, and users check the latest save before retrying. Storage capacity continues to apply to retained records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Document current cluster leadership and worker limits.

Joined computers can coordinate through either relay. Manual searches explain phone availability, saved leadership requests, fallback, scoped workers and retained capacity with the product's own status and refusal text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Explain relay storage, recovery and compatibility.

Three persistent voters coordinate directory changes while encrypted chat content keeps its ordinary storage. Relay guidance explains the one-way upgrade, personal leadership, idle traffic and atomic older requests through the original body limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Describe the relay coordination change.

The relay uses a majority per identity, joined computers can lead, and fallback preserves saved work. Older requests retain their original size limit while scoped workers and irreversible upgrades keep their stated boundaries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Reuse durable directory authority without repeating unchanged work.

Unchanged peer acknowledgements reuse validated routing while every applied change refreshes authority. Reads authenticate original authority at a fresh majority barrier and append only changes to durable time or records. Preserve committed revocation and bound unchanged transport work without relaxing durability.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Preserve directory isolation and exact relay lease expiry.

Recovered personal leaders keep independent directory state and obtain a fresh majority receipt before serving. Lease expiry and maintenance follow one relay timeline, including the instant before expiry and expiry itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Check the hosted directory's paged Go policy boundary.

The hosted directory now evaluates and projects through the reactor's paged Go operations, so the routing law names those operations instead of the earlier single-call frames. It still requires the original signed commit, the barrier read and no JavaScript lease writer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep deferred engine durability on its captured home.

Capture the same socket and engine for primary and fallback routes so a later flush cannot follow a changed home. Await captured startup work before reaping its engine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep reopened chats on the correct ownership record.

Bind each window to the record it opened and check that lease through fresh directory reads. Settle a displaced writer before opening its replacement, and retain a failed closing result for every caller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Await native namespace readiness before watch handshakes.

Complete durable namespace import and election before starting the socket handshake clock, preserving the shared watch cases and their existing deadline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Reduce retained memory in the local hosted member harness.

Use complete local HTTP replies and a smaller inspector client, omit unused services, and bound background compilation caches. Reap completed native hosts while keeping every hosted actor and the whole-run combined memory measurement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep the update question reaching its answer beside the coordination pages.

The coordination pages add enough text to the chat manual that "is there a
newer version" fell out of its answer's top results, behind a short Windows
handoff section that mentions a newer engine. The update section now says the
asker's words in its body as well as in its heading, so the question reaches
the section that answers it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Map the relay's coordination packages in the architecture guide.

The relay now keeps its directory in a replicated log that the native relay,
the hosted relay and a joined computer share. The architecture guide names
the packages a contributor starts from for that work, beside the remote
session row, and points at the relay guide and the manual page that explain
the behaviour people see.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep hosted maintenance working while a cut-short large request's pages remain.

A large request from an older client is staged as raw body pages beside the
join requests in the same namespace. When such a request is cut short, its
pages wait for the next request to replace them, and the maintenance turn that
looks for undelivered join decisions decoded every row it scanned, failed on
the raw pages and skipped the rest of its idle work. It now reads only join
requests, as the delivery replay already does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Describe the proof helpers' snapshot statements by what they check.

The row probe's summary and the hosted smoke test's assertion message now say
what they observe about snapshots instead of naming internal planning steps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep relay directories usable at their content ceiling.

Preserve complete older records with lossless encoding and retain their original sources. Reserve bounded initialization and retry bookkeeping so full directories can finish upgrading and persist capacity refusals without stopping committed work. Share the storage accounting between native and hosted relays while keeping content and protocol limits unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Release sleeping hosted members after saving their state.

Sleeping groups free their reservations so other households can activate. Busy groups keep their reservations and admission pressure returns a retryable refusal. The next operation recovers durable followers and establishes a fresh majority.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prevent older relays from changing upgraded directories.

Freeze the retained tables and install durable write guards in one transaction so older relay code cannot acknowledge divergent changes. Keep the frozen source readable for import and recovery, and document the older relay refusal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep signed relay paths and empty frame plans usable.

Hosted requests retain the configured prefix with the original signed URI so every member interprets the same operation when a joined computer leads. Directory changes, enrollment and large rotation requests use that exact prefix. Native creates and publishes treat an empty frame array like an omitted or null plan while retaining the original actor and operation binding.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Release worker retry sessions when assignments end.

Finished, cancelled and reassigned work frees claim retry records so later assignments and personal leadership requests retain session capacity. Retained assignment receipts keep retries idempotent and prevent old requests from acquiring work again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Batch native hold renewals on the shared thirty-second cycle.

Sharing the existing cycle avoids one replicated renewal per presence ping. Each bounded batch still requires fresh evidence for the exact holder and fence, and receipts remain available only after durable commitment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Measure the coordination core's binary weight on the current dev base.

The coordination core now adds 3,379,200 bytes to the stripped linux/amd64
binary: 75,755,785 bytes on the current dev base and 79,134,985 with the core
linked. The size cap moves by the same amount, from dev's 75,811,848 to
79,191,048, so the build keeps the 56,063 bytes of headroom the base has.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep the coordination change entry to what changed against dev.

The entry lists what a reader of dev now believes wrongly. Problems that
existed only while this feature was being built never reached dev, so their
lines go, and the upgrade line now says what an operator sees when an older
relay is started on an upgraded store.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Number the coordination change entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Give the coordination change entry its pull request number.

The entry was renamed for this pull request, but its pr field still said 0,
which the change-entry check rejects.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Measure the hosted learner's command boundary on the relay's own command.

A hosted relay under a prefix commits that prefix beside the signed URI, and
members now route the proof by it. The maximum-command proof captured its
request on the client without the prefix, so its size probe could no longer
translate the request at all. It now carries the request's prefix, measuring
exactly the command the relay commits; the boundary and delivery assertions
are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Show a joined computer as online only while its relay connection is open.

A joined home's Home took presence from its local learner, which counted any
computer holding an unexpired chat lease as online. A computer that stopped
without goodbye therefore stayed online until its 90-second lease lapsed, and
the other computer could not offer to continue its chat for over a minute.

Home now takes online and offline only from the relay watch, with the usual
fifteen-second offline debounce, while versions, device news and directory
reads still come from the local learner. Lease expiry stays the authority for
ownership decisions and never for the display.

New tests stop the holding process without goodbye and require the
offer within 30 seconds while the lease is still valid, on the native relay
and the hosted Worker. The open-home idle tests now allow Home's watch socket
its platform ping and pong, bounded per ten minutes from the ping interval,
and still require zero HTTP requests, client attempts, peer and forward
frames, recurring alarms and presence-driven coordination wakes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Say that Home keeps a relay watch for presence after joining.

The manual and the change entry described a joined Home with no remote
presence viewer. Home keeps its relay watch with ten-second pings, so a
stopped computer appears offline within 30 seconds and its chat can be
continued elsewhere.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Prepare the take-back proof's holder home like every other test home.

The holder runs in a re-executed test process, which did not mark the cell
engine available the way the test's other homes are prepared. On a computer
without an installed engine binary the holder could not open its home and
exited at once, so the proof depended on the machine it ran on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Link the hosted take-back proof's client before its time limit starts.

Linking the client tests can take minutes on a busy disk. Doing it inside
the timed test let the link consume the proof's own limit, as the other
hosted client launchers already avoid by linking first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Prove a store written by furrow 0.2.0 keeps working under a newer engine

* Add the change entry for the furrow store compatibility test

* Bump embedded furrow to v0.3.3

---------

Co-authored-by: agentfield-bot <agentfield-bot@users.noreply.github.com>
* test(session): building a session must not wait on furrow

* perf(engine): keep furrow detection off the session build path

* docs: number the change entry for #1954

* test: furrow answers must reach the model and waiters must be able to leave

* perf(engine): arm furrow verbs late, ctx-aware detection, settle on the first request

* fix(session): arm furrow verbs under the session lock and only for the folder asked about

* fix(session): read the session config from a build-time copy in the furrow arming goroutine

* fix(session): decide there is nothing to arm before touching the session config; test writes take the lock

* test(session): anchoring must ask furrow about the new folder

* fix(session): anchoring asks furrow about the new folder from the background

* docs: state the first-frame gain against the furrow that was embedded then
* feat(knowledge): build profile knowledge asynchronously in native Go

* fix(knowledge): verify model routing, recovery and evidence support

* fix(knowledge): use shared account configuration and memory call tags

* fix(knowledge): guard optional intake against active compilation
* Make the add-device card clearer and clickable

* Align pairing hierarchy with the shared palette rules

* Keep command emphasis across wrapped instruction lines

* Describe pairing focus and heading-only hover against current dev

* Use keyboard selection ground for the focused pairing field
* test(session): a swept landed fork stays retired

The second sweep of a kept conversation asks furrow about a fork it retired
on the first sweep, and logs "retired" again.

* fix(session): a swept fork stays retired

The checkpoint keeps naming a landed fork forever, and furrow answers a
fork it no longer holds with success, so every launch asked furrow about
the same forks and logged "retired" again. The sweep now remembers what it
retired in forks-retired.json beside the checkpoint and skips those forks.

* docs(changes): a swept fork stays retired
…ce wording (#1967)

* Keep a chat continued at startup owned by the window that took it

A bare start that continued another computer's released chat dropped the
command batch Home returned, which carried the directory ask whose
single-flight guard it had already set. The guard then stayed set, so the
window kept the listing it read before the take: the first message opened
"Continue this chat here?" for a chat this computer held, and choosing
continue here retired this computer's own session and released its own
hold before the conditional take was refused.

Home's commands are now returned on every automatic continuation outcome.
A completed take marks the chat as held here at once and replaces a listing
that was asked before it. A send decides the continuation card from a fresh
ownership read rather than the cached listing, and that read no longer ends
an outstanding listing request. Continuing checks the fresh holder before
retiring anything: a chat this computer already holds stays open as it is,
and a different holder is refused before local work is touched.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Let a coordinating computer's clean quit release its chats, then hand coordination back

A personal computer that led coordination ran its member inside the open
terminal and stopped it when the terminal exited. The engine's closing
release for the chat it held then reached a coordinator that was already
leaving, got 503, and was never tried again, so the chat read as held
offline until its lease ran out and coordination paused until the anchors
elected a new leader.

A coordinating terminal now waits, as it leaves its chats, for each idle
chat's closing release to finish, then transfers leadership to an anchor
and waits for an anchor to answer a fresh majority read before its member
stops. That hand-back is bounded by LeaderQuitWithin (5 seconds); if the
anchors cannot take over, the ordinary fallback applies. The anchors then
restore their own voters as fallback already does, and the computer that
quit does not get leadership back. A terminal that is not coordinating
leaves exactly as before. A closing release that meets a temporary
coordination refusal, including a bare 503, is retried within its existing
close deadline; a definitive refusal is never retried.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Say a removed computer was removed when it asks for cluster status or lead

The cluster control client read every answer other than 200, 404 and 409 as
a coordination outage, including the 401 a removed device receives. So on a
removed computer `codeaf cluster status` said the cluster could not
coordinate and told the person to reconnect voting computers, while
`codeaf devices` correctly said the device was removed.

A 401 carrying the directory's revoked code is now the removed-device error,
and `codeaf cluster status` and `codeaf cluster lead` explain it with the
same sentence `codeaf devices` uses, from one shared definition. Every other
refusal keeps its words.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep continued words, count only real local work, hide empty remote chats, and keep version checks side-effect free

Four small findings from a two-computer run:

- Choosing continue here on the card a send raised replaced the displaced
  session with a fresh one and lost the words held behind the card. The
  arriving chat's box now receives the same draft, pasted text and
  attachments, unsent until the person presses enter again.
- Taking a chat back into a displaced window counted the retired
  session's closing save of unchanged bookkeeping as unsent turns, so the
  move said edits were kept and Home grew a branch row for turns the relay
  already had. A sealed turn is now kept as a branch only when the engine
  finds real local work in it, the same rule continuing already uses.
- Every window registers its empty new chat with the directory, and the
  other computer listed it as an untitled running chat. A record with no
  saved turn is no longer drawn on another computer's Home; it appears
  once its first turn is saved. Registration and ownership are unchanged.
- codeaf --version, help and manual requests ran the former-folder
  adoption and removed old scheduler timers before parsing anything, so a
  version check could move a state folder and recreate the compatibility
  link. Those informational requests now skip both; every real start keeps
  them.

The footer's fit-by-priority, the engine outliving its window, and /quit
closing the conversation in front are the documented behaviour and are
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Add the change entry for the two-computer fixes

The entry is numbered when the pull request exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep the manual's moving and task questions reaching the pages that answer them

The new paragraphs about a send's ownership check and the words kept after
continue here lengthened the page's first section until "continue on
another computer" no longer reached the section that says where moving
works, and the clean-quit heading competed for the same words. They now
have a section of their own and the clean-quit heading says what it is
about. The version-request note on the help page drew "old version of the
file" away from the task pages; the starting page already says that
version, help and manual requests change nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Number the two-computer change entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Name the pull request in the two-computer change entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep a displaced window's reply bookkeeping out of kept local work

Taking a chat back into a window another computer had displaced reported
"your unsaved edits here were kept as 1 turn" and left a branch row on both
Homes, in both directions. A real reply appends pace, usage and call
accounting after its shared save, and retiring the displaced session seals
that tail. Both the take's unsaved-work check and the old drive's closing
publish counted it as new work.

Local work is now decided from the journal beyond the shared boundary:
accounting records are not work, while conversation turns, tool results,
unreadable records and workspace changes are still kept. The same decision
guards the unsaved-work check and the closing publish, so neither creates a
branch for what the relay already has.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Carry the words typed in a displaced window into the chat it continues

A send in a displaced window raises Home with the continuation card. The
typed words then live in the conversation's stashed composer, not in the
input Home shows, and retiring the displaced session can clear them before
the moved chat opens. The moved chat opened with an empty box.

The conversation's composer is now taken when the confirmed take starts,
carried with the take's answer and restored into the arrived chat, even
when its transcript or session differs from the displaced one. Nothing is
sent until the person presses enter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Publish a chat to other computers only after its first conversation turn

A window's launch chat saves a bookkeeping head at start, and that head
was published, so the other computer listed the never-used chat as
"untitled running on <device>". A directory record always has a head, so
the earlier filter on an empty head could never match.

The drive keeps saving startup bookkeeping locally but notes a head for
publication only once its saved journal holds a conversation message,
bounded to that save, so text typed later cannot publish an earlier empty
snapshot. A never-used chat therefore never reaches another computer's
Home; it appears with its first turn and keeps ownership, quit and continue
as before. A keyless end-to-end run covers the displaced take-back in both
directions and both Homes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Say a removed computer was removed when its cluster commands are refused

`codeaf cluster status` and `codeaf cluster lead` start with a control
request whose handler answers an authentication refusal as a plain 403, so
the client still reported the generic coordination failure on a removed
computer.

When a cluster request fails without a typed cause, the command now checks
through the signed directory read whether this device was removed and, if
so, explains it with the `codeaf devices` sentence. An unexplained 403 is
never guessed to be a removal, and other failures keep their words.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Stop a removed computer's sync traffic until it is paired again

After a removal, the open window kept listing chats, sending presence and
heartbeats and running state and member rounds every few seconds, each
answered 401.

The sync HTTP gate now remembers the removed refusal, and so do directory
watches and a committed local removal. A marker bound to this device,
identity and relay shares that fact across windows and restarts, and
blocked requests are answered locally with the same removed refusal. State
and member rounds stand down and open members close. Pairing installs a new
device key, which lifts the marker, and an open Home resumes with the new
device. Local chats keep working throughout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Read repositories without taking git's optional index lock

Home's repository band ran `git status` without `--no-optional-locks`, so
each refresh could create `.git/index.lock` and rewrite `.git/index`. When
that landed during a move's in-place restore, the restore saw `.git` change
and cancelled, and the take gave the chat back.

Every git command codeaf runs on its own to read a repository now passes
`--no-optional-locks`, so a background read never takes the index lock or
rewrites the index. Commands that deliberately write are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Send only from a chat this window holds, and leave a failed move safe

After a failed take the chat was free, so the window's send door, which
only blocked a chat another computer held, let a turn run on a stale copy
that could never publish.

A send now goes ahead only when this window holds the chat at the fence it
recorded, or the chat was never shared. A free chat whose local head equals
the shared head is taken back quietly with a conditional take, then sent;
otherwise the continuation card or the view-only state appears and the
words stay in the box. A failed take marks the window's copy as one it may
not send on and says why in plain words, and a stale-fence refusal makes
the window view-only. When the ownership check cannot reach the other
computers, a chat this window holds keeps working and its turns wait to be
shared, and anything else stays unsent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep the whole draft when continuing a chat fails

The draft captured when a take started was restored only on success, so a
lost race, a refused or unreachable take, or a failed in-place restore lost
the words typed behind the card. Every failure path now puts the draft back
in the box, unsent, and the manual says what a failed move leaves behind.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Offer nothing about continuing on a computer that was removed

A removed computer's Home still offered "Continue <device>'s latest chat
here?" with its chord. Once this computer knows it was removed, Home drops
the left-off offer, the continue chord and the continue action on rows,
and keeps the removal sentence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Explain a removed computer's stopped syncing without the word relay

The device pages keep to the words people use for their computers; the
removal paragraph now says the computer stops asking the other computers
for chats, and that pairing can still reach the service.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Pace the removal traffic test's heartbeats until the removal is known

The test drove the drive's heartbeat every 10 ms from the start, which
queued relay commits ahead of the member's first catch-up. On a slow disk
the member round waited minutes, and the request signed before that wait
was refused as clock skew. The heartbeat now keeps an ordinary pace until
the removal is learned and only then runs fast, so the observation still
spans many heartbeat intervals.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Announce a displaced chat only when another computer took it

After every take the window that took the chat said "<its own device>
continued this chat; this window now only shows it". The take's acquire is
followed by the arrived drive's own acquire, which raises the fence, and the
window compared the listing's fence with the hold it recorded at the take.

The window's recorded hold now follows its own drive's publishing receipt,
so a newer fence held by this computer for this window is still this
window's hold. The notice and the card come only from a hold by another
computer; a stale local receipt still cannot send. The second acquire stays:
it is the boundary that keeps two local drives from publishing under one
fence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep each chat's draft with that chat when continuing from Home

A take captured whatever the window's main box held and restored it into
the chat that arrived, so an unsent draft typed for another chat appeared
in the moved chat's box and was saved as its draft.

The arrived chat's box now holds only the words typed for it: the draft
held behind the card its own send raised, otherwise its own saved draft,
otherwise nothing. A draft typed for another chat stays with that chat,
including when a take fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep the directory's orphaned draft adoption, and limit draft ownership to takes

Keeping each chat's draft with that chat had also stopped an ordinary
opening from adopting the draft a dead window left in its directory, which
is how the words follow the person and the directory. Adoption is restored.
Only a chat arriving through a take now brings just its own saved words and
never adopts another chat's draft.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep "how do I add another computer" reaching the pairing steps

The removal and empty-chat sections spoke of "another computer" and
"adding the computer again", which drew the question away from the section
with the pairing steps once that section's card text changed. They now say
"machine" and "your other devices", and the question reaches the steps
again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ves (#1972)

* test(enginehost): a retiring host's save must not hold the next launch

Two failing tests. A host on its way out keeps the workspace lock while its
conversations save, so no new host can start; and a launch that finds the
lock busy with nothing listening waits out the whole birth wait without
starting a host when the holder lets go.

* perf(enginehost): hand the workspace over before a retiring host saves

A host on its way out closed its conversations, which waits for each one's
last save, and only then removed its socket and released the workspace lock.
In a large folder that save runs for its whole 30 s bound. A launch arriving
in that window found the lock busy and no socket, waited the full 10 s birth
wait for a host nobody was starting, and then opened its conversation in its
own process: about 15 s to the first frame.

The host now removes its socket and releases the lock first, then saves.
Each conversation keeps its own journal lock until its save is done. And a
launch that finds the lock busy now asks again on every turn of its wait, and
starts the host itself the moment the holder lets go, which covers hosts of
older builds that still save while holding the lock.

* fix(enginehost): let a retire wait a moment for the saves it hands over

The full suite caught it: an environment restart retires the idle host and
the new host reopens its journals, which the old process was still closing.
A retiring host now holds host.saving from before it gives up the workspace
until its last conversation is closed, and waitForGone waits up to one second
for it once the slot is free. Launches that only need the slot never wait on
it, and a slow save past that second no longer holds anybody.

Also the change entry.
* Bump embedded furrow to v0.3.4 (prune abandoned capture plans)

* Add change entry for #1974

* Name the #1974 change entry by slug
)

* test(cellstore): a folder holding home is never planned at startup

Red: a seat opened in the home folder (or its parent, or a filesystem
root) asks the engine for a full capture plan of it. The root case walks
the whole disk in the guard before the engine is asked.

* fix(cellstore): never capture a folder that holds the home folder

A chat opened in the home folder, a folder above it or a filesystem root
seals its own folder instead: the conversation is kept, the folder's files
are not. Engine.workspace is the one place every seal, rewind and take
reads the tree from, so all of them agree. The seat says so once.

* docs(changes): a home-folder chat saves the conversation only

* test(cellstore): a home chat never captures the sender's home path elsewhere

Red: the same chat, on a machine whose home is not the sender's, plans
the sender's home path as its project. The syncsetup guard covers the
take: B keeps the chat in a folder of its own and A's path is untouched.

* fix(cellstore): a home chat never lands in the sender's home path elsewhere

A chat that sealed only its own folder because its folder held a home
folder records that in .cell/own-folder, which travels with every
snapshot. Wherever the chat is continued, a workspace outside the chat's
own folder that holds no home folder there is foreign: the engine seals
and restores the chat's own folder instead, and the take side never
takes the chat in place there.

* docs(changes): the own-folder mark travels with a home chat

* test(cellstore): the home notice test declares the engine available

It seals over a fake transport, so it needs no engine binary. A build
with no engine inside (CI's touched rest leg) gave it a plain seat and
no notice.
…ailed chat copies (#1980)

* test(vaultsync): a restore opens the vault a fixed number of times

Fails on dev: a restore of N shared keys opens the whole sealed vault
N+1 times, once per key the ledger knows.

* perf(vaultsync): read a scope's tombstones once per restore

A restore asked the vault whether each key the ledger knew had been
removed, and every question opened the whole sealed vault. A profile with
963 shared memories and a 1.5 MB vault decoded about 1.4 GB of JSON per
state round, every few seconds, in an idle terminal. Removed lists a
scope's tombstones in one read.

* refactor(olderchats): one visit per chat behind a copy seam

No behaviour change: the eligibility checks and the copy move into
olderChats.visit so a test can count copy attempts.

* test(olderchats): a failed copy waits for the chat to change

Fails on dev: the background copier tries a chat whose copy failed again
every round, and each attempt seals the chat's whole folder.

* perf(olderchats): a failed copy waits for its chat to change

The background copier visits every older chat each pass, and a copy seals
the chat's whole folder. A copy that failed was attempted again on every
pass for as long as the terminal stayed open. It is now remembered against
the journal's size and time, and tried again when the chat changes, on the
next launch, or when the person opens it.

* test(olderchats): the background copier leaves a folder that holds codeaf's home

Fails on dev: a chat started in a home directory is sealed whole in the
background, and that seal never settles while codeaf writes its own state
inside the folder.

* perf(olderchats): leave a folder that holds codeaf's home to be copied on open

A chat started in a home directory has codeaf's own state inside its
folder. Codeaf writes there while it runs, so the whole-folder seal a copy
begins with never settles: in a live terminal each attempt planned the
folder eight times and failed with "files kept changing". The background
copier now skips these chats; opening one still copies it.

* revert(olderchats): leave home-folder chats to the cellstore rule

#1976 makes a folder that holds the home folder seal only the chat's own
folder, which is the case this skip covered. One rule for it is enough;
the failed-copy memo stays.

* docs(changes): an idle terminal stops reopening the vault per key
Separate saved evidence, concept organization, reviewed guides, and human changes. Publish maintained pages atomically for Memory and agent recall. Document module contracts for contributors.
… two-computer fixes (#1944)

Delegated tasks can work in private copies that span several folders and come home through /task review and
/task apply. This sits behind CODEAF_PROTOTYPE_WORKSPACE_OWNERSHIP with an absolute CODEAF_HOME, on Linux and
macOS only. Without the switch, ordinary chats land delegated work as before and keep the checkpoint and carrier
formats older builds read. Apply refuses before writing when the profile and the project's Git index are on
different disks.

Warm-transfer frame hints are carried by the Raft relay's state machine. They are bounded, dropped from listings,
and off by default.

Two-computer fixes:
- a removed computer keeps working on chats it held or never shared;
- a move that fails because the other machines are unreachable says the chat stayed and the words were kept,
  in the chat and on Home;
- activity lists a chat once and no longer marks a finished task incomplete;
- a moved task's worker transcript reopens.

Co-authored-by: Santosh kumar <29346072+santoshkumarradha@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and six rough edges (#1990)

Fixes everything the real two-computer relay tests found after #1944, and what three further real PC + Mac rounds found on the way.

- A take-back after a kill reaches its first acquire in about a second; ordinary chats restore once, after the lease. A copied chat can move back and forth any number of times, and a killed computer takes its chat back on the first try.
- /quit waits for the final publish and release. Only a live hold sends without asking the relay; a displaced window raises the continue card on the next Enter and can never fork the chat. A removed computer keeps working in its own chats.
- Words typed while /new opens, sent through a card, or left by a failed take land exactly once in the right box; drafts are keyed per chat.
- Sealing recovery is claimed only when this chat's turns are saved, a stuck store gets a plain sentence, a failed final save names where the turns are kept, and closing never freezes the window. Embedded furrow 0.3.5 removes only its own socket.
- A moved finished task opens read-only with its steps and worker transcript.
- Plain sentences for cluster status, devices and pairing when the relay is down or slow; the holder says when it is not syncing; Home never offers this computer's own chat.

Closes #1982, #1984, #1985, #1986.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…1992)

* Reuse unchanged knowledge checks without fragmenting fresh work

* Preserve separate occurrences of identically worded questions

* Exercise question reuse through the large-catalog path

* Assign knowledge reuse change entry to PR 1992

* Integrate reviewed knowledge into existing guides with coverage checks

* Preserve valid published knowledge through bounded section maintenance

* Weave additions into section prose with support and coverage review

* Reuse supported maintained core when its editorial context is unchanged

* Reconcile every guide update with concept-specific admission review

* Pack reviewed paragraphs and bound whole-guide budget recovery

* Update provider fixtures for maintained guide publication
* Reuse unchanged character-bounded knowledge groups after corrections

* Describe evidence-group reuse at its existing validation boundary

* Record knowledge correction reuse fix
#2001)

* Quitting right after work saves quietly and never branches bookkeeping

A quit straight after a finished task could report "could not save", leave its
hold behind and make the other computer see this one as offline. The drive's
closing step cancelled a publish the relay had already accepted, then retried
it with the old head and got a conflict from itself. Close now joins the
publish in flight, publishes the newest head, and releases.

The saving wait also stopped at the ordinary ten-second call deadline while
saying "up to 30s", so a save that went on to succeed was reported as failed.
Close and detach now use the shared saving bound, and a reply that never came
says the save is still finishing rather than that it failed.

Each launch sealed an empty turn onto the chat, even one held elsewhere, and a
later take turned those turns plus late cost records into a branch chat nobody
typed. A launch no longer seals an empty turn, a viewer never seals, and
accounting-only history is adopted on a take instead of kept as edits. A
removed computer's quit saves locally and prints nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Change entry for the quiet quit and bookkeeping takes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Unlock the final-saving state from a defer at both new sites

The quit's finishing flag was set and read under a lock released by hand, which
turns an absorbed panic into a deadlock. Both now go through small helpers that
unlock from a defer, as the rest of this file does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
)

* fix(sync): never copy an older chat that works in the home folder

The background older-chat copier sealed each recent chat's folder. A chat
started in the home folder has the home folder as its workspace, and a copy's
first seal has no change hints, so furrow planned over every file under it
while holding the shared store's one writer. Every open chat's tool calls
queued behind that plan, hit the 30 s client wait, and failed as
"engine capture-plan answer interrupted: ... use of closed network connection".

Reuse the existing rule that the home folder is not a project
(session.HoldsHomeFolder, exported from delegate_door.go): the background
round and the open-time copy both skip a non-owned chat whose folder is or
holds the home folder. It keeps opening in place, as before Wormhole.

* docs: change entry for #2002

---------

Co-authored-by: agentfield-bot <agentfield-bot@users.noreply.github.com>
* fix(exec): keep completed calls safe from late cancellation

Carry forward the completed-call cancellation fix and regression from santoshkumarradha in #1738 (original commit b11e3d7). Pairing and moving chats already shipped; preserve only this missing cancellation behavior on current dev.

Co-authored-by: santoshkumarradha <instrument.santosh@gmail.com>

* fix(provider): respect explicit fallback lists before catalog guesses

Carry forward santoshkumarradha PR #1738 commit a8039c5: naming only the failing model must not permit a catalog guess, while a list of blanks still permits inference. Retain the original regression and document the behavior.

Co-authored-by: santoshkumarradha <instrument.santosh@gmail.com>

* docs(changes): number the explicit-fallbacks entry for #2020

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: santoshkumarradha <instrument.santosh@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
santoshkumarradha and others added 21 commits October 29, 2026 07:55
…t frame (#2030)

* cellstore: a guard notice with no route goes to the log, never raw stderr

The older-chat copier runs inside the full-screen surface and seals with an
engine that has no notice route. Each file it kept out for a secret (fake keys
in test fixtures) was written straight to os.Stderr and tore through the frame.
The standard logger is parked in chat.log for the surface's lifetime, so the
fallback now writes there; outside the surface the logger is still stderr.

* docs: change entry for #2030

---------

Co-authored-by: agentfield-bot <agentfield-bot@users.noreply.github.com>
…EAF_SYNC_URL (#2067)

codeaf chat --host runs `codeaf engine` under a non-interactive ssh shell that
rarely carries the CODEAF_SYNC_URL a person exported for a self-hosted relay.
The session host compared that silence with the relay its own terminal named,
retired the idle host or refused a busy one, and the terminal's next launch did
the same back. Only a relay the attaching process names is a choice now; with
the variable unset only the pairing's saved (else hosted) relay is compared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… /settings (#2074)

/settings gains a Devices tab between Connections and Privacy. It hosts the
same device list /devices opens: r removes the computer under the cursor, n
renames this one (esc while typing cancels without closing settings), and an
"Add another device" row starts what bare /pair starts. Where pairing cannot
work it says the same sentence /devices says. Settings search finds it, the
online dot comes through the glyph door, and the manual has a section and
probes for it. Hand-tested across three paired computers on a local relay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: reach a paired computer through the relay sync uses

codeaf serve and chat --at fall back to the relay sync resolves, the relay
client keeps the relay address's path, the carrier rides a WebSocket (with a
fallback to the raw upgrade for older relays), the hosted relay gains the
machine doors, and upstream failures become plain sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: address review of the machine doors

A full machine answers 429 on both relays, a registration being decided is
not taken over, a closed stream is not closed twice, a running serve waits
out a redeploying relay, frames reuse one buffer, the door paths are one
constant, per-address limits match codeaf relay, and the hosted README
lists the binding, the migration and the doors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: gofmt relay server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…2075)

Part of #2072: a lapsed or released chat is writable on every sync service, this computer's own lapsed chat opens without a question, a removed device is never offered, devices revoke --all finds every kind, the engine refusal offers --no-host, and .moving explains itself.

Co-authored-by: Santosh kumar <29346072+santoshkumarradha@users.noreply.github.com>
…2076)

Relay streams get deadlines so silent dials and a lone pair byte end at 30 s; only the same key may replace a silent registration; the hosted front limits each caller (30 dials, 10 registrations a minute) and refuses impossible names before any object exists; serve backs off properly and answers lost sync with a back line; the upgrade honours proxies and leaks nothing on 426; upstream bodies become one-sentence facts. Hand-tested against the Worker under wrangler dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent, run-row ordering) (#2077)

The promotion tests pin the shipped Friday schedule again; the archived relay client builds hermetically; a settled run row is persisted before it is announced and Close joins a run that has already ended, so late index writes cannot land in a closed session (the run-spend reds since 2026-10-28).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir sync (#2078)

Fixes #2070. Every ending of a take clears the running mark; a busy engine store is named with its holder; under --host continue here is not offered; Home keys rows by chat id and folds branch copies into the chat's row; codeaf doctor reports and --fix repairs what is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#2079)

Stable and rc keep the production relay; staging and dev builds use their own. The release workflow's relay job deploys the dev and staging relays when the relay's fingerprint changed, proves each deploy and rolls back on failure. Production deploys only once RELAY_DEPLOY_PRODUCTION is true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iped (#2082)

Closes the rest of #2072 (item H) and fixes a pre-existing data-loss path: a rewind or take onto a copied older chat no longer drops its copy-ready marker, and CopyLegacy never removes a destination holding sealed turns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* pair: reach a dev box by the name it was given, through a seeded remote key

A dev box is created by `codeaf devbox up`, and the laptop that creates it
generates the box's remote host key and admission book itself, so the box can
be reached with `codeaf chat --at <name>` without a second approval and
without widening the identity grant (one code still never gives both).

DevboxRemote produces those files with the same readers serve uses, and
returns the pinned machine record. The machine book now keeps an optional
alias beside the key-derived relay name; Reach.Open resolves the alias to
the pinned record, and `codeaf devices` prints the alias when there is one.

A status probe must be able to give up on a box that is not serving yet: a
cancelled or deadlined context now closes the stream during the handshake,
and the raw relay upgrade honours the caller's deadline instead of always
waiting out its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* serve: keep a dev box's saved keys, settings and presence in step

A computer that only runs `codeaf serve` never received the identity's
saved provider keys, preferences or shared memories, because only the
terminal surface ran the home-state loop; every new `--at` chat on such a
computer stopped on a missing key. A dev box only serves, so it needs them.

serve keeps that state in step only when started with CODEAF_SERVE_SYNC=1,
which the dev box's boot script sets. UNSET, SERVE BEHAVES EXACTLY AS IT
ALWAYS HAS: runServe gains one deferred call that starts nothing. When asked,
it runs syncsetup.RunHomeState (one round at start and one per sync
interval, without starting a cluster member; quiet, unpaired, unavailable
and removed homes end it silently, other failures are said once each) and a
presence worker on syncsetup's gated clients that keeps the device record
fresh for `devbox status` and stops when the identity, device or relay
binding changes. Both start through guard.Go and are joined before serve
returns.

The engine cached its first profile load forever, including a failure, so
an engine started before the keys arrived kept refusing after they had. It
now caches only a successful load; success paths are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* devbox: codeaf devbox up, status and down on your own Railway account

`codeaf devbox up <name>` writes a readable Terraform folder the person owns
(default <codeaf home>/devbox/<name>/), shows the plan, and on one
confirmation creates one private project, one service and one persistent
volume on their Railway account with their RAILWAY_TOKEN. Without a token
it says in one sentence where to create one and creates nothing.

Railway's provider triggers several deployments within seconds and a
redeploy can reuse a snapshot without the variables, which killed a box
mid-pair on the first real run. So up applies in three guarded steps:
create the box with no secrets and let Railway settle; send a one-time
pairing code plus the box's seeded remote key and admission book, where the
plan may change only the variable collection; and, once the pinned remote
key answers, remove those one-time values from Railway and from the state,
deleting the state backup that still held them. The remote key is generated
once per up and kept across up to three codes, and a reachability probe runs
alongside the join so a lost acknowledgement is not mistaken for a failure.
"is up" is said only after a successful connection. Once the box answers,
up also sends this computer's saved keys once, so the first `--at` chat from
a laptop that was a fleet of one has them.

The box boots from boot.sh on a stock image. Its first install uses this
computer's own release when they share a channel, because the installer's
GitHub API lookup is rate limited per address and Railway's egress is
shared; later boots refresh to the channel's newest build and keep the
earlier one on failure. It waits idle until it has a code, joins once (a
marker on the volume keeps redeploys from pairing again), pins its sync
relay to the laptop's (each release channel has its own), seeds its remote
files atomically and independently, and serves with CODEAF_SERVE_SYNC=1.

`status` probes the box through its pinned key and reports up or down,
region, project and last seen. `down` destroys every resource, then revokes
the joined device by the id saved at up, and forgets the alias. Failure
receipts name the exact command, with --dir for a non-default folder.

Nothing changes for anyone who never runs devbox. The AWS module is
`terraform validate`d only; it has never been applied and the command does
not offer it. The manual's new page answers "can I run codeaf on my own
server" and "set up a dev box".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changes: the dev box entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* config: register the dev box's setup values and CODEAF_SERVE_SYNC as plumbing

The registry law reads every CODEAF_ name spelled in Go and wants each one to
be a settings row or operator plumbing. The three CODEAF_DEVBOX_ setup values
are service variables for the box's boot script that codeaf only looks for in
Terraform state to remove them, and CODEAF_SERVE_SYNC is one serve process's
posture set by that script; none is a preference a person keeps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…OS test reds, provenance on private apply (#2086)

* release: expand an empty test argument list safely under macOS bash 3.2

The release-surface step ran go test "${test_args[@]}" under set -u. With no
known-red ledger the array is empty, and /bin/bash 3.2 on macOS treats that
as an unbound variable, so the prologue test failed on a Mac. The conditional
expansion behaves the same on bash 5 and is safe on 3.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(syncsetup): give hand-built Sync fixtures their own home

Two fixtures built Sync{} with an empty Home. A refusal retires the send
hold, which joined the empty Home and wrote v3/sync/holds/<hash>.lock into the
package's source directory on every run. They now use t.TempDir().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: engine fixtures record a real conversation and wait for async branches

These tests need a furrow engine. GitHub's full tests stage none, so they skip
there, and they failed on every machine that has one (a Mac after make build,
or CODEAF_FURROW on Linux):

- the scripted drive chat wrote files but never a reply, and a chat with no
  conversation is deliberately not published, so its turns never became
  durable; each scripted turn now writes a reply to the transcript
- the large-home startup test drained a warm capture that has no turn, so
  nothing was sealed; it now completes one recorded call first
- a refused drive branches asynchronously, so the counts and two-home tests
  wait for the branch row instead of reading it once
- the superseded-drive test keeps A offline until its next publish, as the
  scenario states, and waits for the kept-work notice too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Never write a new identity into a codeaf home that was deleted

A codeaf engine that was still saving after it was asked to stop could find
its home deleted and treat that as a first run: identity.EnsureSolo made the
folder again and wrote a new identity.json, device.json and identity-solo,
and the vault capture then wrote the sync ledgers beside them. The Mac tester
saw exactly that 8-22 s after removing a test home.

Each codeaf process now records the home it starts in (home.Witness). The
identity writers, the vault open, the stats recorder and the send-hold writer
refuse with "codeaf home was removed or replaced" once that folder is gone
or is a different folder, and they never re-create a recorded root. A first
run, where no home existed at start, creates it exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A stopping engine ends at once when its home is deleted

An engine asked to stop closes every conversation before it exits, and for a
synced chat that can take tens of seconds while the relay is unreachable. If
the home was removed meanwhile, everything it still wrote went into a folder
that no longer existed. While shutting down, the host now watches its home;
if it is removed or replaced, the engine exits within about a tenth of a
second instead of saving into it. With the home intact the stop does what it
did before, in the same order and with the same limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bound how long a busy engine store spends naming its holder

#2078 named the process holding a busy engine store. On macOS it asked lsof
about every lock file it found, 500 ms each with no cap, so a failing seal
could add seconds. One shared budget now covers the whole lookup, and it
stops at the first busy holder; when the budget runs out the message is the
plain busy sentence. Requests that are not busy never inspect anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A chat made with chat --once always reaches your other computers

runChatV3Once returned before the interactive road registered its process
cleanup, so a paired --once never closed its sync drive. Publishing uploads
a chat's frames before it registers the chat with the relay, and a --once
that exited in between left the uploads with no record pointing at them: the
other computer never listed the chat, and doctor said nothing (Hello saw it
in 1 of 48 runs, about 1 s after pairing).

--once now always registers drive cleanup; with no drive it returns at once,
and a paired chat gets its final send and release inside the same limit the
interactive road uses, with one plain stderr sentence if that fails. A paired
drive records a small local publication marker, so codeaf doctor can report a
chat this computer started that the relay does not list, and doctor --fix
registers it through the ordinary road, create-only, never over another
computer's record and never for a chat put away meanwhile. A never-paired
computer writes no marker and makes no relay request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* manual: --once waits for your other computers, doctor finds a chat that did not arrive, a deleted home is refused

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: spell the Option chords the way the product does on macOS

macOS names the Option key opt, so Home's continue offer shows opt+c and the
Memory switch shows opt+u. Two tests hard-coded alt+. The Memory test now
builds its expected line through the app's own chord spelling, and the
takeback test waits for the offer's question and checks a continue chord is
drawn beside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changes: entry for #2086

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(syncsetup): make the quiet solo home in a fresh folder

The quiet case removed a paired machine's folder and created a solo identity
at the same path in the same process. codeaf now refuses to create an identity
in a home it saw and that was then deleted, so the case uses a fresh folder,
which is what a never-paired home is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(session): task-copy fixtures that hold on macOS

42 internal/session subtests failed on macOS and passed on Linux:

- On macOS a new file takes its folder's group, so the Apply fixtures' files
  often carried a group other than the process's and the portable-metadata
  check refused them during setup, before the test's own behaviour ran. The
  success fixtures now give their files the process's uid and gid, resolve
  temp-path aliases first, and clear extended attributes. The product refusal
  stays: private Apply stages replacements with the process's group, so a
  foreign group cannot be carried. A new setgid-directory test pins that
  refusal on Linux.
- Bare mktemp on macOS ignores TMPDIR, so five scratch probes now use
  mktemp "$TMPDIR/codeaf-runtime.XXXXXX".
- Two path checks compared a canonical path with a raw temp path; both sides
  are canonicalized now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Revert "manual: --once waits for your other computers, doctor finds a chat that did not arrive, a deleted home is refused"

This reverts commit f28b0f4.

* Revert "A chat made with chat --once always reaches your other computers"

This reverts commit f9bb2e7.

* manual: a deleted home is refused, and a stopping engine exits when its home is removed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changes: #2086 no longer carries the --once change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Private apply tolerates macOS's own com.apple.provenance attribute

macOS 15 stamps com.apple.provenance on every file a process tree started
from some apps creates, and the owner cannot remove it. Private apply refused
any extended attribute, so on such a Mac it refused every changed file. That
one attribute is now tolerated on macOS: the replacements apply writes come
from the same process tree and are stamped the same way. Every other
attribute, quarantine included, is still refused, and Linux is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(session): Apply fixtures leave macOS provenance in place

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ken again (#2091)

An ownership check that finds another device holding the chat now retires the hold at once and lets the batcher keep unsent turns as a branch before the window names the new holder, once. Materialize records the received head as published, so a second take no longer refuses with 'snapshot … is not held in this store'. Also fixes the R7 flake and TestPairJourney's stale needles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings -> General -> share teams, a local switch that is off by default. Teams, their members and settings travel through the sealed profile vault only when both paired computers turn it on, and the other computer's recent Traffic shows as read-only history that delivery never reads. A member chat held on the other computer opens through Home's Continue this chat here? question. With the switch off, or unpaired, codeaf behaves exactly as before, and existing vault carriers keep their rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n a slow relay; trust-proxy for every relay limit (#2089)

* relay: trust X-Forwarded-For only with --trust-proxy, for every limit

A self-hosted codeaf relay took the first X-Forwarded-For address as the
caller's network for its registration and dial limits whenever a request
carried one, so a relay exposed directly to the internet let any caller
mint a fresh allowance by naming its own address. --trust-proxy already
existed but reached only the pairing limits.

relay.Server now counts the socket peer unless it is handed a peer policy,
and relayserve hands the pipe the same socket-or-forwarded policy the
pairing mailbox and link pairing use. A relay behind a proxy must now pass
--trust-proxy, or every caller shares the proxy's allowance; the flag help,
docs/relay.md and the relay manual page say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sync: a chat this computer never held primes from its frame plan

SyncEngine.Holds read the store-wide published.<ledger> file, and new chats
share one store, so after any earlier exchange with a relay every new chat
looked warm and Fetcher.prime skipped its frame plan. A large chat then
came over one serial object batch after another instead of its frames,
eight at a time; on the hosted relay each batch costs seconds.

Holds now asks whether this computer holds history for this chat (its
adoption record or a recorded turn), and a return landing in a fresh
staging folder asks about the chat's existing folder, so a warm return
still fetches only its delta. The completeness loop is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* continue: the picked chat opens, a refused continue keeps its card, a slow relay says so

The public-network test found four ways continuing a chat between two
computers went wrong:

- D6: a bare start continued the newest chat another computer let go of,
  and while that move ran every explicit pick on Home answered "a chat is
  already continuing here" and the older chat opened. An automatic move now
  gives way: a confirmed different chat waits with the holder the person
  saw, the automatic move settles and its unused hold is released with a
  fresh bounded context, then the picked chat is taken and opened.
  Confirming the arriving chat joins its move. With no input a bare start
  behaves as before.
- D3: a lost-race refusal skipped #2078's retry card and only said
  "another device continued this chat first" in the footer, and a holder
  whose own writer reopened after a move (raising its ownership number) was
  blamed as another device. Every refusal now keeps the card and the draft;
  a fresh reading tells the same holder reopening (the card asks again with
  what it now shows) from a real different device.
- D5: Home gave up on a slow relay after 5 s, and the first message after a
  successful move was left in the box under Home's timeout sentence. Home's
  listing now waits up to 25 s, says "waiting for <host>; still checking"
  at 5 s and keeps its rows; a real outage still shows at once. A send
  right after this window's own move waits up to 30 s for its writer to
  start, then sends once; a send that cannot leave says the message was
  kept and names the relay.
- D7: each continue writes one chat.log line with the time spent in each
  phase, so a slow empty-folder move can be read instead of guessed.

Consent still binds holder and ownership number, and nothing in
internal/clusterstate, the wire or stored formats changed. The manual
pages say what the code now does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changes: entry for #2089, what continuing between computers now does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* continue: review fixes — leave a shared view open, bound an abandoned move

From the review of the continue fixes:

- An abandoned automatic arrival no longer closes its conversation; it
  leaves through the path a departing window takes, so another window on
  the same session keeps working and the engine releases the chat only
  when the last window goes. AlreadyHere skips cleanup.
- An abandoned automatic move is cancelled the way Escape used to cancel
  it if it has not answered within two minutes, so a waiting pick always
  runs. A second Escape navigates again instead of being swallowed.
- Opening another chat's card no longer abandons the automatic arrival;
  only confirming it, or the resume chord, does.
- The already-here recovery retries silently once, then keeps the card.
- A send's own reading keeps Home's outstanding listing progress and host.
- A writer wait ends at once on displacement, with the existing words,
  instead of waiting 30 s and blaming the relay.
- The manual says what Escape does to an automatic continue; the release
  retry reuses cellsync.ReleaseRetryEvery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* tui3: read the stale listing before the take, as the test intends

askChats now carries its five-second progress tick in a batch, so
a.askChats()() returned the batch rather than the answer, and the test ran
the "old" read only after the take, against the new holder. It now takes
the listing out of the batch and reads it first, which is the in-flight
answer the test was written about; what it asserts is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sync: a continue waits for another window's vault save instead of failing

D4 in the two-network re-measure: 2 of 5 picks on the dev relay failed
with "could not continue this chat here because the chat could not be put
in this folder", and chat.log said "handoff: resource temporarily
unavailable" with the hooks phase at 5 ms. A continue's last hook pulls
the vault under state-sync.lock, taken without waiting, while every
running chat's drive pushes the vault under the same lock; over a slow
relay that push holds it for seconds, so the pull failed at once and the
chat was handed back. The pick fix made this more frequent: picks that
used to be refused as busy now reach the take while the bare start's
engines are still saving.

The continue's pull now waits up to 30 s for the lock, then fails as
before. Background exchanges still skip when the lock is busy, because
the holder is already carrying the same state.
TestAContinueWaitsForAnotherWindowsVaultExchange holds the lock from a
second open file through the move's restore; without the wait it fails
with the exact error L3 saw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* taskdoor: a door paired computers reach each other's tasks through

A paired computer that serves now registers a task door under a name derived
from the identity and its device id, so another paired computer can dial it
without anything being published. Inside the existing Noise IK tunnel (with a
prologue of its own) each end proves its device with a certificate signed over
the channel binding, and a revoked computer is turned away by the target.

The wire carries start, watch, stop and ack for one attempt; the target keeps
each attempt in an atomic store whose admission is written before the executor
runs, so a repeated start or a restart never runs a task twice. The executor is
an interface here; the next commits supply the real one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* taskdoor: send a task copy out as a delta and bring its change home

A task's local copy is sealed into a cell of its own, never the chat's, and
only the objects the relay's store lacks are uploaded, so a second send of a
10,000-file folder with one change uploads about nine kilobytes. The other
computer restores the copy with its Git history, and its finished folder comes
back the same way; this computer refuses the result if its own copy changed
while the work was away, keeping both. Task-only uploads ask the store what it
holds even when small, because no directory head says what was published.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* serve: take tasks from paired computers

`codeaf serve` on a paired Linux or macOS home now also opens the task door and
runs each task it accepts as a `codeaf do` child in the attempt's own folder,
with a hidden flag that keeps the child's run record there. The door follows
the child's steps and per-call spend into the attempt's journal, stops it on
request, and after a restart adopts a child that still lives; a child that died
without an answer ends interrupted and is never started again. The task's
effort, step and no-progress controls travel with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* config: remember which paired computer tasks run on

A new task.runs_on row holds the device id of the paired computer a task goes
to when nobody names one, so a rename does not break it; blank is this
computer, which is the default and what every task did before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* session: run a task on a paired computer

propose_task gains an optional computer argument, present only when this
conversation has paired computers to send to; with none named, the Run tasks
on setting decides, and a quick, in-place, program or multi-folder task always
runs here. A remote placement uses a run engine that sends the task's copy,
follows the other computer's steps and spend into this computer's plan store
and ledger once, and lands the returned change through the local engine's
existing road. Its record lives beside the run's trajectory, so a reopened
conversation follows the same attempt from its last event instead of starting
it again; a computer that stops answering is said within a minute, and run it
here abandons the attempt and runs the same untouched copy locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* taskplace: follow remote tasks and bring their changes home

The adapter session reaches the task door through: it lists this identity's
paired, non-revoked computers by their shown names, dials the chosen one, and
streams its attempt's events back, recovering an admitted attempt from its
saved executor without asking the directory first so an offline reopen can
still reattach or run the copy here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chat: wire paired task computers into v3 conversations

A v3 conversation on a paired Linux or macOS home that is not quiet now carries
its paired computers, so the computer argument and the setting reach a real
door; anywhere else the conversation carries none and the feature is absent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* session: carry effort and the card's computer choice through admission

A task started on a named computer can now also be asked for the best or the
cheaper crew, and a proposal's answer can carry the computer the person picked
on its card; both go through the one placement resolution the computer
argument uses, so there is no second road to the same choice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* remote: forward task placement to a window on this computer's engine

A linked-local window reaches its engine through the connection, so named
starts and run it here travel as two new methods, Task.StartOn and
Task.RunHere, behind an optional welcome capability. Task.Start and every
existing answer keep their bytes and meaning, and a window attached to an older
engine that does not advertise the capability shows neither control. The
welcome now records a watching window before it is built, so a read-only
watcher is never offered controls it cannot use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* tui3: say where a task runs, choose the computer, and run it here

/task --on <computer> names where a task runs, alone or with --best or
--cheap; the proposal card shows where it will run and one key cycles the
computer before it is accepted; the rail, Tasks rows, page and saved card say
running on, ran on, stopped answering or interrupted on that computer beside
the unchanged state word, with run it here where it applies; and settings gain
a Run tasks on row. A conversation with no paired computers draws none of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chat: find paired task computers on an ordinary launch and a connected chat

A launch without CODEAF_PROFILE_DIR handed the task-computer door an empty
profile, so it never found this home's identity, and a connected
conversation whose adaptive lanes chose the plain builder dropped the door.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* e2e: a task runs on the other home and lands here

Two homes, one identity, the real codeaf relay and codeaf serve, and a
scripted model endpoint per home. The task's model calls reach only the
other home, its file and commit land in this project, its steps arrive in
order and its spend is on this ledger once; a computer that is not serving
says so and run it here lands the same file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* manual: tasks on another computer

A new page answers how a task runs on a paired computer, what running on
means, what happens when it stops answering, run it here, the Run tasks on
setting and codeaf serve. The /task, propose_task, settings, keys and serve
pages say what changed, and the retrieval table holds the questions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* tasks: nothing about other computers until a person chooses one

With Run tasks on left at this computer, propose_task's schema, the belt
facts, the proposal card, a local task's brief and its records are what
dev gives, and no roster is read. The computer argument, its belt fact and
the card's runs-on line appear only once the setting names a paired
computer; /task --on and the card's choice stay the per-task road. A
watcher's welcome keeps dev's bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* serve: take tasks only with --take-tasks, and leave pairing's code alone

Plain codeaf serve registers only the machine door, as on dev. The task
door opens with --take-tasks and keeps its own registration loop, so the
pairing and machine-door code is dev's byte for byte; only the task
tunnel records a channel binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* run: order steps across tasks only inside the task door's child

An ordinary run's trajectory lines and codeaf do are dev's; the lock,
the scan and the order fields are switched on only by the hidden task
store flag the door passes its child.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* manual: tasks go elsewhere only by choice, and serve takes them with --take-tasks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* devbox: take tasks when the box's installed build can

The boot script passes --take-tasks to serve only when the installed
build's serve --help lists it. A box can follow a channel whose build
predates the flag, and serve refuses a flag it does not know, which
would leave the box serving nothing; such a box serves as before and
picks the flag up on the restart after its channel ships it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* serve: name --take-tasks in the usage lines

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* manual: a remote task's private apply has the same Mac limit as a local one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changes: a task can run on another paired computer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: hide Run tasks on from the settings tool without task computers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: refuse a finished attempt started again and a removed computer on a live task connection

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep an edit made while a result installs, and leave stop free during downloads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep mirrored record kinds, find an owed acknowledgement on reopen, and tell same-named computers apart

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: quote every task-transfer refusal in the chat manual, with what to do

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: meet the settings, chord, off-loop, guarded-goroutine and complexity laws

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a task sent to another computer answers before its copy is prepared

Preparing the task copy scales with the repository and, on a loaded machine,
took up to twenty seconds before the start returned, past the window's
ten-second patience. A named remote task now publishes its queued row and
answers at once through the existing deferred-start road, reuses the selected
computer instead of reading the roster twice, and keeps stop, close, reopen and
retained-copy ownership through preparation. A local task's start is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: say a task sent elsewhere appears before its copy is ready

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: every list that shows a task sent elsewhere says where it ran

The Mac check found Background work's result row saying only
`<task> · incomplete · open result` for a task that ran on another computer,
and the tasks place blanked the computer in a window without task-computer
controls. Placement is a fact about the record, so every surface now draws the
existing running on / ran on words whenever the record names an executor. A
local task's rows keep their exact bytes, pinned against dev's rendering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a task child sends no reasoning effort to a model without reasoning

`codeaf do` has no catalog on its settings, so the child that
`codeaf serve --take-tasks` runs always sent an effort, and a model without
thinking refused every task sent to that computer (`does not support
thinking`). Only that child, reached through the hidden task-store flag, now
reads the service's already saved catalog rows and omits effort the way chat
does; it never waits for a catalog fetch. Plain `codeaf do` is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the task rail and /history rows say which computer a task ran on

The Mac check found `ran on <computer>` only on Background work's result
row. The rail's headline row now reserves the whole placement phrase
before the title (dropping it whole when the rail is too narrow), and a
finished task's /history row keeps the computer its record named after the
plan row replaces the record. A local task's rail and history rows keep
their exact bytes. The start receipt still does not name the computer: its
reply carries no resolved placement and a request can be redirected here,
so the manual now lists exactly where the words appear, and no longer
claims Home, which lists no tasks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a task child retries once without effort when the model cannot think

A stock Ollama catalog row lists no parameters, so the saved-catalog rule
never applied and a task sent to that computer still asked `llama3.2` for
effort and was refused with `does not support thinking`. Chat works on the
same model only because it asks for no effort; task workers ask for low.
Only the task child that `serve --take-tasks` runs now answers that exact
400 by sending the request once more without effort or a thinking budget,
and remembers it in that client for later calls. Nothing is written to the
shared quirks file, so no other process on that home changes, and plain
`codeaf do` and chat keep dev's behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: the chats-stored heading carries the words people ask with

"where are my chats stored" sat fourth on dev, one place inside the
manual's four results, and this change's new pages moved it to fifth.
The heading now leads with the asker's wording and keeps the old title
after it, so the cross-reference in running-from-the-terminal still finds
it; the probe reaches it second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…2092)

* test: remote-task session fixtures pass on macOS temporary folders

Two #2085 fixtures failed on macOS only. The r8b home was made under /tmp,
which macOS links into /private/tmp, and owned conversation storage
refuses a recorded binding whose spelling crosses a link, so newAgent
stopped with "workspace binding traverses a symbolic link"; the fixture
now names its home by its resolved path. The private-task Review and
Apply test wrote its returned file inside a copy under the temporary home,
where macOS hands a new file its folder's group (wheel under /private/tmp),
and Apply refused that group; the fixture now gives that file the
process's own group, as #2086's Apply fixtures do. Both failures reproduce
on Linux with a home reached through a link and a setgid TMPDIR owned by a
secondary group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: change entry for the macOS remote-task fixture fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ore removing (#2087)

* Name the sync service in its own failures, and keep each error's class

A relay that is down, slow or full now says so in one sentence family
(internal/syncwords) instead of blaming the person's network or other
machines. Status classes and causes stay exactly as before; only the words
a person reads change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pair through the right sync service, and let an unused computer join

Every printed codeaf pair command carries --via on a self-hosted service.
A computer whose only chats never had a turn sent can join without
--replace; one with real chats is told how many and what --replace does.
A removed computer can come back with --replace and a fresh device key.
Removal by name refuses rather than guess when a roster cannot be read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say a quit saved here when only the upload is still pending

A close that saved on this computer but could not reach the sync service
prints that it is saved here and exits 0. A failed local write still says it
could not save. The receipt is negotiated in the hello, so mixed old and new
builds keep their former words and exit codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ask before removing a device, and draw device marks through the glyph door

/devices asks once before removing a computer (y removes, any other key
keeps it), lists this device and an Add another device row, and Home stays
Home for /devices, /pair and /settings. Platform and presence marks come
from tokens slots. A refused send keeps the person's words in the box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name cluster members by device on an opt-in status header

Services that predate Codeaf-Cluster-Names send no labels, and the command
then says another computer rather than claiming one was removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bring the manual and the end-to-end words in step with pairing and sync wording

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fit pairing's service check and the help page to per-channel relays

The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Change entry for #2087

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the #2087 change title under one line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say "that chat stays" when the refusal counts one chat

The hand walk read "those 1 chat stay on this computer but no longer sync".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep cluster, devices and terminal approval on the service this computer uses

Cluster and device errors name the service the command used, never this
build's hosted default. Verbose cluster status prints its membership labels as
before. Roster names stay plain so a reused devbox name still finds the joined
computer; duplicate tails are added only when the list is drawn. A terminal
approving a self-hosted page's link keeps that page's service when it matches
this computer's, and a link on another service is still refused before its code
is sent. Existing tests change only where a deliberate L4 sentence or the solo
device list (this device is listed) changed what they read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep Home's whole credit warning beside its message

Moving the account notice off the key-hint line left the credit warning beside
the footer's project, where it was cut off. It now sits once at the right of
Home's message row, as on dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Let the first-start fixture wait for its knowledge worker without spinning

The wait read the worker's database in a tight loop, which starves the writer
it waits for on a loaded machine. It now polls every 10 ms, and its ceiling is
for setup, not a speed claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A window opened once with a provider connected can still pair without --replace

The picker's model list is cached as models-<key>.json for a non-default
service, and the empty-computer rule knew only models.json, so a computer that
had opened codeaf once with Ollama connected was told it had local work.
internal/catalog now owns that file name, the window builds its path through
it, and pairing accepts exactly those names. A keyless end-to-end test opens the
real window with a non-default service, sends nothing and pairs, and a second
window that sends one turn is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a used, never-paired computer before the pairing code is exchanged

The code road refused only after the exchange, so the approving computer had
already said the new device was paired. A solo computer whose work rules out a
merge now refuses before anything is sent. Chats written before any identity
existed keep dev's behaviour: they come along and sync, which two tests pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Wait for Home by the tuiwords needle, not a bracketed tab

The selected tab reads [Home] only in some terminal modes, so the new window
test timed out on a Home that was already drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say when a pre-pairing chat --once chat reaches the other computers

Hello's check on a Mac found that such a chat is kept at pairing but is
published the next time codeaf's window opens on that computer, in place rather
than as a copy. The manual now says that instead of implying it syncs at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Import devname once in the devices card after the rebase onto #2085

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Lead serve's line with the machine name chat --at accepts

serve printed the device name first (`reachable as  beta (canoe-paddle-65)`),
and `codeaf chat --at beta` is refused by the name check. The machine name
comes first again, as on dev, and the device name follows only as a hint;
every instruction keeps the bare machine name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ask remote-work approval with dev's question, not a name guess

"has the same name as one of your devices" matched only the asker's label,
and the approving computer has no verified link from that request to a paired
device, so it misdescribed a computer that is paired. The question is dev's
again; the weight line stays on the approving side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings dev's fixes since the last dev merge (#2009, which carried dev
through #2002) into app/v3: tasks on another paired computer, teams
sharing between paired computers, the Devices tab and device removal
confirmation, continue-a-chat fixes, quit/local-saving receipts, relay
hardening, write-payload validation, fallback and shell fixes.

The earlier dev merge was squashed, so this merge resolves against dev at
#2002 as the common ancestor; git's own merge base predates that merge and
reported 194 conflicting files instead of 61.

Conflict resolutions that change behaviour:
- Pairing keeps app/v3's model: a solo computer joining keeps and shares
  its own chats; a computer already sharing another set refuses. Dev's
  early "already has N chats, use --replace" refusal is not taken. Dev's
  "Paired with <name>" naming and the "already paired with ..." status
  check are taken, with app/v3's kept-chats sentence.
- Explicit --replace on the same identity renews the device key (dev),
  through app/v3's replacing merge.
- The engine process half retries a failed open on every door (dev), not
  only the app's.
- A displaced chat window: dev's immediate hold retirement and branching
  of unsent turns before naming the holder, on top of app/v3's viewer
  record, resume and same-computer release. The send receipt is kept
  until the holder is read, and displacement is announced once.
- Task placement on another computer is ported into app/v3's split
  hand-off helpers.
Code: a removed home is not recreated when the vault opens (#2086); a
thinking refusal is learned only when the effort was sent (#2085); an
ownership check that finds another computer keeps unsent turns (#2091);
the write tool's content refusal is marked; first-start automation rows
carry app/v3's columns; CODEAF_APP_LOCAL_GUI is registered.

Tests: dev's teams tests are ported to the SQLite teams store (#2066)
instead of skipped; tests whose behaviour app/v3 changed on purpose are
adapted and cite the commit (#2031 pairing keeps chats, f9c0f1a effort
memo, #2034 same-computer takeover); app/v3 tests follow dev where dev's
fix wins (#2081 engine reopen, #2075 own lapsed lease, devbox help lines).
@santoshkumarradha
santoshkumarradha merged commit 558a3c8 into app/v3 Nov 1, 2026
1 check passed
@santoshkumarradha
santoshkumarradha deleted the merge/dev-into-app-v3 branch November 1, 2026 14:24
santoshkumarradha added a commit that referenced this pull request Nov 2, 2026
…in (#2186)

* syncsetup: a window that takes its released chat back is a writer again

When another computer takes a chat and then releases it (a failed install),
this window's ownership check first meets the other holder and marks the hold
lost. The next send reads the released lease, ConfirmWork reacquires it and
clears the viewer line, but it left lost set, and Gate still refused every
tool and send with 'another device continued this chat'. The lost flag came
with the dev merge (#2099) next to an older reacquire path that only knew the
viewer line. Reacquiring now clears both, as resumeViewer already does.

* docs: change entry for this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants