feat(minimal,minimald): first-prompt orientation banner for attached shells - #1149
Conversation
…shells An attached shell started with zero orientation. The launcher baseline now seeds a once-only banner naming the session, the active loadouts, and the detach chord (plus a min init pointer when the workspace has no blueprint), interpolated from env vars the daemon and client already know. Composed PROMPT_COMMANDs override it cleanly; the built-in default loadout's MOTD gains the same orientation lines after the mark. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KyZLpkRf9G4A2hUDgDvn5f
📝 WalkthroughWalkthroughLoadout activation now resolves built-in and user loadouts through ChangesLoadout orientation flow
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
| /// so only a definite not-found counts as absent; a present-but-broken | ||
| /// mfile is "present" (pointing its owner at `min init` would be wrong, | ||
| /// and the upload path already fails loudly on it). | ||
| fn blueprint_present(dir: &camino::Utf8Path) -> bool { |
There was a problem hiding this comment.
Doesnt this need to happen in minimald? Theres no guarantee that the cwd matches the files in a session
There was a problem hiding this comment.
this is minimal lib though right?
There was a problem hiding this comment.
minimal lib as in lib.rs in the minimal CLI / client
There was a problem hiding this comment.
Conceded, @twitchyliquid64 — the probe ran client-side against the host cwd at composition time, and the session workspace can diverge from it (skipped uploads, an in-session min init, later attaches from unrelated directories).
Fixed in 35c8677 by going one step further than moving the probe into minimald: MINIMAL_BLUEPRINT is gone entirely. The banner renders inside the sandbox, so both templates now test the workspace itself at the moment they print — [ -f /workbench/minimal.toml ] || [ -f /workbench/.minimal/minimal.toml ] (the daemon-side template derives the root from sandbox2::SESSION_DEFAULT_WD, the same constant that sets the attach shell's initial cwd, so it can't drift). That's fresher than a daemon-side probe at launch would be, and immune to in-session drift: a shell launched after an in-session min init shows the updated truth. MINIMAL_LOADOUTS stays client-contributed, being static composition truth only the client knows.
| let loadout_display = loadouts::loadout_display_list(&active); | ||
| let (mut contribution, user_policy) = | ||
| loadouts::compose_user_contribution(active.loadouts, user_policy, compose_options)?; | ||
| loadouts::push_orientation_vars( |
There was a problem hiding this comment.
These data points should be first-class fields at the composition layer, otherwise we are mixing user env vars with control plane stuff. Handle collecting orientation type stuff in UserComposer, add fields to carry it in the corresponding wire type (I think WireContribution)
There was a problem hiding this comment.
Done as prescribed in 2146a68, @twitchyliquid64 — orientation is now first-class at the composition layer, out of the user var lane entirely:
WireContribution(andComposition+ its persistedWireCompositionsidecar, so a daemon restart keeps it) carries a serde-defaultedorientationfield (WireOrientation { loadouts_display }); old peers interop — a payload without the field deserializes to the default and seeds nothing.UserComposercollects it (with_orientation) and emits it on the composed contribution; themin task runpath routes through the same field.- The daemon seeds
MINIMAL_LOADOUTSinto the launcher baseline env from the typed field, right besideMINIMAL_SESSION_NAME— all orientation env is daemon-baseline-seeded now, so user vars and user policy can never collide with it. Templates unchanged.
Re the naming comment on MINIMAL_BLUEPRINT: that var is gone entirely — the cwd-probe fix replaced it with an in-shell test of the workspace at print time — so the rename became moot.
The client cwd probe claimed a workspace fact: MINIMAL_BLUEPRINT was computed from the host directory at composition time, which diverges from the session workspace whenever the upload is skipped, after an in-session min init, and on attaches from unrelated directories. The banner renders inside the sandbox, so both templates now test the workspace root in-shell at print time (/workbench, both mfile layouts; the daemon template derives the path from sandbox2::SESSION_DEFAULT_WD, the same constant that sets the shell's initial cwd) and the var is gone. MINIMAL_LOADOUTS stays: static composition truth the client alone knows. The e2e seed becomes a VCS root so the headless upload gate actually ships its blueprint into the workspace the banner now inspects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KyZLpkRf9G4A2hUDgDvn5f
| }; | ||
| push("MINIMAL_LOADOUTS", loadout_display); | ||
| push( | ||
| "MINIMAL_BLUEPRINT", |
There was a problem hiding this comment.
I would name this something descriptive, blueprint doesnt map to anything. Maybe MINIMAL_HAS_LOCAL_MINIMAL_TOML
…ss composition field
Control-plane data leaves the user var lane per review: the loadout
display list now rides WireContribution (and the Composition plus its
persisted WireComposition sidecar) as a serde-defaulted orientation
field, collected by UserComposer from the client's loadout selection.
The daemon seeds MINIMAL_LOADOUTS into the launcher baseline env from
the typed field, beside MINIMAL_SESSION_NAME — all orientation env is
daemon-baseline-seeded, so user vars and user policy can never collide
with it. An empty/missing field (old peer) seeds nothing and the
templates' ${MINIMAL_LOADOUTS:-...} fallbacks render. The task-run
path routes through the same field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KyZLpkRf9G4A2hUDgDvn5f
twitchyliquid64
left a comment
There was a problem hiding this comment.
Last request just trim down the comments, session_baseline_env just needs to say its injecting some env vars for the motd, SESSION_WORKSPACE_ROOT should just be one line, etc
… literals CI clippy (--all-targets) denies default_trait_access in the test targets the lib-scoped local run does not cover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KyZLpkRf9G4A2hUDgDvn5f
There was a problem hiding this comment.
🧹 Nitpick comments (2)
docs/reference/loadouts.md (1)
391-394: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a language tag to the fenced code block.
Static analysis (markdownlint MD040) flags this fenced block for missing a language specifier. Add
text(or similar) after the opening fence, since this block shows plain banner output rather than a shell command.📝 Proposed fix
-``` +```text minimal · session api-server-4f2a · loadout default (built-in) detach: ctrl-w · no minimal.toml here — min init to add one</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@docs/reference/loadouts.mdaround lines 391 - 394, Add the text language tag
to the opening fenced code block containing the loadout banner output, without
changing its contents or closing fence.</details> <!-- cr-comment:v1:b7916e0b07c8704809f8aef3 --> _Source: Linters/SAST tools_ </blockquote></details> <details> <summary>crates/minimal/src/loadouts.rs (1)</summary><blockquote> `56-83`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_ **Remove the duplicated workspace-root literal** `minimal` does not depend on `sandbox2`, and its tests also hardcode `/workbench`. If `sandbox2::SESSION_DEFAULT_WD` changes, the MOTD and tests can drift silently. Share the constant or add a test-only `sandbox2` dependency that compares both values. <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@crates/minimal/src/loadouts.rsaround lines 56 - 83, Eliminate the
duplicated/workbenchworkspace-root value used byBUILTIN_DEFAULT_TOMLand
its tests. Reusesandbox2::SESSION_DEFAULT_WDthrough an appropriate
shared/test-only dependency or comparison so the MOTD and test expectations
remain synchronized when the default changes.</details> <!-- cr-comment:v1:1db856c394e36591e76c71e7 --> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.Nitpick comments:
In@crates/minimal/src/loadouts.rs:
- Around line 56-83: Eliminate the duplicated
/workbenchworkspace-root value
used byBUILTIN_DEFAULT_TOMLand its tests. Reuse
sandbox2::SESSION_DEFAULT_WDthrough an appropriate shared/test-only
dependency or comparison so the MOTD and test expectations remain synchronized
when the default changes.In
@docs/reference/loadouts.md:
- Around line 391-394: Add the text language tag to the opening fenced code
block containing the loadout banner output, without changing its contents or
closing fence.</details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Organization UI **Review profile**: CHILL **Plan**: Pro **Run ID**: `e5998cf9-7e9b-475f-b6aa-6c0e7383bd10` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between b472900b19fc629145037356deacb79575ca02f3 and dcb9ce38b00697d49783f02499a045dda0325269. </details> <details> <summary>📒 Files selected for processing (10)</summary> * `crates/minimal/src/lib.rs` * `crates/minimal/src/loadouts.rs` * `crates/minimal/src/task.rs` * `crates/minimald/src/session_host.rs` * `crates/sessions/src/client/composer.rs` * `crates/sessions/src/core/compose.rs` * `crates/sessions/src/wire/primitives.rs` * `crates/sessions/src/wire/request.rs` * `docs/reference/loadouts.md` * `scripts/session-e2e.sh` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
An attached shell starts with zero orientation — which session, which loadout, how to detach; the first prompt now prints a two-line banner naming them, with a
min initpointer when the project has nominimal.toml.PROMPT_COMMANDoverrides the baseline banner cleanly (baseline sits beneath composition); the built-in default loadout's MOTD carries the same orientation lines after the mark.NO_COLOR-safe.🤖 Generated with Claude Code
https://claude.ai/code/session_01KyZLpkRf9G4A2hUDgDvn5f
Note
Add first-prompt orientation banner to attached shell sessions
MINIMAL_SESSION_NAME,MINIMAL_LOADOUTS, andMINIMAL_BLUEPRINTenv vars into every launched session so the shell can render an orientation banner on the first promptmin initpointer when nominimal.tomlis foundMINIMAL_MOTDis updated to a static template that interpolates these vars; a baseline MOTD is also seeded in session_host.rs for sessions without a custom loadoutloadout_display_listandpush_orientation_varshelpers in loadouts.rs, and ablueprint_presentprobe in lib.rsChanges since #1149 opened
blueprint_presentfunction fromminimalcrate and eliminated propagation ofMINIMAL_BLUEPRINTenvironment variable through session composition [35c8677]Orientationstruct tocore::composemodule and extendedCompositionwith anorientationfield and accessor, allowing first-prompt banner facts (specificallyloadouts_display) to be carried as control-plane data through the composition pipeline [2146a68]WireContributionandWireCompositionwithorientationfield and introducedWireOrientationwire primitive to serialize and deserialize orientation data between client and daemon [2146a68]session_baseline_envfunction inminimaldto acceptOption<&str>forloadouts_displayand conditionally seedMINIMAL_LOADOUTSenvironment variable when orientation data is present, and updatedSandboxLauncher::launchto extract and pass orientation from composition [2146a68]compose_user_contributionfunction incrates/minimalto acceptActiveLoadoutsinstead ofVec<Loadout>, compute loadout display list, and set orientation on composer usingUserComposer::with_orientation, eliminating syntheticMINIMAL_LOADOUTSsession variable [2146a68]UserComposerwithorientationfield and addedwith_orientationbuilder method to set first-prompt orientation facts, and updatedcomposemethod andcomposition_to_wirefunction to include orientation in resultingWireContribution[2146a68]docs/reference/loadouts.mdto reflect thatMINIMAL_SESSION_NAMEandMINIMAL_LOADOUTSare seeded daemon-side from composition orientation field rather than client-side session variable [2146a68]WireOrientationfrom primitives and replacedDefault::default()calls withWireOrientation::default()for theWireContribution.orientationfield [2a98ee3]Macroscope summarized dcb9ce3.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation