Skip to content

feat(sessions): wire project + package composables end-to-end into the sandbox - #650

Merged
evanspearman merged 3 commits into
mainfrom
evan/smushittogether
Jul 7, 2026
Merged

feat(sessions): wire project + package composables end-to-end into the sandbox#650
evanspearman merged 3 commits into
mainfrom
evan/smushittogether

Conversation

@evanspearman

@evanspearman evanspearman commented Jul 7, 2026

Copy link
Copy Markdown
Member

Resolves https://github.com/gominimal/inbox/issues/198

Summary

Wires the daemon-side session composables end-to-end into the sandbox
launcher, so a session activated against a project actually gets that
project's packages, vars, and stack material — not the hardcoded 5-package
literal that previously ran.

Before this change: every session launched with the same static
["base", "bash", "socat", "coreutils", "claude-code"] package set,
the mfile was never consulted, and Composition was computed on the
daemon and then thrown away.

After: CreateSession builds a Composition from all three composables
(loadout, project, per-package), stashes it, and SandboxLauncher::launch
unions its packages and vars over a minimal baseline.

Every package included in the session goes through this pipeline:

  1. The client's requested packages plus the project's declared packages
    (from [session] packages, [stack] build_packages/runtime_packages,
    and the graph-level Stack's own package lists) form the top-level set.
  2. Their transitive runtime closure is walked.
  3. Each package with env_state_wiring or env_dir/file_mappings
    contributes a PackageComposable; the resulting vars and patches are
    tagged Source::Package and gated through the same PatchPolicy /
    VarsPolicy any other contribution goes through.
  4. Composition is materialized and merged into EnvArgs at launch.

The Env::build legacy path stays intact for mip run <task> — task
runs still use SetupForPackages as before. Session launches opt into
the composition path via EnvArgs::without_package_attr_wiring().

Notable pieces

  • mfile::ProjectComposable and mfile::PackageComposable: two new
    Composable impls that read the [session] block and package
    BuildSpec.attrs respectively.
  • minimald::sessions::composables: daemon-side pipeline that resolves
    the project mfile + graph, builds composables, and drives the
    SessionComposer. Split into its own module so each stage is unit-
    testable without spinning the manager mainloop.
  • Env::build composition branch: bypasses SetupForPackages output for
    env_vars/fs_mappings; state dirs derive from resolved var values
    shaped /state/<prefix> instead.
  • mctx::Context split into Context + Arc<DaemonContext> so per-
    session mctx state doesn't duplicate the daemon-scoped config, vcs,
    and cache setup.
  • ApproveProjectAndPackage client hook: default minimal activate
    policy that auto-approves Source::Project / Source::Package
    contributions (project activation implicitly consents to what the
    project declares).
  • Example project at crates/sessions/example_project/ with a
    demo-punchline mfile — comment out [session], re-activate, watch
    every demo command break.

Deferred

  • Composition patches → sandbox. Blocked on the file-upload path.
  • Lifecycle hooks. Blocked on in-sandbox exec plumbing.
  • Credential-class fs mappings. Dropped with a tracing::warn! pending
    the secrets strategy. read_only = true is ignored (with warn); minimal
    is copy-based so the flag has no meaning today.

Summary by CodeRabbit

  • New Features
    • Added optional project session configuration via minimal.toml’s [session] (packages, vars, patches, lifecycle hooks) and session-aware composition.
    • Enabled daemon-scoped shared state so runtime setup consistently reuses daemon config/cache.
    • Added composition-derived package/env wiring for real session launches (with session-provided overrides).
  • Bug Fixes
    • Updated environment/cache handling to use daemon-backed locations for more reliable setup.
    • Improved pending activation gating: unapproved items are allowed only when they originate strictly from daemon-side project/package sources.
    • Enhanced handling of empty/absent session config and forward-compatible unknown fields.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cbb1e548-2262-4919-996f-d45d14ba34b8

📥 Commits

Reviewing files that changed from the base of the PR and between 86f252e and 28dcbf9.

📒 Files selected for processing (20)
  • crates/minimald/src/env.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/session_host.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sessions/composables.rs
  • crates/sessions/src/client/handler.rs
  • crates/sessions/src/core/compose.rs
  • crates/sessions/src/core/enumerate.rs
  • crates/sessions/src/core/expansion.rs
  • crates/sessions/src/core/hooks.rs
  • crates/sessions/src/core/lifecyclehook.rs
  • crates/sessions/src/core/loadout.rs
  • crates/sessions/src/core/policy.rs
  • crates/sessions/src/core/primitives.rs
  • crates/sessions/src/core/source.rs
  • crates/sessions/src/daemon/composer.rs
  • crates/sessions/src/lib.rs
  • crates/sessions/src/store.rs
  • crates/sessions/src/wire/errors.rs
  • crates/sessions/src/wire/policy.rs
✅ Files skipped from review due to trivial changes (13)
  • crates/sessions/src/core/enumerate.rs
  • crates/sessions/src/core/hooks.rs
  • crates/sessions/src/core/expansion.rs
  • crates/sessions/src/core/source.rs
  • crates/sessions/src/core/lifecyclehook.rs
  • crates/sessions/src/wire/policy.rs
  • crates/sessions/src/wire/errors.rs
  • crates/sessions/src/client/handler.rs
  • crates/sessions/src/lib.rs
  • crates/sessions/src/core/policy.rs
  • crates/sessions/src/store.rs
  • crates/sessions/src/core/primitives.rs
  • crates/sessions/src/daemon/composer.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • crates/sessions/src/core/loadout.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/session_host.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sessions/composables.rs
  • crates/minimald/src/env.rs

📝 Walkthrough

Walkthrough

This PR adds project and package session contributors, threads them through daemon-side session composition, and propagates the resulting Composition into session startup and sandbox environment construction. It also moves shared mctx state into DaemonContext and updates the trusted-source approval policy.

Changes

Session composition pipeline

Layer / File(s) Summary
Shared contribution primitives
crates/sessions/src/core/compose.rs, crates/sessions/src/core/loadout.rs
Adds contribute_primitives and refactors Loadout::contribute to use it.
mfile session and composables
crates/mfile/Cargo.toml, crates/mfile/src/lib.rs, crates/mfile/src/package_composable.rs, crates/mfile/src/project_composable.rs
Adds Session, File.session, package/project composables, and related parsing/tests.
DaemonContext extraction
crates/graph/src/lib.rs, crates/mctx/src/lib.rs, crates/mctx/src/env.rs, crates/mctx/src/project_setup.rs, crates/mctx/src/scaffold.rs, crates/mip/src/cmd_run.rs
Introduces DaemonContext, rewires mctx access to daemon-owned config/cache, and updates public re-exports.
Daemon composables pipeline
crates/minimald/src/sessions/composables.rs
Builds project/package composables from minimal.toml, package attrs, and stack vars, then runs SessionComposer.
Session manager composition lifecycle
crates/minimald/src/server.rs, crates/minimald/src/sessions.rs
Builds mctx_config, stashes compositions across session lifecycle, and adds test inspection paths.
Session and sandbox composition wiring
crates/minimald/Cargo.toml, crates/minimald/minimal-ncl, crates/minimald/src/env.rs, crates/minimald/src/session.rs, crates/minimald/src/session_host.rs, crates/mctx/src/env.rs
Threads composition into session startup and env construction, and switches env setup to daemon-backed wiring.
Trusted-source approval policy
crates/minimal/src/main.rs
Allows project/package-sourced unapproved items and aborts otherwise.
Example project assets
crates/sessions/example_project/arch.mmd, crates/sessions/example_project/minimal.toml
Adds demo configuration and architecture diagram.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: twitchyliquid64, norrietaylor, jtnkminimal

Poem

I thump through the warren with package and flair,
Project and daemon now meet in one lair.
A composition hops, then settles to stay,
With trusted-source gatekeepers clearing the way.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Most of #198 is implemented, but the required Loadout round-trip and project session-section integration tests aren't evidenced in the PR summary. Add a declarative TOML → Loadout → Composer → Resolution test and an integration test for project session-section parsing, then recheck source attribution coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, specific, and matches the main change: wiring project and package composables into sandbox launch.
Out of Scope Changes check ✅ Passed I don't see unrelated feature work; the added docs, example project, and daemon/config refactors all support the composable wiring changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@evanspearman
evanspearman force-pushed the evan/smushittogether branch from ca60cf0 to 8e1e4dc Compare July 7, 2026 19:04

@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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/minimald/src/sessions/composables.rs (1)

118-126: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Comment overstates the safety net for schema-tag drift.

The doc says a decode enum-tag rendering change "breaks compilation" — but CREDENTIAL_CLASS_TAG is only ever runtime string-compared (Line 156-159), so a rendering drift wouldn't fail to compile; it would silently stop matching, letting Credential-class mappings flow into the sandbox unfiltered. The actual protection is the credential_class_fs_mappings_are_filtered_out test, which only catches this if it's run/observed. Worth correcting the comment so the real (test-based, not compile-time) guarantee is clear to future maintainers of this credential-handling path.

🤖 Prompt for AI Agents
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/minimald/src/sessions/composables.rs` around lines 118 - 126, The
comment for CREDENTIAL_CLASS_TAG overstates the safety guarantee by saying
schema-tag drift would “break compilation,” but the value is only checked at
runtime in the credential filtering path. Update the documentation near
CREDENTIAL_CLASS_TAG in composables.rs to say that changes to
decode::AttrValue::EnumTag rendering will cause the runtime string comparison to
stop matching, and that the credential_class_fs_mappings_are_filtered_out test
is the actual guard against regressions. Keep the note aligned with the behavior
in the credential-handling code so future maintainers understand the real
protection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/mfile/src/lib.rs`:
- Around line 374-406: Drop the manual Eq implementation for Session in lib.rs.
Session contains extra: HashMap<String, toml::Value>, and toml::Value only
supports PartialEq because Float(f64) can be NaN, so Session cannot validly
promise Eq. Remove the impl Eq for Session block and keep the derived
PartialEq/other derives on Session unless another type or trait bound in the
Session API explicitly requires Eq.

In `@crates/minimald/src/sessions.rs`:
- Around line 463-486: In sessions.rs, the composition is removed from
self.compositions before calling Session::run, so a failed spawn can lose the
stashed packages/vars for the record. Update the GetSession flow around
self.compositions.remove, obj.record().id, and Session::run so the removed
composition is restored back into the map if Session::run returns an error
before propagating the failure. Keep the successful path unchanged, but ensure
the error path reinserts the exact composition associated with that session id.

---

Nitpick comments:
In `@crates/minimald/src/sessions/composables.rs`:
- Around line 118-126: The comment for CREDENTIAL_CLASS_TAG overstates the
safety guarantee by saying schema-tag drift would “break compilation,” but the
value is only checked at runtime in the credential filtering path. Update the
documentation near CREDENTIAL_CLASS_TAG in composables.rs to say that changes to
decode::AttrValue::EnumTag rendering will cause the runtime string comparison to
stop matching, and that the credential_class_fs_mappings_are_filtered_out test
is the actual guard against regressions. Keep the note aligned with the behavior
in the credential-handling code so future maintainers understand the real
protection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26fdbddc-fba2-41cd-84ba-68f8ef7fb122

📥 Commits

Reviewing files that changed from the base of the PR and between b78cd67 and 8e1e4dc.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • crates/graph/src/lib.rs
  • crates/mctx/src/env.rs
  • crates/mctx/src/lib.rs
  • crates/mctx/src/project_setup.rs
  • crates/mctx/src/scaffold.rs
  • crates/mfile/Cargo.toml
  • crates/mfile/src/lib.rs
  • crates/mfile/src/package_composable.rs
  • crates/mfile/src/project_composable.rs
  • crates/minimal/src/main.rs
  • crates/minimald/Cargo.toml
  • crates/minimald/minimal-ncl
  • crates/minimald/src/env.rs
  • crates/minimald/src/server.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/session_host.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sessions/composables.rs
  • crates/mip/src/cmd_run.rs
  • crates/sessions/example_project/arch.mmd
  • crates/sessions/example_project/minimal.toml
  • crates/sessions/src/core/compose.rs
  • crates/sessions/src/core/loadout.rs

Comment thread crates/mfile/src/lib.rs
Comment thread crates/minimald/src/sessions.rs Outdated
Comment thread crates/minimald/src/sessions.rs Outdated
/// then destroy) would leak indefinitely, which is why both
/// paths call `compositions.remove(&id)`.
///
/// Held behind [`Arc`] so the drain-to-actor hop and any

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.

Can we do a pass through all the verbose commentary and trim them down to just the essentials?

i.e. this could just talk about how this is the pending composition for a in-construction session, discussion of the point of Arc and unbounded growth in the edge case is probably unnecessary

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.

Agreed. I'll get Claude to do a pass over all the doc comments in sessions and minimald.

/// Lazily-built [`mctx::Context`] rooted at this session's
/// workspace, cached across [`Self::context`] calls so repeated
/// attach / task-exec paths don't rebuild the daemon setup and
/// re-parse the workspace mfile.

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.

There are cases where we want a fresh Context, like its totally valid for a user to edit their minimal.toml to add some new tasks and then run them - thats why i had it originally as being reconstructed every time someone called for a context via the session handle.

Happy to leave it like this for now, but probably want to circle back to this later.

armed: true,
});

// Package + env-var union of the launcher baseline and every

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.

Would a cleaner way to do this just be to have some "default" loadout/composition that gets unioned in earlier, rather than having a baseline set of packages and stuff that gets combined at the last moment ?

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.

I like that approach. I think I'll do that in a follow on as part of the loadout loading flow though.

@evanspearman
evanspearman merged commit e8fe0af into main Jul 7, 2026
20 of 21 checks passed
@evanspearman
evanspearman deleted the evan/smushittogether branch July 7, 2026 22:09
norrietaylor added a commit that referenced this pull request Jul 8, 2026
The DM1 re-run (2026-07-08, HEAD 9443bec with the G-N9 fix built in)
surfaced two issues and qualified a third:

- G-N10: in-sandbox `curl` vanished after #650, which made the session
  rootfs compose only declared packages; `curl` was only incidental to
  the old base rootfs. TC1b/TC2 now provision it per-session with
  `min add curl` (egress 200, peer YES), restoring the egress probes.
- G-N11 (new): the G-N9 `:7654`/`:7655` proxy lease-routing returns 502
  reproducibly once a prior own-ip session has driven real egress; from a
  churn-free VM the same TCs are 200. TC4 (direct ingress) is unaffected,
  so the fault is the proxy lease resolution under prior switch usage.
- G-N12 (new): TC7 with-cert fails at the host `curl`/LibreSSL, which
  rejects the valid P-256 client cert from `minimal login`; the proxy
  mTLS gate (no-cert 401) works.

So G-N9's proxy fix is confirmed on DM1 only in isolation. test-plan.sh
wires `min add curl` into TC1b/TC2; test-plan.md records the 4-pass
verdicts and adds G-N11/G-N12 to the gap register.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
norrietaylor added a commit that referenced this pull request Jul 10, 2026
The DM1 re-run (2026-07-08, HEAD 9443bec with the G-N9 fix built in)
surfaced two issues and qualified a third:

- G-N10: in-sandbox `curl` vanished after #650, which made the session
  rootfs compose only declared packages; `curl` was only incidental to
  the old base rootfs. TC1b/TC2 now provision it per-session with
  `min add curl` (egress 200, peer YES), restoring the egress probes.
- G-N11 (new): the G-N9 `:7654`/`:7655` proxy lease-routing returns 502
  reproducibly once a prior own-ip session has driven real egress; from a
  churn-free VM the same TCs are 200. TC4 (direct ingress) is unaffected,
  so the fault is the proxy lease resolution under prior switch usage.
- G-N12 (new): TC7 with-cert fails at the host `curl`/LibreSSL, which
  rejects the valid P-256 client cert from `minimal login`; the proxy
  mTLS gate (no-cert 401) works.

So G-N9's proxy fix is confirmed on DM1 only in isolation. test-plan.sh
wires `min add curl` into TC1b/TC2; test-plan.md records the 4-pass
verdicts and adds G-N11/G-N12 to the gap register.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants