release: v0.2.0 — the work outlives the session - #81
Merged
Conversation
Owner
Author
AetherAI3
force-pushed
the
release/v0.2.0
branch
from
August 19, 2026 11:53
5579280 to
1b1b4d4
Compare
`aether resume` could replay a prior session's transcript to the screen, but the brain never saw a byte of it: continuing meant re-typing the story so far, and only ever on the machine where the work started. This makes the resume path mean what it says. A handoff (core/handoff.ts) is the machine-facing half of a session log — a small JSON document distilled from one run: the task, the model that ran it, the verify gate's verdict, the failing-test count, the files it changed, the verification command, and the repository identity (origin remote, branch, HEAD). - `aether agent --resume <id>` now prepends a continuation brief built from that record to the task the brain receives, so a different model picks the thread up with the project context in hand. With no new task, the run continues the original one (it used to fail with "nothing to do"). - `aether resume export [id] [--out <file>]` writes the handoff to a file. Nothing in it is keyed to an absolute path, so it can be copied to another checkout, machine, or OS, and `aether agent --resume <file>` continues there. A file reference is deliberately not workspace-scoped; a session id still is. - The handoff is a summary, not a transcript: no file contents, no shell commands, no credential-shaped values. Untrusted files are validated field by field on the way back in, and one written by a newer Agent is refused with an upgrade hint rather than half-read. Three fixes the above needed, each a bug in its own right: - `aether agent --local "<task>"` spawned the separately-installed Python brain unconditionally, so a plain `npm i -g aether-agents` could only ever answer `spawn python ENOENT`. The one-shot offline path now drives the Ollama brain that ships inside the package — the same one the REPL's `--local` turns already used. `AETHER_LOCAL_BRAIN=python` opts back in, and the choice is a pure decision in core/backend.ts beside chooseBackend. - The session log's credential filter matched `pat` as a substring, so `path` (and PATH, patch, pattern) was stored as "[REDACTED]". Every log could say a file changed but not which one. `pat` is now anchored to a whole segment; pat, gh_pat and pat_token are still redacted. - The Ollama brain reported a placeholder `remaining: 1` on any unsuccessful run, so an unreachable-Ollama turn printed "1 test failing" when no test had run. The failing count comes only from the host's own verify run. `npm run demo:handoff` (scripts/handoff-demo.ts, docs/demo/handoff.md) is the end-to-end proof and takes about five seconds: it builds a throwaway git repo with two genuinely failing tests, runs the real CLI on model A until half the work is done, exports the handoff, creates a second checkout at a different path, deletes the first checkout AND its logs, then finishes the job on model B with `--resume <file>` and no restated task. The model — and only the model — is a scripted local stub so the run is deterministic and needs no download or account; AETHER_DEMO_REAL=1 runs the identical script against real Ollama models. It asserts that session B's prompt carried the brief, that `node --test` is green when run independently of the agent, and that the verify gate exited 0, so it works as a CI gate as well as a demo.
…log once
Cleanup pass over the handoff feature. No intended behaviour change except
where a reused helper is strictly better than the hand-rolled version — those
are called out below.
Reuse:
- `writeHandoff` uses `atomicWriteFile` (core/durable_store.ts), the same
write-then-rename every other durable file in this CLI already uses. An
interrupted `resume export` can no longer destroy a good handoff or leave
half a JSON document, and `--out reports/handoff.json` now creates the
missing parent instead of failing with ENOENT.
- `readHandoff` uses `readJsonFile`, which already distinguishes missing from
unreadable from corrupt — so the hand-rolled ENOENT check is gone and an
empty file reads as "corrupt", the codebase's word for it, rather than
"Unexpected end of JSON input".
- Untrusted strings go through `sanitizeTerm` (ui/text.ts) and `clipCodePoints`
(ui/theme.ts) instead of a local clipper. A handoff arrives from another
machine and its strings are BOTH printed and prepended to the brain's prompt,
so it was the only untrusted-input surface in the CLI not passing through the
terminal sanitizer. `clipCodePoints` also stops a truncation from cutting a
surrogate pair in half.
- `wroteFile()` is now the one definition of "the run changed a file", called
by both a handoff's `filesTouched` and cmdCode's live blast-radius set, which
had the predicate written out twice.
`isHandoffPath` had a real defect: its separator class held only a forward
slash, so `--resume C:\work\handoff` (no .json) was classified as a session id
and died as "invalid session id". It now inverts `requireOpaqueId` — the same
rule `loadSession` enforces a moment later — so the two branches provably
partition the input, and Windows separators, `..`, and `~/` fall out for free.
Read the session log once. `resolveHandoff` loaded the session, distilled it,
and threw it away; `replaySession` then re-derived the file-vs-id decision and
loaded the same session again to render it. `resolveResume` returns
`{handoff, session}` and `resumeReplayLines` renders from it — one read, one
decision, and `replaySession`'s `ref`/`cwd` parameters disappear. On a
2000-event log that is 2000 fewer JSON parses and decodes per resume.
`aether resume` dispatches on argv like every other subcommand-bearing command
in main.ts, instead of joining `rest` into a string and re-splitting it by
prefix. `cmdResumeExport` is its own exported function. Side effect: `aether
resume abc def` reports `no such session: abc` rather than looking up the id
`"abc def"`.
Smaller: `summarizeEvents` bounds `highlights` as it goes rather than growing
to N and slicing to 40, and uses a Set for `filesTouched`; `parseHandoff`
bounds arrays before clipping them, so a hostile file with 10k highlights costs
40 clips rather than 10k; one `repoFrom` and one `asObject` replace two
spellings each; the demo's two model scripts are one shape with two
substitutions; the demo header no longer restates docs/demo/handoff.md.
Tests: the four resume-command cases share one `withLogRoot` fixture instead of
pasting the same 10-line prologue and env restore four times, and there are new
cases for the Windows path classification, the sanitizer, the shared write
predicate, and `--out` into a missing directory.
Deliberately not done: collapsing readRepoIdentity's three git spawns into two
(one saved spawn, subtler parsing); `latestSession`'s full-log scan (real, but
pre-existing and outside this diff); rebuilding `summarizeEvents` on top of
`monologueLine` (its tree-rendering shape is not a summary's).
The README's first screen led with the category ("a coding agent for your
terminal"), which is true of every tool on the shelf. It now leads with the two
things that are specific to this one and reproducible from a clean checkout:
the verify gate decides when a run is done, and a run's context can leave the
machine it started on.
- README hero: "Start a task on one model. Finish it on another, on another
machine. Your tests decide when it's done." — followed by the install command,
a runnable handoff example, and `npm run demo:handoff`.
- Two claims corrected rather than restated. The local brain was credited to
the Unlimited Context engine, which lives in a separate Python package the npm
tarball does not carry; the shipped offline brain talks to Ollama directly,
and the Python one is now described as the opt-in it is. `--test-cmd` was
documented as defaulting to `pytest -q`; it has no default, and a run without
one ends `unverified`, never `ok`.
- The version bumps to 0.2.0. npm still serves 0.1.0 from June, so every
`npm i -g aether-agents` today predates agent dev sessions, the media history,
doctor v2, and everything in this release. Publishing is release-triggered
(.github/workflows/release.yml), so cutting the tag is what ships it.
- The production verifier's test read the expected version from a literal
"v0.1.0". It reads package.json now: a version bump is a release step, not a
reason for the release gate to go red.
Release notes in RELEASE_NOTES.md and docs/releases/2026-08-19.md.
…t or your own Ollama
AetherAI3
force-pushed
the
release/v0.2.0
branch
from
August 19, 2026 12:11
1b1b4d4 to
2606756
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #80. Everything here is packaging: no behaviour changes.
Why
npm still serves 0.1.0, published 2026-06-11. Every
npm i -g aether-agentstoday predates agent dev sessions, the durable media history,doctorv2, and the whole of #80. Publishing is release-triggered (.github/workflows/release.yml), so cutting the tag is what ships it.README first screen
The old hero led with the category — "a coding agent for your terminal" — which is true of every tool on the shelf. It now leads with the two things specific to this one, both reproducible from a clean checkout:
Below it: the install command, a runnable handoff example, and
npm run demo:handoff. Model tables, pricing, and platform links stay where they are, below the fold.Two claims corrected, not restated
AETHER_LOCAL_BRAIN=pythonopt-in it is.--test-cmdwas documented as defaulting topytest -q. It has no default — a run without one endsunverified, and neverok.The old hero paragraph also claimed QOPC memory made the agent "measurably better the more you use it". That is a server-side claim the client cannot demonstrate, so it is out of the first screen.
Version
0.1.0→0.2.0inpackage.jsonandsrc/version.ts(the lockstep test covers both), plus the pinned-version examples inREADME.mdandinstall.sh.test/production_hardening.test.tsasserted the literalv0.1.0. It readspackage.jsonnow — a version bump is a release step, not a reason for the release gate to go red.Release notes
RELEASE_NOTES.mdgains the dated v0.2.0 entry;docs/releases/2026-08-19.mdcarries the long form and is indexed indocs/releases/README.md.Verification
npm run typecheck— cleanversion+production_hardening— 9 tests green at 0.2.0, including the pack → global install →aether --version→aether --helpsmoke against the real tarballnpm pack --dry-run—aether-agents-0.2.0.tgz, 416 files, 2.0 MB unpacked, zero runtime dependenciesNot done here
Cutting the GitHub release (and therefore the npm publish) is deliberately left to a human — the draft text is ready in
docs/releases/2026-08-19.md.