---
name: Sandboxed Sh Missions
slug: sandboxed-sh-missions
category: Automation
description: Sandboxed Sh Missions delegates coding or automation work to isolated container missions with a chosen agent profile and self-contained prompt. Use it to launch a mission, spawn a worker, or run multi-step work in a clean environment.
github: "https://github.com/Th0rgal/sandboxed.sh/tree/master/skills/sandboxed-sh-missions"
language: Rust
stars: 491
forks: 50
install: "npx degit https://github.com/Th0rgal/sandboxed.sh/tree/master/skills/sandboxed-sh-missions ~/.claude/skills/sandboxed-sh-missions"
installs_to: ~/.claude/skills/sandboxed-sh-missions
source_path: skills/sandboxed-sh-missions/SKILL.md
collection_size: 8
category_size: 1523
collection_url: "https://dirskills.com/collections/Th0rgal/sandboxed.sh"
added: 2026-08-26T05:13:08.497Z
last_synced: 2026-08-26T05:13:08.497Z
canonical_url: "https://dirskills.com/skills/sandboxed-sh-missions"
---

# Sandboxed Sh Missions

Sandboxed Sh Missions delegates coding or automation work to isolated container missions with a chosen agent profile and self-contained prompt. Use it to launch a mission, spawn a worker, or run multi-step work in a clean environment.

**Install:**

```bash
npx degit https://github.com/Th0rgal/sandboxed.sh/tree/master/skills/sandboxed-sh-missions ~/.claude/skills/sandboxed-sh-missions
```

## README

# Sandboxed.sh Missions

Delegate coding/automation tasks to **isolated containerised missions** via the `mcp_sandboxed_assistant_*` MCP. Each mission runs in a chosen workspace (a fresh container with a known set of init scripts and pre-installed tools) and executes the prompt autonomously via a configured agent. Intermediate tool calls stay in the mission's own context.

A conversational `start_mission` is a **worker of this chat**. Hermes stamps `origin_session_id`, enrolls the mission, and the terminal webhook folds the result back here. End the turn after dispatch — do not poll, and do not invent a cron just to wait. Controller/cron ticks are different: they pass `project` and report on the next tick / project route.

This is **not** the same as delegating to a CLI coding agent (Claude Code, Codex, OpenCode) via the `terminal` tool. The MCP runs an entire conversation loop inside the container; the CLI agents are interactive programs you spawn in a single `terminal()` call. Use this skill for isolated multi-step research/coding, or work that needs a specific pre-baked workspace (e.g. `tailscale-ubuntu`, `minecraft`, `dgx-spark`).

## When to use

- User says "lance une mission", "sandboxed", "spawn a worker", "delegate to a coder in a clean room".
- Task is non-trivial: a project to scaffold, a refactor, a test suite, a multi-file code review.
- You want isolation: the work must not touch the host's working state, dependencies, or secrets.
- The task benefits from a specific workspace's pre-installed tools (Python/uv, Node/bun, gh CLI, Tailscale, etc.).

**When NOT to use:**
- Single trivial edit → `patch` / `write_file` is faster and cheaper.
- Task needs back-and-forth with the user → subagents can't use `clarify`.
- You need a long-running daemon → use `cronjob` or `terminal(background=true)` instead. Missions terminate when the agent emits its final message.
- Task is purely a tool call with no reasoning → `execute_code` is more direct.

## The MCP surface

| Tool | Purpose |
|------|---------|
| `mcp_sandboxed_assistant_list_workspaces` | List all workspaces (containers) you can target. |
| `mcp_sandboxed_assistant_start_mission` | Launch a new mission: pick workspace + agent + prompt. |
| `mcp_sandboxed_assistant_get_mission` | Fetch a mission's current status and metadata. |
| `mcp_sandboxed_assistant_get_mission_events` | Read transcript/trace of what the agent did. |
| `mcp_sandboxed_assistant_list_missions` | List recent missions (optionally filtered by status). |
| `mcp_sandboxed_assistant_list_active_missions` | Only the in-flight ones (pending/active/blocked/awaiting-user). |
| `mcp_sandboxed_assistant_send_message_to_mission` | Resume a mission with a follow-up prompt (for `awaiting_user` missions). |
| `mcp_sandboxed_assistant_cancel_mission` | Stop a mission. Returns "not found" if the mission is already gone — that's normal. |

