Execution modes

This page helps you decide how each step of a Sagewai workflow should run. After reading, you'll know which of the five execution modes to pick for a given step, what each one costs, and how to mix them inside a single workflow.

Modes are per-step, not per-deployment. A single workflow run can plan in-process, build a site in a fully isolated sandbox with a CLI agent, and summarise the result in-process again — each step picks the cheapest mode that still satisfies the isolation and capability it needs.

Status note — Sealed runtime enforcement. This page describes the designed behaviour of the identity-scoped, CLI-agent, and JIT-credential modes. The pieces that depend on Sealed runtime enforcement — live secret injection into a running sandbox, redaction at the tool-runner boundary, per-key / per-CLI ACL filtering, replay-safe injection, and mid-run (hard-revoke) abort — are experimental and maturing: they are built and tested but are not yet wired into the default worker path (SealedSecretProvider is None by default). What ships today is the per-workload identity model, an external secret backend (HashiCorp Vault), admin profile/secret controls, enqueue-time cascade resolution, and fail-closed revocation checks at enqueue. See the v1.0 status for the full shipped-vs-experimental breakdown.

Because that runtime enforcement is not yet wired, the identity-scoped, CLI-agent, and JIT-credential modes are preview-gated. In multi-tenant mode they are refused for tenants unless the operator explicitly sets SAGEWAI_SEALED_PREVIEW=1 — a tenant must never receive an unenforced Sealed identity by default. In single-org mode the trusted self-hosted operator may run them at their own risk (the gate allows them, behaviour unchanged). The gate fires at both the run-submit API (403) and a worker backstop (the run is failed before it executes), so non-API paths like replay are covered too. The in-process and sandboxed-only modes are never preview-gated.

Before you start

The five modes at a glance

