Skip to repository content
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T00:56:24.211Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitRevision diff
7bb06ca2 → 8a07c5bfdiff --git a/OMEGA_DELTAS.md b/OMEGA_DELTAS.md
index f43bb9d85f..c97d7697e5 100644
--- a/OMEGA_DELTAS.md
+++ b/OMEGA_DELTAS.md
@@ -303,8 +303,15 @@ cargo test -p omega_deltas
303303 that test, but it leaves the model string unpinned: a rebase could change
304304 `gemini-3.6-flash` to any other Google model and every check would stay
305305 green.
306-- **Enforced by:** `the_agent_ships_enabled` and
307- `the_default_model_is_pinned`.
306+- **Enforced by:** `the_agent_ships_enabled`, `the_default_model_is_pinned`, and
307+ `the_new_thread_chord_is_window_global`, which asserts the chord is bound
308+ window-globally to `agent::NewThread` in all three default keymaps and that
309+ every narrower binding of it is one of the deliberately admitted surfaces —
310+ the toolchain and recent-projects pickers, and (on Linux and Windows, where
311+ the chord is `ctrl-shift-a`) a terminal's select-all. omega#76 asked for the
312+ shadowed lower-priority bindings to be resolved deliberately; this is that
313+ resolution written down, so a *new* shadow fails rather than quietly making
314+ the chord focus-dependent again.
308315
309316 ### OMEGA-DELTA-0014 — A protected recovery offers replacement, not protection
310317
@@ -1645,20 +1652,26 @@ than it sounds, because the harness omega#81's acceptance sentence names —
16451652 `test_empty_workspace_opens_the_front_door` in `crates/agent_ui`; the rendered
16461653 proofs `omega_front_door_no_project` and `omega_front_door_typing` in
16471654 `crates/zed/src/visual_test_runner.rs`.
1648-- **What this does not cover.** A **first-ever** launch still lands on the
1649- Onboarding tab, not on the agent — observed on a packaged build with a fresh
1655+- **What this does not cover.** A **first-ever** launch lands on the Onboarding
1656+ tab, not on the agent — observed on a packaged build with a fresh
16501657 `--user-data-dir`, where the front door is behind onboarding and the agent
1651- dock is closed until it is dismissed. Every launch after that lands on the
1652- agent. So omega#76's exit holds for its second half (typing starts a real
1653- thread) and for every launch but the first; the first-run ordering is a
1654- separate, untouched surface. A projectless thread is also not yet *bound* to a
1658+ dock is closed until it is finished. Every launch after that lands on the
1659+ agent. That ordering was left open here as a question for the owner; it is
1660+ now decided and recorded as `OMEGA-DELTA-0040`, which tests the handoff from
1661+ onboarding to this front door. A projectless thread is also not yet *bound* to a
16551662 project on its first workspace-touching action, which is the rest of omega#76's
16561663 project-optional deliverable. Today it simply has no worktree: file mentions
16571664 find nothing and a tool that needs a path fails the way it would in any
1658- worktree-less window. The `cmd-shift-a` editor-context collision and the
1659- shadowed lower-priority bindings named in omega#76 are also untouched; the
1660- two `cmd-shift-a` bindings that remain are inside modal pickers
1661- (`ToolchainSelector`, `RecentProjects`) and fire only while those are open.
1665+ worktree-less window. The `cmd-shift-a` bindings named in omega#76 are
1666+ resolved rather than changed, and the resolution is now checked by
1667+ `the_new_thread_chord_is_window_global` under `OMEGA-DELTA-0013`: on macOS
1668+ there is no editor-context collision to fix — upstream's
1669+ `editor::SelectToBeginningOfLine` is on `ctrl-shift-a`, a different chord —
1670+ and the only narrower `cmd-shift-a` bindings are inside modal pickers
1671+ (`ToolchainSelector`, `RecentProjects`) that fire while the user has opened
1672+ them on purpose. On Linux and Windows the chord is `ctrl-shift-a` and a
1673+ terminal's select-all shadows it there, which is admitted for the same
1674+ reason and enumerated rather than left to be discovered.
16621675
16631676 ### OMEGA-DELTA-0035 — The router is wired, and a pin is a gesture
16641677
@@ -1926,3 +1939,48 @@ than it sounds, because the harness omega#81's acceptance sentence names —
19261939 pass, a dropped pixel threshold, a removed pixel diff and fabricated OCR lines
19271940 — each failed the self-test. Every falsification was probed against a pristine
19281941 copy before its test ran.
1942+
1943+### OMEGA-DELTA-0040 — A first-ever launch lands on identity onboarding, and finishing it opens the front door
1944+
1945+- **Upstream Zed:** first-run onboarding is a page you may open, skip, or close;
1946+ nothing in the startup path waits for it, and the window opens regardless.
1947+- **Omega, before this:** the ordering existed and nothing named it. Identity
1948+ onboarding gates startup (`await_identity_ready`), `OMEGA-DELTA-0019` and
1949+ `OMEGA-DELTA-0034` open the front door on a window with nothing to restore,
1950+ and no test connected the two. A packaged first-ever launch therefore lands on
1951+ Onboarding rather than the agent, which reads as `OMEGA-DELTA-0034` failing
1952+ its own exit unless somebody decides on purpose that it does not.
1953+- **The owner's decision, recorded rather than assumed.** Omega is
1954+ identity-first by design: omega#9's packet exists to put identity before
1955+ editor setup, and an agent thread before an identity would invert it. "Fresh
1956+ launch lands on the surface" is satisfied by the front door being the surface
1957+ a *usable* session opens on. A genuinely first-ever launch legitimately lands
1958+ on onboarding first, and every launch after it lands on the agent. The owner
1959+ may veto this; it is written down so a veto has something to point at.
1960+- **The decision is only sound while the handoff is real,** so the handoff is
1961+ the thing that is tested. "Onboarding first" and "onboarding instead" are the
1962+ same picture on a first launch and completely different products on the
1963+ second. Three links, each of which fails silently on its own:
1964+ `restore_or_create_workspace` **waits** on `await_identity_ready` before it
1965+ opens anything; the first-run branch of `on_finish` **closes its own window
1966+ and releases** that wait; and `release_identity_waiters` **completes the
1967+ channel** the startup path is parked on. Remove the middle link and setup
1968+ completes into a launchpad that never becomes an agent — a failure no check on
1969+ the front door alone can see.
1970+- **Enforced by:** `first_run_onboarding_hands_the_startup_off_to_the_front_door`
1971+ in `crates/omega_deltas`, alongside `a_fresh_window_opens_on_the_agent`
1972+ (`OMEGA-DELTA-0019`) and `the_front_door_does_not_require_an_open_project`
1973+ (`OMEGA-DELTA-0034`).
1974+- **What this does not cover.** **Abandoning** onboarding is a dead end in that
1975+ launch. The first-run onboarding page is an ordinary closeable item; closing
1976+ it calls `Item::on_removed` → `onboarding_closed`, which clears the
1977+ "onboarding is open" flag and **does not** complete the startup channel. So a
1978+ user who closes the tab instead of finishing setup keeps the launchpad window
1979+ the onboarding page was hosted in, with the agent dock closed and no window
1980+ ever created behind it. Identity is still not ready, so relaunching shows
1981+ onboarding again and nothing is lost but the session — and refusing to
1982+ continue without an identity is the identity-first posture working, not
1983+ failing. Making the page unclosable, or making a close mean "quit", is an
1984+ onboarding-surface decision (omega#9) and not this delta's to take. This delta
1985+ also does not cover *what* onboarding asks for, or the editor-setup journey,
1986+ which reaches the welcome page rather than the front door and is unchanged.
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index cb83bee6ae..f3f0af2662 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -6772,6 +6772,45 @@ impl AgentPanel {
67726772 self.set_base_view(thread.into(), true, window, cx);
67736773 }
67746774
6775+ /// Opens an external thread on an arbitrary `AgentServer` under a
6776+ /// `ThreadId` a previous process wrote down.
6777+ ///
6778+ /// The restore path a real relaunch takes: `restore_new_draft` reads the
6779+ /// persisted `ThreadId` and `agent_id` out of the metadata store and hands
6780+ /// the id to `create_agent_thread_with_server`, which is the one argument
6781+ /// that makes the reopened thread *the same thread* rather than a new one
6782+ /// with the same content. A harness proving disclosure after a restart
6783+ /// needs exactly that: the lane correlation on disk is keyed by `ThreadId`,
6784+ /// so a thread reopened under a fresh id would silently stop being the
6785+ /// thread the journal names, and the capture would show a lane run missing
6786+ /// for the wrong reason.
6787+ #[cfg(any(test, feature = "test-support"))]
6788+ pub fn open_external_thread_with_server_under_id(
6789+ &mut self,
6790+ server: Rc<dyn AgentServer>,
6791+ thread_id: ThreadId,
6792+ window: &mut Window,
6793+ cx: &mut Context<Self>,
6794+ ) {
6795+ let ext_agent = Agent::Custom {
6796+ id: server.agent_id(),
6797+ };
6798+
6799+ let thread = self.create_agent_thread_with_server(
6800+ ext_agent,
6801+ Some(server),
6802+ Some(thread_id),
6803+ None,
6804+ None,
6805+ None,
6806+ None,
6807+ AgentThreadSource::AgentPanel,
6808+ window,
6809+ cx,
6810+ );
6811+ self.set_base_view(thread.into(), true, window, cx);
6812+ }
6813+
67756814 /// Opens a restored external thread with an arbitrary AgentServer and
67766815 /// a specific `resume_session_id` — as if we just restored from the KVP.
67776816 ///
diff --git a/crates/agent_ui/src/omega_host_bridge.rs b/crates/agent_ui/src/omega_host_bridge.rs
index 64e67a6275..17ac363d36 100644
--- a/crates/agent_ui/src/omega_host_bridge.rs
+++ b/crates/agent_ui/src/omega_host_bridge.rs
@@ -97,6 +97,80 @@ pub fn publish_engine_lane_run_for_tests(thread_id: ThreadId, operation_ref: Str
9797 }
9898 }
9999
100+/// Persist one lane correlation through the production writer, for a harness
101+/// that has no engine to run.
102+///
103+/// [`publish_engine_lane_run_for_tests`] writes the process-local index, which
104+/// a restart empties. This writes the durable half — the same
105+/// `CorrelationJournal`, in the same schema, at the same path
106+/// [`omega_effectd_host_handler`] reads at startup — so a *second process* can
107+/// be shown rebuilding the disclosure from disk rather than from a static that
108+/// happened to survive. Behind `test-support`, so no shipped build can reach it.
109+#[cfg(any(test, feature = "test-support"))]
110+pub fn persist_engine_lane_run_for_tests(
111+ thread_id: ThreadId,
112+ operation_ref: String,
113+) -> anyhow::Result<()> {
114+ let state = HostBridgeState {
115+ workspace: None,
116+ threads: vec![HostThread {
117+ workspace_ref: SUPERVISED_WORKSPACE_REF.to_string(),
118+ lane: CODEX_LOCAL_LANE.to_string(),
119+ operation_ref,
120+ thread_id,
121+ conversation: None,
122+ turns: Vec::new(),
123+ revision: 1,
124+ }],
125+ correlation_path: correlation_journal_path(),
126+ load_error: None,
127+ sarah_conversation: None,
128+ };
129+ persist_correlation_journal(&state)
130+}
131+
132+/// Where the correlation journal lives.
133+fn correlation_journal_path() -> PathBuf {
134+ paths::data_dir().join("openagents").join(CORRELATION_FILE)
135+}
136+
137+/// Read the correlation journal and refill the lane index from it.
138+///
139+/// OMEGA-DELTA-0021's restart edge, in one place. A freshly started process has
140+/// an empty lane index, and this is what refills it, so a thread resumed after
141+/// a restart still discloses the lane that owns it.
142+///
143+/// [`omega_effectd_host_handler`] calls this at startup and keeps the threads
144+/// for its own state; [`reload_engine_lane_runs_from_disk`] calls it for a
145+/// caller that only needs the index. Neither is a copy of the other, so what a
146+/// cold process is observed doing here is what the shipped startup does.
147+fn load_journal_and_republish(path: &Path) -> (Vec<HostThread>, Option<String>) {
148+ let (threads, load_error) = match load_correlation_journal(path) {
149+ Ok(threads) => (threads, None),
150+ Err(error) => (Vec::new(), Some(error.to_string())),
151+ };
152+ republish_engine_lane_runs(&threads);
153+ (threads, load_error)
154+}
155+
156+/// Refill the lane index from the correlation journal on disk.
157+///
158+/// Returns the number of lane-bound threads the journal named. The error, if
159+/// the journal cannot be read, is returned rather than logged: a caller that
160+/// asked for the restart edge explicitly needs to know it did not happen, and
161+/// an empty index is indistinguishable from a journal with nothing in it.
162+pub fn reload_engine_lane_runs_from_disk() -> anyhow::Result<usize> {
163+ let path = correlation_journal_path();
164+ let (threads, load_error) = load_journal_and_republish(&path);
165+ if let Some(error) = load_error {
166+ anyhow::bail!(
167+ "correlation journal at {} is unreadable: {error}",
168+ path.display()
169+ );
170+ }
171+ Ok(threads.len())
172+}
173+
100174 /// The `omega-effectd` lane run this thread belongs to, if it is one.
101175 ///
102176 /// Returns `None` for every thread the user started themselves, which is what
@@ -247,15 +321,11 @@ struct AppendSystemNoteParams {
247321 pub fn omega_effectd_host_handler(cx: &App) -> OmegaEffectdHostHandler {
248322 let async_cx = cx.to_async();
249323 let openagents_session = omega_effectd::openagents_session(cx);
250- let correlation_path = paths::data_dir().join("openagents").join(CORRELATION_FILE);
251- let (threads, load_error) = match load_correlation_journal(&correlation_path) {
252- Ok(threads) => (threads, None),
253- Err(error) => (Vec::new(), Some(error.to_string())),
254- };
324+ let correlation_path = correlation_journal_path();
255325 // OMEGA-DELTA-0021. This is the restart edge: the lane index is empty in a
256326 // freshly started process, and the journal on disk is what refills it, so a
257327 // thread resumed after a restart still discloses the lane that owns it.
258- republish_engine_lane_runs(&threads);
328+ let (threads, load_error) = load_journal_and_republish(&correlation_path);
259329 let state = Rc::new(RefCell::new(HostBridgeState {
260330 workspace: None,
261331 threads,
diff --git a/crates/omega_deltas/src/omega_deltas.rs b/crates/omega_deltas/src/omega_deltas.rs
index ede030ca4c..183292876e 100644
--- a/crates/omega_deltas/src/omega_deltas.rs
+++ b/crates/omega_deltas/src/omega_deltas.rs
@@ -67,6 +67,7 @@ pub const ENFORCED_DELTAS: &[&str] = &[
6767 "OMEGA-DELTA-0037",
6868 "OMEGA-DELTA-0038",
6969 "OMEGA-DELTA-0039",
70+ "OMEGA-DELTA-0040",
7071 ];
7172
7273 /// OMEGA-DELTA-0036. The uninstall script embedded in the shipped `cli`.
@@ -297,6 +298,42 @@ pub const AGENT_SERVER_FACTORY_PATH: &str = "crates/agent_ui/src/agent_ui.rs";
297298 /// OMEGA-DELTA-0035. The pin-setting methods that must name a human gesture.
298299 pub const PIN_SETTING_CALLS: &[&str] = &["pin_session(", "unpin_session(", "pin_next_session("];
299300
301+/// OMEGA-DELTA-0013. The chord that opens a new agent thread, per platform.
302+///
303+/// omega#76's exit is that this chord reaches the New Agent Thread surface from
304+/// the editor, the welcome surface and the panel. All three live inside
305+/// `Workspace`, so the property is "bound window-globally and not shadowed by
306+/// anything narrower", which is what `the_new_thread_chord_is_window_global`
307+/// checks.
308+pub const NEW_THREAD_CHORDS: &[(&str, &str)] = &[
309+ ("assets/keymaps/default-macos.json", "cmd-shift-a"),
310+ ("assets/keymaps/default-linux.json", "ctrl-shift-a"),
311+ ("assets/keymaps/default-windows.json", "ctrl-shift-a"),
312+];
313+
314+/// OMEGA-DELTA-0013. The narrower surfaces admitted to take the chord back.
315+///
316+/// An allowlist and not a count. omega#76 asked for the shadowed lower-priority
317+/// bindings to be *resolved deliberately*, and the deliberate resolution is
318+/// that a modal the user opened on purpose, and a terminal whose select-all is
319+/// a decades-old convention, may keep the chord while they have focus — but
320+/// nothing else may take it, and an `Editor` or `AgentPanel` binding appearing
321+/// here would put the chord back to being focus-dependent everywhere.
322+pub const NEW_THREAD_CHORD_NARROW_CONTEXTS: &[&str] = &[
323+ "ToolchainSelector",
324+ "RecentProjects || (RecentProjects > Picker > Editor)",
325+ "Terminal",
326+];
327+
328+/// OMEGA-DELTA-0040. The startup path that opens Omega's first window.
329+pub const STARTUP_PATH: &str = "crates/zed/src/main.rs";
330+
331+/// OMEGA-DELTA-0040. First-run onboarding, which that startup path waits on.
332+pub const ONBOARDING_PATH: &str = "crates/onboarding/src/onboarding.rs";
333+
334+/// OMEGA-DELTA-0040. The coordinator that releases the startup path.
335+pub const IDENTITY_STARTUP_PATH: &str = "crates/onboarding/src/identity_startup.rs";
336+
300337 /// OMEGA-DELTA-0029. Vocabulary that would make a route irreproducible.
301338 ///
302339 /// The packet's exit is a *deterministic* router, and determinism is not a
@@ -4801,6 +4838,196 @@ mod tests {
48014838 }
48024839 }
48034840
4841+ /// OMEGA-DELTA-0013. The new-thread chord fires from anywhere in the window.
4842+ ///
4843+ /// Upstream binds `agent::NewThread` to `cmd-n` inside panel-scoped
4844+ /// contexts only, so it cannot start a thread unless the panel already has
4845+ /// focus. Omega's chord is window-global, which is the whole of omega#76's
4846+ /// "from every context" — editor, welcome and panel are all inside
4847+ /// `Workspace`.
4848+ ///
4849+ /// The check is two-sided, because each side alone is weak. A global
4850+ /// binding that exists proves nothing if something narrower shadows it, and
4851+ /// counting bindings would either forbid the modal pickers that legitimately
4852+ /// hold the chord or permit any new binding at all.
4853+ #[test]
4854+ fn the_new_thread_chord_is_window_global() {
4855+ for (keymap, chord) in NEW_THREAD_CHORDS {
4856+ let path = repository_path(keymap);
4857+ let raw = std::fs::read_to_string(&path)
4858+ .unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display()));
4859+ let sections: serde_json::Value = serde_json::from_str(&strip_jsonc(&raw))
4860+ .unwrap_or_else(|error| panic!("cannot parse {}: {error}", path.display()));
4861+ let sections = sections
4862+ .as_array()
4863+ .unwrap_or_else(|| panic!("{keymap} is not an array of sections"));
4864+
4865+ let bound: Vec<(Option<&str>, Option<&str>)> = sections
4866+ .iter()
4867+ .filter_map(|section| {
4868+ let binding = section.get("bindings")?.get(*chord)?;
4869+ Some((
4870+ section.get("context").and_then(serde_json::Value::as_str),
4871+ binding.as_str(),
4872+ ))
4873+ })
4874+ .collect();
4875+
4876+ let global: Vec<&(Option<&str>, Option<&str>)> = bound
4877+ .iter()
4878+ .filter(|(context, _)| {
4879+ context.is_none_or(|context| WINDOW_GLOBAL_KEYMAP_CONTEXTS.contains(&context))
4880+ })
4881+ .collect();
4882+ assert_eq!(
4883+ global.len(),
4884+ 1,
4885+ "OMEGA-DELTA-0013: {keymap} must bind {chord:?} window-globally \
4886+ exactly once. Found: {global:?}"
4887+ );
4888+ assert_eq!(
4889+ global[0].1,
4890+ Some("agent::NewThread"),
4891+ "OMEGA-DELTA-0013: {keymap} binds {chord:?} window-globally to \
4892+ something other than the new agent thread, so omega#76's \
4893+ chord no longer reaches the front door."
4894+ );
4895+
4896+ for (context, action) in &bound {
4897+ let Some(context) = context else { continue };
4898+ if WINDOW_GLOBAL_KEYMAP_CONTEXTS.contains(context) {
4899+ continue;
4900+ }
4901+ assert!(
4902+ NEW_THREAD_CHORD_NARROW_CONTEXTS.contains(context),
4903+ "OMEGA-DELTA-0013: {keymap} binds {chord:?} to {action:?} in \
4904+ context {context:?}, which shadows the window-global new-thread \
4905+ chord while that surface has focus. omega#76 asked for the \
4906+ shadowed bindings to be resolved deliberately; a new one \
4907+ appearing is not a deliberate resolution. Admitted: \
4908+ {NEW_THREAD_CHORD_NARROW_CONTEXTS:?}"
4909+ );
4910+ }
4911+ }
4912+ }
4913+
4914+ /// OMEGA-DELTA-0040. A first-ever launch lands on identity onboarding, and
4915+ /// finishing it opens the front door.
4916+ ///
4917+ /// The owner decided the ordering: Omega is identity-first, so an agent
4918+ /// thread before an identity would invert the thing omega#9's packet exists
4919+ /// to establish. That decision is only sound while the handoff is real —
4920+ /// "onboarding first" and "onboarding instead" are the same picture on a
4921+ /// first launch and completely different products on the second.
4922+ ///
4923+ /// So the chain is checked link by link, because each link fails silently
4924+ /// on its own:
4925+ ///
4926+ /// - the startup path *waits* — without the await, the front door would
4927+ /// open behind onboarding and Omega would be asking for an identity over
4928+ /// the top of a composer;
4929+ /// - finishing *releases* the wait — without the release, completing setup
4930+ /// would leave the user on the launchpad with nothing else ever opening,
4931+ /// and no test that only looks at the front door would notice;
4932+ /// - releasing *completes the channel* the startup path is parked on —
4933+ /// without that, `release_identity_waiters` is a call that returns.
4934+ #[test]
4935+ fn first_run_onboarding_hands_the_startup_off_to_the_front_door() {
4936+ let startup_path = repository_path(STARTUP_PATH);
4937+ let startup = std::fs::read_to_string(&startup_path)
4938+ .unwrap_or_else(|error| panic!("cannot read {}: {error}", startup_path.display()));
4939+ let restore = function_body(&startup, "restore_or_create_workspace").unwrap_or_else(|| {
4940+ panic!(
4941+ "OMEGA-DELTA-0040: {} no longer has a `restore_or_create_workspace`. \
4942+ A check that cannot find its subject passes for the wrong reason.",
4943+ startup_path.display()
4944+ )
4945+ });
4946+
4947+ let waits_at = restore.find("await_identity_ready(").unwrap_or_else(|| {
4948+ panic!(
4949+ "OMEGA-DELTA-0040: `restore_or_create_workspace` in {} no longer \
4950+ waits for identity. The front door would open behind first-run \
4951+ onboarding, which is Omega asking for an identity on top of a \
4952+ composer — the inversion the owner's ordering decision rejects.",
4953+ startup_path.display()
4954+ )
4955+ });
4956+ let opens_at = restore
4957+ .find("AgentPanel::open_front_door(")
4958+ .unwrap_or_else(|| {
4959+ panic!(
4960+ "OMEGA-DELTA-0040: `restore_or_create_workspace` in {} no \
4961+ longer opens the front door at all (OMEGA-DELTA-0019).",
4962+ startup_path.display()
4963+ )
4964+ });
4965+ assert!(
4966+ waits_at < opens_at,
4967+ "OMEGA-DELTA-0040: {} opens the front door before it waits for \
4968+ identity. Onboarding is first *and* the agent is what follows it; \
4969+ reversing them makes the first-run window a race.",
4970+ startup_path.display()
4971+ );
4972+
4973+ let onboarding_path = repository_path(ONBOARDING_PATH);
4974+ let onboarding = std::fs::read_to_string(&onboarding_path)
4975+ .unwrap_or_else(|error| panic!("cannot read {}: {error}", onboarding_path.display()));
4976+ let finish = function_body(&onboarding, "on_finish").unwrap_or_else(|| {
4977+ panic!(
4978+ "OMEGA-DELTA-0040: {} no longer has an `on_finish` to check.",
4979+ onboarding_path.display()
4980+ )
4981+ });
4982+ // The **first-run** arm specifically. `on_finish` releases the waiters
4983+ // on both journeys, and the editor-setup journey is not the one the
4984+ // startup path is parked on — asserting against the whole function
4985+ // would let the first-run release be deleted while the check stayed
4986+ // green on the other arm's copy of the same call.
4987+ let first_run_arm = finish
4988+ .split_once("OnboardingMode::FirstRun(window_handle) => {")
4989+ .and_then(|(_, rest)| rest.split_once("OnboardingMode::EditorSetup"))
4990+ .map(|(arm, _)| arm)
4991+ .unwrap_or_else(|| {
4992+ panic!(
4993+ "OMEGA-DELTA-0040: `on_finish` in {} no longer has a \
4994+ first-run arm to check.",
4995+ onboarding_path.display()
4996+ )
4997+ });
4998+ assert!(
4999+ first_run_arm.contains("release_identity_waiters(cx)"),
5000+ "OMEGA-DELTA-0040: finishing first-run onboarding in {} no longer \
5001+ releases the startup path. Setup would complete and nothing would \
5002+ open: the user is left on the launchpad, the agent dock closed, \
5003+ and the only way forward is relaunching the app.",
5004+ onboarding_path.display()
5005+ );
5006+ assert!(
5007+ first_run_arm.contains("window.remove_window()"),
5008+ "OMEGA-DELTA-0040: the first-run branch of `on_finish` in {} no \
5009+ longer closes its own window, so the front door would open beside \
5010+ a finished onboarding screen rather than instead of it.",
5011+ onboarding_path.display()
5012+ );
5013+
5014+ let coordinator_path = repository_path(IDENTITY_STARTUP_PATH);
5015+ let coordinator = std::fs::read_to_string(&coordinator_path)
5016+ .unwrap_or_else(|error| panic!("cannot read {}: {error}", coordinator_path.display()));
5017+ let release = function_body(&coordinator, "release_identity_waiters").unwrap_or_else(|| {
5018+ panic!(
5019+ "OMEGA-DELTA-0040: {} no longer has a `release_identity_waiters`.",
5020+ coordinator_path.display()
5021+ )
5022+ });
5023+ assert!(
5024+ release.contains("finish(Ok(()), cx)"),
5025+ "OMEGA-DELTA-0040: `release_identity_waiters` in {} no longer \
5026+ completes the startup channel, so it releases nobody.",
5027+ coordinator_path.display()
5028+ );
5029+ }
5030+
48045031 /// OMEGA-DELTA-0035. The router is what the native agent entry resolves to.
48055032 ///
48065033 /// omega#78 shipped the router unwired, which is the failure this guards:
diff --git a/crates/zed/src/visual_test_runner.rs b/crates/zed/src/visual_test_runner.rs
index 180bb44395..9e87966647 100644
--- a/crates/zed/src/visual_test_runner.rs
+++ b/crates/zed/src/visual_test_runner.rs
@@ -55,13 +55,22 @@ fn main() {
5555 // test that exercises real on-disk state — omega#81's harness pins and
5656 // receipts do — would otherwise write into the developer's own Omega
5757 // installation. Set before anything can read `data_dir`.
58- let data_dir = tempfile::tempdir().expect("Failed to create data directory");
59- paths::set_custom_data_dir(
60- data_dir
61- .keep()
62- .to_str()
63- .expect("Data directory path is not UTF-8"),
64- );
58+ //
59+ // `OMEGA_VISUAL_DATA_DIR` overrides the temporary directory so that two
60+ // *processes* can share one data directory. That is what makes omega#77's
61+ // restart captures a restart: the second process is a genuinely cold one —
62+ // empty statics, nothing carried in memory — and the only thing it has of
63+ // the first is what the first left on disk. It is still never the
64+ // developer's own data directory; `script/omega-visual-proof` creates a
65+ // temporary one and passes it in.
66+ let data_dir = match std::env::var("OMEGA_VISUAL_DATA_DIR") {
67+ Ok(path) if !path.is_empty() => std::path::PathBuf::from(path),
68+ _ => tempfile::tempdir()
69+ .expect("Failed to create data directory")
70+ .keep(),
71+ };
72+ std::fs::create_dir_all(&data_dir).expect("Failed to create data directory");
73+ paths::set_custom_data_dir(data_dir.to_str().expect("Data directory path is not UTF-8"));
6574
6675 env_logger::builder()
6776 .filter_level(log::LevelFilter::Info)
@@ -93,7 +102,13 @@ fn main() {
93102 match test_result {
94103 Ok(Ok(())) => {}
95104 Ok(Err(e)) => {
96- eprintln!("Visual tests failed: {}", e);
105+ // `{:#}` and not `{}`: every failure here is wrapped in the name of
106+ // the suite that produced it, and `{}` prints only that wrapper.
107+ // A run that failed because a restored record named the wrong run
108+ // would report "omega_agent_surfaces" and nothing else, which is
109+ // the same class of unreadable failure this file already carries a
110+ // comment about.
111+ eprintln!("Visual tests failed: {:#}", e);
97112 std::process::exit(1);
98113 }
99114 Err(_) => {
@@ -416,8 +431,18 @@ fn run_visual_tests(project_path: PathBuf, update_baseline: bool) -> Result<()>
416431 // status they already had: present, and not standing up.
417432 #[cfg(feature = "visual-tests")]
418433 if std::env::var("OMEGA_VISUAL_ONLY").is_ok() {
419- println!("\n--- Omega: front door, executor disclosure, route pin ---");
420- let outcome = run_omega_agent_visual_tests(app_state.clone(), &mut cx, update_baseline);
434+ // `OMEGA_VISUAL_PHASE=restart` is the second process of omega#77's
435+ // restart proof. It captures nothing the first process captured: it
436+ // reopens two threads the first process left on disk and photographs
437+ // the executor lines a cold process derives for them.
438+ let restart_phase = std::env::var("OMEGA_VISUAL_PHASE").as_deref() == Ok("restart");
439+ let outcome = if restart_phase {
440+ println!("\n--- Omega: executor disclosure after a restart ---");
441+ run_omega_restart_visual_tests(app_state.clone(), &mut cx, update_baseline)
442+ } else {
443+ println!("\n--- Omega: front door, executor disclosure, route pin ---");
444+ run_omega_agent_visual_tests(app_state.clone(), &mut cx, update_baseline)
445+ };
421446 // The shared window this function opened above is torn down here as
422447 // well as at the end of the full run. Returning early without it left
423448 // the sample project's buffers alive, GPUI's leaked-handle check
@@ -2475,6 +2500,57 @@ fn run_omega_agent_visual_tests_inner(
24752500 update_baseline,
24762501 )?;
24772502
2503+ // omega#77's restart proof, first half: leave behind what a relaunch would
2504+ // find, and then end. Everything below the captures writes; nothing below
2505+ // them photographs, so no committed baseline can move because of it.
2506+ //
2507+ // A *second* external thread, because the two restart cases have to be
2508+ // distinguishable. The correlation journal names the first thread and not
2509+ // this one, so a cold process that confused them would disclose a lane run
2510+ // on a thread that never had one — a failure a single-thread shape could
2511+ // not see.
2512+ let plain_stub: Rc<dyn AgentServer> = Rc::new(StubAgentServer::new(StubAgentConnection::new()));
2513+ cx.update_window(workspace_window.into(), |_, window, cx| {
2514+ panel.update(cx, |panel, cx| {
2515+ panel.open_external_thread_with_server(plain_stub, window, cx);
2516+ });
2517+ })?;
2518+ cx.run_until_parked();
2519+
2520+ let plain_thread_id = cx
2521+ .read(|cx| panel.read(cx).active_thread_id(cx))
2522+ .ok_or_else(|| anyhow::anyhow!("the second external thread has no id"))?;
2523+ anyhow::ensure!(
2524+ plain_thread_id != external_thread_id,
2525+ "the restart phase needs two distinct threads; both ids are {plain_thread_id:?}"
2526+ );
2527+ let plain_record = cx
2528+ .read(|cx| omega_executor_record(&panel, cx))
2529+ .ok_or_else(|| anyhow::anyhow!("the second external thread has no disclosure"))?;
2530+ anyhow::ensure!(
2531+ plain_record.class == omega_front_door::ExecutorClass::ExternalAcp
2532+ && plain_record.run_ref.is_none(),
2533+ "the second external thread must carry no lane run before the restart: {plain_record:?}"
2534+ );
2535+
2536+ // The durable half. `publish_engine_lane_run_for_tests` above wrote a
2537+ // process-local index that a restart empties; this writes the correlation
2538+ // journal itself, in the production schema, at the path the shipped startup
2539+ // reads. The next process gets this and nothing else.
2540+ agent_ui::omega_host_bridge::persist_engine_lane_run_for_tests(
2541+ external_thread_id,
2542+ "operation.full-auto.visual".to_string(),
2543+ )?;
2544+
2545+ write_restart_handoff(&RestartHandoff {
2546+ lane_thread: external_thread_id,
2547+ lane_line,
2548+ external_thread: plain_thread_id,
2549+ external_line: plain_record.label(),
2550+ agent_id: plain_record.agent_id,
2551+ operation_ref: "operation.full-auto.visual".to_string(),
2552+ })?;
2553+
24782554 cx.update_window(workspace_window.into(), |_, window, _cx| {
24792555 window.remove_window();
24802556 })
@@ -2508,6 +2584,20 @@ fn run_omega_agent_visual_tests_inner(
25082584 /// the assertion and the pixels cannot disagree about what the line says.
25092585 #[cfg(all(target_os = "macos", feature = "visual-tests"))]
25102586 fn omega_executor_line(panel: &Entity<agent_ui::AgentPanel>, cx: &App) -> Option<String> {
2587+ Some(omega_executor_record(panel, cx)?.label())
2588+}
2589+
2590+/// The executor *record* the agent panel's active thread would render from.
2591+///
2592+/// The record rather than the line, because omega#77's condition is that
2593+/// disclosure is a typed record a label renders. A restart proof that compared
2594+/// only rendered strings could not tell a restored record from a restored
2595+/// string, which is the distinction the condition exists to protect.
2596+#[cfg(all(target_os = "macos", feature = "visual-tests"))]
2597+fn omega_executor_record(
2598+ panel: &Entity<agent_ui::AgentPanel>,
2599+ cx: &App,
2600+) -> Option<omega_front_door::ExecutorDisclosure> {
25112601 let disclosure = panel
25122602 .read(cx)
25132603 .active_thread_view_for_tests()?
@@ -2524,7 +2614,317 @@ fn omega_executor_line(panel: &Entity<agent_ui::AgentPanel>, cx: &App) -> Option
25242614 disclosure.is_coherent(),
25252615 "the rendered executor record is incoherent: {disclosure:?}"
25262616 );
2527- Some(disclosure.label())
2617+ Some(disclosure)
2618+}
2619+
2620+/// What the recording process leaves for the restarted one. omega#77.
2621+///
2622+/// The two thread ids and the agent id are the identifiers a real relaunch
2623+/// reads back out of `sidebar_threads`; the runner sets `ZED_STATELESS=1`, which
2624+/// deliberately keeps that table in memory, so the harness carries them in this
2625+/// file instead. The lane run is **not** here: it goes through the production
2626+/// correlation journal, at the production path, and the restarted process reads
2627+/// it with the production loader. The lines are here so the restarted process
2628+/// can assert it rendered *the same disclosure*, not merely a plausible one.
2629+#[cfg(all(target_os = "macos", feature = "visual-tests"))]
2630+#[derive(serde::Serialize, serde::Deserialize)]
2631+#[serde(rename_all = "camelCase")]
2632+struct RestartHandoff {
2633+ lane_thread: agent_ui::ThreadId,
2634+ lane_line: String,
2635+ external_thread: agent_ui::ThreadId,
2636+ external_line: String,
2637+ agent_id: String,
2638+ operation_ref: String,
2639+}
2640+
2641+#[cfg(all(target_os = "macos", feature = "visual-tests"))]
2642+fn restart_handoff_path() -> std::path::PathBuf {
2643+ paths::data_dir().join("omega-visual-restart-handoff.json")
2644+}
2645+
2646+#[cfg(all(target_os = "macos", feature = "visual-tests"))]
2647+fn write_restart_handoff(handoff: &RestartHandoff) -> Result<()> {
2648+ let path = restart_handoff_path();
2649+ if let Some(parent) = path.parent() {
2650+ std::fs::create_dir_all(parent)?;
2651+ }
2652+ std::fs::write(&path, serde_json::to_vec_pretty(handoff)?)
2653+ .with_context(|| format!("writing the restart handoff to {}", path.display()))?;
2654+ Ok(())
2655+}
2656+
2657+#[cfg(all(target_os = "macos", feature = "visual-tests"))]
2658+fn read_restart_handoff() -> Result<RestartHandoff> {
2659+ let path = restart_handoff_path();
2660+ let bytes = std::fs::read(&path).with_context(|| {
2661+ format!(
2662+ "no restart handoff at {} — the restart phase must run in the same \
2663+ data directory as the phase that recorded it, and after it",
2664+ path.display()
2665+ )
2666+ })?;
2667+ Ok(serde_json::from_slice(&bytes)?)
2668+}
2669+
2670+/// omega#77: the executor line on a `codex-acp`-class thread and on an
2671+/// engine-lane thread, **after a restart**.
2672+///
2673+/// This runs in a second process. Every process-local thing the first process
2674+/// built is gone — the lane index, the router's recorded routes, the panel, the
2675+/// threads themselves — so anything this renders came from disk or was never
2676+/// durable in the first place. That is the whole point: the previous lane
2677+/// demonstrated a real relaunch for the native kind and could not reach these
2678+/// two, and "the mechanism is shared, so I expect them to hold" is not the
2679+/// standard the issue set.
2680+#[cfg(all(target_os = "macos", feature = "visual-tests"))]
2681+fn run_omega_restart_visual_tests(
2682+ app_state: Arc<AppState>,
2683+ cx: &mut VisualTestAppContext,
2684+ update_baseline: bool,
2685+) -> Result<TestResult> {
2686+ let outcome = run_omega_restart_visual_tests_inner(app_state, cx, update_baseline);
2687+ cx.run_until_parked();
2688+ outcome
2689+}
2690+
2691+#[cfg(all(target_os = "macos", feature = "visual-tests"))]
2692+fn run_omega_restart_visual_tests_inner(
2693+ app_state: Arc<AppState>,
2694+ cx: &mut VisualTestAppContext,
2695+ update_baseline: bool,
2696+) -> Result<TestResult> {
2697+ use agent_ui::AgentPanel;
2698+
2699+ let handoff = read_restart_handoff()?;
2700+
2701+ // A cold process knows nothing until it reads the journal. Asserted before
2702+ // the read, because the read is what is under test: if a static had somehow
2703+ // survived, or the harness had published the run itself, every assertion
2704+ // below would pass for the wrong reason and the pictures would be of a
2705+ // process that never restarted anything.
2706+ anyhow::ensure!(
2707+ agent_ui::omega_host_bridge::engine_lane_run(handoff.lane_thread).is_none(),
2708+ "this process already knows a lane run for {:?} before reading the \
2709+ journal, so it is not a cold start",
2710+ handoff.lane_thread
2711+ );
2712+
2713+ // The production restart edge — the same function `omega_effectd_host_handler`
2714+ // calls at startup, not a copy of it.
2715+ let restored = agent_ui::omega_host_bridge::reload_engine_lane_runs_from_disk()?;
2716+ anyhow::ensure!(
2717+ restored == 1,
2718+ "the correlation journal named {restored} lane-bound thread(s); the \
2719+ recording phase wrote exactly one"
2720+ );
2721+ let restored_run = agent_ui::omega_host_bridge::engine_lane_run(handoff.lane_thread)
2722+ .ok_or_else(|| {
2723+ anyhow::anyhow!(
2724+ "the reloaded journal does not name {:?}, so nothing survived \
2725+ the restart",
2726+ handoff.lane_thread
2727+ )
2728+ })?;
2729+ anyhow::ensure!(
2730+ restored_run == handoff.operation_ref,
2731+ "the reloaded run is {restored_run:?}, not {:?}",
2732+ handoff.operation_ref
2733+ );
2734+ anyhow::ensure!(
2735+ agent_ui::omega_host_bridge::engine_lane_run(handoff.external_thread).is_none(),
2736+ "the journal named the plain external thread as a lane run; a restart \
2737+ must not invent an engine lane for a thread the user started"
2738+ );
2739+
2740+ let project = cx.update(|cx| {
2741+ project::Project::local(
2742+ app_state.client.clone(),
2743+ app_state.node_runtime.clone(),
2744+ app_state.user_store.clone(),
2745+ app_state.languages.clone(),
2746+ app_state.fs.clone(),
2747+ None,
2748+ project::LocalProjectFlags {
2749+ init_worktree_trust: false,
2750+ ..Default::default()
2751+ },
2752+ cx,
2753+ )
2754+ });
2755+
2756+ let window_size = size(px(900.0), px(720.0));
2757+ let bounds = Bounds {
2758+ origin: point(px(0.0), px(0.0)),
2759+ size: window_size,
2760+ };
2761+ let workspace_window: WindowHandle<Workspace> = cx
2762+ .update(|cx| {
2763+ cx.open_window(
2764+ WindowOptions {
2765+ window_bounds: Some(WindowBounds::Windowed(bounds)),
2766+ focus: false,
2767+ show: false,
2768+ ..Default::default()
2769+ },
2770+ |window, cx| {
2771+ cx.new(|cx| Workspace::new(None, project.clone(), app_state.clone(), window, cx))
2772+ },
2773+ )
2774+ })
2775+ .context("Failed to open the restarted window")?;
2776+
2777+ cx.run_until_parked();
2778+
2779+ let (weak_workspace, async_window_cx) = workspace_window
2780+ .update(cx, |workspace, window, cx| {
2781+ (workspace.weak_handle(), window.to_async(cx))
2782+ })
2783+ .context("Failed to get workspace handle")?;
2784+
2785+ cx.background_executor.allow_parking();
2786+ let panel = cx
2787+ .foreground_executor
2788+ .block_test(AgentPanel::load(weak_workspace, async_window_cx))
2789+ .context("Failed to load AgentPanel")?;
2790+ cx.background_executor.forbid_parking();
2791+
2792+ workspace_window
2793+ .update(cx, |workspace, window, cx| {
2794+ workspace.add_panel(panel.clone(), window, cx);
2795+ workspace.open_panel::<AgentPanel>(window, cx);
2796+ })
2797+ .context("Failed to add the agent panel")?;
2798+
2799+ cx.run_until_parked();
2800+
2801+ // Zoomed for the same reason the recording phase zooms: the line is long by
2802+ // design, and a picture of a truncated line is not a picture of the line.
2803+ cx.update_window(workspace_window.into(), |_, window, cx| {
2804+ panel.update(cx, |panel, cx| {
2805+ use workspace::dock::Panel as _;
2806+ panel.set_zoomed(true, window, cx);
2807+ });
2808+ })
2809+ .log_err();
2810+ cx.run_until_parked();
2811+
2812+ // The `codex-acp`-class thread, reopened under the id and agent id the
2813+ // previous process left behind — the two things `restore_new_draft` reads
2814+ // out of the metadata store on a real relaunch.
2815+ let agent_id = AgentId::new(handoff.agent_id.clone());
2816+ let external_stub: Rc<dyn AgentServer> = Rc::new(StubAgentServer::new(
2817+ StubAgentConnection::new().with_agent_id(agent_id.clone()),
2818+ ));
2819+ cx.update_window(workspace_window.into(), |_, window, cx| {
2820+ panel.update(cx, |panel, cx| {
2821+ panel.open_external_thread_with_server_under_id(
2822+ external_stub,
2823+ handoff.external_thread,
2824+ window,
2825+ cx,
2826+ );
2827+ });
2828+ })?;
2829+ cx.run_until_parked();
2830+
2831+ let reopened_external = cx
2832+ .read(|cx| panel.read(cx).active_thread_id(cx))
2833+ .ok_or_else(|| anyhow::anyhow!("the reopened external thread has no id"))?;
2834+ anyhow::ensure!(
2835+ reopened_external == handoff.external_thread,
2836+ "the reopened thread is {reopened_external:?}, not the persisted \
2837+ {:?} — a new thread wearing the same content proves nothing about a \
2838+ restart",
2839+ handoff.external_thread
2840+ );
2841+
2842+ let external_line = cx
2843+ .read(|cx| omega_executor_line(&panel, cx))
2844+ .ok_or_else(|| anyhow::anyhow!("the reopened external thread has no disclosure"))?;
2845+ anyhow::ensure!(
2846+ external_line == handoff.external_line,
2847+ "the restarted process discloses {external_line:?}, where the process \
2848+ that owned this thread disclosed {:?}",
2849+ handoff.external_line
2850+ );
2851+ anyhow::ensure!(
2852+ external_line.starts_with("external_acp"),
2853+ "a thread on a connection Omega did not build must not be disclosed as \
2854+ first-party output after a restart either: {external_line:?}"
2855+ );
2856+ println!(" external-acp executor line after restart: {external_line}");
2857+
2858+ let external_after_restart = run_visual_test(
2859+ "omega_executor_disclosure_external_acp_after_restart",
2860+ workspace_window.into(),
2861+ cx,
2862+ update_baseline,
2863+ )?;
2864+
2865+ // The engine-lane thread. Same reopen, different id — and this one the
2866+ // journal on disk names, so the line has to carry the run.
2867+ let lane_stub: Rc<dyn AgentServer> = Rc::new(StubAgentServer::new(
2868+ StubAgentConnection::new().with_agent_id(agent_id),
2869+ ));
2870+ cx.update_window(workspace_window.into(), |_, window, cx| {
2871+ panel.update(cx, |panel, cx| {
2872+ panel.open_external_thread_with_server_under_id(
2873+ lane_stub,
2874+ handoff.lane_thread,
2875+ window,
2876+ cx,
2877+ );
2878+ });
2879+ })?;
2880+ cx.run_until_parked();
2881+
2882+ let reopened_lane = cx
2883+ .read(|cx| panel.read(cx).active_thread_id(cx))
2884+ .ok_or_else(|| anyhow::anyhow!("the reopened lane thread has no id"))?;
2885+ anyhow::ensure!(
2886+ reopened_lane == handoff.lane_thread,
2887+ "the reopened lane thread is {reopened_lane:?}, not the persisted {:?}",
2888+ handoff.lane_thread
2889+ );
2890+
2891+ let lane_record = cx
2892+ .read(|cx| omega_executor_record(&panel, cx))
2893+ .ok_or_else(|| anyhow::anyhow!("the reopened lane thread has no disclosure"))?;
2894+ anyhow::ensure!(
2895+ lane_record.run_ref.as_deref() == Some(handoff.operation_ref.as_str()),
2896+ "the restored record names run {:?}, not {:?}",
2897+ lane_record.run_ref,
2898+ handoff.operation_ref
2899+ );
2900+ let lane_line = lane_record.label();
2901+ anyhow::ensure!(
2902+ lane_line == handoff.lane_line,
2903+ "the restarted process discloses {lane_line:?}, where the process that \
2904+ owned this thread disclosed {:?}",
2905+ handoff.lane_line
2906+ );
2907+ println!(" engine-lane executor line after restart: {lane_line}");
2908+
2909+ let lane_after_restart = run_visual_test(
2910+ "omega_executor_disclosure_engine_lane_after_restart",
2911+ workspace_window.into(),
2912+ cx,
2913+ update_baseline,
2914+ )?;
2915+
2916+ cx.update_window(workspace_window.into(), |_, window, _cx| {
2917+ window.remove_window();
2918+ })
2919+ .log_err();
2920+ cx.run_until_parked();
2921+
2922+ for result in [&external_after_restart, &lane_after_restart] {
2923+ if let TestResult::BaselineUpdated(path) = result {
2924+ return Ok(TestResult::BaselineUpdated(path.clone()));
2925+ }
2926+ }
2927+ Ok(TestResult::Passed)
25282928 }
25292929
25302930 /// A stub AgentServer for visual testing that returns a pre-programmed connection.
diff --git a/crates/zed/test_fixtures/visual_tests/omega_executor_disclosure_engine_lane_after_restart.png b/crates/zed/test_fixtures/visual_tests/omega_executor_disclosure_engine_lane_after_restart.png
new file mode 100644
index 0000000000..149a37ed5a
Binary files /dev/null and b/crates/zed/test_fixtures/visual_tests/omega_executor_disclosure_engine_lane_after_restart.png differ
diff --git a/crates/zed/test_fixtures/visual_tests/omega_executor_disclosure_external_acp_after_restart.png b/crates/zed/test_fixtures/visual_tests/omega_executor_disclosure_external_acp_after_restart.png
new file mode 100644
index 0000000000..fa6a000cfa
Binary files /dev/null and b/crates/zed/test_fixtures/visual_tests/omega_executor_disclosure_external_acp_after_restart.png differ
diff --git a/script/omega-visual-proof b/script/omega-visual-proof
index a74e73e536..eba57b7a39 100755
--- a/script/omega-visual-proof
+++ b/script/omega-visual-proof
@@ -10,6 +10,16 @@
1010 # script/omega-visual-proof compare against the baselines
1111 # script/omega-visual-proof --update rewrite the baselines
1212 #
13+# Two processes, not one. The first captures the front door, the typing path and
14+# the executor line on three thread kinds, then leaves on disk what a relaunch
15+# would find: the correlation journal in its production schema at its production
16+# path, plus the thread ids and agent id that `ZED_STATELESS=1` deliberately
17+# keeps out of a database. The second process is a genuinely cold one — empty
18+# statics, no threads, no panel — and photographs the executor line it derives
19+# for a `codex-acp`-class thread and an engine-lane thread from that disk state
20+# alone. omega#77 asks for those two "after restart", and a second phase of the
21+# same process could not answer it.
22+#
1323 # Baselines live in crates/zed/test_fixtures/visual_tests/ and were generated on
1424 # Apple Silicon with the Metal renderer. A materially different GPU may need
1525 # --update and a human looking at the result before the new baselines are
@@ -21,4 +31,22 @@ if [[ "${1:-}" == "--update" ]]; then
2131 fi
2232
2333 export OMEGA_VISUAL_ONLY=1
24-exec cargo run --release -p zed --bin zed_visual_test_runner --features visual-tests
34+
35+# One data directory, shared by both processes and by neither the developer's
36+# Omega installation nor a previous run of this script. A stale directory would
37+# let the restart phase read a handoff the recording phase did not write, which
38+# is the one way this proof could pass without a restart having happened.
39+OMEGA_VISUAL_DATA_DIR="$(mktemp -d "${TMPDIR:-/tmp}/omega-visual-proof.XXXXXX")"
40+export OMEGA_VISUAL_DATA_DIR
41+trap 'rm -rf "${OMEGA_VISUAL_DATA_DIR}"' EXIT
42+
43+cargo build --release -p zed --bin zed_visual_test_runner --features visual-tests
44+runner="$(cargo metadata --format-version 1 --no-deps | \
45+ python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])')/release/zed_visual_test_runner"
46+
47+echo "==> phase 1: the surfaces, and the disk state a relaunch would find"
48+"${runner}"
49+
50+echo
51+echo "==> phase 2: a cold process, disclosing from that disk state"
52+OMEGA_VISUAL_PHASE=restart "${runner}"