@octavus/docs 2.15.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,7 @@ var docs_default = [
14
14
  section: "getting-started",
15
15
  title: "Quick Start",
16
16
  description: "Get your first Octavus agent running in minutes.",
17
- content: "\n# Quick Start\n\nThis guide will walk you through integrating Octavus into your application in under 10 minutes.\n\n## Prerequisites\n\n- Node.js 18+\n- An Octavus account with API key\n- A Next.js application (or any Node.js backend)\n\n## Test Your Agent First\n\nBefore integrating with SDKs, use **Agent Preview** to test your agent directly in the platform:\n\n1. Open your agent in the platform at `octavus.ai/platform/agents/[agentId]`\n2. Click the **Preview** tab\n3. Configure session inputs and tool mock responses\n4. Start a conversation to test agent behavior\n\nAgent Preview supports all trigger types, file attachments, tool mocking, and real-time streaming. This is the fastest way to iterate on your agent logic before writing any integration code.\n\n## Installation\n\nInstall the Octavus SDKs in your project:\n\n```bash\n# Server SDK for backend\nnpm install @octavus/server-sdk\n\n# React bindings for frontend\nnpm install @octavus/react\n```\n\n## Backend Setup\n\n### 1. Initialize the Client\n\nCreate an Octavus client instance in your backend:\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n### 2. Create a Session Endpoint\n\nCreate an API endpoint that creates sessions and returns the session ID:\n\n```typescript\n// app/api/chat/create/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\n// Agent ID - get from platform or CLI (see below)\nconst SUPPORT_AGENT_ID = process.env.OCTAVUS_SUPPORT_AGENT_ID!;\n\nexport async function POST(request: Request) {\n const { input } = await request.json();\n\n // Create a new session using the agent ID\n const sessionId = await octavus.agentSessions.create(SUPPORT_AGENT_ID, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n### Getting Your Agent ID\n\nThere are two ways to create and manage agents:\n\n**Option 1: Platform UI (Recommended for getting started)**\n\n1. Go to [octavus.ai](https://octavus.ai) and create an agent in the web editor\n2. Copy the agent ID from the URL (e.g., `octavus.ai/platform/agents/clxyz123abc456`)\n3. Add it to your `.env.local`: `OCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456`\n\n**Option 2: Local Development with CLI**\n\nFor version-controlled agent definitions, use the [Octavus CLI](/docs/server-sdk/cli):\n\n```bash\nnpm install --save-dev @octavus/cli\noctavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n```\n\nThe CLI approach is better for teams and CI/CD pipelines where you want agent definitions in your repository.\n\n### 3. Create a Trigger Endpoint\n\nCreate an endpoint that handles triggers and streams responses:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Define tool handlers that run on your server\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n return {\n name: 'Demo User',\n email: 'demo@example.com',\n plan: 'pro',\n };\n },\n 'create-support-ticket': async (args) => {\n // Create ticket in your system\n return {\n ticketId: 'TICKET-123',\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // Execute the request and convert to SSE stream\n const events = session.execute(payload, { signal: request.signal });\n\n // Return as streaming response\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n## Frontend Setup\n\n### 1. Create a Chat Component\n\nUse the `useOctavusChat` hook with the HTTP transport:\n\n```tsx\n// components/chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create a stable transport instance\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send } = useOctavusChat({ transport });\n\n const isStreaming = status === 'streaming';\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || isStreaming) return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Add user message and trigger in one call\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={isStreaming}\n />\n <button\n type=\"submit\"\n disabled={isStreaming}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50\"\n >\n Send\n </button>\n </div>\n </form>\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>\n <div\n className={`p-3 rounded-lg max-w-md ${isUser ? 'bg-blue-500 text-white' : 'bg-gray-100'}`}\n >\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n\n {/* Streaming indicator */}\n {message.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n </div>\n );\n}\n```\n\n### 2. Create Session and Render Chat\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n async function createSession() {\n const response = await fetch('/api/chat/create', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n input: {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n },\n }),\n });\n const { sessionId } = await response.json();\n setSessionId(sessionId);\n }\n\n createSession();\n }, []);\n\n if (!sessionId) {\n return <div>Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Environment Variables\n\nAdd these to your `.env.local`:\n\n```bash\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key-here\n```\n\n## What's Next?\n\nNow that you have a basic integration working:\n\n- [Learn about the protocol](/docs/protocol/overview) to define custom agent behavior\n- [Explore the Server SDK](/docs/server-sdk/overview) for advanced backend features\n- [Build rich UIs](/docs/client-sdk/overview) with the Client SDK\n- [Handle tools on the client](/docs/client-sdk/client-tools) for interactive UIs and browser APIs\n",
17
+ content: "\n# Quick Start\n\nThis guide will walk you through integrating Octavus into your application in under 10 minutes.\n\n## Prerequisites\n\n- Node.js 18+\n- An Octavus account with API key\n- A Next.js application (or any Node.js backend)\n\n## Test Your Agent First\n\nBefore integrating with SDKs, use **Agent Preview** to test your agent directly in the platform:\n\n1. Open your agent in the platform at `octavus.ai/platform/agents/[agentId]`\n2. Click the **Preview** tab\n3. Configure session inputs and tool mock responses\n4. Start a conversation to test agent behavior\n\nAgent Preview supports all trigger types, file attachments, tool mocking, and real-time streaming. This is the fastest way to iterate on your agent logic before writing any integration code.\n\n## Installation\n\nInstall the Octavus SDKs in your project:\n\n```bash\n# Server SDK for backend\nnpm install @octavus/server-sdk\n\n# React bindings for frontend\nnpm install @octavus/react\n```\n\n## Backend Setup\n\n### 1. Initialize the Client\n\nCreate an Octavus client instance in your backend:\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n### 2. Create a Session Endpoint\n\nCreate an API endpoint that creates sessions and returns the session ID:\n\n```typescript\n// app/api/chat/create/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\n// Agent ID - get from platform or CLI (see below)\nconst SUPPORT_AGENT_ID = process.env.OCTAVUS_SUPPORT_AGENT_ID!;\n\nexport async function POST(request: Request) {\n const { input } = await request.json();\n\n // Create a new session using the agent ID\n const sessionId = await octavus.agentSessions.create(SUPPORT_AGENT_ID, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n### Getting Your Agent ID\n\nThere are two ways to create and manage agents:\n\n**Option 1: Platform UI (Recommended for getting started)**\n\n1. Go to [octavus.ai](https://octavus.ai) and create an agent in the web editor\n2. Copy the agent ID from the URL (e.g., `octavus.ai/platform/agents/clxyz123abc456`)\n3. Add it to your `.env.local`: `OCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456`\n\n**Option 2: Local Development with CLI**\n\nFor version-controlled agent definitions, use the [Octavus CLI](/docs/server-sdk/cli):\n\n```bash\nnpm install --save-dev @octavus/cli\noctavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n```\n\nThe CLI approach is better for teams and CI/CD pipelines where you want agent definitions in your repository.\n\n### 3. Create a Trigger Endpoint\n\nCreate an endpoint that handles triggers and streams responses:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Define tool handlers that run on your server\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n return {\n name: 'Demo User',\n email: 'demo@example.com',\n plan: 'pro',\n };\n },\n 'create-support-ticket': async (args) => {\n // Create ticket in your system\n return {\n ticketId: 'TICKET-123',\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // Execute the request and convert to SSE stream\n const events = session.execute(payload, { signal: request.signal });\n\n // Return as streaming response\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n });\n}\n```\n\n## Frontend Setup\n\n### 1. Create a Chat Component\n\nUse the `useOctavusChat` hook with the HTTP transport:\n\n```tsx\n// components/chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create a stable transport instance\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send } = useOctavusChat({ transport });\n\n const isStreaming = status === 'streaming';\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || isStreaming) return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Add user message and trigger in one call\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={isStreaming}\n />\n <button\n type=\"submit\"\n disabled={isStreaming}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50\"\n >\n Send\n </button>\n </div>\n </form>\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>\n <div\n className={`p-3 rounded-lg max-w-md ${isUser ? 'bg-blue-500 text-white' : 'bg-gray-100'}`}\n >\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n\n {/* Streaming indicator */}\n {message.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n </div>\n );\n}\n```\n\n### 2. Create Session and Render Chat\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n async function createSession() {\n const response = await fetch('/api/chat/create', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n input: {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n },\n }),\n });\n const { sessionId } = await response.json();\n setSessionId(sessionId);\n }\n\n createSession();\n }, []);\n\n if (!sessionId) {\n return <div>Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Environment Variables\n\nAdd these to your `.env.local`:\n\n```bash\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key-here\n```\n\n## What's Next?\n\nNow that you have a basic integration working:\n\n- [Learn about the protocol](/docs/protocol/overview) to define custom agent behavior\n- [Explore the Server SDK](/docs/server-sdk/overview) for advanced backend features\n- [Build rich UIs](/docs/client-sdk/overview) with the Client SDK\n- [Handle tools on the client](/docs/client-sdk/client-tools) for interactive UIs and browser APIs\n",
18
18
  excerpt: "Quick Start This guide will walk you through integrating Octavus into your application in under 10 minutes. Prerequisites - Node.js 18+ - An Octavus account with API key - A Next.js application (or...",
19
19
  order: 2
20
20
  },
@@ -23,7 +23,7 @@ var docs_default = [
23
23
  section: "server-sdk",
24
24
  title: "Overview",
25
25
  description: "Introduction to the Octavus Server SDK for backend integration.",
26
- content: "\n# Server SDK Overview\n\nThe `@octavus/server-sdk` package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation loop.\n\n**Current version:** `2.14.0`\n\n## Installation\n\n```bash\nnpm install @octavus/server-sdk\n```\n\nFor agent management (sync, validate), install the CLI as a dev dependency:\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Basic Usage\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: 'https://octavus.ai',\n apiKey: 'your-api-key',\n});\n```\n\n## Key Features\n\n### Agent Management\n\nAgent definitions are managed via the CLI. See the [CLI documentation](/docs/server-sdk/cli) for details.\n\n```bash\n# Sync agent from local files\noctavus sync ./agents/support-chat\n\n# Output: Created: support-chat\n# Agent ID: clxyz123abc456\n```\n\n### Session Management\n\nCreate and manage agent sessions using the agent ID:\n\n```typescript\n// Create a new session (use agent ID from CLI sync)\nconst sessionId = await client.agentSessions.create('clxyz123abc456', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n});\n\n// Get UI-ready session messages (for session restore)\nconst session = await client.agentSessions.getMessages(sessionId);\n```\n\n### Tool Handlers\n\nTools run on your server with your data:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Access your database, APIs, etc.\n return await db.users.findById(args.userId);\n },\n },\n});\n```\n\n### Streaming\n\nAll responses stream in real-time:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of events\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n### Workers\n\nExecute worker agents for task-based processing:\n\n```typescript\n// Non-streaming: get the output directly\nconst { output } = await client.workers.generate(agentId, {\n TOPIC: 'AI safety',\n});\n\n// Streaming: observe events in real-time\nfor await (const event of client.workers.execute(agentId, input)) {\n // Handle stream events\n}\n```\n\n## API Reference\n\n### OctavusClient\n\nThe main entry point for interacting with Octavus.\n\n```typescript\ninterface OctavusClientConfig {\n baseUrl: string; // Octavus API URL\n apiKey?: string; // Your API key\n traceModelRequests?: boolean; // Enable model request tracing (default: false)\n}\n\nclass OctavusClient {\n readonly agents: AgentsApi;\n readonly agentSessions: AgentSessionsApi;\n readonly workers: WorkersApi;\n readonly files: FilesApi;\n\n constructor(config: OctavusClientConfig);\n}\n```\n\n### AgentSessionsApi\n\nManages agent sessions.\n\n```typescript\nclass AgentSessionsApi {\n // Create a new session\n async create(agentId: string, input?: Record<string, unknown>): Promise<string>;\n\n // Get full session state (for debugging/internal use)\n async get(sessionId: string): Promise<SessionState>;\n\n // Get UI-ready messages (for client display)\n async getMessages(sessionId: string): Promise<UISessionState>;\n\n // Attach to a session for triggering\n attach(sessionId: string, options?: SessionAttachOptions): AgentSession;\n}\n\n// Full session state (internal format)\ninterface SessionState {\n id: string;\n agentId: string;\n input: Record<string, unknown>;\n variables: Record<string, unknown>;\n resources: Record<string, unknown>;\n messages: ChatMessage[]; // Internal message format\n createdAt: string;\n updatedAt: string;\n}\n\n// UI-ready session state\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready messages for frontend\n}\n```\n\n### AgentSession\n\nHandles request execution and streaming for a specific session.\n\n```typescript\nclass AgentSession {\n // Execute a request and stream parsed events\n execute(request: SessionRequest, options?: TriggerOptions): AsyncGenerator<StreamEvent>;\n\n // Get the session ID\n getSessionId(): string;\n}\n\ntype SessionRequest = TriggerRequest | ContinueRequest;\n\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n}\n\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n\n// Helper to convert events to SSE stream\nfunction toSSEStream(events: AsyncIterable<StreamEvent>): ReadableStream<Uint8Array>;\n```\n\n### FilesApi\n\nHandles file uploads for sessions.\n\n```typescript\nclass FilesApi {\n // Get presigned URLs for file uploads\n async getUploadUrls(sessionId: string, files: FileUploadRequest[]): Promise<UploadUrlsResponse>;\n}\n\ninterface FileUploadRequest {\n filename: string;\n mediaType: string;\n size: number;\n}\n\ninterface UploadUrlsResponse {\n files: {\n id: string; // File ID for references\n uploadUrl: string; // PUT to this URL\n downloadUrl: string; // GET URL after upload\n }[];\n}\n```\n\nThe client uploads files directly to S3 using the presigned upload URL. See [File Uploads](/docs/client-sdk/file-uploads) for the full integration pattern.\n\n## Next Steps\n\n- [Sessions](/docs/server-sdk/sessions) \u2014 Deep dive into session management\n- [Tools](/docs/server-sdk/tools) \u2014 Implementing tool handlers\n- [Streaming](/docs/server-sdk/streaming) \u2014 Understanding stream events\n- [Workers](/docs/server-sdk/workers) \u2014 Executing worker agents\n- [Debugging](/docs/server-sdk/debugging) \u2014 Model request tracing and debugging\n",
26
+ content: "\n# Server SDK Overview\n\nThe `@octavus/server-sdk` package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation loop.\n\n**Current version:** `2.15.0`\n\n## Installation\n\n```bash\nnpm install @octavus/server-sdk\n```\n\nFor agent management (sync, validate), install the CLI as a dev dependency:\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Basic Usage\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: 'https://octavus.ai',\n apiKey: 'your-api-key',\n});\n```\n\n## Key Features\n\n### Agent Management\n\nAgent definitions are managed via the CLI. See the [CLI documentation](/docs/server-sdk/cli) for details.\n\n```bash\n# Sync agent from local files\noctavus sync ./agents/support-chat\n\n# Output: Created: support-chat\n# Agent ID: clxyz123abc456\n```\n\n### Session Management\n\nCreate and manage agent sessions using the agent ID:\n\n```typescript\n// Create a new session (use agent ID from CLI sync)\nconst sessionId = await client.agentSessions.create('clxyz123abc456', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n});\n\n// Get UI-ready session messages (for session restore)\nconst session = await client.agentSessions.getMessages(sessionId);\n```\n\n### Tool Handlers\n\nTools run on your server with your data:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Access your database, APIs, etc.\n return await db.users.findById(args.userId);\n },\n },\n});\n```\n\n### Streaming\n\nAll responses stream in real-time:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of events\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n### Workers\n\nExecute worker agents for task-based processing:\n\n```typescript\n// Non-streaming: get the output directly\nconst { output } = await client.workers.generate(agentId, {\n TOPIC: 'AI safety',\n});\n\n// Streaming: observe events in real-time\nfor await (const event of client.workers.execute(agentId, input)) {\n // Handle stream events\n}\n```\n\n## API Reference\n\n### OctavusClient\n\nThe main entry point for interacting with Octavus.\n\n```typescript\ninterface OctavusClientConfig {\n baseUrl: string; // Octavus API URL\n apiKey?: string; // Your API key\n traceModelRequests?: boolean; // Enable model request tracing (default: false)\n}\n\nclass OctavusClient {\n readonly agents: AgentsApi;\n readonly agentSessions: AgentSessionsApi;\n readonly workers: WorkersApi;\n readonly files: FilesApi;\n\n constructor(config: OctavusClientConfig);\n}\n```\n\n### AgentSessionsApi\n\nManages agent sessions.\n\n```typescript\nclass AgentSessionsApi {\n // Create a new session\n async create(agentId: string, input?: Record<string, unknown>): Promise<string>;\n\n // Get full session state (for debugging/internal use)\n async get(sessionId: string): Promise<SessionState>;\n\n // Get UI-ready messages (for client display)\n async getMessages(sessionId: string): Promise<UISessionState>;\n\n // Attach to a session for triggering\n attach(sessionId: string, options?: SessionAttachOptions): AgentSession;\n}\n\n// Full session state (internal format)\ninterface SessionState {\n id: string;\n agentId: string;\n input: Record<string, unknown>;\n variables: Record<string, unknown>;\n resources: Record<string, unknown>;\n messages: ChatMessage[]; // Internal message format\n createdAt: string;\n updatedAt: string;\n}\n\n// UI-ready session state\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready messages for frontend\n}\n```\n\n### AgentSession\n\nHandles request execution and streaming for a specific session.\n\n```typescript\nclass AgentSession {\n // Execute a request and stream parsed events\n execute(request: SessionRequest, options?: TriggerOptions): AsyncGenerator<StreamEvent>;\n\n // Get the session ID\n getSessionId(): string;\n}\n\ntype SessionRequest = TriggerRequest | ContinueRequest;\n\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n}\n\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n\n// Helper to convert events to SSE stream\nfunction toSSEStream(events: AsyncIterable<StreamEvent>): ReadableStream<Uint8Array>;\n```\n\n### FilesApi\n\nHandles file uploads for sessions.\n\n```typescript\nclass FilesApi {\n // Get presigned URLs for file uploads\n async getUploadUrls(sessionId: string, files: FileUploadRequest[]): Promise<UploadUrlsResponse>;\n}\n\ninterface FileUploadRequest {\n filename: string;\n mediaType: string;\n size: number;\n}\n\ninterface UploadUrlsResponse {\n files: {\n id: string; // File ID for references\n uploadUrl: string; // PUT to this URL\n downloadUrl: string; // GET URL after upload\n }[];\n}\n```\n\nThe client uploads files directly to S3 using the presigned upload URL. See [File Uploads](/docs/client-sdk/file-uploads) for the full integration pattern.\n\n## Next Steps\n\n- [Sessions](/docs/server-sdk/sessions) \u2014 Deep dive into session management\n- [Tools](/docs/server-sdk/tools) \u2014 Implementing tool handlers\n- [Streaming](/docs/server-sdk/streaming) \u2014 Understanding stream events\n- [Workers](/docs/server-sdk/workers) \u2014 Executing worker agents\n- [Debugging](/docs/server-sdk/debugging) \u2014 Model request tracing and debugging\n",
27
27
  excerpt: "Server SDK Overview The package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation...",
28
28
  order: 1
29
29
  },
@@ -50,7 +50,7 @@ var docs_default = [
50
50
  section: "server-sdk",
51
51
  title: "Streaming",
52
52
  description: "Understanding stream events from the Server SDK.",
53
- content: "\n# Streaming\n\nAll Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates.\n\n## Stream Response\n\nWhen you execute a request, you get an async generator of parsed events:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of StreamEvent\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// For HTTP endpoints, convert to SSE stream\nreturn new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n});\n\n// For sockets, iterate events directly\nfor await (const event of events) {\n conn.write(JSON.stringify(event));\n}\n```\n\n## Event Types\n\nThe stream emits various event types for lifecycle, text, reasoning, and tool interactions.\n\n### Lifecycle Events\n\n```typescript\n// Stream started\n{ type: 'start', messageId: '...', executionId: '...' }\n\n// Stream completed\n{ type: 'finish', finishReason: 'stop' }\n\n// Possible finish reasons:\n// - 'stop': Normal completion\n// - 'tool-calls': Waiting for server tool execution (handled by SDK internally)\n// - 'client-tool-calls': Waiting for client tool execution\n// - 'length': Max tokens reached\n// - 'content-filter': Content filtered\n// - 'error': Error occurred\n// - 'other': Other reason\n\n// Error event (see Error Handling docs for full structure)\n{ type: 'error', errorType: 'internal_error', message: 'Something went wrong', source: 'platform', retryable: false }\n```\n\n### Block Events\n\nTrack execution progress:\n\n```typescript\n// Block started\n{ type: 'block-start', blockId: '...', blockName: 'Respond to user', blockType: 'next-message', display: 'stream', thread: 'main' }\n\n// Block completed\n{ type: 'block-end', blockId: '...', summary: 'Generated response' }\n```\n\n### Text Events\n\nStreaming text content:\n\n```typescript\n// Text generation started\n{ type: 'text-start', id: '...' }\n\n// Incremental text (most common event)\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'text-delta', id: '...', delta: '!' }\n{ type: 'text-delta', id: '...', delta: ' How' }\n{ type: 'text-delta', id: '...', delta: ' can' }\n{ type: 'text-delta', id: '...', delta: ' I' }\n{ type: 'text-delta', id: '...', delta: ' help?' }\n\n// Text generation ended\n{ type: 'text-end', id: '...' }\n```\n\n### Reasoning Events\n\nExtended reasoning (for supported models like Claude):\n\n```typescript\n// Reasoning started\n{ type: 'reasoning-start', id: '...' }\n\n// Reasoning content\n{ type: 'reasoning-delta', id: '...', delta: 'Let me analyze this request...' }\n\n// Reasoning ended\n{ type: 'reasoning-end', id: '...' }\n```\n\n### Tool Events\n\nTool call lifecycle:\n\n```typescript\n// Tool input started\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user-account', title: 'Looking up account' }\n\n// Tool input/arguments streaming\n{ type: 'tool-input-delta', toolCallId: '...', inputTextDelta: '{\"userId\":\"user-123\"}' }\n\n// Tool input streaming ended\n{ type: 'tool-input-end', toolCallId: '...' }\n\n// Tool input is complete and available\n{ type: 'tool-input-available', toolCallId: '...', toolName: 'get-user-account', input: { userId: 'user-123' } }\n\n// Tool output available (success)\n{ type: 'tool-output-available', toolCallId: '...', output: { name: 'Demo User', email: '...' } }\n\n// Tool output error (failure)\n{ type: 'tool-output-error', toolCallId: '...', error: 'User not found' }\n```\n\n### Resource Events\n\nResource updates:\n\n```typescript\n{ type: 'resource-update', name: 'CONVERSATION_SUMMARY', value: 'User asked about...' }\n```\n\n## Display Modes\n\nEach block/tool specifies how it should appear to users:\n\n| Mode | Description |\n| ------------- | ----------------------------------- |\n| `hidden` | Not shown to user (background work) |\n| `name` | Shows block/tool name |\n| `description` | Shows description text |\n| `stream` | Streams content to chat |\n\n**Note**: Hidden events are filtered before reaching the client SDK. Your frontend only sees user-facing events.\n\n## Stream Event Type\n\n```typescript\ntype StreamEvent =\n // Lifecycle\n | StartEvent\n | FinishEvent\n | ErrorEvent\n // Text\n | TextStartEvent\n | TextDeltaEvent\n | TextEndEvent\n // Reasoning\n | ReasoningStartEvent\n | ReasoningDeltaEvent\n | ReasoningEndEvent\n // Tool Input/Output\n | ToolInputStartEvent\n | ToolInputDeltaEvent\n | ToolInputEndEvent\n | ToolInputAvailableEvent\n | ToolOutputAvailableEvent\n | ToolOutputErrorEvent\n // Octavus-Specific\n | BlockStartEvent\n | BlockEndEvent\n | ResourceUpdateEvent\n | ToolRequestEvent\n | ClientToolRequestEvent;\n```\n\n### Client Tool Request\n\nWhen a tool has no server handler registered, the SDK emits a `client-tool-request` event:\n\n```typescript\n{\n type: 'client-tool-request',\n executionId: 'exec_abc123', // Use this to continue execution\n toolCalls: [ // Tools for client to handle\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n args: {}\n }\n ],\n serverToolResults: [ // Results from server tools in same batch\n {\n toolCallId: 'call_def',\n toolName: 'get-user-account',\n result: { name: 'Demo User' }\n }\n ]\n}\n```\n\nAfter the client handles the tools, send a `continue` request with all results:\n\n```typescript\nsession.execute({\n type: 'continue',\n executionId: 'exec_abc123',\n toolResults: [\n ...serverToolResults, // Include server results from the event\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n result: { lat: 40.7128, lng: -74.006 },\n },\n ],\n});\n```\n\nSee [Client Tools](/docs/client-sdk/client-tools) for full client-side implementation.\n\n## Error Events\n\nErrors are emitted as structured events with type classification:\n\n```typescript\n{\n type: 'error',\n errorType: 'rate_limit_error', // Error classification\n message: 'Rate limit exceeded', // Human-readable message\n source: 'provider', // 'platform' | 'provider' | 'tool'\n retryable: true, // Whether retry is possible\n retryAfter: 60, // Seconds to wait (rate limits)\n code: 'ANTHROPIC_429', // Machine-readable code\n provider: { // Provider details (when applicable)\n name: 'anthropic',\n statusCode: 429,\n requestId: 'req_...'\n }\n}\n```\n\n### Error Types\n\n| Type | Description |\n| ---------------------- | --------------------- |\n| `rate_limit_error` | Too many requests |\n| `authentication_error` | Invalid API key |\n| `provider_error` | LLM provider issue |\n| `provider_overloaded` | Provider at capacity |\n| `tool_error` | Tool execution failed |\n| `internal_error` | Platform error |\n\n### Tool Errors\n\nTool errors are captured per-tool and don't stop the stream:\n\n```typescript\n{ type: 'tool-output-error', toolCallId: '...', error: 'Handler threw exception' }\n```\n\nThe stream always ends with either `finish` or `error`.\n\nFor client-side error handling patterns, see [Error Handling](/docs/client-sdk/error-handling).\n",
53
+ content: "\n# Streaming\n\nAll Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates.\n\n## Stream Response\n\nWhen you execute a request, you get an async generator of parsed events:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of StreamEvent\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// For HTTP endpoints, convert to SSE stream\nreturn new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n});\n\n// For sockets, iterate events directly\nfor await (const event of events) {\n conn.write(JSON.stringify(event));\n}\n```\n\nThe `X-Accel-Buffering: no` header disables proxy buffering on Nginx-based infrastructure (including Vercel), ensuring SSE events are forwarded immediately instead of being batched.\n\n### Heartbeat\n\n`toSSEStream` automatically sends SSE comment lines (`: heartbeat`) every 15 seconds during idle periods. This prevents proxies and load balancers from closing the connection due to inactivity \u2014 particularly important during multi-step executions where the stream may be silent while waiting for tool processing or LLM responses.\n\nHeartbeat comments are ignored by all SSE parsers per the spec. No client-side handling is needed.\n\n## Event Types\n\nThe stream emits various event types for lifecycle, text, reasoning, and tool interactions.\n\n### Lifecycle Events\n\n```typescript\n// Stream started\n{ type: 'start', messageId: '...', executionId: '...' }\n\n// Stream completed\n{ type: 'finish', finishReason: 'stop' }\n\n// Possible finish reasons:\n// - 'stop': Normal completion\n// - 'tool-calls': Waiting for server tool execution (handled by SDK internally)\n// - 'client-tool-calls': Waiting for client tool execution\n// - 'length': Max tokens reached\n// - 'content-filter': Content filtered\n// - 'error': Error occurred\n// - 'other': Other reason\n\n// Error event (see Error Handling docs for full structure)\n{ type: 'error', errorType: 'internal_error', message: 'Something went wrong', source: 'platform', retryable: false }\n```\n\n### Block Events\n\nTrack execution progress:\n\n```typescript\n// Block started\n{ type: 'block-start', blockId: '...', blockName: 'Respond to user', blockType: 'next-message', display: 'stream', thread: 'main' }\n\n// Block completed\n{ type: 'block-end', blockId: '...', summary: 'Generated response' }\n```\n\n### Text Events\n\nStreaming text content:\n\n```typescript\n// Text generation started\n{ type: 'text-start', id: '...' }\n\n// Incremental text (most common event)\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'text-delta', id: '...', delta: '!' }\n{ type: 'text-delta', id: '...', delta: ' How' }\n{ type: 'text-delta', id: '...', delta: ' can' }\n{ type: 'text-delta', id: '...', delta: ' I' }\n{ type: 'text-delta', id: '...', delta: ' help?' }\n\n// Text generation ended\n{ type: 'text-end', id: '...' }\n```\n\n### Reasoning Events\n\nExtended reasoning (for supported models like Claude):\n\n```typescript\n// Reasoning started\n{ type: 'reasoning-start', id: '...' }\n\n// Reasoning content\n{ type: 'reasoning-delta', id: '...', delta: 'Let me analyze this request...' }\n\n// Reasoning ended\n{ type: 'reasoning-end', id: '...' }\n```\n\n### Tool Events\n\nTool call lifecycle:\n\n```typescript\n// Tool input started\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user-account', title: 'Looking up account' }\n\n// Tool input/arguments streaming\n{ type: 'tool-input-delta', toolCallId: '...', inputTextDelta: '{\"userId\":\"user-123\"}' }\n\n// Tool input streaming ended\n{ type: 'tool-input-end', toolCallId: '...' }\n\n// Tool input is complete and available\n{ type: 'tool-input-available', toolCallId: '...', toolName: 'get-user-account', input: { userId: 'user-123' } }\n\n// Tool output available (success)\n{ type: 'tool-output-available', toolCallId: '...', output: { name: 'Demo User', email: '...' } }\n\n// Tool output error (failure)\n{ type: 'tool-output-error', toolCallId: '...', error: 'User not found' }\n```\n\n### Resource Events\n\nResource updates:\n\n```typescript\n{ type: 'resource-update', name: 'CONVERSATION_SUMMARY', value: 'User asked about...' }\n```\n\n## Display Modes\n\nEach block/tool specifies how it should appear to users:\n\n| Mode | Description |\n| ------------- | ----------------------------------- |\n| `hidden` | Not shown to user (background work) |\n| `name` | Shows block/tool name |\n| `description` | Shows description text |\n| `stream` | Streams content to chat |\n\n**Note**: Hidden events are filtered before reaching the client SDK. Your frontend only sees user-facing events.\n\n## Stream Event Type\n\n```typescript\ntype StreamEvent =\n // Lifecycle\n | StartEvent\n | FinishEvent\n | ErrorEvent\n // Text\n | TextStartEvent\n | TextDeltaEvent\n | TextEndEvent\n // Reasoning\n | ReasoningStartEvent\n | ReasoningDeltaEvent\n | ReasoningEndEvent\n // Tool Input/Output\n | ToolInputStartEvent\n | ToolInputDeltaEvent\n | ToolInputEndEvent\n | ToolInputAvailableEvent\n | ToolOutputAvailableEvent\n | ToolOutputErrorEvent\n // Octavus-Specific\n | BlockStartEvent\n | BlockEndEvent\n | ResourceUpdateEvent\n | ToolRequestEvent\n | ClientToolRequestEvent;\n```\n\n### Client Tool Request\n\nWhen a tool has no server handler registered, the SDK emits a `client-tool-request` event:\n\n```typescript\n{\n type: 'client-tool-request',\n executionId: 'exec_abc123', // Use this to continue execution\n toolCalls: [ // Tools for client to handle\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n args: {}\n }\n ],\n serverToolResults: [ // Results from server tools in same batch\n {\n toolCallId: 'call_def',\n toolName: 'get-user-account',\n result: { name: 'Demo User' }\n }\n ]\n}\n```\n\nAfter the client handles the tools, send a `continue` request with all results:\n\n```typescript\nsession.execute({\n type: 'continue',\n executionId: 'exec_abc123',\n toolResults: [\n ...serverToolResults, // Include server results from the event\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n result: { lat: 40.7128, lng: -74.006 },\n },\n ],\n});\n```\n\nSee [Client Tools](/docs/client-sdk/client-tools) for full client-side implementation.\n\n## Error Events\n\nErrors are emitted as structured events with type classification:\n\n```typescript\n{\n type: 'error',\n errorType: 'rate_limit_error', // Error classification\n message: 'Rate limit exceeded', // Human-readable message\n source: 'provider', // 'platform' | 'provider' | 'tool'\n retryable: true, // Whether retry is possible\n retryAfter: 60, // Seconds to wait (rate limits)\n code: 'ANTHROPIC_429', // Machine-readable code\n provider: { // Provider details (when applicable)\n name: 'anthropic',\n statusCode: 429,\n requestId: 'req_...'\n }\n}\n```\n\n### Error Types\n\n| Type | Description |\n| ---------------------- | --------------------- |\n| `rate_limit_error` | Too many requests |\n| `authentication_error` | Invalid API key |\n| `provider_error` | LLM provider issue |\n| `provider_overloaded` | Provider at capacity |\n| `tool_error` | Tool execution failed |\n| `internal_error` | Platform error |\n\n### Tool Errors\n\nTool errors are captured per-tool and don't stop the stream:\n\n```typescript\n{ type: 'tool-output-error', toolCallId: '...', error: 'Handler threw exception' }\n```\n\nThe stream always ends with either `finish` or `error`.\n\nFor client-side error handling patterns, see [Error Handling](/docs/client-sdk/error-handling).\n",
54
54
  excerpt: "Streaming All Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates. Stream Response When you execute a request, you get an async...",
55
55
  order: 4
56
56
  },
