Skip to content

feat(minimal,minimald): first-prompt orientation banner for attached shells - #1149

Merged
norrietaylor merged 6 commits into
mainfrom
feat/inbox-banner
Jul 31, 2026
Merged

feat(minimal,minimald): first-prompt orientation banner for attached shells#1149
norrietaylor merged 6 commits into
mainfrom
feat/inbox-banner

Conversation

@norrietaylor

@norrietaylor norrietaylor commented Jul 31, 2026

Copy link
Copy Markdown
Member

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 init pointer when the project has no minimal.toml.

  • Static templates + dynamic env vars: the daemon's launcher baseline seeds the session name and a once-only PROMPT_COMMAND banner; the client contributes the loadout list and blueprint flag as plain composed vars; the shell interpolates at print time.
  • A loadout that sets its own PROMPT_COMMAND overrides the baseline banner cleanly (baseline sits beneath composition); the built-in default loadout's MOTD carries the same orientation lines after the mark.
  • TTY-gated, prints once, NO_COLOR-safe.
  • Daemon-side tests ride the Linux CI lanes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KyZLpkRf9G4A2hUDgDvn5f

Note

Add first-prompt orientation banner to attached shell sessions

  • Injects MINIMAL_SESSION_NAME, MINIMAL_LOADOUTS, and MINIMAL_BLUEPRINT env vars into every launched session so the shell can render an orientation banner on the first prompt
  • The banner shows the session name, active loadouts, detach chord, and a conditional min init pointer when no minimal.toml is found
  • The built-in default loadout's MINIMAL_MOTD is updated to a static template that interpolates these vars; a baseline MOTD is also seeded in session_host.rs for sessions without a custom loadout
  • Adds loadout_display_list and push_orientation_vars helpers in loadouts.rs, and a blueprint_present probe in lib.rs
  • The e2e script in session-e2e.sh is made hermetic and extended to assert banner output

Changes since #1149 opened

  • Removed blueprint_present function from minimal crate and eliminated propagation of MINIMAL_BLUEPRINT environment variable through session composition [35c8677]
  • Replaced environment variable-based banner logic with in-shell filesystem checks in MOTD templates [35c8677]
  • Updated documentation and end-to-end test to reflect filesystem-based banner behavior [35c8677]
  • Added Orientation struct to core::compose module and extended Composition with an orientation field and accessor, allowing first-prompt banner facts (specifically loadouts_display) to be carried as control-plane data through the composition pipeline [2146a68]
  • Extended WireContribution and WireComposition with orientation field and introduced WireOrientation wire primitive to serialize and deserialize orientation data between client and daemon [2146a68]
  • Modified session_baseline_env function in minimald to accept Option<&str> for loadouts_display and conditionally seed MINIMAL_LOADOUTS environment variable when orientation data is present, and updated SandboxLauncher::launch to extract and pass orientation from composition [2146a68]
  • Refactored compose_user_contribution function in crates/minimal to accept ActiveLoadouts instead of Vec<Loadout>, compute loadout display list, and set orientation on composer using UserComposer::with_orientation, eliminating synthetic MINIMAL_LOADOUTS session variable [2146a68]
  • Extended UserComposer with orientation field and added with_orientation builder method to set first-prompt orientation facts, and updated compose method and composition_to_wire function to include orientation in resulting WireContribution [2146a68]
  • Updated documentation in docs/reference/loadouts.md to reflect that MINIMAL_SESSION_NAME and MINIMAL_LOADOUTS are seeded daemon-side from composition orientation field rather than client-side session variable [2146a68]
  • Imported WireOrientation from primitives and replaced Default::default() calls with WireOrientation::default() for the WireContribution.orientation field [2a98ee3]

Macroscope summarized dcb9ce3.

Summary by CodeRabbit

  • New Features

    • Added an interactive orientation banner showing the session name, active loadouts, detach guidance, and workspace setup status.
    • Built-in default loadouts now activate automatically when no configuration is provided.
    • Loadout information is preserved across session composition and reconnection.
    • User-defined default loadouts can replace the built-in default.
  • Bug Fixes

    • Improved handling and display of selected loadouts during activation.
    • Ensured custom prompt settings correctly override banner defaults.
  • Documentation

    • Expanded loadout documentation with banner behavior, customization, and configuration details.

