---
name: v091-picker-status
description: "v0.9.1 picker 4-state status (amber harness-only) + project_history loader fix — doyle dispatch, todlando exec"
metadata: 
  node_type: memory
  type: project
  originSessionId: 5e7350bf-0514-40db-9112-edf4b5a5b740
---

**DONE 2026-06-17: PR #23 (base main) PR-READY, MERGEABLE, CI in-flight, doyle pinged to gate.** All REQ-PICKER-1..5 [impl,unit], traceable EXIT=0. Commits: 969b06e PICKER-1 · 7bb83f2 PICKER-2/3/4(v1 picker-local map) · 9e5cb8b PICKER-4 REWORK(DRY ResourceRow) + PICKER-5. PICKER-2=BranchStore::branches_by_recency (new read-only method) loads p-<project> branch history (was empty read_dir over bare working tree). PICKER-3=reconcile_self_owned (roster overrides stale subnet square). PICKER-4=node_label threaded onto ResourceRow in resource_projection + canonical node_label_display(node,label) beside key_prefix, reused by picker+endpoint-list. PICKER-5(=doyle endpoint-list ISSUE-1)=pure format_subnet_rows char-width pad (kills \t ragged status) + label + --local hint(ISSUE-2b). ISSUE-2a NOT escalated (--local shows wall-a registered+alive → local reg intact; wall-b exited w/ crashed rc). QUEUED for doyle to mint: Finding C = rc severed-stream-EOF (rc.rs:352-362 hard-fails non-WouldBlock/TimedOut incl UnexpectedEof → RC_FAIL crashes PTY; same EOF family as F1-seed/listener; wants shared classify-EOF helper + graceful rc + auto-reattach) + Finding A deferred-manifest. NEXT: monitor CI green, await doyle GATE-PASS → deployah publishes v0.10.0 (MINOR, counter 23). docs-drift: no clap surface touched; xtask gen blocked locally by live daemon holding target/debug/spt.exe (shared runner, no kill) — CI confirms.

doyle DISPATCH 2026-06-17 (operator-requested). **STATUS: branch v0.10.0-picker-status off post-v0.9.1 main (@7f93ecd, Cargo 0.9.1) CREATED + pushed; base @577300d minted REQ-PICKER-1..4 registry-first (required_stages=[], traceable EXIT=0); plan + REQ ids PINGED to doyle (he gates the plan).** NEXT = BUILD (paused here for context — base + plan delivered, impl is a large 4-REQ multi-file seam best with fresh context). v0.9.0 + v0.9.1 BOTH SHIPPED this session ([[v090-harness-resolution]], [[v091-resolve-dotstem]]). Self-drive, doyle gates, PR base main.

**PROGRESS: PICKER-1 DONE + committed @969b06e (pushed branch v0.10.0-picker-status, base @577300d minted 4 REQs).** PICKER-1 = InfoJson.controllable Option<bool> (info.rs, serde-default) stamped in establish_perch (startup.rs: controllable param + carry-forward; bind_from_seed→Some(false); cmd_bind→Some(true) if endpoint_type=="live_agent" else None) + EpDisplay 4-state enum (model.rs, type-gated display_status method) + square_span colour + AMBER=Rgb(255,176,0) + STATUS line in description_pane (view.rs) + EndpointRow gained controllable+endpoint_type (data.rs local_rows reads info.json; subnet_rows=None/""). Tests: controllable_is_additive (info), establish_stamps_controllable_by_hosting_mode (startup), display_status_four_state_type_gated (model), pick_status_line_four_state (view). REQ-PICKER-1 [impl,unit] active, traceable EXIT=0. BINDING SEAM SWEEP all green (clippy workspace; live_resolve/firsthost/bind_firsthost/contract/quickstart/livehost_bootrace + daemon_lifecycle_real_brain + gateway/poll_envelope). 