@@ -59,7 +59,7 @@ var docs_default = [
59
59
  section: "server-sdk",
60
60
  title: "CLI",
61
61
  description: "Command-line interface for validating and syncing agent definitions.",
62
- content: '\n# Octavus CLI\n\nThe `@octavus/cli` package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform.\n\n**Current version:** `2.14.0`\n\n## Installation\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Configuration\n\nThe CLI requires an API key with the **Agents** permission.\n\n### Environment Variables\n\n| Variable | Description |\n| --------------------- | ---------------------------------------------- |\n| `OCTAVUS_CLI_API_KEY` | API key with "Agents" permission (recommended) |\n| `OCTAVUS_API_KEY` | Fallback if `OCTAVUS_CLI_API_KEY` not set |\n| `OCTAVUS_API_URL` | Optional, defaults to `https://octavus.ai` |\n\n### Two-Key Strategy (Recommended)\n\nFor production deployments, use separate API keys with minimal permissions:\n\n```bash\n# CI/CD or .env.local (not committed)\nOCTAVUS_CLI_API_KEY=oct_sk_... # "Agents" permission only\n\n# Production .env\nOCTAVUS_API_KEY=oct_sk_... # "Sessions" permission only\n```\n\nThis ensures production servers only have session permissions (smaller blast radius if leaked), while agent management is restricted to development/CI environments.\n\n### Multiple Environments\n\nUse separate Octavus projects for staging and production, each with their own API keys. The `--env` flag lets you load different environment files:\n\n```bash\n# Local development (default: .env)\noctavus sync ./agents/my-agent\n\n# Staging project\noctavus --env .env.staging sync ./agents/my-agent\n\n# Production project\noctavus --env .env.production sync ./agents/my-agent\n```\n\nExample environment files:\n\n```bash\n# .env.staging (syncs to your staging project)\nOCTAVUS_CLI_API_KEY=oct_sk_staging_project_key...\n\n# .env.production (syncs to your production project)\nOCTAVUS_CLI_API_KEY=oct_sk_production_project_key...\n```\n\nEach project has its own agents, so you\'ll get different agent IDs per environment.\n\n## Global Options\n\n| Option | Description |\n| -------------- | ------------------------------------------------------- |\n| `--env <file>` | Load environment from a specific file (default: `.env`) |\n| `--help` | Show help |\n| `--version` | Show version |\n\n## Commands\n\n### `octavus sync <path>`\n\nSync an agent definition to the platform. Creates the agent if it doesn\'t exist, or updates it if it does.\n\n```bash\noctavus sync ./agents/my-agent\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Reading agent from ./agents/my-agent...\n\u2139 Syncing support-chat...\n\u2713 Created: support-chat\n Agent ID: clxyz123abc456\n```\n\n### `octavus validate <path>`\n\nValidate an agent definition without saving. Useful for CI/CD pipelines.\n\n```bash\noctavus validate ./agents/my-agent\n```\n\n**Exit codes:**\n\n- `0` \u2014 Validation passed\n- `1` \u2014 Validation errors\n- `2` \u2014 Configuration errors (missing API key, etc.)\n\n### `octavus list`\n\nList all agents in your project.\n\n```bash\noctavus list\n```\n\n**Example output:**\n\n```\nSLUG NAME FORMAT ID\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nsupport-chat Support Chat Agent interactive clxyz123abc456\n\n1 agent(s)\n```\n\n### `octavus get <slug>`\n\nGet details about a specific agent by its slug.\n\n```bash\noctavus get support-chat\n```\n\n### `octavus archive <slug>`\n\nArchive an agent by slug (soft delete). Archived agents are removed from the active agent list and their slug is freed for reuse.\n\n```bash\noctavus archive support-chat\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Archiving support-chat...\n\u2713 Archived: support-chat\n Agent ID: clxyz123abc456\n```\n\n## Agent Directory Structure\n\nThe CLI expects agent definitions in a specific directory structure:\n\n```\nmy-agent/\n\u251C\u2500\u2500 settings.json # Required: Agent metadata\n\u251C\u2500\u2500 protocol.yaml # Required: Agent protocol\n\u251C\u2500\u2500 prompts/ # Optional: Prompt templates\n\u2502 \u251C\u2500\u2500 system.md\n\u2502 \u2514\u2500\u2500 user-message.md\n\u2514\u2500\u2500 references/ # Optional: Reference documents\n \u2514\u2500\u2500 api-guidelines.md\n```\n\n### references/\n\nReference files are markdown documents with YAML frontmatter containing a `description`. The agent can fetch these on demand during execution. See [References](/docs/protocol/references) for details.\n\n### settings.json\n\n```json\n{\n "slug": "my-agent",\n "name": "My Agent",\n "description": "A helpful assistant",\n "format": "interactive"\n}\n```\n\n### protocol.yaml\n\nSee the [Protocol documentation](/docs/protocol/overview) for details on protocol syntax.\n\n## CI/CD Integration\n\n### GitHub Actions\n\n```yaml\nname: Validate and Sync Agents\n\non:\n push:\n branches: [main]\n paths:\n - \'agents/**\'\n\njobs:\n sync:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - uses: actions/setup-node@v4\n with:\n node-version: \'22\'\n\n - run: npm install\n\n - name: Validate agent\n run: npx octavus validate ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n\n - name: Sync agent\n run: npx octavus sync ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n```\n\n### Package.json Scripts\n\nAdd sync scripts to your `package.json`:\n\n```json\n{\n "scripts": {\n "agents:validate": "octavus validate ./agents/my-agent",\n "agents:sync": "octavus sync ./agents/my-agent"\n },\n "devDependencies": {\n "@octavus/cli": "^0.1.0"\n }\n}\n```\n\n## Workflow\n\nThe recommended workflow for managing agents:\n\n1. **Define agent locally** \u2014 Create `settings.json`, `protocol.yaml`, and prompts\n2. **Validate** \u2014 Run `octavus validate ./my-agent` to check for errors\n3. **Sync** \u2014 Run `octavus sync ./my-agent` to push to platform\n4. **Store agent ID** \u2014 Save the output ID in an environment variable\n5. **Use in app** \u2014 Read the ID from env and pass to `client.agentSessions.create()`\n\n```bash\n# After syncing: octavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n\n# Add to your .env file\nOCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456\n```\n\n```typescript\nconst agentId = process.env.OCTAVUS_SUPPORT_AGENT_ID;\n\nconst sessionId = await client.agentSessions.create(agentId, {\n COMPANY_NAME: \'Acme Corp\',\n});\n```\n',
62
+ content: '\n# Octavus CLI\n\nThe `@octavus/cli` package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform.\n\n**Current version:** `2.15.0`\n\n## Installation\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Configuration\n\nThe CLI requires an API key with the **Agents** permission.\n\n### Environment Variables\n\n| Variable | Description |\n| --------------------- | ---------------------------------------------- |\n| `OCTAVUS_CLI_API_KEY` | API key with "Agents" permission (recommended) |\n| `OCTAVUS_API_KEY` | Fallback if `OCTAVUS_CLI_API_KEY` not set |\n| `OCTAVUS_API_URL` | Optional, defaults to `https://octavus.ai` |\n\n### Two-Key Strategy (Recommended)\n\nFor production deployments, use separate API keys with minimal permissions:\n\n```bash\n# CI/CD or .env.local (not committed)\nOCTAVUS_CLI_API_KEY=oct_sk_... # "Agents" permission only\n\n# Production .env\nOCTAVUS_API_KEY=oct_sk_... # "Sessions" permission only\n```\n\nThis ensures production servers only have session permissions (smaller blast radius if leaked), while agent management is restricted to development/CI environments.\n\n### Multiple Environments\n\nUse separate Octavus projects for staging and production, each with their own API keys. The `--env` flag lets you load different environment files:\n\n```bash\n# Local development (default: .env)\noctavus sync ./agents/my-agent\n\n# Staging project\noctavus --env .env.staging sync ./agents/my-agent\n\n# Production project\noctavus --env .env.production sync ./agents/my-agent\n```\n\nExample environment files:\n\n```bash\n# .env.staging (syncs to your staging project)\nOCTAVUS_CLI_API_KEY=oct_sk_staging_project_key...\n\n# .env.production (syncs to your production project)\nOCTAVUS_CLI_API_KEY=oct_sk_production_project_key...\n```\n\nEach project has its own agents, so you\'ll get different agent IDs per environment.\n\n## Global Options\n\n| Option | Description |\n| -------------- | ------------------------------------------------------- |\n| `--env <file>` | Load environment from a specific file (default: `.env`) |\n| `--help` | Show help |\n| `--version` | Show version |\n\n## Commands\n\n### `octavus sync <path>`\n\nSync an agent definition to the platform. Creates the agent if it doesn\'t exist, or updates it if it does.\n\n```bash\noctavus sync ./agents/my-agent\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Reading agent from ./agents/my-agent...\n\u2139 Syncing support-chat...\n\u2713 Created: support-chat\n Agent ID: clxyz123abc456\n```\n\n### `octavus validate <path>`\n\nValidate an agent definition without saving. Useful for CI/CD pipelines.\n\n```bash\noctavus validate ./agents/my-agent\n```\n\n**Exit codes:**\n\n- `0` \u2014 Validation passed\n- `1` \u2014 Validation errors\n- `2` \u2014 Configuration errors (missing API key, etc.)\n\n### `octavus list`\n\nList all agents in your project.\n\n```bash\noctavus list\n```\n\n**Example output:**\n\n```\nSLUG NAME FORMAT ID\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nsupport-chat Support Chat Agent interactive clxyz123abc456\n\n1 agent(s)\n```\n\n### `octavus get <slug>`\n\nGet details about a specific agent by its slug.\n\n```bash\noctavus get support-chat\n```\n\n### `octavus archive <slug>`\n\nArchive an agent by slug (soft delete). Archived agents are removed from the active agent list and their slug is freed for reuse.\n\n```bash\noctavus archive support-chat\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Archiving support-chat...\n\u2713 Archived: support-chat\n Agent ID: clxyz123abc456\n```\n\n## Agent Directory Structure\n\nThe CLI expects agent definitions in a specific directory structure:\n\n```\nmy-agent/\n\u251C\u2500\u2500 settings.json # Required: Agent metadata\n\u251C\u2500\u2500 protocol.yaml # Required: Agent protocol\n\u251C\u2500\u2500 prompts/ # Optional: Prompt templates\n\u2502 \u251C\u2500\u2500 system.md\n\u2502 \u2514\u2500\u2500 user-message.md\n\u2514\u2500\u2500 references/ # Optional: Reference documents\n \u2514\u2500\u2500 api-guidelines.md\n```\n\n### references/\n\nReference files are markdown documents with YAML frontmatter containing a `description`. The agent can fetch these on demand during execution. See [References](/docs/protocol/references) for details.\n\n### settings.json\n\n```json\n{\n "slug": "my-agent",\n "name": "My Agent",\n "description": "A helpful assistant",\n "format": "interactive"\n}\n```\n\n### protocol.yaml\n\nSee the [Protocol documentation](/docs/protocol/overview) for details on protocol syntax.\n\n## CI/CD Integration\n\n### GitHub Actions\n\n```yaml\nname: Validate and Sync Agents\n\non:\n push:\n branches: [main]\n paths:\n - \'agents/**\'\n\njobs:\n sync:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - uses: actions/setup-node@v4\n with:\n node-version: \'22\'\n\n - run: npm install\n\n - name: Validate agent\n run: npx octavus validate ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n\n - name: Sync agent\n run: npx octavus sync ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n```\n\n### Package.json Scripts\n\nAdd sync scripts to your `package.json`:\n\n```json\n{\n "scripts": {\n "agents:validate": "octavus validate ./agents/my-agent",\n "agents:sync": "octavus sync ./agents/my-agent"\n },\n "devDependencies": {\n "@octavus/cli": "^0.1.0"\n }\n}\n```\n\n## Workflow\n\nThe recommended workflow for managing agents:\n\n1. **Define agent locally** \u2014 Create `settings.json`, `protocol.yaml`, and prompts\n2. **Validate** \u2014 Run `octavus validate ./my-agent` to check for errors\n3. **Sync** \u2014 Run `octavus sync ./my-agent` to push to platform\n4. **Store agent ID** \u2014 Save the output ID in an environment variable\n5. **Use in app** \u2014 Read the ID from env and pass to `client.agentSessions.create()`\n\n```bash\n# After syncing: octavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n\n# Add to your .env file\nOCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456\n```\n\n```typescript\nconst agentId = process.env.OCTAVUS_SUPPORT_AGENT_ID;\n\nconst sessionId = await client.agentSessions.create(agentId, {\n COMPANY_NAME: \'Acme Corp\',\n});\n```\n',
63
63
  excerpt: "Octavus CLI The package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform. Current version: Installation ...",
64
64
  order: 5
65
65
  },
@@ -86,7 +86,7 @@ var docs_default = [
86
86
  section: "client-sdk",
87
87
  title: "Overview",
88
88
  description: "Introduction to the Octavus Client SDKs for building chat interfaces.",
89
- content: "\n# Client SDK Overview\n\nOctavus provides two packages for frontend integration:\n\n| Package | Purpose | Use When |\n| --------------------- | ------------------------ | ----------------------------------------------------- |\n| `@octavus/react` | React hooks and bindings | Building React applications |\n| `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |\n\n**Most users should install `@octavus/react`** \u2014 it includes everything from `@octavus/client-sdk` plus React-specific hooks.\n\n## Installation\n\n### React Applications\n\n```bash\nnpm install @octavus/react\n```\n\n**Current version:** `2.14.0`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `2.14.0`\n\n## Transport Pattern\n\nThe Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:\n\n| Transport | Use Case | Docs |\n| ----------------------- | -------------------------------------------- | ----------------------------------------------------- |\n| `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |\n| `createSocketTransport` | WebSocket, SockJS, or other socket protocols | [Socket Transport](/docs/client-sdk/socket-transport) |\n\nWhen the transport changes (e.g., when `sessionId` changes), the `useOctavusChat` hook automatically reinitializes with the new transport.\n\n> **Recommendation**: Use HTTP transport unless you specifically need WebSocket features (custom real-time events, Meteor/Phoenix, etc.).\n\n## React Usage\n\nThe `useOctavusChat` hook provides state management and streaming for React applications:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n // Create a stable transport instance (memoized on sessionId)\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n return (\n <div>\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n </div>\n );\n}\n```\n\n## Framework-Agnostic Usage\n\nThe `OctavusChat` class can be used with any framework or vanilla JavaScript:\n\n```typescript\nimport { OctavusChat, createHttpTransport } from '@octavus/client-sdk';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n\nconst chat = new OctavusChat({ transport });\n\n// Subscribe to state changes\nconst unsubscribe = chat.subscribe(() => {\n console.log('Messages:', chat.messages);\n console.log('Status:', chat.status);\n // Update your UI here\n});\n\n// Send a message\nawait chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });\n\n// Cleanup when done\nunsubscribe();\n```\n\n## Key Features\n\n### Unified Send Function\n\nThe `send` function handles both user message display and agent triggering in one call:\n\n```tsx\nconst { send } = useOctavusChat({ transport });\n\n// Add user message to UI and trigger agent\nawait send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n\n// Trigger without adding a user message (e.g., button click)\nawait send('request-human');\n```\n\n### Message Parts\n\nMessages contain ordered `parts` for rich content:\n\n```tsx\nconst { messages } = useOctavusChat({ transport });\n\n// Each message has typed parts\nmessage.parts.map((part) => {\n switch (part.type) {\n case 'text': // Text content\n case 'reasoning': // Extended reasoning/thinking\n case 'tool-call': // Tool execution\n case 'operation': // Internal operations (set-resource, etc.)\n }\n});\n```\n\n### Status Tracking\n\n```tsx\nconst { status } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error' | 'awaiting-input'\n// 'awaiting-input' occurs when interactive client tools need user action\n```\n\n### Stop Streaming\n\n```tsx\nconst { stop } = useOctavusChat({ transport });\n\n// Stop current stream and finalize message\nstop();\n```\n\n### Retry Last Trigger\n\nRe-execute the last trigger from the same starting point. Messages are rolled back to the state before the trigger, the user message is re-added (if any), and the agent re-executes. Already-uploaded files are reused without re-uploading.\n\n```tsx\nconst { retry, canRetry } = useOctavusChat({ transport });\n\n// Retry after an error, cancellation, or unsatisfactory result\nif (canRetry) {\n await retry();\n}\n```\n\n`canRetry` is `true` when a trigger has been sent and the chat is not currently streaming or awaiting input.\n\n## Hook Reference (React)\n\n### useOctavusChat\n\n```typescript\nfunction useOctavusChat(options: OctavusChatOptions): UseOctavusChatReturn;\n\ninterface OctavusChatOptions {\n // Required: Transport for streaming events\n transport: Transport;\n\n // Optional: Function to request upload URLs for file uploads\n requestUploadUrls?: (\n files: { filename: string; mediaType: string; size: number }[],\n ) => Promise<UploadUrlsResponse>;\n\n // Optional: Client-side tool handlers\n // - Function: executes automatically and returns result\n // - 'interactive': appears in pendingClientTools for user input\n clientTools?: Record<string, ClientToolHandler>;\n\n // Optional: Pre-populate with existing messages (session restore)\n initialMessages?: UIMessage[];\n\n // Optional: Callbacks\n onError?: (error: OctavusError) => void; // Structured error with type, source, retryable\n onFinish?: () => void;\n onStop?: () => void; // Called when user stops generation\n onResourceUpdate?: (name: string, value: unknown) => void;\n}\n\ninterface UseOctavusChatReturn {\n // State\n messages: UIMessage[];\n status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n error: OctavusError | null; // Structured error with type, source, retryable\n\n // Connection (socket transport only - undefined for HTTP)\n connectionState: ConnectionState | undefined; // 'disconnected' | 'connecting' | 'connected' | 'error'\n connectionError: Error | undefined;\n\n // Client tools (interactive tools awaiting user input)\n pendingClientTools: Record<string, InteractiveTool[]>; // Keyed by tool name\n\n // Actions\n send: (\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ) => Promise<void>;\n stop: () => void;\n retry: () => Promise<void>; // Retry last trigger from same starting point\n canRetry: boolean; // Whether retry() can be called\n\n // Connection management (socket transport only - undefined for HTTP)\n connect: (() => Promise<void>) | undefined;\n disconnect: (() => void) | undefined;\n\n // File uploads (requires requestUploadUrls)\n uploadFiles: (\n files: FileList | File[],\n onProgress?: (fileIndex: number, progress: number) => void,\n ) => Promise<FileReference[]>;\n}\n\ninterface UserMessageInput {\n content?: string;\n files?: FileList | File[] | FileReference[];\n}\n```\n\n## Transport Reference\n\n### createHttpTransport\n\nCreates an HTTP/SSE transport using native `fetch()`:\n\n```typescript\nimport { createHttpTransport } from '@octavus/react';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n```\n\n### createSocketTransport\n\nCreates a WebSocket/SockJS transport for real-time connections:\n\n```typescript\nimport { createSocketTransport } from '@octavus/react';\n\nconst transport = createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(`wss://api.example.com/stream?sessionId=${sessionId}`);\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('Connection failed'));\n }),\n});\n```\n\nSocket transport provides additional connection management:\n\n```typescript\n// Access connection state directly\ntransport.connectionState; // 'disconnected' | 'connecting' | 'connected' | 'error'\n\n// Subscribe to state changes\ntransport.onConnectionStateChange((state, error) => {\n /* ... */\n});\n\n// Eager connection (instead of lazy on first send)\nawait transport.connect();\n\n// Manual disconnect\ntransport.disconnect();\n```\n\nFor detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).\n\n## Class Reference (Framework-Agnostic)\n\n### OctavusChat\n\n```typescript\nclass OctavusChat {\n constructor(options: OctavusChatOptions);\n\n // State (read-only)\n readonly messages: UIMessage[];\n readonly status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n readonly error: OctavusError | null; // Structured error\n readonly pendingClientTools: Record<string, InteractiveTool[]>; // Interactive tools\n\n // Actions\n send(\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ): Promise<void>;\n stop(): void;\n\n // Subscription\n subscribe(callback: () => void): () => void; // Returns unsubscribe function\n}\n```\n\n## Next Steps\n\n- [HTTP Transport](/docs/client-sdk/http-transport) \u2014 HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) \u2014 WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) \u2014 Working with message state\n- [Streaming](/docs/client-sdk/streaming) \u2014 Building streaming UIs\n- [Client Tools](/docs/client-sdk/client-tools) \u2014 Interactive browser-side tool handling\n- [Operations](/docs/client-sdk/execution-blocks) \u2014 Showing agent progress\n- [Error Handling](/docs/client-sdk/error-handling) \u2014 Handling errors with type guards\n- [File Uploads](/docs/client-sdk/file-uploads) \u2014 Uploading images and documents\n- [Examples](/docs/examples/overview) \u2014 Complete working examples\n",
89
+ content: "\n# Client SDK Overview\n\nOctavus provides two packages for frontend integration:\n\n| Package | Purpose | Use When |\n| --------------------- | ------------------------ | ----------------------------------------------------- |\n| `@octavus/react` | React hooks and bindings | Building React applications |\n| `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |\n\n**Most users should install `@octavus/react`** \u2014 it includes everything from `@octavus/client-sdk` plus React-specific hooks.\n\n## Installation\n\n### React Applications\n\n```bash\nnpm install @octavus/react\n```\n\n**Current version:** `2.15.0`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `2.15.0`\n\n## Transport Pattern\n\nThe Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:\n\n| Transport | Use Case | Docs |\n| ----------------------- | -------------------------------------------- | ----------------------------------------------------- |\n| `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |\n| `createSocketTransport` | WebSocket, SockJS, or other socket protocols | [Socket Transport](/docs/client-sdk/socket-transport) |\n\nWhen the transport changes (e.g., when `sessionId` changes), the `useOctavusChat` hook automatically reinitializes with the new transport.\n\n> **Recommendation**: Use HTTP transport unless you specifically need WebSocket features (custom real-time events, Meteor/Phoenix, etc.).\n\n## React Usage\n\nThe `useOctavusChat` hook provides state management and streaming for React applications:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n // Create a stable transport instance (memoized on sessionId)\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n return (\n <div>\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n </div>\n );\n}\n```\n\n## Framework-Agnostic Usage\n\nThe `OctavusChat` class can be used with any framework or vanilla JavaScript:\n\n```typescript\nimport { OctavusChat, createHttpTransport } from '@octavus/client-sdk';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n\nconst chat = new OctavusChat({ transport });\n\n// Subscribe to state changes\nconst unsubscribe = chat.subscribe(() => {\n console.log('Messages:', chat.messages);\n console.log('Status:', chat.status);\n // Update your UI here\n});\n\n// Send a message\nawait chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });\n\n// Cleanup when done\nunsubscribe();\n```\n\n## Key Features\n\n### Unified Send Function\n\nThe `send` function handles both user message display and agent triggering in one call:\n\n```tsx\nconst { send } = useOctavusChat({ transport });\n\n// Add user message to UI and trigger agent\nawait send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n\n// Trigger without adding a user message (e.g., button click)\nawait send('request-human');\n```\n\n### Message Parts\n\nMessages contain ordered `parts` for rich content:\n\n```tsx\nconst { messages } = useOctavusChat({ transport });\n\n// Each message has typed parts\nmessage.parts.map((part) => {\n switch (part.type) {\n case 'text': // Text content\n case 'reasoning': // Extended reasoning/thinking\n case 'tool-call': // Tool execution\n case 'operation': // Internal operations (set-resource, etc.)\n }\n});\n```\n\n### Status Tracking\n\n```tsx\nconst { status } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error' | 'awaiting-input'\n// 'awaiting-input' occurs when interactive client tools need user action\n```\n\n### Stop Streaming\n\n```tsx\nconst { stop } = useOctavusChat({ transport });\n\n// Stop current stream and finalize message\nstop();\n```\n\n### Retry Last Trigger\n\nRe-execute the last trigger from the same starting point. Messages are rolled back to the state before the trigger, the user message is re-added (if any), and the agent re-executes. Already-uploaded files are reused without re-uploading.\n\n```tsx\nconst { retry, canRetry } = useOctavusChat({ transport });\n\n// Retry after an error, cancellation, or unsatisfactory result\nif (canRetry) {\n await retry();\n}\n```\n\n`canRetry` is `true` when a trigger has been sent and the chat is not currently streaming or awaiting input.\n\n## Hook Reference (React)\n\n### useOctavusChat\n\n```typescript\nfunction useOctavusChat(options: OctavusChatOptions): UseOctavusChatReturn;\n\ninterface OctavusChatOptions {\n // Required: Transport for streaming events\n transport: Transport;\n\n // Optional: Function to request upload URLs for file uploads\n requestUploadUrls?: (\n files: { filename: string; mediaType: string; size: number }[],\n ) => Promise<UploadUrlsResponse>;\n\n // Optional: Client-side tool handlers\n // - Function: executes automatically and returns result\n // - 'interactive': appears in pendingClientTools for user input\n clientTools?: Record<string, ClientToolHandler>;\n\n // Optional: Pre-populate with existing messages (session restore)\n initialMessages?: UIMessage[];\n\n // Optional: Callbacks\n onError?: (error: OctavusError) => void; // Structured error with type, source, retryable\n onFinish?: () => void;\n onStop?: () => void; // Called when user stops generation\n onResourceUpdate?: (name: string, value: unknown) => void;\n}\n\ninterface UseOctavusChatReturn {\n // State\n messages: UIMessage[];\n status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n error: OctavusError | null; // Structured error with type, source, retryable\n\n // Connection (socket transport only - undefined for HTTP)\n connectionState: ConnectionState | undefined; // 'disconnected' | 'connecting' | 'connected' | 'error'\n connectionError: Error | undefined;\n\n // Client tools (interactive tools awaiting user input)\n pendingClientTools: Record<string, InteractiveTool[]>; // Keyed by tool name\n\n // Actions\n send: (\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ) => Promise<void>;\n stop: () => void;\n retry: () => Promise<void>; // Retry last trigger from same starting point\n canRetry: boolean; // Whether retry() can be called\n\n // Connection management (socket transport only - undefined for HTTP)\n connect: (() => Promise<void>) | undefined;\n disconnect: (() => void) | undefined;\n\n // File uploads (requires requestUploadUrls)\n uploadFiles: (\n files: FileList | File[],\n onProgress?: (fileIndex: number, progress: number) => void,\n ) => Promise<FileReference[]>;\n}\n\ninterface UserMessageInput {\n content?: string;\n files?: FileList | File[] | FileReference[];\n}\n```\n\n## Transport Reference\n\n### createHttpTransport\n\nCreates an HTTP/SSE transport using native `fetch()`:\n\n```typescript\nimport { createHttpTransport } from '@octavus/react';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n```\n\n### createSocketTransport\n\nCreates a WebSocket/SockJS transport for real-time connections:\n\n```typescript\nimport { createSocketTransport } from '@octavus/react';\n\nconst transport = createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(`wss://api.example.com/stream?sessionId=${sessionId}`);\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('Connection failed'));\n }),\n});\n```\n\nSocket transport provides additional connection management:\n\n```typescript\n// Access connection state directly\ntransport.connectionState; // 'disconnected' | 'connecting' | 'connected' | 'error'\n\n// Subscribe to state changes\ntransport.onConnectionStateChange((state, error) => {\n /* ... */\n});\n\n// Eager connection (instead of lazy on first send)\nawait transport.connect();\n\n// Manual disconnect\ntransport.disconnect();\n```\n\nFor detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).\n\n## Class Reference (Framework-Agnostic)\n\n### OctavusChat\n\n```typescript\nclass OctavusChat {\n constructor(options: OctavusChatOptions);\n\n // State (read-only)\n readonly messages: UIMessage[];\n readonly status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n readonly error: OctavusError | null; // Structured error\n readonly pendingClientTools: Record<string, InteractiveTool[]>; // Interactive tools\n\n // Actions\n send(\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ): Promise<void>;\n stop(): void;\n\n // Subscription\n subscribe(callback: () => void): () => void; // Returns unsubscribe function\n}\n```\n\n## Next Steps\n\n- [HTTP Transport](/docs/client-sdk/http-transport) \u2014 HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) \u2014 WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) \u2014 Working with message state\n- [Streaming](/docs/client-sdk/streaming) \u2014 Building streaming UIs\n- [Client Tools](/docs/client-sdk/client-tools) \u2014 Interactive browser-side tool handling\n- [Operations](/docs/client-sdk/execution-blocks) \u2014 Showing agent progress\n- [Error Handling](/docs/client-sdk/error-handling) \u2014 Handling errors with type guards\n- [File Uploads](/docs/client-sdk/file-uploads) \u2014 Uploading images and documents\n- [Examples](/docs/examples/overview) \u2014 Complete working examples\n",
90
90
  excerpt: "Client SDK Overview Octavus provides two packages for frontend integration: | Package | Purpose | Use When | |...",
91
91
  order: 1
92
92
  },