### Terminology: backend, workspace, host, and remote node are different layers

When a user asks which “servers/backends” sandboxed.sh uses, answer by layer instead of conflating them:

1. **Control-plane host** — runs sandboxed.sh production and orchestrates mission state.
2. **Workspace** — container/host execution environment selected by `workspace_id`; examples include project workspaces and the dedicated `dgx-spark` workspace.
3. **LLM backend** — the `backend` field (`codex`, `claudecode`, `opencode`, `gemini`, `grok`); this selects the agent/model transport, not a physical machine.
4. **Remote node/build worker** — extra compute reached through sandboxed-node or `/api/remote-build`; current documented general runners are `babylon`, `nippur`, and `ashur`, while the Lean build fleet also includes `dgx-spark`.
5. **Adjacent runner** — e.g. a GitHub Actions self-hosted runner. This is not automatically a sandboxed.sh remote node even if it runs on the same machine.

For inventory questions, report all SSH-reachable machines separately from the subset registered as sandboxed.sh compute. Date any “currently deployed” claim unless live state was checked via `GET /api/remote-nodes` and `GET /api/health/fleet`.

**Production DGX routing incident rule.** If `disk-sentinel` reports only `dgx-spark` unreachable, check `systemctl is-enabled tailscaled`, `systemctl is-active tailscaled`, and `tailscale ping -c 2 100.77.4.93` on `agent-core` before changing keys or topology. The expected recovery is `systemctl enable --now tailscaled`, followed by strict-host-key SSH and a silent sentinel run. Do not recreate an `old-agent` tunnel: `old-agent` is compute-only.

## Picking the agent

The `agent` field is **one of the agent names registered on the platform**. The two universal agents are `build` and `plan`. Some setups also offer `ana` (audit) and `paloma` (coordinator), but **availability varies per installation and workspace** — do NOT assume all four are available. The right one depends on the work:

- `build` — general implementation agent. Use for "build this project", "scaffold this repo", "implement feature X". Default for coding tasks.
- `plan` — research/planning agent. Use for "investigate options", "draft an architecture", "compare libraries", "write a draft", "research and summarize". No code (or minimal code). Default for research/article/analysis tasks.
- `ana` — analytical/audit agent. **May not be available** — check first or use `build` if you just need code review.
- `paloma` — coordinator/operator agent. **May not be available.**

> **Pitfall — agent availability and naming vary by deployment.** `agent` is a registered platform profile and `backend` selects the execution backend; they are logically separate, but current deployments may register backend-aligned agent IDs such as `codex` in addition to profiles such as `build` and `plan`. Do not hard-code a universal agent list. Use the live tool schema/catalog or a previously verified route. For Codex, always set `backend="codex"` and the exact OpenAI model ID in `model_override`; use the deployment's accepted agent profile (`codex`, `build`, or `plan`) and verify the created mission records the requested `agent`, `backend`, `model_override`, and `model_effort`. A mission merely reaching `pending` is not enough—re-read it after startup and require execution evidence before calling the route healthy.

The `backend` parameter (`opencode`, `claudecode`, `codex`, `gemini`, `grok`) selects the LLM backend, not the agent — this is the underlying model the agent uses.

### User-mandated model runs: bound, verify, then independently gate

When the user explicitly requests a particular non-default model, honor the request with the exact backend/model identifier and verify the created mission records that identifier plus an `active` status shortly after dispatch. Treat that worker as a bounded implementation or investigation lane, not as the sole merge or architecture authority:

