Skip to main content

Run on Plue

run-on-plue executes a Smithers workflow script on Plue infrastructure instead of the local machine. It uses the <Sandbox> provider seam with createPlueSandboxProvider, and the provider shells through the plue CLI so Plue owns workspace lifecycle, authentication, quotas, and Freestyle VM access. Use it when a child workflow needs a real Plue workspace: a repo-bound VM with SSH, git, jj, Node, network egress, and bootstrapped Claude Code and Codex CLIs.

What it does

The workflow lives at .smithers/workflows/run-on-plue.tsx. It accepts a path to any Smithers .tsx workflow, reads that file, and sends it to a Plue-backed sandbox provider. The provider:
  1. Creates a Plue workspace with plue workspace create --repo <owner>/<repo>.
  2. Polls plue workspace view --format json until the workspace is running and has an SSH command.
  3. Connects over SSH in batch mode.
  4. Installs Bun if missing, then installs @anthropic-ai/claude-code and @openai/codex when those CLIs are absent.
  5. Seeds Claude and Codex auth via plue workspace exec --seed-agent-auth claude,codex.
  6. Ships a small remote project containing the child workflow source, agents.ts, package.json, and input.json.
  7. Runs smithers-orchestrator up in the workspace and reads the remote run result back over SSH.
  8. Deletes the workspace unless keepWorkspace is true.
The parent workflow receives:
{
  status: "finished" | "failed" | "cancelled";
  output?: unknown;
  remoteRunId?: string;
  workspaceId?: string;
}

Requirements

  • plue CLI installed and authenticated on the host running Smithers. Verify with plue auth status.
  • The target Plue server must have workspaces enabled. Local Plue uses SMITHERS_FEATURE_FLAGS_WORKSPACES=true.
  • The Plue server must be configured for real Freestyle workspaces, including the Freestyle API key required by that server (SMITHERS_FREESTYLE_API_KEY or the environment wiring used by your Plue deployment).
  • The repo passed as repo must exist in Plue and be accessible to the authenticated user.
  • Claude auth must be available locally. The preferred path is Claude Code subscription OAuth in the OS credential store; ANTHROPIC_API_KEY only works when that key has usable API billing.
  • Codex auth must be available locally. The preferred path is ~/.codex/auth.json; OPENAI_API_KEY is only a fallback for setups where the Codex CLI honors API-key auth.
  • If plue is not on PATH, set PLUE_BIN or pass plueBin in the workflow input.
Secrets are seeded at run time by the Plue CLI and written into the VM with restrictive file permissions. Do not bake Claude, Codex, OpenAI, Anthropic, or Freestyle credentials into workspace snapshots or committed files.

Run a workflow remotely

Run the included demo child workflow:
bunx smithers-orchestrator up .smithers/workflows/run-on-plue.tsx --input '{
  "script": ".smithers/workflows/plue-demo-child.tsx",
  "repo": "alice/smoke-test"
}'
Pass input through to the remote child:
bunx smithers-orchestrator up .smithers/workflows/run-on-plue.tsx --input '{
  "script": "workflows/review-child.tsx",
  "repo": "acme/app",
  "input": {
    "ticketId": "APP-123"
  }
}'
Use a local or custom CLI binary and keep the workspace for inspection:
bunx smithers-orchestrator up .smithers/workflows/run-on-plue.tsx --input '{
  "script": ".smithers/workflows/plue-demo-child.tsx",
  "repo": "alice/smoke-test",
  "plueBin": "/path/to/plue",
  "keepWorkspace": true
}'

Use the provider directly

run-on-plue is the default wrapper, but workflows can pass the provider object to <Sandbox> directly:
/** @jsxImportSource smithers-orchestrator */
import { createSmithers } from "smithers-orchestrator";
import { Sandbox } from "@smithers-orchestrator/components";
import { createPlueSandboxProvider } from "../lib/plue-provider";
import { childWorkflow } from "./child";
import { z } from "zod/v4";

const { Workflow, smithers, outputs } = createSmithers({
  result: z.object({ summary: z.string() }),
});

const provider = createPlueSandboxProvider({
  repo: "acme/app",
  keepWorkspace: false,
});

export default smithers((ctx) => (
  <Workflow name="remote-child">
    <Sandbox
      id="plue-run"
      provider={provider}
      workflow={childWorkflow}
      input={{ prompt: ctx.input.prompt }}
      output={outputs.result}
      reviewDiffs={false}
      timeoutMs={30 * 60_000}
    />
  </Workflow>
));

Non-interactive caveat

Keep reviewDiffs={false} for Plue runs. <Sandbox> defaults to fail-closed diff review, which expects a local review/approval interaction before applying file changes. The v1 Plue provider is non-interactive: it runs the remote child, maps the remote result into the parent run, and does not open a human approval loop inside the VM. Design child workflows so they return structured output or artifacts. If you need to inspect the remote VM after a failure, run with keepWorkspace: true, then use plue workspace view or plue workspace ssh to connect.

Claude and Codex bootstrap

Remote workspaces may not have all agent CLIs preinstalled. The provider’s bootstrap is idempotent:
  • bun is installed from the official installer when missing.
  • claude is installed with bun i -g @anthropic-ai/claude-code when missing.
  • codex is installed with bun i -g @openai/codex when missing.
  • ~/.bun/bin is prepended to PATH for remote commands.
  • plue workspace exec --seed-agent-auth claude,codex stages Claude Code and Codex credentials before the child workflow runs.
The remote mini-project includes an agents.ts that exports both ClaudeCodeAgent and CodexAgent, so child workflows can import or define tasks that use either CLI once bootstrap completes.