@@ -209,6 +209,7 @@ export async function POST(request: Request) {
209
209
  'Content-Type': 'text/event-stream',
210
210
  'Cache-Control': 'no-cache',
211
211
  Connection: 'keep-alive',
212
+ 'X-Accel-Buffering': 'no',
212
213
  },
213
214
  });
214
215
  }
@@ -363,6 +364,7 @@ app.post('/api/trigger', async (req, res) => {
363
364
  res.setHeader('Content-Type', 'text/event-stream');
364
365
  res.setHeader('Cache-Control', 'no-cache');
365
366
  res.setHeader('Connection', 'keep-alive');
367
+ res.setHeader('X-Accel-Buffering', 'no');
366
368
 
367
369
  // Pipe the stream to the response
368
370
  const reader = stream.getReader();
@@ -577,7 +579,7 @@ See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list
577
579
  section: "protocol",
578
580
  title: "Skills",
579
581
  description: "Using Octavus skills for code execution and specialized capabilities.",
580
- content: "\n# Skills\n\nSkills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are self-contained packages with documentation and scripts that run in secure sandboxes.\n\n## Overview\n\nOctavus Skills provide **provider-agnostic** code execution. They work with any LLM provider (Anthropic, OpenAI, Google) by using explicit tool calls and system prompt injection.\n\n### How Skills Work\n\n1. **Skill Definition**: Skills are defined in the protocol's `skills:` section\n2. **Skill Resolution**: Skills are resolved from available sources (see below)\n3. **Sandbox Execution**: When a skill is used, code runs in an isolated sandbox environment\n4. **File Generation**: Files saved to `/output/` are automatically captured and made available for download\n\n### Skill Sources\n\nSkills come from two sources, visible in the Skills tab of your organization:\n\n| Source | Badge in UI | Visibility | Example |\n| ----------- | ----------- | ------------------------------ | ------------------ |\n| **Octavus** | `Octavus` | Available to all organizations | `qr-code` |\n| **Custom** | None | Private to your organization | `my-company-skill` |\n\nWhen you reference a skill in your protocol, Octavus resolves it from your available skills. If you create a custom skill with the same name as an Octavus skill, your custom skill takes precedence.\n\n## Defining Skills\n\nDefine skills in the protocol's `skills:` section:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n```\n\n### Skill Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------------------------------------- |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` (default: `description`) |\n| `description` | No | Custom description shown to users (overrides skill's built-in description) |\n\n### Display Modes\n\n| Mode | Behavior |\n| ------------- | ------------------------------------------- |\n| `hidden` | Skill usage not shown to users |\n| `name` | Shows skill name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams progress if available |\n\n## Enabling Skills\n\nAfter defining skills in the `skills:` section, specify which skills are available. Skills work in both interactive agents and workers.\n\n### Interactive Agents\n\nReference skills in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account]\n skills: [qr-code]\n agentic: true\n```\n\n### Workers and Named Threads\n\nReference skills per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nsteps:\n Start thread:\n block: start-thread\n thread: worker\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n maxSteps: 10\n```\n\nThis also works for named threads in interactive agents, allowing different threads to have different skills.\n\n## Skill Tools\n\nWhen skills are enabled, the LLM has access to these tools:\n\n| Tool | Purpose |\n| -------------------- | --------------------------------------- |\n| `octavus_skill_read` | Read skill documentation (SKILL.md) |\n| `octavus_skill_list` | List available scripts in a skill |\n| `octavus_skill_run` | Execute a pre-built script from a skill |\n| `octavus_code_run` | Execute arbitrary Python/Bash code |\n| `octavus_file_write` | Create files in the sandbox |\n| `octavus_file_read` | Read files from the sandbox |\n\nThe LLM learns about available skills through system prompt injection and can use these tools to interact with skills.\n\n## Example: QR Code Generation\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n agentic: true\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond:\n block: next-message\n```\n\nWhen a user asks \"Create a QR code for octavus.ai\", the LLM will:\n\n1. Recognize the task matches the `qr-code` skill\n2. Call `octavus_skill_read` to learn how to use the skill\n3. Execute code (via `octavus_code_run` or `octavus_skill_run`) to generate the QR code\n4. Save the image to `/output/` in the sandbox\n5. The file is automatically captured and made available for download\n\n## File Output\n\nFiles saved to `/output/` in the sandbox are automatically:\n\n1. **Captured** after code execution\n2. **Uploaded** to S3 storage\n3. **Made available** via presigned URLs\n4. **Included** in the message as file parts\n\nFiles persist across page refreshes and are stored in the session's message history.\n\n## Skill Format\n\nSkills follow the [Agent Skills](https://agentskills.io) open standard:\n\n- `SKILL.md` - Required skill documentation with YAML frontmatter\n- `scripts/` - Optional executable code (Python/Bash)\n- `references/` - Optional documentation loaded as needed\n- `assets/` - Optional files used in outputs (templates, images)\n\n### SKILL.md Format\n\n````yaml\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\nversion: 1.0.0\nlicense: MIT\nauthor: Octavus Team\n---\n\n# QR Code Generator\n\n## Overview\n\nThis skill creates QR codes from text data using Python...\n\n## Quick Start\n\nGenerate a QR code with Python:\n\n```python\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n# ... code to generate QR code ...\n````\n\n## Scripts Reference\n\n### scripts/generate.py\n\nMain script for generating QR codes...\n\n````\n\n## Best Practices\n\n### 1. Clear Descriptions\n\nProvide clear, purpose-driven descriptions:\n\n```yaml\nskills:\n # Good - clear purpose\n qr-code:\n description: Generating QR codes for URLs, contact info, or any text data\n\n # Avoid - vague\n utility:\n description: Does stuff\n````\n\n### 2. When to Use Skills vs Tools\n\n| Use Skills When | Use Tools When |\n| ------------------------ | ---------------------------- |\n| Code execution needed | Simple API calls |\n| File generation | Database queries |\n| Complex calculations | External service integration |\n| Data processing | Authentication required |\n| Provider-agnostic needed | Backend-specific logic |\n\n### 3. Skill Selection\n\nDefine all skills available to this agent in the `skills:` section. Then specify which skills are available for the chat thread in `agent.skills`:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n pdf-processor:\n display: description\n description: Processing PDFs\n\n# Skills available for this chat thread\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis] # Skills available for this thread\n```\n\n### 4. Display Modes\n\nChoose appropriate display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n## Comparison: Skills vs Tools vs Provider Options\n\n| Feature | Octavus Skills | External Tools | Provider Tools/Skills |\n| ------------------ | ----------------- | ------------------- | --------------------- |\n| **Execution** | Isolated sandbox | Your backend | Provider servers |\n| **Provider** | Any (agnostic) | N/A | Provider-specific |\n| **Code Execution** | Yes | No | Yes (provider tools) |\n| **File Output** | Yes | No | Yes (provider skills) |\n| **Implementation** | Skill packages | Your code | Built-in |\n| **Cost** | Sandbox + LLM API | Your infrastructure | Included in API |\n\n## Uploading Custom Skills\n\nYou can upload custom skills to your organization:\n\n1. Create a skill following the [Agent Skills](https://agentskills.io) format\n2. Package it as a `.skill` bundle (ZIP file)\n3. Upload via the platform UI\n4. Reference by slug in your protocol\n\n```yaml\nskills:\n custom-analysis:\n display: description\n description: Custom analysis tool\n\nagent:\n skills: [custom-analysis]\n```\n\n## Sandbox Timeout\n\nThe default sandbox timeout is 5 minutes. You can configure a custom timeout using `sandboxTimeout` in the agent config or on individual `start-thread` blocks:\n\n```yaml\n# Agent-level timeout (applies to main thread)\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes (in milliseconds)\n```\n\n```yaml\n# Thread-level timeout (overrides agent-level for this thread)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour\n```\n\nThread-level `sandboxTimeout` takes priority over agent-level. Maximum: 1 hour (3,600,000 ms).\n\n## Security\n\nSkills run in isolated sandbox environments:\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n\n## Next Steps\n\n- [Agent Config](/docs/protocol/agent-config) \u2014 Configuring skills in agent settings\n- [Provider Options](/docs/protocol/provider-options) \u2014 Anthropic's built-in skills\n- [Skills Advanced Guide](/docs/protocol/skills-advanced) \u2014 Best practices and advanced patterns\n",
582
+ content: "\n# Skills\n\nSkills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are self-contained packages with documentation and scripts that run in secure sandboxes.\n\n## Overview\n\nOctavus Skills provide **provider-agnostic** code execution. They work with any LLM provider (Anthropic, OpenAI, Google) by using explicit tool calls and system prompt injection.\n\n### How Skills Work\n\n1. **Skill Definition**: Skills are defined in the protocol's `skills:` section\n2. **Skill Resolution**: Skills are resolved from available sources (see below)\n3. **Sandbox Execution**: When a skill is used, code runs in an isolated sandbox environment\n4. **File Generation**: Files saved to `/output/` are automatically captured and made available for download\n\n### Skill Sources\n\nSkills come from two sources, visible in the Skills tab of your organization:\n\n| Source | Badge in UI | Visibility | Example |\n| ----------- | ----------- | ------------------------------ | ------------------ |\n| **Octavus** | `Octavus` | Available to all organizations | `qr-code` |\n| **Custom** | None | Private to your organization | `my-company-skill` |\n\nWhen you reference a skill in your protocol, Octavus resolves it from your available skills. If you create a custom skill with the same name as an Octavus skill, your custom skill takes precedence.\n\n## Defining Skills\n\nDefine skills in the protocol's `skills:` section:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n```\n\n### Skill Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------------------------------------- |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` (default: `description`) |\n| `description` | No | Custom description shown to users (overrides skill's built-in description) |\n\n### Display Modes\n\n| Mode | Behavior |\n| ------------- | ------------------------------------------- |\n| `hidden` | Skill usage not shown to users |\n| `name` | Shows skill name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams progress if available |\n\n## Enabling Skills\n\nAfter defining skills in the `skills:` section, specify which skills are available. Skills work in both interactive agents and workers.\n\n### Interactive Agents\n\nReference skills in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account]\n skills: [qr-code]\n agentic: true\n```\n\n### Workers and Named Threads\n\nReference skills per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nsteps:\n Start thread:\n block: start-thread\n thread: worker\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n maxSteps: 10\n```\n\nThis also works for named threads in interactive agents, allowing different threads to have different skills.\n\n## Skill Tools\n\nWhen skills are enabled, the LLM has access to these tools:\n\n| Tool | Purpose | Availability |\n| -------------------- | --------------------------------------- | -------------------- |\n| `octavus_skill_read` | Read skill documentation (SKILL.md) | All skills |\n| `octavus_skill_list` | List available scripts in a skill | All skills |\n| `octavus_skill_run` | Execute a pre-built script from a skill | All skills |\n| `octavus_code_run` | Execute arbitrary Python/Bash code | Standard skills only |\n| `octavus_file_write` | Create files in the sandbox | Standard skills only |\n| `octavus_file_read` | Read files from the sandbox | Standard skills only |\n\nThe LLM learns about available skills through system prompt injection and can use these tools to interact with skills.\n\nSkills that have [secrets](#skill-secrets) configured run in **secure mode**, where only `octavus_skill_read`, `octavus_skill_list`, and `octavus_skill_run` are available. See [Skill Secrets](#skill-secrets) below.\n\n## Example: QR Code Generation\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n agentic: true\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond:\n block: next-message\n```\n\nWhen a user asks \"Create a QR code for octavus.ai\", the LLM will:\n\n1. Recognize the task matches the `qr-code` skill\n2. Call `octavus_skill_read` to learn how to use the skill\n3. Execute code (via `octavus_code_run` or `octavus_skill_run`) to generate the QR code\n4. Save the image to `/output/` in the sandbox\n5. The file is automatically captured and made available for download\n\n## File Output\n\nFiles saved to `/output/` in the sandbox are automatically:\n\n1. **Captured** after code execution\n2. **Uploaded** to S3 storage\n3. **Made available** via presigned URLs\n4. **Included** in the message as file parts\n\nFiles persist across page refreshes and are stored in the session's message history.\n\n## Skill Format\n\nSkills follow the [Agent Skills](https://agentskills.io) open standard:\n\n- `SKILL.md` - Required skill documentation with YAML frontmatter\n- `scripts/` - Optional executable code (Python/Bash)\n- `references/` - Optional documentation loaded as needed\n- `assets/` - Optional files used in outputs (templates, images)\n\n### SKILL.md Format\n\n````yaml\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\nversion: 1.0.0\nlicense: MIT\nauthor: Octavus Team\n---\n\n# QR Code Generator\n\n## Overview\n\nThis skill creates QR codes from text data using Python...\n\n## Quick Start\n\nGenerate a QR code with Python:\n\n```python\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n# ... code to generate QR code ...\n````\n\n## Scripts Reference\n\n### scripts/generate.py\n\nMain script for generating QR codes...\n\n````\n\n### Frontmatter Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------ |\n| `name` | Yes | Skill slug (lowercase, hyphens) |\n| `description` | Yes | What the skill does (shown to the LLM) |\n| `version` | No | Semantic version string |\n| `license` | No | License identifier |\n| `author` | No | Skill author |\n| `secrets` | No | Array of secret declarations (enables secure mode) |\n\n## Best Practices\n\n### 1. Clear Descriptions\n\nProvide clear, purpose-driven descriptions:\n\n```yaml\nskills:\n # Good - clear purpose\n qr-code:\n description: Generating QR codes for URLs, contact info, or any text data\n\n # Avoid - vague\n utility:\n description: Does stuff\n````\n\n### 2. When to Use Skills vs Tools\n\n| Use Skills When | Use Tools When |\n| ------------------------ | ---------------------------- |\n| Code execution needed | Simple API calls |\n| File generation | Database queries |\n| Complex calculations | External service integration |\n| Data processing | Authentication required |\n| Provider-agnostic needed | Backend-specific logic |\n\n### 3. Skill Selection\n\nDefine all skills available to this agent in the `skills:` section. Then specify which skills are available for the chat thread in `agent.skills`:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n pdf-processor:\n display: description\n description: Processing PDFs\n\n# Skills available for this chat thread\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis] # Skills available for this thread\n```\n\n### 4. Display Modes\n\nChoose appropriate display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n## Comparison: Skills vs Tools vs Provider Options\n\n| Feature | Octavus Skills | External Tools | Provider Tools/Skills |\n| ------------------ | ----------------- | ------------------- | --------------------- |\n| **Execution** | Isolated sandbox | Your backend | Provider servers |\n| **Provider** | Any (agnostic) | N/A | Provider-specific |\n| **Code Execution** | Yes | No | Yes (provider tools) |\n| **File Output** | Yes | No | Yes (provider skills) |\n| **Implementation** | Skill packages | Your code | Built-in |\n| **Cost** | Sandbox + LLM API | Your infrastructure | Included in API |\n\n## Uploading Custom Skills\n\nYou can upload custom skills to your organization:\n\n1. Create a skill following the [Agent Skills](https://agentskills.io) format\n2. Package it as a `.skill` bundle (ZIP file)\n3. Upload via the platform UI\n4. Reference by slug in your protocol\n\n```yaml\nskills:\n custom-analysis:\n display: description\n description: Custom analysis tool\n\nagent:\n skills: [custom-analysis]\n```\n\n## Sandbox Timeout\n\nThe default sandbox timeout is 5 minutes. You can configure a custom timeout using `sandboxTimeout` in the agent config or on individual `start-thread` blocks:\n\n```yaml\n# Agent-level timeout (applies to main thread)\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes (in milliseconds)\n```\n\n```yaml\n# Thread-level timeout (overrides agent-level for this thread)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour\n```\n\nThread-level `sandboxTimeout` takes priority over agent-level. Maximum: 1 hour (3,600,000 ms).\n\n## Skill Secrets\n\nSkills can declare secrets they need to function. When an organization configures those secrets, the skill runs in **secure mode** with additional isolation.\n\n### Declaring Secrets\n\nAdd a `secrets` array to your SKILL.md frontmatter:\n\n```yaml\n---\nname: github\ndescription: >\n Run GitHub CLI (gh) commands to manage repos, issues, PRs, and more.\nsecrets:\n - name: GITHUB_TOKEN\n description: GitHub personal access token with repo access\n required: true\n - name: GITHUB_ORG\n description: Default GitHub organization\n required: false\n---\n```\n\nEach secret declaration has:\n\n| Field | Required | Description |\n| ------------- | -------- | ----------------------------------------------------------- |\n| `name` | Yes | Environment variable name (uppercase, e.g., `GITHUB_TOKEN`) |\n| `description` | No | Explains what this secret is for (shown in the UI) |\n| `required` | No | Whether the secret is required (defaults to `true`) |\n\nSecret names must match the pattern `^[A-Z_][A-Z0-9_]*$` (uppercase letters, digits, and underscores).\n\n### Configuring Secrets\n\nOrganization admins configure secret values through the skill editor in the platform UI. Each organization maintains its own independent set of secrets for each skill.\n\nSecrets are encrypted at rest and only decrypted at execution time.\n\n### Secure Mode\n\nWhen a skill has secrets configured for the organization, it automatically runs in **secure mode**:\n\n- The skill gets its own **isolated sandbox** (separate from other skills)\n- Secrets are injected as **environment variables** available to all scripts\n- Only `octavus_skill_read`, `octavus_skill_list`, and `octavus_skill_run` are available \u2014 `octavus_code_run`, `octavus_file_write`, and `octavus_file_read` are blocked\n- Scripts receive input as **JSON via stdin** (using the `input` parameter on `octavus_skill_run`) instead of CLI args\n- All output (stdout/stderr) is **automatically redacted** for secret values before being returned to the LLM\n\n### Writing Scripts for Secure Skills\n\nScripts in secure skills read input from stdin as JSON and access secrets from environment variables:\n\n```python\nimport json\nimport os\nimport sys\n\ninput_data = json.load(sys.stdin)\ntoken = os.environ.get('GITHUB_TOKEN')\n\n# Use the token and input_data to perform the task\n```\n\nFor standard skills (without secrets), scripts receive input as CLI arguments. For secure skills, always use stdin JSON.\n\n## Security\n\nSkills run in isolated sandbox environments:\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n- **Secret redaction** \u2014 output from secure skills is automatically scanned for secret values\n\n## Next Steps\n\n- [Agent Config](/docs/protocol/agent-config) \u2014 Configuring skills in agent settings\n- [Provider Options](/docs/protocol/provider-options) \u2014 Anthropic's built-in skills\n- [Skills Advanced Guide](/docs/protocol/skills-advanced) \u2014 Best practices and advanced patterns\n",
581
583
  excerpt: "Skills Skills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are...",
582
584
  order: 5
583
585
  },
@@ -613,7 +615,7 @@ See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list
613
615
  section: "protocol",
614
616
  title: "Skills Advanced Guide",
615
617
  description: "Best practices and advanced patterns for using Octavus skills.",
616
- content: "\n# Skills Advanced Guide\n\nThis guide covers advanced patterns and best practices for using Octavus skills in your agents.\n\n## When to Use Skills\n\nSkills are ideal for:\n\n- **Code execution** - Running Python/Bash scripts\n- **File generation** - Creating images, PDFs, reports\n- **Data processing** - Analyzing, transforming, or visualizing data\n- **Provider-agnostic needs** - Features that should work with any LLM\n\nUse external tools instead when:\n\n- **Simple API calls** - Database queries, external services\n- **Authentication required** - Accessing user-specific resources\n- **Backend integration** - Tight coupling with your infrastructure\n\n## Skill Selection Strategy\n\n### Defining Available Skills\n\nDefine all skills in the `skills:` section, then reference which skills are available where they're used:\n\n**Interactive agents** \u2014 reference in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n pdf-processor:\n display: description\n description: Processing PDFs\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\n**Workers and named threads** \u2014 reference per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n\nsteps:\n Start analysis:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis]\n maxSteps: 10\n```\n\n### Match Skills to Use Cases\n\nDifferent threads can have different skills. Define all skills at the protocol level, then scope them to each thread:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n visualization:\n display: description\n description: Creating charts and visualizations\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\nFor a data analysis thread, you would specify `[data-analysis, visualization]` in `agent.skills` or in a `start-thread` block's `skills` field.\n\n## Display Mode Strategy\n\nChoose display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n### Guidelines\n\n- **`hidden`**: Background work that doesn't need user awareness\n- **`description`**: User-facing operations (default)\n- **`name`**: Quick operations where name is sufficient\n- **`stream`**: Long-running operations where progress matters\n\n## System Prompt Integration\n\nSkills are automatically injected into the system prompt. The LLM learns:\n\n1. **Available skills** - List of enabled skills with descriptions\n2. **How to use skills** - Instructions for using skill tools\n3. **Tool reference** - Available skill tools (`octavus_skill_read`, `octavus_code_run`, etc.)\n\nYou don't need to manually document skills in your system prompt. However, you can guide the LLM:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a helpful assistant that can generate QR codes.\n\n## When to Generate QR Codes\n\nGenerate QR codes when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Share WiFi credentials\n- Create scannable data\n\nUse the qr-code skill for all QR code generation tasks.\n```\n\n## Error Handling\n\nSkills handle errors gracefully:\n\n```yaml\n# Skill execution errors are returned to the LLM\n# The LLM can retry or explain the error to the user\n```\n\nCommon error scenarios:\n\n1. **Invalid skill slug** - Skill not found in organization\n2. **Code execution errors** - Syntax errors, runtime exceptions\n3. **Missing dependencies** - Required packages not installed\n4. **File I/O errors** - Permission issues, invalid paths\n\nThe LLM receives error messages and can:\n\n- Retry with corrected code\n- Explain errors to users\n- Suggest alternatives\n\n## File Output Patterns\n\n### Single File Output\n\n```python\n# Save single file to /output/\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\nqr = qrcode.QRCode()\nqr.add_data('https://example.com')\nimg = qr.make_image()\nimg.save(f'{output_dir}/qrcode.png')\n```\n\n### Multiple Files\n\n```python\n# Save multiple files\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Generate multiple outputs\nfor i in range(3):\n filename = f'{output_dir}/output_{i}.png'\n # ... generate file ...\n```\n\n### Structured Output\n\n```python\n# Save structured data + files\nimport json\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Save metadata\nmetadata = {\n 'files': ['chart.png', 'data.csv'],\n 'summary': 'Analysis complete'\n}\nwith open(f'{output_dir}/metadata.json', 'w') as f:\n json.dump(metadata, f)\n\n# Save actual files\n# ... generate chart.png and data.csv ...\n```\n\n## Performance Considerations\n\n### Lazy Initialization\n\nSandboxes are created only when a skill tool is first called:\n\n```yaml\nagent:\n skills: [qr-code] # Sandbox created on first skill tool call\n```\n\nThis means:\n\n- No cost if skills aren't used\n- Fast startup (no sandbox creation delay)\n- Each `next-message` execution gets its own sandbox with only the skills it needs\n\n### Timeout Limits\n\nSandboxes default to a 5-minute timeout. Configure `sandboxTimeout` on the agent config or per thread:\n\n```yaml\n# Agent-level\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes\n```\n\n```yaml\n# Thread-level (overrides agent-level)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour for long-running analysis\n```\n\nThread-level `sandboxTimeout` takes priority. Maximum: 1 hour (3,600,000 ms).\n\n### Sandbox Lifecycle\n\nEach `next-message` execution gets its own sandbox:\n\n- **Scoped** - Only contains the skills available to that thread\n- **Isolated** - Interactive agents and workers don't share sandboxes\n- **Resilient** - If a sandbox expires, it's transparently recreated\n- **Cleaned up** - Sandbox destroyed when the LLM call completes\n\n## Combining Skills with Tools\n\nSkills and tools can work together:\n\n```yaml\ntools:\n get-user-data:\n description: Fetch user data from database\n parameters:\n userId: { type: string }\n\nskills:\n data-analysis:\n display: description\n description: Analyzing data\n\nagent:\n tools: [get-user-data]\n skills: [data-analysis]\n agentic: true\n\nhandlers:\n analyze-user:\n Get user data:\n block: tool-call\n tool: get-user-data\n input:\n userId: USER_ID\n output: USER_DATA\n\n Analyze:\n block: next-message\n # LLM can use data-analysis skill with USER_DATA\n```\n\nPattern:\n\n1. Fetch data via tool (from your backend)\n2. LLM uses skill to analyze/process the data\n3. Generate outputs (files, reports)\n\n## Skill Development Tips\n\n### Writing SKILL.md\n\nFocus on **when** and **how** to use the skill:\n\n```markdown\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\n---\n\n# QR Code Generator\n\n## When to Use\n\nUse this skill when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Create scannable data\n\n## Quick Start\n\n[Clear examples of how to use the skill]\n```\n\n### Script Organization\n\nOrganize scripts logically:\n\n```\nskill-name/\n\u251C\u2500\u2500 SKILL.md\n\u2514\u2500\u2500 scripts/\n \u251C\u2500\u2500 generate.py # Main script\n \u251C\u2500\u2500 utils.py # Helper functions\n \u2514\u2500\u2500 requirements.txt # Dependencies\n```\n\n### Error Messages\n\nProvide helpful error messages:\n\n```python\ntry:\n # ... code ...\nexcept ValueError as e:\n print(f\"Error: Invalid input - {e}\")\n sys.exit(1)\n```\n\nThe LLM sees these errors and can retry or explain to users.\n\n## Security Considerations\n\n### Sandbox Isolation\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n\n### Input Validation\n\nSkills should validate inputs:\n\n```python\nimport sys\n\nif not data:\n print(\"Error: Data is required\")\n sys.exit(1)\n\nif len(data) > 1000:\n print(\"Error: Data too long (max 1000 characters)\")\n sys.exit(1)\n```\n\n### Resource Limits\n\nBe aware of:\n\n- **File size limits** - Large files may fail to upload\n- **Execution time** - Sandbox timeout (5-minute default, 1-hour maximum)\n- **Memory limits** - Sandbox environment constraints\n\n## Debugging Skills\n\n### Check Skill Documentation\n\nThe LLM can read skill docs:\n\n```python\n# LLM calls octavus_skill_read to see skill instructions\n```\n\n### Test Locally\n\nTest skills before uploading:\n\n```bash\n# Test skill locally\npython scripts/generate.py --data \"test\"\n```\n\n### Monitor Execution\n\nCheck execution logs in the platform debug view:\n\n- Tool calls and arguments\n- Code execution results\n- File outputs\n- Error messages\n\n## Common Patterns\n\n### Pattern 1: Generate and Return\n\n```yaml\n# User asks for QR code\n# LLM generates QR code\n# File automatically available for download\n```\n\n### Pattern 2: Analyze and Report\n\n```yaml\n# User provides data\n# LLM analyzes with skill\n# Generates report file\n# Returns summary + file link\n```\n\n### Pattern 3: Transform and Save\n\n```yaml\n# User uploads file (via tool)\n# LLM processes with skill\n# Generates transformed file\n# Returns new file link\n```\n\n## Best Practices Summary\n\n1. **Enable only needed skills** - Don't overwhelm the LLM\n2. **Choose appropriate display modes** - Match user experience needs\n3. **Write clear skill descriptions** - Help LLM understand when to use\n4. **Handle errors gracefully** - Provide helpful error messages\n5. **Test skills locally** - Verify before uploading\n6. **Monitor execution** - Check logs for issues\n7. **Combine with tools** - Use tools for data, skills for processing\n8. **Consider performance** - Be aware of timeouts and limits\n\n## Next Steps\n\n- [Skills](/docs/protocol/skills) - Basic skills documentation\n- [Agent Config](/docs/protocol/agent-config) - Configuring skills\n- [Tools](/docs/protocol/tools) - External tools integration\n",
618
+ content: "\n# Skills Advanced Guide\n\nThis guide covers advanced patterns and best practices for using Octavus skills in your agents.\n\n## When to Use Skills\n\nSkills are ideal for:\n\n- **Code execution** - Running Python/Bash scripts\n- **File generation** - Creating images, PDFs, reports\n- **Data processing** - Analyzing, transforming, or visualizing data\n- **Provider-agnostic needs** - Features that should work with any LLM\n\nUse external tools instead when:\n\n- **Simple API calls** - Database queries, external services\n- **Authentication required** - Accessing user-specific resources\n- **Backend integration** - Tight coupling with your infrastructure\n\n## Skill Selection Strategy\n\n### Defining Available Skills\n\nDefine all skills in the `skills:` section, then reference which skills are available where they're used:\n\n**Interactive agents** \u2014 reference in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n pdf-processor:\n display: description\n description: Processing PDFs\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\n**Workers and named threads** \u2014 reference per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n\nsteps:\n Start analysis:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis]\n maxSteps: 10\n```\n\n### Match Skills to Use Cases\n\nDifferent threads can have different skills. Define all skills at the protocol level, then scope them to each thread:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n visualization:\n display: description\n description: Creating charts and visualizations\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\nFor a data analysis thread, you would specify `[data-analysis, visualization]` in `agent.skills` or in a `start-thread` block's `skills` field.\n\n## Display Mode Strategy\n\nChoose display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n### Guidelines\n\n- **`hidden`**: Background work that doesn't need user awareness\n- **`description`**: User-facing operations (default)\n- **`name`**: Quick operations where name is sufficient\n- **`stream`**: Long-running operations where progress matters\n\n## System Prompt Integration\n\nSkills are automatically injected into the system prompt. The LLM learns:\n\n1. **Available skills** - List of enabled skills with descriptions\n2. **How to use skills** - Instructions for using skill tools\n3. **Tool reference** - Available skill tools (`octavus_skill_read`, `octavus_code_run`, etc.)\n\nYou don't need to manually document skills in your system prompt. However, you can guide the LLM:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a helpful assistant that can generate QR codes.\n\n## When to Generate QR Codes\n\nGenerate QR codes when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Share WiFi credentials\n- Create scannable data\n\nUse the qr-code skill for all QR code generation tasks.\n```\n\n## Error Handling\n\nSkills handle errors gracefully:\n\n```yaml\n# Skill execution errors are returned to the LLM\n# The LLM can retry or explain the error to the user\n```\n\nCommon error scenarios:\n\n1. **Invalid skill slug** - Skill not found in organization\n2. **Code execution errors** - Syntax errors, runtime exceptions\n3. **Missing dependencies** - Required packages not installed\n4. **File I/O errors** - Permission issues, invalid paths\n\nThe LLM receives error messages and can:\n\n- Retry with corrected code\n- Explain errors to users\n- Suggest alternatives\n\n## File Output Patterns\n\n### Single File Output\n\n```python\n# Save single file to /output/\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\nqr = qrcode.QRCode()\nqr.add_data('https://example.com')\nimg = qr.make_image()\nimg.save(f'{output_dir}/qrcode.png')\n```\n\n### Multiple Files\n\n```python\n# Save multiple files\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Generate multiple outputs\nfor i in range(3):\n filename = f'{output_dir}/output_{i}.png'\n # ... generate file ...\n```\n\n### Structured Output\n\n```python\n# Save structured data + files\nimport json\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Save metadata\nmetadata = {\n 'files': ['chart.png', 'data.csv'],\n 'summary': 'Analysis complete'\n}\nwith open(f'{output_dir}/metadata.json', 'w') as f:\n json.dump(metadata, f)\n\n# Save actual files\n# ... generate chart.png and data.csv ...\n```\n\n## Performance Considerations\n\n### Lazy Initialization\n\nSandboxes are created only when a skill tool is first called:\n\n```yaml\nagent:\n skills: [qr-code] # Sandbox created on first skill tool call\n```\n\nThis means:\n\n- No cost if skills aren't used\n- Fast startup (no sandbox creation delay)\n- Each `next-message` execution gets its own sandbox with only the skills it needs\n\n### Timeout Limits\n\nSandboxes default to a 5-minute timeout. Configure `sandboxTimeout` on the agent config or per thread:\n\n```yaml\n# Agent-level\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes\n```\n\n```yaml\n# Thread-level (overrides agent-level)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour for long-running analysis\n```\n\nThread-level `sandboxTimeout` takes priority. Maximum: 1 hour (3,600,000 ms).\n\n### Sandbox Lifecycle\n\nEach `next-message` execution gets its own sandbox:\n\n- **Scoped** - Only contains the skills available to that thread\n- **Isolated** - Interactive agents and workers don't share sandboxes\n- **Resilient** - If a sandbox expires, it's transparently recreated\n- **Cleaned up** - Sandbox destroyed when the LLM call completes\n\n## Combining Skills with Tools\n\nSkills and tools can work together:\n\n```yaml\ntools:\n get-user-data:\n description: Fetch user data from database\n parameters:\n userId: { type: string }\n\nskills:\n data-analysis:\n display: description\n description: Analyzing data\n\nagent:\n tools: [get-user-data]\n skills: [data-analysis]\n agentic: true\n\nhandlers:\n analyze-user:\n Get user data:\n block: tool-call\n tool: get-user-data\n input:\n userId: USER_ID\n output: USER_DATA\n\n Analyze:\n block: next-message\n # LLM can use data-analysis skill with USER_DATA\n```\n\nPattern:\n\n1. Fetch data via tool (from your backend)\n2. LLM uses skill to analyze/process the data\n3. Generate outputs (files, reports)\n\n## Secure Skills\n\nWhen a skill declares secrets and an organization configures them, the skill runs in secure mode with its own isolated sandbox.\n\n### Standard vs Secure Skills\n\n| Aspect | Standard Skills | Secure Skills |\n| ------------------- | --------------------------------- | --------------------------------------------------- |\n| **Sandbox** | Shared with other standard skills | Isolated (one per skill) |\n| **Available tools** | All 6 skill tools | `skill_read`, `skill_list`, `skill_run` only |\n| **Script input** | CLI arguments via `args` | JSON via stdin (use `input` parameter) |\n| **Environment** | No secrets | Secrets as env vars |\n| **Output** | Raw stdout/stderr | Redacted (secret values replaced with `[REDACTED]`) |\n\n### Writing Scripts for Secure Skills\n\nSecure skill scripts receive structured input via stdin (JSON) and access secrets from environment variables:\n\n```python\n#!/usr/bin/env python3\nimport json\nimport os\nimport sys\nimport subprocess\n\ninput_data = json.load(sys.stdin)\ntoken = os.environ[\"GITHUB_TOKEN\"]\n\nrepo = input_data.get(\"repo\", \"\")\nresult = subprocess.run(\n [\"gh\", \"repo\", \"view\", repo, \"--json\", \"name,description\"],\n capture_output=True, text=True,\n env={**os.environ, \"GH_TOKEN\": token}\n)\n\nprint(result.stdout)\n```\n\nKey patterns:\n\n- **Read stdin**: `json.load(sys.stdin)` to get the `input` object from the `octavus_skill_run` call\n- **Access secrets**: `os.environ[\"SECRET_NAME\"]` \u2014 secrets are injected as env vars\n- **Print output**: Write results to stdout \u2014 the LLM sees the (redacted) stdout\n- **Error handling**: Write errors to stderr and exit with non-zero code\n\n### Declaring Secrets in SKILL.md\n\n```yaml\n---\nname: github\ndescription: >\n Run GitHub CLI (gh) commands to manage repos, issues, PRs, and more.\nsecrets:\n - name: GITHUB_TOKEN\n description: GitHub personal access token with repo access\n required: true\n - name: GITHUB_ORG\n description: Default GitHub organization\n required: false\n---\n```\n\n### Testing Secure Skills Locally\n\nYou can test scripts locally by piping JSON to stdin:\n\n```bash\necho '{\"repo\": \"octavus-ai/agent-sdk\"}' | GITHUB_TOKEN=ghp_xxx python scripts/list-issues.py\n```\n\n## Skill Development Tips\n\n### Writing SKILL.md\n\nFocus on **when** and **how** to use the skill:\n\n```markdown\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\n---\n\n# QR Code Generator\n\n## When to Use\n\nUse this skill when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Create scannable data\n\n## Quick Start\n\n[Clear examples of how to use the skill]\n```\n\n### Script Organization\n\nOrganize scripts logically:\n\n```\nskill-name/\n\u251C\u2500\u2500 SKILL.md\n\u2514\u2500\u2500 scripts/\n \u251C\u2500\u2500 generate.py # Main script\n \u251C\u2500\u2500 utils.py # Helper functions\n \u2514\u2500\u2500 requirements.txt # Dependencies\n```\n\n### Error Messages\n\nProvide helpful error messages:\n\n```python\ntry:\n # ... code ...\nexcept ValueError as e:\n print(f\"Error: Invalid input - {e}\")\n sys.exit(1)\n```\n\nThe LLM sees these errors and can retry or explain to users.\n\n## Security Considerations\n\n### Sandbox Isolation\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n\n### Secret Protection\n\nFor skills with configured secrets:\n\n- **Isolated sandbox** \u2014 each secure skill gets its own sandbox, preventing cross-skill secret leakage\n- **No arbitrary code** \u2014 `octavus_code_run`, `octavus_file_write`, and `octavus_file_read` are blocked for secure skills, so only pre-built scripts can execute\n- **Output redaction** \u2014 all stdout and stderr are scanned for secret values before being returned to the LLM\n- **Encrypted at rest** \u2014 secrets are encrypted using AES-256-GCM and only decrypted at execution time\n\n### Input Validation\n\nSkills should validate inputs:\n\n```python\nimport sys\n\nif not data:\n print(\"Error: Data is required\")\n sys.exit(1)\n\nif len(data) > 1000:\n print(\"Error: Data too long (max 1000 characters)\")\n sys.exit(1)\n```\n\n### Resource Limits\n\nBe aware of:\n\n- **File size limits** - Large files may fail to upload\n- **Execution time** - Sandbox timeout (5-minute default, 1-hour maximum)\n- **Memory limits** - Sandbox environment constraints\n\n## Debugging Skills\n\n### Check Skill Documentation\n\nThe LLM can read skill docs:\n\n```python\n# LLM calls octavus_skill_read to see skill instructions\n```\n\n### Test Locally\n\nTest skills before uploading:\n\n```bash\n# Test skill locally\npython scripts/generate.py --data \"test\"\n```\n\n### Monitor Execution\n\nCheck execution logs in the platform debug view:\n\n- Tool calls and arguments\n- Code execution results\n- File outputs\n- Error messages\n\n## Common Patterns\n\n### Pattern 1: Generate and Return\n\n```yaml\n# User asks for QR code\n# LLM generates QR code\n# File automatically available for download\n```\n\n### Pattern 2: Analyze and Report\n\n```yaml\n# User provides data\n# LLM analyzes with skill\n# Generates report file\n# Returns summary + file link\n```\n\n### Pattern 3: Transform and Save\n\n```yaml\n# User uploads file (via tool)\n# LLM processes with skill\n# Generates transformed file\n# Returns new file link\n```\n\n## Best Practices Summary\n\n1. **Enable only needed skills** \u2014 Don't overwhelm the LLM\n2. **Choose appropriate display modes** \u2014 Match user experience needs\n3. **Write clear skill descriptions** \u2014 Help LLM understand when to use\n4. **Handle errors gracefully** \u2014 Provide helpful error messages\n5. **Test skills locally** \u2014 Verify before uploading\n6. **Monitor execution** \u2014 Check logs for issues\n7. **Combine with tools** \u2014 Use tools for data, skills for processing\n8. **Consider performance** \u2014 Be aware of timeouts and limits\n9. **Use secrets for credentials** \u2014 Declare secrets in frontmatter instead of hardcoding tokens\n10. **Design scripts for stdin input** \u2014 Secure skills receive JSON via stdin, so plan for both input methods if the skill might be used in either mode\n\n## Next Steps\n\n- [Skills](/docs/protocol/skills) - Basic skills documentation\n- [Agent Config](/docs/protocol/agent-config) - Configuring skills\n- [Tools](/docs/protocol/tools) - External tools integration\n",
617
619
  excerpt: "Skills Advanced Guide This guide covers advanced patterns and best practices for using Octavus skills in your agents. When to Use Skills Skills are ideal for: - Code execution - Running Python/Bash...",
618
620
  order: 9
619
621
  },
@@ -685,7 +687,7 @@ See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list
685
687
  section: "examples",
686
688
  title: "Next.js Chat",
687
689
  description: "Building a chat interface with Next.js and HTTP transport.",
688
- content: "\n# Next.js Chat Example\n\nThis example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications.\n\n## What You're Building\n\nA chat interface that:\n\n- Creates sessions server-side\n- Streams AI responses in real-time\n- Handles tool calls on your server\n- Shows typing status during streaming\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] -->|\"POST /api/chat\"| NextJS[\"Next.js API<br/>Routes\"]\n NextJS -->|\"SSE\"| Browser\n NextJS --> Platform[\"Octavus Platform\"]\n```\n\n## Prerequisites\n\n- Next.js 14+ with App Router\n- Octavus account with API key\n- An agent configured in Octavus\n\n## Step 1: Install Dependencies\n\n```bash\nnpm install @octavus/server-sdk @octavus/react\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env.local\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\n```\n\n## Step 3: Create the Octavus Client\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n## Step 4: Create Upload URLs Endpoint (Optional)\n\nIf your agent supports file uploads (images, documents), create an endpoint to get presigned URLs:\n\n```typescript\n// app/api/upload-urls/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, files } = await request.json();\n\n // Get presigned URLs from Octavus\n const result = await octavus.files.getUploadUrls(sessionId, files);\n\n return NextResponse.json(result);\n}\n```\n\n## Step 5: Create Session Endpoint\n\nSessions hold conversation state. Create one when the user opens the chat:\n\n```typescript\n// app/api/sessions/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { agentId, input } = await request.json();\n\n // Create a new session with initial input variables\n const sessionId = await octavus.agentSessions.create(agentId, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n**Protocol Note:** The `input` object contains variables defined in your agent's protocol. For example, if your agent has `COMPANY_NAME` as an input variable:\n\n```typescript\nconst sessionId = await octavus.agentSessions.create(agentId, {\n COMPANY_NAME: 'Acme Corp',\n USER_ID: user.id,\n});\n```\n\n## Step 6: Create Trigger Endpoint\n\nTriggers execute agent actions. The `user-message` trigger is the most common:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to the session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Server-side tool handlers run on YOUR server, not Octavus\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n const user = await db.users.findUnique({ where: { id: userId } });\n return {\n name: user.name,\n email: user.email,\n plan: user.plan,\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await db.tickets.create({\n data: {\n summary: args.summary as string,\n priority: args.priority as string,\n },\n });\n return {\n ticketId: ticket.id,\n estimatedResponse: '24 hours',\n };\n },\n\n // Tools without handlers here are forwarded to the client\n // See Client Tools docs for handling on frontend\n },\n });\n\n // execute() handles both triggers and client tool continuations\n const events = session.execute(payload, { signal: request.signal });\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n**Protocol Note:** Tool names and arguments are defined in your agent's protocol YAML. The tool handlers here must match those definitions. Tools without server handlers are forwarded to the client.\n\n## Step 7: Build the Chat Component\n\n```tsx\n// components/Chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create transport - memoized on sessionId\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Send triggers the 'user-message' action\n // The third argument adds the user message to the UI\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div key={msg.id} className={msg.role === 'user' ? 'text-right' : 'text-left'}>\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n if (part.type === 'tool-call') {\n return (\n <div key={i} className=\"text-sm opacity-70\">\n Using {part.toolName}...\n </div>\n );\n }\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n <button\n type=\"submit\"\n disabled={status === 'streaming'}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\"\n >\n {status === 'streaming' ? 'Sending...' : 'Send'}\n </button>\n </div>\n </form>\n </div>\n );\n}\n```\n\n## Step 8: Create the Page\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nconst AGENT_ID = 'your-agent-id'; // From Octavus dashboard\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n // Create session on mount\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: AGENT_ID,\n input: {\n COMPANY_NAME: 'Acme Corp',\n },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <div className=\"p-8\">Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Protocol Integration\n\nYour agent's protocol defines the triggers and tools. Here's how the code maps to protocol:\n\n### Triggers\n\n```yaml\n# In your agent's protocol.yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\nThe `send()` call maps directly:\n\n```typescript\nawait send(\n 'user-message', // trigger name\n { USER_MESSAGE: message }, // trigger inputs\n { userMessage: { content: message } },\n);\n```\n\n### Tools\n\n```yaml\n# In your agent's protocol.yaml\ntools:\n get-user-account:\n description: Fetch user account details\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\nTool handlers receive the parameters as `args`:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n // ...\n}\n```\n\n## Next Steps\n\n- [Protocol Overview](/docs/protocol/overview) \u2014 Define agent behavior\n- [Messages](/docs/client-sdk/messages) \u2014 Rich message rendering\n- [Streaming](/docs/client-sdk/streaming) \u2014 Advanced streaming UI\n",
690
+ content: "\n# Next.js Chat Example\n\nThis example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications.\n\n## What You're Building\n\nA chat interface that:\n\n- Creates sessions server-side\n- Streams AI responses in real-time\n- Handles tool calls on your server\n- Shows typing status during streaming\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] -->|\"POST /api/chat\"| NextJS[\"Next.js API<br/>Routes\"]\n NextJS -->|\"SSE\"| Browser\n NextJS --> Platform[\"Octavus Platform\"]\n```\n\n## Prerequisites\n\n- Next.js 14+ with App Router\n- Octavus account with API key\n- An agent configured in Octavus\n\n## Step 1: Install Dependencies\n\n```bash\nnpm install @octavus/server-sdk @octavus/react\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env.local\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\n```\n\n## Step 3: Create the Octavus Client\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n## Step 4: Create Upload URLs Endpoint (Optional)\n\nIf your agent supports file uploads (images, documents), create an endpoint to get presigned URLs:\n\n```typescript\n// app/api/upload-urls/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, files } = await request.json();\n\n // Get presigned URLs from Octavus\n const result = await octavus.files.getUploadUrls(sessionId, files);\n\n return NextResponse.json(result);\n}\n```\n\n## Step 5: Create Session Endpoint\n\nSessions hold conversation state. Create one when the user opens the chat:\n\n```typescript\n// app/api/sessions/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { agentId, input } = await request.json();\n\n // Create a new session with initial input variables\n const sessionId = await octavus.agentSessions.create(agentId, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n**Protocol Note:** The `input` object contains variables defined in your agent's protocol. For example, if your agent has `COMPANY_NAME` as an input variable:\n\n```typescript\nconst sessionId = await octavus.agentSessions.create(agentId, {\n COMPANY_NAME: 'Acme Corp',\n USER_ID: user.id,\n});\n```\n\n## Step 6: Create Trigger Endpoint\n\nTriggers execute agent actions. The `user-message` trigger is the most common:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to the session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Server-side tool handlers run on YOUR server, not Octavus\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n const user = await db.users.findUnique({ where: { id: userId } });\n return {\n name: user.name,\n email: user.email,\n plan: user.plan,\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await db.tickets.create({\n data: {\n summary: args.summary as string,\n priority: args.priority as string,\n },\n });\n return {\n ticketId: ticket.id,\n estimatedResponse: '24 hours',\n };\n },\n\n // Tools without handlers here are forwarded to the client\n // See Client Tools docs for handling on frontend\n },\n });\n\n // execute() handles both triggers and client tool continuations\n const events = session.execute(payload, { signal: request.signal });\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n });\n}\n```\n\n**Protocol Note:** Tool names and arguments are defined in your agent's protocol YAML. The tool handlers here must match those definitions. Tools without server handlers are forwarded to the client.\n\n## Step 7: Build the Chat Component\n\n```tsx\n// components/Chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create transport - memoized on sessionId\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Send triggers the 'user-message' action\n // The third argument adds the user message to the UI\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div key={msg.id} className={msg.role === 'user' ? 'text-right' : 'text-left'}>\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n if (part.type === 'tool-call') {\n return (\n <div key={i} className=\"text-sm opacity-70\">\n Using {part.toolName}...\n </div>\n );\n }\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n <button\n type=\"submit\"\n disabled={status === 'streaming'}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\"\n >\n {status === 'streaming' ? 'Sending...' : 'Send'}\n </button>\n </div>\n </form>\n </div>\n );\n}\n```\n\n## Step 8: Create the Page\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nconst AGENT_ID = 'your-agent-id'; // From Octavus dashboard\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n // Create session on mount\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: AGENT_ID,\n input: {\n COMPANY_NAME: 'Acme Corp',\n },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <div className=\"p-8\">Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Protocol Integration\n\nYour agent's protocol defines the triggers and tools. Here's how the code maps to protocol:\n\n### Triggers\n\n```yaml\n# In your agent's protocol.yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\nThe `send()` call maps directly:\n\n```typescript\nawait send(\n 'user-message', // trigger name\n { USER_MESSAGE: message }, // trigger inputs\n { userMessage: { content: message } },\n);\n```\n\n### Tools\n\n```yaml\n# In your agent's protocol.yaml\ntools:\n get-user-account:\n description: Fetch user account details\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\nTool handlers receive the parameters as `args`:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n // ...\n}\n```\n\n## Next Steps\n\n- [Protocol Overview](/docs/protocol/overview) \u2014 Define agent behavior\n- [Messages](/docs/client-sdk/messages) \u2014 Rich message rendering\n- [Streaming](/docs/client-sdk/streaming) \u2014 Advanced streaming UI\n",
689
691
  excerpt: "Next.js Chat Example This example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications. What You're Building A...",
690
692
  order: 2
691
693
  },
@@ -731,7 +733,7 @@ var sections_default = [
731
733
  section: "getting-started",
732
734
  title: "Quick Start",
733
735
  description: "Get your first Octavus agent running in minutes.",
734
- content: "\n# Quick Start\n\nThis guide will walk you through integrating Octavus into your application in under 10 minutes.\n\n## Prerequisites\n\n- Node.js 18+\n- An Octavus account with API key\n- A Next.js application (or any Node.js backend)\n\n## Test Your Agent First\n\nBefore integrating with SDKs, use **Agent Preview** to test your agent directly in the platform:\n\n1. Open your agent in the platform at `octavus.ai/platform/agents/[agentId]`\n2. Click the **Preview** tab\n3. Configure session inputs and tool mock responses\n4. Start a conversation to test agent behavior\n\nAgent Preview supports all trigger types, file attachments, tool mocking, and real-time streaming. This is the fastest way to iterate on your agent logic before writing any integration code.\n\n## Installation\n\nInstall the Octavus SDKs in your project:\n\n```bash\n# Server SDK for backend\nnpm install @octavus/server-sdk\n\n# React bindings for frontend\nnpm install @octavus/react\n```\n\n## Backend Setup\n\n### 1. Initialize the Client\n\nCreate an Octavus client instance in your backend:\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n### 2. Create a Session Endpoint\n\nCreate an API endpoint that creates sessions and returns the session ID:\n\n```typescript\n// app/api/chat/create/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\n// Agent ID - get from platform or CLI (see below)\nconst SUPPORT_AGENT_ID = process.env.OCTAVUS_SUPPORT_AGENT_ID!;\n\nexport async function POST(request: Request) {\n const { input } = await request.json();\n\n // Create a new session using the agent ID\n const sessionId = await octavus.agentSessions.create(SUPPORT_AGENT_ID, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n### Getting Your Agent ID\n\nThere are two ways to create and manage agents:\n\n**Option 1: Platform UI (Recommended for getting started)**\n\n1. Go to [octavus.ai](https://octavus.ai) and create an agent in the web editor\n2. Copy the agent ID from the URL (e.g., `octavus.ai/platform/agents/clxyz123abc456`)\n3. Add it to your `.env.local`: `OCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456`\n\n**Option 2: Local Development with CLI**\n\nFor version-controlled agent definitions, use the [Octavus CLI](/docs/server-sdk/cli):\n\n```bash\nnpm install --save-dev @octavus/cli\noctavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n```\n\nThe CLI approach is better for teams and CI/CD pipelines where you want agent definitions in your repository.\n\n### 3. Create a Trigger Endpoint\n\nCreate an endpoint that handles triggers and streams responses:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Define tool handlers that run on your server\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n return {\n name: 'Demo User',\n email: 'demo@example.com',\n plan: 'pro',\n };\n },\n 'create-support-ticket': async (args) => {\n // Create ticket in your system\n return {\n ticketId: 'TICKET-123',\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // Execute the request and convert to SSE stream\n const events = session.execute(payload, { signal: request.signal });\n\n // Return as streaming response\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n## Frontend Setup\n\n### 1. Create a Chat Component\n\nUse the `useOctavusChat` hook with the HTTP transport:\n\n```tsx\n// components/chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create a stable transport instance\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send } = useOctavusChat({ transport });\n\n const isStreaming = status === 'streaming';\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || isStreaming) return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Add user message and trigger in one call\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={isStreaming}\n />\n <button\n type=\"submit\"\n disabled={isStreaming}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50\"\n >\n Send\n </button>\n </div>\n </form>\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>\n <div\n className={`p-3 rounded-lg max-w-md ${isUser ? 'bg-blue-500 text-white' : 'bg-gray-100'}`}\n >\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n\n {/* Streaming indicator */}\n {message.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n </div>\n );\n}\n```\n\n### 2. Create Session and Render Chat\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n async function createSession() {\n const response = await fetch('/api/chat/create', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n input: {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n },\n }),\n });\n const { sessionId } = await response.json();\n setSessionId(sessionId);\n }\n\n createSession();\n }, []);\n\n if (!sessionId) {\n return <div>Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Environment Variables\n\nAdd these to your `.env.local`:\n\n```bash\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key-here\n```\n\n## What's Next?\n\nNow that you have a basic integration working:\n\n- [Learn about the protocol](/docs/protocol/overview) to define custom agent behavior\n- [Explore the Server SDK](/docs/server-sdk/overview) for advanced backend features\n- [Build rich UIs](/docs/client-sdk/overview) with the Client SDK\n- [Handle tools on the client](/docs/client-sdk/client-tools) for interactive UIs and browser APIs\n",
736
+ content: "\n# Quick Start\n\nThis guide will walk you through integrating Octavus into your application in under 10 minutes.\n\n## Prerequisites\n\n- Node.js 18+\n- An Octavus account with API key\n- A Next.js application (or any Node.js backend)\n\n## Test Your Agent First\n\nBefore integrating with SDKs, use **Agent Preview** to test your agent directly in the platform:\n\n1. Open your agent in the platform at `octavus.ai/platform/agents/[agentId]`\n2. Click the **Preview** tab\n3. Configure session inputs and tool mock responses\n4. Start a conversation to test agent behavior\n\nAgent Preview supports all trigger types, file attachments, tool mocking, and real-time streaming. This is the fastest way to iterate on your agent logic before writing any integration code.\n\n## Installation\n\nInstall the Octavus SDKs in your project:\n\n```bash\n# Server SDK for backend\nnpm install @octavus/server-sdk\n\n# React bindings for frontend\nnpm install @octavus/react\n```\n\n## Backend Setup\n\n### 1. Initialize the Client\n\nCreate an Octavus client instance in your backend:\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n### 2. Create a Session Endpoint\n\nCreate an API endpoint that creates sessions and returns the session ID:\n\n```typescript\n// app/api/chat/create/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\n// Agent ID - get from platform or CLI (see below)\nconst SUPPORT_AGENT_ID = process.env.OCTAVUS_SUPPORT_AGENT_ID!;\n\nexport async function POST(request: Request) {\n const { input } = await request.json();\n\n // Create a new session using the agent ID\n const sessionId = await octavus.agentSessions.create(SUPPORT_AGENT_ID, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n### Getting Your Agent ID\n\nThere are two ways to create and manage agents:\n\n**Option 1: Platform UI (Recommended for getting started)**\n\n1. Go to [octavus.ai](https://octavus.ai) and create an agent in the web editor\n2. Copy the agent ID from the URL (e.g., `octavus.ai/platform/agents/clxyz123abc456`)\n3. Add it to your `.env.local`: `OCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456`\n\n**Option 2: Local Development with CLI**\n\nFor version-controlled agent definitions, use the [Octavus CLI](/docs/server-sdk/cli):\n\n```bash\nnpm install --save-dev @octavus/cli\noctavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n```\n\nThe CLI approach is better for teams and CI/CD pipelines where you want agent definitions in your repository.\n\n### 3. Create a Trigger Endpoint\n\nCreate an endpoint that handles triggers and streams responses:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Define tool handlers that run on your server\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n return {\n name: 'Demo User',\n email: 'demo@example.com',\n plan: 'pro',\n };\n },\n 'create-support-ticket': async (args) => {\n // Create ticket in your system\n return {\n ticketId: 'TICKET-123',\n estimatedResponse: '24 hours',\n };\n },\n },\n });\n\n // Execute the request and convert to SSE stream\n const events = session.execute(payload, { signal: request.signal });\n\n // Return as streaming response\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n });\n}\n```\n\n## Frontend Setup\n\n### 1. Create a Chat Component\n\nUse the `useOctavusChat` hook with the HTTP transport:\n\n```tsx\n// components/chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create a stable transport instance\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, error, send } = useOctavusChat({ transport });\n\n const isStreaming = status === 'streaming';\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || isStreaming) return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Add user message and trigger in one call\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={isStreaming}\n />\n <button\n type=\"submit\"\n disabled={isStreaming}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50\"\n >\n Send\n </button>\n </div>\n </form>\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n const isUser = message.role === 'user';\n\n return (\n <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>\n <div\n className={`p-3 rounded-lg max-w-md ${isUser ? 'bg-blue-500 text-white' : 'bg-gray-100'}`}\n >\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n\n {/* Streaming indicator */}\n {message.status === 'streaming' && (\n <span className=\"inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1\" />\n )}\n </div>\n </div>\n );\n}\n```\n\n### 2. Create Session and Render Chat\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/chat';\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n async function createSession() {\n const response = await fetch('/api/chat/create', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n input: {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n },\n }),\n });\n const { sessionId } = await response.json();\n setSessionId(sessionId);\n }\n\n createSession();\n }, []);\n\n if (!sessionId) {\n return <div>Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Environment Variables\n\nAdd these to your `.env.local`:\n\n```bash\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key-here\n```\n\n## What's Next?\n\nNow that you have a basic integration working:\n\n- [Learn about the protocol](/docs/protocol/overview) to define custom agent behavior\n- [Explore the Server SDK](/docs/server-sdk/overview) for advanced backend features\n- [Build rich UIs](/docs/client-sdk/overview) with the Client SDK\n- [Handle tools on the client](/docs/client-sdk/client-tools) for interactive UIs and browser APIs\n",
735
737
  excerpt: "Quick Start This guide will walk you through integrating Octavus into your application in under 10 minutes. Prerequisites - Node.js 18+ - An Octavus account with API key - A Next.js application (or...",
736
738
  order: 2
737
739
  }
@@ -748,7 +750,7 @@ var sections_default = [
748
750
  section: "server-sdk",
749
751
  title: "Overview",
750
752
  description: "Introduction to the Octavus Server SDK for backend integration.",
751
- content: "\n# Server SDK Overview\n\nThe `@octavus/server-sdk` package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation loop.\n\n**Current version:** `2.14.0`\n\n## Installation\n\n```bash\nnpm install @octavus/server-sdk\n```\n\nFor agent management (sync, validate), install the CLI as a dev dependency:\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Basic Usage\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: 'https://octavus.ai',\n apiKey: 'your-api-key',\n});\n```\n\n## Key Features\n\n### Agent Management\n\nAgent definitions are managed via the CLI. See the [CLI documentation](/docs/server-sdk/cli) for details.\n\n```bash\n# Sync agent from local files\noctavus sync ./agents/support-chat\n\n# Output: Created: support-chat\n# Agent ID: clxyz123abc456\n```\n\n### Session Management\n\nCreate and manage agent sessions using the agent ID:\n\n```typescript\n// Create a new session (use agent ID from CLI sync)\nconst sessionId = await client.agentSessions.create('clxyz123abc456', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n});\n\n// Get UI-ready session messages (for session restore)\nconst session = await client.agentSessions.getMessages(sessionId);\n```\n\n### Tool Handlers\n\nTools run on your server with your data:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Access your database, APIs, etc.\n return await db.users.findById(args.userId);\n },\n },\n});\n```\n\n### Streaming\n\nAll responses stream in real-time:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of events\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n### Workers\n\nExecute worker agents for task-based processing:\n\n```typescript\n// Non-streaming: get the output directly\nconst { output } = await client.workers.generate(agentId, {\n TOPIC: 'AI safety',\n});\n\n// Streaming: observe events in real-time\nfor await (const event of client.workers.execute(agentId, input)) {\n // Handle stream events\n}\n```\n\n## API Reference\n\n### OctavusClient\n\nThe main entry point for interacting with Octavus.\n\n```typescript\ninterface OctavusClientConfig {\n baseUrl: string; // Octavus API URL\n apiKey?: string; // Your API key\n traceModelRequests?: boolean; // Enable model request tracing (default: false)\n}\n\nclass OctavusClient {\n readonly agents: AgentsApi;\n readonly agentSessions: AgentSessionsApi;\n readonly workers: WorkersApi;\n readonly files: FilesApi;\n\n constructor(config: OctavusClientConfig);\n}\n```\n\n### AgentSessionsApi\n\nManages agent sessions.\n\n```typescript\nclass AgentSessionsApi {\n // Create a new session\n async create(agentId: string, input?: Record<string, unknown>): Promise<string>;\n\n // Get full session state (for debugging/internal use)\n async get(sessionId: string): Promise<SessionState>;\n\n // Get UI-ready messages (for client display)\n async getMessages(sessionId: string): Promise<UISessionState>;\n\n // Attach to a session for triggering\n attach(sessionId: string, options?: SessionAttachOptions): AgentSession;\n}\n\n// Full session state (internal format)\ninterface SessionState {\n id: string;\n agentId: string;\n input: Record<string, unknown>;\n variables: Record<string, unknown>;\n resources: Record<string, unknown>;\n messages: ChatMessage[]; // Internal message format\n createdAt: string;\n updatedAt: string;\n}\n\n// UI-ready session state\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready messages for frontend\n}\n```\n\n### AgentSession\n\nHandles request execution and streaming for a specific session.\n\n```typescript\nclass AgentSession {\n // Execute a request and stream parsed events\n execute(request: SessionRequest, options?: TriggerOptions): AsyncGenerator<StreamEvent>;\n\n // Get the session ID\n getSessionId(): string;\n}\n\ntype SessionRequest = TriggerRequest | ContinueRequest;\n\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n}\n\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n\n// Helper to convert events to SSE stream\nfunction toSSEStream(events: AsyncIterable<StreamEvent>): ReadableStream<Uint8Array>;\n```\n\n### FilesApi\n\nHandles file uploads for sessions.\n\n```typescript\nclass FilesApi {\n // Get presigned URLs for file uploads\n async getUploadUrls(sessionId: string, files: FileUploadRequest[]): Promise<UploadUrlsResponse>;\n}\n\ninterface FileUploadRequest {\n filename: string;\n mediaType: string;\n size: number;\n}\n\ninterface UploadUrlsResponse {\n files: {\n id: string; // File ID for references\n uploadUrl: string; // PUT to this URL\n downloadUrl: string; // GET URL after upload\n }[];\n}\n```\n\nThe client uploads files directly to S3 using the presigned upload URL. See [File Uploads](/docs/client-sdk/file-uploads) for the full integration pattern.\n\n## Next Steps\n\n- [Sessions](/docs/server-sdk/sessions) \u2014 Deep dive into session management\n- [Tools](/docs/server-sdk/tools) \u2014 Implementing tool handlers\n- [Streaming](/docs/server-sdk/streaming) \u2014 Understanding stream events\n- [Workers](/docs/server-sdk/workers) \u2014 Executing worker agents\n- [Debugging](/docs/server-sdk/debugging) \u2014 Model request tracing and debugging\n",
753
+ content: "\n# Server SDK Overview\n\nThe `@octavus/server-sdk` package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation loop.\n\n**Current version:** `2.15.0`\n\n## Installation\n\n```bash\nnpm install @octavus/server-sdk\n```\n\nFor agent management (sync, validate), install the CLI as a dev dependency:\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Basic Usage\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: 'https://octavus.ai',\n apiKey: 'your-api-key',\n});\n```\n\n## Key Features\n\n### Agent Management\n\nAgent definitions are managed via the CLI. See the [CLI documentation](/docs/server-sdk/cli) for details.\n\n```bash\n# Sync agent from local files\noctavus sync ./agents/support-chat\n\n# Output: Created: support-chat\n# Agent ID: clxyz123abc456\n```\n\n### Session Management\n\nCreate and manage agent sessions using the agent ID:\n\n```typescript\n// Create a new session (use agent ID from CLI sync)\nconst sessionId = await client.agentSessions.create('clxyz123abc456', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n});\n\n// Get UI-ready session messages (for session restore)\nconst session = await client.agentSessions.getMessages(sessionId);\n```\n\n### Tool Handlers\n\nTools run on your server with your data:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n // Access your database, APIs, etc.\n return await db.users.findById(args.userId);\n },\n },\n});\n```\n\n### Streaming\n\nAll responses stream in real-time:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of events\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// Convert to SSE stream for HTTP responses\nreturn new Response(toSSEStream(events), {\n headers: { 'Content-Type': 'text/event-stream' },\n});\n```\n\n### Workers\n\nExecute worker agents for task-based processing:\n\n```typescript\n// Non-streaming: get the output directly\nconst { output } = await client.workers.generate(agentId, {\n TOPIC: 'AI safety',\n});\n\n// Streaming: observe events in real-time\nfor await (const event of client.workers.execute(agentId, input)) {\n // Handle stream events\n}\n```\n\n## API Reference\n\n### OctavusClient\n\nThe main entry point for interacting with Octavus.\n\n```typescript\ninterface OctavusClientConfig {\n baseUrl: string; // Octavus API URL\n apiKey?: string; // Your API key\n traceModelRequests?: boolean; // Enable model request tracing (default: false)\n}\n\nclass OctavusClient {\n readonly agents: AgentsApi;\n readonly agentSessions: AgentSessionsApi;\n readonly workers: WorkersApi;\n readonly files: FilesApi;\n\n constructor(config: OctavusClientConfig);\n}\n```\n\n### AgentSessionsApi\n\nManages agent sessions.\n\n```typescript\nclass AgentSessionsApi {\n // Create a new session\n async create(agentId: string, input?: Record<string, unknown>): Promise<string>;\n\n // Get full session state (for debugging/internal use)\n async get(sessionId: string): Promise<SessionState>;\n\n // Get UI-ready messages (for client display)\n async getMessages(sessionId: string): Promise<UISessionState>;\n\n // Attach to a session for triggering\n attach(sessionId: string, options?: SessionAttachOptions): AgentSession;\n}\n\n// Full session state (internal format)\ninterface SessionState {\n id: string;\n agentId: string;\n input: Record<string, unknown>;\n variables: Record<string, unknown>;\n resources: Record<string, unknown>;\n messages: ChatMessage[]; // Internal message format\n createdAt: string;\n updatedAt: string;\n}\n\n// UI-ready session state\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready messages for frontend\n}\n```\n\n### AgentSession\n\nHandles request execution and streaming for a specific session.\n\n```typescript\nclass AgentSession {\n // Execute a request and stream parsed events\n execute(request: SessionRequest, options?: TriggerOptions): AsyncGenerator<StreamEvent>;\n\n // Get the session ID\n getSessionId(): string;\n}\n\ntype SessionRequest = TriggerRequest | ContinueRequest;\n\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n}\n\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n\n// Helper to convert events to SSE stream\nfunction toSSEStream(events: AsyncIterable<StreamEvent>): ReadableStream<Uint8Array>;\n```\n\n### FilesApi\n\nHandles file uploads for sessions.\n\n```typescript\nclass FilesApi {\n // Get presigned URLs for file uploads\n async getUploadUrls(sessionId: string, files: FileUploadRequest[]): Promise<UploadUrlsResponse>;\n}\n\ninterface FileUploadRequest {\n filename: string;\n mediaType: string;\n size: number;\n}\n\ninterface UploadUrlsResponse {\n files: {\n id: string; // File ID for references\n uploadUrl: string; // PUT to this URL\n downloadUrl: string; // GET URL after upload\n }[];\n}\n```\n\nThe client uploads files directly to S3 using the presigned upload URL. See [File Uploads](/docs/client-sdk/file-uploads) for the full integration pattern.\n\n## Next Steps\n\n- [Sessions](/docs/server-sdk/sessions) \u2014 Deep dive into session management\n- [Tools](/docs/server-sdk/tools) \u2014 Implementing tool handlers\n- [Streaming](/docs/server-sdk/streaming) \u2014 Understanding stream events\n- [Workers](/docs/server-sdk/workers) \u2014 Executing worker agents\n- [Debugging](/docs/server-sdk/debugging) \u2014 Model request tracing and debugging\n",
752
754
  excerpt: "Server SDK Overview The package provides a Node.js SDK for integrating Octavus agents into your backend application. It handles session management, streaming, and the tool execution continuation...",
753
755
  order: 1
754
756
  },
@@ -775,7 +777,7 @@ var sections_default = [
775
777
  section: "server-sdk",
776
778
  title: "Streaming",
777
779
  description: "Understanding stream events from the Server SDK.",
778
- content: "\n# Streaming\n\nAll Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates.\n\n## Stream Response\n\nWhen you execute a request, you get an async generator of parsed events:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of StreamEvent\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// For HTTP endpoints, convert to SSE stream\nreturn new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n});\n\n// For sockets, iterate events directly\nfor await (const event of events) {\n conn.write(JSON.stringify(event));\n}\n```\n\n## Event Types\n\nThe stream emits various event types for lifecycle, text, reasoning, and tool interactions.\n\n### Lifecycle Events\n\n```typescript\n// Stream started\n{ type: 'start', messageId: '...', executionId: '...' }\n\n// Stream completed\n{ type: 'finish', finishReason: 'stop' }\n\n// Possible finish reasons:\n// - 'stop': Normal completion\n// - 'tool-calls': Waiting for server tool execution (handled by SDK internally)\n// - 'client-tool-calls': Waiting for client tool execution\n// - 'length': Max tokens reached\n// - 'content-filter': Content filtered\n// - 'error': Error occurred\n// - 'other': Other reason\n\n// Error event (see Error Handling docs for full structure)\n{ type: 'error', errorType: 'internal_error', message: 'Something went wrong', source: 'platform', retryable: false }\n```\n\n### Block Events\n\nTrack execution progress:\n\n```typescript\n// Block started\n{ type: 'block-start', blockId: '...', blockName: 'Respond to user', blockType: 'next-message', display: 'stream', thread: 'main' }\n\n// Block completed\n{ type: 'block-end', blockId: '...', summary: 'Generated response' }\n```\n\n### Text Events\n\nStreaming text content:\n\n```typescript\n// Text generation started\n{ type: 'text-start', id: '...' }\n\n// Incremental text (most common event)\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'text-delta', id: '...', delta: '!' }\n{ type: 'text-delta', id: '...', delta: ' How' }\n{ type: 'text-delta', id: '...', delta: ' can' }\n{ type: 'text-delta', id: '...', delta: ' I' }\n{ type: 'text-delta', id: '...', delta: ' help?' }\n\n// Text generation ended\n{ type: 'text-end', id: '...' }\n```\n\n### Reasoning Events\n\nExtended reasoning (for supported models like Claude):\n\n```typescript\n// Reasoning started\n{ type: 'reasoning-start', id: '...' }\n\n// Reasoning content\n{ type: 'reasoning-delta', id: '...', delta: 'Let me analyze this request...' }\n\n// Reasoning ended\n{ type: 'reasoning-end', id: '...' }\n```\n\n### Tool Events\n\nTool call lifecycle:\n\n```typescript\n// Tool input started\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user-account', title: 'Looking up account' }\n\n// Tool input/arguments streaming\n{ type: 'tool-input-delta', toolCallId: '...', inputTextDelta: '{\"userId\":\"user-123\"}' }\n\n// Tool input streaming ended\n{ type: 'tool-input-end', toolCallId: '...' }\n\n// Tool input is complete and available\n{ type: 'tool-input-available', toolCallId: '...', toolName: 'get-user-account', input: { userId: 'user-123' } }\n\n// Tool output available (success)\n{ type: 'tool-output-available', toolCallId: '...', output: { name: 'Demo User', email: '...' } }\n\n// Tool output error (failure)\n{ type: 'tool-output-error', toolCallId: '...', error: 'User not found' }\n```\n\n### Resource Events\n\nResource updates:\n\n```typescript\n{ type: 'resource-update', name: 'CONVERSATION_SUMMARY', value: 'User asked about...' }\n```\n\n## Display Modes\n\nEach block/tool specifies how it should appear to users:\n\n| Mode | Description |\n| ------------- | ----------------------------------- |\n| `hidden` | Not shown to user (background work) |\n| `name` | Shows block/tool name |\n| `description` | Shows description text |\n| `stream` | Streams content to chat |\n\n**Note**: Hidden events are filtered before reaching the client SDK. Your frontend only sees user-facing events.\n\n## Stream Event Type\n\n```typescript\ntype StreamEvent =\n // Lifecycle\n | StartEvent\n | FinishEvent\n | ErrorEvent\n // Text\n | TextStartEvent\n | TextDeltaEvent\n | TextEndEvent\n // Reasoning\n | ReasoningStartEvent\n | ReasoningDeltaEvent\n | ReasoningEndEvent\n // Tool Input/Output\n | ToolInputStartEvent\n | ToolInputDeltaEvent\n | ToolInputEndEvent\n | ToolInputAvailableEvent\n | ToolOutputAvailableEvent\n | ToolOutputErrorEvent\n // Octavus-Specific\n | BlockStartEvent\n | BlockEndEvent\n | ResourceUpdateEvent\n | ToolRequestEvent\n | ClientToolRequestEvent;\n```\n\n### Client Tool Request\n\nWhen a tool has no server handler registered, the SDK emits a `client-tool-request` event:\n\n```typescript\n{\n type: 'client-tool-request',\n executionId: 'exec_abc123', // Use this to continue execution\n toolCalls: [ // Tools for client to handle\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n args: {}\n }\n ],\n serverToolResults: [ // Results from server tools in same batch\n {\n toolCallId: 'call_def',\n toolName: 'get-user-account',\n result: { name: 'Demo User' }\n }\n ]\n}\n```\n\nAfter the client handles the tools, send a `continue` request with all results:\n\n```typescript\nsession.execute({\n type: 'continue',\n executionId: 'exec_abc123',\n toolResults: [\n ...serverToolResults, // Include server results from the event\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n result: { lat: 40.7128, lng: -74.006 },\n },\n ],\n});\n```\n\nSee [Client Tools](/docs/client-sdk/client-tools) for full client-side implementation.\n\n## Error Events\n\nErrors are emitted as structured events with type classification:\n\n```typescript\n{\n type: 'error',\n errorType: 'rate_limit_error', // Error classification\n message: 'Rate limit exceeded', // Human-readable message\n source: 'provider', // 'platform' | 'provider' | 'tool'\n retryable: true, // Whether retry is possible\n retryAfter: 60, // Seconds to wait (rate limits)\n code: 'ANTHROPIC_429', // Machine-readable code\n provider: { // Provider details (when applicable)\n name: 'anthropic',\n statusCode: 429,\n requestId: 'req_...'\n }\n}\n```\n\n### Error Types\n\n| Type | Description |\n| ---------------------- | --------------------- |\n| `rate_limit_error` | Too many requests |\n| `authentication_error` | Invalid API key |\n| `provider_error` | LLM provider issue |\n| `provider_overloaded` | Provider at capacity |\n| `tool_error` | Tool execution failed |\n| `internal_error` | Platform error |\n\n### Tool Errors\n\nTool errors are captured per-tool and don't stop the stream:\n\n```typescript\n{ type: 'tool-output-error', toolCallId: '...', error: 'Handler threw exception' }\n```\n\nThe stream always ends with either `finish` or `error`.\n\nFor client-side error handling patterns, see [Error Handling](/docs/client-sdk/error-handling).\n",
780
+ content: "\n# Streaming\n\nAll Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates.\n\n## Stream Response\n\nWhen you execute a request, you get an async generator of parsed events:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() returns an async generator of StreamEvent\nconst events = session.execute({\n type: 'trigger',\n triggerName: 'user-message',\n input: { USER_MESSAGE: 'Hello!' },\n});\n\n// For HTTP endpoints, convert to SSE stream\nreturn new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n});\n\n// For sockets, iterate events directly\nfor await (const event of events) {\n conn.write(JSON.stringify(event));\n}\n```\n\nThe `X-Accel-Buffering: no` header disables proxy buffering on Nginx-based infrastructure (including Vercel), ensuring SSE events are forwarded immediately instead of being batched.\n\n### Heartbeat\n\n`toSSEStream` automatically sends SSE comment lines (`: heartbeat`) every 15 seconds during idle periods. This prevents proxies and load balancers from closing the connection due to inactivity \u2014 particularly important during multi-step executions where the stream may be silent while waiting for tool processing or LLM responses.\n\nHeartbeat comments are ignored by all SSE parsers per the spec. No client-side handling is needed.\n\n## Event Types\n\nThe stream emits various event types for lifecycle, text, reasoning, and tool interactions.\n\n### Lifecycle Events\n\n```typescript\n// Stream started\n{ type: 'start', messageId: '...', executionId: '...' }\n\n// Stream completed\n{ type: 'finish', finishReason: 'stop' }\n\n// Possible finish reasons:\n// - 'stop': Normal completion\n// - 'tool-calls': Waiting for server tool execution (handled by SDK internally)\n// - 'client-tool-calls': Waiting for client tool execution\n// - 'length': Max tokens reached\n// - 'content-filter': Content filtered\n// - 'error': Error occurred\n// - 'other': Other reason\n\n// Error event (see Error Handling docs for full structure)\n{ type: 'error', errorType: 'internal_error', message: 'Something went wrong', source: 'platform', retryable: false }\n```\n\n### Block Events\n\nTrack execution progress:\n\n```typescript\n// Block started\n{ type: 'block-start', blockId: '...', blockName: 'Respond to user', blockType: 'next-message', display: 'stream', thread: 'main' }\n\n// Block completed\n{ type: 'block-end', blockId: '...', summary: 'Generated response' }\n```\n\n### Text Events\n\nStreaming text content:\n\n```typescript\n// Text generation started\n{ type: 'text-start', id: '...' }\n\n// Incremental text (most common event)\n{ type: 'text-delta', id: '...', delta: 'Hello' }\n{ type: 'text-delta', id: '...', delta: '!' }\n{ type: 'text-delta', id: '...', delta: ' How' }\n{ type: 'text-delta', id: '...', delta: ' can' }\n{ type: 'text-delta', id: '...', delta: ' I' }\n{ type: 'text-delta', id: '...', delta: ' help?' }\n\n// Text generation ended\n{ type: 'text-end', id: '...' }\n```\n\n### Reasoning Events\n\nExtended reasoning (for supported models like Claude):\n\n```typescript\n// Reasoning started\n{ type: 'reasoning-start', id: '...' }\n\n// Reasoning content\n{ type: 'reasoning-delta', id: '...', delta: 'Let me analyze this request...' }\n\n// Reasoning ended\n{ type: 'reasoning-end', id: '...' }\n```\n\n### Tool Events\n\nTool call lifecycle:\n\n```typescript\n// Tool input started\n{ type: 'tool-input-start', toolCallId: '...', toolName: 'get-user-account', title: 'Looking up account' }\n\n// Tool input/arguments streaming\n{ type: 'tool-input-delta', toolCallId: '...', inputTextDelta: '{\"userId\":\"user-123\"}' }\n\n// Tool input streaming ended\n{ type: 'tool-input-end', toolCallId: '...' }\n\n// Tool input is complete and available\n{ type: 'tool-input-available', toolCallId: '...', toolName: 'get-user-account', input: { userId: 'user-123' } }\n\n// Tool output available (success)\n{ type: 'tool-output-available', toolCallId: '...', output: { name: 'Demo User', email: '...' } }\n\n// Tool output error (failure)\n{ type: 'tool-output-error', toolCallId: '...', error: 'User not found' }\n```\n\n### Resource Events\n\nResource updates:\n\n```typescript\n{ type: 'resource-update', name: 'CONVERSATION_SUMMARY', value: 'User asked about...' }\n```\n\n## Display Modes\n\nEach block/tool specifies how it should appear to users:\n\n| Mode | Description |\n| ------------- | ----------------------------------- |\n| `hidden` | Not shown to user (background work) |\n| `name` | Shows block/tool name |\n| `description` | Shows description text |\n| `stream` | Streams content to chat |\n\n**Note**: Hidden events are filtered before reaching the client SDK. Your frontend only sees user-facing events.\n\n## Stream Event Type\n\n```typescript\ntype StreamEvent =\n // Lifecycle\n | StartEvent\n | FinishEvent\n | ErrorEvent\n // Text\n | TextStartEvent\n | TextDeltaEvent\n | TextEndEvent\n // Reasoning\n | ReasoningStartEvent\n | ReasoningDeltaEvent\n | ReasoningEndEvent\n // Tool Input/Output\n | ToolInputStartEvent\n | ToolInputDeltaEvent\n | ToolInputEndEvent\n | ToolInputAvailableEvent\n | ToolOutputAvailableEvent\n | ToolOutputErrorEvent\n // Octavus-Specific\n | BlockStartEvent\n | BlockEndEvent\n | ResourceUpdateEvent\n | ToolRequestEvent\n | ClientToolRequestEvent;\n```\n\n### Client Tool Request\n\nWhen a tool has no server handler registered, the SDK emits a `client-tool-request` event:\n\n```typescript\n{\n type: 'client-tool-request',\n executionId: 'exec_abc123', // Use this to continue execution\n toolCalls: [ // Tools for client to handle\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n args: {}\n }\n ],\n serverToolResults: [ // Results from server tools in same batch\n {\n toolCallId: 'call_def',\n toolName: 'get-user-account',\n result: { name: 'Demo User' }\n }\n ]\n}\n```\n\nAfter the client handles the tools, send a `continue` request with all results:\n\n```typescript\nsession.execute({\n type: 'continue',\n executionId: 'exec_abc123',\n toolResults: [\n ...serverToolResults, // Include server results from the event\n {\n toolCallId: 'call_xyz',\n toolName: 'get-browser-location',\n result: { lat: 40.7128, lng: -74.006 },\n },\n ],\n});\n```\n\nSee [Client Tools](/docs/client-sdk/client-tools) for full client-side implementation.\n\n## Error Events\n\nErrors are emitted as structured events with type classification:\n\n```typescript\n{\n type: 'error',\n errorType: 'rate_limit_error', // Error classification\n message: 'Rate limit exceeded', // Human-readable message\n source: 'provider', // 'platform' | 'provider' | 'tool'\n retryable: true, // Whether retry is possible\n retryAfter: 60, // Seconds to wait (rate limits)\n code: 'ANTHROPIC_429', // Machine-readable code\n provider: { // Provider details (when applicable)\n name: 'anthropic',\n statusCode: 429,\n requestId: 'req_...'\n }\n}\n```\n\n### Error Types\n\n| Type | Description |\n| ---------------------- | --------------------- |\n| `rate_limit_error` | Too many requests |\n| `authentication_error` | Invalid API key |\n| `provider_error` | LLM provider issue |\n| `provider_overloaded` | Provider at capacity |\n| `tool_error` | Tool execution failed |\n| `internal_error` | Platform error |\n\n### Tool Errors\n\nTool errors are captured per-tool and don't stop the stream:\n\n```typescript\n{ type: 'tool-output-error', toolCallId: '...', error: 'Handler threw exception' }\n```\n\nThe stream always ends with either `finish` or `error`.\n\nFor client-side error handling patterns, see [Error Handling](/docs/client-sdk/error-handling).\n",
779
781
  excerpt: "Streaming All Octavus responses stream in real-time using Server-Sent Events (SSE). This enables responsive UX with incremental updates. Stream Response When you execute a request, you get an async...",
780
782
  order: 4
781
783
  },