**REMAINING (resume here, all data.rs picker-only, NO shared-seam risk): PICKER-2/3/4 still required_stages=[].**
- PICKER-4 (smallest, do first): subnet node_label. data.rs subnet_rows uses raw row.node → bare key-hex. Thread node_label: ResourceRow (spt-net registry.rs:331 resource_projection) DROPS it — add node_label to ResourceRow OR look up reg.node_labels() in subnet_rows; render canonical format!("{l} ({}…)", key_prefix) (copy from cli.rs:4011 / wansend.rs:495-505). Then subnet_rows group/node uses it. unit: a subnet row renders LABEL (keyprefix…) not bare hex.
- PICKER-3: self-owned subnet status reconcile. data.rs gather_endpoints() — after local_rows+subnet_rows, a reconcile pass: build a map id→roster status from local (is_local) rows; for each subnet row whose id ∈ that map, OVERRIDE its status (+controllable+endpoint_type if useful) with the local. Keep both listings. unit: dual-listed self-owned endpoint, stale snapshot disagrees → both render roster status.
- PICKER-2 (investigation-heaviest, do last): project_history via git BranchStore. data.rs project_history_for(id) reads raw fs::read_dir(tracked_dir()/projects) on the BARE working tree (empty). FIX: enumerate via BranchStore (spt-store branchstore.rs:159 branches()) — list branches, filter project branches (contextstore.rs:54 project_branch(project_id) gives the branch name pattern; reverse it), for each check the agent has context in that branch (contextstore.rs:508 test = one project branch holds many agents), order newest→oldest by branch commit recency. Need to read branchstore.rs + contextstore.rs API first. Degrade to empty.
- THEN: activate PICKER-2/3/4 stages, traceable, FULL preflight (clippy + binding seam sweep AGAIN if data.rs touches shared — it doesn't, but run picker+endpoint-list units + docs-drift gen), PR base main, ping doyle PR-ready.

**BUILD ORDER + edge (PICKER-1 reference, DONE):** PICKER-1 first = add `controllable: Option<bool>` to InfoJson (info.rs, serde-default N-1-safe) + carry-forward in establish_perch (startup.rs) — add `controllable` param to establish_perch; bind_from_seed (harness-hosted listen) passes Some(false); cmd_bind passes Some(true) ONLY for endpoint_type=="live_agent" (spt-hosted broker PTY), else None (gateways/other stay None — NOT amber-forced). After stamp_creation_fields: `rec.controllable = controllable.or(prior.and_then(|p| p.controllable))`. Then EpStatus 4-state (model.rs:33 — extend or add derived enum Offline/Online/HarnessOnly/Controlled), square_span color (view.rs:31), STATUS line atop render_pick details, data.rs derive — REFINED TYPE-GATED precedence (doyle ruling 2026-06-17, amber=LiveAgent-ONLY): offline→gray / driven_by.is_some()→blue CONTROLLED / type!=LiveAgent→green ONLINE (gateways/shells NEVER amber) / LiveAgent+controllable==Some(false)→amber HARNESS-ONLY / LiveAgent+None→amber (legacy self-corrects next bind) / LiveAgent+Some(true)→green. STAMP: listen→Some(false), bind live_agent→Some(true), bind gateway/non-live→None. Carry rec.state (EndpointType tag, info.rs:71) onto EndpointRow → derive store-only. establish-stamp unit: listen→false / bind live_agent→true / bind gateway→None. BINDING seam gate (startup.rs shared seam): full set incl daemon_lifecycle_real_brain + whoami/endpoint-list + N-1 serde unit. Then PICKER-2/3/4 (all data.rs, fully specified below). GATE BAR: clippy -D warnings · traceable · seam (picker model/view/data + endpoint-list/status + listen/bind seam I touch) · docs-drift gen. Add view snapshot tests (4-state + node_label) + project_history-populated + reconcile units.

**THREE ASKS:**
- ASK1: color-coded STATUS line at TOP of render_pick right-side details, MATCHING the list-item square. FOUR states (extend BOTH square_span view.rs:31 AND new details line): gray OFFLINE · green ONLINE (online+PTY-controllable spt-hosted, not attached) · **amber "ONLINE - HARNESS ONLY"** (online but NOT controllable — harness-hosted, e.g. wall-a; NEW, today mis-shows green) · blue **"ONLINE + CONTROLLED"** (online + driven_by.is_some(). FINAL label = CONTROLLED — operator flip-flopped CONTROLLED→ATTACHED→back to CONTROLLED 2026-06-17; use "CONTROLLED" in STATUS line + list-square legend + test assertions). doyle APPROVED option A + this derivation.
- ASK2: amber detection — distinguish harness-hosted (no broker PTY seat → not controllable) from spt-hosted (broker PTY → controllable). Add `controllable: bool` on EndpointRow; derive 4-state from {offline | controllable | driven_by(attached)}.
- ASK3: fix project_history empty-for-ALL-rows bug (loader not populating).

**REQ ids (mint registry-first, NOT yet minted — do first): FOUR now** — REQ-PICKER-1 (4-state status) + REQ-PICKER-2 (project_history loader) + REQ-PICKER-3 (self-owned subnet status reconcile) + REQ-PICKER-4 (subnet node_label render). Picker = a MINOR (likely v0.10.0; v0.9.1 patch took that number). Open branch off POST-v0.9.1 main; ping doyle full plan + REQ ids at branch-open.

**DEFECT 3 (doyle 2026-06-17, operator screenshots — status inconsistency):** same endpoint shows OPPOSITE online square in Local vs Subnet category. NOT polarity — both mappings correct. Root: Local rows = live roster p.alive (data.rs:83); Subnet rows = persisted WAN snapshot via wansend::load_snapshots (data.rs:128), independently stale. Self-owned endpoints dual-list, sources disagree. CONTEXT:348-350 = one status square per endpoint. RULING (doyle lean + my AGREE): reconcile — a subnet row whose endpoint_id OVERLAPS a local (is_local) roster id is self-owned → OVERRIDE snapshot status with the live roster status (roster = ground truth for an endpoint THIS node hosts; snapshot is lagged projection). Keep BOTH listings (distinct views); unify only the STATUS. Detection = id-set membership, a reconcile pass in data.rs after local_rows+subnet_rows gather. Store-only preserved.

**DEFECT 4 (doyle 2026-06-17 — confirmed impl drift, no ruling):** picker Subnet category renders BARE node key-hex (SPT_DEV:14efb80cb...) as group label. CONTEXT.md:650 mandates "HFENDULEAM (bcead52b…)" = LABEL (keyprefix…), NOT bare hex. Picker-only regression: data.rs subnet_rows() uses raw row.node because resource_projection→ResourceRow (registry.rs:331) DROPS node_label. FIX: thread node_label into picker subnet rows (add field to ResourceRow OR look up reg.node_labels() in subnet_rows) + REUSE canonical render format!("{l} ({}…)", key_prefix) (cli.rs:4011 / wansend.rs:495-505), do NOT re-implement.

**DERIVATION precedence (doyle RULED 2026-06-17, LOCKED + todlando ACK — type-gated):** amber "ONLINE - HARNESS ONLY" is a **LiveAgent-ONLY** state. Type-gate the DERIVATION (not the stamp); type = InfoJson.state = EndpointType::as_tag (info.rs:71), carried onto EndpointRow from rec.state → store-only. Precedence: offline→gray · driven_by.is_some()→blue CONTROLLED · **type!=LiveAgent→green ONLINE (gateways/shells NEVER amber)** · LiveAgent+controllable==Some(false)→amber HARNESS-ONLY · LiveAgent+None→amber (legacy default, self-corrects next bind) · else (LiveAgent+Some(true))→green. KEEPS wall-a immediate amber (LiveAgent+None) AND never amber-paints a gateway. Resolves todlando's cmd_bind-gateway flag (non-live binds stay controllable=None → type gate → green). ASK2 A/B = option A (InfoJson.controllable) + type-gate ANSWERED; amber wiring UNBLOCKED.

**GROUNDING DONE (read model.rs + data.rs + info.rs):**
- model.rs: `EpStatus`=Online|Offline only (line 33). `EndpointRow` (line 162) ALREADY has driven_by:Option<String> (→blue), viewer_count:u32, project_history:Vec<String>, status, is_local, adapter_profile. MISSING = `controllable` discriminator. square() maps status→glyph; view applies color. Confirm-layer already uses driven_by for View/Kick (REQ-RCVIEW-1/KICK-1).
- data.rs: `local_rows()` builds EndpointRow from info.json (reads driven_by, viewer_count). `gather_endpoints()` = local_rows + subnet_rows. Picker is STORE-PROJECTION-ONLY (doyle ruling: no live daemon query).
- info.rs InfoJson 18 fields: id/started/pid/session_id/state/parent_pid/cwd/status/last_active_ms/driven_by/viewer_count/rest_state/dormant_since_ms/auto_suspend_after_ms/resources/home_subnet/adapter/psyche_host_error. **NONE record hosting-mode.** status=online set by BOTH paths (my v0.9.0 cmd_listen sets it for harness-hosted live-capable too) → NOT a discriminator.

**ASK2 AMBIGUITY → PINGED doyle (BLOCKING the amber wiring), awaiting A/B:**
- (A) MY LEAN: add `controllable: Option<bool>` to InfoJson (serde-default N-1-safe), stamped at establish seam — cmd_listen (harness-hosted relay, no broker PTY)→Some(false); cmd_bind live_agent spt-hosted (broker PTY)→Some(true). data.rs reads onto EndpointRow.controllable. Absent (pre-change)→default harness-only/amber (common mis-reported case wall-a; self-corrects in 1 bind cycle). Touches my just-shipped startup.rs + info.rs schema.
- (B) doyle wants a different source (broker PTY-seat query = breaks store-only; or another field). It's doyle's v0.9.0 seam so want sign-off before editing.
- DO NOT wire amber until doyle answers A/B.

**REQ-PICKER-2 root cause CONFIRMED (unambiguous, can build NOW):** `data.rs::project_history_for(id)` (line 168) uses raw `std::fs::read_dir(tracked_dir()/projects)` + fs::metadata on the WORKING TREE. But the context store is a **git-backed BranchStore** (`tracked/.seed.git`) — content lives in git BRANCHES (`contextstore::project_branch(project_id)` → branch name; `branchstore::branches()` enumerates), checked out to `projects/<project>/<id>/` only on-demand. Bare working tree → read_dir finds nothing → empty for all. FIX: enumerate via `BranchStore::branches()` → filter project-branch names → reverse project_branch() to project_ids → for each, check this agent has context in that branch (contextstore.rs:508 `project_branch_holds_many_agents` — one project branch holds many agents) → order newest→oldest by branch commit recency. PROJECT_CONTEXT_FILE = the per-(agent,project) file; project_context_file(root,project,id) = root/projects/<project>/<id>/<file>.

**GATE BAR:** clippy --workspace --all-targets -D warnings · traceable EXIT0 (REQ-PICKER-1 + REQ-PICKER-2 real evidence) · seam (picker view.rs snapshot tests + model unit + any endpoint-list/status seam) · docs-drift xtask gen (TUI, likely no --help change, run anyway). Picker has snapshot-style view.rs tests — add 4-state rendering coverage + a project_history-populated test. Then PR base main, ping doyle PR-ready.

NOTES: independent of perri PREP-4 (diff surface). doyle's ccs PTY fix = separate (shipped v0.8.4). v0.9.0 already on main. LESSON (carry): single-quote heredoc for owl-send/commit/-m bodies (backticks shell-substitute).
