Skip to content

a dev build installs beside codeaf as devaf and follows the dev channel - #1333

Merged
AbirAbbas merged 10 commits into
devfrom
feat/devaf
Sep 21, 2026
Merged

AbirAbbas merged 10 commits into
devfrom
feat/devaf

Conversation

@AbirAbbas

@AbirAbbas AbirAbbas commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Why

Santosh asked for internal dev usage — downloads and everything around them —
to be separated from the main install. Today a dev build can be installed, but
only over ~/.codeaf/bin/codeaf: its launch check is silent, /update with no
word installs stable over it, and the curl line it offers reinstalls stable.
So a dev daily driver was three surprises away from being a stable binary again.

This makes a dev build a thing you can keep: it installs beside codeaf under its
own file name, it follows the dev channel, and every road it offers back leads to
the file you are actually running.

What changes

The product is still codeaf. devaf is only a file name — every sentence
the program prints still says codeaf, and devaf version prints
codeaf dev-… · go… linux/amd64. Nothing is renamed: not the state root, not the
socket names, not one identifier.

  • The installer takes a name. --name WORD and CODEAF_INSTALL_NAME, checked
    against ^[A-Za-z0-9][A-Za-z0-9._-]*$ before anything is written (anything else
    is a usage error, exit 2, naming the rule). The binary, the temporary file, the
    printed line and the closing version run all use that name. INSTALL_NAME sits
    on one unindented line directly under the CHANNEL line so the website proxy can
    rewrite it exactly the way it already rewrites the channel.
  • A channel build follows its channel. A dev-* or staging-* build asks its
    channel at launch and gets the same one dim line when that channel has something
    newer; /update and codeaf update with no channel word select it. An explicit
    word or tag still wins — /update stable on a devaf installs stable over that same
    file, because the person asked. Stable and rc behave exactly as before; a source
    build is still silent and still refused.
  • Two tags of one channel are ordered by publish moment first, then by the
    YYYYMMDD in the tag, and a same-day tie with a moment missing goes to the release
    the API named. The running build's own moment comes out of the release list the
    check already fetches, so there is no extra request and no build-moment stamp. A
    build ahead of the newest is never silently downgraded.
  • The cache is one file per channel. update-check.dev.json and
    update-check.staging.json, cached for one hour because dev publishes several
    times a day and a devaf beside a codeaf must not thrash one file. Stable and rc keep
    update-check.json and 24 hours, byte-for-byte as before.
  • The curl line names this file. update.CurlLine(executable, channel) replaces
    the one constant everywhere a road back is offered: a file named devaf is told to
    reinstall from /get/devaf, a codeaf on a channel from /get/codeaf/<channel>,
    and any other name carries CODEAF_INSTALL_NAME=<name>. A source build's refusal
    still prints the CurlCommand constant.
  • Shared state. devaf and codeaf share ~/.codeaf — keys, conversations, one
    engine host per workspace — under the existing retire-or-attach law. Nothing here
    changes it.
  • Dev release notes carry curl -fsSL https://agentfield.ai/get/devaf | bash and
    one sentence saying what it installs. Only the dev channel gets the block.

Validation contract, and how each line is proven