…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
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Loadout activation now resolves built-in and user loadouts through ActiveLoadouts. Loadout names travel as composition orientation metadata. The daemon seeds launcher variables and renders a TTY-gated banner. Wire compatibility, documentation, and end-to-end checks were updated.

Changes

Loadout orientation flow

Layer / File(s) Summary
Resolve and compose active loadouts
crates/minimal/src/lib.rs, crates/minimal/src/loadouts.rs, crates/minimal/src/task.rs
Active loadouts track built-in-default usage. Composition creates a loadout display label and stores it as orientation data.
Carry orientation through composition APIs
crates/sessions/src/client/composer.rs, crates/sessions/src/core/compose.rs, crates/sessions/src/wire/*
Core and wire composition types carry orientation data with defaulted deserialization. Tests cover serialization, reconstruction, and composition merging.
Seed launcher baseline and render banner
crates/minimald/src/session_host.rs
The daemon seeds session and loadout environment values. A TTY-gated banner renders once, while composition variables can override baseline values.
Document and validate orientation behavior
docs/reference/loadouts.md, scripts/session-e2e.sh
Documentation describes banner inputs and override behavior. End-to-end checks validate session, loadout, detach, and workspace blueprint output.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • gominimal/inbox#455 — The PR implements the requested orientation banner across loadouts, launcher environment setup, documentation, and tests.

Possibly related PRs

Suggested labels: needs-human

Suggested reviewers: evanspearman, 0chroma

Poem

A rabbit sees loadouts in a bright little line,
Through wires and shells they travel just fine.
The session banner appears with a hop,
Then gently unsets when its greeting must stop.
min init points the way—
“Welcome,” says the hare, “have a clear day!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change, but it omits the required Summary, Testing, and Checklist headings and provides no test commands or output. Add the required template sections, list the test commands and results, and complete the checklist, including documentation and breaking-change status.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the main change: a first-prompt orientation banner for attached shells.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/inbox-banner

Comment @coderabbitai help to get the list of available commands.

@norrietaylor
norrietaylor marked this pull request as ready for review July 31, 2026 21:57
@norrietaylor
norrietaylor requested a review from a team as a code owner July 31, 2026 21:57
Comment thread crates/minimal/src/lib.rs Outdated
/// 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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesnt this need to happen in minimald? Theres no guarantee that the cwd matches the files in a session

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good eye

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is minimal lib though right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal lib as in lib.rs in the minimal CLI / client

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

riiiiiiiight

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread crates/minimal/src/lib.rs Outdated
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(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done as prescribed in 2146a68, @twitchyliquid64 — orientation is now first-class at the composition layer, out of the user var lane entirely:

  • WireContribution (and Composition + its persisted WireComposition sidecar, so a daemon restart keeps it) carries a serde-defaulted orientation field (WireOrientation { loadouts_display }); old peers interop — a payload without the field deserializes to the default and seeds nothing.
  • UserComposer collects it (with_orientation) and emits it on the composed contribution; the min task run path routes through the same field.
  • The daemon seeds MINIMAL_LOADOUTS into the launcher baseline env from the typed field, right beside MINIMAL_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
Comment thread crates/minimal/src/loadouts.rs Outdated
};
push("MINIMAL_LOADOUTS", loadout_display);
push(
"MINIMAL_BLUEPRINT",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 twitchyliquid64 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

norrietaylor and others added 2 commits July 31, 2026 16:35
… 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
docs/reference/loadouts.md (1)

391-394: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add 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.md around 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.rs around lines 56 - 83, Eliminate the
duplicated /workbench workspace-root value used by BUILTIN_DEFAULT_TOML and
its tests. Reuse sandbox2::SESSION_DEFAULT_WD through 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 /workbench workspace-root value
    used by BUILTIN_DEFAULT_TOML and its tests. Reuse
    sandbox2::SESSION_DEFAULT_WD through 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 -->

@norrietaylor
norrietaylor merged commit 996295a into main Jul 31, 2026
30 checks passed
@norrietaylor
norrietaylor deleted the feat/inbox-banner branch July 31, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants