# Dynamic Routing Route workflows conditionally using `@Guard` decorators and `priority` to control which transition fires when multiple transitions share the same source state. ## Basic Guard ```typescript @Transition({ from: 'check', to: 'high', priority: 10 }) @Guard('isHigh') async routeHigh(state: MyState): Promise { return state; } @Transition({ from: 'check', to: 'low' }) async routeLow(state: MyState): Promise { return state; } // Fallback — no guard isHigh(state: MyState): boolean { return state.value > 100; } ``` **How it works:** 1. Transitions with higher `priority` are checked first 2. The `@Guard` references a method that returns a boolean 3. First transition whose guard returns `true` fires 4. A transition without `@Guard` acts as the fallback ## Multi-Level Routing Chain routing decisions with cascading forks: ```typescript import { z } from 'zod'; import { BaseWorkflow, Guard, Transition, Workflow } from '@loopstack/common'; import type { LoopstackContext } from '@loopstack/common'; import { MessageDocument } from '@loopstack/common'; interface RoutingState { value?: number; } @Workflow({ schema: z.object({ value: z.number().default(150) }).strict(), }) export class DynamicRoutingExampleWorkflow extends BaseWorkflow<{ value: number }, RoutingState> { @Transition({ to: 'prepared' }) async createMockData(state: RoutingState, ctx: LoopstackContext): Promise { const args = ctx.args as { value: number }; await this.documentStore.save(MessageDocument, { role: 'assistant', content: `Analysing value = ${args.value}`, }); return { ...state, value: args.value }; } // First fork: value > 100? @Transition({ from: 'prepared', to: 'placeA', priority: 10 }) @Guard('isAbove100') async routeToPlaceA(state: RoutingState): Promise { return state; } @Transition({ from: 'prepared', to: 'placeB' }) async routeToPlaceB(state: RoutingState): Promise { return state; } // Fallback: value <= 100 isAbove100(state: RoutingState): boolean { return (state.value ?? 0) > 100; } // Second fork: value > 200? @Transition({ from: 'placeA', to: 'placeC', priority: 10 }) @Guard('isAbove200') async routeToPlaceC(state: RoutingState): Promise { return state; } @Transition({ from: 'placeA', to: 'placeD' }) async routeToPlaceD(state: RoutingState): Promise { return state; } // Fallback: 100 < value <= 200 isAbove200(state: RoutingState): boolean { return (state.value ?? 0) > 200; } // Terminal transitions @Transition({ from: 'placeB', to: 'end' }) async showMessagePlaceB(state: RoutingState): Promise { await this.documentStore.save(MessageDocument, { role: 'assistant', content: 'Value is less or equal 100' }); return {}; } @Transition({ from: 'placeC', to: 'end' }) async showMessagePlaceC(state: RoutingState): Promise { await this.documentStore.save(MessageDocument, { role: 'assistant', content: 'Value is greater than 200' }); return {}; } @Transition({ from: 'placeD', to: 'end' }) async showMessagePlaceD(state: RoutingState): Promise { await this.documentStore.save(MessageDocument, { role: 'assistant', content: 'Value is less or equal 200, but greater than 100', }); return {}; } } ``` ## Routing Flow ``` prepared → [value > 100?] ├─ yes → placeA → [value > 200?] │ ├─ yes → placeC (done) │ └─ no → placeD (done) └─ no → placeB (done) ``` ## Common Patterns ### Tool Call Routing Route based on LLM response (see [AI Tool Calling](/features/ai-tool-calling)): ```typescript @Transition({ from: 'prompt_executed', to: 'awaiting_tools', priority: 10 }) @Guard('hasToolCalls') async executeToolCalls(state: MyState): Promise { ... } @Transition({ from: 'prompt_executed', to: 'end' }) async respond(state: MyState): Promise { ... } // Fallback: no tool calls hasToolCalls(state: MyState): boolean { return state.llmResult?.message.stopReason === 'tool_use'; } ``` ### Error-Based Routing Route based on a tool's error response: ```typescript @Transition({ from: 'fetched', to: 'auth_needed', priority: 10 }) @Guard('needsAuth') async startAuth(state: MyState): Promise { ... } @Transition({ from: 'fetched', to: 'end' }) async displayResults(state: MyState): Promise { ... } needsAuth(state: MyState): boolean { return state.fetchResult?.error === 'unauthorized'; } ``` ## Guard Method Rules - Guard methods must return a **boolean** (or truthy/falsy value) - They receive `state` as their first parameter - They should be **synchronous** — no async guards - Use descriptive names: `hasToolCalls`, `isAbove100`, `needsAuth` ## Registry References - [dynamic-routing-example-workflow](https://loopstack.ai/registry/loopstack-dynamic-routing-example-workflow) — Multi-level guard-based routing with cascading forks - [tool-call-example-workflow](https://loopstack.ai/registry/loopstack-tool-call-example-workflow) — Guard-based routing for LLM tool call detection