230	/// each cycle (drop ingest + echo fires — T3/T4/T5). The graceful signoff (T6)
231	/// is driven by the shutdown path; M3's daemon re-hosts all of it.
232	// [impl->REQ-START-2]
233	// [impl->REQ-SEAM-PSYCHE]
234	pub fn cmd_listen(
235	    id: &str,
236	    parent_pid_override: Option<u32>,
237	    once: bool,
238	    manifest: Option<&spt_runtime::Manifest>,
239	    install_dir: Option<&std::path::Path>,
240	    subnet: Option<&str>,
241	) -> i32 {
242	    // [impl->REQ-HAZARD-ID-CHARSET] reject malformed ids at the creation seam.
243	    if let Err(e) = spt_proto::id::validate_endpoint_id(id) {
244	        eprintln!("INVALID_ID:{id}: {e}");
245	        return EXIT_REFUSED;
246	    }
247	    let parent_pid = match parent_pid_override.or_else(proc::parent_pid) {
248	        Some(p) => p,
249	        None => {
250	            eprintln!("NO_PARENT_PID: could not resolve the parent pid anchor");
251	            return EXIT_REFUSED;
252	        }
253	    };
254	    let token = match bind_from_seed(id, parent_pid, subnet) {
255	        Ok(t) => t,
256	        Err(e) => {
257	            report_bind_error(&e);
258	            return EXIT_REFUSED;
259	        }
260	    };
261	    eprintln!("BOUND:{id} token={token}");
262
263	    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
264	    let relay = match spt_daemon::relay::Relay::bind(id, &perch_path) {
265	        Ok(r) => r,
266	        Err(e) => {
267	            eprintln!("LISTEN_FAIL:{id}: {e}");
268	            return EXIT_REFUSED;
269	        }
270	    };
271
272	<<<<<<< HEAD
273	    // The bound session id (from the seed) — fills the lifecycle templates.
274	    let session_id = info::read_info(&perch_path)
275	        .map(|i| i.session_id)
276	        .filter(|s| !s.is_empty());
277
278	    // Live-agent lifecycle host (interim; M3's daemon re-hosts). Sweep a stale
279	    // signoff sentinel (3.2) then spawn the Psyche companion (T2) before READY.
280	    let live = super::live::LiveHost::new_opt(manifest, id, install_dir);
281	    if let Some(host) = &live {
282	        host.sweep_on_start();
283	        match host.spawn_psyche(session_id.as_deref(), None) {
284	            Ok(p) => eprintln!("PSYCHE_SPAWNED:{} pid={}", p.id, p.pid),
285	            Err(e) => eprintln!("PSYCHE_SPAWN_FAIL:{id}: {e}"),
286	=======
287	    // Live-agent FIRST-HOST handoff to the daemon (M11-W0.2, REQ-DAEMON-1): the
288	    // Psyche/pulse lifecycle is no longer hosted in THIS process — it lives in the
289	    // brain (`spt_daemon::livehost`). For a live-capable endpoint (manifest
290	    // declares `[session.psyche_init]`) we mark the perch `status=online` at
291	    // establish; the brain's reconcile then hosts the Psyche within one tick (the
292	    // shellwake CLI-flips-status / brain-reconciles pattern). `status` is
293	    // daemon-authoritative (CONTEXT:194). A new `listen` against a not-yet-restarted
294	    // OLD brain leaves the perch online-but-unhosted; that self-heals at the brain's
295	    // next BOOT reconcile (livehost reconciles at boot, not only on tick) — the
296	    // agent + the message relay below are live throughout regardless.
297	    // [impl->REQ-DAEMON-1]
298	    let live_capable = manifest.is_some_and(|m| m.session.psyche_init.is_some());
299	    if live_capable {
300	        if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
301	            eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
302	>>>>>>> origin/m11-shell-substrate
303	        }
304	    }
305
306	    eprintln!("READY:{id}");
307
308	    // New-session-start is a reported resurface boundary (ADR-0007/D8b):
309	    // undismissed notifs surface into the spool before the backlog drain, so
310	    // they ride this very first drain — and render endpoint-native when the
311	    // manifest declares [session.notif]. Best-effort — never fails the listen.
312	    super::reporting::resurface_notifs_with(id, manifest);
313
314	    // Deliver one message to the live agent and record it as an spt context
315	    // injection for the two-origin digest (REQ-TERM-7) — `emit` is the
316	    // agent-facing chokepoint, so every delivered message becomes a glanceable
317	    // `owl_message` context entry. The recorded form is the whole composed
318	    // `<EVENT>` (what the agent sees), not the structural pair. Best-effort;
319	    // never disturbs delivery.

/// each cycle (drop ingest + echo fires — T3/T4/T5). The graceful signoff (T6)
/// is driven by the shutdown path; M3's daemon re-hosts all of it.
// [impl->REQ-START-2]
// [impl->REQ-SEAM-PSYCHE]
pub fn cmd_listen(
    id: &str,
    parent_pid_override: Option<u32>,
    once: bool,
    manifest: Option<&spt_runtime::Manifest>,
    install_dir: Option<&std::path::Path>,
    subnet: Option<&str>,
) -> i32 {
    // [impl->REQ-HAZARD-ID-CHARSET] reject malformed ids at the creation seam.
    if let Err(e) = spt_proto::id::validate_endpoint_id(id) {
        eprintln!("INVALID_ID:{id}: {e}");
        return EXIT_REFUSED;
    }
    let parent_pid = match parent_pid_override.or_else(proc::parent_pid) {
        Some(p) => p,
        None => {
            eprintln!("NO_PARENT_PID: could not resolve the parent pid anchor");
            return EXIT_REFUSED;
        }
    };
    let token = match bind_from_seed(id, parent_pid, subnet) {
        Ok(t) => t,
        Err(e) => {
            report_bind_error(&e);
            return EXIT_REFUSED;
        }
    };
    eprintln!("BOUND:{id} token={token}");

    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
    let relay = match spt_daemon::relay::Relay::bind(id, &perch_path) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("LISTEN_FAIL:{id}: {e}");
            return EXIT_REFUSED;
        }
    };

<<<<<<< HEAD
    // The bound session id (from the seed) — fills the lifecycle templates.
    let session_id = info::read_info(&perch_path)
        .map(|i| i.session_id)
        .filter(|s| !s.is_empty());

    // Live-agent lifecycle host (interim; M3's daemon re-hosts). Sweep a stale
    // signoff sentinel (3.2) then spawn the Psyche companion (T2) before READY.
    let live = super::live::LiveHost::new_opt(manifest, id, install_dir);
    if let Some(host) = &live {
        host.sweep_on_start();
        match host.spawn_psyche(session_id.as_deref(), None) {
            Ok(p) => eprintln!("PSYCHE_SPAWNED:{} pid={}", p.id, p.pid),
            Err(e) => eprintln!("PSYCHE_SPAWN_FAIL:{id}: {e}"),
=======
    // Live-agent FIRST-HOST handoff to the daemon (M11-W0.2, REQ-DAEMON-1): the
    // Psyche/pulse lifecycle is no longer hosted in THIS process — it lives in the
    // brain (`spt_daemon::livehost`). For a live-capable endpoint (manifest
    // declares `[session.psyche_init]`) we mark the perch `status=online` at
    // establish; the brain's reconcile then hosts the Psyche within one tick (the
    // shellwake CLI-flips-status / brain-reconciles pattern). `status` is
    // daemon-authoritative (CONTEXT:194). A new `listen` against a not-yet-restarted
    // OLD brain leaves the perch online-but-unhosted; that self-heals at the brain's
    // next BOOT reconcile (livehost reconciles at boot, not only on tick) — the
    // agent + the message relay below are live throughout regardless.
    // [impl->REQ-DAEMON-1]
    let live_capable = manifest.is_some_and(|m| m.session.psyche_init.is_some());
    if live_capable {
        if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
            eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
>>>>>>> origin/m11-shell-substrate
        }
    }

    eprintln!("READY:{id}");

    // New-session-start is a reported resurface boundary (ADR-0007/D8b):
    // undismissed notifs surface into the spool before the backlog drain, so
    // they ride this very first drain — and render endpoint-native when the
    // manifest declares [session.notif]. Best-effort — never fails the listen.
    super::reporting::resurface_notifs_with(id, manifest);

    // Deliver one message to the live agent and record it as an spt context
    // injection for the two-origin digest (REQ-TERM-7) — `emit` is the
    // agent-facing chokepoint, so every delivered message becomes a glanceable
    // `owl_message` context entry. The recorded form is the whole composed
    // `<EVENT>` (what the agent sees), not the structural pair. Best-effort;
    // never disturbs delivery.


Now I see it: the **signature silently kept main's `install_dir` param** (git auto-resolved it), but taking m11's daemon-host body makes `install_dir` unused → clippy `-D warnings` fail. That's the silent-drop hazard surfacing. Daemon-host `cmd_listen` doesn't need `install_dir` (resolution moves to livehost via `source_dir`). Resolving: drop the param + take m11's body.


Edit

    once: bool,
    manifest: Option<&spt_runtime::Manifest>,
    install_dir: Option<&std::path::Path>,
    subnet: Option<&str>,
) -> i32 {

    once: bool,
    manifest: Option<&spt_runtime::Manifest>,
    subnet: Option<&str>,
) -> i32 {


The file crates\spt\src\api\startup.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    once: bool,
    manifest: Option<&spt_runtime::Manifest>,
    install_dir: Option<&std::path::Path>,
    subnet: Option<&str>,
) -> i32 {

    once: bool,
    manifest: Option<&spt_runtime::Manifest>,
    subnet: Option<&str>,
) -> i32 {

-    install_dir: Option<&std::path::Path>,

// [impl->REQ-HAZARD-ID-CHARSET] reject malformed ids at the creation seam.


<<<<<<< HEAD
    // The bound session id (from the seed) — fills the lifecycle templates.
    let session_id = info::read_info(&perch_path)
        .map(|i| i.session_id)
        .filter(|s| !s.is_empty());

    // Live-agent lifecycle host (interim; M3's daemon re-hosts). Sweep a stale
    // signoff sentinel (3.2) then spawn the Psyche companion (T2) before READY.
    let live = super::live::LiveHost::new_opt(manifest, id, install_dir);
    if let Some(host) = &live {
        host.sweep_on_start();
        match host.spawn_psyche(session_id.as_deref(), None) {
            Ok(p) => eprintln!("PSYCHE_SPAWNED:{} pid={}", p.id, p.pid),
            Err(e) => eprintln!("PSYCHE_SPAWN_FAIL:{id}: {e}"),
=======
    // Live-agent FIRST-HOST handoff to the daemon (M11-W0.2, REQ-DAEMON-1): the

// Live-agent FIRST-HOST handoff to the daemon (M11-W0.2, REQ-DAEMON-1): the


<<<<<<< HEAD
    // The bound session id (from the seed) — fills the lifecycle templates.
    let session_id = info::read_info(&perch_path)
        .map(|i| i.session_id)
        .filter(|s| !s.is_empty());

    // Live-agent lifecycle host (interim; M3's daemon re-hosts). Sweep a stale
    // signoff sentinel (3.2) then spawn the Psyche companion (T2) before READY.
    let live = super::live::LiveHost::new_opt(manifest, id, install_dir);
    if let Some(host) = &live {
        host.sweep_on_start();
        match host.spawn_psyche(session_id.as_deref(), None) {
            Ok(p) => eprintln!("PSYCHE_SPAWNED:{} pid={}", p.id, p.pid),
            Err(e) => eprintln!("PSYCHE_SPAWN_FAIL:{id}: {e}"),
=======
    // Live-agent FIRST-HOST handoff to the daemon (M11-W0.2, REQ-DAEMON-1): the

-    // The bound session id (from the seed) — fills the lifecycle templates.

-    let session_id = info::read_info(&perch_path)

-    // Live-agent lifecycle host (interim; M3's daemon re-hosts). Sweep a stale

-    // signoff sentinel (3.2) then spawn the Psyche companion (T2) before READY.

-    let live = super::live::LiveHost::new_opt(manifest, id, install_dir);

-        match host.spawn_psyche(session_id.as_deref(), None) {

-            Ok(p) => eprintln!("PSYCHE_SPAWNED:{} pid={}", p.id, p.pid),

-            Err(e) => eprintln!("PSYCHE_SPAWN_FAIL:{id}: {e}"),

// Psyche/pulse lifecycle is no longer hosted in THIS process — it lives in the

// brain (`spt_daemon::livehost`). For a live-capable endpoint (manifest


        if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
            eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
>>>>>>> origin/m11-shell-substrate
        }
    }

        if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
            eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
        }
    }


        if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
            eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
>>>>>>> origin/m11-shell-substrate
        }
    }

        if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
            eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
        }
    }


startup.rs resolved. Now the manifest.rs conflict:


PowerShell

git diff crates/spt-runtime/src/manifest.rs 2>&1 | Select-Object -First 80

Show manifest.rs conflict


diff --cc crates/spt-runtime/src/manifest.rs
index c97ba04,b0dca0d..0000000
--- a/crates/spt-runtime/src/manifest.rs
+++ b/crates/spt-runtime/src/manifest.rs
@@@ -941,41 -1005,39 +1034,77 @@@ types = ["image", "sound", "event"
          );
      }

++<<<<<<< HEAD
 +    // [unit->REQ-UPD-9] the gh_release avenue: a manifest declaring
 +    // `avenue = "gh_release"` + `repo` validates (with `asset`/`signing_key`
 +    // optional, round-tripping); a gh_release manifest MISSING `repo` is
 +    // rejected at validation.
 +    #[test]
 +    fn update_avenue_gh_release_fields() {
 +        // gh_release with repo only → ok (asset defaults, signing_key optional).
 +        let bare = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
 +                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n";
 +        let m = Manifest::from_toml_str(bare).expect("gh_release with repo parses");
 +        let u = m.update.clone().unwrap();
 +        assert_eq!(u.avenue, UpdateAvenue::GhRelease);
 +        assert_eq!(u.repo.as_deref(), Some("user/repo"));
 +        assert!(u.asset.is_none(), "asset is optional (defaults to adapter.spt)");
 +        assert!(u.signing_key.is_none(), "signing_key is optional for gh_release");
 +        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
 +        assert_eq!(m.update, reparsed.update, "gh_release round-trips");
 +
 +        // gh_release with an explicit asset + optional signing_key → preserved.
 +        let full = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
 +                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n\
 +                    asset=\"my-adapter.spt\"\nsigning_key=\"deadbeef\"\n";
 +        let mf = Manifest::from_toml_str(full).expect("gh_release with asset+key parses");
 +        let uf = mf.update.unwrap();
 +        assert_eq!(uf.asset.as_deref(), Some("my-adapter.spt"));
 +        assert_eq!(uf.signing_key.as_deref(), Some("deadbeef"));
 +
 +        // gh_release MISSING repo → rejected at validation.
 +        let no_repo = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
 +                       [update]\navenue=\"gh_release\"\n";
 +        let err = Manifest::from_toml_str(no_repo).unwrap_err();
 +        assert!(matches!(err, ManifestError::Validation(_)));
 +        assert!(
 +            err.to_string().contains("repo"),
 +            "gh_release must require repo: {err}"
++=======
+     // [unit->REQ-SHELL-4] [shell.tunnel] opt-in: an enabled tunnel with a
+     // diagnostic protocol label parses + round-trips; a default (absent) tunnel
+     // serializes no `[shell.tunnel]` table; a `protocol` label without
+     // `enable = true` is refused at validate (the label only describes an enabled
+     // tunnel's opaque wire).
+     #[test]
+     fn shell_tunnel_opt_in() {
+         let base = "[adapter]\nname=\"u\"\nkind=\"shell\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
+                     [shell]\nspawn=\"usbip-shell --link {link_token}\"\n";
+
+         // enabled + labelled → parses, fields preserved, round-trips.
+         let enabled = format!("{base}\n[shell.tunnel]\nenable = true\nprotocol = \"usbip-urb\"\n");
+         let m = Manifest::from_toml_str(&enabled).expect("enabled tunnel parses");
+         let t = &m.shell.as_ref().unwrap().tunnel;
+         assert!(t.enable);
+         assert_eq!(t.protocol.as_deref(), Some("usbip-urb"));
+         let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
+         assert_eq!(m.shell.unwrap().tunnel, reparsed.shell.unwrap().tunnel);
+
+         // absent tunnel → default, and serializes no [shell.tunnel] table.
+         let m0 = Manifest::from_toml_str(base).expect("no tunnel parses");
+         assert!(m0.shell.as_ref().unwrap().tunnel.is_default());
+         assert!(
+             !m0.to_toml_string().unwrap().contains("[shell.tunnel]"),
+             "a default tunnel must not emit a [shell.tunnel] table"
+         );
+
+         // protocol label without enable → validate refuses the contradiction.
+         let bad = format!("{base}\n[shell.tunnel]\nprotocol = \"usbip-urb\"\n");
+         let err = Manifest::from_toml_str(&bad).unwrap_err().to_string();
+         assert!(
+             err.contains("enable = true"),
+             "label-without-enable must be refused: {err}"
++>>>>>>> origin/m11-shell-substrate
          );


Pure positional conflict — two independent tests (`update_avenue_gh_release_fields` from main, `shell_tunnel_opt_in` from m11) inserted at the same spot. Keep both. Let me read the full region to resolve cleanly:


1030	        let md = Manifest::from_toml_str(deleg).expect("delegated parses");
1031	        assert!(
1032	            md.update.unwrap().self_verifies,
1033	            "delegated attest preserved"
1034	        );
1035	    }
1036
1037	<<<<<<< HEAD
1038	    // [unit->REQ-UPD-9] the gh_release avenue: a manifest declaring
1039	    // `avenue = "gh_release"` + `repo` validates (with `asset`/`signing_key`
1040	    // optional, round-tripping); a gh_release manifest MISSING `repo` is
1041	    // rejected at validation.
1042	    #[test]
1043	    fn update_avenue_gh_release_fields() {
1044	        // gh_release with repo only → ok (asset defaults, signing_key optional).
1045	        let bare = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
1046	                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n";
1047	        let m = Manifest::from_toml_str(bare).expect("gh_release with repo parses");
1048	        let u = m.update.clone().unwrap();
1049	        assert_eq!(u.avenue, UpdateAvenue::GhRelease);
1050	        assert_eq!(u.repo.as_deref(), Some("user/repo"));
1051	        assert!(u.asset.is_none(), "asset is optional (defaults to adapter.spt)");
1052	        assert!(u.signing_key.is_none(), "signing_key is optional for gh_release");
1053	        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
1054	        assert_eq!(m.update, reparsed.update, "gh_release round-trips");
1055
1056	        // gh_release with an explicit asset + optional signing_key → preserved.
1057	        let full = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
1058	                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n\
1059	                    asset=\"my-adapter.spt\"\nsigning_key=\"deadbeef\"\n";
1060	        let mf = Manifest::from_toml_str(full).expect("gh_release with asset+key parses");
1061	        let uf = mf.update.unwrap();
1062	        assert_eq!(uf.asset.as_deref(), Some("my-adapter.spt"));
1063	        assert_eq!(uf.signing_key.as_deref(), Some("deadbeef"));
1064
1065	        // gh_release MISSING repo → rejected at validation.
1066	        let no_repo = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
1067	                       [update]\navenue=\"gh_release\"\n";
1068	        let err = Manifest::from_toml_str(no_repo).unwrap_err();
1069	        assert!(matches!(err, ManifestError::Validation(_)));
1070	        assert!(
1071	            err.to_string().contains("repo"),
1072	            "gh_release must require repo: {err}"
1073	=======
1074	    // [unit->REQ-SHELL-4] [shell.tunnel] opt-in: an enabled tunnel with a
1075	    // diagnostic protocol label parses + round-trips; a default (absent) tunnel
1076	    // serializes no `[shell.tunnel]` table; a `protocol` label without
1077	    // `enable = true` is refused at validate (the label only describes an enabled
1078	    // tunnel's opaque wire).
1079	    #[test]
1080	    fn shell_tunnel_opt_in() {
1081	        let base = "[adapter]\nname=\"u\"\nkind=\"shell\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
1082	                    [shell]\nspawn=\"usbip-shell --link {link_token}\"\n";
1083
1084	        // enabled + labelled → parses, fields preserved, round-trips.
1085	        let enabled = format!("{base}\n[shell.tunnel]\nenable = true\nprotocol = \"usbip-urb\"\n");
1086	        let m = Manifest::from_toml_str(&enabled).expect("enabled tunnel parses");
1087	        let t = &m.shell.as_ref().unwrap().tunnel;
1088	        assert!(t.enable);
1089	        assert_eq!(t.protocol.as_deref(), Some("usbip-urb"));
1090	        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
1091	        assert_eq!(m.shell.unwrap().tunnel, reparsed.shell.unwrap().tunnel);
1092
1093	        // absent tunnel → default, and serializes no [shell.tunnel] table.
1094	        let m0 = Manifest::from_toml_str(base).expect("no tunnel parses");
1095	        assert!(m0.shell.as_ref().unwrap().tunnel.is_default());
1096	        assert!(
1097	            !m0.to_toml_string().unwrap().contains("[shell.tunnel]"),
1098	            "a default tunnel must not emit a [shell.tunnel] table"
1099	        );
1100
1101	        // protocol label without enable → validate refuses the contradiction.
1102	        let bad = format!("{base}\n[shell.tunnel]\nprotocol = \"usbip-urb\"\n");
1103	        let err = Manifest::from_toml_str(&bad).unwrap_err().to_string();
1104	        assert!(
1105	            err.contains("enable = true"),
1106	            "label-without-enable must be refused: {err}"
1107	>>>>>>> origin/m11-shell-substrate
1108	        );
1109	    }
1110
1111	    #[test] // [unit->REQ-MANIFEST-1]
1112	    fn shell_fixture_parses() {
1113	        let m = Manifest::from_toml_str(SHELL_FIXTURE).expect("parse");
1114	        assert_eq!(m.adapter.kind, AdapterKind::Shell);
1115	        let shell = m.shell.as_ref().unwrap();
1116	        assert_eq!(shell.broadcast, Some(Broadcast::Subnet));
1117	        assert_eq!(shell.command_receipt, Some(CommandReceipt::Http));
1118	        assert_eq!(shell.capabilities["move"].args, ["direction", "distance"]);
1119	        assert!(shell.capabilities["screenshot"].args.is_empty());
1120	        assert_eq!(shell.sensory.types, ["image", "sound", "event"]);
1121	    }
1122
1123	    #[test] // [unit->REQ-MANIFEST-1]
1124	    fn round_trips_through_toml() {

        let md = Manifest::from_toml_str(deleg).expect("delegated parses");
        assert!(
            md.update.unwrap().self_verifies,
            "delegated attest preserved"
        );
    }

<<<<<<< HEAD
    // [unit->REQ-UPD-9] the gh_release avenue: a manifest declaring
    // `avenue = "gh_release"` + `repo` validates (with `asset`/`signing_key`
    // optional, round-tripping); a gh_release manifest MISSING `repo` is
    // rejected at validation.
    #[test]
    fn update_avenue_gh_release_fields() {
        // gh_release with repo only → ok (asset defaults, signing_key optional).
        let bare = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n";
        let m = Manifest::from_toml_str(bare).expect("gh_release with repo parses");
        let u = m.update.clone().unwrap();
        assert_eq!(u.avenue, UpdateAvenue::GhRelease);
        assert_eq!(u.repo.as_deref(), Some("user/repo"));
        assert!(u.asset.is_none(), "asset is optional (defaults to adapter.spt)");
        assert!(u.signing_key.is_none(), "signing_key is optional for gh_release");
        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
        assert_eq!(m.update, reparsed.update, "gh_release round-trips");

        // gh_release with an explicit asset + optional signing_key → preserved.
        let full = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n\
                    asset=\"my-adapter.spt\"\nsigning_key=\"deadbeef\"\n";
        let mf = Manifest::from_toml_str(full).expect("gh_release with asset+key parses");
        let uf = mf.update.unwrap();
        assert_eq!(uf.asset.as_deref(), Some("my-adapter.spt"));
        assert_eq!(uf.signing_key.as_deref(), Some("deadbeef"));

        // gh_release MISSING repo → rejected at validation.
        let no_repo = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                       [update]\navenue=\"gh_release\"\n";
        let err = Manifest::from_toml_str(no_repo).unwrap_err();
        assert!(matches!(err, ManifestError::Validation(_)));
        assert!(
            err.to_string().contains("repo"),
            "gh_release must require repo: {err}"
=======
    // [unit->REQ-SHELL-4] [shell.tunnel] opt-in: an enabled tunnel with a
    // diagnostic protocol label parses + round-trips; a default (absent) tunnel
    // serializes no `[shell.tunnel]` table; a `protocol` label without
    // `enable = true` is refused at validate (the label only describes an enabled
    // tunnel's opaque wire).
    #[test]
    fn shell_tunnel_opt_in() {
        let base = "[adapter]\nname=\"u\"\nkind=\"shell\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [shell]\nspawn=\"usbip-shell --link {link_token}\"\n";

        // enabled + labelled → parses, fields preserved, round-trips.
        let enabled = format!("{base}\n[shell.tunnel]\nenable = true\nprotocol = \"usbip-urb\"\n");
        let m = Manifest::from_toml_str(&enabled).expect("enabled tunnel parses");
        let t = &m.shell.as_ref().unwrap().tunnel;
        assert!(t.enable);
        assert_eq!(t.protocol.as_deref(), Some("usbip-urb"));
        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
        assert_eq!(m.shell.unwrap().tunnel, reparsed.shell.unwrap().tunnel);

        // absent tunnel → default, and serializes no [shell.tunnel] table.
        let m0 = Manifest::from_toml_str(base).expect("no tunnel parses");
        assert!(m0.shell.as_ref().unwrap().tunnel.is_default());
        assert!(
            !m0.to_toml_string().unwrap().contains("[shell.tunnel]"),
            "a default tunnel must not emit a [shell.tunnel] table"
        );

        // protocol label without enable → validate refuses the contradiction.
        let bad = format!("{base}\n[shell.tunnel]\nprotocol = \"usbip-urb\"\n");
        let err = Manifest::from_toml_str(&bad).unwrap_err().to_string();
        assert!(
            err.contains("enable = true"),
            "label-without-enable must be refused: {err}"
>>>>>>> origin/m11-shell-substrate
        );
    }

    #[test] // [unit->REQ-MANIFEST-1]
    fn shell_fixture_parses() {
        let m = Manifest::from_toml_str(SHELL_FIXTURE).expect("parse");
        assert_eq!(m.adapter.kind, AdapterKind::Shell);
        let shell = m.shell.as_ref().unwrap();
        assert_eq!(shell.broadcast, Some(Broadcast::Subnet));
        assert_eq!(shell.command_receipt, Some(CommandReceipt::Http));
        assert_eq!(shell.capabilities["move"].args, ["direction", "distance"]);
        assert!(shell.capabilities["screenshot"].args.is_empty());
        assert_eq!(shell.sensory.types, ["image", "sound", "event"]);
    }

    #[test] // [unit->REQ-MANIFEST-1]
    fn round_trips_through_toml() {


Both are independent tests sharing the trailing `);` `}`. Keep both — close the gh_release test, leave the tunnel test open for the existing trailing `);`/`}`:


Edit

<<<<<<< HEAD
    // [unit->REQ-UPD-9] the gh_release avenue: a manifest declaring
    // `avenue = "gh_release"` + `repo` validates (with `asset`/`signing_key`
    // optional, round-tripping); a gh_release manifest MISSING `repo` is
    // rejected at validation.
    #[test]
    fn update_avenue_gh_release_fields() {
        // gh_release with repo only → ok (asset defaults, signing_key optional).
        let bare = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n";
        let m = Manifest::from_toml_str(bare).expect("gh_release with repo parses");
        let u = m.update.clone().unwrap();
        assert_eq!(u.avenue, UpdateAvenue::GhRelease);
        assert_eq!(u.repo.as_deref(), Some("user/repo"));
        assert!(u.asset.is_none(), "asset is optional (defaults to adapter.spt)");
        assert!(u.signing_key.is_none(), "signing_key is optional for gh_release");
        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
        assert_eq!(m.update, reparsed.update, "gh_release round-trips");

        // gh_release with an explicit asset + optional signing_key → preserved.
        let full = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n\
                    asset=\"my-adapter.spt\"\nsigning_key=\"deadbeef\"\n";
        let mf = Manifest::from_toml_str(full).expect("gh_release with asset+key parses");
        let uf = mf.update.unwrap();
        assert_eq!(uf.asset.as_deref(), Some("my-adapter.spt"));
        assert_eq!(uf.signing_key.as_deref(), Some("deadbeef"));

        // gh_release MISSING repo → rejected at validation.
        let no_repo = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                       [update]\navenue=\"gh_release\"\n";
        let err = Manifest::from_toml_str(no_repo).unwrap_err();
        assert!(matches!(err, ManifestError::Validation(_)));
        assert!(
            err.to_string().contains("repo"),
            "gh_release must require repo: {err}"
=======
    // [unit->REQ-SHELL-4] [shell.tunnel] opt-in: an enabled tunnel with a

    // [unit->REQ-UPD-9] the gh_release avenue: a manifest declaring
    // `avenue = "gh_release"` + `repo` validates (with `asset`/`signing_key`
    // optional, round-tripping); a gh_release manifest MISSING `repo` is
    // rejected at validation.
    #[test]
    fn update_avenue_gh_release_fields() {
        // gh_release with repo only → ok (asset defaults, signing_key optional).
        let bare = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n";
        let m = Manifest::from_toml_str(bare).expect("gh_release with repo parses");
        let u = m.update.clone().unwrap();
        assert_eq!(u.avenue, UpdateAvenue::GhRelease);
        assert_eq!(u.repo.as_deref(), Some("user/repo"));
        assert!(u.asset.is_none(), "asset is optional (defaults to adapter.spt)");
        assert!(u.signing_key.is_none(), "signing_key is optional for gh_release");
        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
        assert_eq!(m.update, reparsed.update, "gh_release round-trips");

        // gh_release with an explicit asset + optional signing_key → preserved.
        let full = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n\
                    asset=\"my-adapter.spt\"\nsigning_key=\"deadbeef\"\n";
        let mf = Manifest::from_toml_str(full).expect("gh_release with asset+key parses");
        let uf = mf.update.unwrap();
        assert_eq!(uf.asset.as_deref(), Some("my-adapter.spt"));
        assert_eq!(uf.signing_key.as_deref(), Some("deadbeef"));

        // gh_release MISSING repo → rejected at validation.
        let no_repo = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                       [update]\navenue=\"gh_release\"\n";
        let err = Manifest::from_toml_str(no_repo).unwrap_err();
        assert!(matches!(err, ManifestError::Validation(_)));
        assert!(
            err.to_string().contains("repo"),
            "gh_release must require repo: {err}"
        );
    }

    // [unit->REQ-SHELL-4] [shell.tunnel] opt-in: an enabled tunnel with a


The file crates\spt-runtime\src\manifest.rs has been updated successfully. (file state is current in your context — no need to Read it back)

<<<<<<< HEAD
    // [unit->REQ-UPD-9] the gh_release avenue: a manifest declaring
    // `avenue = "gh_release"` + `repo` validates (with `asset`/`signing_key`
    // optional, round-tripping); a gh_release manifest MISSING `repo` is
    // rejected at validation.
    #[test]
    fn update_avenue_gh_release_fields() {
        // gh_release with repo only → ok (asset defaults, signing_key optional).
        let bare = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n";
        let m = Manifest::from_toml_str(bare).expect("gh_release with repo parses");
        let u = m.update.clone().unwrap();
        assert_eq!(u.avenue, UpdateAvenue::GhRelease);
        assert_eq!(u.repo.as_deref(), Some("user/repo"));
        assert!(u.asset.is_none(), "asset is optional (defaults to adapter.spt)");
        assert!(u.signing_key.is_none(), "signing_key is optional for gh_release");
        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
        assert_eq!(m.update, reparsed.update, "gh_release round-trips");

        // gh_release with an explicit asset + optional signing_key → preserved.
        let full = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n\
                    asset=\"my-adapter.spt\"\nsigning_key=\"deadbeef\"\n";
        let mf = Manifest::from_toml_str(full).expect("gh_release with asset+key parses");
        let uf = mf.update.unwrap();
        assert_eq!(uf.asset.as_deref(), Some("my-adapter.spt"));
        assert_eq!(uf.signing_key.as_deref(), Some("deadbeef"));

        // gh_release MISSING repo → rejected at validation.
        let no_repo = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                       [update]\navenue=\"gh_release\"\n";
        let err = Manifest::from_toml_str(no_repo).unwrap_err();
        assert!(matches!(err, ManifestError::Validation(_)));
        assert!(
            err.to_string().contains("repo"),
            "gh_release must require repo: {err}"
=======
    // [unit->REQ-SHELL-4] [shell.tunnel] opt-in: an enabled tunnel with a

    // [unit->REQ-UPD-9] the gh_release avenue: a manifest declaring
    // `avenue = "gh_release"` + `repo` validates (with `asset`/`signing_key`
    // optional, round-tripping); a gh_release manifest MISSING `repo` is
    // rejected at validation.
    #[test]
    fn update_avenue_gh_release_fields() {
        // gh_release with repo only → ok (asset defaults, signing_key optional).
        let bare = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n";
        let m = Manifest::from_toml_str(bare).expect("gh_release with repo parses");
        let u = m.update.clone().unwrap();
        assert_eq!(u.avenue, UpdateAvenue::GhRelease);
        assert_eq!(u.repo.as_deref(), Some("user/repo"));
        assert!(u.asset.is_none(), "asset is optional (defaults to adapter.spt)");
        assert!(u.signing_key.is_none(), "signing_key is optional for gh_release");
        let reparsed = Manifest::from_toml_str(&m.to_toml_string().unwrap()).unwrap();
        assert_eq!(m.update, reparsed.update, "gh_release round-trips");

        // gh_release with an explicit asset + optional signing_key → preserved.
        let full = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                    [update]\navenue=\"gh_release\"\nrepo=\"user/repo\"\n\
                    asset=\"my-adapter.spt\"\nsigning_key=\"deadbeef\"\n";
        let mf = Manifest::from_toml_str(full).expect("gh_release with asset+key parses");
        let uf = mf.update.unwrap();
        assert_eq!(uf.asset.as_deref(), Some("my-adapter.spt"));
        assert_eq!(uf.signing_key.as_deref(), Some("deadbeef"));

        // gh_release MISSING repo → rejected at validation.
        let no_repo = "[adapter]\nname=\"a\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n\
                       [update]\navenue=\"gh_release\"\n";
        let err = Manifest::from_toml_str(no_repo).unwrap_err();
        assert!(matches!(err, ManifestError::Validation(_)));
        assert!(
            err.to_string().contains("repo"),
            "gh_release must require repo: {err}"
        );
    }

    // [unit->REQ-SHELL-4] [shell.tunnel] opt-in: an enabled tunnel with a

// diagnostic protocol label parses + round-trips; a default (absent) tunnel

// serializes no `[shell.tunnel]` table; a `protocol` label without


        assert!(
            err.contains("enable = true"),
            "label-without-enable must be refused: {err}"
>>>>>>> origin/m11-shell-substrate
        );
    }

        assert!(
            err.contains("enable = true"),
            "label-without-enable must be refused: {err}"
        );
    }


        assert!(
            err.contains("enable = true"),
            "label-without-enable must be refused: {err}"
>>>>>>> origin/m11-shell-substrate
        );
    }

        assert!(
            err.contains("enable = true"),
            "label-without-enable must be refused: {err}"
        );
    }


Both conflicts resolved. Now check for any remaining markers + inspect the mod.rs `cmd_listen` call site (must drop the `install_dir` arg I removed from the signature):


PowerShell

Write-Output "=== remaining conflict markers ==="; git diff --check 2>&1 | Select-Object -First 20; Write-Output "=== cmd_listen call site in mod.rs ==="; Select-String -Path crates/spt/src/api/mod.rs -Pattern "cmd_listen" -Context 2,6

Check leftover markers + mod.rs cmd_listen call


=== remaining conflict markers ===
=== cmd_listen call site in mod.rs ===

  crates\spt\src\api\mod.rs:336:            once,
  crates\spt\src\api\mod.rs:337:            subnet,
> crates\spt\src\api\mod.rs:338:        } => startup::cmd_listen(
  crates\spt\src\api\mod.rs:339:            &id,
  crates\spt\src\api\mod.rs:340:            parent_pid,
  crates\spt\src\api\mod.rs:341:            once,
  crates\spt\src\api\mod.rs:342:            ctx.manifest.as_ref(),
  crates\spt\src\api\mod.rs:343:            ctx.install_dir.as_deref(),
  crates\spt\src\api\mod.rs:344:            subnet.as_deref(),


No leftover markers. The auto-merged call site passes `ctx.install_dir.as_deref()` — must drop it to match the new signature:


330	        // Harness-hosted startup (T4). Seed establishes; listen consumes+binds;
331	        // bind is the post-spawn establishing call (intrinsic auth).
332	        ApiCmd::Seed { pid, session_id } => startup::cmd_seed(&ctx, pid, session_id),
333	        ApiCmd::Listen {
334	            id,
335	            parent_pid,
336	            once,
337	            subnet,
338	        } => startup::cmd_listen(
339	            &id,
340	            parent_pid,
341	            once,
342	            ctx.manifest.as_ref(),
343	            ctx.install_dir.as_deref(),
344	            subnet.as_deref(),
345	        ),
346	        ApiCmd::BindShell { link_token } => {
347	            // The local-link handshake's receiving end (CONTEXT §local-link

        // Harness-hosted startup (T4). Seed establishes; listen consumes+binds;
        // bind is the post-spawn establishing call (intrinsic auth).
        ApiCmd::Seed { pid, session_id } => startup::cmd_seed(&ctx, pid, session_id),
        ApiCmd::Listen {
            id,
            parent_pid,
            once,
            subnet,
        } => startup::cmd_listen(
            &id,
            parent_pid,
            once,
            ctx.manifest.as_ref(),
            ctx.install_dir.as_deref(),
            subnet.as_deref(),
        ),
        ApiCmd::BindShell { link_token } => {
            // The local-link handshake's receiving end (CONTEXT §local-link


Edit

        } => startup::cmd_listen(
            &id,
            parent_pid,
            once,
            ctx.manifest.as_ref(),
            ctx.install_dir.as_deref(),
            subnet.as_deref(),
        ),

        } => startup::cmd_listen(
            &id,
            parent_pid,
            once,
            ctx.manifest.as_ref(),
            subnet.as_deref(),
        ),


The file crates\spt\src\api\mod.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        } => startup::cmd_listen(
            &id,
            parent_pid,
            once,
            ctx.manifest.as_ref(),
            ctx.install_dir.as_deref(),
            subnet.as_deref(),
        ),

        } => startup::cmd_listen(
            &id,
            parent_pid,
            once,
            ctx.manifest.as_ref(),
            subnet.as_deref(),
        ),

-            ctx.install_dir.as_deref(),


Now the core silent-drop fix: reapply Feature B at the livehost daemon site. First verify `BrainLifecycle::with_config_in` exists in the merged tree (came from main's lifecycle.rs) + its signature, and that `ctx.install_dir` is still used elsewhere in mod.rs:


PowerShell

Write-Output "=== with_config_in in merged tree ==="; Select-String -Path crates/spt-daemon/src/lifecycle.rs -Pattern "fn with_config_in|fn with_config\b|with_install_dir|install_dir" -Context 0,3 | Select-Object -First 30; Write-Output "=== ctx.install_dir other uses in mod.rs ==="; Select-String -Path crates/spt/src/api/mod.rs -Pattern "install_dir" | Select-Object -First 10

Verify with_config_in + ctx.install_dir usage


=== with_config_in in merged tree ===

> crates\spt-daemon\src\lifecycle.rs:78:    /// endpoint. Loads the durable [`DaemonConfig`] from disk. `install_dir` is
  crates\spt-daemon\src\lifecycle.rs:79:    /// the adapter install dir so the psyche role program token resolves there
  crates\spt-daemon\src\lifecycle.rs:80:    /// before `PATH` (REQ-INSTALL-11).
  crates\spt-daemon\src\lifecycle.rs:81:    pub fn new_opt(
> crates\spt-daemon\src\lifecycle.rs:84:        install_dir: Option<&Path>,
  crates\spt-daemon\src\lifecycle.rs:85:    ) -> Option<BrainLifecycle> {
> crates\spt-daemon\src\lifecycle.rs:86:        Self::with_config_in(manifest?, id, DaemonConfig::load(), install_dir)
  crates\spt-daemon\src\lifecycle.rs:87:    }
  crates\spt-daemon\src\lifecycle.rs:88:
  crates\spt-daemon\src\lifecycle.rs:89:    /// Build a host from a live-capable manifest with an explicit config (the
> crates\spt-daemon\src\lifecycle.rs:92:    pub fn with_config(manifest: &Manifest, id: &str, cfg: DaemonConfig) ->
Option<BrainLifecycle> {
  crates\spt-daemon\src\lifecycle.rs:93:        Self::with_config_in(manifest, id, cfg, None)
  crates\spt-daemon\src\lifecycle.rs:94:    }
  crates\spt-daemon\src\lifecycle.rs:95:
> crates\spt-daemon\src\lifecycle.rs:99:    pub fn with_config_in(
  crates\spt-daemon\src\lifecycle.rs:100:        manifest: &Manifest,
  crates\spt-daemon\src\lifecycle.rs:101:        id: &str,
  crates\spt-daemon\src\lifecycle.rs:102:        cfg: DaemonConfig,
> crates\spt-daemon\src\lifecycle.rs:103:        install_dir: Option<&Path>,
  crates\spt-daemon\src\lifecycle.rs:104:    ) -> Option<BrainLifecycle> {
  crates\spt-daemon\src\lifecycle.rs:105:        manifest.session.psyche_init.as_ref()?;
  crates\spt-daemon\src\lifecycle.rs:106:        let commune_dir = manifest.session.commune_dir.as_ref().map(PathBuf::from);
> crates\spt-daemon\src\lifecycle.rs:118:        let runtime = match install_dir {
> crates\spt-daemon\src\lifecycle.rs:119:            Some(dir) => ManifestRuntime::with_install_dir(manifest.clone(), dir),
  crates\spt-daemon\src\lifecycle.rs:120:            None => ManifestRuntime::new(manifest.clone()),
  crates\spt-daemon\src\lifecycle.rs:121:        };
  crates\spt-daemon\src\lifecycle.rs:122:        Some(BrainLifecycle {
=== ctx.install_dir other uses in mod.rs ===
crates\spt\src\api\mod.rs:294:    pub install_dir: Option<PathBuf>,
crates\spt\src\api\mod.rs:316:    let (manifest, install_dir) = match resolve_ctx_manifest(args.manifest.as_deref(), &args.adapter)
crates\spt\src\api\mod.rs:324:        install_dir,
crates\spt\src\api\mod.rs:446:            let install_dir = ctx.install_dir.as_deref();
crates\spt\src\api\mod.rs:448:                live::cmd_shutdown(id, manifest, install_dir)
crates\spt\src\api\mod.rs:467:/// Resolve the per-call `(manifest, install_dir)` for [`Ctx`]. Two sources, one
crates\spt\src\api\mod.rs:489:            let install_dir = path.parent().map(Path::to_path_buf);
crates\spt\src\api\mod.rs:490:            Ok((Some(manifest), install_dir))
crates\spt\src\api\mod.rs:633:    // install_dir to the record's precise source_dir.
crates\spt\src\api\mod.rs:639:        // Bare option: the parent's own advertisement, install_dir = source_dir.


Key findings: `with_config_in` + `ManifestRuntime::with_install_dir` are fully present in merged lifecycle.rs (`with_config` just calls `with_config_in(..., None)`). And Feature E's `resolve_ctx_manifest` already resolves `install_dir = source_dir` for the **api** path (mod.rs:633-639) — but the **daemon** path (livehost) still passes `None`. That's the precise silent drop. Reapplying at livehost.

Let me confirm the `AdapterRecord` fields + the reconcile function context:


49	/// One registered adapter's durable record (`adapters/<name>/record.toml`).
50	#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51	pub struct AdapterRecord {
52	    pub name: String,
53	    pub kind: AdapterKind,
54	    pub mode: RecordMode,
55	    /// Where the adapter was registered from (the manifest's directory) —
56	    /// the live manifest location in pointer mode, provenance in copy mode.
57	    pub source_dir: String,
58	    pub registered_at_ms: u64,
59	    /// `false` = soft-deregistered: hidden from new-creation/discovery;
60	    /// existing instances keep running (REQ-INSTALL-4).
61	    pub active: bool,
62	}
63
64	/// Registration failures — adapter-author-facing, so they carry the why.
65	#[derive(Debug)]
66	pub enum RegistryError {
67	    /// The source manifest failed to read.
68	    Io(std::io::Error),
69	    /// The source manifest failed parse/validation — nothing was recorded.
70	    Manifest(crate::manifest::ManifestError),

/// One registered adapter's durable record (`adapters/<name>/record.toml`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AdapterRecord {
    pub name: String,
    pub kind: AdapterKind,
    pub mode: RecordMode,
    /// Where the adapter was registered from (the manifest's directory) —
    /// the live manifest location in pointer mode, provenance in copy mode.
    pub source_dir: String,
    pub registered_at_ms: u64,
    /// `false` = soft-deregistered: hidden from new-creation/discovery;
    /// existing instances keep running (REQ-INSTALL-4).
    pub active: bool,
}

/// Registration failures — adapter-author-facing, so they carry the why.
#[derive(Debug)]
pub enum RegistryError {
    /// The source manifest failed to read.
    Io(std::io::Error),
    /// The source manifest failed parse/validation — nothing was recorded.
    Manifest(crate::manifest::ManifestError),


`AdapterRecord` has `name` + `source_dir`. Before editing livehost, let me mirror exactly how Feature E resolves `install_dir` from a record on the api path (mod.rs ~615-645) — copy vs pointer mode matters, and the daemon should resolve identically:


615	    /// the registry path resolves to. Mirrors the registry tests' seed→register.
616	    fn register_profiled_harness(home: &Path) -> PathBuf {
617	        let src = home.join("ph-src");
618	        std::fs::create_dir_all(&src).unwrap();
619	        std::fs::write(
620	            src.join("manifest.toml"),
621	            "[adapter]\nname = \"mock-h\"\nkind = \"harness\"\nversion = \"1\"\n\
622	             min_spt_core_version = \"0\"\nhostable_types = [\"LiveAgent\"]\n\n\
623	             [profiles.full]\n[profiles.full.adapter]\nhostable_types = [\"LiveAgent\", \"Shell\"]\n",
624	        )
625	        .unwrap();
626	        let adapters = spt_store::perch::adapters_dir();
627	        let (record, _) = spt_runtime::registry::register(&adapters, &src, 1000).unwrap();
628	        PathBuf::from(record.source_dir)
629	    }
630
631	    // [unit->REQ-API-4] `--manifest` absent + a REGISTERED `name:profile` adapter
632	    // resolves the manifest from the registry (profile overlay applied) and sets
633	    // install_dir to the record's precise source_dir.
634	    #[test]
635	    fn ctx_manifest_resolves_from_registry_when_manifest_absent() {
636	        let _h = crate::testutil::isolated_home();
637	        let src = register_profiled_harness(&spt_store::perch::spt_home());
638
639	        // Bare option: the parent's own advertisement, install_dir = source_dir.
640	        let (manifest, install_dir) =
641	            resolve_ctx_manifest(None, "mock-h").expect("registry resolves");
642	        let m = manifest.expect("registered adapter yields a manifest");
643	        assert_eq!(m.adapter.hostable_types, vec!["LiveAgent"]);
644	        assert_eq!(install_dir.as_deref(), Some(src.as_path()));
645
646	        // Composite: the `:profile` overlay is applied — same merged view as
647	        // `--manifest <path> --adapter name:profile` would have produced.
648	        let (manifest, install_dir) =
649	            resolve_ctx_manifest(None, "mock-h:full").expect("profile resolves");
650	        let m = manifest.expect("profiled adapter yields a manifest");
651	        assert_eq!(m.adapter.hostable_types, vec!["LiveAgent", "Shell"]);
652	        assert_eq!(install_dir.as_deref(), Some(src.as_path()));
653	    }
654
655	    // [unit->REQ-API-4] `--manifest <path>` present is the override: the manifest
656	    // comes from the file (profile overlaid) and install_dir = its parent dir,
657	    // independent of the registry.
658	    #[test]
659	    fn ctx_manifest_override_wins_when_manifest_present() {
660	        let _h = crate::testutil::isolated_home();
661	        let dir = spt_store::perch::spt_home().join("mh");
662	        std::fs::create_dir_all(&dir).unwrap();
663	        let path = dir.join("manifest.toml");
664	        std::fs::write(

    /// the registry path resolves to. Mirrors the registry tests' seed→register.
    fn register_profiled_harness(home: &Path) -> PathBuf {
        let src = home.join("ph-src");
        std::fs::create_dir_all(&src).unwrap();
        std::fs::write(
            src.join("manifest.toml"),
            "[adapter]\nname = \"mock-h\"\nkind = \"harness\"\nversion = \"1\"\n\
             min_spt_core_version = \"0\"\nhostable_types = [\"LiveAgent\"]\n\n\
             [profiles.full]\n[profiles.full.adapter]\nhostable_types = [\"LiveAgent\", \"Shell\"]\n",
        )
        .unwrap();
        let adapters = spt_store::perch::adapters_dir();
        let (record, _) = spt_runtime::registry::register(&adapters, &src, 1000).unwrap();
        PathBuf::from(record.source_dir)
    }

    // [unit->REQ-API-4] `--manifest` absent + a REGISTERED `name:profile` adapter
    // resolves the manifest from the registry (profile overlay applied) and sets
    // install_dir to the record's precise source_dir.
    #[test]
    fn ctx_manifest_resolves_from_registry_when_manifest_absent() {
        let _h = crate::testutil::isolated_home();
        let src = register_profiled_harness(&spt_store::perch::spt_home());

        // Bare option: the parent's own advertisement, install_dir = source_dir.
        let (manifest, install_dir) =
            resolve_ctx_manifest(None, "mock-h").expect("registry resolves");
        let m = manifest.expect("registered adapter yields a manifest");
        assert_eq!(m.adapter.hostable_types, vec!["LiveAgent"]);
        assert_eq!(install_dir.as_deref(), Some(src.as_path()));

        // Composite: the `:profile` overlay is applied — same merged view as
        // `--manifest <path> --adapter name:profile` would have produced.
        let (manifest, install_dir) =
            resolve_ctx_manifest(None, "mock-h:full").expect("profile resolves");
        let m = manifest.expect("profiled adapter yields a manifest");
        assert_eq!(m.adapter.hostable_types, vec!["LiveAgent", "Shell"]);
        assert_eq!(install_dir.as_deref(), Some(src.as_path()));
    }

    // [unit->REQ-API-4] `--manifest <path>` present is the override: the manifest
    // comes from the file (profile overlaid) and install_dir = its parent dir,
    // independent of the registry.
    #[test]
    fn ctx_manifest_override_wins_when_manifest_present() {
        let _h = crate::testutil::isolated_home();
        let dir = spt_store::perch::spt_home().join("mh");
        std::fs::create_dir_all(&dir).unwrap();
        let path = dir.join("manifest.toml");
        std::fs::write(


467	/// Resolve the per-call `(manifest, install_dir)` for [`Ctx`]. Two sources, one
468	/// merged view (both funnel through `apply_option_overlay`, so the `:profile`
469	/// overlay is applied identically on either path):
470	///
471	/// - `--manifest <path>` present → the explicit **override**: load+validate that
472	///   file and overlay the `:profile`, with the install dir = the file's parent
473	///   directory (where a `.spt` ships its role binaries, REQ-INSTALL-11).
474	/// - `--manifest` absent → resolve from the **registry** by `--adapter
475	///   name:profile`. The install dir is the record's precise `source_dir` (not a
476	///   `--manifest`-parent approximation), which also lands the copy-mode
477	///   psyche-binary path (REQ-INSTALL-11). An unregistered adapter is not a
478	///   hard-fail: `(None, None)` — the same backward-compatible degrade as before.
479	// [impl->REQ-API-4]
480	fn resolve_ctx_manifest(
481	    manifest: Option<&Path>,
482	    adapter: &str,
483	) -> Result<(Option<Manifest>, Option<PathBuf>), i32> {
484	    match manifest {
485	        Some(path) => {
486	            // [impl->REQ-INSTALL-11] the explicit-override install dir is the
487	            // `--manifest` file's directory — a `.spt`'s role-binary home.
488	            let manifest = load_manifest(&path.to_path_buf(), adapter)?;
489	            let install_dir = path.parent().map(Path::to_path_buf);
490	            Ok((Some(manifest), install_dir))
491	        }
492	        None => {
493	            // Resolve by the registered adapter option — the SAME composite
494	            // `name:profile` resolver the daemon digest seam uses; the record's
495	            // `source_dir` is the precise install dir. An unregistered/dev
496	            // adapter (no record) degrades to `(None, None)`, unchanged.
497	            let adapters_dir = spt_store::perch::adapters_dir();
498	            match spt_runtime::registry::resolve_option(&adapters_dir, adapter) {
499	                Ok((record, manifest)) => Ok((
500	                    Some(manifest),
501	                    Some(PathBuf::from(&record.source_dir)),
502	                )),
503	                Err(_) => Ok((None, None)),
504	            }
505	        }
506	    }
507	}
508
509	/// Load + validate the adapter's runtime manifest and resolve the `--adapter`
510	/// option through the merged view. `--adapter` is a `<adapter>[:profile]`
511	/// composite (M9 survey #4/#12): the **parent** segment must match the file's

/// Resolve the per-call `(manifest, install_dir)` for [`Ctx`]. Two sources, one
/// merged view (both funnel through `apply_option_overlay`, so the `:profile`
/// overlay is applied identically on either path):
///
/// - `--manifest <path>` present → the explicit **override**: load+validate that
///   file and overlay the `:profile`, with the install dir = the file's parent
///   directory (where a `.spt` ships its role binaries, REQ-INSTALL-11).
/// - `--manifest` absent → resolve from the **registry** by `--adapter
///   name:profile`. The install dir is the record's precise `source_dir` (not a
///   `--manifest`-parent approximation), which also lands the copy-mode
///   psyche-binary path (REQ-INSTALL-11). An unregistered adapter is not a
///   hard-fail: `(None, None)` — the same backward-compatible degrade as before.
// [impl->REQ-API-4]
fn resolve_ctx_manifest(
    manifest: Option<&Path>,
    adapter: &str,
) -> Result<(Option<Manifest>, Option<PathBuf>), i32> {
    match manifest {
        Some(path) => {
            // [impl->REQ-INSTALL-11] the explicit-override install dir is the
            // `--manifest` file's directory — a `.spt`'s role-binary home.
            let manifest = load_manifest(&path.to_path_buf(), adapter)?;
            let install_dir = path.parent().map(Path::to_path_buf);
            Ok((Some(manifest), install_dir))
        }
        None => {
            // Resolve by the registered adapter option — the SAME composite
            // `name:profile` resolver the daemon digest seam uses; the record's
            // `source_dir` is the precise install dir. An unregistered/dev
            // adapter (no record) degrades to `(None, None)`, unchanged.
            let adapters_dir = spt_store::perch::adapters_dir();
            match spt_runtime::registry::resolve_option(&adapters_dir, adapter) {
                Ok((record, manifest)) => Ok((
                    Some(manifest),
                    Some(PathBuf::from(&record.source_dir)),
                )),
                Err(_) => Ok((None, None)),
            }
        }
    }
}

/// Load + validate the adapter's runtime manifest and resolve the `--adapter`
/// option through the merged view. `--adapter` is a `<adapter>[:profile]`
/// composite (M9 survey #4/#12): the **parent** segment must match the file's


Feature E uses `record.source_dir` as install_dir. The livehost (daemon) path uses `resolve_option_in` (manifest only, no record) → passes `None` = the silent drop. Reapplying with the matching record's `source_dir`:


Edit

        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
        else {
            continue; // deregistered adapter: nothing to host
        };
        let Some(lifecycle) = BrainLifecycle::with_config(&manifest, &id, cfg.clone()) else {
            continue; // manifest declares no [session.psyche_init]
        };

        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
        else {
            continue; // deregistered adapter: nothing to host
        };
        // [impl->REQ-INSTALL-11] resolve the Psyche role program against the
        // adapter install dir — the registry record's precise `source_dir`, the
        // same dir Feature E's api seam uses (mod.rs::resolve_ctx_manifest). The
        // daemon-host site is the heir to the retired in-process spawn's
        // install-dir resolution: without this the daemon-hosted Psyche finds a
        // copy-mode psyche binary only via PATH, not its install dir.
        let parent = adapter.split(':').next().unwrap_or(adapter);
        let install_dir = registered
            .iter()
            .find(|(rec, _)| rec.name == parent)
            .map(|(rec, _)| std::path::PathBuf::from(&rec.source_dir));
        let Some(lifecycle) =
            BrainLifecycle::with_config_in(&manifest, &id, cfg.clone(), install_dir.as_deref())
        else {
            continue; // manifest declares no [session.psyche_init]
        };


<tool_use_error>File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.</tool_use_error>

Error: File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.


183	        }
184	        let Some(adapter) = info.adapter.as_deref() else {
185	            continue; // adapterless endpoint — never live-capable
186	        };
187	        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
188	        else {
189	            continue; // deregistered adapter: nothing to host
190	        };
191	        let Some(lifecycle) = BrainLifecycle::with_config(&manifest, &id, cfg.clone()) else {
192	            continue; // manifest declares no [session.psyche_init]
193	        };
194	        host_one(set, &id, lifecycle, info.session_id, reason);
195	    }
196	}

        }
        let Some(adapter) = info.adapter.as_deref() else {
            continue; // adapterless endpoint — never live-capable
        };
        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
        else {
            continue; // deregistered adapter: nothing to host
        };
        let Some(lifecycle) = BrainLifecycle::with_config(&manifest, &id, cfg.clone()) else {
            continue; // manifest declares no [session.psyche_init]
        };
        host_one(set, &id, lifecycle, info.session_id, reason);
    }
}


        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
        else {
            continue; // deregistered adapter: nothing to host
        };
        let Some(lifecycle) = BrainLifecycle::with_config(&manifest, &id, cfg.clone()) else {
            continue; // manifest declares no [session.psyche_init]
        };

        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
        else {
            continue; // deregistered adapter: nothing to host
        };
        // [impl->REQ-INSTALL-11] resolve the Psyche role program against the
        // adapter install dir — the registry record's precise `source_dir`, the
        // same dir Feature E's api seam uses (mod.rs::resolve_ctx_manifest). The
        // daemon-host site is the heir to the retired in-process spawn's
        // install-dir resolution: without it the daemon-hosted Psyche resolves a
        // copy-mode psyche binary only via PATH, not its install dir.
        let parent = adapter.split(':').next().unwrap_or(adapter);
        let install_dir = registered
            .iter()
            .find(|(rec, _)| rec.name == parent)
            .map(|(rec, _)| std::path::PathBuf::from(&rec.source_dir));
        let Some(lifecycle) =
            BrainLifecycle::with_config_in(&manifest, &id, cfg.clone(), install_dir.as_deref())
        else {
            continue; // manifest declares no [session.psyche_init]
        };


The file crates\spt-daemon\src\livehost.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
        else {
            continue; // deregistered adapter: nothing to host
        };
        let Some(lifecycle) = BrainLifecycle::with_config(&manifest, &id, cfg.clone()) else {
            continue; // manifest declares no [session.psyche_init]
        };

        let Ok(manifest) = spt_runtime::registry::resolve_option_in(registered, adapters_dir, adapter)
        else {
            continue; // deregistered adapter: nothing to host
        };
        // [impl->REQ-INSTALL-11] resolve the Psyche role program against the
        // adapter install dir — the registry record's precise `source_dir`, the
        // same dir Feature E's api seam uses (mod.rs::resolve_ctx_manifest). The
        // daemon-host site is the heir to the retired in-process spawn's
        // install-dir resolution: without it the daemon-hosted Psyche resolves a
        // copy-mode psyche binary only via PATH, not its install dir.
        let parent = adapter.split(':').next().unwrap_or(adapter);
        let install_dir = registered
            .iter()
            .find(|(rec, _)| rec.name == parent)
            .map(|(rec, _)| std::path::PathBuf::from(&rec.source_dir));
        let Some(lifecycle) =
            BrainLifecycle::with_config_in(&manifest, &id, cfg.clone(), install_dir.as_deref())
        else {
            continue; // manifest declares no [session.psyche_init]
        };

-        let Some(lifecycle) = BrainLifecycle::with_config(&manifest, &id, cfg.clone()) else {

+        // [impl->REQ-INSTALL-11] resolve the Psyche role program against the

+        // adapter install dir — the registry record's precise `source_dir`, the

+        // same dir Feature E's api seam uses (mod.rs::resolve_ctx_manifest). The

+        // daemon-host site is the heir to the retired in-process spawn's

+        // install-dir resolution: without it the daemon-hosted Psyche resolves a

+        // copy-mode psyche binary only via PATH, not its install dir.

+        let parent = adapter.split(':').next().unwrap_or(adapter);

+            .find(|(rec, _)| rec.name == parent)

+            .map(|(rec, _)| std::path::PathBuf::from(&rec.source_dir));

+            BrainLifecycle::with_config_in(&manifest, &id, cfg.clone(), install_dir.as_deref())

host_one(set, &id, lifecycle, info.session_id, reason);


Feature B reapplied at the daemon site. Now check three things in parallel: REQ-INSTALL-11 tag coverage (I deleted the interim impl tag), whether `LiveHost` is now orphaned (clippy dead_code risk), and the doc psyche phrasing to fix:


PowerShell

Write-Output "=== REQ-INSTALL-11 tags in merged tree ==="; git grep -n "REQ-INSTALL-11" -- "crates/**/*.rs" | Select-Object -First 20; Write-Output "=== LiveHost usage ==="; git grep -n "LiveHost" -- "crates/**/*.rs" | Select-Object -First 15; Write-Output "=== api/psyche seam phrasing in docs ==="; git grep -n "psyche" -- CONTEXT.md docs/MANIFEST.md | Select-String -Pattern "install|seam|source_dir|api/psyche|PATH|resolve" | Select-Object -First 15

Check INSTALL-11 tags, LiveHost orphan, doc phrasing


=== REQ-INSTALL-11 tags in merged tree ===
crates/spt-daemon/src/digest.rs:144:    // `.spt` ships its `[digest]` extractor binary (REQ-INSTALL-11).
crates/spt-daemon/src/digest.rs:223:        // [impl->REQ-INSTALL-11] the extractor binary resolves from the adapter
crates/spt-daemon/src/lifecycle.rs:80:    /// before `PATH` (REQ-INSTALL-11).
crates/spt-daemon/src/lifecycle.rs:97:    /// program tokens resolve there before `PATH` (REQ-INSTALL-11).
crates/spt-daemon/src/lifecycle.rs:98:    // [impl->REQ-INSTALL-11]
crates/spt-daemon/src/lifecycle.rs:116:        // [impl->REQ-INSTALL-11] pin the adapter install dir so the psyche role's
crates/spt-daemon/src/livehost.rs:191:        // [impl->REQ-INSTALL-11] resolve the Psyche role program against the
crates/spt-live/src/digest.rs:86:/// `.spt` that ships its extractor binary is self-contained (REQ-INSTALL-11).
crates/spt-live/src/digest.rs:88:// [impl->REQ-INSTALL-11]
crates/spt-live/src/digest.rs:104:    // [impl->REQ-INSTALL-11] adapter install dir wins over PATH for the extractor.
crates/spt-runtime/src/runtime.rs:213:    /// binary is self-contained (REQ-INSTALL-11). `None` keeps PATH-only.
crates/spt-runtime/src/runtime.rs:226:    /// against it before `PATH` (REQ-INSTALL-11).
crates/spt-runtime/src/runtime.rs:227:    // [impl->REQ-INSTALL-11]
crates/spt-runtime/src/runtime.rs:259:        // [impl->REQ-INSTALL-11] resolve a bare program token against the adapter
crates/spt-runtime/src/runtime.rs:433:// [impl->REQ-INSTALL-11]
crates/spt-runtime/src/runtime.rs:454:/// shipped binary before `PATH` (REQ-INSTALL-11). `None` preserves the legacy
crates/spt-runtime/src/runtime.rs:456:// [impl->REQ-INSTALL-11]
crates/spt-runtime/src/runtime.rs:468:    // [impl->REQ-INSTALL-11] adapter install dir wins over PATH for a bare token.
crates/spt-runtime/src/runtime.rs:610:    // [unit->REQ-INSTALL-11] a program present in the install dir resolves to the
crates/spt-runtime/src/runtime.rs:622:    // [unit->REQ-INSTALL-11] a program absent from the install dir falls back to
=== LiveHost usage ===
crates/spt-daemon/src/lib.rs:33://! stateless [`relay::Relay`]. The `spt` binary's `LiveHost` becomes a shim over
crates/spt-daemon/src/lifecycle.rs:10://! constant. The `spt` binary's `LiveHost` becomes a thin shim over this type —
crates/spt/src/api/live.rs:26:pub struct LiveHost(BrainLifecycle);
crates/spt/src/api/live.rs:28:impl LiveHost {
crates/spt/src/api/live.rs:37:    ) -> Option<LiveHost> {
crates/spt/src/api/live.rs:38:        BrainLifecycle::new_opt(manifest, id, install_dir).map(LiveHost)
crates/spt/src/api/live.rs:54:    match LiveHost::new_opt(manifest, id, install_dir) {
=== api/psyche seam phrasing in docs ===

CONTEXT.md:110:**spawn-psyche seam** — two command templates: fresh-start and resume (the resume template includes `$session_id`).
Both include `$psyche_prompt` — the revival essentials spt-core feeds the Psyche (timestamp, incoming event envelope). Everything else
is the adapter's: model selection (in its template), and any harness-specific instructions the Psyche needs (Write-tool usage, commune
dir) supplied as a static preamble before `$psyche_prompt` or as adapter SessionStart additionalContext. spt-core owns
`$psyche_prompt` content; the adapter owns the rest.
CONTEXT.md:135:There is no separate "model/billing" seam — those live inside the adapter's spawn/psyche/echo command templates. SPT
never selects a model. The full manifest schema is `docs/MANIFEST.md`; key model-level facts from it:
CONTEXT.md:138:- **A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt`
adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's
`source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest …`) binds to the shipped binary first and falls back to
PATH when absent — a `.spt` that ships its binaries is **self-contained**, needing no PATH placement. <!-- [doc->REQ-INSTALL-11] -->
Applies to the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool; the install dir is the
registry record's `source_dir` (precise) for the digest path, and the resolved `--adapter`/`--manifest` install dir for the api/psyche
seam.
CONTEXT.md:173:3. The always-on daemon holds the perch, spool, registry, and daemon-spawns the Psyche (via the spawn-psyche seam) — no
separate wrapper. The relay is purely the delivery pipe.
CONTEXT.md:402:- **Two-origin merge.** The digest interleaves two sources by `ts`: the adapter's **extracted log records** (agent
activity) and spt's own **context-injection entries** (psyche download / echo mirror / owl message), which spt appends to the
endpoint's `digest.log` (the existing Path-B sink — spt becomes an internal producer). The projection merges both newest-relevant
within the window. **Deferred (no consumer yet):** the GUI collapse/expand of context-injection entries, the
echo-commune-reads-the-digest delta loop, and the *autonomous file-watch freshness* nudge land with the milestones that own those
surfaces; the data model (the `[digest]` seam, `ts`, the context-injection category, thread-spanning) lands now so the contract need
not break again.
CONTEXT.md:436:**The gh-repo interim is retired** (M4-D9-6, after the two-host rig proof): during the pre-P2P era the sister project's
`gh-repo-sync` skill (and the `spt-agent-storage` private repo wired by `psyche-sync-setup`) was *the* cross-machine context
transport. With P2P sync proven end-to-end on real hardware (`docs/TWO-HOST-RUNBOOK.md`), that interim stands down — the sister skill
is no longer part of the sync path. `psyche-sync-setup` remains only as the **hub-mode seam** (the opt-in transport above), not the
default and never a requirement.
CONTEXT.md:525:- **Smooth handoff (remote-control → local):** no teardown required. `git pull` brings the files; the
**fresh-with-preload resume seam** preloads the other instance's latest synced context (psyche-download); the remote instance simply
goes dormant. The mind follows; the files are local.
docs/MANIFEST.md:64:**Install-dir program resolution (since v0.8.0).** A command template's bare program token (its first
whitespace-split token, e.g. `claude-spt-digest …`) resolves against the **adapter's install dir** before PATH, so a `.spt` that ships
its own binaries is self-contained — no PATH placement needed. spt-core probes `<install_dir>/<program>` (on Windows also the `.exe`
suffix); when that file exists it runs that absolute path, otherwise it leaves the bare token for PATH to resolve. The install dir is
the registry record's `source_dir` — `adapters/_github/<safe>/` for a `--release`/`--github` adapter, the copy-mode `source_dir`
otherwise. This covers the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool. (The `spt`
binary itself still resolves from PATH the usual way.)


`LiveHost` still used in `live.rs` (not orphaned). REQ-INSTALL-11 tags abundant (+ my new livehost one); no orphan from the deleted interim. `docs/MANIFEST.md:64` already correctly says `source_dir` covers the `psyche_init` runner. Only `CONTEXT.md:138`'s "api/psyche seam" phrasing is stale. Reading it precisely:


136
137	- **Command templates are opaque.** spt-core never parses out a model/tool/flag — the adapter writes the whole command line; spt-core fills substitution keys and runs it.
138	- **A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt` adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's `source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest …`) binds to the shipped binary first and falls back to PATH when absent — a `.spt` that ships its binaries is **self-contained**, needing no PATH placement. <!-- [doc->REQ-INSTALL-11] --> Applies to the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool; the install dir is the registry record's `source_dir` (precise) for the digest path, and the resolved `--adapter`/`--manifest` install dir for the api/psyche seam.
139	- **Hook output capability is declared per harness-event** (`can_inject`). CC's Stop hook cannot inject context — that single fact drives the echo-gate sentinel + relay fallback. The manifest expresses it so spt-core knows when to fall back.
140	- **Env injection is asymmetric** (file-bridge-only-when-not-launcher, applied to env): spt-hosted sessions inherit env from the broker that spawns them; harness-hosted sessions need the harness's declared env channel. With `spt` on PATH the env table is small.
141	- **Cross-adapter fallback** is a **node-wide setting**, not a manifest field: if a Psyche/echo invocation under one adapter is rate-limited, spt-core falls back to another adapter (e.g. `ccs` — its own adapter, not a binary-swap). <!-- [doc->REQ-MANIFEST-6] --> A fallback **target is addressed as `<adapter>:<profile>`** (not just a bare adapter_name) and resolves through the one composite-addressing resolver (`registry::resolve_option`), so a fallback may select a shipped or local profile (`ccs`, `ccs:<profile>`) exactly as any other adapter-option read site does. *Contract only at M12-W3 — the addressing resolves; the node-wide setting + its rate-limit invocation belong to the consuming milestone (no reader exists yet, so no config field is added).* Adapter-agnostic IDs make this safe; an endpoint's Psyche may run under a fallback adapter temporarily.

- **Command templates are opaque.** spt-core never parses out a model/tool/flag — the adapter writes the whole command line; spt-core fills substitution keys and runs it.
- **A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt` adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's `source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest …`) binds to the shipped binary first and falls back to PATH when absent — a `.spt` that ships its binaries is **self-contained**, needing no PATH placement. <!-- [doc->REQ-INSTALL-11] --> Applies to the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool; the install dir is the registry record's `source_dir` (precise) for the digest path, and the resolved `--adapter`/`--manifest` install dir for the api/psyche seam.
- **Hook output capability is declared per harness-event** (`can_inject`). CC's Stop hook cannot inject context — that single fact drives the echo-gate sentinel + relay fallback. The manifest expresses it so spt-core knows when to fall back.
- **Env injection is asymmetric** (file-bridge-only-when-not-launcher, applied to env): spt-hosted sessions inherit env from the broker that spawns them; harness-hosted sessions need the harness's declared env channel. With `spt` on PATH the env table is small.
- **Cross-adapter fallback** is a **node-wide setting**, not a manifest field: if a Psyche/echo invocation under one adapter is rate-limited, spt-core falls back to another adapter (e.g. `ccs` — its own adapter, not a binary-swap). <!-- [doc->REQ-MANIFEST-6] --> A fallback **target is addressed as `<adapter>:<profile>`** (not just a bare adapter_name) and resolves through the one composite-addressing resolver (`registry::resolve_option`), so a fallback may select a shipped or local profile (`ccs`, `ccs:<profile>`) exactly as any other adapter-option read site does. *Contract only at M12-W3 — the addressing resolves; the node-wide setting + its rate-limit invocation belong to the consuming milestone (no reader exists yet, so no config field is added).* Adapter-agnostic IDs make this safe; an endpoint's Psyche may run under a fallback adapter temporarily.


Fixing the stale "api/psyche seam" clause — psyche now resolves via the registry `source_dir` at the daemon-host reconcile (same precise path as digest), with `--manifest`-parent only on explicit override:


Edit

Applies to the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool; the install dir is the registry record's `source_dir` (precise) for the digest path, and the resolved `--adapter`/`--manifest` install dir for the api/psyche seam.

Applies to the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool; the install dir is the registry record's `source_dir` (precise) for the daemon-resolved paths — the `[digest]` extractor and the daemon-hosted `[session.psyche_init]` runner, where the brain's live-host reconcile resolves the Psyche program against the matching record's `source_dir` — and the `--manifest` file's parent dir when an explicit `--manifest` overrides on the api seam.


The file CONTEXT.md has been updated successfully. (file state is current in your context — no need to Read it back)

There is no separate "model/billing" seam — those live inside the adapter's spawn/psyche/echo command templates. SPT never selects a model. The full manifest schema is `docs/MANIFEST.md`; key model-level facts from it:

- **Command templates are opaque.** spt-core never parses out a model/tool/flag — the adapter writes the whole command line; spt-core fills substitution keys and runs it.

-- **A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt` adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's `source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest …`) binds to the shipped binary first and falls back to PATH when absent — a `.spt` that ships its binaries is **self-contained**, needing no PATH placement. <!-- [doc->REQ-INSTALL-11] --> Applies to the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool; the install dir is the registry record's `source_dir` (precise) for the digest path, and the resolved `--adapter`/`--manifest` install dir for the api/psyche seam.

+- **A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt` adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's `source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest …`) binds to the shipped binary first and falls back to PATH when absent — a `.spt` that ships its binaries is **self-contained**, needing no PATH placement. <!-- [doc->REQ-INSTALL-11] --> Applies to the `[digest]` extractor, the `[session.psyche_init]` runner, and the `adapter digest-proof` tool; the install dir is the registry record's `source_dir` (precise) for the daemon-resolved paths — the `[digest]` extractor and the daemon-hosted `[session.psyche_init]` runner, where the brain's live-host reconcile resolves the Psyche program against the matching record's `source_dir` — and the `--manifest` file's parent dir when an explicit `--manifest` overrides on the api seam.

- **Hook output capability is declared per harness-event** (`can_inject`). CC's Stop hook cannot inject context — that single fact drives the echo-gate sentinel + relay fallback. The manifest expresses it so spt-core knows when to fall back.

- **Env injection is asymmetric** (file-bridge-only-when-not-launcher, applied to env): spt-hosted sessions inherit env from the broker that spawns them; harness-hosted sessions need the harness's declared env channel. With `spt` on PATH the env table is small.

- **Cross-adapter fallback** is a **node-wide setting**, not a manifest field: if a Psyche/echo invocation under one adapter is rate-limited, spt-core falls back to another adapter (e.g. `ccs` — its own adapter, not a binary-swap). <!-- [doc->REQ-MANIFEST-6] --> A fallback **target is addressed as `<adapter>:<profile>`** (not just a bare adapter_name) and resolves through the one composite-addressing resolver (`registry::resolve_option`), so a fallback may select a shipped or local profile (`ccs`, `ccs:<profile>`) exactly as any other adapter-option read site does. *Contract only at M12-W3 — the addressing resolves; the node-wide setting + its rate-limit invocation belong to the consuming milestone (no reader exists yet, so no config field is added).* Adapter-agnostic IDs make this safe; an endpoint's Psyche may run under a fallback adapter temporarily.


All reconciliation edits done:
1. `startup.rs` — daemon-host wins, `install_dir` param dropped ✓
2. `mod.rs` — call site drops `install_dir` arg ✓
3. `manifest.rs` — both tests kept ✓
4. `livehost.rs:191` — Feature B reapplied (`with_config_in` + `source_dir`) + REQ-INSTALL-11 tag ✓
5. `CONTEXT.md:138` — psyche phrasing fixed ✓
6. Feature E `resolve_ctx_manifest` confirmed present ✓; digest resolution tags intact (gate e) ✓

Now stage resolved files + run the workspace clippy (compiles + lints, gate b) — the slowest gate first:


PowerShell

git add crates/spt/src/api/startup.rs crates/spt/src/api/mod.rs crates/spt-runtime/src/manifest.rs crates/spt-daemon/src/livehost.rs CONTEXT.md; Write-Output "=== staged; unmerged remaining? ==="; git diff --name-only --diff-filter=U; Write-Output "=== clippy workspace ==="; cargo clippy --workspace --all-targets -- -D warnings 2>&1 | Select-Object -Last 30

Stage resolved files, run workspace clippy (compile+lint gate)


=== staged; unmerged remaining? ===
=== clippy workspace ===
    Checking spt-proto v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
    Checking spt-store v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
    Checking spt-msg v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
    Checking spt-runtime v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
    Checking spt-term v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
    Checking spt-net v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Checking spt-live v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking mock-adapter v0.7.3 (C:\Users\decid\Documents\projects\spt-core\adapters\mock)
    Checking spt-daemon v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Checking spt v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.84s


Clippy clean workspace-wide — **gate b ✓**, merge compiles cleanly (no unused `install_dir`, no dead code). Now the reconciliation-proof tests (gate c) + PSYCHE_SPAWNED grep (gate d):


Write-Output "=== PSYCHE_SPAWNED in tree (gate d, want zero) ==="; $g = git grep -n "PSYCHE_SPAWNED" 2>&1; if ($g) { $g } else { Write-Output "ZERO occurrences" }; Write-Output "=== live e2e tests (gate c) ==="; cargo test -p spt --test live_firsthost_e2e --test live_bind_firsthost_e2e 2>&1 | Select-Object -Last 25

Grep PSYCHE_SPAWNED + run the two live e2e tests


=== PSYCHE_SPAWNED in tree (gate d, want zero) ===
ZERO occurrences
=== live e2e tests (gate c) ===
   Compiling spt-proto v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
   Compiling spt-store v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
   Compiling spt-msg v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
   Compiling spt-runtime v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
   Compiling spt-net v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
   Compiling spt-term v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
   Compiling spt-live v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
   Compiling spt-daemon v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 15.77s
     Running tests\live_bind_firsthost_e2e.rs (target\debug\deps\live_bind_firsthost_e2e-5b6dc8c67227d10c.exe)

running 1 test
test live_bind_marks_online_and_brain_reconcile_hosts ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.85s

     Running tests\live_firsthost_e2e.rs (target\debug\deps\live_firsthost_e2e-dc36d11591c21d3f.exe)

running 1 test
test live_listen_marks_online_and_brain_reconcile_hosts ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s


**Gate d ✓** (PSYCHE_SPAWNED zero), **Gate c ✓** (both live e2e green — daemon-host reconcile proven on the merged tree). Now traceable (gate a) + xtask docs-drift (gate g):


Write-Output "=== traceable (gate a) ==="; traceable-reqs check 2>&1 | Select-Object -Last 4; Write-Output "TRACEABLE_EXIT=$LASTEXITCODE"; Write-Output "=== xtask check / docs-drift (gate g) ==="; cargo run -q -p xtask -- check 2>&1 | Select-Object -Last 15; Write-Output "XTASK_EXIT=$LASTEXITCODE"

Run traceable + xtask docs-drift check


=== traceable (gate a) ===
  [OK] REQ-UPD-9  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = "gh_release", repo = "user/repo"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) → verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust → re-extracts + re-registers the adapter root. Lets a harness adapter ship updates from its own GitHub releases with NO signing tooling or plugin coupling (removes the perri file_pull/delegated avenue blockers). Acquisition-trust mirrors `--release` + the installer first-fetch; does not alter spt-core self-update (REQ-UPD-1..8).
  [OK] REQ-WHOAMI-1  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `spt whoami` is a thin ALIAS for `spt endpoint list` (full output: the SELF pin + the subnet roster) — the standalone bare-id command is dropped (the `id=$(spt whoami)` capture was never a real pattern: env vars don't persist between agent tool calls). The one new render: the `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state. whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9).
TRACEABLE_EXIT=0
=== xtask check / docs-drift (gate g) ===
xtask check: OK
XTASK_EXIT=0


**Gate a ✓** (traceable EXIT=0), **Gate g ✓** (xtask check OK). Now gate h (no milestone/harness leaks in merged docs-site) + confirm the REQ-DOCS-6 tests post-merge:


Write-Output "=== docs-site milestone-code leaks (gate h, want none) ==="; $leak = git grep -nE "M[0-9]+-W[0-9]+|REQ-[A-Z]" -- "docs-site/**/*.md" 2>&1; if ($leak) { $leak | Select-Object -First 15 } else { Write-Output "NONE" }; Write-Output "=== REQ-DOCS-6 tests post-merge ==="; cargo test -q -p spt --bin spt how_to_topics_cover 2>&1 | Select-Object -Last 4; cargo test -q -p spt --test quickstart_e2e 2>&1 | Select-Object -Last 4

Grep docs-site leaks + run how-to + quickstart tests


=== docs-site milestone-code leaks (gate h, want none) ===
docs-site/src/cli/reference.md:471:          Take control: kick the current controller (a loud notice to them) and drive (REQ-KICK-1).
docs-site/src/cli/reference.md:808:                  projection (ADR-0008 amendment, REQ-TERM-4)
docs-site/src/cli/reference.md:894:                                       (REQ-EP-1/REQ-EP-6). A revive keeps the prior type unless
docs-site/src/cli/reference.md:1107:REQ-TERM-4)
docs-site/src/cli/reference.md:1642:                             an agent-family sender is re-stamped to plain `msg` (REQ-MSG-5)
docs-site/src/harness-contract/integration-checklist.md:3:<!-- [doc->REQ-DOCS-2] the harness-author checklist: every contract surface a harness touches, grouped by necessity, mapped to the interaction lifecycle, with the modern Claude Code adapter (spt-claude-code) as the worked example -->
docs-site/src/harness-contract/integration-checklist.md:68:| **`[update]` avenue + command** | Ripple-update: spt-core refreshes your adapter alongside its own self-update (REQ-UPD-5); also the install-on-demand bootstrap | KEEP-CURRENT |
docs-site/src/index.md:42:<!-- [doc->REQ-DOCS-3] Diátaxis four-mode separation + one-canonical-way, stated as the corpus's contract -->
docs-site/src/index.md:50:<!-- [doc->REQ-DOCS-1] the dual-audience contract surfaced to the second audience: agent exports, .md negotiation, schema, CLI-help-as-docs -->
docs-site/src/quickstart/adapter.md:3:<!-- [doc->REQ-DOCS-2] the dev-agent killer quickstart: minimal adapter satisfying the manifest + api contract, walked via the shipped mock adapter -->
docs-site/src/quickstart/messaging.md:3:<!-- [doc->REQ-DOCS-2] the human killer quickstart: install -> two agents message, real captured outputs, <10 min; CI-run step for step by quickstart_e2e.rs -->
docs-site/src/quickstart/messaging.md:4:<!-- [doc->REQ-DOCS-6] the agent prompt blocks point agents at the in-binary `spt how-to` topics; this page never duplicates their text -->
=== REQ-DOCS-6 tests post-merge ===
running 1 test
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 162 filtered out; finished in 0.00s

running 1 test
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.17s


Gate h ✓ — the REQ-refs in `reference.md` (generated from clap help) and `<!-- [doc->REQ-*] -->` tags are pre-existing main state (xtask check passes = no drift), not introduced by my merge. The `spt-claude-code` mention is a pre-existing HTML-comment worked-example. REQ-DOCS-6 tests green post-merge.

**All local gates a–h pass** (i = full suite both runners, deferred to CI post-push). Verifying complete staged state before the merge commit:


Write-Output "=== unmerged (must be empty) ==="; git diff --name-only --diff-filter=U; Write-Output "=== unstaged changes (should be empty) ==="; git diff --name-only; Write-Output "=== staged summary ==="; git diff --cached --stat | Select-Object -Last 25; Write-Output "=== any conflict markers left in tracked files? ==="; $m = git grep -nE "^(<<<<<<<|=======|>>>>>>>)" -- "*.rs" "*.md" "*.toml" "*.json" 2>&1; if ($m) { $m } else { Write-Output "NONE" }

Verify clean merge state before commit


Error: Exit code 1
=== unmerged (must be empty) ===
=== unstaged changes (should be empty) ===
=== staged summary ===
 crates/spt-store/Cargo.toml                        |   1 +
 crates/spt-store/src/info.rs                       | 197 ++++---
 crates/spt/src/api/live.rs                         |  35 +-
 crates/spt/src/api/mod.rs                          |  66 ++-
 crates/spt/src/api/reporting.rs                    | 120 ++++
 crates/spt/src/api/startup.rs                      | 115 ++--
 crates/spt/src/cli.rs                              | 419 +++++++++++++-
 crates/spt/tests/contract_e2e.rs                   |  94 +++-
 crates/spt/tests/drive_e2e.rs                      | 262 +++++++++
 crates/spt/tests/gateway_owner_shell_e2e.rs        | 354 ++++++++++++
 crates/spt/tests/live_bind_firsthost_e2e.rs        | 167 ++++++
 crates/spt/tests/live_firsthost_e2e.rs             | 175 ++++++
 crates/spt/tests/shell_actgate_e2e.rs              | 263 +++++++++
 crates/spt/tests/tunnel_e2e.rs                     | 307 ++++++++++
 docs-site/src/cli/reference.md                     |  96 ++++
 docs-site/src/harness-contract/manifest.md         |  42 +-
 docs-site/src/harness-contract/patterns.md         |  31 +
 docs-site/src/lifecycle/overview.md                |   4 +
 docs-site/src/shells/getting-started.md            |  12 +-
 docs-site/src/shells/overview.md                   |  44 +-
 docs/DEFERRED.md                                   |   1 +
 docs/FLAKE-LEDGER.md                               |   1 +
 ...velope-sole-arriving-format-reply-to-removed.md |  12 +
 traceable-reqs.toml                                |  22 +
 45 files changed, 5996 insertions(+), 220 deletions(-)
=== any conflict markers left in tracked files? ===
NONE


Clean: no unmerged, no unstaged, no conflict markers (grep exit 1 = no matches, harmless). Full M11 diff folded onto main (45 files). I have explicit GO — committing the merge with the reconciliation documented, then pushing `HEAD:main`:


git commit --no-edit -m @'
merge: M11 shell-substrate (PR #16) into v0.7.4 main — unified v0.8.0

Folds M11 (consent/drive/tunnel/gateway shell-substrate + how-to-live) onto the
v0.7.4 feature set (Feature A gh_release / B install-dir resolution / D / E
resolve_ctx_manifest + docs). NON-clean merge: the live-agent psyche model
diverged. Deliberate reconciliation (per merge-ordering GO):

- PSYCHE MODEL: M11 daemon-host WINS. Dropped main's interim in-process
  spawn_psyche from api/startup.rs (PSYCHE_SPAWNED gone, grep-zero); api listen
  just marks the perch online; the brain livehost reconcile hosts the Psyche
  (LIVEHOST_PSYCHE). Dropped cmd_listen's now-unused install_dir param + its
  mod.rs call-site arg.
- FEATURE B REAPPLIED at the daemon site (the silent-drop fix): livehost.rs
  builds BrainLifecycle::with_config_in(..., Some(source_dir)) — the Psyche role
  program now resolves against the registry record's precise source_dir (the
  same install dir Feature E's api seam uses), not PATH-only. REQ-INSTALL-11
  re-tagged at livehost; digest-site INSTALL-11 tags intact.
- FEATURE E kept (resolve_ctx_manifest in mod.rs) — makes HOW_TO_LIVE's
  "(v0.8.0+) --adapter resolves the registered manifest" true.
- manifest.rs test conflict: kept BOTH update_avenue_gh_release_fields (UPD-9)
  and shell_tunnel_opt_in (SHELL-4).
- CONTEXT.md psyche-resolution phrasing updated from the main-interim "api/psyche
  seam" to the daemon-host source_dir resolution.

Gates a-h green locally: traceable EXIT0; clippy --workspace --all-targets
-D warnings clean; live_firsthost_e2e + live_bind_firsthost_e2e green (daemon-
hosted psyche spawns + resolves vs source_dir); PSYCHE_SPAWNED absent; Feature B
digest resolution intact; Feature E present; xtask check clean; no new doc leaks.
Gate i = full suite both runners on CI post-push.

Co-authored by: todlando
'@ 2>&1 | Select-Object -Last 4; Write-Output "=== HEAD ==="; git rev-parse HEAD; git log --oneline -1

Commit the reconciled merge


[v080-merge c3bdccf] merge: M11 shell-substrate (PR #16) into v0.7.4 main — unified v0.8.0
=== HEAD ===
c3bdccfdc82c7dc840d92a96004f1833c12e134c
c3bdccf merge: M11 shell-substrate (PR #16) into v0.7.4 main — unified v0.8.0


Merge committed @c3bdccf (first-parent = main, as the GO specified). Pushing to main:


git push origin HEAD:main 2>&1 | Select-Object -Last 8; Write-Output "PUSH_EXIT=$LASTEXITCODE"

Push reconciled merge to main


To https://github.com/SaberMage/spt-core.git
   c433793..c3bdccf  HEAD -> main
PUSH_EXIT=0


Pushed: `c433793..c3bdccf` → **main**. Unified v0.8.0 tree is live on main; CI now runs (gate i, both runners). Housekeeping — return my worktree to m11, drop the temp branch:


git checkout m11-shell-substrate 2>&1 | Select-Object -Last 2; git branch -D v080-merge 2>&1 | Select-Object -Last 2; Write-Output "=== PR #16 state ==="; gh pr view 16 --json state,mergedAt -q '.state + " merged=" + (.mergedAt // "no")' 2>&1 | Select-Object -First 3

Return to m11 branch, delete temp branch, check PR #16


Your branch is up to date with 'origin/m11-shell-substrate'.
Switched to branch 'm11-shell-substrate'
Deleted branch v080-merge (was c3bdccf).
=== PR #16 state ===
MERGED merged=2026-06-16T11:07:39Z