Guides
End-to-end tutorials for common application patterns. Each is built from the primitives covered
elsewhere in these docs — Agent, Runner, Tool, middleware, structured output — combined the
way a real application would. Where a guide needs something the SDK doesn't provide yet (retrieval,
multi-agent handoff), that's called out explicitly rather than glossed over.
Chatbot
Goal
A command-line chatbot that remembers the conversation across turns.
Requirements
A working provider configuration (see Providers).
Steps
- Create one
Agentwith a default (in-memory) session. - Read a line of input, call
runner.run, print the reply, repeat. - Because the agent's session persists across calls, each turn sees the full prior conversation.
Final Result
A loop that holds a coherent, multi-turn conversation without you managing history yourself.
Source Code
import { Agent, Runner } from "aniki-sdk";import * as readline from "node:readline/promises"; async function main(): Promise<void> { const agent = new Agent({ name: "Chatbot", instructions: "You are a friendly, concise chatbot.", model: "gpt-4o-mini", provider: "openai", }); const runner = new Runner(); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); for (;;) { const message = await rl.question("You: "); if (message === "/exit") break; const result = await runner.run(agent, { message }); console.log("Bot:", result.content); } rl.close();} void main();AI Assistant
Goal
An assistant that returns a typed, structured decision instead of free text, with full lifecycle logging.
Requirements
A Zod schema describing the assistant's output shape.
Steps
- Define an
outputschema on theAgent. - Attach a
ConsoleLoggervia aLoggingMiddlewareso every provider round trip is logged. - Subscribe to lifecycle events for anything logging alone doesn't cover (e.g. tool activity).
- Read
result.output, already typed and validated.
Final Result
A typed { action: string; reason: string } value, with structured logs for every request.
Source Code
import { Agent, ConsoleLogger, LoggingMiddleware, Runner } from "aniki-sdk";import { z } from "zod"; const DecisionSchema = z.object({ action: z.enum(["approve", "reject", "escalate"]), reason: z.string(),}); async function main(): Promise<void> { const logger = new ConsoleLogger({ level: "info" }); const agent = new Agent({ name: "Assistant", instructions: "Decide whether to approve, reject, or escalate the request. Explain why.", model: "gpt-4o-mini", provider: "openai", output: DecisionSchema, middleware: [new LoggingMiddleware({ logger })], }); const runner = new Runner(); runner.on("agent:end", (event) => logger.info("run finished", { iterations: event.iterations })); const result = await runner.run(agent, { message: "A refund request for $12,000." }); console.log(result.output.action, "—", result.output.reason);} void main();Code Reviewer
Goal
Given a diff, return a structured list of findings instead of prose.
Requirements
A Zod schema for a single finding, and an array schema for the full response.
Steps
- Model a
Findingschema (file,line,severity,summary). - Give the agent an
output: z.array(FindingSchema)schema. - Run it against a diff and iterate over
result.output.
Final Result
A typed array of findings you can render, filter by severity, or post as PR comments.
Source Code
import { Agent, Runner } from "aniki-sdk";import { z } from "zod"; const FindingSchema = z.object({ file: z.string(), line: z.number(), severity: z.enum(["low", "medium", "high"]), summary: z.string(),}); async function main(): Promise<void> { const agent = new Agent({ name: "CodeReviewer", instructions: "Review the given diff for correctness and security issues. Report findings as JSON.", model: "gpt-4o-mini", provider: "openai", output: z.array(FindingSchema), }); const diff = "--- a/src/auth.ts\n+++ b/src/auth.ts\n@@ ..."; const result = await new Runner().run(agent, { message: diff }); for (const finding of result.output) { console.log(`[${finding.severity}] ${finding.file}:${finding.line} — ${finding.summary}`); }} void main();Streaming Chat
Goal
A chat interface that renders tokens as they arrive instead of waiting for the full reply.
Requirements
None beyond MockProvider for this offline example — swap it for a real provider in production.
Steps
- Call
runner.streaminstead ofrunner.run. - Consume
stream.textStreamand write each token as it arrives. - Remember the constraint from Streaming: this agent must have no tools, since streaming with tool calls isn't supported.
Final Result
Text rendered incrementally, with the same underlying Agent/Runner setup as a non-streaming
chat.
Source Code
import { Agent, Runner } from "aniki-sdk";import { MockProvider } from "aniki-sdk/testing"; async function main(): Promise<void> { const provider = new MockProvider(); provider.enqueueStream(["Sure", ", ", "here's ", "an ", "answer."], "stop"); const agent = new Agent({ name: "Chatbot", instructions: "You are a helpful, concise chatbot.", model: "gpt-4o-mini", provider, // no tools — required for streaming }); const stream = new Runner().stream(agent, { message: "Explain streaming in one sentence." }); for await (const token of stream.textStream) { process.stdout.write(token); }} void main();RAG (Retrieval-Augmented Generation)
Assembled by hand. The SDK has no retriever, no vector store, and no embeddings support —
retrieval is entirely out of scope for aniki-sdk today. This guide shows the integration pattern:
fetch relevant context yourself (from wherever you already store embeddings), and inject it into
the message you send.
Goal
Answer a question using context retrieved from your own document store.
Requirements
A retrieval function of your own — any vector database or search index. It's stubbed here.
Steps
- Retrieve relevant passages for the user's question, outside the SDK entirely.
- Build the message you send to
runner.runby prepending that context. - Everything downstream (the agent, the run, the response) is ordinary
Runnerusage — the SDK has no concept of "retrieved context" as a distinct input.
Final Result
An answer grounded in retrieved context, using only primitives this SDK already provides.
Source Code
import { Agent, Runner } from "aniki-sdk"; // Stand-in for your own retrieval — a real implementation would query a vector// store or search index. The SDK has no built-in equivalent of this function.async function retrieveContext(question: string): Promise<readonly string[]> { return [`Relevant passage about: ${question}`];} async function main(): Promise<void> { const agent = new Agent({ name: "Assistant", instructions: "Answer using only the provided context. Say so if the context is insufficient.", model: "gpt-4o-mini", provider: "openai", }); const question = "What is the SDK's retry policy?"; const passages = await retrieveContext(question); const message = `Context:\n${passages.join("\n")}\n\nQuestion: ${question}`; const result = await new Runner().run(agent, { message }); console.log(result.content);} void main();Multi-Agent
Assembled by hand. There's no handoff primitive in this SDK — no Agent.delegate or
orchestration layer. This guide shows the pattern: drive two separate Agents with one Runner,
passing one's output into the other's input yourself.
Goal
A "researcher" agent gathers facts; a "writer" agent turns them into prose.
Requirements
Two Agent instances with different instructions.
Steps
- Run the researcher agent and take
result.content. - Pass that content as the writer agent's input message.
- This is ordinary application code, not an SDK feature — the SDK has no concept of one agent calling another.
Final Result
A two-stage pipeline where each stage is a plain, independently testable Agent.
Source Code
import { Agent, Runner } from "aniki-sdk"; async function main(): Promise<void> { const researcher = new Agent({ name: "Researcher", instructions: "List three factual bullet points about the given topic. No prose.", model: "gpt-4o-mini", provider: "openai", }); const writer = new Agent({ name: "Writer", instructions: "Turn the given bullet points into a short, engaging paragraph.", model: "gpt-4o-mini", provider: "openai", }); const runner = new Runner(); const research = await runner.run(researcher, { message: "The history of the printing press" }); const article = await runner.run(writer, { message: research.content }); console.log(article.content);} void main();Production Deployment
Goal
Wire the SDK up the way you would for a real deployment: configuration from the environment, structured logging, retry and caching, and metrics fed from lifecycle events.
Requirements
OPENAI_API_KEY (or your provider's equivalent) set in the environment.
Steps
- Call
Aniki.configureonce at startup, sourcing everything fromprocess.env. - Use
ConsoleLogger({ json: true })so logs are structured for ingestion. - Compose
LoggingMiddleware,CacheMiddleware, andRetryMiddlewareon theRunner. - Handle errors by branching on
error.code, not by parsing messages. - Pipe lifecycle events into your metrics system.
Final Result
A Runner configured the way this SDK's own middleware and logging primitives are designed to be
used together in production.
Source Code
import { Aniki, CacheMiddleware, ConsoleLogger, LoggingMiddleware, RetryMiddleware, Runner, isAnikiError,} from "aniki-sdk"; function recordLatency(model: string, durationMs: number): void { // stand-in for your metrics client void model; void durationMs;} function main(): void { const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) throw new Error("Set OPENAI_API_KEY before starting."); Aniki.configure({ provider: "openai", apiKey, timeout: 30_000, }); const logger = new ConsoleLogger({ level: "info", json: true }); const runner = new Runner(undefined, undefined, { middleware: [ new LoggingMiddleware({ logger }), new CacheMiddleware({ ttlMs: 60_000 }), new RetryMiddleware({ maxAttempts: 3, logger }), ], logger, }); runner.on("llm:end", (event) => recordLatency(event.model, event.durationMs)); runner.on("agent:error", (event) => { const error = event.error; if (isAnikiError(error)) { logger.error("run failed", error.toJSON()); } else { logger.error("run failed with a non-SDK error", { message: error.message }); } });} main();NestJS Integration
Goal
Wrap Runner as an injectable NestJS service.
Requirements
An existing NestJS application.
Steps
- Call
Aniki.configureonce, in your module's initializer. - Wrap a
Runnerin an injectable service with one method per use case. - Inject that service into any controller that needs it.
Final Result
A Runner accessible through Nest's dependency injection, configured once at module init.
Source Code
// aniki.module.ts (illustrative — depends on @nestjs/common, not part of this SDK)import { Injectable, Module, OnModuleInit } from "@nestjs/common";import { Agent, Aniki, Runner } from "aniki-sdk"; @Injectable()export class AnikiService implements OnModuleInit { private runner!: Runner; onModuleInit(): void { const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) throw new Error("Set OPENAI_API_KEY before starting."); Aniki.configure({ provider: "openai", apiKey }); this.runner = new Runner(); } async ask(message: string): Promise<string> { const agent = new Agent({ name: "Assistant", instructions: "You are a helpful assistant.", model: "gpt-4o-mini", provider: "openai", }); const result = await this.runner.run(agent, { message }); return result.content; }} @Module({ providers: [AnikiService], exports: [AnikiService] })export class AnikiModule {}Next.js Integration
Goal
A Next.js route handler that runs an agent per request.
Requirements
An existing Next.js application using the App Router.
Note — This must run on the Node.js runtime, not the Edge runtime. The SDK's session ids are generated with
node:crypto'srandomUUID, which isn't available in Edge's restricted runtime.
Steps
- Add
export const runtime = "nodejs";to the route file. - Construct an
AgentandRunnerper request (or reuse a module-levelRunner, which holds no per-agent state). - Return
result.contentin the response.
Final Result
A working POST /api/chat endpoint backed by this SDK.
Source Code
// app/api/chat/route.ts (illustrative — depends on next/server, not part of this SDK)export const runtime = "nodejs"; import { Agent, Runner } from "aniki-sdk"; const runner = new Runner(); export async function POST(request: Request): Promise<Response> { const { message } = (await request.json()) as { message: string }; const agent = new Agent({ name: "Assistant", instructions: "You are a helpful assistant.", model: "gpt-4o-mini", provider: "openai", }); const result = await runner.run(agent, { message }); return Response.json({ content: result.content });}