CCAR-F · Study Guide

← Domain 1: Agentic Architecture & Orchestration

1.4 · Lesson 4 of 7

Workflow Enforcement and Handoff

What you need to know

Task statement 1.4 is the choice between a prompt and a programmatic control. Prompt guidance is advice the model usually follows. A gate is code that runs whether or not the model agrees. On a high-stakes mistake — a refund, a permission change, a compliance step — a probabilistic rule costs marks.

The enforcement spectrum

A prompt such as "Always verify the customer before a refund" lands in the 90–95% range. The remaining calls skip the check. A deterministic gate refuses process_refund until get_customer has verified the identity for this session. The model can request the refund. The gate decides whether the tool runs.

The exam decision rule

  • Financial, security, and compliance operations use programmatic enforcement.
  • Low-stakes preferences — formatting, style, ordering — can stay in the prompt.
  • Reject a stronger prompt, and reject few-shot examples, when the operation moves money, changes access, or has a compliance consequence.

Prerequisite gates in practice

The refund flow has three tools: get_customer, lookup_order, and process_refund. The gate sits in front of the refund.

  1. Tools. get_customer resolves the person. lookup_order reads the order. process_refund moves the money.
  2. The check. The gate looks for a verified customer ID stored for this session.
  3. If it is present. The refund runs against that ID.
  4. If it is missing. The call returns: "Cannot process refund — customer identity not verified. Please call get_customer first."

The gate is code. The model cannot skip it by rephrasing the request.

Subagent lifecycle hooks: SubagentStart and SubagentStop

SubagentStart fires when the Task or Agent tool spawns a subagent. It receives the subagent type and id. It cannot block the spawn. It can return additionalContext as JSON, and that JSON is placed in the subagent's context before the first prompt. To refuse the spawn itself, use PreToolUse on the Agent tool.

SubagentStop fires when the subagent finishes. It receives the id and the final message. Exit code 2 blocks the stop and sends the subagent back to keep working. There is no decision field. The hook does not rewrite the output. The coordinator reshapes the result after it comes back.

SubagentStartSubagentStop
FiresWhen a Task or Agent tool spawns a subagentWhen a subagent finishes
ReceivesSubagent type and idSubagent id and the final message
Can blockNo. It cannot stop the spawn.Yes. Exit code 2 blocks the stop and sends the subagent back.
Can add contextYes. additionalContext JSON is injected before the first prompt.No. It does not append context to the result.
Can rewrite the resultNoNo. The coordinator reshapes the output after the subagent returns.
Typical useLog the spawn, or inject constraints the subagent must see first.Reject a final message that fails a schema.
To enforce insteadPreToolUse on the Agent tool, if the spawn itself must be blocked.The coordinator, after the result comes back, if the output must be rewritten.

Register both in settings.json or in frontmatter. The start command only logs the spawn. Any additionalContext is JSON printed by the script. The stop command reads stdin and exits 2 when the final message fails the schema.

{
  "hooks": {
    "SubagentStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date) $CLAUDE_SUBAGENT_ID\" >> .claude/spawns.log"
          }
        ]
      }
    ],
    "SubagentStop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/validate-subagent-output.sh"
          }
        ]
      }
    ]
  }
}

A hook declared in a subagent's frontmatter is scoped to that subagent for its lifetime. A billing subagent can carry its own PreToolUse check, for example a refund threshold, without applying that check to every other agent. A Stop hook written in subagent frontmatter is converted to SubagentStop.

Multi-concern request handling

A customer asks, in one message, to start a return, update a shipping address, and check a loyalty balance. The correct handling is to decompose the message, investigate the independent parts in parallel with the same account context, and return one answer that covers all three.

The wrong handling is a new conversation per concern, or answering only the first item and dropping the rest.

Structured handoff protocols

The human who receives the case does not have the transcript. The payload has to stand on its own. Five fields:

  1. Customer ID
  2. Conversation summary
  3. Root cause analysis
  4. Refund amount, if a refund applies
  5. Recommended action

Practical example: the 8% failure rate

The prompt says "Always verify the customer with get_customer before process_refund." It works on 92% of calls and fails on 8%. Those failures refund the wrong account.

The fix is a gate in front of process_refund that requires a customer ID verified by get_customer in this session. A stronger prompt is not the fix.

Exam traps

  • Use a stronger prompt for a high-stakes step

    A stricter sentence can move an 8% miss rate to 3% or 4%. It never reaches zero. If one failure is a financial loss, a security breach, or a compliance violation, the enforcement is code.

  • Add a few-shot example of the correct sequence

    Few-shot examples are still probabilistic. The model can follow the sample and still skip verification on a later turn.

  • Add a routing classifier

    A classifier chooses which agent receives the request. It does not force get_customer to run before process_refund inside that agent.

  • Hand off without a customer ID or a recommended action

    The human has no transcript. The payload needs customer ID, conversation summary, root cause analysis, refund amount if applicable, and recommended action. Drop either the customer ID or the recommended action and the handoff is incomplete.

Practice scenario

A refund agent is told in its prompt to always verify the customer with get_customer before calling process_refund. In production, about 8% of refunds still go out against an unverified account. What is the correct fix?

Choose one answer

Build exercise

Build a prerequisite gate for financial operations

60 minutes

What you will learn

  • Why money uses programmatic enforcement
  • How a gate blocks a tool until its precondition is true
  • Why an 8% prompt failure is not fixed by a 0% claim in the prompt
  • How a five-field handoff stands in for a transcript the human does not have
  • How a multi-concern request is investigated in parallel and answered once
  1. Step 1

    Define the three financial tools

    Declare get_customer, lookup_order, and process_refund. get_customer takes a name or email and returns a customer id plus a verification status. lookup_order takes an order id. process_refund takes a customer id and an amount. Give each tool a JSON Schema input_schema.

    Why: The gate can only check a tool that exists as code. A prompt that names process_refund does not create a precondition.

    You should see: Three tool definitions, each with a name, a description, and an input_schema. process_refund's required fields are customer id and amount.

  2. Step 2

    Gate process_refund on session state

    Before process_refund runs, read session state. Allow the call only when get_customer has already stored a verified customer id for this session. Otherwise return an error and do not call the refund.

    Why: The check lives in code, so the model cannot talk its way past it. A prompt reminder is not a gate.

    You should see: A refused refund whose error is: Cannot process refund — customer identity not verified. Please call get_customer first.

  3. Step 3

    Prove the gate, then the happy path

    Prompt the agent to refund without verifying. Confirm the gate blocks process_refund. Then call get_customer, and only after that call process_refund.

    Why: The exam wants the failure you can see: the tool does not run until the precondition is true. A prompt that usually complies is not evidence.

    You should see: First attempt: the error, and no refund. Second attempt: get_customer stores a verified id, then process_refund runs for that id.

  4. Step 4

    Write the five-field handoff

    When a human has to take over, build one object with customer ID, conversation summary, root cause analysis, refund amount if one applies, and recommended action. The human has no transcript.

    Why: A missing customer ID or a missing recommended action leaves the human unable to act. Those two fields are the usual incomplete-handoff traps.

    You should see: One JSON object with all five fields populated. Refund amount is a number or null when no refund applies. There is no full transcript.

  5. Step 5

    Handle a multi-concern request in one response

    Take a request that asks for a return, a billing dispute, and an account update. Investigate the three concerns in parallel against the same account, then write one summary that covers all three.

    Why: Handling only the first item, or opening a new conversation per item, drops work the customer already asked for.

    You should see: The final summary names the return, the billing dispute, and the account update. None of the three is missing.

Sources