CCAR-F Claude Certified Architect — Foundations

Chapter 04 — Enforcement, Hooks, and Decomposition Strategy

Domain 1: Agentic Architecture & Orchestration (27%) — task statements 1.4, 1.5, 1.6

If you learn one thing from Domain 1, learn this chapter's central claim: hooks are deterministic, prompts are probabilistic. It is the answer to more items than any other single idea on this exam.


1. Programmatic enforcement vs prompt guidance

🎯 The core principle

Prompt instructions have a non-zero failure rate. That's the guide's own language, and it's the whole argument. A system prompt that says "always call get_customer before process_refund" will be followed most of the time. "Most of the time" is fine for formatting conventions. It is not fine for issuing refunds.

Mechanism Guarantee Appropriate for
System prompt instruction Probabilistic — usually followed Style, tone, preferences, soft conventions
Few-shot examples Probabilistic — improves consistency Ambiguous judgement calls, output shape
Hook / prerequisite gate / programmatic check Deterministic — cannot be bypassed Ordering, authorization, limits, anything irreversible

The decision rule

Ask: what happens the one time in fifty that the model doesn't comply?

  • Output is slightly off-style → prompt is fine.
  • A refund is issued to an unverified customer → you need a gate.
  • A compliance record is written without a required field → you need a gate.

If the answer involves money, policy violation, data loss, or anything you can't undo, the correct answer is deterministic enforcement. ⚠️ Options offering "add a stronger instruction to the system prompt," "provide few-shot examples of the correct order," or "clarify the tool descriptions" are all plausible and all lose to a gate in these items.

The canonical example

Scenario 1's requirement: process_refund must never execute before get_customer has returned a verified customer ID.

# Deterministic gate: the tool cannot run until its prerequisite is satisfied.
def pre_tool_use(tool_name, tool_input, session_state):
    if tool_name == "process_refund":
        verified_id = session_state.get("verified_customer_id")
        if not verified_id:
            return {
                "block": True,
                "reason": (
                    "process_refund requires a verified customer ID. "
                    "Call get_customer first and confirm the returned "
                    "verification status before attempting a refund."
                ),
            }
    return {"block": False}

Two things to notice, because both are examinable:

  1. The gate blocks the action, so no prompt-following failure can produce a refund.
  2. The block returns an actionable reason to the model, so the agent recovers rather than dead-ends. A gate that returns a bare "denied" leaves the agent stuck — this is the same principle as structured errors (Chapter 06 §3).

2. Agent SDK hooks

Task statement 1.5. Hooks intercept the agentic loop at defined points, in your code, deterministically.

📎 PostToolUse — normalizing heterogeneous tool output

The guide's exact use case: an agent talks to several backend systems that return the same logical field in different shapes — Unix timestamps from one, ISO 8601 from another, numeric status codes from a third. The model can usually cope, but "usually" produces intermittent misreadings that are extremely hard to debug.

The fix is a PostToolUse hook that normalizes every tool result before it enters the conversation:

def post_tool_use(tool_name, tool_result):
    """Normalize before the model ever sees it. Deterministic, not probabilistic."""
    if "created_at" in tool_result:
        tool_result["created_at"] = to_iso8601(tool_result["created_at"])
    if "status" in tool_result:
        tool_result["status"] = STATUS_CODE_MAP.get(
            tool_result["status"], tool_result["status"]
        )
    return tool_result

🎯 The item shape: "different backend systems return timestamps in different formats and the agent occasionally misinterprets dates — what should the architect do?" The answer is a PostToolUse hook. The distractors: tell the model about the formats in the system prompt (probabilistic), add few-shot examples of each format (probabilistic), or modify each backend (out of scope, high effort, often impossible).

⚠️ Note what this hook is not for: it isn't a place to reduce verbosity for cost reasons only — though trimming there is legitimate (Chapter 11). Its examinable purpose is making heterogeneous data uniform before the model reasons over it.

