Edit the workspace's critical apps through previews, the careful flow, and a kept rollback point - #552
Merged
Conversation
Co-authored-by: Sculptor <sculptor@imbue.com>
Co-authored-by: Sculptor <sculptor@imbue.com>
The chat app no longer runs its own 'mngr observe'. It follows the event file the supervised agent-observer program writes, through mngr's follower, and starts before the observer without retrying: /api/health gains agent_events, which reports the outage until the observer's opening snapshot is folded, and a dead observer freezes the agent view only until supervisord brings it back. --secondary boots a second chat beside the live one (a preview): it follows the same observer and reads the live accounts, but reconciles no accounts, writes no memory scores, registers nothing, and nudges only the shell named by --nudge-shell-url. CHAT_DATA_DIR moves the chat's data directory so such a chat's stamps land in a scratch copy. Co-authored-by: Sculptor <sculptor@imbue.com>
Tracks 48bbd811c332c3eb4d4bbb2892bf2d1f80b817ca in mngr.
agent-observer in supervisord runs the workspace's one 'mngr observe --quiet' from the primary agent's work dir, in the chat's memory band, restarted on exit like every service. A system test runs the chat over a real 'mngr observe': the chat boots first, lists once the opening snapshot lands, keeps its list through the observer's death, and recovers when a new one takes over. The contracts, READMEs, and changelogs record the program and the chat's health field. Co-authored-by: Sculptor <sculptor@imbue.com>
The built-in service bands are strictly ordered and distinct, so the observer cannot share the chat's 25. It takes 24: shedding it blinds every chat's agent view at once while freeing almost nothing, whereas a shed chat app comes back to a stream the observer kept writing. Co-authored-by: Sculptor <sculptor@imbue.com>
…flag Co-authored-by: Sculptor <sculptor@imbue.com>
PreviewSpec in app_manifest describes how a throwaway instance boots: named free ports, scratch copies of the directories it names, and placeholders in its command, args, and env, validated against what the table declares. A manifest without the table gets the build-app scaffold's convention, so every app previews by construction; the shell, chat, and terminal declare what they need beyond it, and the scaffold writes the convention out for a new app. Co-authored-by: Sculptor <sculptor@imbue.com>
The root ruff config reformatted three modules this branch does not change; they go back to main's spelling so the diff stays the preview table. Co-authored-by: Sculptor <sculptor@imbue.com>
system-interface --preview marks its page with a meta tag, refuses every relay verb and app stop/start with a 403 naming the preview (the op route's create included), says so in its inventory document, and raises no staleness banner. The frontend reads the tag and hides the verbs the backend refuses: rename, delete, stop, and start on tab and rail menus, the launcher's tiles, the picker's rows, and a stopped app's Start; tab, filing, and pinning verbs stay, since they edit the preview's own copy. Co-authored-by: Sculptor <sculptor@imbue.com>
… inner path
Carries the old branch's refresh, verified teardown, boot-log excerpts, and
memory-band tagging forward, and adds what a manifest's [preview] table needs:
--port-env NAME=ENVVAR for a second free port, --copy KEY=SOURCE for a directory
copied into the instance's scratch space, {port:...}/{copy:...}/{scratch}/{host}
filled in the launch argv and --env values, and --inner-path for the page the
preview frame opens. The state file records ports, copies, and the inner path,
so refresh and down need nothing but the name.
Co-authored-by: Sculptor <sculptor@imbue.com>
terminal-app --no-register serves ttyd and its instances API without re-pointing the live terminal's registry row; the sidecar launcher takes the same choice as is_registered. Co-authored-by: Sculptor <sculptor@imbue.com>
preview_app.py reads the worktree's manifest, boots the siblings named with
--with first, writes the shell's registry copy with each sibling's row pointing
at its preview (URL, instances URL, and origin label), fills {registry} and
{shell_url}, and hands the table to the isolated-instance script, which gains
--port NAME for a port no env var carries. One preview per app at a time: a
different worktree's live preview is refused rather than hijacked. down tears
the siblings down too.
Co-authored-by: Sculptor <sculptor@imbue.com>
The contracts gain the manifest's [preview] table and the built-ins' tables, the preview shell's refusals and meta tag, is_preview on the inventory document, and where an isolated instance keeps its copies; update-app points at preview_app.py for any app with a manifest; every touched project's changelog records phase 3. Co-authored-by: Sculptor <sculptor@imbue.com>
update-app reads the app's app.toml first: critical = true, or a change under the shared workspace_ui library, follows references/critical-app.md, which replaces the update-system-interface skill for the shell, the chat, the terminal, and any app that declares itself critical. The worker references gain the critical-app handoff, create_worker.py carries the old branch's --branch passthrough and reads the worker's branch back from mngr, and the skills and docs that named the removed skill name the flow instead. Co-authored-by: Sculptor <sculptor@imbue.com>
An update-self apply run with --keep-rollback-point records where it came from (merge sha, the commit to return to, the snapshots it kept, the critical apps and programs it touched, and whether the diff reached the workspace's own setup) in data/.state/update-apply/last-good.json instead of discarding its snapshots. Two new subcommands act on that record: rollback-last reverts the merge, restores the copies, restarts exactly the recorded programs (or names the services-agent restart when the diff reached provisioning) and waits for the workspace to settle; confirm-last drops the record and the snapshots. "Settled" is now one verdict shared by the apply, the recovery path and the rollback: several consecutive healthy answers from the shell and every critical app's instances API, on supervisord pids that hold still across all of them. Co-authored-by: Sculptor <sculptor@imbue.com>
The shell reads and watches the rollback point the careful flow's apply keeps (data/.state/update-apply/last-good.json), pushes every distinct reading over the socket as update_notice_changed, and answers GET /api/updates/pending plus the two verbs: confirm runs the update-self script's confirm-last, rollback starts rollback-last detached in its own session (the rollback restarts the shell's own supervisord group when the shell was touched). The frontend shows the notice as a band above the page in every tab of an app the record names and as a top banner for the shell itself: Roll back asks first, naming the update and what restarts; Everything seems good confirms; a running rollback shows its progress and a settled one its outcome with Close. A preview shell shows the notice but refuses both verbs. The registry watch and the notice watch share one file-watch helper. The direct-subprocess ratchet admits the one detached launch. A release e2e test raises the band from a seeded record over stub apps and confirms it away. Co-authored-by: Sculptor <sculptor@imbue.com>
Carries the New Tab redesign and the minds-v0.5.1 vendor refresh under the preview table, the preview shell, and the update notice. Conflicts resolved by keeping both sides: the manifest's launcher_rank beside preview, the test state's template catalog beside repo_root and is_preview, the launcher's three-argument onRunAction beside the preview shell's inert tiles, and the contracts table's preview column beside the chat's message param. Co-authored-by: Sculptor <sculptor@imbue.com>
Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: UpdateNoticeBand.test.ts imported noticeWire from src/models/UpdateNotice.test, so vitest executed that test module while collecting this one and registered its three tests a second time (the band file reported 11 tests, three of them the model file's suite), along with its top-level afterEach hook. Fix: move noticeWire beside the other record factories in src/testing/records.ts and import it from there in both files. Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: rollback_last wrote `progress` into last-good.json as its first step, but only the git-revert refusal settled the record. Any other failure -- _commit_rollback's checked `git commit`, the checked `git diff --name-only`, refresh_workspace_view -- ended the detached script with progress set and no outcome, which the shell reads as "a rollback is already running": every verb answers 409 and the band shows "Rolling back..." on the touched apps' tabs forever, with the traceback only in rollback-last.log. Fix: split _finish_rollback into settling the record and discarding the copies, extract the rollback body into _run_rollback, and wrap the call in a last-resort catch that settles the record with what failed (keeping the copies, since the tree is half-restored) and re-raises so the traceback still reaches the log. Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: the new [program:agent-observer] block was inserted between the chat's descriptive comment and [program:chat], so the chat's paragraph read as the observer's. That paragraph also still said the chat "runs ``mngr observe`` for the workspace's agents" and that its band sits just above the shell's, both of which this branch changes. Fix: move the chat's comment back above [program:chat] (the observer's program stays first, since file order is start order) and correct it: the chat follows the agent-observer program's event file, and its band sits just above the observer's. Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: "agent-observer": 24 and its comment were inserted between the chat's comment and "chat": 25 in SERVICE_BANDS, so the chat's paragraph read as the observer's entry -- and it still said the chat sits "just above the shell it is embedded in", which the new entry between them makes false. Fix: put the observer's comment and entry above the chat's, and say the chat sits just above the observer it follows, itself just above the shell. Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: pre-declutter-layout.md's rename table exists so a migrating agent is not sent looking for a file that does not exist, but its artifact-system-interface.md row still answered type-system-interface.md, which this branch deletes. Fix: answer type-app.md's "Critical apps" section, and say in the note that the system interface no longer has a reference of its own. Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: retargeting the docstring's pointer off the deleted update-system-interface flow put it on the app previews, which do not use oom_tag_shell_prefix -- serve_isolated_instance.py bands its instances through oom_tag_service.py. The function's only other caller is update_banding.py. Fix: name the update apply, and describe what it does with the prefix (banding its own forward steps down out of its own protection). Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: adding the [preview] table also ran an 88-column formatter with combining isort over the whole module, collapsing its one-import-per-line block and rewrapping every untouched Field declaration and error message. The file's longest line went 133 to 115 and about 90 of the 276 changed lines were that churn, leaving this module's import style different from the rest of the package. Fix: restore the untouched declarations and imports to their origin/main form and write the new preview code, and its tests, in the file's own style. The module's diff against origin/main is now purely additive. Co-authored-by: Sculptor <sculptor@imbue.com>
Problem: six hunks across scaffold_flask_lib.py and bands.py were pure rewraps from a formatter at a shorter line length than either file uses (their longest lines are 203 and 102), unrelated to the [preview] table or the observer's band. Fix: restore them to their origin/main form, and write the one call that really changed on a single line as the file writes such calls. Co-authored-by: Sculptor <sculptor@imbue.com>
The first run's fixes and their review changed behaviour the plan did not name: the rollback's restored-page check, confirm-last keeping a touched point's copies, the bundle stamp comparison, tool-environment touches, the workspace-only banner, the serialized rollback launch and its relayed refusals, the band dropping a refusal once the notice moves on, the sibling reframe, the unregistered terminal's silence, and the inner-path and is_frontend_built probes. Section 5 lists a scenario per change, the plan corrections the run surfaced (the apply restarts every critical program, so the scoped restart is the rollback's), and the ids to run again first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ces agent needs_workspace_restart read as the whole container restarting, when what a rollback cannot do itself is restart the system-services agent, the mngr agent that runs the bootstrap and supervisord. The flag, its wire key, the frontend's needsSystemServicesRestart, and the band's details constant now name that agent, and the details text says the workspace's system services restart rather than the workspace. The shell's meta-ratchet exempts these three identifiers: they name an agent, not an app called a service. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A wrapped preview registered both the inner app and the labeled frame as ordinary apps, so one preview added two rows to the Everything rail, the All apps popover, the New Tab launcher, and the tab list -- and both read as raw service names. The inner service now registers --internal: the frame is how it is meant to be reached, and its bare origin serves the same page with nothing marking it as a preview. An instance registered with --service-name alone, whose own origin is the surface, is unaffected. forward_port.py gains --display-name for the label a manifest-less row would otherwise lack, so the frame reads as the app it previews. Like --internal and --program it is authoritative per call, and a value the shell would reject on read is refused at registration, since such a row is skipped rather than shown. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A preview shell refused every create, so there was no way to open a terminal or a file viewer inside one -- the only route to either is their `new` action, and `_create_through_relay` raised 403 the moment `is_preview` was set. A preview of the shell whose New Tab page, rail, and app picker are all inert cannot be clicked around, which is most of what previewing a shell change is for. Worse, only a `new`-mode rail shortcut was disabled, and Terminal, File Viewer, and Browser all declare `default_shortcut mode = "focus"`. A focus-mode click falls through to a create when the view holds no instance of the app, so those three rows looked fully live and quietly 403ed. Creating is now the one mutating verb a preview keeps. It reaches whatever app the copied registry names: a sibling booted with `--with` gets the instance, an app that was not previewed gets a real live one the user can delete. Every verb that would change an instance the user already has -- rename, location, stop, start, delete, and an app's own stop and start -- is refused as before. The careful flow's live loop now closes the preview tab before each round's edits and re-opens it when the round is ready, so "verify before the user can see it" holds every round rather than only the first and a broken build is never on their screen. The preview itself stays up throughout, on the same ports and registrations, so this costs only the tab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The notice is one rollback point, and a rollback takes everything it names
back together, but each touched app's tabs carried their own band beside the
shell's banner, with Roll back buttons that looked independent and were not.
A frontend apply also lists both chat and shell, so a chat-only change raised
a "workspace interface was updated" banner too. The shell now shows the notice
once, as its top banner, naming the apps ("Chat and the workspace interface
were updated a moment ago..."), and no tab carries a notice of its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes the previous commit, which landed only the band's removal: the banner now renders the notice itself for any record, the model drops its per-app filter, the tests and the e2e test assert one banner naming the touched apps and no notice inside a tab, and the README, contracts, plan, and update-app reference describe the single banner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…editing
Carries the critical-app editing branch onto main's desktop interface. Main
replaced the tabbed shell (Dockview tabs, the New Tab page, the rail, the
instances API and its relay, manage-layout) with the desktop (windows, launch
paths, manage-desktop) and split the terminal into terminal and terminal-pty,
so several of the branch's features were re-homed rather than merged:
- The preview shell refuses only an app's stop and start (supervisord is the
live workspace's) and the update notice's verbs. Refresh, reload, window
ops, and the avatar edit the preview's own state copy or reach only its own
windows, so they stay live; the relay-verb refusals went with the relay.
The frontend hides Stop/Start through DesktopStore.canStopApp.
- The update notice lives in the desktop state, arrives over the shell socket
as update_notice_changed, and its banner reads the store; it uses main's
shared file watch. The old Inventory/Sidebar/tabMenu/Dockview hooks and
their tests are gone with those views.
- update-self's settled verdict polls each critical app's /api/health (main's
probes) instead of the instances API, and holds every critical program's
pid steady, terminal-pty's included.
- The terminal preview is the wrapper pages alone, booted --with terminal-pty
over a registry copy; both programs take --no-register, and an unregistered
terminal runs no window sweep. terminal_pty/app.toml gains a [preview] table.
- The chat preview opens on /?chat={key}, reports no client activity to the
shell, and loses --nudge-shell-url and the {shell_url} placeholder along with
the instances API they served.
- The app_instances sidecar edits, the branch's shell/file_watch.py, and the
manage-layout note (moved to manage-desktop) are dropped; the
update-system-interface skill stays deleted and main's references to it are
repointed at update-app's critical-app flow.
Tracks d9d88a911b46485c547a6ce0984d5633c2657e9f in mngr.
…s are The shell manifest's and the app state's notes still described the tabbed shell's blanket refusal; the two section banners the merge kept become plain headings.
Problem: the merge onto the desktop interface appended UpdateNoticeRecordError to shell/errors.py directly under GridSearchExhaustedError's docstring, with no blank line between the two classes; ruff format flagged the file. Fix: the two blank lines every other class in the file has.
… contracts Problem: the desktop interface's contracts.md is the normative contract for the shell after the merge (its code cites it throughout, and UpdateNotice.ts points at its section 5 for the notice's wire shape), but the branch recorded the update routes, the JSON 404, the preview shell's refusals and meta tag, is_preview on the inventory document, the update_notice_changed socket message, the [preview] manifest table, and the new data/.state files only in the workspace app model's contracts, whose route section the desktop document supersedes. Fix: rows for each in the desktop contracts' sections 2, 5.1, 5.5, 6, and 13, pointing at the workspace app model's contracts for the long form; the system changelog says so.
Problem: the preview row of the manifest table in the workspace app model's
contracts said command, args, and env values may carry {shell_url}; PreviewSpec
accepts only port, copy, host, scratch, and registry and refuses any other
placeholder as unknown, so a manifest written to the contract failed to load.
{shell_url} went with the instances API it served.
Fix: the row names the five placeholders the validator accepts.
Problem: the workspace app model's contracts still described a preview shell as booted read-only, refusing the instance relay routes (rename, location, stop, start, delete) and hiding them, with creates reaching the instances route and the New Tab page, rail, and All apps popover; none of those routes or surfaces exist on the desktop shell the branch was merged onto, and routes.py refuses only an app's stop and start and the update notice's two verbs. a9941fb corrected the code comments, app.toml, and the changelog but not this paragraph. Fix: the paragraph says what stays live (everything that edits the copy or reaches the preview's own windows), the two refused kinds with the 403 detail, what a window it opens frames, and the meta tag the frontend hides Stop and Start on.
…ords Problem: a9941fb corrected the preview shell's description in app.toml, app_context.py, routes.py, and shell/testing.py, but the --preview flag's help, PreviewShell.ts, the store's preview test, testing.py's build_test_state docstring, the shell README, and critical-app.md still called it read-only and said it refuses every verb that would act on a live instance. A preview shell opens, places, and closes windows in its state copy and refuses two kinds of verb: an app's stop and start, and the update notice's. Fix: each sentence now says that.
…start to an agent Problem: aef245d renamed the flag to needs_system_services_restart because a workspace restart read as the whole container restarting, and changed the dialog's details to say the workspace's system services restart, but the outcome rollback-last writes into the notice record still said only a restart of the workspace finishes it, its stderr line that the workspace must be restarted, and UpdateNotice.ts's field comment that an agent must restart the workspace. Fix: all three name the workspace's system services, as the dialog and the command they cite (mngr start --restart system-services) do.
…indows and the banner Problem: after the merge onto the desktop interface, comments and docs this branch adds still spoke of the tabbed shell: the notice sitting on the user's tabs (update_apply.py), a secondary chat opening no tabs (chat/main.py), a chat's own tab's presence (agent_manager.py), the live app's tabs (type-app.md), a note on each touched app's tabs (the shell README), the tab to open and the rail and tab list (serve_isolated_instance_test.py), and the band hiding the notice's verbs (update_notice.py), where 9fc6d6b replaced the band with the banner. Fix: each names windows, the launcher, the desktop, or the banner.
Problem: UpdateNoticeBanner.ts's docstring said the notice is read off what the socket keeps current twice, once for the three states and again in a sentence appended when the rendering moved from the band into the banner. Fix: one statement of it, in the sentence that names the three states.
…iel/critical-app-editing # Conflicts: # system/apps/system_interface/frontend/src/reducers/desktopState.ts
…template into gabriel/critical-app-editing
Tracks cfa25959df03c54abfd8f21d8d05e71ffb98dc37 in mngr.
Contributor
|
lgtm I guess -- it's a little weird how aware the apps are of being previews, but that doesn't seem TOO crazy... it feels a little smelly, but I'm ok trying it out for now (can't think of a better way off the top of my head that doesn't involve considerably more complexity than we want right now) long term it's probably about putting previews in containers or something like that |
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.
The template side of editing the workspace's critical apps (
docs/system/blueprint/critical-app-editing/plan-critical-app-editing.md), all six phases on one branch. The paired mngr PR is imbue-ai/mngr-internal#877 (the observe read side and the follower's start-without-a-writer mode);system/vendor/mngris refreshed from it, and that PR lands first.Before: every edit to the shell went through the
update-system-interfaceskill, and no other critical app (the chat, the terminal) had a safe path at all: the chat spawned its ownmngr observe(a shed or restarted observer froze its agent view for good), a preview existed only for the shell, and an apply that turned out bad had nothing kept to go back to once it exited 0.Now:
agent-observerinsystem/supervisord.confruns the workspace's onemngr observe --quiet, in the chat's memory band (SERVICE_BANDS24, just below the chat: shedding it blinds every chat's agent view at once while freeing almost nothing). The chat follows its event file through mngr'sObserveEventFollower, reports the outage in/api/health'sagent_events(statusstaysok), andchat-app --secondaryboots a second chat beside the live one for previews -- same observer and live accounts, but it reconciles nothing, writes no memory scores, opens no windows, registers nothing, and refuses an account switch with a 400 (it would write the chat's record only into a scratch copy the live chat never reads).CHAT_DATA_DIRnames the chat's data directory so a secondary's writes stay out of it.app.tomlcan carry a[preview]table (PreviewSpec: command, named ports, copied directories, env and args with{port:}/{copy:}/{scratch}/{registry}/{host}placeholders, health and open paths; a default is scaffolded per name, and build-app writes it into every new app).update-app'spreview_app.py up --app <name> --worktree <dir> [--with <sibling>]boots any app from it as the labeled<name>-previewtab, siblings first, over copies of its data, withrefreshre-booting in place after a rebuild anddowntearing the whole set down. One preview per app: another worktree's live preview is refused rather than hijacked, and a sibling re-upped or taken down on its own is written into every live preview's registry copy so the framing preview follows without a re-up.serve_isolated_instance.pygains named ports, copies, placeholders, and--inner-path, and carries the old live-editing branch's work forward:refresh --namere-boots the inner server on its existing port,downescalates SIGTERM to SIGKILL and keeps the state (exiting non-zero) for a survivor, a failed boot quotes the log since the last boot marker and the health response and copies the log aside, and both processes launch throughoom_tag_service.py user. The health probe reads the route's own verdict, so a 200 whose JSON saysis_frontend_builtis false is not up. A preview now puts one app in the launcher, not two: the labeled frame registers as the app, the inner service registers--internal.system-interface --previewandterminal-app --no-registerare the read-only boots. The preview shell runs the real desktop over a seeded state copy and a copied registry; everything that edits that copy or reaches its own windows stays live, and only an app's stop and start and the update notice's verbs are refused with a 403.terminal-app/terminal-pty --no-registerserve without re-pointing the live rows, appending a discovery event, or running the window sweep.update-approutes by manifest:critical = true(or aworkspace_uichange) follows the newreferences/critical-app.md, the careful flow that replacesupdate-system-interface(deleted withreveal_system_interface.py): one lease, an isolated worktree, the live loop through the preview (each round closes the preview's window before editing and re-opens it when the round is ready), a harden worker on the lead's branch, and the apply with both bundles and--keep-rollback-point.update_self.py apply --keep-rollback-pointkeeps its snapshots and writesdata/.state/update-apply/last-good.json(what it landed, the copies, the critical apps and programs it touched);rollback-lastforward-reverts, restores, restarts exactly those programs (or names the services-agent restart when the diff reached the workspace's own setup) and waits for a settled verdict now shared with the apply;confirm-lastdiscards. A rollback holds a lock for its whole run, so a double press starts one script; a restore it cannot complete reports incomplete recovery, keeps the copies, and writes emergency state rather than letting later health checks read as success.update_notice_changed, the verbs on/api/updates/pending/{confirm,rollback}(refused in a preview shell). An unknown path under/api/now answers a JSON 404 rather than the app shell.forward_port.py --display-name <label>gives a manifest-less row the label users read instead of the raw service name; like--internaland--programevery call is authoritative, and a label the shell would reject on read is refused at registration.agentjoins the reserved app names, since an app by that name would claimagent-observeras its sidecar.launch-task'screate_worker.py launch/launch-synctake--branch <spec>, so a worker can extend a branch the lead already built up (what the careful flow's harden worker uses).launch-syncreads the worker's branch back frommngr ls --format json'sinitial_branch-- the mngr PR's widening -- rather than assumingmngr/<name>, anddestroy's unmerged-work warning counts commits on that recorded branch.Validation: the automated suites (update-self apply and rollback tests over a fixture tree, the chat's follower tests against a real
mngr observe, the shell's route and watch tests, the frontend vitest, and one release e2e test that raises the band from a seeded record over stub apps and confirms it away).docs/system/blueprint/critical-app-editing/test-plan.mdis the hand-run plan; its manual scenario in a real workspace has not been run, and that is recorded as owed in the spec.Deviations from the spec, recorded there: the observer's band is 24 (bands must be distinct and ordered),
preview_app.pyruns underuv run python3because it importsapp_manifest, a rollback's outcome lives in the notice's own record rather than the apply's run record,CHAT_DATA_DIRmoves only the message stamps (chat instances are in memory), and thewait_settledverdict is one function over every program rather than the shell alone. The shell's direct-subprocess ratchet admits one use: the rollback is launched detached in its own session, since supervisord stops the shell's program as a group and the rollback restarts it.🤖 Generated with Claude Code