@@ -784,7 +786,7 @@ var sections_default = [
784
786
  section: "server-sdk",
785
787
  title: "CLI",
786
788
  description: "Command-line interface for validating and syncing agent definitions.",
787
- content: '\n# Octavus CLI\n\nThe `@octavus/cli` package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform.\n\n**Current version:** `2.14.0`\n\n## Installation\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Configuration\n\nThe CLI requires an API key with the **Agents** permission.\n\n### Environment Variables\n\n| Variable | Description |\n| --------------------- | ---------------------------------------------- |\n| `OCTAVUS_CLI_API_KEY` | API key with "Agents" permission (recommended) |\n| `OCTAVUS_API_KEY` | Fallback if `OCTAVUS_CLI_API_KEY` not set |\n| `OCTAVUS_API_URL` | Optional, defaults to `https://octavus.ai` |\n\n### Two-Key Strategy (Recommended)\n\nFor production deployments, use separate API keys with minimal permissions:\n\n```bash\n# CI/CD or .env.local (not committed)\nOCTAVUS_CLI_API_KEY=oct_sk_... # "Agents" permission only\n\n# Production .env\nOCTAVUS_API_KEY=oct_sk_... # "Sessions" permission only\n```\n\nThis ensures production servers only have session permissions (smaller blast radius if leaked), while agent management is restricted to development/CI environments.\n\n### Multiple Environments\n\nUse separate Octavus projects for staging and production, each with their own API keys. The `--env` flag lets you load different environment files:\n\n```bash\n# Local development (default: .env)\noctavus sync ./agents/my-agent\n\n# Staging project\noctavus --env .env.staging sync ./agents/my-agent\n\n# Production project\noctavus --env .env.production sync ./agents/my-agent\n```\n\nExample environment files:\n\n```bash\n# .env.staging (syncs to your staging project)\nOCTAVUS_CLI_API_KEY=oct_sk_staging_project_key...\n\n# .env.production (syncs to your production project)\nOCTAVUS_CLI_API_KEY=oct_sk_production_project_key...\n```\n\nEach project has its own agents, so you\'ll get different agent IDs per environment.\n\n## Global Options\n\n| Option | Description |\n| -------------- | ------------------------------------------------------- |\n| `--env <file>` | Load environment from a specific file (default: `.env`) |\n| `--help` | Show help |\n| `--version` | Show version |\n\n## Commands\n\n### `octavus sync <path>`\n\nSync an agent definition to the platform. Creates the agent if it doesn\'t exist, or updates it if it does.\n\n```bash\noctavus sync ./agents/my-agent\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Reading agent from ./agents/my-agent...\n\u2139 Syncing support-chat...\n\u2713 Created: support-chat\n Agent ID: clxyz123abc456\n```\n\n### `octavus validate <path>`\n\nValidate an agent definition without saving. Useful for CI/CD pipelines.\n\n```bash\noctavus validate ./agents/my-agent\n```\n\n**Exit codes:**\n\n- `0` \u2014 Validation passed\n- `1` \u2014 Validation errors\n- `2` \u2014 Configuration errors (missing API key, etc.)\n\n### `octavus list`\n\nList all agents in your project.\n\n```bash\noctavus list\n```\n\n**Example output:**\n\n```\nSLUG NAME FORMAT ID\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nsupport-chat Support Chat Agent interactive clxyz123abc456\n\n1 agent(s)\n```\n\n### `octavus get <slug>`\n\nGet details about a specific agent by its slug.\n\n```bash\noctavus get support-chat\n```\n\n### `octavus archive <slug>`\n\nArchive an agent by slug (soft delete). Archived agents are removed from the active agent list and their slug is freed for reuse.\n\n```bash\noctavus archive support-chat\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Archiving support-chat...\n\u2713 Archived: support-chat\n Agent ID: clxyz123abc456\n```\n\n## Agent Directory Structure\n\nThe CLI expects agent definitions in a specific directory structure:\n\n```\nmy-agent/\n\u251C\u2500\u2500 settings.json # Required: Agent metadata\n\u251C\u2500\u2500 protocol.yaml # Required: Agent protocol\n\u251C\u2500\u2500 prompts/ # Optional: Prompt templates\n\u2502 \u251C\u2500\u2500 system.md\n\u2502 \u2514\u2500\u2500 user-message.md\n\u2514\u2500\u2500 references/ # Optional: Reference documents\n \u2514\u2500\u2500 api-guidelines.md\n```\n\n### references/\n\nReference files are markdown documents with YAML frontmatter containing a `description`. The agent can fetch these on demand during execution. See [References](/docs/protocol/references) for details.\n\n### settings.json\n\n```json\n{\n "slug": "my-agent",\n "name": "My Agent",\n "description": "A helpful assistant",\n "format": "interactive"\n}\n```\n\n### protocol.yaml\n\nSee the [Protocol documentation](/docs/protocol/overview) for details on protocol syntax.\n\n## CI/CD Integration\n\n### GitHub Actions\n\n```yaml\nname: Validate and Sync Agents\n\non:\n push:\n branches: [main]\n paths:\n - \'agents/**\'\n\njobs:\n sync:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - uses: actions/setup-node@v4\n with:\n node-version: \'22\'\n\n - run: npm install\n\n - name: Validate agent\n run: npx octavus validate ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n\n - name: Sync agent\n run: npx octavus sync ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n```\n\n### Package.json Scripts\n\nAdd sync scripts to your `package.json`:\n\n```json\n{\n "scripts": {\n "agents:validate": "octavus validate ./agents/my-agent",\n "agents:sync": "octavus sync ./agents/my-agent"\n },\n "devDependencies": {\n "@octavus/cli": "^0.1.0"\n }\n}\n```\n\n## Workflow\n\nThe recommended workflow for managing agents:\n\n1. **Define agent locally** \u2014 Create `settings.json`, `protocol.yaml`, and prompts\n2. **Validate** \u2014 Run `octavus validate ./my-agent` to check for errors\n3. **Sync** \u2014 Run `octavus sync ./my-agent` to push to platform\n4. **Store agent ID** \u2014 Save the output ID in an environment variable\n5. **Use in app** \u2014 Read the ID from env and pass to `client.agentSessions.create()`\n\n```bash\n# After syncing: octavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n\n# Add to your .env file\nOCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456\n```\n\n```typescript\nconst agentId = process.env.OCTAVUS_SUPPORT_AGENT_ID;\n\nconst sessionId = await client.agentSessions.create(agentId, {\n COMPANY_NAME: \'Acme Corp\',\n});\n```\n',
789
+ content: '\n# Octavus CLI\n\nThe `@octavus/cli` package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform.\n\n**Current version:** `2.15.0`\n\n## Installation\n\n```bash\nnpm install --save-dev @octavus/cli\n```\n\n## Configuration\n\nThe CLI requires an API key with the **Agents** permission.\n\n### Environment Variables\n\n| Variable | Description |\n| --------------------- | ---------------------------------------------- |\n| `OCTAVUS_CLI_API_KEY` | API key with "Agents" permission (recommended) |\n| `OCTAVUS_API_KEY` | Fallback if `OCTAVUS_CLI_API_KEY` not set |\n| `OCTAVUS_API_URL` | Optional, defaults to `https://octavus.ai` |\n\n### Two-Key Strategy (Recommended)\n\nFor production deployments, use separate API keys with minimal permissions:\n\n```bash\n# CI/CD or .env.local (not committed)\nOCTAVUS_CLI_API_KEY=oct_sk_... # "Agents" permission only\n\n# Production .env\nOCTAVUS_API_KEY=oct_sk_... # "Sessions" permission only\n```\n\nThis ensures production servers only have session permissions (smaller blast radius if leaked), while agent management is restricted to development/CI environments.\n\n### Multiple Environments\n\nUse separate Octavus projects for staging and production, each with their own API keys. The `--env` flag lets you load different environment files:\n\n```bash\n# Local development (default: .env)\noctavus sync ./agents/my-agent\n\n# Staging project\noctavus --env .env.staging sync ./agents/my-agent\n\n# Production project\noctavus --env .env.production sync ./agents/my-agent\n```\n\nExample environment files:\n\n```bash\n# .env.staging (syncs to your staging project)\nOCTAVUS_CLI_API_KEY=oct_sk_staging_project_key...\n\n# .env.production (syncs to your production project)\nOCTAVUS_CLI_API_KEY=oct_sk_production_project_key...\n```\n\nEach project has its own agents, so you\'ll get different agent IDs per environment.\n\n## Global Options\n\n| Option | Description |\n| -------------- | ------------------------------------------------------- |\n| `--env <file>` | Load environment from a specific file (default: `.env`) |\n| `--help` | Show help |\n| `--version` | Show version |\n\n## Commands\n\n### `octavus sync <path>`\n\nSync an agent definition to the platform. Creates the agent if it doesn\'t exist, or updates it if it does.\n\n```bash\noctavus sync ./agents/my-agent\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Reading agent from ./agents/my-agent...\n\u2139 Syncing support-chat...\n\u2713 Created: support-chat\n Agent ID: clxyz123abc456\n```\n\n### `octavus validate <path>`\n\nValidate an agent definition without saving. Useful for CI/CD pipelines.\n\n```bash\noctavus validate ./agents/my-agent\n```\n\n**Exit codes:**\n\n- `0` \u2014 Validation passed\n- `1` \u2014 Validation errors\n- `2` \u2014 Configuration errors (missing API key, etc.)\n\n### `octavus list`\n\nList all agents in your project.\n\n```bash\noctavus list\n```\n\n**Example output:**\n\n```\nSLUG NAME FORMAT ID\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nsupport-chat Support Chat Agent interactive clxyz123abc456\n\n1 agent(s)\n```\n\n### `octavus get <slug>`\n\nGet details about a specific agent by its slug.\n\n```bash\noctavus get support-chat\n```\n\n### `octavus archive <slug>`\n\nArchive an agent by slug (soft delete). Archived agents are removed from the active agent list and their slug is freed for reuse.\n\n```bash\noctavus archive support-chat\n```\n\n**Options:**\n\n- `--json` \u2014 Output as JSON (for CI/CD parsing)\n- `--quiet` \u2014 Suppress non-essential output\n\n**Example output:**\n\n```\n\u2139 Archiving support-chat...\n\u2713 Archived: support-chat\n Agent ID: clxyz123abc456\n```\n\n## Agent Directory Structure\n\nThe CLI expects agent definitions in a specific directory structure:\n\n```\nmy-agent/\n\u251C\u2500\u2500 settings.json # Required: Agent metadata\n\u251C\u2500\u2500 protocol.yaml # Required: Agent protocol\n\u251C\u2500\u2500 prompts/ # Optional: Prompt templates\n\u2502 \u251C\u2500\u2500 system.md\n\u2502 \u2514\u2500\u2500 user-message.md\n\u2514\u2500\u2500 references/ # Optional: Reference documents\n \u2514\u2500\u2500 api-guidelines.md\n```\n\n### references/\n\nReference files are markdown documents with YAML frontmatter containing a `description`. The agent can fetch these on demand during execution. See [References](/docs/protocol/references) for details.\n\n### settings.json\n\n```json\n{\n "slug": "my-agent",\n "name": "My Agent",\n "description": "A helpful assistant",\n "format": "interactive"\n}\n```\n\n### protocol.yaml\n\nSee the [Protocol documentation](/docs/protocol/overview) for details on protocol syntax.\n\n## CI/CD Integration\n\n### GitHub Actions\n\n```yaml\nname: Validate and Sync Agents\n\non:\n push:\n branches: [main]\n paths:\n - \'agents/**\'\n\njobs:\n sync:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - uses: actions/setup-node@v4\n with:\n node-version: \'22\'\n\n - run: npm install\n\n - name: Validate agent\n run: npx octavus validate ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n\n - name: Sync agent\n run: npx octavus sync ./agents/support-chat\n env:\n OCTAVUS_CLI_API_KEY: ${{ secrets.OCTAVUS_CLI_API_KEY }}\n```\n\n### Package.json Scripts\n\nAdd sync scripts to your `package.json`:\n\n```json\n{\n "scripts": {\n "agents:validate": "octavus validate ./agents/my-agent",\n "agents:sync": "octavus sync ./agents/my-agent"\n },\n "devDependencies": {\n "@octavus/cli": "^0.1.0"\n }\n}\n```\n\n## Workflow\n\nThe recommended workflow for managing agents:\n\n1. **Define agent locally** \u2014 Create `settings.json`, `protocol.yaml`, and prompts\n2. **Validate** \u2014 Run `octavus validate ./my-agent` to check for errors\n3. **Sync** \u2014 Run `octavus sync ./my-agent` to push to platform\n4. **Store agent ID** \u2014 Save the output ID in an environment variable\n5. **Use in app** \u2014 Read the ID from env and pass to `client.agentSessions.create()`\n\n```bash\n# After syncing: octavus sync ./agents/support-chat\n# Output: Agent ID: clxyz123abc456\n\n# Add to your .env file\nOCTAVUS_SUPPORT_AGENT_ID=clxyz123abc456\n```\n\n```typescript\nconst agentId = process.env.OCTAVUS_SUPPORT_AGENT_ID;\n\nconst sessionId = await client.agentSessions.create(agentId, {\n COMPANY_NAME: \'Acme Corp\',\n});\n```\n',
788
790
  excerpt: "Octavus CLI The package provides a command-line interface for validating and syncing agent definitions from your local filesystem to the Octavus platform. Current version: Installation ...",
789
791
  order: 5
790
792
  },
@@ -819,7 +821,7 @@ var sections_default = [
819
821
  section: "client-sdk",
820
822
  title: "Overview",
821
823
  description: "Introduction to the Octavus Client SDKs for building chat interfaces.",
822
- content: "\n# Client SDK Overview\n\nOctavus provides two packages for frontend integration:\n\n| Package | Purpose | Use When |\n| --------------------- | ------------------------ | ----------------------------------------------------- |\n| `@octavus/react` | React hooks and bindings | Building React applications |\n| `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |\n\n**Most users should install `@octavus/react`** \u2014 it includes everything from `@octavus/client-sdk` plus React-specific hooks.\n\n## Installation\n\n### React Applications\n\n```bash\nnpm install @octavus/react\n```\n\n**Current version:** `2.14.0`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `2.14.0`\n\n## Transport Pattern\n\nThe Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:\n\n| Transport | Use Case | Docs |\n| ----------------------- | -------------------------------------------- | ----------------------------------------------------- |\n| `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |\n| `createSocketTransport` | WebSocket, SockJS, or other socket protocols | [Socket Transport](/docs/client-sdk/socket-transport) |\n\nWhen the transport changes (e.g., when `sessionId` changes), the `useOctavusChat` hook automatically reinitializes with the new transport.\n\n> **Recommendation**: Use HTTP transport unless you specifically need WebSocket features (custom real-time events, Meteor/Phoenix, etc.).\n\n## React Usage\n\nThe `useOctavusChat` hook provides state management and streaming for React applications:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n // Create a stable transport instance (memoized on sessionId)\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n return (\n <div>\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n </div>\n );\n}\n```\n\n## Framework-Agnostic Usage\n\nThe `OctavusChat` class can be used with any framework or vanilla JavaScript:\n\n```typescript\nimport { OctavusChat, createHttpTransport } from '@octavus/client-sdk';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n\nconst chat = new OctavusChat({ transport });\n\n// Subscribe to state changes\nconst unsubscribe = chat.subscribe(() => {\n console.log('Messages:', chat.messages);\n console.log('Status:', chat.status);\n // Update your UI here\n});\n\n// Send a message\nawait chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });\n\n// Cleanup when done\nunsubscribe();\n```\n\n## Key Features\n\n### Unified Send Function\n\nThe `send` function handles both user message display and agent triggering in one call:\n\n```tsx\nconst { send } = useOctavusChat({ transport });\n\n// Add user message to UI and trigger agent\nawait send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n\n// Trigger without adding a user message (e.g., button click)\nawait send('request-human');\n```\n\n### Message Parts\n\nMessages contain ordered `parts` for rich content:\n\n```tsx\nconst { messages } = useOctavusChat({ transport });\n\n// Each message has typed parts\nmessage.parts.map((part) => {\n switch (part.type) {\n case 'text': // Text content\n case 'reasoning': // Extended reasoning/thinking\n case 'tool-call': // Tool execution\n case 'operation': // Internal operations (set-resource, etc.)\n }\n});\n```\n\n### Status Tracking\n\n```tsx\nconst { status } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error' | 'awaiting-input'\n// 'awaiting-input' occurs when interactive client tools need user action\n```\n\n### Stop Streaming\n\n```tsx\nconst { stop } = useOctavusChat({ transport });\n\n// Stop current stream and finalize message\nstop();\n```\n\n### Retry Last Trigger\n\nRe-execute the last trigger from the same starting point. Messages are rolled back to the state before the trigger, the user message is re-added (if any), and the agent re-executes. Already-uploaded files are reused without re-uploading.\n\n```tsx\nconst { retry, canRetry } = useOctavusChat({ transport });\n\n// Retry after an error, cancellation, or unsatisfactory result\nif (canRetry) {\n await retry();\n}\n```\n\n`canRetry` is `true` when a trigger has been sent and the chat is not currently streaming or awaiting input.\n\n## Hook Reference (React)\n\n### useOctavusChat\n\n```typescript\nfunction useOctavusChat(options: OctavusChatOptions): UseOctavusChatReturn;\n\ninterface OctavusChatOptions {\n // Required: Transport for streaming events\n transport: Transport;\n\n // Optional: Function to request upload URLs for file uploads\n requestUploadUrls?: (\n files: { filename: string; mediaType: string; size: number }[],\n ) => Promise<UploadUrlsResponse>;\n\n // Optional: Client-side tool handlers\n // - Function: executes automatically and returns result\n // - 'interactive': appears in pendingClientTools for user input\n clientTools?: Record<string, ClientToolHandler>;\n\n // Optional: Pre-populate with existing messages (session restore)\n initialMessages?: UIMessage[];\n\n // Optional: Callbacks\n onError?: (error: OctavusError) => void; // Structured error with type, source, retryable\n onFinish?: () => void;\n onStop?: () => void; // Called when user stops generation\n onResourceUpdate?: (name: string, value: unknown) => void;\n}\n\ninterface UseOctavusChatReturn {\n // State\n messages: UIMessage[];\n status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n error: OctavusError | null; // Structured error with type, source, retryable\n\n // Connection (socket transport only - undefined for HTTP)\n connectionState: ConnectionState | undefined; // 'disconnected' | 'connecting' | 'connected' | 'error'\n connectionError: Error | undefined;\n\n // Client tools (interactive tools awaiting user input)\n pendingClientTools: Record<string, InteractiveTool[]>; // Keyed by tool name\n\n // Actions\n send: (\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ) => Promise<void>;\n stop: () => void;\n retry: () => Promise<void>; // Retry last trigger from same starting point\n canRetry: boolean; // Whether retry() can be called\n\n // Connection management (socket transport only - undefined for HTTP)\n connect: (() => Promise<void>) | undefined;\n disconnect: (() => void) | undefined;\n\n // File uploads (requires requestUploadUrls)\n uploadFiles: (\n files: FileList | File[],\n onProgress?: (fileIndex: number, progress: number) => void,\n ) => Promise<FileReference[]>;\n}\n\ninterface UserMessageInput {\n content?: string;\n files?: FileList | File[] | FileReference[];\n}\n```\n\n## Transport Reference\n\n### createHttpTransport\n\nCreates an HTTP/SSE transport using native `fetch()`:\n\n```typescript\nimport { createHttpTransport } from '@octavus/react';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n```\n\n### createSocketTransport\n\nCreates a WebSocket/SockJS transport for real-time connections:\n\n```typescript\nimport { createSocketTransport } from '@octavus/react';\n\nconst transport = createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(`wss://api.example.com/stream?sessionId=${sessionId}`);\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('Connection failed'));\n }),\n});\n```\n\nSocket transport provides additional connection management:\n\n```typescript\n// Access connection state directly\ntransport.connectionState; // 'disconnected' | 'connecting' | 'connected' | 'error'\n\n// Subscribe to state changes\ntransport.onConnectionStateChange((state, error) => {\n /* ... */\n});\n\n// Eager connection (instead of lazy on first send)\nawait transport.connect();\n\n// Manual disconnect\ntransport.disconnect();\n```\n\nFor detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).\n\n## Class Reference (Framework-Agnostic)\n\n### OctavusChat\n\n```typescript\nclass OctavusChat {\n constructor(options: OctavusChatOptions);\n\n // State (read-only)\n readonly messages: UIMessage[];\n readonly status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n readonly error: OctavusError | null; // Structured error\n readonly pendingClientTools: Record<string, InteractiveTool[]>; // Interactive tools\n\n // Actions\n send(\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ): Promise<void>;\n stop(): void;\n\n // Subscription\n subscribe(callback: () => void): () => void; // Returns unsubscribe function\n}\n```\n\n## Next Steps\n\n- [HTTP Transport](/docs/client-sdk/http-transport) \u2014 HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) \u2014 WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) \u2014 Working with message state\n- [Streaming](/docs/client-sdk/streaming) \u2014 Building streaming UIs\n- [Client Tools](/docs/client-sdk/client-tools) \u2014 Interactive browser-side tool handling\n- [Operations](/docs/client-sdk/execution-blocks) \u2014 Showing agent progress\n- [Error Handling](/docs/client-sdk/error-handling) \u2014 Handling errors with type guards\n- [File Uploads](/docs/client-sdk/file-uploads) \u2014 Uploading images and documents\n- [Examples](/docs/examples/overview) \u2014 Complete working examples\n",
824
+ content: "\n# Client SDK Overview\n\nOctavus provides two packages for frontend integration:\n\n| Package | Purpose | Use When |\n| --------------------- | ------------------------ | ----------------------------------------------------- |\n| `@octavus/react` | React hooks and bindings | Building React applications |\n| `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |\n\n**Most users should install `@octavus/react`** \u2014 it includes everything from `@octavus/client-sdk` plus React-specific hooks.\n\n## Installation\n\n### React Applications\n\n```bash\nnpm install @octavus/react\n```\n\n**Current version:** `2.15.0`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `2.15.0`\n\n## Transport Pattern\n\nThe Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:\n\n| Transport | Use Case | Docs |\n| ----------------------- | -------------------------------------------- | ----------------------------------------------------- |\n| `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |\n| `createSocketTransport` | WebSocket, SockJS, or other socket protocols | [Socket Transport](/docs/client-sdk/socket-transport) |\n\nWhen the transport changes (e.g., when `sessionId` changes), the `useOctavusChat` hook automatically reinitializes with the new transport.\n\n> **Recommendation**: Use HTTP transport unless you specifically need WebSocket features (custom real-time events, Meteor/Phoenix, etc.).\n\n## React Usage\n\nThe `useOctavusChat` hook provides state management and streaming for React applications:\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\n // Create a stable transport instance (memoized on sessionId)\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n return (\n <div>\n {messages.map((msg) => (\n <MessageBubble key={msg.id} message={msg} />\n ))}\n </div>\n );\n}\n\nfunction MessageBubble({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n return null;\n })}\n </div>\n );\n}\n```\n\n## Framework-Agnostic Usage\n\nThe `OctavusChat` class can be used with any framework or vanilla JavaScript:\n\n```typescript\nimport { OctavusChat, createHttpTransport } from '@octavus/client-sdk';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n\nconst chat = new OctavusChat({ transport });\n\n// Subscribe to state changes\nconst unsubscribe = chat.subscribe(() => {\n console.log('Messages:', chat.messages);\n console.log('Status:', chat.status);\n // Update your UI here\n});\n\n// Send a message\nawait chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });\n\n// Cleanup when done\nunsubscribe();\n```\n\n## Key Features\n\n### Unified Send Function\n\nThe `send` function handles both user message display and agent triggering in one call:\n\n```tsx\nconst { send } = useOctavusChat({ transport });\n\n// Add user message to UI and trigger agent\nawait send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n\n// Trigger without adding a user message (e.g., button click)\nawait send('request-human');\n```\n\n### Message Parts\n\nMessages contain ordered `parts` for rich content:\n\n```tsx\nconst { messages } = useOctavusChat({ transport });\n\n// Each message has typed parts\nmessage.parts.map((part) => {\n switch (part.type) {\n case 'text': // Text content\n case 'reasoning': // Extended reasoning/thinking\n case 'tool-call': // Tool execution\n case 'operation': // Internal operations (set-resource, etc.)\n }\n});\n```\n\n### Status Tracking\n\n```tsx\nconst { status } = useOctavusChat({ transport });\n\n// status: 'idle' | 'streaming' | 'error' | 'awaiting-input'\n// 'awaiting-input' occurs when interactive client tools need user action\n```\n\n### Stop Streaming\n\n```tsx\nconst { stop } = useOctavusChat({ transport });\n\n// Stop current stream and finalize message\nstop();\n```\n\n### Retry Last Trigger\n\nRe-execute the last trigger from the same starting point. Messages are rolled back to the state before the trigger, the user message is re-added (if any), and the agent re-executes. Already-uploaded files are reused without re-uploading.\n\n```tsx\nconst { retry, canRetry } = useOctavusChat({ transport });\n\n// Retry after an error, cancellation, or unsatisfactory result\nif (canRetry) {\n await retry();\n}\n```\n\n`canRetry` is `true` when a trigger has been sent and the chat is not currently streaming or awaiting input.\n\n## Hook Reference (React)\n\n### useOctavusChat\n\n```typescript\nfunction useOctavusChat(options: OctavusChatOptions): UseOctavusChatReturn;\n\ninterface OctavusChatOptions {\n // Required: Transport for streaming events\n transport: Transport;\n\n // Optional: Function to request upload URLs for file uploads\n requestUploadUrls?: (\n files: { filename: string; mediaType: string; size: number }[],\n ) => Promise<UploadUrlsResponse>;\n\n // Optional: Client-side tool handlers\n // - Function: executes automatically and returns result\n // - 'interactive': appears in pendingClientTools for user input\n clientTools?: Record<string, ClientToolHandler>;\n\n // Optional: Pre-populate with existing messages (session restore)\n initialMessages?: UIMessage[];\n\n // Optional: Callbacks\n onError?: (error: OctavusError) => void; // Structured error with type, source, retryable\n onFinish?: () => void;\n onStop?: () => void; // Called when user stops generation\n onResourceUpdate?: (name: string, value: unknown) => void;\n}\n\ninterface UseOctavusChatReturn {\n // State\n messages: UIMessage[];\n status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n error: OctavusError | null; // Structured error with type, source, retryable\n\n // Connection (socket transport only - undefined for HTTP)\n connectionState: ConnectionState | undefined; // 'disconnected' | 'connecting' | 'connected' | 'error'\n connectionError: Error | undefined;\n\n // Client tools (interactive tools awaiting user input)\n pendingClientTools: Record<string, InteractiveTool[]>; // Keyed by tool name\n\n // Actions\n send: (\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ) => Promise<void>;\n stop: () => void;\n retry: () => Promise<void>; // Retry last trigger from same starting point\n canRetry: boolean; // Whether retry() can be called\n\n // Connection management (socket transport only - undefined for HTTP)\n connect: (() => Promise<void>) | undefined;\n disconnect: (() => void) | undefined;\n\n // File uploads (requires requestUploadUrls)\n uploadFiles: (\n files: FileList | File[],\n onProgress?: (fileIndex: number, progress: number) => void,\n ) => Promise<FileReference[]>;\n}\n\ninterface UserMessageInput {\n content?: string;\n files?: FileList | File[] | FileReference[];\n}\n```\n\n## Transport Reference\n\n### createHttpTransport\n\nCreates an HTTP/SSE transport using native `fetch()`:\n\n```typescript\nimport { createHttpTransport } from '@octavus/react';\n\nconst transport = createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n});\n```\n\n### createSocketTransport\n\nCreates a WebSocket/SockJS transport for real-time connections:\n\n```typescript\nimport { createSocketTransport } from '@octavus/react';\n\nconst transport = createSocketTransport({\n connect: () =>\n new Promise((resolve, reject) => {\n const ws = new WebSocket(`wss://api.example.com/stream?sessionId=${sessionId}`);\n ws.onopen = () => resolve(ws);\n ws.onerror = () => reject(new Error('Connection failed'));\n }),\n});\n```\n\nSocket transport provides additional connection management:\n\n```typescript\n// Access connection state directly\ntransport.connectionState; // 'disconnected' | 'connecting' | 'connected' | 'error'\n\n// Subscribe to state changes\ntransport.onConnectionStateChange((state, error) => {\n /* ... */\n});\n\n// Eager connection (instead of lazy on first send)\nawait transport.connect();\n\n// Manual disconnect\ntransport.disconnect();\n```\n\nFor detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).\n\n## Class Reference (Framework-Agnostic)\n\n### OctavusChat\n\n```typescript\nclass OctavusChat {\n constructor(options: OctavusChatOptions);\n\n // State (read-only)\n readonly messages: UIMessage[];\n readonly status: ChatStatus; // 'idle' | 'streaming' | 'error' | 'awaiting-input'\n readonly error: OctavusError | null; // Structured error\n readonly pendingClientTools: Record<string, InteractiveTool[]>; // Interactive tools\n\n // Actions\n send(\n triggerName: string,\n input?: Record<string, unknown>,\n options?: { userMessage?: UserMessageInput },\n ): Promise<void>;\n stop(): void;\n\n // Subscription\n subscribe(callback: () => void): () => void; // Returns unsubscribe function\n}\n```\n\n## Next Steps\n\n- [HTTP Transport](/docs/client-sdk/http-transport) \u2014 HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) \u2014 WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) \u2014 Working with message state\n- [Streaming](/docs/client-sdk/streaming) \u2014 Building streaming UIs\n- [Client Tools](/docs/client-sdk/client-tools) \u2014 Interactive browser-side tool handling\n- [Operations](/docs/client-sdk/execution-blocks) \u2014 Showing agent progress\n- [Error Handling](/docs/client-sdk/error-handling) \u2014 Handling errors with type guards\n- [File Uploads](/docs/client-sdk/file-uploads) \u2014 Uploading images and documents\n- [Examples](/docs/examples/overview) \u2014 Complete working examples\n",
823
825
  excerpt: "Client SDK Overview Octavus provides two packages for frontend integration: | Package | Purpose | Use When | |...",
824
826
  order: 1
825
827
  },
