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";
// Create context
const ctx = createCtx({
model: openAIAdapter({ model: "gpt-5.4" }),
input: "User input here",
systemPrompt: "You are a helpful assistant.",
});
// Build pipeline
const app = new Agent()
.use(errorBoundary())
.use(traceViewer())
.use(registerTools([...])) // Add tools here
.use(inputToMessage)
.use(conversationBuffer({ window: 8 }))
.use(execute);
// Run
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 stringmessages- Conversation historymodel- LLM adaptertools- Tool registrymemory- Key-value storagestate- Middleware statesignal- AbortSignal for cancellationlog- 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.signalto all async operations
const myMiddleware = async (ctx, next) => {
// Before logic
ctx.log.info("Starting");
await next(); // MUST call this
// After logic
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) => {
// ctx is sandboxed - has memory, signal, log, model, deps
// Cannot access: tools, messages, state, input, stream
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";
// Standard OpenAI
const model = openAIAdapter({ model: "gpt-5.4" });
// Compatible APIs (LM Studio, vLLM, OpenRouter)
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';
// Sequential steps
.use(sequence([step1, step2, step3]))
// Conditional branching
.use(branch(
ctx => /weather/.test(ctx.input ?? ''),
toolPipeline,
chatPipeline
))
// Route by intent
.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()) // Always first
.use(guardrails({
maxTokens: 2000,
timeout: 30000
}))
.use(invariants()) // Development mode validation
Observability
import { traceViewer } from '@sisu-ai/mw-trace-viewer';
import { usageTracker } from '@sisu-ai/mw-usage-tracker';
.use(traceViewer()) // Generates HTML traces
.use(usageTracker()) // Tracks costs
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
# Required for examples
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# Optional
LOG_LEVEL=info # debug|info|warn|error
TRACE_HTML=1 # Generate HTML traces
TRACE_STYLE=dark # light|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()
// WRONG
const bad = async (ctx, next) => {
ctx.state.value = 1;
// Missing await next()!
};
❌ Not propagating signal
// WRONG
const res = await ctx.model.generate(ctx.messages, {});
// CORRECT
const res = await ctx.model.generate(ctx.messages, {
signal: ctx.signal,
});
❌ Mutating other middleware state
// WRONG - don't touch other middleware state
ctx.state.otherMiddlewareData = modified;
// CORRECT - namespace your state
ctx.state.myFeature = { myData: value };
❌ Using console.log
// WRONG
console.log("debug info");
// CORRECT
ctx.log.info("debug info");
Reference documentation
For detailed documentation, see:
- CONTROL_FLOW.md - Branching, loops, parallel, graphs
- RAG.md - Retrieval augmented generation
- TOOLS.md - Built-in tools (web, cloud, dev)
- STREAMING.md - Token streaming patterns
- SISU_SKILLS.md - Filesystem-based skills support
- EXAMPLES.md - 25+ working examples from the repo
External resources
- Main repo: github.com/finger-gun/sisu
- Core docs: packages/core
- Examples: examples/
- Middleware list: packages/middleware
- Tools list: packages/tools
微信扫一扫