🤖 Agentic Patterns (TypeScript)
Practical patterns for building agentic and multi-agent systems on top of @cascadeflow/core:
- 🔁 Tool loops (multi-turn tool calling)
- 🧩 Multi-agent orchestration (planner/executor/researcher)
- 🧰 Agent-as-a-tool delegation
- 🧭 Tool cascade routing (pick tool-capable models only when needed)
- 🧱 Message list best practices (including system prompt handling)
📋 Table of Contents
- What cascadeflow Gives You (and What You Still Own)
- Message Format (Tool Loops)
- Tool Loop: Minimal Working Pattern
- Multi-Agent: Two Proven Orchestration Patterns
- Tool Cascade Routing
- System Prompt Handling (Important)
- Example (Copy/Paste Ready)
- Troubleshooting
What cascadeflow Gives You (and What You Still Own)
cascadeflow handles:
- ✅ Model cascading (cheap first, escalate when needed)
- ✅ Tool-capable model filtering (if you pass
tools, non-tool models are skipped) - ✅ Tool-call detection + tool cascade routing heuristics
- ✅ Streaming events with tool call visibility
You still implement:
- 🔁 Tool implementations (your functions, your side effects)
- 🧠 Multi-agent orchestration (agent graph, delegation, memory, state)
Optional (built-in tool loop):
- If you configure
toolExecutoron the agent (or passtoolExecutorper call), cascadeflow can automatically:- Execute tool calls
- Append tool results
- Continue the conversation until the model stops requesting tools (or
maxStepsis reached)
Message Format (Tool Loops)
For multi-turn tool calling, you must persist three kinds of messages:
role: "assistant"withtool_calls(the model asking to run tools)role: "tool"withtool_call_id(your tool results, one per call)- Regular
user/assistanttext messages
@cascadeflow/core uses a universal message type:
type Message = {
role: 'system' | 'user' | 'assistant' | 'tool'
content: string
tool_call_id?: string
tool_calls?: Array<{ id: string; type: 'function'; function: { name: string; arguments: string } }>
}
Tool Loop: Minimal Working Pattern
This is the core pattern:
import { CascadeAgent, ToolExecutor, ToolCall, ToolConfig, type Message, type Tool } from '@cascadeflow/core';
async function runToolLoop(params: {
agent: CascadeAgent;
messages: Message[];
tools: Tool[];
executor: ToolExecutor;
maxTurns?: number;
}) {
const { agent, tools, executor } = params;
const maxTurns = params.maxTurns ?? 6;
const messages: Message[] = [...params.messages];
for (let turn = 0; turn < maxTurns; turn++) {
const result = await agent.run(messages, { tools });
// Persist the assistant message, including tool calls if present.
messages.push({
role: 'assistant',
content: result.content ?? '',
tool_calls: result.toolCalls,
});
if (!result.toolCalls || result.toolCalls.length === 0) {
return { result, messages };
}
for (const raw of result.toolCalls) {
const call = ToolCall.fromOpenAI(raw as any);
const toolResult = await executor.execute(call);
messages.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(
toolResult.success ? toolResult.result : { error: toolResult.error },
),
});
}
}
throw new Error(`Tool loop exceeded maxTurns=${maxTurns}`);
}
Built-in Tool Loop (Fastest DX)
If you prefer not to manage the loop yourself, configure a ToolExecutor:
import { CascadeAgent, ToolConfig, ToolExecutor, type Tool } from '@cascadeflow/core';
const executor = new ToolExecutor([
new ToolConfig({
name: 'calculate',
description: 'Calculator',
parameters: {
type: 'object',
properties: { expression: { type: 'string' } },
required: ['expression'],
},
function: async ({ expression }: { expression: string }) => ({ expression, result: 4 }),
}),
]);
const agent = new CascadeAgent({
models: [
{ name: 'gpt-4o-mini', provider: 'openai', cost: 0.00015, supportsTools: true },
{ name: 'gpt-4o', provider: 'openai', cost: 0.00625, supportsTools: true },
],
toolExecutor: executor,
});
const tools: Tool[] = [
{
type: 'function',
function: {
name: 'calculate',
description: 'Calculator',
parameters: {
type: 'object',
properties: { expression: { type: 'string' } },
required: ['expression'],
},
},
},
];
const result = await agent.run('Compute 2+2 using the calculate tool.', {
tools,
maxSteps: 5,
});
Notes:
- When tool execution is enabled, cascadeflow runs a direct multi-step loop using the best available tool-capable model.
Multi-Agent: Two Proven Orchestration Patterns
Pattern A: “Coordinator Calls Specialists”
plannerAgentproduces a planresearchAgentgathers facts/toolswriterAgentproduces final output
Use multiple CascadeAgent instances (different prompts, different model stacks, different tool sets).
Pattern B: “Agent-as-a-Tool” Delegation
Expose a tool like delegate_to_researcher({ question }) whose implementation calls a second agent:
const delegateTool = new ToolConfig({
name: 'delegate_to_researcher',
description: 'Ask the research agent for focused help',
parameters: {
type: 'object',
properties: { question: { type: 'string' } },
required: ['question'],
},
function: async ({ question }: { question: string }) => {
const res = await researchAgent.run(question);
return { answer: res.content, model: res.modelUsed, cost: res.totalCost };
},
});
This pattern scales well because:
- Your “main” agent stays simple
- Delegation becomes just another tool call
- You can rate-limit, sandbox, or trace delegations separately
Tool Cascade Routing
When you pass tools, cascadeflow can:
- Filter out models that do not support tools
- Use tool-intent + risk/complexity heuristics to decide:
- Direct to a strong tool model (when tool usage is likely)
- Cascade across tool-capable models (when uncertain / high-risk)
This reduces cost while keeping tool correctness high.
System Prompt Handling (Important)
For best portability across providers, cascadeflow normalizes system prompts:
- If you pass
Message[]containingrole: "system"messages, those are extracted and merged. - If you also pass
options.systemPrompt, it is merged in front (explicit first). - Providers then receive:
systemPrompt: one combined stringmessages: system messages removed
Recommendation: Prefer one place to define system prompts.
Example (Copy/Paste Ready)
See the runnable example:
packages/core/examples/nodejs/agentic-multi-agent.ts
It demonstrates:
- A full tool loop with persisted assistant
tool_calls - A multi-agent “delegate to researcher” tool
- Tool execution with
ToolExecutor
Troubleshooting
My tool loop doesn’t continue
- Ensure you’re persisting the assistant message with
tool_calls. - Ensure each tool result message includes the correct
tool_call_id.
I see duplicated system prompts
- Don’t include system prompts both as
options.systemPromptand asrole: "system"messages unless you intend to merge them.
No tool-capable models found
- Ensure at least one model config includes
supportsTools: true. - Ensure you passed
toolstoagent.run(...).