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 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git

Revision diff

7bb06ca2 8a07c5bf
diff --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}"
Served at tenant.openagents/omega Member data and write actions are omitted.