@@ -942,6 +944,7 @@ export async function POST(request: Request) {
942
944
  'Content-Type': 'text/event-stream',
943
945
  'Cache-Control': 'no-cache',
944
946
  Connection: 'keep-alive',
947
+ 'X-Accel-Buffering': 'no',
945
948
  },
946
949
  });
947
950
  }
@@ -1096,6 +1099,7 @@ app.post('/api/trigger', async (req, res) => {
1096
1099
  res.setHeader('Content-Type', 'text/event-stream');
1097
1100
  res.setHeader('Cache-Control', 'no-cache');
1098
1101
  res.setHeader('Connection', 'keep-alive');
1102
+ res.setHeader('X-Accel-Buffering', 'no');
1099
1103
 
1100
1104
  // Pipe the stream to the response
1101
1105
  const reader = stream.getReader();
@@ -1318,7 +1322,7 @@ See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list
1318
1322
  section: "protocol",
1319
1323
  title: "Skills",
1320
1324
  description: "Using Octavus skills for code execution and specialized capabilities.",
1321
- content: "\n# Skills\n\nSkills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are self-contained packages with documentation and scripts that run in secure sandboxes.\n\n## Overview\n\nOctavus Skills provide **provider-agnostic** code execution. They work with any LLM provider (Anthropic, OpenAI, Google) by using explicit tool calls and system prompt injection.\n\n### How Skills Work\n\n1. **Skill Definition**: Skills are defined in the protocol's `skills:` section\n2. **Skill Resolution**: Skills are resolved from available sources (see below)\n3. **Sandbox Execution**: When a skill is used, code runs in an isolated sandbox environment\n4. **File Generation**: Files saved to `/output/` are automatically captured and made available for download\n\n### Skill Sources\n\nSkills come from two sources, visible in the Skills tab of your organization:\n\n| Source | Badge in UI | Visibility | Example |\n| ----------- | ----------- | ------------------------------ | ------------------ |\n| **Octavus** | `Octavus` | Available to all organizations | `qr-code` |\n| **Custom** | None | Private to your organization | `my-company-skill` |\n\nWhen you reference a skill in your protocol, Octavus resolves it from your available skills. If you create a custom skill with the same name as an Octavus skill, your custom skill takes precedence.\n\n## Defining Skills\n\nDefine skills in the protocol's `skills:` section:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n```\n\n### Skill Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------------------------------------- |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` (default: `description`) |\n| `description` | No | Custom description shown to users (overrides skill's built-in description) |\n\n### Display Modes\n\n| Mode | Behavior |\n| ------------- | ------------------------------------------- |\n| `hidden` | Skill usage not shown to users |\n| `name` | Shows skill name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams progress if available |\n\n## Enabling Skills\n\nAfter defining skills in the `skills:` section, specify which skills are available. Skills work in both interactive agents and workers.\n\n### Interactive Agents\n\nReference skills in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account]\n skills: [qr-code]\n agentic: true\n```\n\n### Workers and Named Threads\n\nReference skills per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nsteps:\n Start thread:\n block: start-thread\n thread: worker\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n maxSteps: 10\n```\n\nThis also works for named threads in interactive agents, allowing different threads to have different skills.\n\n## Skill Tools\n\nWhen skills are enabled, the LLM has access to these tools:\n\n| Tool | Purpose |\n| -------------------- | --------------------------------------- |\n| `octavus_skill_read` | Read skill documentation (SKILL.md) |\n| `octavus_skill_list` | List available scripts in a skill |\n| `octavus_skill_run` | Execute a pre-built script from a skill |\n| `octavus_code_run` | Execute arbitrary Python/Bash code |\n| `octavus_file_write` | Create files in the sandbox |\n| `octavus_file_read` | Read files from the sandbox |\n\nThe LLM learns about available skills through system prompt injection and can use these tools to interact with skills.\n\n## Example: QR Code Generation\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n agentic: true\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond:\n block: next-message\n```\n\nWhen a user asks \"Create a QR code for octavus.ai\", the LLM will:\n\n1. Recognize the task matches the `qr-code` skill\n2. Call `octavus_skill_read` to learn how to use the skill\n3. Execute code (via `octavus_code_run` or `octavus_skill_run`) to generate the QR code\n4. Save the image to `/output/` in the sandbox\n5. The file is automatically captured and made available for download\n\n## File Output\n\nFiles saved to `/output/` in the sandbox are automatically:\n\n1. **Captured** after code execution\n2. **Uploaded** to S3 storage\n3. **Made available** via presigned URLs\n4. **Included** in the message as file parts\n\nFiles persist across page refreshes and are stored in the session's message history.\n\n## Skill Format\n\nSkills follow the [Agent Skills](https://agentskills.io) open standard:\n\n- `SKILL.md` - Required skill documentation with YAML frontmatter\n- `scripts/` - Optional executable code (Python/Bash)\n- `references/` - Optional documentation loaded as needed\n- `assets/` - Optional files used in outputs (templates, images)\n\n### SKILL.md Format\n\n````yaml\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\nversion: 1.0.0\nlicense: MIT\nauthor: Octavus Team\n---\n\n# QR Code Generator\n\n## Overview\n\nThis skill creates QR codes from text data using Python...\n\n## Quick Start\n\nGenerate a QR code with Python:\n\n```python\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n# ... code to generate QR code ...\n````\n\n## Scripts Reference\n\n### scripts/generate.py\n\nMain script for generating QR codes...\n\n````\n\n## Best Practices\n\n### 1. Clear Descriptions\n\nProvide clear, purpose-driven descriptions:\n\n```yaml\nskills:\n # Good - clear purpose\n qr-code:\n description: Generating QR codes for URLs, contact info, or any text data\n\n # Avoid - vague\n utility:\n description: Does stuff\n````\n\n### 2. When to Use Skills vs Tools\n\n| Use Skills When | Use Tools When |\n| ------------------------ | ---------------------------- |\n| Code execution needed | Simple API calls |\n| File generation | Database queries |\n| Complex calculations | External service integration |\n| Data processing | Authentication required |\n| Provider-agnostic needed | Backend-specific logic |\n\n### 3. Skill Selection\n\nDefine all skills available to this agent in the `skills:` section. Then specify which skills are available for the chat thread in `agent.skills`:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n pdf-processor:\n display: description\n description: Processing PDFs\n\n# Skills available for this chat thread\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis] # Skills available for this thread\n```\n\n### 4. Display Modes\n\nChoose appropriate display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n## Comparison: Skills vs Tools vs Provider Options\n\n| Feature | Octavus Skills | External Tools | Provider Tools/Skills |\n| ------------------ | ----------------- | ------------------- | --------------------- |\n| **Execution** | Isolated sandbox | Your backend | Provider servers |\n| **Provider** | Any (agnostic) | N/A | Provider-specific |\n| **Code Execution** | Yes | No | Yes (provider tools) |\n| **File Output** | Yes | No | Yes (provider skills) |\n| **Implementation** | Skill packages | Your code | Built-in |\n| **Cost** | Sandbox + LLM API | Your infrastructure | Included in API |\n\n## Uploading Custom Skills\n\nYou can upload custom skills to your organization:\n\n1. Create a skill following the [Agent Skills](https://agentskills.io) format\n2. Package it as a `.skill` bundle (ZIP file)\n3. Upload via the platform UI\n4. Reference by slug in your protocol\n\n```yaml\nskills:\n custom-analysis:\n display: description\n description: Custom analysis tool\n\nagent:\n skills: [custom-analysis]\n```\n\n## Sandbox Timeout\n\nThe default sandbox timeout is 5 minutes. You can configure a custom timeout using `sandboxTimeout` in the agent config or on individual `start-thread` blocks:\n\n```yaml\n# Agent-level timeout (applies to main thread)\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes (in milliseconds)\n```\n\n```yaml\n# Thread-level timeout (overrides agent-level for this thread)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour\n```\n\nThread-level `sandboxTimeout` takes priority over agent-level. Maximum: 1 hour (3,600,000 ms).\n\n## Security\n\nSkills run in isolated sandbox environments:\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n\n## Next Steps\n\n- [Agent Config](/docs/protocol/agent-config) \u2014 Configuring skills in agent settings\n- [Provider Options](/docs/protocol/provider-options) \u2014 Anthropic's built-in skills\n- [Skills Advanced Guide](/docs/protocol/skills-advanced) \u2014 Best practices and advanced patterns\n",
1325
+ content: "\n# Skills\n\nSkills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are self-contained packages with documentation and scripts that run in secure sandboxes.\n\n## Overview\n\nOctavus Skills provide **provider-agnostic** code execution. They work with any LLM provider (Anthropic, OpenAI, Google) by using explicit tool calls and system prompt injection.\n\n### How Skills Work\n\n1. **Skill Definition**: Skills are defined in the protocol's `skills:` section\n2. **Skill Resolution**: Skills are resolved from available sources (see below)\n3. **Sandbox Execution**: When a skill is used, code runs in an isolated sandbox environment\n4. **File Generation**: Files saved to `/output/` are automatically captured and made available for download\n\n### Skill Sources\n\nSkills come from two sources, visible in the Skills tab of your organization:\n\n| Source | Badge in UI | Visibility | Example |\n| ----------- | ----------- | ------------------------------ | ------------------ |\n| **Octavus** | `Octavus` | Available to all organizations | `qr-code` |\n| **Custom** | None | Private to your organization | `my-company-skill` |\n\nWhen you reference a skill in your protocol, Octavus resolves it from your available skills. If you create a custom skill with the same name as an Octavus skill, your custom skill takes precedence.\n\n## Defining Skills\n\nDefine skills in the protocol's `skills:` section:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n```\n\n### Skill Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------------------------------------- |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` (default: `description`) |\n| `description` | No | Custom description shown to users (overrides skill's built-in description) |\n\n### Display Modes\n\n| Mode | Behavior |\n| ------------- | ------------------------------------------- |\n| `hidden` | Skill usage not shown to users |\n| `name` | Shows skill name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams progress if available |\n\n## Enabling Skills\n\nAfter defining skills in the `skills:` section, specify which skills are available. Skills work in both interactive agents and workers.\n\n### Interactive Agents\n\nReference skills in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account]\n skills: [qr-code]\n agentic: true\n```\n\n### Workers and Named Threads\n\nReference skills per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nsteps:\n Start thread:\n block: start-thread\n thread: worker\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n maxSteps: 10\n```\n\nThis also works for named threads in interactive agents, allowing different threads to have different skills.\n\n## Skill Tools\n\nWhen skills are enabled, the LLM has access to these tools:\n\n| Tool | Purpose | Availability |\n| -------------------- | --------------------------------------- | -------------------- |\n| `octavus_skill_read` | Read skill documentation (SKILL.md) | All skills |\n| `octavus_skill_list` | List available scripts in a skill | All skills |\n| `octavus_skill_run` | Execute a pre-built script from a skill | All skills |\n| `octavus_code_run` | Execute arbitrary Python/Bash code | Standard skills only |\n| `octavus_file_write` | Create files in the sandbox | Standard skills only |\n| `octavus_file_read` | Read files from the sandbox | Standard skills only |\n\nThe LLM learns about available skills through system prompt injection and can use these tools to interact with skills.\n\nSkills that have [secrets](#skill-secrets) configured run in **secure mode**, where only `octavus_skill_read`, `octavus_skill_list`, and `octavus_skill_run` are available. See [Skill Secrets](#skill-secrets) below.\n\n## Example: QR Code Generation\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n agentic: true\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond:\n block: next-message\n```\n\nWhen a user asks \"Create a QR code for octavus.ai\", the LLM will:\n\n1. Recognize the task matches the `qr-code` skill\n2. Call `octavus_skill_read` to learn how to use the skill\n3. Execute code (via `octavus_code_run` or `octavus_skill_run`) to generate the QR code\n4. Save the image to `/output/` in the sandbox\n5. The file is automatically captured and made available for download\n\n## File Output\n\nFiles saved to `/output/` in the sandbox are automatically:\n\n1. **Captured** after code execution\n2. **Uploaded** to S3 storage\n3. **Made available** via presigned URLs\n4. **Included** in the message as file parts\n\nFiles persist across page refreshes and are stored in the session's message history.\n\n## Skill Format\n\nSkills follow the [Agent Skills](https://agentskills.io) open standard:\n\n- `SKILL.md` - Required skill documentation with YAML frontmatter\n- `scripts/` - Optional executable code (Python/Bash)\n- `references/` - Optional documentation loaded as needed\n- `assets/` - Optional files used in outputs (templates, images)\n\n### SKILL.md Format\n\n````yaml\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\nversion: 1.0.0\nlicense: MIT\nauthor: Octavus Team\n---\n\n# QR Code Generator\n\n## Overview\n\nThis skill creates QR codes from text data using Python...\n\n## Quick Start\n\nGenerate a QR code with Python:\n\n```python\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n# ... code to generate QR code ...\n````\n\n## Scripts Reference\n\n### scripts/generate.py\n\nMain script for generating QR codes...\n\n````\n\n### Frontmatter Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------ |\n| `name` | Yes | Skill slug (lowercase, hyphens) |\n| `description` | Yes | What the skill does (shown to the LLM) |\n| `version` | No | Semantic version string |\n| `license` | No | License identifier |\n| `author` | No | Skill author |\n| `secrets` | No | Array of secret declarations (enables secure mode) |\n\n## Best Practices\n\n### 1. Clear Descriptions\n\nProvide clear, purpose-driven descriptions:\n\n```yaml\nskills:\n # Good - clear purpose\n qr-code:\n description: Generating QR codes for URLs, contact info, or any text data\n\n # Avoid - vague\n utility:\n description: Does stuff\n````\n\n### 2. When to Use Skills vs Tools\n\n| Use Skills When | Use Tools When |\n| ------------------------ | ---------------------------- |\n| Code execution needed | Simple API calls |\n| File generation | Database queries |\n| Complex calculations | External service integration |\n| Data processing | Authentication required |\n| Provider-agnostic needed | Backend-specific logic |\n\n### 3. Skill Selection\n\nDefine all skills available to this agent in the `skills:` section. Then specify which skills are available for the chat thread in `agent.skills`:\n\n```yaml\n# All skills available to this agent (defined once at protocol level)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n pdf-processor:\n display: description\n description: Processing PDFs\n\n# Skills available for this chat thread\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis] # Skills available for this thread\n```\n\n### 4. Display Modes\n\nChoose appropriate display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n## Comparison: Skills vs Tools vs Provider Options\n\n| Feature | Octavus Skills | External Tools | Provider Tools/Skills |\n| ------------------ | ----------------- | ------------------- | --------------------- |\n| **Execution** | Isolated sandbox | Your backend | Provider servers |\n| **Provider** | Any (agnostic) | N/A | Provider-specific |\n| **Code Execution** | Yes | No | Yes (provider tools) |\n| **File Output** | Yes | No | Yes (provider skills) |\n| **Implementation** | Skill packages | Your code | Built-in |\n| **Cost** | Sandbox + LLM API | Your infrastructure | Included in API |\n\n## Uploading Custom Skills\n\nYou can upload custom skills to your organization:\n\n1. Create a skill following the [Agent Skills](https://agentskills.io) format\n2. Package it as a `.skill` bundle (ZIP file)\n3. Upload via the platform UI\n4. Reference by slug in your protocol\n\n```yaml\nskills:\n custom-analysis:\n display: description\n description: Custom analysis tool\n\nagent:\n skills: [custom-analysis]\n```\n\n## Sandbox Timeout\n\nThe default sandbox timeout is 5 minutes. You can configure a custom timeout using `sandboxTimeout` in the agent config or on individual `start-thread` blocks:\n\n```yaml\n# Agent-level timeout (applies to main thread)\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes (in milliseconds)\n```\n\n```yaml\n# Thread-level timeout (overrides agent-level for this thread)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour\n```\n\nThread-level `sandboxTimeout` takes priority over agent-level. Maximum: 1 hour (3,600,000 ms).\n\n## Skill Secrets\n\nSkills can declare secrets they need to function. When an organization configures those secrets, the skill runs in **secure mode** with additional isolation.\n\n### Declaring Secrets\n\nAdd a `secrets` array to your SKILL.md frontmatter:\n\n```yaml\n---\nname: github\ndescription: >\n Run GitHub CLI (gh) commands to manage repos, issues, PRs, and more.\nsecrets:\n - name: GITHUB_TOKEN\n description: GitHub personal access token with repo access\n required: true\n - name: GITHUB_ORG\n description: Default GitHub organization\n required: false\n---\n```\n\nEach secret declaration has:\n\n| Field | Required | Description |\n| ------------- | -------- | ----------------------------------------------------------- |\n| `name` | Yes | Environment variable name (uppercase, e.g., `GITHUB_TOKEN`) |\n| `description` | No | Explains what this secret is for (shown in the UI) |\n| `required` | No | Whether the secret is required (defaults to `true`) |\n\nSecret names must match the pattern `^[A-Z_][A-Z0-9_]*$` (uppercase letters, digits, and underscores).\n\n### Configuring Secrets\n\nOrganization admins configure secret values through the skill editor in the platform UI. Each organization maintains its own independent set of secrets for each skill.\n\nSecrets are encrypted at rest and only decrypted at execution time.\n\n### Secure Mode\n\nWhen a skill has secrets configured for the organization, it automatically runs in **secure mode**:\n\n- The skill gets its own **isolated sandbox** (separate from other skills)\n- Secrets are injected as **environment variables** available to all scripts\n- Only `octavus_skill_read`, `octavus_skill_list`, and `octavus_skill_run` are available \u2014 `octavus_code_run`, `octavus_file_write`, and `octavus_file_read` are blocked\n- Scripts receive input as **JSON via stdin** (using the `input` parameter on `octavus_skill_run`) instead of CLI args\n- All output (stdout/stderr) is **automatically redacted** for secret values before being returned to the LLM\n\n### Writing Scripts for Secure Skills\n\nScripts in secure skills read input from stdin as JSON and access secrets from environment variables:\n\n```python\nimport json\nimport os\nimport sys\n\ninput_data = json.load(sys.stdin)\ntoken = os.environ.get('GITHUB_TOKEN')\n\n# Use the token and input_data to perform the task\n```\n\nFor standard skills (without secrets), scripts receive input as CLI arguments. For secure skills, always use stdin JSON.\n\n## Security\n\nSkills run in isolated sandbox environments:\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n- **Secret redaction** \u2014 output from secure skills is automatically scanned for secret values\n\n## Next Steps\n\n- [Agent Config](/docs/protocol/agent-config) \u2014 Configuring skills in agent settings\n- [Provider Options](/docs/protocol/provider-options) \u2014 Anthropic's built-in skills\n- [Skills Advanced Guide](/docs/protocol/skills-advanced) \u2014 Best practices and advanced patterns\n",
1322
1326
  excerpt: "Skills Skills are knowledge packages that enable agents to execute code and generate files in isolated sandbox environments. Unlike external tools (which you implement in your backend), skills are...",
1323
1327
  order: 5
1324
1328
  },
@@ -1354,7 +1358,7 @@ See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list
1354
1358
  section: "protocol",
1355
1359
  title: "Skills Advanced Guide",
1356
1360
  description: "Best practices and advanced patterns for using Octavus skills.",
1357
- content: "\n# Skills Advanced Guide\n\nThis guide covers advanced patterns and best practices for using Octavus skills in your agents.\n\n## When to Use Skills\n\nSkills are ideal for:\n\n- **Code execution** - Running Python/Bash scripts\n- **File generation** - Creating images, PDFs, reports\n- **Data processing** - Analyzing, transforming, or visualizing data\n- **Provider-agnostic needs** - Features that should work with any LLM\n\nUse external tools instead when:\n\n- **Simple API calls** - Database queries, external services\n- **Authentication required** - Accessing user-specific resources\n- **Backend integration** - Tight coupling with your infrastructure\n\n## Skill Selection Strategy\n\n### Defining Available Skills\n\nDefine all skills in the `skills:` section, then reference which skills are available where they're used:\n\n**Interactive agents** \u2014 reference in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n pdf-processor:\n display: description\n description: Processing PDFs\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\n**Workers and named threads** \u2014 reference per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n\nsteps:\n Start analysis:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis]\n maxSteps: 10\n```\n\n### Match Skills to Use Cases\n\nDifferent threads can have different skills. Define all skills at the protocol level, then scope them to each thread:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n visualization:\n display: description\n description: Creating charts and visualizations\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\nFor a data analysis thread, you would specify `[data-analysis, visualization]` in `agent.skills` or in a `start-thread` block's `skills` field.\n\n## Display Mode Strategy\n\nChoose display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n### Guidelines\n\n- **`hidden`**: Background work that doesn't need user awareness\n- **`description`**: User-facing operations (default)\n- **`name`**: Quick operations where name is sufficient\n- **`stream`**: Long-running operations where progress matters\n\n## System Prompt Integration\n\nSkills are automatically injected into the system prompt. The LLM learns:\n\n1. **Available skills** - List of enabled skills with descriptions\n2. **How to use skills** - Instructions for using skill tools\n3. **Tool reference** - Available skill tools (`octavus_skill_read`, `octavus_code_run`, etc.)\n\nYou don't need to manually document skills in your system prompt. However, you can guide the LLM:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a helpful assistant that can generate QR codes.\n\n## When to Generate QR Codes\n\nGenerate QR codes when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Share WiFi credentials\n- Create scannable data\n\nUse the qr-code skill for all QR code generation tasks.\n```\n\n## Error Handling\n\nSkills handle errors gracefully:\n\n```yaml\n# Skill execution errors are returned to the LLM\n# The LLM can retry or explain the error to the user\n```\n\nCommon error scenarios:\n\n1. **Invalid skill slug** - Skill not found in organization\n2. **Code execution errors** - Syntax errors, runtime exceptions\n3. **Missing dependencies** - Required packages not installed\n4. **File I/O errors** - Permission issues, invalid paths\n\nThe LLM receives error messages and can:\n\n- Retry with corrected code\n- Explain errors to users\n- Suggest alternatives\n\n## File Output Patterns\n\n### Single File Output\n\n```python\n# Save single file to /output/\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\nqr = qrcode.QRCode()\nqr.add_data('https://example.com')\nimg = qr.make_image()\nimg.save(f'{output_dir}/qrcode.png')\n```\n\n### Multiple Files\n\n```python\n# Save multiple files\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Generate multiple outputs\nfor i in range(3):\n filename = f'{output_dir}/output_{i}.png'\n # ... generate file ...\n```\n\n### Structured Output\n\n```python\n# Save structured data + files\nimport json\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Save metadata\nmetadata = {\n 'files': ['chart.png', 'data.csv'],\n 'summary': 'Analysis complete'\n}\nwith open(f'{output_dir}/metadata.json', 'w') as f:\n json.dump(metadata, f)\n\n# Save actual files\n# ... generate chart.png and data.csv ...\n```\n\n## Performance Considerations\n\n### Lazy Initialization\n\nSandboxes are created only when a skill tool is first called:\n\n```yaml\nagent:\n skills: [qr-code] # Sandbox created on first skill tool call\n```\n\nThis means:\n\n- No cost if skills aren't used\n- Fast startup (no sandbox creation delay)\n- Each `next-message` execution gets its own sandbox with only the skills it needs\n\n### Timeout Limits\n\nSandboxes default to a 5-minute timeout. Configure `sandboxTimeout` on the agent config or per thread:\n\n```yaml\n# Agent-level\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes\n```\n\n```yaml\n# Thread-level (overrides agent-level)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour for long-running analysis\n```\n\nThread-level `sandboxTimeout` takes priority. Maximum: 1 hour (3,600,000 ms).\n\n### Sandbox Lifecycle\n\nEach `next-message` execution gets its own sandbox:\n\n- **Scoped** - Only contains the skills available to that thread\n- **Isolated** - Interactive agents and workers don't share sandboxes\n- **Resilient** - If a sandbox expires, it's transparently recreated\n- **Cleaned up** - Sandbox destroyed when the LLM call completes\n\n## Combining Skills with Tools\n\nSkills and tools can work together:\n\n```yaml\ntools:\n get-user-data:\n description: Fetch user data from database\n parameters:\n userId: { type: string }\n\nskills:\n data-analysis:\n display: description\n description: Analyzing data\n\nagent:\n tools: [get-user-data]\n skills: [data-analysis]\n agentic: true\n\nhandlers:\n analyze-user:\n Get user data:\n block: tool-call\n tool: get-user-data\n input:\n userId: USER_ID\n output: USER_DATA\n\n Analyze:\n block: next-message\n # LLM can use data-analysis skill with USER_DATA\n```\n\nPattern:\n\n1. Fetch data via tool (from your backend)\n2. LLM uses skill to analyze/process the data\n3. Generate outputs (files, reports)\n\n## Skill Development Tips\n\n### Writing SKILL.md\n\nFocus on **when** and **how** to use the skill:\n\n```markdown\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\n---\n\n# QR Code Generator\n\n## When to Use\n\nUse this skill when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Create scannable data\n\n## Quick Start\n\n[Clear examples of how to use the skill]\n```\n\n### Script Organization\n\nOrganize scripts logically:\n\n```\nskill-name/\n\u251C\u2500\u2500 SKILL.md\n\u2514\u2500\u2500 scripts/\n \u251C\u2500\u2500 generate.py # Main script\n \u251C\u2500\u2500 utils.py # Helper functions\n \u2514\u2500\u2500 requirements.txt # Dependencies\n```\n\n### Error Messages\n\nProvide helpful error messages:\n\n```python\ntry:\n # ... code ...\nexcept ValueError as e:\n print(f\"Error: Invalid input - {e}\")\n sys.exit(1)\n```\n\nThe LLM sees these errors and can retry or explain to users.\n\n## Security Considerations\n\n### Sandbox Isolation\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n\n### Input Validation\n\nSkills should validate inputs:\n\n```python\nimport sys\n\nif not data:\n print(\"Error: Data is required\")\n sys.exit(1)\n\nif len(data) > 1000:\n print(\"Error: Data too long (max 1000 characters)\")\n sys.exit(1)\n```\n\n### Resource Limits\n\nBe aware of:\n\n- **File size limits** - Large files may fail to upload\n- **Execution time** - Sandbox timeout (5-minute default, 1-hour maximum)\n- **Memory limits** - Sandbox environment constraints\n\n## Debugging Skills\n\n### Check Skill Documentation\n\nThe LLM can read skill docs:\n\n```python\n# LLM calls octavus_skill_read to see skill instructions\n```\n\n### Test Locally\n\nTest skills before uploading:\n\n```bash\n# Test skill locally\npython scripts/generate.py --data \"test\"\n```\n\n### Monitor Execution\n\nCheck execution logs in the platform debug view:\n\n- Tool calls and arguments\n- Code execution results\n- File outputs\n- Error messages\n\n## Common Patterns\n\n### Pattern 1: Generate and Return\n\n```yaml\n# User asks for QR code\n# LLM generates QR code\n# File automatically available for download\n```\n\n### Pattern 2: Analyze and Report\n\n```yaml\n# User provides data\n# LLM analyzes with skill\n# Generates report file\n# Returns summary + file link\n```\n\n### Pattern 3: Transform and Save\n\n```yaml\n# User uploads file (via tool)\n# LLM processes with skill\n# Generates transformed file\n# Returns new file link\n```\n\n## Best Practices Summary\n\n1. **Enable only needed skills** - Don't overwhelm the LLM\n2. **Choose appropriate display modes** - Match user experience needs\n3. **Write clear skill descriptions** - Help LLM understand when to use\n4. **Handle errors gracefully** - Provide helpful error messages\n5. **Test skills locally** - Verify before uploading\n6. **Monitor execution** - Check logs for issues\n7. **Combine with tools** - Use tools for data, skills for processing\n8. **Consider performance** - Be aware of timeouts and limits\n\n## Next Steps\n\n- [Skills](/docs/protocol/skills) - Basic skills documentation\n- [Agent Config](/docs/protocol/agent-config) - Configuring skills\n- [Tools](/docs/protocol/tools) - External tools integration\n",
1361
+ content: "\n# Skills Advanced Guide\n\nThis guide covers advanced patterns and best practices for using Octavus skills in your agents.\n\n## When to Use Skills\n\nSkills are ideal for:\n\n- **Code execution** - Running Python/Bash scripts\n- **File generation** - Creating images, PDFs, reports\n- **Data processing** - Analyzing, transforming, or visualizing data\n- **Provider-agnostic needs** - Features that should work with any LLM\n\nUse external tools instead when:\n\n- **Simple API calls** - Database queries, external services\n- **Authentication required** - Accessing user-specific resources\n- **Backend integration** - Tight coupling with your infrastructure\n\n## Skill Selection Strategy\n\n### Defining Available Skills\n\nDefine all skills in the `skills:` section, then reference which skills are available where they're used:\n\n**Interactive agents** \u2014 reference in `agent.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n pdf-processor:\n display: description\n description: Processing PDFs\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\n**Workers and named threads** \u2014 reference per-thread in `start-thread.skills`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data\n\nsteps:\n Start analysis:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code, data-analysis]\n maxSteps: 10\n```\n\n### Match Skills to Use Cases\n\nDifferent threads can have different skills. Define all skills at the protocol level, then scope them to each thread:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n data-analysis:\n display: description\n description: Analyzing data and generating reports\n visualization:\n display: description\n description: Creating charts and visualizations\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n skills: [qr-code]\n```\n\nFor a data analysis thread, you would specify `[data-analysis, visualization]` in `agent.skills` or in a `start-thread` block's `skills` field.\n\n## Display Mode Strategy\n\nChoose display modes based on user experience:\n\n```yaml\nskills:\n # Background processing - hide from user\n data-analysis:\n display: hidden\n\n # User-facing generation - show description\n qr-code:\n display: description\n\n # Interactive progress - stream updates\n report-generation:\n display: stream\n```\n\n### Guidelines\n\n- **`hidden`**: Background work that doesn't need user awareness\n- **`description`**: User-facing operations (default)\n- **`name`**: Quick operations where name is sufficient\n- **`stream`**: Long-running operations where progress matters\n\n## System Prompt Integration\n\nSkills are automatically injected into the system prompt. The LLM learns:\n\n1. **Available skills** - List of enabled skills with descriptions\n2. **How to use skills** - Instructions for using skill tools\n3. **Tool reference** - Available skill tools (`octavus_skill_read`, `octavus_code_run`, etc.)\n\nYou don't need to manually document skills in your system prompt. However, you can guide the LLM:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a helpful assistant that can generate QR codes.\n\n## When to Generate QR Codes\n\nGenerate QR codes when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Share WiFi credentials\n- Create scannable data\n\nUse the qr-code skill for all QR code generation tasks.\n```\n\n## Error Handling\n\nSkills handle errors gracefully:\n\n```yaml\n# Skill execution errors are returned to the LLM\n# The LLM can retry or explain the error to the user\n```\n\nCommon error scenarios:\n\n1. **Invalid skill slug** - Skill not found in organization\n2. **Code execution errors** - Syntax errors, runtime exceptions\n3. **Missing dependencies** - Required packages not installed\n4. **File I/O errors** - Permission issues, invalid paths\n\nThe LLM receives error messages and can:\n\n- Retry with corrected code\n- Explain errors to users\n- Suggest alternatives\n\n## File Output Patterns\n\n### Single File Output\n\n```python\n# Save single file to /output/\nimport qrcode\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\nqr = qrcode.QRCode()\nqr.add_data('https://example.com')\nimg = qr.make_image()\nimg.save(f'{output_dir}/qrcode.png')\n```\n\n### Multiple Files\n\n```python\n# Save multiple files\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Generate multiple outputs\nfor i in range(3):\n filename = f'{output_dir}/output_{i}.png'\n # ... generate file ...\n```\n\n### Structured Output\n\n```python\n# Save structured data + files\nimport json\nimport os\n\noutput_dir = os.environ.get('OUTPUT_DIR', '/output')\n\n# Save metadata\nmetadata = {\n 'files': ['chart.png', 'data.csv'],\n 'summary': 'Analysis complete'\n}\nwith open(f'{output_dir}/metadata.json', 'w') as f:\n json.dump(metadata, f)\n\n# Save actual files\n# ... generate chart.png and data.csv ...\n```\n\n## Performance Considerations\n\n### Lazy Initialization\n\nSandboxes are created only when a skill tool is first called:\n\n```yaml\nagent:\n skills: [qr-code] # Sandbox created on first skill tool call\n```\n\nThis means:\n\n- No cost if skills aren't used\n- Fast startup (no sandbox creation delay)\n- Each `next-message` execution gets its own sandbox with only the skills it needs\n\n### Timeout Limits\n\nSandboxes default to a 5-minute timeout. Configure `sandboxTimeout` on the agent config or per thread:\n\n```yaml\n# Agent-level\nagent:\n model: anthropic/claude-sonnet-4-5\n skills: [data-analysis]\n sandboxTimeout: 1800000 # 30 minutes\n```\n\n```yaml\n# Thread-level (overrides agent-level)\nsteps:\n Start thread:\n block: start-thread\n thread: analysis\n skills: [data-analysis]\n sandboxTimeout: 3600000 # 1 hour for long-running analysis\n```\n\nThread-level `sandboxTimeout` takes priority. Maximum: 1 hour (3,600,000 ms).\n\n### Sandbox Lifecycle\n\nEach `next-message` execution gets its own sandbox:\n\n- **Scoped** - Only contains the skills available to that thread\n- **Isolated** - Interactive agents and workers don't share sandboxes\n- **Resilient** - If a sandbox expires, it's transparently recreated\n- **Cleaned up** - Sandbox destroyed when the LLM call completes\n\n## Combining Skills with Tools\n\nSkills and tools can work together:\n\n```yaml\ntools:\n get-user-data:\n description: Fetch user data from database\n parameters:\n userId: { type: string }\n\nskills:\n data-analysis:\n display: description\n description: Analyzing data\n\nagent:\n tools: [get-user-data]\n skills: [data-analysis]\n agentic: true\n\nhandlers:\n analyze-user:\n Get user data:\n block: tool-call\n tool: get-user-data\n input:\n userId: USER_ID\n output: USER_DATA\n\n Analyze:\n block: next-message\n # LLM can use data-analysis skill with USER_DATA\n```\n\nPattern:\n\n1. Fetch data via tool (from your backend)\n2. LLM uses skill to analyze/process the data\n3. Generate outputs (files, reports)\n\n## Secure Skills\n\nWhen a skill declares secrets and an organization configures them, the skill runs in secure mode with its own isolated sandbox.\n\n### Standard vs Secure Skills\n\n| Aspect | Standard Skills | Secure Skills |\n| ------------------- | --------------------------------- | --------------------------------------------------- |\n| **Sandbox** | Shared with other standard skills | Isolated (one per skill) |\n| **Available tools** | All 6 skill tools | `skill_read`, `skill_list`, `skill_run` only |\n| **Script input** | CLI arguments via `args` | JSON via stdin (use `input` parameter) |\n| **Environment** | No secrets | Secrets as env vars |\n| **Output** | Raw stdout/stderr | Redacted (secret values replaced with `[REDACTED]`) |\n\n### Writing Scripts for Secure Skills\n\nSecure skill scripts receive structured input via stdin (JSON) and access secrets from environment variables:\n\n```python\n#!/usr/bin/env python3\nimport json\nimport os\nimport sys\nimport subprocess\n\ninput_data = json.load(sys.stdin)\ntoken = os.environ[\"GITHUB_TOKEN\"]\n\nrepo = input_data.get(\"repo\", \"\")\nresult = subprocess.run(\n [\"gh\", \"repo\", \"view\", repo, \"--json\", \"name,description\"],\n capture_output=True, text=True,\n env={**os.environ, \"GH_TOKEN\": token}\n)\n\nprint(result.stdout)\n```\n\nKey patterns:\n\n- **Read stdin**: `json.load(sys.stdin)` to get the `input` object from the `octavus_skill_run` call\n- **Access secrets**: `os.environ[\"SECRET_NAME\"]` \u2014 secrets are injected as env vars\n- **Print output**: Write results to stdout \u2014 the LLM sees the (redacted) stdout\n- **Error handling**: Write errors to stderr and exit with non-zero code\n\n### Declaring Secrets in SKILL.md\n\n```yaml\n---\nname: github\ndescription: >\n Run GitHub CLI (gh) commands to manage repos, issues, PRs, and more.\nsecrets:\n - name: GITHUB_TOKEN\n description: GitHub personal access token with repo access\n required: true\n - name: GITHUB_ORG\n description: Default GitHub organization\n required: false\n---\n```\n\n### Testing Secure Skills Locally\n\nYou can test scripts locally by piping JSON to stdin:\n\n```bash\necho '{\"repo\": \"octavus-ai/agent-sdk\"}' | GITHUB_TOKEN=ghp_xxx python scripts/list-issues.py\n```\n\n## Skill Development Tips\n\n### Writing SKILL.md\n\nFocus on **when** and **how** to use the skill:\n\n```markdown\n---\nname: qr-code\ndescription: >\n Generate QR codes from text, URLs, or data. Use when the user needs to create\n a QR code for any purpose - sharing links, contact information, WiFi credentials,\n or any text data that should be scannable.\n---\n\n# QR Code Generator\n\n## When to Use\n\nUse this skill when users want to:\n\n- Share URLs easily\n- Provide contact information\n- Create scannable data\n\n## Quick Start\n\n[Clear examples of how to use the skill]\n```\n\n### Script Organization\n\nOrganize scripts logically:\n\n```\nskill-name/\n\u251C\u2500\u2500 SKILL.md\n\u2514\u2500\u2500 scripts/\n \u251C\u2500\u2500 generate.py # Main script\n \u251C\u2500\u2500 utils.py # Helper functions\n \u2514\u2500\u2500 requirements.txt # Dependencies\n```\n\n### Error Messages\n\nProvide helpful error messages:\n\n```python\ntry:\n # ... code ...\nexcept ValueError as e:\n print(f\"Error: Invalid input - {e}\")\n sys.exit(1)\n```\n\nThe LLM sees these errors and can retry or explain to users.\n\n## Security Considerations\n\n### Sandbox Isolation\n\n- **No network access** (unless explicitly configured)\n- **No persistent storage** (sandbox destroyed after each `next-message` execution)\n- **File output only** via `/output/` directory\n- **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)\n\n### Secret Protection\n\nFor skills with configured secrets:\n\n- **Isolated sandbox** \u2014 each secure skill gets its own sandbox, preventing cross-skill secret leakage\n- **No arbitrary code** \u2014 `octavus_code_run`, `octavus_file_write`, and `octavus_file_read` are blocked for secure skills, so only pre-built scripts can execute\n- **Output redaction** \u2014 all stdout and stderr are scanned for secret values before being returned to the LLM\n- **Encrypted at rest** \u2014 secrets are encrypted using AES-256-GCM and only decrypted at execution time\n\n### Input Validation\n\nSkills should validate inputs:\n\n```python\nimport sys\n\nif not data:\n print(\"Error: Data is required\")\n sys.exit(1)\n\nif len(data) > 1000:\n print(\"Error: Data too long (max 1000 characters)\")\n sys.exit(1)\n```\n\n### Resource Limits\n\nBe aware of:\n\n- **File size limits** - Large files may fail to upload\n- **Execution time** - Sandbox timeout (5-minute default, 1-hour maximum)\n- **Memory limits** - Sandbox environment constraints\n\n## Debugging Skills\n\n### Check Skill Documentation\n\nThe LLM can read skill docs:\n\n```python\n# LLM calls octavus_skill_read to see skill instructions\n```\n\n### Test Locally\n\nTest skills before uploading:\n\n```bash\n# Test skill locally\npython scripts/generate.py --data \"test\"\n```\n\n### Monitor Execution\n\nCheck execution logs in the platform debug view:\n\n- Tool calls and arguments\n- Code execution results\n- File outputs\n- Error messages\n\n## Common Patterns\n\n### Pattern 1: Generate and Return\n\n```yaml\n# User asks for QR code\n# LLM generates QR code\n# File automatically available for download\n```\n\n### Pattern 2: Analyze and Report\n\n```yaml\n# User provides data\n# LLM analyzes with skill\n# Generates report file\n# Returns summary + file link\n```\n\n### Pattern 3: Transform and Save\n\n```yaml\n# User uploads file (via tool)\n# LLM processes with skill\n# Generates transformed file\n# Returns new file link\n```\n\n## Best Practices Summary\n\n1. **Enable only needed skills** \u2014 Don't overwhelm the LLM\n2. **Choose appropriate display modes** \u2014 Match user experience needs\n3. **Write clear skill descriptions** \u2014 Help LLM understand when to use\n4. **Handle errors gracefully** \u2014 Provide helpful error messages\n5. **Test skills locally** \u2014 Verify before uploading\n6. **Monitor execution** \u2014 Check logs for issues\n7. **Combine with tools** \u2014 Use tools for data, skills for processing\n8. **Consider performance** \u2014 Be aware of timeouts and limits\n9. **Use secrets for credentials** \u2014 Declare secrets in frontmatter instead of hardcoding tokens\n10. **Design scripts for stdin input** \u2014 Secure skills receive JSON via stdin, so plan for both input methods if the skill might be used in either mode\n\n## Next Steps\n\n- [Skills](/docs/protocol/skills) - Basic skills documentation\n- [Agent Config](/docs/protocol/agent-config) - Configuring skills\n- [Tools](/docs/protocol/tools) - External tools integration\n",
1358
1362
  excerpt: "Skills Advanced Guide This guide covers advanced patterns and best practices for using Octavus skills in your agents. When to Use Skills Skills are ideal for: - Code execution - Running Python/Bash...",
1359
1363
  order: 9
1360
1364
  },
