Quickstart — Build a portfolio site with Claude Code

This quickstart takes about 15 minutes. You will:

  1. Install the SDK and bring up an admin server.
  2. Create a Sealed Identity holding your Anthropic and GitHub credentials.
  3. Define a workflow that dispatches Claude Code inside an isolated Docker sandbox to build a portfolio site.
  4. Trigger the workflow and watch the artifact commit to GitHub.

If you want a hello-world agent with no sandbox or per-customer credentials, start with Minimal Setup instead. This page exercises the full stack.

What happens, in one sentence: Sagewai's worker dispatches a CLI agent (Claude Code) inside an identity-isolated Docker sandbox; your customer's credentials are injected at sandbox-start and never touch the worker host. See Architecture: Execution modes — Mode 3 for the full topology.


Prerequisites

  • Python 3.10 or later (uv recommended for venv management)
  • Docker installed and running (the default sandbox backend)
  • An Anthropic API key (Claude Code uses this — Tier-2 in Sagewai terminology)
  • A GitHub personal access token with repo scope (artifact upload uses this — also Tier-2)
  • A target GitHub repository to receive the generated site (an empty repo is fine)
  • An operator-side LLM key for orchestration (the "Tier-1" key — see Security tiers). For this quickstart, set ORCHESTRATION_OPENAI_KEY or use a local Ollama URL.

1. Install Sagewai

uv add 'sagewai[fastapi,postgres]'
# OR with pip:
pip install 'sagewai[fastapi,postgres]'

The fastapi extra brings the admin server. The postgres extra brings durable workflow storage; SQLite works for local exploration but production should use Postgres.


2. Bring up the admin server + a worker

In one terminal, start the admin server:

sagewai admin serve --host 0.0.0.0 --port 8000

Open http://localhost:8000 in a browser and complete the first-time setup wizard (org name, admin email, admin password — see the Admin Panel guide). This creates the operator account.

In a second terminal, start a worker with the Docker sandbox backend:

export ORCHESTRATION_OPENAI_KEY=sk-...   # Tier-1 — orchestration brain

sagewai worker start \
    --pool default \
    --sandbox-mode per_run \
    --sandbox-backend docker \
    --sandbox-image ghcr.io/sagewai/sandbox-claude-code:latest \
    --sandbox-network egress-only

The worker registers with the admin server, advertises capability labels (sandbox.backend=docker, sandbox.image_variants=claude-code), and is ready to claim runs.


3. Create a Sealed Identity for your customer credentials

A Sealed Identity is a named bundle of Tier-2 credentials and behavior knobs. Claude Code reads its API key from os.environ inside the sandbox; the env values come from the Identity at sandbox-start time.

In the admin panel, go to Sealed → Profiles → Create. Name the profile portfolio-customer-X and add:

ANTHROPIC_API_KEY=sk-ant-...     ← Claude Code uses this
GITHUB_TOKEN=ghp_...              ← git push uses this

The profile is stored encrypted at rest (Fernet) in ~/.sagewai/profiles.json. The admin server holds the master key; the worker host and control plane never see the plaintext values again. See Security tiers for the trust boundary.


4. Define the workflow

Note on the code below. The decorator API for per-step modes is still stabilising — the canonical reference is the Execution modes architecture page. The example here matches the shape that page commits to. If a parameter name has evolved by the time you read this, the Workflows reference is the authoritative API.

Create build_portfolio.py:

import asyncio
from sagewai import DurableWorkflow, UniversalAgent
from sagewai.core.stores.postgres import PostgresStore
from sagewai.sandbox import SandboxMode

store = PostgresStore(database_url="postgresql://localhost/sagewai")

planner = UniversalAgent(
    name="planner",
    model="gpt-4o-mini",  # Tier-1 LLM — operator pays
)

wf = DurableWorkflow(name="portfolio-builder", store=store)

@wf.step("plan")
async def plan(brief: str) -> dict:
    """Mode 0 — pure orchestration on the worker."""
    result = await planner.chat(
        f"Extract structured requirements from this portfolio brief: {brief}"
    )
    return {"brief": brief, "requirements": result}

@wf.step(
    "build_site",
    sandbox_mode=SandboxMode.PER_RUN,
    sandbox_image="ghcr.io/sagewai/sandbox-claude-code:latest",
    security_profile_ref="portfolio-customer-X",
    cli_agent="claude-code",
    artifact_destination={
        "kind": "github",
        "repo": "your-org/portfolio-site",
        "branch": "main",
    },
)
async def build_site(plan: dict) -> str:
    """Mode 3 — Claude Code in a sandbox, push to GitHub."""
    return await wf.dispatch_cli_agent(
        prompt=f"Scaffold a Next.js portfolio site per this brief: {plan['requirements']}",
        workdir="/workspace",
    )

@wf.step("summarise")
async def summarise(artifact_url: str) -> str:
    """Mode 0 — orchestration."""
    result = await planner.chat(
        f"Write a one-paragraph completion message for site at {artifact_url}"
    )
    return result

async def main():
    await store.initialize()
    run_id = await wf.enqueue(input_data={"brief": "Modern minimal portfolio for a senior product designer named Sam Park."})
    print(f"Enqueued: {run_id}")

asyncio.run(main())

Three steps, three modes:

StepModeWhy
plan0 (Bare)Pure orchestration; Tier-1 LLM call on the worker.
build_site3 (Full + CLI agent)Claude Code does the actual work, with customer credentials injected into the sandbox env.
summarise0 (Bare)Tier-1 again. No sandbox cost for a one-line summary.

Selecting mode per-step keeps sandbox costs where they are justified — see Execution modes — Mixing modes within a workflow.


5. Run it

python build_portfolio.py

Watch the admin panel's Runs view. You'll see:

  1. plan step completes in ~500ms (Mode 0 — Tier-1 LLM call).
  2. build_site enters running. The worker acquires a Docker sandbox, injects the portfolio-customer-X Identity, and spawns Claude Code as a subprocess. Claude Code edits files in /workspace, then git pushs to your repo using GITHUB_TOKEN. This typically takes 5–15 minutes.
  3. summarise completes in ~500ms.

Check your GitHub repo — there's a portfolio site there. Check the admin Audit view — every secret-injection, cascade-resolution, and pool-reset event is recorded.


What happened

[control plane: admin server]              [your terminal]
    workflow_runs.status='pending'   ←── enqueue
              │
              ▼
[worker fleet: 1 worker]                    Tier-1: ORCHESTRATION_OPENAI_KEY
    claims run; resolves Identity
              │
              ▼
[Sagewai Agent on worker host]
    plan step (Mode 0) — Tier-1 LLM
              │
              ▼
[Docker sandbox]                            Tier-2: ANTHROPIC_API_KEY,
    Identity env injected                            GITHUB_TOKEN
              │
              ▼
[tool runner spawns Claude Code]
    Claude Code → Anthropic API → /workspace
              │
              ▼
[git push to your repo]                     artifact destination
              │
              ▼
[control plane: admin server]
    workflow_runs.status='completed'

Three things to note:

  1. Your Tier-2 credentials never reached the worker host. They were injected into the sandbox env directly from the Sealed profile and scrubbed on sandbox release.
  2. The control plane never executed a workflow step. It persisted, scheduled, and queried state — workers did the actual work.
  3. Mode selection was per-step. The cheap orchestration steps ran on the worker; only the CLI-agent step paid the sandbox-startup cost.

Learn the model

Go deeper on the SDK

  • Minimal Setup — the no-sandbox, no-Identity flow if you want to start from a hello-world agent first.
  • Agents — UniversalAgent, composition patterns.
  • Workflows — durable workflows, approval gates.
  • Sandboxing handbook — operator-level sandbox configuration (CLI flags, fallbacks, debugging).

Operate