Behaviour Proof
V1 --name devaf --dev writes <dir>/devaf, prints the real file, ends with that file's version line, leaves a sibling codeaf byte-identical; the variable does the same; the flag wins; ../x, -x, "", a bad variable and a bare --name all exit 2 and write nothing internal/release · TestV1InstallerName
V2 INSTALL_NAME="${CODEAF_INSTALL_NAME:-codeaf}" is one unindented line, exactly once, directly under CHANNEL; --help names both spellings internal/release · TestV2InstallerNameSeamAndHelp, TestInstallerKeepsTheWebsiteChannelSeam
V3 a dev build one build behind gets exactly one notice and a devaf gets the devaf road; the newest gets none; staging asks the staging list; stable's request and notice are unchanged; source makes no request; one call to releases?per_page=100 internal/update · TestV3LaunchNoticeForChannelBuilds, TestSourceLaunchesNeverReachTheNetwork
V4 the ordering law, both directions, including the cases it left open and the pairs it cannot rank at all internal/update · TestV4ChannelOrdering, TestV4UnrankablePairsAreNeitherNewerNorAhead
V5 a dev build writes and reads only update-check.dev.json; one hour there, 24 hours on stable; alternating launches in one profile make one request each internal/update · TestV5ChannelCacheFilesAndLifetimes
V6 /update and codeaf update with no word follow the running build; every explicit override wins; a check across two channels says only what it can order internal/tui3 · TestV6UpdateChoiceFollowsTheRunningBuildUnlessOverridden; cmd/codeaf · TestV6TerminalUpdateDefaultsToTheRunningDevChannel, TestV6CrossChannelChecksSayOnlyWhatTheyCanOrder
V7 an ahead dev build is refused in both doors with the documented sentence and installs nothing; --check exits 0 with the newest/running line; naming the tag installs it; a publish moment beats the tag's date through the whole door cmd/codeaf · TestV7TerminalUpdateRefusesAnAheadDevUnlessTheTagIsNamed, TestV7APublishMomentOutranksTheDateInTheTag; internal/tui3 · TestV7ChatUpdateRefusesAnAheadChannelBuildUnlessTheTagIsNamed
V8 the twelve-row curl table, and the line reaching the launch notice, /update's failure, the source refusal and a failed replace internal/update · TestV8CurlLineTable, TestV8InstallFailureCarriesTheCallersCurlLine; cmd/codeaf · TestV8TerminalFailureUsesTheRunningFilesCurlLine; internal/tui3 · TestV8UpdateNotesUseTheRightCurlLine
V9 "what is devaf", "devaf", "can I run two versions of codeaf side by side", "how do I keep my dev build up to date" and "how do I install the latest dev build beside my codeaf" all reach the terminal page internal/manual · TestV9DevafQuestionsReachTheTerminalManual and the probe table
V10 the dev notes block spells agentfield.ai/get/devaf and no other channel enters it internal/release · TestV10DevReleaseNotesNameTheDevafInstaller

Verified by hand

  • scripts/install.sh --name '../x' | '-x' | '' each prints
    codeaf: --name / CODEAF_INSTALL_NAME must match ^[A-Za-z0-9][A-Za-z0-9._-]*$
    and writes nothing; --name devaf against the real release service really does
    write <dir>/devaf.
  • codeaf version does not create the state root, so moving the installer's
    closing version run after the install-marker block (which is what "the last action
    runs that file's version" asks for) changes no marker behaviour — only the order in
    which the version line and the telemetry notice print.
  • Gates run in the worktree: go build ./... && go vet ./... clean, gofmt -l cmd internal
    silent, bash test/installer-telemetry.sh35 passed, 0 failed, the four touched
    packages green, and make pr-ready green.
  • An adversarial read-only review round against the contract; its one real finding
    (the manual overclaiming what --check does across two channels) is fixed in the
    last commit, with two tests that pin the corrected sentences.

Not verified here

  • Windows. CurlLine strips .exe and the installer appends its existing
    extension, both covered by tests, but no native Windows shell run was made.
  • The live proxy. agentfield.ai/get/devaf is the other half of this change and
    ships as a separate website2.0 pull request. That route rewrites two lines of this
    installer — the channel and the name — and fails closed with a 502 until this
    pull request reaches the dev branch, because the line it must rewrite does not
    exist there yet. Land this first.

Added after the first review pass

  • A release candidate follows stable, so it is offered the stable road. The first
    draft took the curl line from the tag's own channel, which handed an rc build
    …/get/codeaf/rc while the notice beside it named the stable release — it would have
    reinstalled a channel that build's own /update never selects. update.FollowedChannel
    is now the single answer to "which channel is this build on" (dev and staging follow
    themselves, everything else follows stable), and the launch check, the surface and the
    terminal door all take the curl line from it. The two places that spelled the same rule
    out longhand to pick a default channel ask it too. Proven by
    TestTheChannelABuildFollowsIsStableUnlessItIsDevOrStaging,
    TestAReleaseCandidateNoticeOffersTheStableRoad and
    TestAFailedReleaseCandidateUpdateOffersTheStableRoad; all three were checked to fail
    against the old behaviour before the fix, and a real stamped v0.3.0-rc.1 binary was
    driven by hand to confirm it.
  • CODEAF_INSTALL_NAME is registered as operator plumbing. CurlLine spells the
    word into the road it offers a binary installed under another name, and the settings
    registry law counts every CODEAF_* word in the tree. It is the installer's pin and
    codeaf never reads it, so it lands on the allowlist rather than becoming a row a person
    could set and this binary could not act on.
  • The keys page answers "how do I exit codeaf" again. Two new sections landed in one
    release — this branch's shell-update section and santos/dev: crew family, Pareto crewing and the Model Pool, custom connections #1108's headless exit-code section on
    dev, both genuinely about exit CODES — and between them they pushed the page that
    answers the question out of the results. Neither branch is red alone; the pair is. The
    quitting section was written almost entirely in quit, leave and close, so it now
    opens with the sentence a person actually types and says exit where it said leaves.
    Fixing the page rather than the probe is the rule this corpus is held to.

Verified against the real binary, by an independent lane

Head 1b9eb958d was built six ways (stamped dev-20260917-a0cc22169334 as devaf and as
codeaf, dev-20260921-ffffffffffff, dev-20260918-9258f7052379, v0.2.1, unstamped) and
driven under tmux against a local mirror of the real release list and one real dev asset,
never GitHub. Eleven ledger rows, all passing: the launch line for a behind dev build ends
with the devaf road and only the codeaf-named file gets /get/codeaf/dev; /update on a
devaf downloads, checks, installs at the devaf path and restarts the same transcript
(/status names the new build, /proc/<pid>/exe is the devaf file, sha matches the
asset); an ahead dev build gets no line and both doors refuse the implicit downgrade with
the quoted sentences, --version installs it; an equal build says it is newest; the
stable road is unchanged and writes update-check.json while dev writes
update-check.dev.json; a source build makes no request; the installer with --name devaf --dev writes devaf beside an untouched codeaf, the variable form does the same, and the
three bad names exit 2 writing nothing; three alternating launches of a dev and a stable
build in one profile made two requests. Extras driven: a staging build follows staging, a
file named winner prints the CODEAF_INSTALL_NAME=winner form, /update stable on a
devaf takes the override and a failed download ends with the devaf line. The rc road was
driven at both heads: wrong at 1b9eb958d (/get/codeaf/rc beside a notice naming
v0.3.0), right at 806be7745.

The four manual gaps that drive surfaced — the third curl form and the chat's source-build
refusal unquoted, a wrong clause about cross-channel --check, and "how do I keep my dev
build up to date" ranking behind the dev-server page — are closed in a6d98c596, by heading
vocabulary and quoted strings, never by the probe.

🤖 Generated with Claude Code

AbirAbbas and others added 10 commits September 21, 2026 10:04
…deaf

The installer takes --name WORD and CODEAF_INSTALL_NAME, validated against
^[A-Za-z0-9][A-Za-z0-9._-]*$ before anything is written, and writes
$INSTALL_DIR/$NAME with a matching temporary file, a printed line naming the
real file, and a closing version run of that file. INSTALL_NAME sits on one
unindented line directly under CHANNEL so the website proxy can rewrite it the
same way it rewrites the channel.

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

A dev or staging build now asks its own channel at launch and gets the same one
dim line when that channel has a newer build; /update and codeaf update with no
channel word select it, while an explicit word or tag still wins. Two channel
tags are ordered by publish moment first, then by the date in the tag, and a
same-day tie with a moment missing goes to the release the API named, so a build
ahead of the newest is never silently downgraded. The answer is cached per
channel — update-check.dev.json and update-check.staging.json for one hour —
so a devaf beside a codeaf cannot thrash one file, and stable keeps
update-check.json and its 24 hours.

CurlLine replaces the one constant everywhere a road back is offered: a file
named devaf is told to reinstall from /get/devaf, a codeaf on a channel from
/get/codeaf/<channel>, and any other name carries CODEAF_INSTALL_NAME. A source
build's refusal still prints CurlCommand.

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

The update section no longer claims dev and staging launches are silent: it
names the channel each build checks, the cache file and window it uses, the
default channel of a bare /update, the ahead refusal, and the curl line that
reinstalls this particular file. A new section answers "what is devaf" — the
/get/devaf line, the file it writes beside codeaf, the general --name spelling,
and the state both builds share. codeaf --help says this build's own channel is
the default, and GUIDE.md carries the --name row.

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

A dev build's notes now carry curl -fsSL https://agentfield.ai/get/devaf | bash
and one sentence saying it installs that dev channel as devaf beside codeaf.
Only the dev channel gets the block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A review round caught the page overclaiming: it said exit 3 means the selected
release is newer, when a dev or staging tag selected from a build of another
channel also exits 3 — a channel tag and a version number cannot be ordered
against each other — and a dev build asking --stable exits 0 for the same
reason. The ahead line is spelled with the channel it names rather than dev
alone. Two terminal tests now pin both sentences.

The ordering table gains the cases the law left open: no moment at all, one
moment twice on two days, and one moment twice on one day. A pair the law
cannot rank — two channels, a channel tag beside a version, a tag that names
no release — is neither newer nor ahead and draws no launch line. A terminal
case proves a publish moment beats the date in the tag all the way through the
door, an install failure proves it offers the road back to THIS file, and the
installer refuses a bad name from the environment and a --name with no word.

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

An rc build follows stable — its launch line names the stable release ahead of
it, and a bare /update or codeaf update installs stable — but the curl line
under that line said /get/codeaf/rc, which installs a channel the build never
selects. The notice named one release and offered another.

FollowedChannel is now the single answer to "which channel is this build on":
dev and staging follow themselves, everything else follows stable. The launch
check, the surface and the terminal door all take the curl line from it, and
the two places that spelled the same rule out longhand to pick a default
channel now ask it instead. CurlLine keeps its whole table, because an
explicit road to any channel is still a thing a person can ask for.

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

CurlLine spells CODEAF_INSTALL_NAME into the road it offers a binary installed
under another file name, and the registry law counts every CODEAF_* word in the
tree. The word is the shell installer's and codeaf never reads it, so it lands
on the operator allowlist rather than becoming a row: a row would persist a
preference this binary cannot act on.

The law only went red now because origin/dev moved under the branch and widened
the touched-package set; the pin has been unregistered since it was written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"how do I exit codeaf" stopped reaching the keys page once two new sections
landed in one release — this branch's shell-update section and the worker
harness's headless exit-code section on dev — and both of them are genuinely
about exit CODES. The page that answers the question was written almost
entirely in quit, leave and close, so it lost a question its own heading spells
out. It now opens with the sentence a person asks for and says exit where it
used to say leaves, which puts it back at the top of that search.

Neither branch is red alone; the pair is. Fixing the page rather than the probe
is the rule this corpus is held to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e asker's words

The verifier's real-binary drive found four gaps between the page and the
program. A build under any file name but codeaf or devaf prints its channel's
line with CODEAF_INSTALL_NAME on the end, and the page never said so. The
chat's own source-build refusal was unquoted; only the terminal's variant was.
The reason a cross-channel --check exits 3 was written for a version number
against a channel tag, which is not what two channel tags are. And "how do I
keep my dev build up to date" reached the dev-server section first, so the
update heading now carries the asker's words, as the devaf heading carries
"different file name" and "--name".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas merged commit 744ff16 into dev Sep 21, 2026
4 checks passed
@AbirAbbas
AbirAbbas deleted the feat/devaf branch September 21, 2026 16:49
santoshkumarradha added a commit that referenced this pull request Sep 21, 2026
…1333)

CODEAF_TASK_BELT kept its name, its one reader and the sense of every caller.
What moved is the answer it gives when nobody has spoken: bashBeltAsked was a
match against "bash" and is now a lookup in a closed list of the words that
mean the older road — node, legacy, off. Both engines are still in one binary.

The list is closed on purpose. An unrecognised value leaves a person on the
harness rather than moving them off it, because a typo in an environment
variable must not be able to change which engine does the work and the failure
it would cause is silent. An exported but blank value reads as unset, which is
the default, which is bash.

The two refusals that named the way in now name the way out. The manual pages
that told a person the harness was not the default tell them it is, and name
the three words that turn it off.

The bench is the one place this could have been wrong without saying so: arm A
ran with the variable unset, so after the flip both arms would have ridden the
bash belt and the driver would have reported a comparison it never made. Both
arms now name a word, and the branch that handled an absent one is gone.

A hundred and forty-one tests in internal/session and cmd/codeaf were written
against the node engine and said so by saying nothing. They did not become
wrong when the default moved, they became silent about which road they meant.
Each package's TestMain names that road once, rather than each test naming it,
and a test that means the harness still sets "bash" for itself and wins.

That pin has a cost, so the truth table is asserted directly in a new file:
with the variable unset the belt is bash, the three words turn it off, case and
surrounding space do not matter, and an unknown word moves nobody. A second
test asserts the pin itself, because without it every other test in the package
would quietly change what it tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant