From bfdfa6459ae7127071751a54b18dbace5b9ac50e Mon Sep 17 00:00:00 2001 From: Ondrej Drapalik Date: Tue, 8 Sep 2026 23:12:13 +0200 Subject: [PATCH 1/2] feat: muse is the shipped default template --- src/config.js | 23 +++++++++++++++++------ test/cli.test.sh | 20 +++++++++++++------- test/config.test.js | 13 ++++++++----- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/config.js b/src/config.js index 08699eb..fca890f 100644 --- a/src/config.js +++ b/src/config.js @@ -16,10 +16,13 @@ export { AUTH_PATH, CONFIG_PATH } from "./config-paths.js" const CLI_CONFIG_PATH = path.join(os.homedir(), ".e2b", "config.json") const DEFAULTS = { - // Safe minimal default that always exists. For real work build a bigger - // custom template (more disk/CPU + your toolchain) and set it here — see the - // README "Recommended: a bigger custom template" and install.sh. - template: "base", + // What a box boots from when nothing else decided: Meta's Muse Code template, + // an agent ready to work rather than an empty image to install one into. `base` + // (E2B's minimal image, always present) stays in the picker and stays the + // fallback a create lands on when the chosen template is not on the cluster. + // For real work build a bigger custom template (more disk/CPU + your toolchain) + // and set it here, see the README "Recommended: a bigger custom template". + template: "muse", templateRules: [], // [{pattern, template}] per-branch overrides // The `open` picker's menu, in the order shown. Ships with the plugin so the // picker is useful out of the box; `[sandbox] templates` in config REPLACES it. @@ -1013,7 +1016,7 @@ export function templateRuleMatches(branch, cfg) { * always offered so "just boot the usual one" is one keypress. */ /** - * The templates a FLEET may be built from: the same list, minus the plain default. + * The templates a FLEET may be built from: the same list, minus the agentless ones. * * A fleet is several coding agents working one checkout in parallel, so a box with * no agent in it is not a member — it is a shell nobody asked for. `base` is also @@ -1026,7 +1029,15 @@ export function templateRuleMatches(branch, cfg) { * keeps offering it: one plain box is a perfectly good thing to want. */ export function fleetTemplateChoices(cfg) { - return templateChoices(cfg).filter((t) => t !== cfg.template) + // Agentless means: `base` (E2B's plain image, the one public template whose name + // is not a CLI), or a template the user mapped to "" in `[fleet.agents]`, the + // documented "plain shell, start nothing" for a control arm or their own plain + // image. NOT the configured default: since the default became an agent template + // (`muse`), filtering it out would drop the one member most people want. + const agents = cfg?.fleetAgents || {} + return templateChoices(cfg).filter( + (t) => t !== "base" && !(Object.prototype.hasOwnProperty.call(agents, t) && agents[t] === ""), + ) } export function templateChoices(cfg) { diff --git a/test/cli.test.sh b/test/cli.test.sh index 067f59b..0b9f5a0 100644 --- a/test/cli.test.sh +++ b/test/cli.test.sh @@ -484,15 +484,21 @@ out=$(cd "$RUNREPO" && "$E2B" run --task x --dashboard 2>&1); rc=$? { [ "$rc" -eq 2 ] && printf '%s' "$out" | grep -q "doesn't take"; } \ && ok "run with an unknown flag → exit 2" || bad "run unknown flag (rc=$rc, out=$out)" -# The default template is `base`, which ships no agent, and a template nobody has -# verified a headless mode for has no default either. Both are refused BY NAME with -# the fix on the line, and nothing boots. -# An empty config dir, so the developer's own [sandbox] template cannot answer here. -NOCFG="$TMP/nocfg"; mkdir -p "$NOCFG" -out=$(cd "$RUNREPO" && HERDR_PLUGIN_CONFIG_DIR="$NOCFG" "$E2B" run --task x 2>&1); rc=$? +# `base` ships no agent, and a template nobody has verified a headless mode for has +# no default either. Both are refused BY NAME with the fix on the line, and nothing +# boots. +out=$(cd "$RUNREPO" && "$E2B" run -t base --task x 2>&1); rc=$? { [ "$rc" -eq 2 ] && printf '%s' "$out" | grep -q "no headless agent command for template 'base'" \ && printf '%s' "$out" | grep -q "\[run.agents\]"; } \ - && ok "run on the default template → refused by name, exit 2" || bad "run on base (rc=$rc, out=$out)" + && ok "run -t base → refused by name, exit 2" || bad "run on base (rc=$rc, out=$out)" +# With nothing configured the shipped default template is `muse`, an agent, so a bare +# `run` has something to run. An empty config dir, so the developer's own [sandbox] +# template cannot answer here. +NOCFG="$TMP/nocfg"; mkdir -p "$NOCFG" +out=$(cd "$RUNREPO" && HERDR_PLUGIN_CONFIG_DIR="$NOCFG" "$E2B" run --task x --dry-run 2>/dev/null); rc=$? +{ [ "$rc" -eq 0 ] && [ "$(printf '%s' "$out" | jq -r '.template')" = "muse" ] \ + && printf '%s' "$out" | jq -e '.agentCommand | startswith("muse exec ")' >/dev/null; } \ + && ok "run with no -t and no config → the shipped default, muse" || bad "default template (rc=$rc, out=$out)" out=$(cd "$RUNREPO" && "$E2B" run -t my-project/my-template --template-any --task x 2>&1); rc=$? { [ "$rc" -eq 2 ] && printf '%s' "$out" | grep -q "for template 'my-project/my-template'" \ && printf '%s' "$out" | grep -q "pass -t one of: claude, codex, opencode, amp, grok, droid, muse, prime"; } \ diff --git a/test/config.test.js b/test/config.test.js index bcc3d60..4e9d635 100644 --- a/test/config.test.js +++ b/test/config.test.js @@ -662,17 +662,20 @@ test("resolveEnv: amp gets the welcome-splash skip, and a user can still overrid ) }) -test("fleetTemplateChoices: a fleet is agents only — the plain default is not a member", () => { +test("fleetTemplateChoices: a fleet is agents only: agentless templates are not members", () => { // A fleet is several coding agents on one checkout, so a box with no agent in it // is a shell nobody asked for. `base` is also the one template whose name is not // a CLI, so a member built from it typed `base` into a sandbox shell and waited // for a TUI that never came. const cfg = { template: "base", templates: ["claude", "codex", "base"], templateRules: [] } assert.deepEqual(fleetTemplateChoices(cfg), ["claude", "codex"]) - // Filtered by the CONFIGURED default, not the literal "base", so someone whose - // plain image is called something else gets the same treatment. - const mine = { template: "plain", templates: ["plain", "claude"], templateRules: [] } - assert.deepEqual(fleetTemplateChoices(mine), ["claude"]) + // The configured default is NOT what is filtered: the shipped default is `muse`, an + // agent, and the member most people want. A plain image of your own leaves the + // roster the documented way, `[fleet.agents] plain = ""`. + const mine = { template: "muse", templates: ["muse", "plain", "claude"], templateRules: [], fleetAgents: { plain: "" } } + assert.deepEqual(fleetTemplateChoices(mine), ["muse", "claude"]) + const unmapped = { template: "muse", templates: ["muse", "plain", "claude"], templateRules: [] } + assert.deepEqual(fleetTemplateChoices(unmapped), ["muse", "plain", "claude"]) // And the box picker is untouched — one plain box is a fine thing to want. assert.deepEqual(templateChoices(cfg), ["claude", "codex", "base"]) }) From b68905c2d82c8ceb3464e9e327593fe556c382f7 Mon Sep 17 00:00:00 2001 From: Ondrej Drapalik Date: Tue, 8 Sep 2026 23:12:13 +0200 Subject: [PATCH 2/2] docs: muse as the default template --- CHANGELOG.md | 10 ++++++++++ README.md | 10 +++++----- config/config.example.toml | 18 +++++++++++------- docs/headless-run.md | 3 +-- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e2f26d..6b12086 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- The shipped default template is `muse` (Meta's Muse Code), not `base`: a new box + with nothing configured boots an agent ready to work instead of an empty image. + `base` stays in the picker, stays the create-time fallback for a template that is + not on your cluster, and is still excluded from fleet rosters. A fleet roster now + drops templates that are agentless (`base`, or `[fleet.agents] = ""`) + rather than whatever `[sandbox] template` names, so the default is a member. + `[sandbox] template = "base"` restores the old behaviour. + ### Added - `[templates.] model = "…"` and `reasoning = "…"`: pin which model a diff --git a/README.md b/README.md index 0b4d7ec..5ba51ed 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ either, you get the chooser: ``` E2B template for my-worktree - ▸ [1] base default + ▸ [1] muse default [2] claude [3] codex [4] opencode @@ -253,14 +253,14 @@ either, you get the chooser: [6] grok [7] droid [8] prime - [9] muse + [9] base ↑/↓ · j/k move enter confirm number jumps t type a name q default ``` -`t` takes any name, so the menu is a shortcut and not a whitelist. `base` is -E2B's minimal image — fine for trying the flow, tight on disk for real work. For -that, [build a custom template](https://e2b.dev/docs/sandbox-template) with your +`t` takes any name, so the menu is a shortcut and not a whitelist. `muse` (Meta's +Muse Code) is the shipped default; `base` is E2B's minimal image, fine for trying +the flow or a plain shell, tight on disk for real work. For that, [build a custom template](https://e2b.dev/docs/sandbox-template) with your toolchain and roomier resources, and point `[sandbox].template` at it. > **Regions are named, not spelled as hosts.** `[sandbox] region = "us" | "eu"` diff --git a/config/config.example.toml b/config/config.example.toml index 93f897a..1edad3f 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -61,14 +61,18 @@ # the plugin owns. An old `domain =` is an error, not an ignored line — silently # dropping it would move an EU user's boxes to US without a word. -# E2B template. "base" (default) is minimal and always available — fine to try -# the flow, but tight on disk/tools for real work. -# RECOMMENDED: build a bigger CUSTOM template (more disk + CPU, your toolchain -# baked in) and set it here — see the README and install.sh. Public agent -# templates (claude, codex, opencode, … https://e2b.dev/docs/agents) also -# work but can run out of space. (Claude Code's template is named `claude`; +# E2B template a NEW box boots from when nothing else decided (no --template, no +# branch rule, no picker answer). "muse" (default) is Meta's Muse Code agent, ready +# to work once [templates.muse.env] holds a META_API_KEY. "base" is E2B's minimal +# image, always available and always in the picker: fine to try the flow or for a +# plain shell, tight on disk/tools for real work, and the image a create falls back +# to when the chosen template is not built on your cluster. +# RECOMMENDED for real work: build a bigger CUSTOM template (more disk + CPU, your +# toolchain baked in) and set it here, see the README and install.sh. The other +# public agent templates (claude, codex, opencode, … https://e2b.dev/docs/agents) +# also work but can run out of space. (Claude Code's template is named `claude`; # Devin has no prebuilt template — install it into a base sandbox yourself.) -template = "base" +template = "muse" # The menu `e2b-box open` offers when it has to BUILD a sandbox and nothing else # decided which template (no --template, no matching rule below). The plugin diff --git a/docs/headless-run.md b/docs/headless-run.md index d472c1c..95e9165 100644 --- a/docs/headless-run.md +++ b/docs/headless-run.md @@ -58,8 +58,7 @@ The template decides which agent works the task, and `run` needs that agent's | `muse` | `muse exec --yolo --user-input-auto-resolve --prompt-file $HOME/.herdr-e2b-task.md` | | `prime` | `prime-agent -p --no-session "$(cat $HOME/.herdr-e2b-task.md)"` | -`base` (the default) and your own templates are refused by name before anything -boots. An invented flag fails to launch and reads exactly like an agent +`base` and your own templates are refused by name before anything boots. An invented flag fails to launch and reads exactly like an agent that did nothing. Teach one in `[run.agents]`: ```toml