| name | sisu-framework |
| description | Build AI agents using the Sisu TypeScript framework. Use when creating agents, implementing middleware, defining tools with Zod schemas, setting up LLM adapters (OpenAI, Anthropic, Ollama), or working with agent control flow, tracing, and observability. |
Sisu Framework
Build reliable AI agents in TypeScript with full transparency and control.
When to use this skill
- Creating new AI agents or agent pipelines
- Implementing tool calling with LLM models
- Setting up middleware for control flow, error handling, or tracing
- Working with multiple LLM providers (OpenAI, Anthropic, Ollama)
- Debugging agent behavior with trace viewers
- Building RAG (Retrieval Augmented Generation) systems
Discovery-first rule
Before inventing new Sisu middleware, tools, adapters, or RAG primitives:
- check the maintained package surface first
- prefer composition of existing packages over custom framework code
- only propose new middleware/tools when an existing maintained package truly does not fit
If the sisu CLI is available, use it first:
sisu list middleware
sisu list tools
sisu list adapters
sisu list vector
sisu info mw-rag
sisu info tool-rag
If sisu is not installed, use npx equivalents:
npx @sisu-ai/cli list middleware
npx @sisu-ai/cli list tools
npx @sisu-ai/cli info mw-rag
If the CLI is not available, inspect:
- installed package READMEs in
node_modules/@sisu-ai/*
- official package docs on npm/GitHub
- agent-friendly docs:
http://sisuai.me/agents.html
- the public examples in
https://github.com/finger-gun/sisu/tree/main/examples
Do not invent ad-hoc rag(...), custom vector abstractions, or custom tool registries when maintained Sisu packages already exist.
Quick start
Installation
pnpm add @sisu-ai/core @sisu-ai/adapter-openai \
@sisu-ai/mw-register-tools \
@sisu-ai/mw-conversation-buffer @sisu-ai/mw-trace-viewer \
@sisu-ai/mw-error-boundary zod dotenv
Basic agent template
import "dotenv/config";
import { Agent, createCtx, execute, type Tool } from "@sisu-ai/core";
import { registerTools } from "@sisu-ai/mw-register-tools";
import { inputToMessage, conversationBuffer } from "@sisu-ai/mw-conversation-buffer";
import { errorBoundary } from "@sisu-ai/mw-error-boundary";
import { openAIAdapter } from "@sisu-ai/adapter-openai";
import { traceViewer } from "@sisu-ai/mw-trace-viewer";
import { z } from "zod";
const ctx = createCtx({
model: openAIAdapter({ model: "gpt-5.4" }),
input: "User input here",
systemPrompt: "You are a helpful assistant.",
});
const app = new Agent()
.use(errorBoundary())
.use(traceViewer())
.use(registerTools([...]))
.use(inputToMessage)
.use(conversationBuffer({ window: 8 }))
.use(execute);
await app.handler()(ctx);
Core concepts
Context (Ctx)
Everything flows through a single typed context object. Never create hidden state.
Key properties:
input - User input string
messages - Conversation history
model - LLM adapter
tools - Tool registry
memory - Key-value storage
state - Middleware state
signal - AbortSignal for cancellation
log - Logger
Middleware pattern
Middleware signature: (ctx, next) => Promise<void>
Critical rules:
- Always
await next() unless short-circuiting
- Don't mutate unrelated ctx properties
- Propagate
ctx.signal to all async operations
const myMiddleware = async (ctx, next) => {
ctx.log.info("Starting");
await next();
ctx.log.info("Finished");
};
Tools with Zod validation
import { z } from "zod";
import type { Tool } from "@sisu-ai/core";
const myTool: Tool<{ city: string }> = {
name: "toolName",
description: "Clear description for the LLM",
schema: z.object({
city: z.string().min(1),
}),
handler: async ({ city }, ctx) => {
return { result: "data" };
},
};
Common patterns
Simple chat agent
const app = new Agent()
.use(errorBoundary())
.use(traceViewer())
.use(inputToMessage)
.use(conversationBuffer({ window: 8 }))
.use(async (ctx) => {
const res = await ctx.model.generate(ctx.messages, {
toolChoice: "none",
signal: ctx.signal,
});
if (res?.message) ctx.messages.push(res.message);
});
Tool-calling agent
const app = new Agent()
.use(errorBoundary())
.use(traceViewer())
.use(registerTools([tool1, tool2]))
.use(inputToMessage)
.use(conversationBuffer({ window: 8 }))
.use(execute);
Agent with control flow
See CONTROL_FLOW.md for branching, looping, and parallel execution.
RAG agent
See RAG.md for retrieval augmented generation patterns.
LLM adapters
OpenAI
import { openAIAdapter } from "@sisu-ai/adapter-openai";
const model = openAIAdapter({ model: "gpt-5.4" });
const model = openAIAdapter({
model: "gpt-5.4",
baseUrl: "http://localhost:1234/v1",
});
Anthropic
import { anthropicAdapter } from "@sisu-ai/adapter-anthropic";
const model = anthropicAdapter({ model: "claude-sonnet-4" });
Ollama (local)
import { ollamaAdapter } from "@sisu-ai/adapter-ollama";
const model = ollamaAdapter({ model: "llama3.1" });
Essential middleware
Control flow
import { sequence, branch, switchCase, loopUntil } from '@sisu-ai/mw-control-flow';
.use(sequence([step1, step2, step3]))
.use(branch(
ctx => /weather/.test(ctx.input ?? ''),
toolPipeline,
chatPipeline
))
.use(switchCase(
ctx => ctx.state.intent,
{ 'search': searchFlow, 'chat': chatFlow }
))
Safety and validation
import { guardrails } from '@sisu-ai/mw-guardrails';
import { invariants } from '@sisu-ai/mw-invariants';
.use(errorBoundary())
.use(guardrails({
maxTokens: 2000,
timeout: 30000
}))
.use(invariants())
Observability
import { traceViewer } from '@sisu-ai/mw-trace-viewer';
import { usageTracker } from '@sisu-ai/mw-usage-tracker';
.use(traceViewer())
.use(usageTracker())
Error handling
import {
isSisuError,
getErrorDetails,
ToolExecutionError,
ValidationError,
} from "@sisu-ai/core";
try {
await app.handler()(ctx);
} catch (err) {
if (isSisuError(err)) {
console.error("Code:", err.code);
console.error("Context:", err.context);
} else {
console.error(getErrorDetails(err));
}
}
Use errorBoundary middleware:
.use(errorBoundary(async (err, ctx) => {
ctx.log.error('Error:', getErrorDetails(err));
ctx.messages.push({
role: 'assistant',
content: 'I encountered an error.'
});
}))
Environment variables
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
LOG_LEVEL=info
TRACE_HTML=1
TRACE_STYLE=dark
Best practices
- Always use errorBoundary as first middleware
- Enable traceViewer during development (second middleware)
- Validate tool inputs with Zod schemas
- Use conversationBuffer to prevent context overflow
- Propagate ctx.signal to all async operations
- Keep middleware small - one responsibility each
- Use control flow combinators over complex conditionals
- Never log secrets - use createRedactingLogger
- Set guardrails in production (maxTokens, timeout)
- Test with AbortSignal for cancellation
Common mistakes
❌ Not calling next()
const bad = async (ctx, next) => {
ctx.state.value = 1;
};
❌ Not propagating signal
const res = await ctx.model.generate(ctx.messages, {});
const res = await ctx.model.generate(ctx.messages, {
signal: ctx.signal,
});
❌ Mutating other middleware state
ctx.state.otherMiddlewareData = modified;
ctx.state.myFeature = { myData: value };
❌ Using console.log
console.log("debug info");
ctx.log.info("debug info");
Reference documentation
For detailed documentation, see:
External resources