1. Give it a concrete scope, reproduction/validation criteria, and explicit no-go boundaries (for example: no merge, no legal attestation, no secrets).
2. Require it to distinguish source evidence from assumptions and return verifiable handles for every external change.
3. Independently verify pushed commits, tests, PR state, and any production claim before reporting success.
4. Route material architecture decisions or merge readiness through the normal stronger/latest-head review gate unless the user explicitly waives it.

This keeps a user-requested model useful without converting its self-report into an unverified operational decision.

### Codex `/goal` objective length limit

Codex goal mode has a strict objective-size limit: if the prompt starts with `/goal ` and the objective is too long, the mission can immediately fail with:

```text
codex thread/goal/set failed: goal objective must be at most 4000 characters
```

Recovery pattern:
1. Keep `/goal` itself short (under ~3500 characters to leave margin).
2. Put only the durable objective, constraints, and final-output schema in the `/goal` text.
3. Move bulky context into linked repo docs, a branch issue, project files, or a follow-up non-goal prompt if the platform supports it.
4. Relaunch a fresh mission with the shortened `/goal`; do not keep retrying the same oversized objective.

This limit is separate from model context size. A model can handle the information, but the Codex goal API rejects overlong goal objectives before execution starts.

### Backend tiering for cost optimisation (multi-provider routing)

The `opencode` backend is the **universal router** — it can target ANY provider model via `model_override`, including non-OpenAI/Anthropic providers that the other backends can't reach (Z.AI GLM, Minimax, Kimi/Moonshot, Cerebras, Spark, Virtuals). The other backends (`claudecode`, `codex`, `gemini`, `grok`) are locked to their respective providers.

This enables a **cost-tiering strategy** when the user has "unlimited" quota on alternative providers:

| Tier | Backends | Use for |
|------|----------|---------|
| **Premium** (subscription-limited) | `codex` (approved GPT-5.6 Terra/Sol routes), `claudecode` (current approved Claude route) | Critical code, hard proofs, deep reasoning — tokens are scarce, use them where they matter |
| **Alternative** (often unlimited) | `opencode` + `model_override=glm-5.2` / `MiniMax-M3` / `moonshotai-kimi-k2-7-code` | Research, article drafting, investigation, documentation — tasks that benefit from a model but don't need frontier-tier reasoning |

**How to dispatch on an alternative provider:**
```python
mcp_sandboxed_assistant_start_mission(
    agent="plan",           # research/planning agent
    backend="opencode",     # the universal router
    model_override="glm-5.2",   # or "MiniMax-M3", "moonshotai-kimi-k2-7-code", etc.
    ...
)
```

**Discovering available providers and their model IDs:**
```python
# GET /api/ai/providers (JWT-authenticated) returns:
# - id (UUID), name, provider_type, use_for_backends[]
# - Models live in the static catalog: GET /api/providers → {providers: [...]}
# Provider types seen (June 2026): anthropic, openai, google, xai, cerebras,
# zai, minimax, kimi, spark
```

**Fable 5 via `claudecode` only.** Virtuals is deprecated and no longer available. To use Fable 5, launch via `backend="claudecode"` with `model_override="claude-fable-5"`. Do NOT use `opencode` + `virtuals/claude-fable-5` — Virtuals has been removed from the provider catalog.

**Model ID gotchas:**
- Z.AI: `glm-5.2`, `glm-5.1`, `glm-5-turbo` (lowercase, hyphenated)
- Minimax: `MiniMax-M3`, `MiniMax-M2.7` (camelCase as in catalog)
- Kimi: `moonshotai-kimi-k2-7-code` (not bare `kimi-k2.7`)
- If `model_override` fails, the error message lists valid IDs — copy from there

**User-mandated model that produces no output:** do not silently replace it with another model and present the substitute as its opinion. Retry once with a sharply bounded, direct-output prompt (for example: no tools/web, explicit word cap and output schema). If the same model again reaches an idle/stall timeout without content, report that it was successfully addressed but returned no usable answer, then synthesize only from workers that actually produced output. Preserve the distinction between provider failure and research conclusions.

**The routing mental model:** `opencode` is a superset of the provider surface. If a provider's `use_for_backends` includes `opencode`, you can route to it. If it only lists a specialised backend (e.g. `codex` for OpenAI), you MUST use that backend and cannot reach it via `opencode`.

> **Pitfall — `model_override` needs the exact catalog ID.** To override which LLM a backend uses, pass `model_override` — but it must match the provider's catalog ID exactly. Anthropic uses `claude-<family>-<ver>` with hyphens, not the shorthand leaderboard name: `claude-opus-4-8` ✅, NOT `opus-4.8` ❌ (the leaderboard/website may call it "opus-4.8", but the API catalog does not). If you get `Model 'X' not found in <provider> catalog`, the error message lists every valid ID — copy the exact string from there. Other providers (OpenAI, etc.) generally accept their public model names directly.

> **Pitfall — `model_effort` caps differ per backend and may change by model/backend version.** The `model_effort` field accepts `low/medium/high/xhigh/max`, but **not all backends honor all values**. Historically `codex` rejected `"max"`; recent `codex` + `gpt-5.5` missions have accepted `"xhigh"` and moved to `active` successfully. If a user explicitly requests `codex` + `xhigh`, pass it through and verify the mission status immediately with `get_mission` / `list_active_missions`. If launch fails with an effort-related error, drop one level (`"max"` → `"xhigh"` → `"high"`) until accepted. When no model/effort is specified, `"high"` remains the safest cross-backend default.

> **Pitfall — `opencode` backend can crash on non-standard `model_override` values with `ProviderModelNotFoundError`.** Seen in the Beal campaign (June 2026): launching `opencode` with `model_override="glm-5.2"` or `model_override="MiniMax-M3"` (passed as if they were model IDs) returned `awaiting_user` status with `ProviderModelNotFoundError`. The `opencode` routing treats the override as a **provider identifier** for some configurations, not a model name, even though the same `model_override` is documented to work as a model name in other contexts. **Workaround**: use an approved reliable route for critical work (for Verity: `codex` + `gpt-5.6-terra`), and **always verify the dispatch succeeded** by checking `get_mission` status within 30s. If it is terminal before repo work, do not treat it as a model result or burn a cache build on a doomed mission.

> **Pitfall — a shared OpenCode adapter crash is an infrastructure incident, not three independent bad-model results.** A launch can pass creation, run the one-time OpenCode database migration, then terminate before any repository command with `fn3 is not a function` (observed identically for `glm-5.2` and `MiniMax-M3`). When two or more alternative-provider scopes fail at that same initialization boundary: (1) classify them as failed transport attempts with no proof/review evidence; (2) stop further OpenCode launches rather than serially burning surplus quota; (3) start exactly one bounded `sandboxed-sh-dev` repair mission on a reliable route, requiring an alternative-override regression test and a PR; and (4) keep critical work moving only on approved reliable routes, respecting Codex OAuth launch staggering. After the fix is merged/deployed, validate with one narrow no-push alternative-provider probe before relaunching deferred workers.

## Picking the workspace

`list_workspaces` returns the full set. The right choice depends on what tools the task needs. Key workspace properties to check:

- `init_scripts` — e.g. `base`, `uv-python`, `bun-mcp`, `github-cli`, `tailscale`, `browser-x11`. These are the only things pre-installed.
- `mcps` — additional MCPs the agent gets in-container (e.g. `google-calendar`, `orchestrator`).
- `env_vars` — secrets/tokens injected at start (e.g. `GH_TOKEN`, `GOOGLE_OAUTH_CREDENTIALS`).
- `tailscale_mode` — if you need network reach through the tailnet.
- `status` — `ready` is what you want; `pending` means the container hasn't been started yet and may take minutes to initialise.

> **Secret-safe workspace discovery.** Treat a workspace record as potentially secret-bearing, even for a read-only REST lookup: some control-plane responses can include `env_vars` values rather than just their names. For selection, reduce the response at the source to only `id`, `name`, `status`, `init_scripts`, `skills`, and an **array of environment-variable names**. Never print or retain the `env_vars` map. If an inspection nevertheless exposes a credential-like value, stop expanding that output, do not copy it into prompts/reports/other calls, and report only the exposure class and endpoint to the security owner for normal rotation/remediation.

