Skip to content

Critical-app editing follow-ups: per-app leases, a real preview lifecycle test, an inert secondary prioritizer - #661

Draft
gnguralnick wants to merge 29 commits into
mainfrom
wild-bedbug
Draft

gnguralnick wants to merge 29 commits into
mainfrom
wild-bedbug

Conversation

@gnguralnick

@gnguralnick gnguralnick commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Follow-ups to three of the review comments on #552 that were still open when it merged. One commit per item.

What changes

  • Critical-app edits lease per app. The careful flow held one editing critical apps lease, so an unrelated chat edit and shell edit could not run at once. Nothing needed that: update_self.py apply refuses to run beside another apply, and its bundle-stamp check builds from the merged tree instead of installing a bundle whose sources another pass changed. A pass now takes editing critical app <name> for each critical app it changes or previews (a workspace_ui or npm-file change counts as both the shell and the chat). The go-live freshness check also covers every file the pass changes, which the single lease used to guarantee, and the lead re-checks for another pass's notice before applying, since the apply replaces its rollback point. update-self and harden-contention.md use the per-app names; update-self's release step also stops naming the stale editing service system_interface lease.

  • A real preview lifecycle test. preview_app_test.py stubs serve_isolated_instance.py and hand-writes the state file it would write, so drift between the two passed both suites. test_preview_app_lifecycle.py boots a fixture app from its [preview] table through the real shared script and forward_port.py, checks it serves a copy of the live data, refreshes a rebuild on the same port, and tears it down. Renaming the state file's inner_port in the shared script alone fails this test while the 11 stubbed tests still pass.

  • An inert OOM prioritizer for a secondary chat. _refuse_to_set_oom_score_adj let a preview chat run the whole ranking (pid lookups, process start times) only to drop each write. ChatOomPrioritizer's set_adj is now optional; None accepts every report and resolves, computes, and writes nothing, and runs no sweep.

The other two comments (routing /assist through heal-creation, and making system/scripts importable) need no change: heal-creation itself sends critical apps to the careful flow, and system/scripts stays stdlib-only so registration and recovery work without the root venv.

Testing

  • Full chat suite (from a clean checkout, with coverage): 1714 passed, 31 skipped.
  • .agents: 785 passed, 8 skipped. Root system/libs 458 passed, system/services 453 passed, system/test_supervisord_layout.py 6 passed. system/scripts: 372 passed, 5 failed, none touched by this branch: the four agy_shim tests are known macOS-only failures (no /proc, bash 3.2), and test_lead_address_by_id.py fails the same way on main here (Docker not running). CI is the real signal for those.
  • The new prioritizer test fails when the inert early return is removed, and the lifecycle test fails against the renamed-key shared script above.
  • The per-app lease change is instructions only; the new freshness command was exercised on a scratch repo (it flags a shared file changed on both sides and ignores an unrelated one). No concurrent critical-app passes have been run in a real workspace.
  • An architecture review then flagged that two passes needing the same pair of apps could each take one lease and stall; leases are now taken all or none at entry (in update-self too). Review passes made 18 further follow-up commits, mostly wording that keeps the reference, spec, and test plan consistent with the per-app leases. Two change behaviour: update-self now counts terminal_pty among the critical apps it leases for, and the freshness check lists both paths of a renamed file (--no-renames).

Gabriel Guralnick and others added 29 commits September 22, 2026 11:14
…writer

ChatOomPrioritizer's set_adj is now optional; None makes it accept every
report while resolving, computing, and writing nothing, and skip the sweep.
The secondary chat passes None, replacing _refuse_to_set_oom_score_adj,
which let the whole ranking run only to drop each write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
preview_app_test.py stubs serve_isolated_instance.py and writes the state
file the real script would, so drift between the two (a renamed flag or
state key) passed both suites. The new integration test boots a fixture
app from its [preview] table with nothing stubbed, checks the preview
serves a copy of the live data, refreshes a rebuild on the same port, and
tears it down, registrations included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The careful flow held one 'editing critical apps' lease, so an unrelated
chat edit and shell edit could not run at once. Nothing needed that: the
apply refuses to run beside another apply and rebuilds a bundle whose
sources moved under it. Each pass now leases the critical apps it changes
or previews ('editing critical app <name>'), the go-live freshness check
also covers every file the pass changes, and the lead re-checks for a
notice another pass raised before applying.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: test-plan.md's C1 secondary-contract check still said the live
chat's oom_score_adj stays unchanged "after the preview's sweep interval
(the refusing set_adj)", but a secondary chat's prioritizer now has no
writer and runs no sweep.
Fix: the check now waits after a send from the preview and names the
inert prioritizer (no writer, no sweep) as the reason nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: critical-app.md step 4's freshness check said the branch is
mergeable only if "nothing the two bundles are built from" changed, but
the command diffs only the pass's own package, the shared library, the
npm files, and the branch's own files. With per-app leases a pass on the
other critical app can legitimately change that app's sources, which
section 1 says the apply handles by rebuilding the bundle.
Fix: the sentence now names what is checked and says a change to the
other critical app's sources is not staleness because the apply rebuilds
that bundle from the merged tree. The command is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: plan-critical-app-editing.md's summary of references/critical-app.md
still listed "the lease" and a freshness check "over both frontends, the
library, and the lockfile", while the reference (and line 56 of the same
plan) now describe per-app leases and a check that also covers every file
the pass changes.
Fix: the summary names the per-app leases and the freshness check's actual
scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: update-self step 5b takes an `editing critical app <name>` lease for
each critical app an update touches, but its list of critical-app trees
omitted system/apps/terminal_pty/, which is `critical = true` and whose lease
the careful flow takes when a terminal preview boots it.
Fix: add system/apps/terminal_pty/ to the list, so an update touching only
the pty checks for and takes that app's lease like the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the sentence saying a None set_adj makes the prioritizer inert was
spliced into the ChatOomPrioritizer class docstring mid-line, leaving one
121-character line in a paragraph that wraps at about 80.
Fix: break the line before the resolve_process_started_at sentence. Wording
is unchanged.

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

Problem: harden-contention.md summarized critical-app.md step 4's freshness
check as the app package, workspace_ui, and the npm files, but that check now
also diffs every file the pass branch changes.
Fix: add the pass branch's own files to the summary so it matches the check it
points at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: critical-app.md step 4's freshness check spoke of "your app's bundle"
and diffed a single system/apps/<package>/, though a pass can hold leases on
several critical apps (a workspace_ui or npm-file change always holds both the
shell and the chat).
Fix: say the check covers every app the pass holds a lease on and to name each
one's package, and call a foreign app "another critical app".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: update-self lists the critical apps by directory (including
system/apps/terminal_pty/) and takes `editing critical app <name>` per app, but
never says <name> is the app.toml name; terminal_pty's is `terminal-pty`, so a
lease named after the directory would not match the careful flow's anchored
tk ready check.
Fix: state that <name> is the app.toml name, with terminal_pty as the example.

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

Problem: the critical-app-editing plan's testing section still said
`--secondary` "refuses OOM writes", the refusing-writer shape the branch
replaced with an inert prioritizer.
Fix: say it keeps an inert OOM prioritizer with no writer, matching the
agent_manager.py bullet and the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: update-self 5b called its trigger list "the trees the critical bundles are
built from, the same set the careful flow's freshness check names", but the list now
includes the terminal and terminal-pty trees (no bundle is built from them), and the
careful flow's freshness check names only the leased apps' packages plus the pass's
own files.
Fix: say the list is every critical app's tree plus the shared library and npm files
both frontend bundles are built from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the careful flow's freshness check now names only the leased apps' packages
(so passes on different critical apps run side by side), but the test plan's E1 only
checked that an edit under the pass's own app is named.
Fix: E1 also commits an edit under the shell with a chat-only pass and expects the
check to stay empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: plan-critical-app-editing.md's careful-flow summary said go-live
checks freshness over 'the bundles' sources', which reads as both frontend
bundles, while critical-app.md step 4 diffs only the leased apps' packages,
the shared library, the npm files, and the pass's own files.
Fix: name that set, matching the reference and the plan's own summary of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: ChatOomPrioritizer.start and reapply return immediately when
set_adj is None (a secondary chat's inert prioritizer), but their docstrings
still said they begin the sweep and write every chat's score.
Fix: add the inert case to each docstring.

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

Problem: test_preview_app_lifecycle.py recomputed the forward_port.py path
that preview_app.py already holds as _FORWARD_PORT_SCRIPT, so a move of
either file could leave the two copies pointing at different places.
Fix: copy mod._FORWARD_PORT_SCRIPT from the loaded preview_app module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the plan's summary of references/critical-app.md said the go-live
freshness check covers "the pass's app", while the reference and the plan's
own careful-flow bullet say it covers every leased app's package.
Fix: say "the leased apps' packages" so the two bullets agree.

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

Problem: harden-contention.md said critical-app.md step 4 applies the
freshness check over one system/apps/<package>/, but step 4 names the package
of every app the pass leases (a workspace_ui pass leases the shell and chat).
Fix: say "each leased app's system/apps/<package>/".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the test plan's C1 secondary-contract check (docs/system/blueprint/critical-app-editing/test-plan.md) said a running chat's agent's oom_score_adj stays unchanged after a send from the preview. The send is real, so the live chat sees the addressed agent's turn start and its own prioritizer legitimately re-tags that agent; checked there, the step could fail for reasons unrelated to the preview.
Fix: name an agent the send did not address, and say why the addressed one may move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: step 4 of critical-app.md built the branch's own file list with a
plain `git diff --name-only`, whose default rename detection names only a
rename's destination, so an edit to the rename's source on the served branch
passed the check and merged into the renamed file unverified.
Fix: pass --no-renames so both paths of a rename enter the pathspec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: test_preview_app_lifecycle.py raised its budget to 180s, 18x the
suite's 10s default, as a bare literal, where every other .agents test that
overrides the budget names the constant and says why.
Fix: move it into _BOOT_TIMEOUT_SECONDS with a note that the shared script
allows each boot 60s to pass its health check and the test boots twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With one lease per critical app, two passes that each need the shell and
the chat could each take one lease, find the other's, and both stop. A
pass now takes its leases in name order and releases the ones it took
when any is held by another agent.
Problem: update-self's critical-app lease step read per app (check one, take one), so an update touching workspace_ui could take chat, find system_interface held, and surface while still holding chat -- the hold-one-each state critical-app.md's all-or-none rule prevents.
Fix: say update-self takes them all or none as critical-app.md does: check each, take them in name order, and release the ones taken before surfacing a held one; note it in the changelog line.
Problem: the spec's careful-flow bullet (plan-critical-app-editing.md) summarized the per-app leases but not the all-or-none rule critical-app.md now sets for taking them, and the system changelog did not mention it.
Fix: add the rule to the spec's lease parenthetical and to the system changelog paragraph.
Problem: critical-app.md said a pass taking its leases all or none means two
passes needing the same apps never hold one each, but the growth path still
lets a pass take another app's lease while holding its own, with no guidance
when that lease is held, so a mutual wait surfaced as a bare "another chat is
editing" message.
Fix: scope the guarantee to entry, and have a growing pass that finds the new
lease held surface it along with the leases it already holds, since the holder
may be waiting on one of them. The changelog entry says the same.
Problem: the careful-flow bullet in plan-critical-app-editing.md said the
leases are taken all or none "so two passes never hold one each", but
critical-app.md scopes that guarantee to entry: a pass that grows to another
critical app takes that lease while holding its own.
Fix: say the leases are taken all or none at entry, so two passes needing the
same apps never hold one each, matching critical-app.md and the changelog.
Problem: the all-or-none rewrite of update-self's step 5b lease rule dropped
the earlier "surface instead of proceeding", leaving only "release the ones
you took before surfacing it"; with no approval gate before the apply, the
text no longer said outright that the apply does not go ahead.
Fix: release the leases taken and surface the held one instead of proceeding.
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