πŸŽ“ Claude Architect KB CCAR-F Β· CCAR-P study guide

Pro Domain 1 Β· Solution Design & Architecture (17%)

In this domain you act as the architect, not the builder. The exam guide describes the minimally qualified candidate in two quotes. This person "translates business requirements into scalable, secure, and reliable architectures". This person also "understands trade-offs related to cost, latency, performance, safety, and maintainability." Every item in this domain starts from a business situation: a backlog, a missed SLA, a cost line, or a promise from an executive. The item then asks which design solves it. Foundations Domain 1 taught the mechanics: the agent loop, workflows versus agents, and harness controls. Professional Domain 1 asks you to defend a choice among those mechanics to a business owner. You explain why this pattern, why this split, why this SLA, and what it costs if you are wrong. So the artifacts (the written documents you produce) are different: an architecture decision record, an SLA table, and a decomposition tree. This page shows each one in its real shape.

πŸ“– New here? Halden Freight and the 40,000 exception emails

The business problem arrives as a number, not as a technology. Halden Freight is a mid-size logistics company. It receives about 40,000 "shipment exception" emails a month: delays, damaged goods, customs holds, and wrong addresses. Twenty-two operations staff read these emails. They look up the shipment in the tracking system, decide what to do, and reply. The median time to the first reply is nine hours. Three key customers have contracts that promise two hours. The VP of Operations asks: "Can Claude fix this?"

The architect's first step is to not accept the question in this form. "Fix this" is not a requirement. The architect writes down what a fixed system looks like: 90% of exceptions get a correct first reply within two hours; no reply promises money without a human; ops staff handle only the cases the system cannot handle. That sentence is the whole design brief. Every later decision is a trade-off measured against it.

Then the architect draws the loop. Input: the email plus the shipment record. Processing: classify the exception, fetch the shipment, and draft the reply and the next action. Output: a reply to the customer and an internal ticket. Feedback: every case where an operator overrides (corrects) the system becomes a labeled example, and every customer rating becomes a signal. Without the feedback arrow, the system is only a demo. It will slowly get worse as carriers, routes, and customers change.

Next comes the pattern choice, and the exam's favorite trap. Most exceptions follow a known path: classify β†’ look up β†’ draft β†’ send. That is a workflow with one augmented-LLM step, not an agent. Only the long tail of rare cases justifies letting the model choose its own steps, inside a budget. An example is a customs hold that needs three lookups and a judgment. The exam writes its distractors (tempting wrong answers) about the architect who chooses "an agent with all the tools".

Multi-agent appears once, for a different job. Every Friday, Halden wants a root-cause report across the week's exceptions: which carriers, which lanes, and which customers caused them. That job really does split into independent research subtasks with separate contexts: a coordinator and three workers. The daily triage does not need this, and the architect must be able to explain why.

Finally, the architect names the value and signs the SLA. Efficiency: 22 staff hours per day recovered. Performance SLA: p95 first reply under two hours. Cost: a token budget per exception, with a ceiling. Transformation: customers check status themselves. Each pillar gets a number, an owner, and a measurement. The ADR records these choices. The exam expects you to be able to write this artifact, and to defend it when the VP asks "why not just an agent?"

At a glance β€” the six decisions this domain grades

Objective (verbatim) The decision you make The artifact The distractor
Translate business problems into Claude-based AI solutions What outcome, measured how, for whom Problem statement + solution brief Starting from a capability ("use tools") instead of an outcome
Design end-to-end architectures Where input, processing, output, and feedback each live Loop diagram + feedback-loop design A pipeline with no feedback arrow
Select appropriate architectural patterns Workflow, [agentic](term:agentic system), or augmented LLM Architecture decision record "Give it all the tools and let it figure it out"
Design multi-agent systems and orchestration strategies Whether to split, who coordinates, how results return Orchestrator plan + worker schema Multi-agent as a throughput device
Apply decomposition techniques Where the seams are; what is fixed versus decided Decomposition tree One giant prompt with a numbered list
Align solutions to business value pillars Which pillar, which number, which SLA Value pillar table + SLA table Vague "faster and better" without a metric

Four more decisions sit inside these six. Inside multi-agent design: which structure (coordinator-worker, parallel, or sequential) and how to brief a worker (goal or script). Then how a pipeline resumes after a crash and how much review comes before an agent edits (plan mode, direct execution, or multi-phase).

Click any technical term. Its dialog shows the plain meaning, the real document or payload, and where the idea stops being true.

Translate business problems into Claude-based AI solutions

Objective: "Translate business problems into Claude-based AI solutions."

The translation has four parts, and the architect writes all four. The business owner supplies the pain and the constraints. The architect supplies the outcome statement, the scope boundary, the measurement, and the first candidate architecture. The result is a one-page solution brief. It lives in the project's design repository. In this KB's house convention, that is a docs/ folder in the architecture project, which is created before the code repository.

Outcome before capability. Suppose a brief starts with "we will use an agent with MCP tools". That brief has skipped the translation. Now suppose a brief starts with "reduce median first-reply time from 9 hours to under 2 for 90% of exceptions". People can discuss it, measure it, and scope it. The exam's stems (question texts) describe the pain. The correct answer names the outcome first, and then chooses the smallest mechanism that reaches it.

Scope is a list of what is out. For Halden Freight, address corrections and damage claims are in scope. Refunds and legal disputes are out of scope (people keep handling them). Because you wrote the out-of-scope list, you can later say "the agent does not need a refund tool". The [least privilege](term:least privilege) argument starts here, not in the tool configuration.

Here is the brief in the form the architect really writes it:

MARKDOWN
# Solution brief β€” Halden Freight exception triage

## Business problem
40,000 shipment-exception emails/month are triaged by 22 operations staff.
Median first reply: 9 h. Three enterprise contracts require 2 h.

## Outcome (measurable)
- 90% of exceptions receive a correct first reply within 2 h (p95 < 2 h).
- Correct = operator would not have changed the classification or next action.
- Zero replies promise money, credit, or a claim decision without human approval.

## In scope
Delay, damage report, customs hold, address correction, missing paperwork.

## Out of scope (stays with people)
Refund decisions, legal disputes, contract renegotiation.

## Constraints
- Shipment data never leaves the tracking system region (EU tenant).
- Per-exception model spend ceiling: set by finance; reviewed monthly.
- Existing ticketing system remains the system of record.

## First candidate architecture
Routing workflow β†’ augmented-LLM draft step β†’ deterministic send gate.
Long-tail customs holds β†’ bounded agent (max 6 turns) β†’ human queue.

Who owns each line. The VP owns the outcome numbers (the company will judge the VP by them). Legal owns the "no money without a human" line. The platform team owns the data-residency constraint. The architect owns the candidate architecture. Only the architect may change it, and only through an ADR, which this page covers below.

Translation step Question it answers Where it lives Wrong version
Outcome statement What does "fixed" look like, as a number? Solution brief, line 1 "Improve customer experience"
Scope boundary What stays with people? Brief + later the tools list Everything the model could do
Constraints What must never happen? Brief β†’ guardrails and harness gates Constraints discovered in production
Measurement How will we know? Brief β†’ eval set and SLA table Anecdotes from the pilot team
Candidate architecture What is the simplest sufficient shape? Brief β†’ ADR The most capable shape

The wrong choice and its failure. Imagine a team that starts from capability ("agent + all tools"). It ships something that looks impressive in the demo, but nobody can measure it in production. Six weeks later, nobody can say whether it helped. The refund tool has been called twice. The VP has no number to justify the spending. The exam writes this distractor as "deploy a general-purpose agent with access to all support tools". It is wrong because the team never translated the problem, not because agents are bad.

Design end-to-end architectures (input β†’ processing β†’ output β†’ feedback loops)

Objective: "Design end-to-end architectures (input β†’ processing β†’ output β†’ feedback loops)."

"End-to-end" means the model call is only one box among many. The exam draws the architecture as four stages. Each stage has a concrete place in Halden's system:

flowchart LR
  IN[Input: email + shipment record] --> CL[Classify: routing call]
  CL --> AUG[Augmented LLM: draft reply + next action]
  AUG --> GATE{Deterministic send gate}
  GATE -->|allowed| OUT[Output: customer reply + ticket]
  GATE -->|blocked| HQ[Human queue]
  OUT --> FB[Feedback: ratings, overrides, outcomes]
  HQ --> FB
  FB --> EV[Eval set + drift dashboard]
  EV -.->|prompt / routing changes| CL

Input is more than the email. The application builds the input from three parts: the raw email, the shipment record fetched from the tracking system, and the customer's contract tier. It matters who builds it. The harness (the code that runs and controls the model calls) builds it, in a fixed and predictable way, before any model call. In the fixed path, the model never fetches the shipment record by itself. If it did, the path would become an agent, and its latency would become unpredictable.

Processing is a chain with checks between the calls. Classification is one small call. Drafting is one augmented call with retrieved context and a lookup tool. Code sits between the two calls. If the classifier returns refund_request, the chain stops and sends the case to a person. This is prompt chaining with a [routing](term:workflow routing) step. The code between the calls is where the policy lives.

Output passes through a gate that the model cannot argue its way past. The send gate is a deterministic check (code that gives the same result every time). It checks three things: the reply text has no currency amounts, the next action is in the allowed list, and the customer tier matches the reply template. The gate is code. When it fails, it fails closed: the case goes to the human queue. This is the harness control idea from Foundations, now placed at one specific point in one specific loop.

Feedback is a planned data flow, not a hope. Every operator override creates an event. Every customer rating creates an event. The events go into a store that the eval process reads. Here is the override event as the application records it. This is application state, not an API payload:

JSON
{
  "event": "operator_override",
  "case_id": "EXC-2026-091442",
  "model_output": {
    "category": "delay",
    "next_action": "send_eta_update"
  },
  "operator_correction": {
    "category": "customs_hold",
    "next_action": "request_commercial_invoice"
  },
  "operator_id": "ops-17",
  "recorded_at": "2026-09-12T14:03:11Z",
  "promote_to_eval_set": true
}

Two things can set the promote_to_eval_set flag: a rule (for example, overrides on in-scope categories) or a human reviewer. Each promoted event becomes a row in the [golden set](term:golden set). The operator's correction is the expected answer. That is the feedback-loop design. The loop closes through a dataset and an eval run. A change to the prompt or the routing ships only after the eval passes.

Stage What it contains at Halden Who owns it Storage
Input Email, shipment record, contract tier Harness (assembly code) Request context; cached shipment snapshot
Processing Routing call β†’ augmented draft call β†’ policy code Architect (pattern) + prompt owner Prompt templates in the repo; run trace
Output Reply text, next action, ticket update Deterministic send gate Ticketing system (system of record)
Feedback Overrides, ratings, downstream outcomes Ops lead (labels) + eval owner Event store β†’ [golden set](term:golden set) β†’ eval run

The wrong choice and its failure. Imagine a pipeline drawn as input β†’ model β†’ output, with no feedback arrow. The team measures its accuracy once, during the pilot, and never again. Then a carrier changes its status codes. Classification gets worse without any visible sign. Nobody finds out until a key customer complains to management. The exam distractor is "monitor latency and error rates". That monitoring is necessary, but a latency dashboard cannot see a classification that is wrong but confident. Only captured overrides can show it.

Select appropriate architectural patterns (workflow, agentic, augmented LLM)

Objective: "Select appropriate architectural patterns (workflow, agentic, augmented LLM)."

Start with the building block. Anthropic's article Building Effective Agents names the augmented LLM as the unit that every pattern is built from. It is a model call with three additions: retrieval (the application supplies relevant documents), tools (the model can request an action), and memory (state that the application carries between calls). Halden's draft step is exactly this. It is one call inside a sequence that code defines, and that is why the whole path stays a workflow.

Here is the request for the draft step. The application has already retrieved the shipment record (retrieval) and offers one lookup tool (tools). The summary of the customer's previous case is memory that the application chose to include. This request is illustrative; replace the model placeholder with a supported model.

JSON
{
  "model": "YOUR_SUPPORTED_MODEL",
  "max_tokens": 1024,
  "system": [
    {
      "type": "text",
      "text": "You draft first replies to shipment-exception emails for Halden Freight. Use only the shipment record and carrier data supplied. Never state or promise a refund, credit, or claim outcome. Choose next_action from: send_eta_update, request_commercial_invoice, confirm_address, open_damage_report, escalate_to_operator.",
      "cache_control": {"type": "ephemeral"}
    }
  ],
  "tools": [
    {
      "name": "get_carrier_eta",
      "description": "Return the carrier's latest estimated delivery date for a tracking number. Use when the customer asks when a delayed shipment will arrive.",
      "input_schema": {
        "type": "object",
        "properties": {
          "tracking_number": {"type": "string"}
        },
        "required": ["tracking_number"],
        "additionalProperties": false
      }
    }
  ],
  "messages": [
    {
      "role": "user",
      "content": "<shipment_record>\n{\"tracking_number\":\"HF88213\",\"status\":\"in_transit\",\"origin\":\"Rotterdam\",\"destination\":\"Lyon\",\"promised_date\":\"2026-09-10\"}\n</shipment_record>\n<customer_history>\nPrevious case EXC-2026-088102: delay, resolved with ETA update.\n</customer_history>\n<classification>delay</classification>\n<email>\nHi, order HF88213 was due yesterday and nothing has arrived. When will it get here?\n</email>\n\nDraft the reply and choose next_action."
    }
  ]
}
Not supplied in this example β€” defaults and disabled features
Setting not supplied or not visible Meaning for this example
context_management β€” not supplied here No server compaction strategy is enabled by this body. Host/manual compaction is separate.
output_config β€” not supplied here No output_config schema or effort override is supplied. A prompt asking for JSON does not provide schema enforcement.
tool_choice β€” not supplied here With tools supplied, the default is auto: the model chooses whether to request a tool. Without tools, no supplied tool can be selected.
stop_sequences β€” not supplied here No custom stop strings are configured. Generation can still stop for end_turn, tool_use, max_tokens, or another documented reason.
stream β€” not supplied here For a direct non-streaming create/parse call, the result is one message rather than an event stream. A stream helper configures this separately.
thinking β€” not supplied here No explicit thinking override is shown. Behavior depends on the selected model; omission does not universally mean thinking is off.
cache_control β€” not supplied here No top-level automatic cache marker is supplied. Check nested system/tool/content blocks for separate cache_control markers.
metadata β€” not supplied here No application user reference is supplied through metadata. Authentication still comes from the request credentials.

Full request catalog and omission rules. JSON has no comments; these notes are outside the payload.

Where each augmentation sits in that JSON. Retrieval is the <shipment_record> block inside messages[0].content; the application put it there. Tools are tools[0]; the application offered exactly one. Memory is the <customer_history> block; the application chose what to remember. The system block holds the standing policy. It also has a cache marker, because it never changes from one exception to the next.

The model may request the tool. Here is an illustrative response with one tool_use block and a stop_reason of tool_use:

JSON
{
  "id": "msg_illustrative_01",
  "type": "message",
  "role": "assistant",
  "model": "YOUR_SUPPORTED_MODEL",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_illustrative_01",
      "name": "get_carrier_eta",
      "input": {"tracking_number": "HF88213"}
    }
  ],
  "stop_reason": "tool_use",
  "stop_sequence": null,
  "usage": {"input_tokens": 612, "output_tokens": 48}
}

The harness runs get_carrier_eta, adds a tool_result block, and calls the model again. That second call returns the draft. Two calls do not make this an agent. The code decided that there would be a lookup step, and it limited that step to one tool round. The model only chose whether to use the one tool it was offered.

Now the decision rule. Foundations taught the workflows-versus-agents rule: use the simplest thing that works. Professional asks you to apply that rule to each path in the same system, and to write down the reasons:

Path at Halden Is the step sequence known in advance? Pattern Why
Delay, address, paperwork (β‰ˆ85% of volume) Yes: classify β†’ look up β†’ draft β†’ gate Workflow with one augmented step Predictable latency and cost; testable per step
Damage report Yes, plus a photo check Workflow + [routing](term:workflow routing) to a vision-capable prompt Still fixed; one extra branch
Customs hold with missing documents No: which lookups are needed depends on what the first lookup reveals Bounded [agent](term:agentic system) (max 6 turns, 3 tools, then human) Path cannot be hardcoded; budget contains the risk
Refund request Not applicable Route to human; no model action Out of scope by the brief

The pattern choice is written down, not kept in someone's memory. The artifact is an architecture decision record (ADR). It states the context, the decision, the rejected alternatives, and the consequences. A Professional-level reviewer asks for this document when the VP says "why not just an agent?"

MARKDOWN
# ADR-003 Β· Triage path is a workflow; customs holds are a bounded agent

Status: Accepted Β· Date: 2026-09-08 Β· Owner: Solutions architect

## Context
85% of exceptions follow a known sequence (classify β†’ look up β†’ draft β†’ gate).
Customs holds require a variable number of lookups depending on which
documents are missing. Enterprise SLA: p95 first reply < 2 h.

## Decision
- Fixed-sequence exceptions run as a routing workflow with one augmented-LLM
  draft step (one offered tool, one tool round).
- Customs holds run as an agent bounded to 6 turns and 3 read-only tools;
  on budget exhaustion the case goes to the human queue with the trace.
- A deterministic send gate sits between every model output and the customer.

## Alternatives considered
1. Single agent for all exceptions with all tools β€” rejected: unpredictable
   latency against a 2 h SLA; refund tool exposure contradicts the brief.
2. Pure workflow for customs holds β€” rejected: three hardcoded branches
   still missed 40% of the pilot's hold cases.
3. Rule-based classifier without a model β€” rejected: 61% accuracy on the
   pilot set versus 94% for the routing call.

## Consequences
+ Per-exception cost is bounded and predictable for 85% of volume.
+ Each workflow step is unit-testable against the golden set.
- Two execution paths to operate and monitor.
- The agent path needs its own trajectory evals and a turn-budget alert.

The wrong choice and its failure. "One agent, all tools" fails in two ways, and the exam names them separately. Latency: the agent may take nine tool rounds on a simple delay. That breaks the 2 h p95 for exactly the customers the project was built for. Authorization: an agent that holds a refund tool breaks [least-privilege](term:least privilege), and no prompt can fix that. The opposite error is a pure workflow for customs holds. It fails because it is too rigid: the hardcoded branches cannot cover all the cases, so the operators stop trusting the system. The exam explains these answers with two pairs of words: predictability versus flexibility and bounded versus unbounded autonomy.

Design multi-agent systems and orchestration strategies

Objective: "Design multi-agent systems and orchestration strategies."

The reason for multi-agent is context, not volume. Foundations made this point clearly in two axes, two owners. Throughput belongs to the harness (a pool, a queue, the [batch API](term:batch API)). Decomposition belongs to the pattern. Professional adds the orchestration strategy: the concrete plan for who does what, with which inputs, and what comes back.

Four strategies, one decision table. Each strategy has a real coordinator and a real result contract (an agreed shape for what comes back).

