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 (
SealedSecretProviderisNoneby 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
- Have a worker running. If not, see Quickstart.
- Know what a workflow step is. If not, see First agent.
The five modes at a glance
| Mode | Worker | Sandbox | Identity | CLI agent(s) | Artifact dest | Best for |
|---|---|---|---|---|---|---|
| In-process | yes | — | — | — | — | Pure orchestration: planning, summarising, simple Q&A, lightweight transforms |
| Sandboxed | yes | yes | — | — | — | Untrusted tool execution with no per-customer creds (e.g. run user's Python snippet) |
| Identity-scoped | yes | yes | yes | — | optional | Tool execution with customer creds (read their S3, query their DB, call their API) |
| CLI-agent | yes | yes | yes | yes | yes | Real user task: build a website, fix a bug in a repo, generate a report and push it |
| CLI-agent + JIT credentials | yes | yes | yes + callback | yes | yes | Like 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_URLfrom the customer's identity profile. - "Fetch from the customer's S3 bucket and process" — needs
AWS_ACCESS_KEY_IDandAWS_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:
github—git pushto a target repo, usingGITHUB_TOKENfrom the identity profiles3—aws s3 sync /workspace s3://bucket/path, using AWS creds from the identity profilelocal—cp /workspace /host-mounted/path, target path passed by the workernone— destination is the workflow output (the Sagewai agent reads/workspaceand 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
| Dimension | in-process | sandboxed | identity-scoped | CLI-agent | CLI-agent + JIT |
|---|---|---|---|---|---|
| Step latency overhead | ~ms | container start (sec; <100ms pooled) | + identity resolution (~tens of ms) | + CLI startup (varies) | + callback latency |
| LLM cost paid by | operator | n/a (no LLM by default) | n/a or operator-side tools | customer | customer |
| Customer credentials accessible | no | no | yes (env-injected) | yes (env-injected) | yes (env + JIT) |
| Network isolation | host network | sandbox network policy | sandbox network policy | sandbox network policy | sandbox network policy |
| Filesystem isolation | host fs | container fs | container fs + identity | + /workspace + artifact dest | + JIT creds |
| Audit coverage | n/a | n/a | full | full | full + callback events |
| Replay-determinism | trivial (pure code) | sandbox env = empty (trivial) | replay original identity values | replay original identity + CLI prompts/responses | replay 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
-
One mode for the whole workflow. Setting
sandbox_mode=PER_RUNat the workflow level forces every step into the sandbox, making the cheap orchestration steps needlessly expensive. -
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.
-
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.
-
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.
-
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
- Runtime topology — how workers, sandboxes, and the control plane fit together
- Security tiers — how credentials and trust boundaries are layered
- Sandbox backends — Docker, Kubernetes, Lambda, and how to pick one
- Sandboxing guide — the operator-side recipe for configuring a sandboxed worker
- Quickstart — runs a CLI-agent-mode workflow end-to-end