📎 Tool-call interception — blocking and redirecting

Hooks can inspect a pending tool call and block or redirect it. The guide's example: refunds above $500 must not be processed automatically; they must be redirected to human escalation.

def pre_tool_use(tool_name, tool_input, session_state):
    if tool_name == "process_refund" and tool_input.get("amount", 0) > 500:
        return {
            "block": True,
            "reason": (
                "Refunds over $500 require human approval. "
                "Call escalate_to_human with the customer ID, order ID, "
                "requested amount, and the reason for the refund."
            ),
        }
    return {"block": False}

Again: block and redirect. The hook doesn't just refuse; it names the correct next action. A hook that blocks without direction converts a policy violation into a stuck conversation.

Where hooks sit relative to everything else

        user turn
            │
            ▼
    ┌───────────────┐
    │  model turn   │  ← system prompt, tools, few-shot  (PROBABILISTIC)
    └───────────────┘
            │  requests tool
            ▼
    ┌───────────────┐
    │  PreToolUse   │  ← gate: block / allow / redirect    (DETERMINISTIC)
    └───────────────┘
            │  executes
            ▼
    ┌───────────────┐
    │  PostToolUse  │  ← normalize / trim / annotate        (DETERMINISTIC)
    └───────────────┘
            │  result appended
            ▼
        loop continues

🧠 Read the diagram as a policy statement: everything above the first hook is advice to the model; everything at or below is enforcement. Put a requirement in the layer that matches its consequence.


3. Structured handoffs

Task statement 1.4, second half. When an agent escalates or hands off, what it passes is examinable.

A handoff to a human agent should carry a structured summary, not the raw conversation. The guide's named fields for Scenario 1:

Field Why
Customer ID The human shouldn't re-identify the customer
Root cause What the agent determined, not the transcript
Refund amount (or the relevant quantity) The specific number in dispute
Recommended action The agent's proposal, so the human reviews rather than restarts

⚠️ The distractor is "pass the full conversation transcript." It sounds thorough and is worse: it forces the human to re-derive conclusions the agent already reached, and it buries the numbers. The same logic applies between agents — upstream agents should return structured data, not verbose reasoning (Chapter 11 §3).

📎 Note the parallel structure across three different boundaries. All three want the same shape:

Boundary Carries
Agent → human (escalation) customer ID, root cause, amount, recommended action
Subagent → coordinator (error) failure type, retryability, what was attempted, partial results, alternatives
Session → session (fresh start) structured summary of durable facts, not stale tool results

One reflex covers all three: cross a boundary with conclusions and identifiers, never with raw history.


4. Task decomposition strategy

Task statement 1.6. Two named strategies, and a rule for choosing.

Prompt chaining vs dynamic adaptive decomposition

Prompt chaining Dynamic adaptive decomposition
Structure A fixed sequence of prompts; each stage's output feeds the next The agent decides what to investigate next based on findings
Fits Predictable, multi-aspect work where the aspects are known in advance Open-ended investigation where the path depends on what you find
Example Code review covering security, then performance, then style — every time "Find out why this service started timing out last Tuesday"
Predictability High: same stages, same cost, auditable Low: variable depth and cost

🎯 The exam's discriminator is one word in the stem: is the work predictable or open-ended? A review that always covers the same five dimensions is a chain. A root-cause investigation is dynamic. Don't overthink it.

🎯🎯 Attention dilution and the split-pass pattern

This is one of the most reliably tested ideas on the whole exam, and it's the direct application of Reflex 7.

The problem: a code review agent given 15 changed files in one prompt misses issues it would catch in any single file. Not because the content doesn't fit — because attention spreads thin across a large prompt. This is attention dilution.

The fix: per-file passes, plus a cross-file integration pass.

Pass 1..N   → one focused review per changed file        (catches local issues)
Pass N+1    → integration review across all files        (catches interface and
                                                          contract mismatches)

Both halves matter. Per-file passes alone miss the bugs that only exist between files — a changed function signature and its unchanged callers. The integration pass exists specifically to catch those.

⚠️ The distractor is always "use a model with a larger context window." It appears in the published sample questions and it is wrong: the content already fit. Capacity was never the constraint; attention was. Memorize this: larger context does not fix attention dilution.

Other options in this family that also lose: "increase max_tokens," "enable extended thinking," "summarize the files before reviewing." The last one is actively harmful — you'd be reviewing a summary instead of the code.


5. Worked examples

Example 1 — enforcement layer

In a customer support agent, process_refund has occasionally been called before the customer's identity was verified via get_customer, resulting in refunds to unverified accounts. The system prompt already instructs the agent to verify first. What is the most effective way to guarantee correct ordering?

A. Implement a programmatic prerequisite check that blocks process_refund until get_customer has returned a verified ID. B. Add few-shot examples to the system prompt demonstrating the correct call order. C. Rewrite the process_refund tool description to emphasize that verification is required first. D. Combine the two tools into a single verify_and_refund tool.

Answer and reasoning

A.

The stem contains the decisive detail: the system prompt already instructs it. Prompt guidance has been tried and has a non-zero failure rate; the consequence is financial. Only a deterministic gate provides a guarantee.

  • B — Few-shot improves the rate. It does not eliminate the failure. The requirement here is "guarantee."
  • C — Same class of fix as B: still an instruction the model may not follow, and the prompt already carries the instruction.
  • D — Genuinely tempting, and note why it's inferior: it removes the agent's ability to verify identity without refunding (a legitimate standalone need), couples two distinct operations, and still doesn't gate the refund path if the combined tool is called with incomplete data. It's a redesign where a check suffices — over-engineering (Reflex 2).

Reflex applied: #1. Financial consequence + prompt already tried → deterministic mechanism.

Example 2 — attention dilution

A CI code review agent reviews pull requests that typically touch 12–20 files. Reviewers observe that the agent catches far fewer issues per file than it does when asked to review a single file in isolation. The full diff fits comfortably within the model's context window. What is the most effective approach?

A. Run a separate focused review pass for each changed file, followed by one integration pass across all files. B. Switch to a model with a larger context window. C. Increase max_tokens so the agent can produce a longer review. D. Summarize each file before submitting the diff for review.

Answer and reasoning

A.

"The full diff fits comfortably within the context window" is the stem telling you capacity is not the problem. The symptom — good per-file performance in isolation, poor performance in bulk — is the definition of attention dilution. Split the passes. The integration pass is not optional: it's what catches cross-file contract breaks that per-file passes structurally cannot see.

  • B — The named distractor. Larger context adds capacity, and capacity was already sufficient. Attention dilution is unaffected.
  • C — Bounds output length. The problem is input attention, not output room.
  • D — Actively harmful: you'd be reviewing summaries rather than code, discarding exactly the detail a review depends on.

Reflex applied: #7. Split passes, don't enlarge context.


6. Decision reflexes (reproduce from memory)

  • Hooks are deterministic; prompts are probabilistic. Match the mechanism to the consequence.
  • Prompt instructions have a non-zero failure rate → never rely on them for money, policy, or irreversible actions.
  • "The system prompt already says so" in a stem = prompt guidance has been tried = the answer is a gate.
  • PreToolUse gates: block and redirect with an actionable reason (e.g. refunds > $500 → escalate_to_human).
  • PostToolUse hooks: normalize heterogeneous tool output (Unix vs ISO 8601 timestamps, numeric status codes) before the model sees it.
  • Cross any boundary with conclusions and identifiers, never raw history: escalations carry customer ID, root cause, amount, recommended action.
  • Prompt chaining for predictable multi-aspect work; dynamic decomposition for open-ended investigation.
  • Attention dilution → per-file passes + cross-file integration pass. Larger context windows do not fix it.