The previous article covered what Claude Opus 4.8's dynamic workflows are — the architecture, the benchmarks, the pricing, and the token explosion risk. This article is the implementation companion: how to actually run a workflow, what to configure before you do, and how to replicate the same pipeline in Python when you need programmatic control. The worked example is a data pipeline code review: each module gets its own agent, agents run in parallel, and the orchestrator aggregates the results into a structured report.

There are two distinct ways to use Claude Opus 4.8 workflows in production. They are not interchangeable — each has a different control model, a different integration surface, and different failure modes. Choosing the wrong one for your context is the most common mistake teams make when first adopting agentic workflows.

Track A
Claude Code CLI
  • Claude decides how to orchestrate
  • 16 concurrent agents max; 1,000 total per run
  • Claude writes a JS orchestration script — you provide direction
  • Best for: open-ended codebase tasks
  • Setup time: minutes (AGENTS.md + one command)
  • Cost visibility: usage.iterations in output
Track B
Python SDK
  • You write the orchestration code
  • Concurrency limited by your Semaphore + rate limits
  • Full control: retry logic, kill switch, custom routing
  • Best for: structured, repeatable pipelines
  • Setup time: hours (orchestrator class + telemetry)
  • Cost visibility: explicit per-call usage tracking

The use case: Python data pipeline code review

The example codebase throughout this article is a standard Python ETL project: four modules, each with known issues that a code review should catch — missing error handling, no type annotations, unsafe SQL concatenation, and untested edge cases. The goal is to have each module reviewed by a dedicated agent, with test generation and fix proposals produced in parallel, then aggregated into a single report.

Project structure — the target codebase
etl_pipeline/
├── AGENTS.md                    # agent instructions (shared by both tracks)
├── src/
│   ├── extractors/
│   │   └── api_extractor.py     # fetches data from external API
│   ├── transformers/
│   │   └── data_transformer.py  # normalises and validates raw records
│   ├── loaders/
│   │   └── db_loader.py         # writes to PostgreSQL via SQLAlchemy
│   └── validators/
│       └── schema_validator.py  # validates Pydantic schemas against data
└── tests/                       # empty — agents will populate this

Track A: Claude Code CLI with ultracode

Prerequisites

Dynamic workflows require Claude Code v2.1.154 or later. Check your version with claude --version and update with claude update (or npm update -g @anthropic-ai/claude-code if you prefer npm directly). You must also switch to Opus 4.8 inside the session — the workflow engine only activates on models that support xhigh effort (Anthropic, 2026b).

Plan availability: Dynamic workflows are active by default on Max and Team plans. Pro users can enable them manually from /config. Enterprise plans default to off — administrators enable them through managed settings before team members can access the feature.

Step 1: Write AGENTS.md

AGENTS.md is the document Claude reads before starting any work. Write it before triggering the workflow — Claude uses it to plan the orchestration, allocate agents, and enforce boundaries. Vague instructions produce vague orchestration; specific instructions produce specific, reviewable output.

AGENTS.md — code review workflow instructions
# AGENTS.md — ETL Pipeline Code Review

## Context
Python ETL pipeline. Four modules in src/. Tests in tests/ are empty and must be populated.
The pipeline runs in production on AWS Lambda with a 15-second timeout per invocation.

## Review criteria (apply to every module)
1. Security: SQL injection, secrets in code, unsafe deserialization, unvalidated inputs
2. Error handling: all IO operations must have try/except with specific exception types
3. Type annotations: all public functions must have full signatures (PEP 484)
4. Testability: functions with side effects must be injectable (no hard-coded dependencies)

## Output format per module
For each file, produce three artefacts:
- ISSUES.md  — numbered list of issues with severity (critical / high / medium)
- tests/test_[module].py — pytest file that fails on the identified issues
- PATCH.diff  — unified diff that fixes all issues and makes the tests pass

## Forbidden
- Do NOT modify files outside src/ and tests/
- Do NOT add external dependencies not already in requirements.txt
- Do NOT rewrite a module from scratch — patch the existing code

## Concurrency note
Each module is independent. All four can be reviewed in parallel.
Assign one primary agent and one adversarial reviewer per module.

Step 2: Launch the workflow

Claude Code CLI — starting the review workflow
# 1. Navigate to the project root (where AGENTS.md lives)
cd etl_pipeline/

# 2. Start a Claude Code session
claude

# 3. Inside the session: switch to Opus 4.8
/model claude-opus-4-8

# 4. Set ultracode effort — activates xhigh reasoning + workflow engine
/effort ultracode

# 5. Trigger the workflow — include "workflow" anywhere in the prompt
Create a workflow to review all four Python modules in src/ following AGENTS.md,
producing ISSUES.md, a test file, and a PATCH.diff for each module.
Modules are independent — run reviews in parallel.

# Claude will now plan the orchestration, confirm the approach,
# then dispatch agents. Each wave runs up to 16 agents concurrently.
# Track spend in real time:
/usage
What happens inside the workflow

After you submit the prompt, Claude Code enters planning mode: it reads AGENTS.md, surveys the src/ directory, and writes a JavaScript orchestration script — not Python, not prose — that encodes loops, branching, and agent-count decisions as runnable code. The model's context window receives only the final verified answer; the intermediate work lives in script variables. Claude then dispatches agents in waves of up to 16 concurrent workers (the concurrency ceiling protects local CPU resources). For this four-module task you will see: a planning agent that maps dependencies, four primary review agents (one per module), four adversarial reviewers checking for missed issues, and a synthesis agent that produces SUMMARY.md. Claude Code surfaces progress in the terminal — the /usage command shows token spend per iteration (Anthropic, 2026b).

When the workflow finishes, drop back to standard effort immediately: /effort high. Ultracode applies xhigh reasoning to every subsequent message in the session, including trivial follow-up questions.

Track B: Python SDK orchestration

The CLI approach works well for one-off tasks where Claude can freely explore the codebase. For scheduled pipelines, CI/CD integration, or workflows that need custom retry logic and cost accounting, you need a Python orchestrator — a class that manages the Anthropic API calls, enforces concurrency limits, tracks token spend, and handles compaction in long sessions.

Why build your own orchestrator