### Backend-specific workspace-visibility canary

A mission record carrying `workspace_id` / `workspace_name` does **not** prove that its selected backend sees the intended workspace mount or project checkout. This matters for MCP canaries: one backend can reach the project while another starts in an isolated empty mission directory.

Before declaring a workspace-dependent canary successful, require all of:
1. the expected checkout/project path exists in that mission's execution scope;
2. its Git ref and project pin (`lean-toolchain`, when relevant) can be read there;
3. an actual registered MCP tool resolves a file from that checkout, not merely that its tool name is callable.

If the route sees only an ephemeral mission directory and MCP calls report project/file-not-found, classify it as a **workspace-mount/configuration defect**, not a Lean or MCP semantic failure. Keep the canary read-only and retain only concise path/tool-result evidence. When authorized, dispatch at most one bounded no-deploy control-plane repair on a reliable route; require a regression covering backend mount/project-path propagation and prohibit it from modifying the target workspace, checkouts, credentials, or permissions. Re-run the canary only after the repair is independently verified.

### Stored GitHub auth may not appear in workspace `env_vars`

A workspace inventory with no `GH_TOKEN` / `GITHUB_TOKEN` does **not** by itself prove authenticated GitHub access is unavailable: it may have valid stored `gh` and git credentials. Before recording an auth blocker for an existing-branch repair, run this secret-safe probe in the **same workspace** and retain only booleans:

```bash
# Do not print login, token, credential-bearing remote, or git config.
env -u GH_TOKEN -u GITHUB_TOKEN gh api user --jq .login >/dev/null
gh repo view <owner>/<repo> --json nameWithOwner >/dev/null
git ls-remote https://github.com/<owner>/<repo>.git HEAD >/dev/null
echo 'github_api_capability=ok; git_read_capability=ok'
```

This proves authenticated API/read transport in that execution scope and prevents a false external-auth blocker. It does **not** prove normal push transport; require the worker to re-check that immediately before a guarded non-force push. It also does not waive any controller policy that explicitly requires propagation of a named environment secret: record that distinction rather than inventing a secret name or exposing stored credentials.

### Known workspace init-script matrix (June 2026)

| Workspace | Base | Python | gh CLI | Tailscale | Browser | Notable extra |
|-----------|------|--------|--------|-----------|---------|---------------|
| `assistant` | ubuntu-noble | uv | yes | no | no | google-calendar MCP, bun-mcp, bitwarden-secrets skill |
| `misc` | ubuntu-noble | uv | yes | no | no | orchestrator MCP, bitwarden-secrets |
| `dumbcontracts` | ubuntu-noble | uv | yes | no | no | ORACLE env (large), 32G memory limit, vercel-cli |
| `minecraft` | ubuntu-noble | uv | yes | no | x11 | minecraft-shard, deployment-management, vercel-cli |
| `dgx-spark` | tailscale-ubuntu | uv | yes | yes | no | Tailscale exit node, dgx-spark skill |
| `sandboxed-sh-dev` | browser-tailscale | uv | yes | yes | x11 | design-taste-frontend skill, shared_network=true |
| `host` | (host machine) | (host Python) | yes | no | no | Path: `/root`. Use for "skip the container, run on my actual host." |

> **Critical pitfall — `assistant` workspace has NO coding-agent CLI installed.** Missions in `assistant` cannot invoke `opencode`, `codex`, or `claude` (the Anthropic CLI), AND the workspace lacks `curl`, `wget`, `npm`, and `bun` globally. The `bun-mcp` init script only enables `bun` for MCP server processes — not for shell-level installs. Errors surface as one of:
> - `OpenCode CLI 'opencode' not found and neither curl nor wget is available in the workspace.`
> - `Codex CLI 'codex' not found and neither npm nor bun is available in the workspace.`
> - `Claude
