10 KiB
Architecture Patterns
Architecture mechanics reference. Start with
references/patterns-by-requirement.mdto choose patterns by scenario, then use this file for implementation details.
All architecture patterns below work for both
AgentforceServiceAgentandAgentforceEmployeeAgent. The only difference is that employee agents cannot use@utils.escalateorconnection messaging:— replace escalation with a@utils.transitionto a help subagent or an action that creates a case/ticket.
When to Use Each Pattern
These are composable mechanics, not stages or a hierarchy. Start with the smallest architecture that satisfies the requirements, then add each independently required gate or post-action behavior.
| Pattern | Use When |
|---|---|
| Single Scope | Default. One domain start_agent block, zero subagent blocks, and one compatible set of instructions, actions, authority, and escalation behavior |
| Router-First Architecture | Multiple genuine domains require different objectives, instructions, actions, authority, or escalation behavior |
| Verification Gate | Sensitive data, payments, or PII require identity verification first |
| Post-Action Re-resolution | Trusted action output must drive a named follow-up instruction, gate, or action input |
Smallest Architecture First
Default to one domain start_agent block and zero subagent blocks. Do not
create an agent_router that only transitions to that one domain. Add a
subagent only when the boundary changes at least one of:
objective | instructions | available actions | authority | escalation behavior
Use start_agent agent_router only when multiple genuine domains require
current-intent classification. Verification gates and post-action
re-resolution are independent mechanics that can be combined with either a
single-subagent or router-first design. Linear sequencing is workflow-local
external ordering, not a default conversation architecture.
Router-First Architecture Mechanics
A central agent_router routes to specialized subagents. Transition paths should be use-case-driven: subagent -> subagent when workflow continues naturally, and subagent -> router when the conversation needs reclassification.
start_agent agent_router:
description: "Route user requests to the appropriate subagent"
reasoning:
instructions: |
You are a router only. Do NOT answer questions directly.
Always use a transition action to route immediately.
actions:
to_orders: @utils.transition to @subagent.order_support
description: "Order questions"
to_returns: @utils.transition to @subagent.return_support
description: "Return or refund requests"
to_general: @utils.transition to @subagent.general_support
description: "General questions"
subagent order_support:
description: "Handle order inquiries"
reasoning:
instructions: ->
| Help the customer with their order.
actions:
lookup: @actions.get_order
description: "Look up order"
to_returns: @utils.transition to @subagent.return_support
description: "Continue to return workflow when needed"
Routing lives in
start_agent-- put classification transitions instart_agent agent_router:. Do NOT create a separate routing-only subagent (e.g.main_menu,central_hub) -- that duplicates the router, adds an extra LLM hop (~3-5s latency), and confuses the platform. A transition back to router is optional and should only be added when the use case requires reclassification.
instructions: |in a router is probabilistic. The LLM may respond conversationally instead of emitting a transition. This is appropriate for unstructured current-intent classification. Useinstructions: ->only when a named deterministic cause, such as verified authorization, selects the transition; do not encode ordinary dialogue stages as state.
Verification Gate
Users must pass through identity verification before accessing protected subagents. Use when handling sensitive data, payments, or PII. Uses deterministic routing (instructions: ->) so the gate cannot be bypassed by LLM conversational drift.
variables:
is_verified: mutable boolean = False
start_agent agent_router:
description: "Route through identity verification"
reasoning:
instructions: ->
if @variables.is_verified == False:
transition to @subagent.identity_verification
| Select the best tool to call based on conversation history and the user's current intent.
actions:
to_account: @utils.transition to @subagent.account_mgmt
description: "Account management"
available when @variables.is_verified == True
to_refund: @utils.transition to @subagent.refund_processor
description: "Process a refund"
available when @variables.is_verified == True
subagent identity_verification:
description: "Verify customer identity"
reasoning:
instructions: ->
if @variables.is_verified == True:
| Identity verified. Ask which protected task to continue.
else:
| Ask for the minimum information needed to verify identity.
actions:
verify_email: @actions.verify_identity
description: "Verify customer email"
set @variables.is_verified = @outputs.verified
to_account: @utils.transition to @subagent.account_mgmt
description: "Account management"
available when @variables.is_verified == True
escalate_now: @utils.escalate
description: "Transfer to human"
is_verified has one trusted writer (verify_identity) and named consumers
(the authorization transitions). Define its expiry, reset, correction, and
cancellation behavior. No mutable variable is needed for the user's ordinary
follow-up context.
Post-Action Re-resolution
The subagent re-resolves after an action completes. Persist action output only
when a named later runtime consumer needs the exact value. Place the
post-action check at the top of instructions: -> so it applies on
re-resolution:
variables:
risk_score: mutable number = -1
subagent retention_review:
description: "Use a returned risk score to select retention guidance"
reasoning:
instructions: ->
# POST-ACTION CHECK (at top on re-resolution)
if @variables.risk_score >= 80:
| The returned risk score is {!@variables.risk_score}.
| Offer the approved retention options.
else if @variables.risk_score >= 0:
| The returned risk score is {!@variables.risk_score}.
| Follow the standard retention policy.
else:
| Explain that a risk assessment is needed before making an offer.
actions:
assess_risk: @actions.load_risk_score
with customer_id = ...
available when @variables.risk_score < 0
set @variables.risk_score = @outputs.score
Here risk_score is trusted action output consumed by prompt branches and the
repeat-prevention gate. Define when a new request resets or refreshes it.
Migrating to Multi-Domain Router-First Architecture
Refactor a flat agent only after identifying multiple genuine domains whose boundaries change objective, instructions, actions, authority, or escalation behavior:
- Prove the boundaries — group related intents unless behavior changes
- Move instructions and actions from the monolithic subagent into specialized subagents. Each subagent needs BOTH its Level 1 action definitions (under
subagent > actions) AND Level 2 action invocations (undersubagent > reasoning > actions). - Create
start_agent agent_router:with transition actions pointing to each specialized subagent - Add transitions based on workflow needs — subagent -> subagent for continuous workflows, or subagent -> router for reclassification turns
- Re-preview immediately — verify subagent routing works before making further changes
Common migration mistakes:
- Creating a separate
main_menusubagent instead of usingstart_agent agent_router:as the hub — adds an unnecessary LLM hop - Leaving action definitions in
start_agentinstead of moving them to specialized subagents — all actions visible in all subagents, confusing the planner - Routing everything back to router by default, even when a direct subagent-to-subagent transition better matches the workflow
- If trace shows
topic: "DefaultTopic", check that subagent descriptions contain keywords matching test utterances
Multi-Intent Handling
When a user sends requests for multiple domains in one message, route one domain and preserve the remaining request in conversation history:
start_agent agent_router:
description: "Route one domain request at a time"
reasoning:
instructions: |
You are a router only. Do NOT answer questions directly.
If the user asks about multiple domains in one message, route to
the first domain. After that task is complete, remind the user
about the other request from conversation history.
No queue variable is needed while the original turn survives in conversation history. Persist a queue only if exact external ordering must outlive the configured history window, and then define its writer, consumer, reset, expiry, correction, and cancellation behavior.
Handling Incomplete Action Inputs
- Use
with param = ...(slot-fill) for inputs the LLM should extract from conversation - Add instructions that tell the LLM to invoke the action with whatever data is available
- Anti-pattern: Making the LLM ask for ALL inputs before invoking
Controlling Opportunistic Action Chains
In long action chains (A->B->C->D), the LLM may invoke downstream actions as soon as prerequisites are met. To control this:
- Add explicit gating in instructions: "Only invoke generate_resolution if the user explicitly asks"
- Use
available whenguards when successful external output, authorization, confirmation, or required ordering supplies a machine-checkable precondition - Distinguish between "analyze only" and "full resolution" workflows in instructions
Anti-pattern: Leaving action chains ungated so the LLM runs the entire pipeline for every query.