Skip to content
Everything Agents

Lesson 4 of 14 · 5 min

Anatomy of a turn

What actually happens between user input and assistant reply. Enabled tools are computed before the LLM sees anything.

The four control surfaces from the previous lesson don't fire all at once. They run in a specific order each turn. Knowing that order is what lets you predict where a bug will surface in the trace.

What actually happens inside the planner each time the user types a message?

Anatomy of a planner turnA horizontal pipeline of six stages inside one planner turn. Instructions resolve and enabled tools are computed deterministically. The LLM then picks a tool or reply. The chosen action runs and set clauses fire. after_reasoning runs deterministically before the assistant reply. The deterministic stages form a hard shell around the soft LLM core.instructionsif branchesenabled toolsavailable whenLLM pickstool or replyaction runsset firesafter_reasoningdeterministicreplyInformDETERMINISTICgates compute firstLLMsoft coreDETERMINISTICcapture, enforce, replyONE PLANNER TURNuser message → assistant reply

A few things to notice in this picture, because they explain almost every bug later in the tutorial:

  • The "enabled tools" list is computed before the LLM sees anything. If your action is missing from this list, the LLM cannot call it no matter how clear the prompt is.
  • set clauses run on the LLM's report of what the action returned, not on a raw byte stream from Apex. This is the seam where hallucination enters (covered in lesson 9).
  • after_reasoning runs last and is fully deterministic. Use it to enforce invariants that the LLM should not be trusted to maintain.

Two-level actions: definitions vs invocations

A subtle but crucial distinction.

  • Level 1 definitions under topic.actions: declare that an action exists with a target, schema, and overall description.
  • Level 2 invocations under reasoning.actions: declare when and how to invoke it: the description the LLM sees as a tool, slot-fill bindings, and where to write outputs.
actions:
    verify_customer:                            # Level 1: definition
        target: "apex://VerifyCustomer"
        inputs: { email: string }
        outputs: { customer_id: string, verified: string }

reasoning:
    actions:
        verify: @actions.verify_customer        # Level 2: invocation 1
            description: "Verify when user has provided an email"
            with email = ...
            set @variables.customer_id   = @outputs.customer_id
            set @variables.verified_flag = @outputs.verified

        revalidate: @actions.verify_customer    # Level 2: invocation 2
            description: "Re-check verification mid-conversation"
            with email = @variables.customer_email
            available when @variables.suspect_session == True

One Apex class, two distinct planner-visible tools with different gating. This is also why test specs assert against Level 2 names like verify, not Level 1 names like verify_customer.