The Python SDK gives you three things the CLI cannot: deterministic retry behaviour on API errors, a token budget kill switch enforced in your code (not dependent on Anthropic's optional guardrails), and structured output that feeds directly into your downstream systems — a database, a Slack notification, a CI gate — without manual intervention.

The orchestrator class

Python — AsyncWorkflowOrchestrator with token kill switch
import asyncio
import anthropic
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

@dataclass
class ReviewResult:
    file_path: str
    issues: str = ""
    test_code: str = ""
    patch: str = ""
    tokens_used: int = 0
    error: Optional[str] = None


class AsyncWorkflowOrchestrator:
    """Parallel code review orchestrator with token budget enforcement."""

    SYSTEM_PROMPT = """You are a senior Python engineer performing a code review.
For the file provided, return a JSON object with three keys:
  "issues": markdown list of issues with severity labels
  "test_code": complete pytest file that fails on the issues you found
  "patch": unified diff that fixes all issues and makes the tests pass
Return ONLY the JSON — no prose before or after."""

    def __init__(
        self,
        model: str = "claude-opus-4-8",
        token_budget: int = 200_000,  # kill switch threshold
        max_concurrency: int = 5,       # respect API rate limits
    ):
        self.client = anthropic.AsyncAnthropic()
        self.model = model
        self.token_budget = token_budget
        self.tokens_spent = 0
        self._killed = False
        self._semaphore = asyncio.Semaphore(max_concurrency)
        self._lock = asyncio.Lock()   # guards tokens_spent counter

    async def _charge_tokens(self, tokens: int) -> bool:
        """Thread-safe token accounting. Returns False if budget exceeded."""
        async with self._lock:
            self.tokens_spent += tokens
            if self.tokens_spent > self.token_budget:
                self._killed = True
                return False
        return True

    async def review_file(self, path: Path) -> ReviewResult:
        result = ReviewResult(file_path=str(path))

        if self._killed:
            result.error = "Workflow killed: token budget exceeded"
            return result

        async with self._semaphore:
            try:
                content = path.read_text(encoding="utf-8")
                response = await self.client.messages.create(
                    model=self.model,
                    max_tokens=8192,
                    thinking={"type": "enabled", "budget_tokens": 10_000},
                    system=[{
                        "type": "text",
                        "text": self.SYSTEM_PROMPT,
                        "cache_control": {"type": "ephemeral", "ttl": "1h"}
                    }],
                    messages=[{
                        "role": "user",
                        "content": f"Review this file ({path.name}):\n\n```python\n{content}\n```"
                    }]
                )

                # Charge tokens — kill if over budget
                total = response.usage.input_tokens + response.usage.output_tokens
                result.tokens_used = total
                if not await self._charge_tokens(total):
                    result.error = f"Budget exceeded after {self.tokens_spent:,} tokens"
                    return result

                # Extract the text block (skip thinking blocks)
                text = next(
                    b.text for b in response.content if b.type == "text"
                )
                import json
                data = json.loads(text)
                result.issues    = data.get("issues", "")
                result.test_code = data.get("test_code", "")
                result.patch     = data.get("patch", "")

            except Exception as e:
                result.error = str(e)

        return result

    async def run(self, src_dir: Path) -> list[ReviewResult]:
        files = list(src_dir.rglob("*.py"))
        tasks = [self.review_file(f) for f in files]
        return await asyncio.gather(*tasks)

Running the orchestrator

Python — end-to-end script with report generation
import asyncio
from pathlib import Path
from orchestrator import AsyncWorkflowOrchestrator

async def main():
    src = Path("etl_pipeline/src")
    out = Path("review_output")
    out.mkdir(exist_ok=True)

    orchestrator = AsyncWorkflowOrchestrator(
        model="claude-opus-4-8",
        token_budget=300_000,  # ~$1.50 at Standard tier — adjust to your comfort
        max_concurrency=4,      # 4 files in parallel, one API call each
    )

    print(f"Reviewing {src} — kill switch at {orchestrator.token_budget:,} tokens")
    results = await orchestrator.run(src)

    # Write artefacts and print summary
    errors, reviewed = [], []
    for r in results:
        name = Path(r.file_path).stem
        if r.error:
            errors.append(r)
            print(f"  ✗ {name}: {r.error}")
            continue

        (out / f"{name}_ISSUES.md").write_text(r.issues)
        (out / f"test_{name}.py").write_text(r.test_code)
        (out / f"{name}.patch").write_text(r.patch)
        reviewed.append(r)
        print(f"  ✓ {name}: {r.tokens_used:,} tokens")

    total = orchestrator.tokens_spent
    cost  = (total / 1_000_000) * 5.00   # Standard tier input rate
    print(f"\nTotal: {len(reviewed)} reviewed, {len(errors)} failed")
    print(f"Tokens: {total:,} | Estimated cost: ${cost:.4f}")
    print(f"Output: {out}/")

asyncio.run(main())
Token math for this example

A typical Python module of 150–300 lines costs roughly 2,000–4,000 input tokens when passed with the system prompt (which is cached after the first call). Four modules in parallel: approximately 8,000–16,000 input tokens total, plus output. At Standard-tier pricing ($5.00/M input, $25.00/M output), the full review of a four-module project costs under $0.15. The token_budget=300_000 in the example is a conservative safety net — at Standard pricing that is $1.50 of maximum exposure before the kill switch fires.

The shared system prompt is cached for 1 hour after the first agent writes it. Agents 2–4 hit the cache and are charged $0.50/M instead of $5.00/M on those tokens — a 90% reduction on the most expensive input block. This is why setting the system prompt cache before dispatching parallel agents is worth the one-line overhead.

Handling long sessions: compaction in the Python orchestrator

For pipelines that run continuously — a nightly review across a large codebase, or a monitoring agent that accumulates context over hours — you need to handle compaction inside the orchestrator. The pattern from the engineering guide applies directly: enable pause_after_compaction, intercept the stop reason, persist the summary, and resume (Anthropic, 2026d).

Python — compaction-aware call wrapper for long sessions
async def call_with_compaction(
    client: anthropic.AsyncAnthropic,
    messages: list,
    vector_store,
    session_id: str,
    **kwargs,
) -> anthropic.types.Message:
    """Wrap a beta messages call; handles compaction transparently."""
    response = await client.beta.messages.create(
        betas=["compact-2026-01-12"],
        context_management={
            "edits": [{
                "strategy": "compact_20260112",
                "trigger": {"type": "token_count", "threshold": 80_000},
                "instructions": "Preserve all file paths, issue IDs, and patch hunks verbatim.",
                "pause_after_compaction": True,
            }]
        },
        messages=messages,
        **kwargs,
    )

    if response.stop_reason == "compaction":
        block = next(b for b in response.content if b.type == "compaction")
        summary = block.content or ""           # guard against null (known edge case)
        if summary:
            await vector_store.upsert(session_id, summary)
        # Resume with empty messages — server has already replaced history
        return await call_with_compaction(client, [], vector_store, session_id, **kwargs)

    return response

Choosing between the two tracks

Decision point Use Claude Code CLI Use Python SDK
Task structure Open-ended — the scope is not fully defined upfront Structured — you know exactly which files get which treatment
Trigger Manual, interactive, or scheduled via cron + shell script Any Python event: CI webhook, schedule, API call, queue message
Output Files written to disk; Claude Code formats them Structured data (JSON, dataclass) that your code processes
Error handling Claude Code retries internally; limited visibility Full control: exponential backoff, partial retry, selective skipping
Cost visibility /usage inside the session; not easily exported Per-call usage object — log to any observability system
Compaction Automatic; you cannot pause and inspect the summary Explicit pause_after_compaction — you control persistence

A hybrid approach works well for mature teams: use Claude Code CLI for exploratory runs (new codebases, ambiguous tasks, proof-of-concept), then codify the successful pattern into a Python orchestrator for scheduled production use. The AGENTS.md file travels between both tracks unchanged — it is the stable specification that both Claude Code and your Python agents read.

Production checklist before going live

01
Set token_budget before the first run, not after the first bill
Start at 2× the cost of a single manual review. If one module costs 4,000 tokens to review, four modules in parallel should cost under 16,000 — set the budget at 40,000 for the first run. Anthropic's no-refund policy on runaway agentic consumption is explicit; your kill switch is the only protection you have.
02
Cache the system prompt before dispatching parallel agents
All agents share the same system prompt. The first call writes the cache; every subsequent parallel call reads it at $0.50/M instead of $5.00/M. With max_concurrency=4, only one of the four parallel calls writes the cache — the rest are instant reads. Set "ttl": "1h" if all agents will finish within an hour.
03
Validate JSON output before writing artefacts
Agents occasionally return well-formed prose instead of JSON (especially at lower effort levels or on short inputs). Wrap the json.loads() call in a try/except and log the raw response on failure — do not silently discard it. A ReviewResult with error set is far more useful than a silently empty file.
04
Keep max_concurrency at 4–6 and handle 429s with backoff
Above 6–8 concurrent API calls to Opus 4.8 you will start hitting 429 rate-limit errors — the exact threshold depends on your account tier and current platform load. Implement exponential backoff on 429 responses rather than a fixed sleep: catch anthropic.RateLimitError, wait 2attempt seconds (capped at 60s), and retry. The Semaphore in the orchestrator limits concurrency at dispatch time; backoff handles transient limits during execution.
05
Drop to /effort high as soon as the heavy task is done
In Claude Code, ultracode applies to every message in the session — including trivial follow-up questions after the workflow completes. Forgetting to run /effort high after a workflow finish means your next "what files did you change?" message burns xhigh reasoning tokens. This is a common source of unexplained cost spikes in team deployments.
References
  1. Anthropic. (2026a). Introducing Claude Opus 4.8. anthropic.com/news/claude-opus-4-8
  2. Anthropic. (2026b). Introducing dynamic workflows in Claude Code. claude.com/blog/introducing-dynamic-workflows-in-claude-code
  3. Anthropic. (2026c). Python SDK — Claude API Docs. platform.claude.com/docs/en/api/sdks/python
  4. Anthropic. (2026d). Compaction — Claude API Docs. platform.claude.com/docs/en/build-with-claude/compaction
  5. Anthropic. (2026e). Prompt caching — Claude API Docs. platform.claude.com/docs/en/build-with-claude/prompt-caching
  6. Anthropic. (2026f). CLI reference — Claude Code Docs. code.claude.com/docs/en/cli-reference
M
Michele Mader
Technical Leader · AI Systems & Data Engineering

I lead technical direction on AI-driven data products for enterprise clients — defining architecture, making stack decisions, and owning delivery from roadmap to production.

Connect on LinkedIn