NOETIC
Framework
Examples

Branching Agent

A support ticket router using conditional, runCode, callModel, and loop to classify and handle tickets by keyword.

Overview

A support ticket router that classifies incoming tickets by keyword and routes them to different handlers. Demonstrates how conditional selects a step at runtime based on input, combined with loop to process the ticket.

Primitives used: conditional + runCode + callModel + loop

How It Works

  1. Input arrives as a support ticket string
  2. conditional inspects the input for keyword matches:
    • Billing keywords (invoice, refund, etc.) -> deterministic runCode response
    • Technical keywords (error, bug, etc.) -> callModel for reasoning
    • Everything else -> runCode fallback
  3. The conditional is wrapped in loop({ until: until.maxSteps(1) }) to show conditional routing inside a loop body

Code

import { conditional } from '@noetic-tools/core';
import type { ContextData } from '@noetic-tools/core';
import { loop } from '@noetic-tools/core';
import { callModel, runCode } from '@noetic-tools/core';
import { until } from '@noetic-tools/core';

const BILLING_KEYWORDS = ['invoice', 'charge', 'refund', 'billing', 'payment', 'subscription'];
const TECHNICAL_KEYWORDS = ['error', 'bug', 'crash', 'broken', 'fix', 'debug', 'logs'];

function containsKeyword(input: string, keywords: readonly string[]): boolean {
  const lower = input.toLowerCase();
  return keywords.some((kw) => lower.includes(kw));
}

const billingHandler = runCode<ContextData, string, string>({
  id: 'billing-handler',
  execute: async (input) => {
    return [
      'Billing Support Response:',
      `Your ticket: "${input}"`,
      '',
      'Please visit your account dashboard at /billing to review charges.',
      'For refunds, allow 5-7 business days after approval.',
      'A billing specialist will follow up within 24 hours.',
    ].join('\n');
  },
});

const technicalHandler = callModel<ContextData, string, string>({
  id: 'technical-handler',
  model: 'openai/gpt-4o',
  instructions: 'You are a technical support specialist. Analyze the issue and provide troubleshooting steps.',
});

const fallbackHandler = runCode<ContextData, string, string>({
  id: 'fallback-handler',
  execute: async (input) => {
    return `General Support: We received your ticket: "${input}". Expected response: 48 hours.`;
  },
});

export function buildBranchingAgent() {
  const router = conditional<ContextData, string, string>({
    id: 'ticket-router',
    route: (input) => {
      if (containsKeyword(input, BILLING_KEYWORDS)) return billingHandler;
      if (containsKeyword(input, TECHNICAL_KEYWORDS)) return technicalHandler;
      return fallbackHandler;
    },
  });

  return loop({
    id: 'ticket-processing-loop',
    steps: [router],
    until: until.maxSteps(1),
  });
}

Key Concepts

Conditional as Router

conditional takes a route function that receives the input and returns a step (or null to skip). This makes it ideal for content-based routing -- inspecting the input to decide which handler runs.

Mixing Deterministic and callModel Steps

Not every path needs an LLM call. Billing tickets get a deterministic runCode response (fast, cheap, predictable), while technical tickets use callModel for reasoning. This is a practical pattern for production agents where some paths are well-understood.

Conditional Inside Loop

Wrapping conditional in loop is how you build agents that process multiple items or iterate on a decision. Here maxSteps(1) processes a single ticket, but increasing it (with prepareNext) enables batch processing.

On this page