ModeWorkerSandboxIdentityCLI agent(s)Artifact destBest for
In-processyesPure orchestration: planning, summarising, simple Q&A, lightweight transforms
SandboxedyesyesUntrusted tool execution with no per-customer creds (e.g. run user's Python snippet)
Identity-scopedyesyesyesoptionalTool execution with customer creds (read their S3, query their DB, call their API)
CLI-agentyesyesyesyesyesReal user task: build a website, fix a bug in a repo, generate a report and push it
CLI-agent + JIT credentialsyesyesyes + callbackyesyesLike CLI-agent, but the agent can request credentials it doesn't have at runtime

Each @workflow.step(...) decorator picks its own mode. A workflow that "reads a brief, builds a site, summarises what it did" is typically in-process → CLI-agent → in-process.

In-process mode

The simplest mode. The worker process executes the step directly; no isolation, no sandbox, no identity injection.

Topology:

Worker process
  └── Sagewai agent runs the step inline
        └── reads inputs from postgres
        └── calls operator-side LLM if needed (worker env keys)
        └── writes output to postgres

Cost: essentially free (no container start, no network bridging, no env injection).

Security: the step has full access to the worker host process. Anything the worker can do, the step can do. Use only for code you control — never for code the user wrote or untrusted CLI invocations.

Use it for:

  • "Summarise the previous step's output in plain English" — reads postgres, calls a cheap planning LLM, writes summary.
  • "Decide which CLI agent to dispatch next based on input shape" — pure planning.
  • "Validate the user's input against a JSON schema" — no IO, no LLM.

Don't use it when:

  • The step calls user-provided code, scripts, or shell commands.
  • The step needs access to customer credentials.
  • The step calls external APIs that should be rate-limited or audited per customer.

Sandboxed mode

The step runs in a sandbox container with empty env. Useful for executing code Sagewai doesn't trust, but where no customer credentials are needed.

Topology:

Worker process
  └── Sagewai agent
        └── acquires sandbox (Docker/K8s/Lambda) with EMPTY env
        └── dispatches tool execution via the tool runner
        └── writes output to postgres

Cost: sandbox start (~2–8s cold; ~50–100ms warm with the sandbox warm pool).

Security: isolation is the trust boundary. Network policy applies (none, egress_only, or full). Filesystem is container-local; nothing persists by default.

Use it for:

  • Running a user-supplied Python snippet and returning the result.
  • Executing an unverified shell command from a workflow input.
  • Running a code linter or formatter on user-provided code.
  • Generating a thumbnail with imagemagick — needs isolation in case of a bad input file, but no credentials.

No customer credentials are injected in this mode. The sandbox has the standard container env only (PATH, HOME). For per-customer secrets, use identity-scoped mode below. For how Sagewai's identity layer works, see Security tiers.

Identity-scoped mode

Sandboxed mode plus a per-customer identity is injected into the sandbox env. Tools running inside can read customer credentials and behavior knobs.

Topology:

Worker process
  └── Sagewai agent
        ├── resolves the identity for this run (system + workflow + user)
        │   re-resolves at sandbox-start time so revoked keys don't leak
        ├── acquires sandbox; backend injects env from the resolved identity
        └── dispatches tool execution via the tool runner
              └── tools read os.environ for creds

Cost: sandboxed-mode cost plus identity resolution (~10–30ms for a typical 1–3 level cascade against the built-in backend; ~100ms+ for external backends like Vault).

Security: all of sandboxed-mode plus per-customer credential scoping. By design, every key injection, every cascade resolution, and every revocation interaction is audited; with no CLI agent in the picture, no LLM keys leave the sandbox unless a tool you wrote calls one. Cascade resolution and fail-closed revocation checks run at enqueue today; the runtime side of this — injecting the resolved identity into a live sandbox and auditing each injection — is part of Sealed's experimental runtime enforcement (see the status note above), not yet wired into the default worker path.

Use it for:

  • "Query the customer's PostgreSQL database, return summary" — needs CUSTOMER_DB_URL from the customer's identity profile.
  • "Fetch from the customer's S3 bucket and process" — needs AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
  • "Call the customer's internal API with their auth token" — needs CUSTOMER_API_TOKEN.

Choose identity-scoped over CLI-agent when the work is deterministic and well-bounded. CLI-agent mode is for open-ended tasks where the LLM needs to make choices about what to do. Identity-scoped is for "execute this specific operation with the right credentials."

CLI-agent mode

Identity-scoped mode plus the tool runner spawns a CLI agent (Claude Code, Codex, Gemini, custom) inside the sandbox. The CLI agent does the work; the Sagewai agent on the worker host orchestrates.

This is the mode the Quickstart demonstrates.

Topology:

Worker process
  └── Sagewai agent (host)
        ├── decides which CLI to invoke + with what prompt
        ├── acquires sandbox with identity (customer keys + artifact creds)
        ├── dispatches via the tool runner:
        │     "claude-code-cli run --prompt='build portfolio site'
        │      --workdir=/workspace"
        └── streams stdout/stderr back from sandbox

Sandbox
  ├── Identity env: ANTHROPIC_API_KEY, GITHUB_TOKEN, …
  ├── tool runner spawns CLI as subprocess
  └── CLI:
        ├── reads ANTHROPIC_API_KEY from os.environ
        ├── calls Anthropic API directly (network egress from sandbox)
        ├── edits files in /workspace
        └── on completion: pushes to artifact destination
              (git push using GITHUB_TOKEN, or aws s3 sync, or cp)

Cost: identity-scoped-mode cost plus CLI agent runtime (varies — Claude Code on a complex prompt can take 5–30 minutes). LLM API costs are charged to the customer's keys, not the operator's.

Security: the CLI runs inside the sandbox boundary. By design its LLM keys are sandbox-only, its filesystem access is /workspace (or wherever the image variant configures), and network policy applies — typically egress_only to the LLM provider plus the artifact destination. The sandbox-only credential guarantee depends on Sealed's experimental runtime enforcement (live injection plus redaction at the tool-runner boundary), which is maturing and not yet wired into the default worker path — see the status note above.

Use it for:

  • "Build a portfolio website from this brief" → Claude Code
  • "Refactor this Python module for performance" → Codex or Claude Code
  • "Generate a marketing landing page in Next.js" → Gemini CLI or Claude Code
  • "Run linters + tests + open a PR with fixes" → Codex

The sandbox image determines which CLI agents are available. The variant catalog (sagewai/sandbox-claude-code, sagewai/sandbox-multi, …) is operator-curated; workflows declare which variant they need.

Configure where the artifact goes. CLI-agent mode implies output. Pick a destination per workflow:

  • githubgit push to a target repo, using GITHUB_TOKEN from the identity profile
  • s3aws s3 sync /workspace s3://bucket/path, using AWS creds from the identity profile
  • localcp /workspace /host-mounted/path, target path passed by the worker
  • none — destination is the workflow output (the Sagewai agent reads /workspace and persists it to postgres on step completion)

CLI-agent + JIT credentials mode

Pick this mode when you can't enumerate every credential the CLI will need at the time you enqueue the run. The CLI agent or the tool runner can request a credential it doesn't have, and the Sagewai agent on the host evaluates the request against your policy (auto-approve, deny, or human-in-the-loop).

This mode builds on Sealed's experimental runtime enforcement — live injection into a running sandbox plus the JIT human-in-the-loop credential path — which is built and tested but not yet wired into the default worker path. Treat the behaviour described here as the designed contract; see the status note above for what ships today.

The tool runner channel is bidirectional in this mode: the host dispatches into the sandbox as before, and the sandbox can call back out to ask for credentials.

Topology (additional flow on top of CLI-agent mode):

Sandbox (CLI agent or tool runner)
  └── needs credential X not in current env
        ↓ callback
        request_credential(name="ANOTHER_REPO_TOKEN",
                           scope="github:push",
                           reason="user said push to repo Y")
        ↓
Worker process: Sagewai agent
  └── policy engine evaluates the request:
        ├── auto-approve (matches a policy rule)
        ├── deny (forbidden by policy)
        └── HITL (requires operator approval via admin UI)
        ↓ if approved:
  └── identity lookup or dynamic creation
        ↓ inject value into running sandbox env
  └── audit: requested → approved → delivered

Cost: CLI-agent-mode cost plus a per-callback round-trip (sandbox ↔ host) plus policy evaluation (~ms) or human-in-the-loop approval delay (operator-bounded).

Security: every callback is audit-logged. The injection mechanism (e.g., docker exec env-set, a Kubernetes pod env update via projected secret reload, or a Lambda env update via reinvocation) limits blast radius — the new credential lives in this sandbox only, not propagated to siblings in the pool.

Use it for:

  • Claude Code is asked to push to a repo not pre-configured. It requests GITHUB_TOKEN_REPO_X. Operator policy auto-approves writes to repos under your org, so the token is injected.
  • Codex needs to call a third-party API the operator hasn't pre-authorised. It requests THIRD_PARTY_API_KEY. Policy: HITL required for new API keys. The admin UI surfaces the request; an operator approves; the key is injected.
  • A tool needs to escalate to a more powerful AWS role. It requests AWS_ROLE_ARN_X. Policy denies escalations to admin roles, so the request is rejected.

Choose this over CLI-agent when the workflow is open-ended enough that you can't enumerate all credentials at enqueue time. For closed workflows ("build a site, push to repo X"), use CLI-agent mode with that repo's creds pre-set.

Decision tree: which mode for which step?

Is the step pure orchestration (planning, summarising, validation)?
  └── YES → in-process
  └── NO ↓

Does the step need customer-specific credentials?
  ├── NO → Does the step run untrusted code or call shell/tools?
  │         ├── NO  → in-process (just trusted Sagewai code)
  │         └── YES → sandboxed
  └── YES ↓

Does the step invoke a CLI agent (Claude Code, Codex, Gemini, …)?
  ├── NO  → identity-scoped
  └── YES ↓

Are all credentials needed at start known at enqueue time?
  ├── YES → CLI-agent
  └── NO  → CLI-agent + JIT credentials

Cost, security, and capability trade-offs

Loading diagram...
Dimensionin-processsandboxedidentity-scopedCLI-agentCLI-agent + JIT
Step latency overhead~mscontainer start (sec; <100ms pooled)+ identity resolution (~tens of ms)+ CLI startup (varies)+ callback latency
LLM cost paid byoperatorn/a (no LLM by default)n/a or operator-side toolscustomercustomer
Customer credentials accessiblenonoyes (env-injected)yes (env-injected)yes (env + JIT)
Network isolationhost networksandbox network policysandbox network policysandbox network policysandbox network policy
Filesystem isolationhost fscontainer fscontainer fs + identity+ /workspace + artifact dest+ JIT creds
Audit coveragen/an/afullfullfull + callback events
Replay-determinismtrivial (pure code)sandbox env = empty (trivial)replay original identity valuesreplay original identity + CLI prompts/responsesreplay original + cached callback results

Mixing modes within a workflow

Real workflows almost always mix modes. Each @workflow.step(...) decorator picks its own mode independently.

@workflow.step("plan")           # in-process (default)
async def plan(brief: str) -> dict:
    """Pure orchestration step — runs inline on worker."""
    return await agent.plan(brief)

@workflow.step(
    "build_site",
    sandbox_mode=SandboxMode.PER_RUN,
    security_profile_ref="customer-portfolio",
    cli_agent="claude-code",
)                                # CLI-agent mode
async def build_site(plan: dict) -> str:
    """Sandbox + identity + CLI agent + artifact dest."""
    return await dispatch_cli(plan)

@workflow.step("summarise")      # in-process (default)
async def summarise(artifact_path: str) -> str:
    """Pure orchestration."""
    return await agent.summarise_changes(artifact_path)

Worked example: build a customer's portfolio site

A typical in-process → CLI-agent → sandboxed → in-process pipeline:

Step 1 — receive_brief                                 in-process
  Input: customer's natural-language brief
  Reads: workflow input
  LLM call: "extract structured requirements" (cheap planning model,
            operator-side keys)
  Output: JSON {style, sections, target_repo, ...}
  Cost: ~500ms

Step 2 — scaffold                                      CLI-agent
  Input: requirements JSON, target repo URL
  Acquires: sandbox (image variant claude-code)
  Identity profile "portfolio-customer-X" injected:
    ANTHROPIC_API_KEY (Claude Code uses)
    GITHUB_TOKEN (push artifact)
  Tool runner spawns Claude Code:
    claude-code run --prompt="scaffold Next.js site per JSON brief"
  Claude Code calls Anthropic, edits /workspace
  On completion: git push origin main → customer's repo
  Cost: ~5–15 min, customer-side LLM tokens (Claude Sonnet)

Step 3 — verify                                        sandboxed
  Input: target repo URL (just-pushed commit SHA)
  Acquires: sandbox (image variant base — no creds needed)
  Tool runner runs:
    git clone <url> /tmp/check
    cd /tmp/check && npm ci && npm run build
  Returns: {build: "ok"} or {build: "failed", error: "..."}
  Cost: ~30–60s, no LLM
  No identity needed — the repo is public-readable for the build.

Step 4 — summarise                                     in-process
  Input: build result
  LLM call: "write a one-paragraph completion message"
  Output: human-friendly status to webhook / Slack
  Cost: ~500ms

Net result: one customer LLM bill (Step 2), zero customer credentials touched outside the sandbox, and a full audit trail covering every identity resolution, key injection, and sandbox lifecycle event.

Anti-patterns

  1. One mode for the whole workflow. Setting sandbox_mode=PER_RUN at the workflow level forces every step into the sandbox, making the cheap orchestration steps needlessly expensive.

  2. CLI-agent mode for an identity-scoped task. If you don't need the CLI agent's open-ended decision-making, don't pay for it. A scheduled "fetch S3, transform CSV, write to DB" job is identity-scoped, not CLI-agent.

  3. Sandboxed mode with credentials in workflow input. "Pass the API key as a step argument" defeats the identity layer entirely. Credentials always flow through identity profiles, never through workflow inputs.

  4. JIT credentials without a real policy. The whole point of CLI-agent + JIT credentials is that the host-side policy decides what's allowed. If your "policy" is "auto-approve everything," you have CLI-agent mode plus extra steps and an attack surface.

  5. In-process for code Sagewai doesn't write. The worker host is the only thing trusted in in-process mode. User-provided shell commands, plugin code, and scripts all belong in sandboxed or stricter modes.

See also