@@ -1442,7 +1446,7 @@ See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list
1442
1446
  section: "examples",
1443
1447
  title: "Next.js Chat",
1444
1448
  description: "Building a chat interface with Next.js and HTTP transport.",
1445
- content: "\n# Next.js Chat Example\n\nThis example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications.\n\n## What You're Building\n\nA chat interface that:\n\n- Creates sessions server-side\n- Streams AI responses in real-time\n- Handles tool calls on your server\n- Shows typing status during streaming\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] -->|\"POST /api/chat\"| NextJS[\"Next.js API<br/>Routes\"]\n NextJS -->|\"SSE\"| Browser\n NextJS --> Platform[\"Octavus Platform\"]\n```\n\n## Prerequisites\n\n- Next.js 14+ with App Router\n- Octavus account with API key\n- An agent configured in Octavus\n\n## Step 1: Install Dependencies\n\n```bash\nnpm install @octavus/server-sdk @octavus/react\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env.local\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\n```\n\n## Step 3: Create the Octavus Client\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n## Step 4: Create Upload URLs Endpoint (Optional)\n\nIf your agent supports file uploads (images, documents), create an endpoint to get presigned URLs:\n\n```typescript\n// app/api/upload-urls/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, files } = await request.json();\n\n // Get presigned URLs from Octavus\n const result = await octavus.files.getUploadUrls(sessionId, files);\n\n return NextResponse.json(result);\n}\n```\n\n## Step 5: Create Session Endpoint\n\nSessions hold conversation state. Create one when the user opens the chat:\n\n```typescript\n// app/api/sessions/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { agentId, input } = await request.json();\n\n // Create a new session with initial input variables\n const sessionId = await octavus.agentSessions.create(agentId, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n**Protocol Note:** The `input` object contains variables defined in your agent's protocol. For example, if your agent has `COMPANY_NAME` as an input variable:\n\n```typescript\nconst sessionId = await octavus.agentSessions.create(agentId, {\n COMPANY_NAME: 'Acme Corp',\n USER_ID: user.id,\n});\n```\n\n## Step 6: Create Trigger Endpoint\n\nTriggers execute agent actions. The `user-message` trigger is the most common:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to the session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Server-side tool handlers run on YOUR server, not Octavus\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n const user = await db.users.findUnique({ where: { id: userId } });\n return {\n name: user.name,\n email: user.email,\n plan: user.plan,\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await db.tickets.create({\n data: {\n summary: args.summary as string,\n priority: args.priority as string,\n },\n });\n return {\n ticketId: ticket.id,\n estimatedResponse: '24 hours',\n };\n },\n\n // Tools without handlers here are forwarded to the client\n // See Client Tools docs for handling on frontend\n },\n });\n\n // execute() handles both triggers and client tool continuations\n const events = session.execute(payload, { signal: request.signal });\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n```\n\n**Protocol Note:** Tool names and arguments are defined in your agent's protocol YAML. The tool handlers here must match those definitions. Tools without server handlers are forwarded to the client.\n\n## Step 7: Build the Chat Component\n\n```tsx\n// components/Chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create transport - memoized on sessionId\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Send triggers the 'user-message' action\n // The third argument adds the user message to the UI\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div key={msg.id} className={msg.role === 'user' ? 'text-right' : 'text-left'}>\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n if (part.type === 'tool-call') {\n return (\n <div key={i} className=\"text-sm opacity-70\">\n Using {part.toolName}...\n </div>\n );\n }\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n <button\n type=\"submit\"\n disabled={status === 'streaming'}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\"\n >\n {status === 'streaming' ? 'Sending...' : 'Send'}\n </button>\n </div>\n </form>\n </div>\n );\n}\n```\n\n## Step 8: Create the Page\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nconst AGENT_ID = 'your-agent-id'; // From Octavus dashboard\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n // Create session on mount\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: AGENT_ID,\n input: {\n COMPANY_NAME: 'Acme Corp',\n },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <div className=\"p-8\">Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Protocol Integration\n\nYour agent's protocol defines the triggers and tools. Here's how the code maps to protocol:\n\n### Triggers\n\n```yaml\n# In your agent's protocol.yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\nThe `send()` call maps directly:\n\n```typescript\nawait send(\n 'user-message', // trigger name\n { USER_MESSAGE: message }, // trigger inputs\n { userMessage: { content: message } },\n);\n```\n\n### Tools\n\n```yaml\n# In your agent's protocol.yaml\ntools:\n get-user-account:\n description: Fetch user account details\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\nTool handlers receive the parameters as `args`:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n // ...\n}\n```\n\n## Next Steps\n\n- [Protocol Overview](/docs/protocol/overview) \u2014 Define agent behavior\n- [Messages](/docs/client-sdk/messages) \u2014 Rich message rendering\n- [Streaming](/docs/client-sdk/streaming) \u2014 Advanced streaming UI\n",
1449
+ content: "\n# Next.js Chat Example\n\nThis example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications.\n\n## What You're Building\n\nA chat interface that:\n\n- Creates sessions server-side\n- Streams AI responses in real-time\n- Handles tool calls on your server\n- Shows typing status during streaming\n\n## Architecture\n\n```mermaid\nflowchart LR\n Browser[\"Browser<br/>(React)\"] -->|\"POST /api/chat\"| NextJS[\"Next.js API<br/>Routes\"]\n NextJS -->|\"SSE\"| Browser\n NextJS --> Platform[\"Octavus Platform\"]\n```\n\n## Prerequisites\n\n- Next.js 14+ with App Router\n- Octavus account with API key\n- An agent configured in Octavus\n\n## Step 1: Install Dependencies\n\n```bash\nnpm install @octavus/server-sdk @octavus/react\n```\n\n## Step 2: Configure Environment\n\n```bash\n# .env.local\nOCTAVUS_API_URL=https://octavus.ai\nOCTAVUS_API_KEY=your-api-key\n```\n\n## Step 3: Create the Octavus Client\n\n```typescript\n// lib/octavus.ts\nimport { OctavusClient } from '@octavus/server-sdk';\n\nexport const octavus = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n```\n\n## Step 4: Create Upload URLs Endpoint (Optional)\n\nIf your agent supports file uploads (images, documents), create an endpoint to get presigned URLs:\n\n```typescript\n// app/api/upload-urls/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { sessionId, files } = await request.json();\n\n // Get presigned URLs from Octavus\n const result = await octavus.files.getUploadUrls(sessionId, files);\n\n return NextResponse.json(result);\n}\n```\n\n## Step 5: Create Session Endpoint\n\nSessions hold conversation state. Create one when the user opens the chat:\n\n```typescript\n// app/api/sessions/route.ts\nimport { NextResponse } from 'next/server';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const { agentId, input } = await request.json();\n\n // Create a new session with initial input variables\n const sessionId = await octavus.agentSessions.create(agentId, input);\n\n return NextResponse.json({ sessionId });\n}\n```\n\n**Protocol Note:** The `input` object contains variables defined in your agent's protocol. For example, if your agent has `COMPANY_NAME` as an input variable:\n\n```typescript\nconst sessionId = await octavus.agentSessions.create(agentId, {\n COMPANY_NAME: 'Acme Corp',\n USER_ID: user.id,\n});\n```\n\n## Step 6: Create Trigger Endpoint\n\nTriggers execute agent actions. The `user-message` trigger is the most common:\n\n```typescript\n// app/api/trigger/route.ts\nimport { toSSEStream } from '@octavus/server-sdk';\nimport { octavus } from '@/lib/octavus';\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n // Attach to the session with tool handlers\n const session = octavus.agentSessions.attach(sessionId, {\n tools: {\n // Server-side tool handlers run on YOUR server, not Octavus\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n // Fetch from your database\n const user = await db.users.findUnique({ where: { id: userId } });\n return {\n name: user.name,\n email: user.email,\n plan: user.plan,\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await db.tickets.create({\n data: {\n summary: args.summary as string,\n priority: args.priority as string,\n },\n });\n return {\n ticketId: ticket.id,\n estimatedResponse: '24 hours',\n };\n },\n\n // Tools without handlers here are forwarded to the client\n // See Client Tools docs for handling on frontend\n },\n });\n\n // execute() handles both triggers and client tool continuations\n const events = session.execute(payload, { signal: request.signal });\n\n return new Response(toSSEStream(events), {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n });\n}\n```\n\n**Protocol Note:** Tool names and arguments are defined in your agent's protocol YAML. The tool handlers here must match those definitions. Tools without server handlers are forwarded to the client.\n\n## Step 7: Build the Chat Component\n\n```tsx\n// components/Chat.tsx\n'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\ninterface ChatProps {\n sessionId: string;\n}\n\nexport function Chat({ sessionId }: ChatProps) {\n const [inputValue, setInputValue] = useState('');\n\n // Create transport - memoized on sessionId\n const transport = useMemo(\n () =>\n createHttpTransport({\n request: (payload, options) =>\n fetch('/api/trigger', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, ...payload }),\n signal: options?.signal,\n }),\n }),\n [sessionId],\n );\n\n const { messages, status, send } = useOctavusChat({ transport });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!inputValue.trim() || status === 'streaming') return;\n\n const message = inputValue.trim();\n setInputValue('');\n\n // Send triggers the 'user-message' action\n // The third argument adds the user message to the UI\n await send('user-message', { USER_MESSAGE: message }, { userMessage: { content: message } });\n };\n\n return (\n <div className=\"flex flex-col h-screen\">\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {messages.map((msg) => (\n <div key={msg.id} className={msg.role === 'user' ? 'text-right' : 'text-left'}>\n <div\n className={`inline-block p-3 rounded-lg ${\n msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100'\n }`}\n >\n {msg.parts.map((part, i) => {\n if (part.type === 'text') {\n return <p key={i}>{part.text}</p>;\n }\n if (part.type === 'tool-call') {\n return (\n <div key={i} className=\"text-sm opacity-70\">\n Using {part.toolName}...\n </div>\n );\n }\n return null;\n })}\n </div>\n </div>\n ))}\n </div>\n\n {/* Input */}\n <form onSubmit={handleSubmit} className=\"p-4 border-t\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1 px-4 py-2 border rounded-lg\"\n disabled={status === 'streaming'}\n />\n <button\n type=\"submit\"\n disabled={status === 'streaming'}\n className=\"px-4 py-2 bg-blue-500 text-white rounded-lg\"\n >\n {status === 'streaming' ? 'Sending...' : 'Send'}\n </button>\n </div>\n </form>\n </div>\n );\n}\n```\n\n## Step 8: Create the Page\n\n```tsx\n// app/chat/page.tsx\n'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Chat } from '@/components/Chat';\n\nconst AGENT_ID = 'your-agent-id'; // From Octavus dashboard\n\nexport default function ChatPage() {\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n useEffect(() => {\n // Create session on mount\n fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: AGENT_ID,\n input: {\n COMPANY_NAME: 'Acme Corp',\n },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <div className=\"p-8\">Loading...</div>;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n## Protocol Integration\n\nYour agent's protocol defines the triggers and tools. Here's how the code maps to protocol:\n\n### Triggers\n\n```yaml\n# In your agent's protocol.yaml\ntriggers:\n user-message:\n description: User sends a chat message\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n```\n\nThe `send()` call maps directly:\n\n```typescript\nawait send(\n 'user-message', // trigger name\n { USER_MESSAGE: message }, // trigger inputs\n { userMessage: { content: message } },\n);\n```\n\n### Tools\n\n```yaml\n# In your agent's protocol.yaml\ntools:\n get-user-account:\n description: Fetch user account details\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\nTool handlers receive the parameters as `args`:\n\n```typescript\n'get-user-account': async (args) => {\n const userId = args.userId as string;\n // ...\n}\n```\n\n## Next Steps\n\n- [Protocol Overview](/docs/protocol/overview) \u2014 Define agent behavior\n- [Messages](/docs/client-sdk/messages) \u2014 Rich message rendering\n- [Streaming](/docs/client-sdk/streaming) \u2014 Advanced streaming UI\n",
1446
1450
  excerpt: "Next.js Chat Example This example builds a support chat interface using Next.js App Router with HTTP/SSE transport. This is the recommended pattern for most web applications. What You're Building A...",
1447
1451
  order: 2
1448
1452
  },
@@ -1506,4 +1510,4 @@ export {
1506
1510
  getDocSlugs,
1507
1511
  getSectionBySlug
1508
1512
  };
1509
- //# sourceMappingURL=chunk-JEOGYIRI.js.map
1513
+ //# sourceMappingURL=chunk-4WYG2JYA.js.map