Strategy Coordinator What workers receive What returns Use at Halden
Orchestrator–workers A model plans subtasks dynamically Shared instructions + one assignment, clean context A validated summary object per worker Weekly root-cause report (subtasks unknown until the week's data is seen)
Parallel sectioning Code, fixed fan-out One slice each of a known partition Per-slice results merged by code Reprocessing a backlog by carrier (partition known)
Voting Code, N identical runs The same task N candidates, aggregated by rule High-stakes damage-claim classification where disagreement routes to a human
Evaluator–optimizer Code loop with a limit Draft + critique Accepted draft or bounded failure Enterprise-tier replies where tone and completeness are checked before sending

The coordinator's plan is data, and the harness checks it. Here is what the coordinator model outputs for the weekly report. It is illustrative. The model generates it under a schema that the application enforces through output_config:

JSON
{
  "goal": "Weekly root-cause analysis of shipment exceptions, week 37",
  "subtasks": [
    {
      "id": "carriers",
      "instruction": "Group week-37 exceptions by carrier; report exception rate per 1,000 shipments and the top two causes per carrier.",
      "inputs": ["exceptions_week37.jsonl", "shipments_week37.csv"],
      "tools": ["read_dataset"]
    },
    {
      "id": "lanes",
      "instruction": "Group week-37 exceptions by origin-destination lane; flag lanes whose rate rose more than 30% versus week 36.",
      "inputs": ["exceptions_week37.jsonl", "exceptions_week36.jsonl"],
      "tools": ["read_dataset"]
    },
    {
      "id": "customers",
      "instruction": "List enterprise customers with any SLA breach in week 37 and the exception category of each breach.",
      "inputs": ["exceptions_week37.jsonl", "sla_breaches_week37.csv"],
      "tools": ["read_dataset"]
    }
  ],
  "merge": "Synthesize into a one-page report ranked by business impact."
}

Who owns what in that plan. The coordinator model chose the three subtasks and their instructions. The harness owner defined three things: the plan schema, the allowed tool names (only read_dataset, so the design is read-only), and the schema for worker results. The harness starts one worker per subtask, each with its own [context](term:context isolation). It rejects any subtask that names a tool outside the allowlist.

The orchestration sketch: coordinator, fan-out, merge, budgets. This is illustrative Python. The model calls sit behind a call_model function that this page does not define, and this code reads no data.

PYTHON
import asyncio
import json

MAX_WORKERS = 4
WORKER_TURN_BUDGET = 8
ALLOWED_TOOLS = {"read_dataset"}


def validate_plan(plan):
    subtasks = plan["subtasks"]
    if not 1 <= len(subtasks) <= MAX_WORKERS:
        raise ValueError("plan size outside budget")
    for task in subtasks:
        bad = set(task.get("tools", [])) - ALLOWED_TOOLS
        if bad:
            raise ValueError(f"subtask {task['id']} requests disallowed tools {bad}")
    return subtasks


async def run_worker(task, call_model, run_tool):
    messages = [{"role": "user", "content": task["instruction"]}]
    for _ in range(WORKER_TURN_BUDGET):
        response = await call_model(messages, tools=task["tools"])
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return {"id": task["id"], "status": "done", "summary": response.text}
        results = [await run_tool(block) for block in response.tool_uses]
        messages.append({"role": "user", "content": results})
    return {"id": task["id"], "status": "budget_exhausted", "summary": None}


async def weekly_report(goal, call_model, run_tool):
    plan_response = await call_model([{"role": "user", "content": goal}], schema="plan")
    subtasks = validate_plan(json.loads(plan_response.text))
    worker_results = await asyncio.gather(
        *(run_worker(task, call_model, run_tool) for task in subtasks)
    )
    failed = [r["id"] for r in worker_results if r["status"] != "done"]
    if failed:
        return {"status": "partial", "failed": failed, "results": worker_results}
    merge_prompt = json.dumps({"goal": goal, "findings": worker_results})
    report = await call_model([{"role": "user", "content": merge_prompt}])
    return {"status": "done", "report": report.text, "trace": worker_results}
🏭 Production shape: typed plan, SDK tool loop

Two parts of the sketch change. The plan is not read with json.loads: messages.parse sends a schema built from a Pydantic model and gives back a typed object. Each worker's hand-written turn loop becomes the SDK tool runner, with max_iterations as the turn budget. The budget and allowlist check (validate_plan above) stays your code.

PYTHON
import asyncio
import os

import anthropic
from pydantic import BaseModel, ConfigDict

from app.reports.tools import read_dataset  # a @beta_tool function; read-only

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY
MODEL = os.environ.get("CLAUDE_MODEL", "claude-sonnet-5")
WORKER_TURN_BUDGET = 8


class Subtask(BaseModel):
    model_config = ConfigDict(extra="forbid")
    id: str
    instruction: str
    inputs: list[str]
    tools: list[str]


class Plan(BaseModel):
    model_config = ConfigDict(extra="forbid")
    goal: str
    subtasks: list[Subtask]
    merge: str


def make_plan(goal: str) -> Plan:
    response = client.messages.parse(
        model=MODEL, max_tokens=2048,
        messages=[{"role": "user", "content": goal}],
        output_format=Plan,
    )
    if response.stop_reason != "end_turn":  # max_tokens or refusal: no plan to trust
        raise RuntimeError(f"planner stopped: {response.stop_reason}")
    plan = response.parsed_output
    validate_plan(plan.model_dump())  # same size + allowlist check as above
    return plan


def run_worker(task: Subtask) -> dict:
    runner = client.beta.messages.tool_runner(
        model=MODEL, max_tokens=4096, tools=[read_dataset],
        messages=[{"role": "user", "content": task.instruction}],
        max_iterations=WORKER_TURN_BUDGET,
    )
    final = runner.until_done()
    if final.stop_reason != "end_turn":  # still asking for tools when the budget ran out
        return {"id": task.id, "status": "budget_exhausted", "summary": None}
    text = "".join(b.text for b in final.content if b.type == "text")
    return {"id": task.id, "status": "done", "summary": text}


async def fan_out(plan: Plan) -> list[dict]:
    return await asyncio.gather(*(asyncio.to_thread(run_worker, t) for t in plan.subtasks))
Not supplied in this example β€” defaults and disabled features
Setting not supplied or not visible Meaning for this example
context_management β€” not supplied here No server compaction strategy is enabled by this body. Host/manual compaction is separate.
output_config β€” varies between calls This example contains multiple model calls. Read each call separately; their settings need not match.
tools β€” varies between calls This example contains multiple model calls. Read each call separately; their settings need not match.
tool_choice β€” not supplied here With tools supplied, the default is auto: the model chooses whether to request a tool. Without tools, no supplied tool can be selected.
system β€” not supplied here No application system instructions are supplied through this field. User messages still provide task text.
stop_sequences β€” not supplied here No custom stop strings are configured. Generation can still stop for end_turn, tool_use, max_tokens, or another documented reason.
stream β€” not supplied here For a direct non-streaming create/parse call, the result is one message rather than an event stream. A stream helper configures this separately.
thinking β€” not supplied here No explicit thinking override is shown. Behavior depends on the selected model; omission does not universally mean thinking is off.
cache_control β€” not supplied here No top-level automatic cache marker is supplied. Check nested system/tool/content blocks for separate cache_control markers.
metadata β€” not supplied here No application user reference is supplied through metadata. Authentication still comes from the request credentials.
betas β€” not explicitly supplied No beta list appears in these calls. SDK/client configuration can add headers; inspect those before assuming no beta header is sent.
SDK-generated format output_format passed to parse becomes output_config.format. It is not an omitted schema merely because output_config is absent in Python.

Full request catalog and omission rules. JSON has no comments; these notes are outside the payload.

Where it lives: app/reports/plan.py (models + planner), app/reports/workers.py (fan-out), app/reports/tools.py (the tool).

Read the sketch for its decisions, not for its syntax. In validate_plan, the harness refuses a plan that has too many subtasks or asks for too many permissions. run_worker gives each worker a fresh messages list; that one line is context isolation. It also gives a turn budget that ends in a named failure state, never in a silent loop. asyncio.gather is the throughput axis; it would work the same way for a fixed partition. The merge call receives summaries, not full transcripts, and this protects the coordinator's [context window](term:context window). A partial status goes to a person, so the report is never published with a missing part.

Always hub-and-spoke. Workers do not send messages to each other. All coordination goes through the coordinator or the harness. This is the hub-and-spoke rule from Foundations. It means you can always tell which worker caused a failure, and you can replay the trace.

πŸ” See it run β€” Friday's report, one worker exhausting its budget

Step 1: the goal enters. The scheduler calls weekly_report("Weekly root-cause analysis of shipment exceptions, week 37", ...). The coordinator call returns the three-subtask plan shown above. validate_plan passes: there are three subtasks, and all tools are in the allowlist.

Step 2: fan-out. Three workers start at the same time. Each worker holds only its own instruction; no worker sees another worker's data. The lanes worker reads two weekly files (about 18,000 tokens of JSONL). Those tokens exist only in its own context.

Step 3: one worker fails cleanly. The customers worker keeps reading the breach file again and again. The reason: the customer IDs in that file do not match the format in the exception file. At turn 8 the budget runs out. The worker returns:

JSON
{"id": "customers", "status": "budget_exhausted", "summary": null}

Step 4: the harness refuses to merge a partial result. weekly_report sees failed == ["customers"] and returns {"status": "partial", ...}. Nothing is published. The ops lead gets a partial-report alert with the trace of the customers worker attached. The ops lead finds the ID-format mismatch in ten minutes.

Step 5: the fix is a data fix, not a prompt fix. Someone corrects the breach export, and Monday's rerun completes. The team logs the event as a data-quality failure in the risk register that Pro Domain 5 maintains. The report was never wrong, because the design treated partial as a normal, named outcome.

What this proves. Budgets end in states, not in loops. Isolation kept the two healthy workers healthy. The merge step has a precondition: "all workers are done". The exam prefers this kind of harness rule over "ask the model to check whether all workers finished."

The wrong choice and its failure. Some teams choose multi-agent for speed ("three agents will finish a delay email faster than one"). This triples the token cost and adds coordination latency. It also lets three contexts disagree about one shipment. The exam explains this with two pairs of words: decomposition benefit versus coordination overhead, and context isolation versus shared state. If the stem's only reason for multiple agents is parallelism, the answer is a pool of copies of one workflow, not a multi-agent system.

Coordinator-worker, parallel, or sequential pipeline

Objective: "Evaluate multi-agent orchestration patternsβ€”coordinator-worker, parallel execution, and sequential pipelinesβ€”to select the structure best satisfying research coverage, latency, and reliability requirements."

A sequential pipeline is the multi-agent form of a prompt chain. Each stage is a separate model context with its own instructions and tools. The harness passes one stage's validated output to the next stage as input. Halden uses one for the monthly carrier-contract review. Stage 1 extracts the promised service levels from each contract PDF. Stage 2 compares them with the month's measured performance. Stage 3 drafts the findings. Stage 4 checks the draft against legal wording rules. Stage 2 cannot start before stage 1, because it needs the extracted terms. Between stages, code checks the output against a schema, the same way as in a prompt chain.

Read the stem for three kinds of evidence: coverage, latency, and reliability. Each structure is strong on some of them and weak on others:

Structure Coverage (are all needed areas researched?) Latency (wall-clock time) Reliability (what happens when one part fails)
Coordinator-worker Best when the areas are not known in advance. The coordinator plans the areas, compares results with the goal, and can send a follow-up subtask for a gap Planning call + slowest worker + merge, plus one more round for each gap The coordinator is the single point of judgment. Workers return validated results; a missing or failed worker becomes a named partial state
Parallel execution Fixed by the partition list. Complete only if the list is complete About the slowest slice, not the sum One failed slice is isolated. Retry that slice only
Sequential pipeline Fixed by the stage list The sum of all stages Easiest to check and audit: a gate after each stage. A failed stage stops everything after it (fail fast)

Now the selection rule, keyed on the evidence in the stem.

Situation Evidence Choose Why the tempting alternative is wrong
Weekly root-cause report: which carriers, lanes, and customers? The areas worth researching depend on what this week's data shows; the requirement is coverage Coordinator-worker Parallel over a fixed list cannot add an area the list forgot. Sequential would make each area wait for the previous one for no reason
Re-classify 30 days of exceptions, one slice per carrier Partition known; slices independent; the requirement is latency Parallel execution (code fan-out) A coordinator adds a planning call and a non-deterministic plan, with no coverage benefit. Sequential multiplies the wall-clock time by the number of carriers
Monthly contract review: extract β†’ compare β†’ draft β†’ legal check Each stage needs the previous output; the requirement is reliability and an audit trail Sequential pipeline with a validation gate between stages Parallel starts stage 2 without the extracted terms, so it guesses. A coordinator that "decides the order" adds variation to an order that never changes
Report misses a whole area, yet every worker reports success Workers did their assignments; nobody was assigned the missing area Fix the coordinator's decomposition (and add a coverage check against the goal) Re-prompting or replacing the workers cannot help. They were never given that area

Structures combine. A coordinator can run its workers in parallel. A stage in a pipeline can itself fan out. Choose the structure at each point by the evidence at that point. Say in the ADR which requirement each choice protects.

πŸ” See it run β€” the report that missed customers

The symptom. In week 38 the report covered carriers and lanes well. It said nothing about the enterprise customer whose SLA was breached four times. Every worker returned status: "done".

The evidence. The coordinator's plan for week 38 had only two subtasks: carriers and lanes. No worker failed. No worker was asked about customers.

The fix. The failure sits in the coordinator's decomposition, so the fix sits there too. The harness owner adds a coverage rule that code can check: every plan must include the three required areas (carriers, lanes, customers). The coordinator may add more areas, but it may not drop these. Before the merge, the coordinator also compares the findings with the goal and lists the open questions. Each open question becomes one more worker round, inside the plan budget.

What this proves. Coverage is the coordinator's job. Worker success says nothing about whether the right subtasks existed.

Goal-oriented or procedural delegation

Objective: "Evaluate subagent delegation strategiesβ€”goal-oriented versus procedural instructionsβ€”and select the approach that enables adaptive behavior while maintaining coordinator visibility and control."

Goal-oriented does not mean vague. The brief has four parts. The goal says what the result is for. The success criteria say how the coordinator will judge it. The constraints say what the worker may not do: scope, tools, turn budget. The return contract says what comes back. The contract asks for findings, but also for the method the worker used, its confidence, and anything it could not resolve. The worker adapts inside these limits. The coordinator keeps visibility (it can see how each result was reached) and control (it judges the result against criteria it wrote).

Here is the brief for the customers worker. It is illustrative application data: the coordinator model writes it under a schema, and the harness sends it as the worker's first user-role message. A person does not type it.

JSON
{
  "subtask_id": "customers",
  "goal": "Explain the week-37 SLA breaches for enterprise customers so the ops lead can act on them.",
  "success_criteria": [
    "Every row in sla_breaches_week37.csv is either explained or listed as unmatched",
    "Each explanation names the exception category and case_id"
  ],
  "constraints": {
    "inputs": ["exceptions_week37.jsonl", "sla_breaches_week37.csv"],
    "tools": ["read_dataset"],
    "max_turns": 8,
    "out_of_scope": "Do not propose credits or contract changes"
  },
  "return_contract": {
    "findings": "list of {customer, case_id, category, cause}",
    "unmatched": "list of breach rows you could not link, with the reason",
    "method": "two or three sentences: how you joined the files",
    "confidence": "high | medium | low"
  }
}

Why this brief handles the week-37 surprise. The customer IDs in the breach file had a different format from the exception file. A procedural script ("join on customer_id") gets zero matches, or it loops until the budget runs out. A goal-oriented worker can notice the mismatch, try matching by case_id instead, and report what it did in method. Rows it still cannot link go into unmatched. The coordinator sees the changed method and the gaps, so the adaptation is visible, not hidden.

Who enforces what. The brief only states the constraints to the model. The harness enforces the ones that must hold: it gives the worker only the read_dataset tool and stops it after 8 turns. The schema check on the returned object is also code. A sentence in the brief is guidance; a missing tool is a boundary.

When procedural instructions are the right choice. Use a fixed script when the steps themselves are the requirement. An example is a customs-compliance check where regulation sets the order of the checks, and each step must be done and recorded. Then adaptivity is a risk, not a benefit. Ask a second question too: if every step is fixed, does the step need a model at all? Often the right design is code for the fixed steps, with a model call only for the one step that needs judgment (reading a free-text document, for example).

Situation Evidence Choose Why the tempting alternative is wrong
Research where the data may surprise you Inputs vary; the right method depends on what the worker finds Goal + success criteria + constraints + return contract A step-by-step script breaks on the first surprise, and the worker cannot report why
Regulated check with a mandated order The order and evidence of each step are the requirement Procedural steps, each recorded (or plain code) "Let the worker decide" can skip or reorder a mandated step
Coordinator cannot tell how a worker reached its result Results arrive as free text with no method or gaps Add a return contract (method, confidence, unmatched) Writing more detailed steps removes adaptivity and still does not report gaps
Worker uses a tool it should not have A constraint was only written in the brief Remove the tool in the harness A stronger sentence in the brief is still a request, not a boundary

Persist pipeline state for reliable resumption

Objective: "Design state persistence strategies for multi-agent pipelines that enable reliable resumption after interruption without repeating completed work or losing prior findings."

Name the runtime first. Halden's report harness is a custom application on the Messages API. The API keeps no state between requests: the application sends the whole messages array every time. So the application must keep the run's state. Claude Code and the Agent SDK can resume a session (claude --resume <id>; in the SDK, resume=<session_id>). Session resume restores the conversation, including its tool calls. It does not tell you which subtask outputs were saved. The Claude Code docs also say: "Sessions persist the conversation, not the filesystem." A session is not a work manifest.

The manifest is the checkpoint, and the coordinator's harness owns it. Workers never write it directly. The harness writes one entry per subtask. Each entry has a status, the attempt count, a key built from the inputs, and a pointer to the stored output. Here is the manifest as the application stores it (application state, not an API payload; the storage path is illustrative):

JSON
{
  "run_id": "rca-2026-w37",
  "goal": "Weekly root-cause analysis of shipment exceptions, week 37",
  "plan_ref": "runs/rca-2026-w37/plan.json",
  "subtasks": [
    {
      "id": "carriers",
      "status": "done",
      "attempts": 1,
      "input_key": "sha256:9f2c…",
      "output_ref": "runs/rca-2026-w37/carriers.json",
      "completed_at": "2026-09-19T06:12:40Z"
    },
    {
      "id": "lanes",
      "status": "done",
      "attempts": 1,
      "input_key": "sha256:41ab…",
      "output_ref": "runs/rca-2026-w37/lanes.json",
      "completed_at": "2026-09-19T06:13:02Z"
    },
    {
      "id": "customers",
      "status": "running",
      "attempts": 1,
      "input_key": "sha256:c07e…",
      "output_ref": null,
      "completed_at": null
    }
  ],
  "merge": {"status": "pending", "output_ref": null}
}

Five rules make resumption reliable.

Rule What the harness does What it prevents
Store outputs outside the context Each worker's validated result is written to durable storage; the manifest keeps only the pointer Losing findings when a context or process ends
Save the output first, then mark it done Write carriers.json, then set status: "done" A "done" entry that points to nothing after a crash between the two writes
Make each subtask idempotent The key is subtask id + input_key. A rerun with the same key overwrites the same output; it never adds a second one Duplicate findings or duplicate side effects
Resume = read the manifest Skip done entries whose input_key still matches. Rerun pending, running and failed entries Repeating finished work, and silently skipping unfinished work
Merge from stored outputs The merge call loads summaries through output_ref, not from any earlier context A merge that depends on a context that no longer exists

Why running is rerun. After a crash, a running entry means "started, but no saved output". The harness cannot know whether the worker was nearly finished. Because the subtask is idempotent, running it again is safe. If the inputs changed since the first run (the input_key no longer matches), a done entry is also stale, and the harness reruns it.

Situation Evidence Choose Why the tempting alternative is wrong
Run stops after 2 of 3 workers finish Two findings are saved; one subtask has no output Resume from the manifest: run only customers, then merge "Re-run everything" pays for the two finished workers again, takes longer, and can produce different findings for work that was already accepted
Coordinator context is replaced or compacted mid-run Earlier worker findings existed only in the coordinator's messages Store each finding outside the context; keep pointers in the manifest "Rely on the conversation history" fails exactly when history is lost or summarized
Rerun after a crash creates duplicate rows in the report The rerun added a second copy of the same subtask's output Idempotent writes keyed by subtask id + input key "Be careful not to rerun" is not a control; crashes decide when reruns happen
A stage's input file was corrected after the first run The input_key of a done subtask no longer matches Rerun that subtask; keep the others Trusting the old done status merges findings built on the wrong data

Foundations shows the code for saving and resuming a single run in start and resume: the code fundamentals, and a multi-stage version in what the coordinator saves between stages.

πŸ” See it run β€” Friday's report survives a restart

Step 1: the interruption. At 06:13 the report job's container is restarted during a deploy. carriers and lanes are done, with saved outputs. customers is running, with no output.

Step 2: the scheduler starts the job again with the same run_id. The harness finds runs/rca-2026-w37/manifest.json and does not create a new plan. It recomputes each input_key; all three still match.

Step 3: only unfinished work runs. The harness skips carriers and lanes. It starts customers again with a fresh context and the same brief, as attempt 2. The worker finishes. The harness writes customers.json, then marks the entry done.

Step 4: the merge reads from storage. The merge call loads the three stored summaries through their output_ref values. No earlier context is needed.

What this proves. The cost of the restart was one worker, not three. No accepted finding changed, and none was lost.

Apply decomposition techniques for complex problem solving

Objective: "Apply decomposition techniques for complex problem solving."

Decomposition is the point where the architecture stops being a sketch. There are four techniques, and each one creates a different kind of seam:

Technique Seam is at… Produces Halden example
Functional split A change in the kind of work A prompt chain with code between calls classify β†’ retrieve β†’ draft β†’ verify β†’ send
Risk split A change in what can go wrong A gate or a route to a person money-related intents leave the model path entirely
Data split A change in what the step needs to see Separate calls or [isolated contexts](term:context isolation) the classifier never sees the customer history; the drafter never sees other customers' cases
Skill split A change in which model or prompt is best [Routing](term:workflow routing) to a specialised prompt or a different [model tier](term:model tiering) damage reports with photos route to a vision-capable prompt

The decomposition tree is the artifact. The architect writes it before any prompt. Each leaf holds the contract that the prompt owner must meet. The house format is YAML, because it stays easy to read in a review:

YAML
problem: Reply correctly to a shipment-exception email within 2 h
decomposition:
  - step: classify
    kind: model_call
    input: email text only
    output: {category: enum, confidence: number}
    check: confidence >= 0.8 else route human
    owner: prompt owner
  - step: retrieve
    kind: code
    input: tracking number parsed from email
    output: shipment record snapshot
    check: record exists and belongs to sender's account
    owner: platform team
  - step: draft
    kind: model_call
    input: shipment record, classification, allowed next_actions
    output: {reply_text: string, next_action: enum}
    check: schema-validated; next_action in allowed list
    owner: prompt owner
  - step: verify
    kind: code
    input: reply_text
    output: pass | block
    check: no currency amounts; no claim language; tier template matches
    owner: platform team
  - step: send
    kind: code
    input: verified reply, next_action
    output: ticket update, customer email
    check: idempotency key = case_id
    owner: platform team

Read the owner field the way the exam reads it. Model calls belong to the prompt owner, who tests them with the golden set. Code steps belong to the platform team, who test them with unit tests. A stem may ask "who should own the accuracy of the classification step". It is testing whether you kept these two kinds of owner apart.

Decomposition also decides what is cached and what is retried. The output of the retrieve step is a snapshot. The draft call can reuse it if the draft is retried. The send step carries an idempotency key, so a retry after a timeout cannot email the customer twice. You do not add these later; they come directly from the seams. Foundations covers the mechanics in scale a fixed workflow across many documents.

The wrong choice and its failure. Imagine one prompt that says "1. classify the email, 2. look up the shipment, 3. draft a reply, 4. check for refund language, 5. send". That is not decomposition. It is a numbered list inside a single call. Without a tool, the model cannot look anything up and cannot send anything. Its "check" is only a sentence, not code. The exam distractor says "add step-by-step instructions to the system prompt". It fails because steps inside a prompt are not seams with contracts. The exam explains this with two pairs: written instruction versus enforced boundary, and single-call versus chained with checks.

Select the review architecture (plan mode, direct execution, multi-phase)

Objective: "Select the appropriate agentic review architectureβ€”plan mode, direct execution, or multi-phase workflowβ€”based on task scope, risk level, and human approval requirements."

Name the runtime. This section is about Halden's platform team using Claude Code (the coding agent) to change Halden's own code. Plan mode is a Claude Code permission mode. The Claude Code harness enforces it, not the model. A custom Messages API application has no plan mode; there you build the same review step yourself (for example, a proposal that waits in an approval queue).

What plan mode does in Claude Code (from the Claude Code docs). Claude "reads files, runs shell commands to explore, and writes a plan, but does not edit your source." Edits stay blocked until you approve the plan. Commands outside the read-only set ask for permission. When you approve, Claude Code leaves plan mode and enters the mode that the approve option names. Ways to turn it on:

BASH
# Start a session in plan mode
claude --permission-mode plan

# In a running session: press Shift+Tab to cycle default -> acceptEdits -> plan,
# or start one prompt with /plan

To make plan mode the project default, set it in .claude/settings.json (illustrative file, one key shown):

JSON
{
  "permissions": {
    "defaultMode": "plan"
  }
}

One documented exception. In interactive terminal sessions where bypass permissions are available, plan-mode blocks are not enforced. In -p (print, headless) runs and Agent SDK sessions, plan mode keeps its blocks. So if a team relies on plan mode as a control, it must check how its sessions are configured.

Three review architectures, three kinds of evidence.

Architecture Scope Risk Human approval Where the review happens
Direct execution Small, clear, well specified (one file, known fix) Low; tests cover it None before the change; normal code review after Tests + the usual pull-request review
Plan mode Several files, or several valid approaches, or a sensitive file Medium to high The developer approves the plan inside the session Read-only exploration, then the plan, before any edit
Multi-phase workflow Large (many files, several services), or regulated High; mistakes are costly or hard to undo Recorded approval, often by someone other than the developer, before implementation A gate after each phase: explore β†’ plan β†’ approve β†’ implement (in stages) β†’ verify

Multi-phase is more than plan mode. Plan mode gives one review point inside one session. A multi-phase workflow makes each phase produce an artifact that people can check. Exploration produces a written map (an Explore subagent keeps that long output out of the main context). Planning produces a plan document. Approval produces a record, for example a change ticket or an ADR, signed by the owner the brief names. Implementation runs in stages. Verification runs the tests, the eval set, and a review against the plan. This is a design practice built from Claude Code features; Claude Code does not have a single "multi-phase" switch.

Situation Evidence Choose Why the tempting alternative is wrong
Typo in the delay-reply template One file; no logic change; tests exist Direct execution Plan mode adds a review step that finds nothing; it only slows the fix
Add offer_redelivery to the send gate's allowed actions One file, but it is the money-safety control; Legal owns that line of the brief Plan mode, and the plan names the eval cases to add "It's one line, edit directly" treats small scope as low risk. The risk is in what the file controls
Replace the ticketing integration (40 files, customer data, an EU data-residency rule) Large scope, regulated data, approval needed from the platform owner Multi-phase: explore β†’ plan β†’ recorded approval β†’ staged implementation β†’ verify Plan mode alone gives no recorded sign-off by the owner and no verify phase. Direct execution "lets the design emerge" and finds the dependencies in the middle of editing
Team asks for "always ask before editing" The rule is written in CLAUDE.md Use plan mode or permission settings, which the harness enforces The Claude Code docs treat CLAUDE.md as context, not enforced configuration. A sentence there is a request

Align solutions to business value pillars (efficiency, transformation, productivity, cost, performance SLAs)

Objective: "Align solutions to business value pillars (efficiency, transformation, productivity, cost, performance SLAs)."

The exam's stems use the five pillars as their vocabulary. Learn to recognise them:

Pillar The stem says… The number Halden target
Efficiency "staff spend hours on…", "backlog" Hours or cases per person per day 22 staff-hours/day recovered; ops handle only escalations
Transformation "customers cannot…", "new capability" Something that was not possible before Customers check exception status themselves; 24 h coverage
Productivity "each analyst can only…", "throughput per person" Output per person per unit time Each operator handles 3Γ— more cases per day (escalations only)
Cost "spend", "per-request", "budget" Money spent per unit of work Model spend per exception stays under the finance ceiling; reviewed monthly
Performance SLA "must respond within", "p95", "uptime" A threshold with a consequence p95 first reply < 2 h; correct-reply rate β‰₯ 90%

An SLA is a contract, so write it like a contract. The SLA table names six things: the metric, the threshold, the measurement window, how it is measured, what happens when it is missed, and which design choice protects it. The last column belongs to the architect, and the exam looks there.

SLA Threshold Window Measured from Breach action Design that protects it
First-reply latency p95 < 2 h Rolling 7 days Email received β†’ reply sent (ticket timestamps) Alert the ops lead; send enterprise-tier cases to the human queue automatically Fixed workflow path with one tool round; [prompt caching](term:prompt caching) on the policy prefix
First-reply correctness β‰₯ 90% Weekly A sample of cases, graded against what an operator would decide Stop prompt changes; run the eval; find the root cause Feedback loop β†’ [golden set](term:golden set); eval gate before any prompt change
Money-safety 0 replies with financial promises Continuous Send-gate block log + weekly audit sample Stop automatic sending at once; open an incident Deterministic send gate; refund intents routed to humans
Agent-path containment 100% of customs-hold runs end within 6 turns Continuous Run trace stop_state Alert; send the case to the human queue with its trace Turn budget in harness; budget exhaustion is a named state
Cost per exception Under finance ceiling Monthly usage fields summed per case Review the model tier; retrieve less, more precise context [Model tiering](term:model tiering); caching; batch API for the weekly report

SLOs are the internal targets behind the SLA. The team works toward an internal objective that is stricter than the contract (p95 < 90 min), so that breaches are rare. In a repository, the objectives sit next to the alerts:

YAML
service: exception-triage
slos:
  - name: first_reply_latency
    objective: p95 <= 90m
    sla_threshold: p95 <= 120m
    window: 7d
    source: ticket.created_at -> ticket.first_reply_at
    alert: p95 > 100m for 30m
  - name: first_reply_correctness
    objective: ">= 0.93"
    sla_threshold: ">= 0.90"
    window: 7d
    source: weekly graded sample of 200 cases
    alert: "< 0.92 on any weekly sample"
  - name: money_safety
    objective: 0 financial promises sent
    window: continuous
    source: send_gate.blocked + audit_sample
    alert: any audit finding

Pillars conflict, and the ADR records the ranking. Halden's cost pillar pushes for the faster model tier everywhere. The correctness SLA pushes for the larger tier on customs holds. The architect ranks them: money-safety and correctness above cost, and cost above latency (as long as latency stays inside the SLA). The architect writes this ranking into the consequences section of the ADR. When a stem shows two pillars in conflict, it asks for that ranking, not for a way to satisfy both.

The workload shape decides which lever helps which pillar. Interactive triage is limited by latency and makes thousands of small calls. Caching and a fast tier help it. The weekly report runs offline and makes a few large calls. The [batch API](term:batch API) helps it, at lower cost, because there is no latency pressure. Using the batch API for the triage path would break the SLA. Using real-time calls for the weekly report wastes money against the cost pillar.

πŸ” See it run β€” the VP asks "why not just one big agent?" and the ADR answers

The question. Two weeks after launch, the VP reads about agents. The VP asks why Halden built "a pipeline" instead. The architect answers with ADR-003 and the SLA table, not with a slide deck.

Pillar 1: performance SLA. Last week, the p95 of the workflow path was 71 minutes. The p95 of the bounded agent path was 3 h 40 min. That is acceptable only because customs holds are 6% of volume and the enterprise contracts exclude them. One agent for all volume would put the 71-minute number at risk for the 85% of cases that bring in the money.

Pillar 2: cost. The usage fields show this. On average, the workflow path uses one cached policy prefix plus about 700 uncached input tokens per case. The agent path uses six model calls per case on average. One agent everywhere would multiply the spend on the 85% by about the agent's number of calls.

Pillar 3: money-safety. Last week, the send gate blocked 14 replies that contained the word "credit." All of them came from the workflow path. An unbounded agent with a refund tool has no gate that can block it. The team would need to redesign the harness, and until then the system would break the constraint in the brief.

The answer the VP hears. "We use an agent where nobody can know the path in advance (customs holds), and a workflow where we can know it. The ADR lists the single-agent option as alternative 1, and the numbers above are why it lost. If the mix of exceptions changes, we replace the ADR with a new one; we do not ignore it."

What this proves. Value alignment is not a slide. It is the last column of the SLA table plus the alternatives section of the ADR, updated with live numbers. That is what "align solutions to business value pillars" looks like as an artifact.

The wrong choice and its failure. Imagine a solution justified only as "faster and more accurate", with no baseline. When the spending appears on the finance report, nobody can defend it. The exam distractor is the design that optimises a pillar the stem did not ask for. Examples: a cheaper model when the stem said accuracy is in the contract, or maximum autonomy when the stem asked for predictability. The exam explains this with two phrases: stated constraint versus assumed preference, and optimising the wrong pillar.

Exam lens β€” how CCAR-P asks about this domain

Tells: phrases that name the objective being tested.

Trade-off words that the rationale uses. Learn to recognise these pairs. A distractor is always the other half of a pair:

Pair The correct answer usually sits on…
Predictability vs flexibility Predictability, unless the stem says the path cannot be known
Bounded vs unbounded autonomy Bounded β€” a budget that ends in a named state
Decomposition benefit vs coordination overhead Overhead wins unless isolation or independence is stated
Written instruction vs enforced boundary Enforced β€” code gates, not prompt sentences
Stated constraint vs assumed preference The stated one; never optimise a pillar the stem did not name
Removal vs compensating control Removal β€” leave the tool out β€” when the role does not need the capability
Detective vs preventive control Preventive for money and safety; detective (logs, dashboards) is necessary but insufficient

How the three official sample questions connect to this page. Sample 1 (remove unneeded tools) is scope translation: the out-of-scope list in the brief decides the tool list. Sample 2 (stable prefix + caching) is about the cost pillar and the workload shape. Sample 3 (retrieval after a refresh) is about the end-to-end loop: the input stage changed, so look there first. "A better model" is not the answer to any of them, and it is not the answer to the items in this domain either.