@octavus/docs 2.11.0 → 2.13.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.
- package/content/02-server-sdk/02-sessions.md +1 -0
- package/content/02-server-sdk/05-cli.md +9 -3
- package/content/03-client-sdk/01-overview.md +17 -0
- package/content/03-client-sdk/06-http-transport.md +1 -0
- package/content/03-client-sdk/08-file-uploads.md +57 -3
- package/content/03-client-sdk/09-error-handling.md +28 -4
- package/content/04-protocol/01-overview.md +12 -6
- package/content/04-protocol/04-tools.md +4 -3
- package/content/04-protocol/07-agent-config.md +46 -1
- package/content/04-protocol/08-provider-options.md +2 -7
- package/content/04-protocol/11-workers.md +4 -2
- package/content/04-protocol/12-references.md +189 -0
- package/content/05-api-reference/02-sessions.md +6 -5
- package/content/05-api-reference/03-agents.md +31 -9
- package/dist/{chunk-H6M6M3MY.js → chunk-PYLADDXH.js} +51 -31
- package/dist/chunk-PYLADDXH.js.map +1 -0
- package/dist/{chunk-NCTX3Y2J.js → chunk-SNBEHHFU.js} +43 -25
- package/dist/chunk-SNBEHHFU.js.map +1 -0
- package/dist/{chunk-EIUCL4CP.js → chunk-Z5E72EIS.js} +51 -31
- package/dist/chunk-Z5E72EIS.js.map +1 -0
- package/dist/content.js +1 -1
- package/dist/docs.json +25 -16
- package/dist/index.js +1 -1
- package/dist/search-index.json +1 -1
- package/dist/search.js +1 -1
- package/dist/search.js.map +1 -1
- package/dist/sections.json +25 -16
- package/package.json +1 -1
- package/dist/chunk-6TO62UOU.js +0 -1489
- package/dist/chunk-6TO62UOU.js.map +0 -1
- package/dist/chunk-EIUCL4CP.js.map +0 -1
- package/dist/chunk-H6M6M3MY.js.map +0 -1
- package/dist/chunk-NCTX3Y2J.js.map +0 -1
package/dist/docs.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"section": "server-sdk",
|
|
23
23
|
"title": "Overview",
|
|
24
24
|
"description": "Introduction to the Octavus Server SDK for backend integration.",
|
|
25
|
-
"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.
|
|
25
|
+
"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.13.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) — Deep dive into session management\n- [Tools](/docs/server-sdk/tools) — Implementing tool handlers\n- [Streaming](/docs/server-sdk/streaming) — Understanding stream events\n- [Workers](/docs/server-sdk/workers) — Executing worker agents\n- [Debugging](/docs/server-sdk/debugging) — Model request tracing and debugging\n",
|
|
26
26
|
"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...",
|
|
27
27
|
"order": 1
|
|
28
28
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"section": "server-sdk",
|
|
32
32
|
"title": "Sessions",
|
|
33
33
|
"description": "Managing agent sessions with the Server SDK.",
|
|
34
|
-
"content": "\n# Sessions\n\nSessions represent conversations with an agent. They store conversation history, track resources and variables, and enable stateful interactions.\n\n## Creating Sessions\n\nCreate a session by specifying the agent ID and initial input variables:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\n// Create a session with the support-chat agent\nconst sessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n USER_ID: 'user-123', // Optional inputs\n});\n\nconsole.log('Session created:', sessionId);\n```\n\n## Getting Session Messages\n\nTo restore a conversation on page load, use `getMessages()` to retrieve UI-ready messages:\n\n```typescript\nconst session = await client.agentSessions.getMessages(sessionId);\n\nconsole.log({\n sessionId: session.sessionId,\n agentId: session.agentId,\n messages: session.messages.length, // UIMessage[] ready for frontend\n});\n```\n\nThe returned messages can be passed directly to the client SDK's `initialMessages` option.\n\n### UISessionState Interface\n\n```typescript\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready conversation history\n}\n```\n\n## Full Session State (Debug)\n\nFor debugging or internal use, you can retrieve the complete session state including all variables and internal message format:\n\n```typescript\nconst state = await client.agentSessions.get(sessionId);\n\nconsole.log({\n id: state.id,\n agentId: state.agentId,\n messages: state.messages.length, // ChatMessage[] (internal format)\n resources: state.resources,\n variables: state.variables,\n createdAt: state.createdAt,\n updatedAt: state.updatedAt,\n});\n```\n\n> **Note**: Use `getMessages()` for client-facing code. The `get()` method returns internal message format that includes hidden content not intended for end users.\n\n## Attaching to Sessions\n\nTo trigger actions on a session, you need to attach to it first:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n // Tool handlers (see Tools documentation)\n },\n resources: [\n // Resource watchers (optional)\n ],\n});\n```\n\n## Executing Requests\n\nOnce attached, execute requests on the session using `execute()`:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() handles both triggers and client tool continuations\nconst events = session.execute(\n { type: 'trigger', triggerName: 'user-message', input: { USER_MESSAGE: 'Hello!' } },\n { signal: request.signal },\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### Request Types\n\nThe `execute()` method accepts a discriminated union:\n\n```typescript\ntype SessionRequest = TriggerRequest | ContinueRequest;\n\n// Start a new conversation turn\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n}\n\n// Continue after client-side tool handling\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n```\n\nThis makes it easy to pass requests through from the client:\n\n```typescript\n// Simple passthrough from HTTP request body\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n /* ... */\n },\n });\n const events = session.execute(payload, { signal: request.signal });\n\n return new Response(toSSEStream(events));\n}\n```\n\n### Stop Support\n\nPass an abort signal to allow clients to stop generation:\n\n```typescript\nconst events = session.execute(request, {\n signal: request.signal, // Forward the client's abort signal\n});\n```\n\nWhen the client aborts the request, the signal propagates through to the LLM provider, stopping generation immediately. Any partial content is preserved.\n\n## WebSocket Handling\n\nFor WebSocket integrations, use `handleSocketMessage()` which manages abort controller lifecycle internally:\n\n```typescript\nimport type { SocketMessage } from '@octavus/server-sdk';\n\n// In your socket handler\nconn.on('data', async (rawData: string) => {\n const msg = JSON.parse(rawData);\n\n if (msg.type === 'trigger' || msg.type === 'continue' || msg.type === 'stop') {\n await session.handleSocketMessage(msg as SocketMessage, {\n onEvent: (event) => conn.write(JSON.stringify(event)),\n onFinish: async () => {\n // Fetch and persist messages to your database for restoration\n },\n });\n }\n});\n```\n\nThe `handleSocketMessage()` method:\n\n- Handles `trigger`, `continue`, and `stop` messages\n- Automatically aborts previous requests when a new one arrives\n- Streams events via the `onEvent` callback\n- Calls `onFinish` after streaming completes (not called if aborted)\n\nSee [Socket Chat Example](/docs/examples/socket-chat) for a complete implementation.\n\n## Session Lifecycle\n\n```mermaid\nflowchart TD\n A[1. CREATE] --> B[2. ATTACH]\n B --> C[3. TRIGGER]\n C --> C\n C --> D[4. RETRIEVE]\n D --> C\n C --> E[5. EXPIRE]\n C --> G[5b. CLEAR]\n G --> F\n E --> F{6. RESTORE?}\n F -->|Yes| C\n F -->|No| A\n\n A -.- A1[\"`**client.agentSessions.create()**\n Returns sessionId\n Initializes state`\"]\n\n B -.- B1[\"`**client.agentSessions.attach()**\n Configure tool handlers\n Configure resource watchers`\"]\n\n C -.- C1[\"`**session.execute()**\n Execute request\n Stream events\n Update state`\"]\n\n D -.- D1[\"`**client.agentSessions.getMessages()**\n Get UI-ready messages\n Check session status`\"]\n\n E -.- E1[\"`Sessions expire after\n 24 hours (configurable)`\"]\n\n G -.- G1[\"`**client.agentSessions.clear()**\n Programmatically clear state\n Session becomes expired`\"]\n\n F -.- F1[\"`**client.agentSessions.restore()**\n Restore from stored messages\n Or create new session`\"]\n```\n\n## Session Expiration\n\nSessions expire after a period of inactivity (default: 24 hours). When you call `getMessages()` or `get()`, the response includes a `status` field:\n\n```typescript\nconst result = await client.agentSessions.getMessages(sessionId);\n\nif (result.status === 'expired') {\n // Session has expired - restore or create new\n console.log('Session expired:', result.sessionId);\n} else {\n // Session is active\n console.log('Messages:', result.messages.length);\n}\n```\n\n### Response Types\n\n| Status | Type | Description |\n| --------- | --------------------- | ------------------------------------------------------------- |\n| `active` | `UISessionState` | Session is active, includes `messages` array |\n| `expired` | `ExpiredSessionState` | Session expired, includes `sessionId`, `agentId`, `createdAt` |\n\n## Persisting Chat History\n\nTo enable session restoration, store the chat messages in your own database after each interaction:\n\n```typescript\n// After each trigger completes, save messages\nconst result = await client.agentSessions.getMessages(sessionId);\n\nif (result.status === 'active') {\n // Store in your database\n await db.chats.update({\n where: { id: chatId },\n data: {\n sessionId: result.sessionId,\n messages: result.messages, // Store UIMessage[] as JSON\n },\n });\n}\n```\n\n> **Best Practice**: Store the full `UIMessage[]` array. This preserves all message parts (text, tool calls, files, etc.) needed for accurate restoration.\n\n## Restoring Sessions\n\nWhen a user returns to your app:\n\n```typescript\n// 1. Load stored data from your database\nconst chat = await db.chats.findUnique({ where: { id: chatId } });\n\n// 2. Check if session is still active\nconst result = await client.agentSessions.getMessages(chat.sessionId);\n\nif (result.status === 'active') {\n // Session is active - use it directly\n return {\n sessionId: result.sessionId,\n messages: result.messages,\n };\n}\n\n// 3. Session expired - restore from stored messages\nif (chat.messages && chat.messages.length > 0) {\n const restored = await client.agentSessions.restore(\n chat.sessionId,\n chat.messages,\n { COMPANY_NAME: 'Acme Corp' }, // Optional: same input as create()\n );\n\n if (restored.restored) {\n // Session restored successfully\n return {\n sessionId: restored.sessionId,\n messages: chat.messages,\n };\n }\n}\n\n// 4. Cannot restore - create new session\nconst newSessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n});\n\nreturn {\n sessionId: newSessionId,\n messages: [],\n};\n```\n\n### Restore Response\n\n```typescript\ninterface RestoreSessionResult {\n sessionId: string;\n restored: boolean; // true if restored, false if session was already active\n}\n```\n\n## Complete Example\n\nHere's a complete session management flow:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nasync function getOrCreateSession(chatId: string, agentId: string, input: Record<string, unknown>) {\n // Load existing chat data\n const chat = await db.chats.findUnique({ where: { id: chatId } });\n\n if (chat?.sessionId) {\n // Check session status\n const result = await client.agentSessions.getMessages(chat.sessionId);\n\n if (result.status === 'active') {\n return { sessionId: result.sessionId, messages: result.messages };\n }\n\n // Try to restore expired session\n if (chat.messages?.length > 0) {\n const restored = await client.agentSessions.restore(chat.sessionId, chat.messages, input);\n if (restored.restored) {\n return { sessionId: restored.sessionId, messages: chat.messages };\n }\n }\n }\n\n // Create new session\n const sessionId = await client.agentSessions.create(agentId, input);\n\n // Save to database\n await db.chats.upsert({\n where: { id: chatId },\n create: { id: chatId, sessionId, messages: [] },\n update: { sessionId, messages: [] },\n });\n\n return { sessionId, messages: [] };\n}\n```\n\n## Clearing Sessions\n\nTo programmatically clear a session's state (e.g., for testing reset/restore flows), use `clear()`:\n\n```typescript\nconst result = await client.agentSessions.clear(sessionId);\nconsole.log(result.cleared); // true\n```\n\nAfter clearing, the session transitions to `expired` status. You can then restore it with `restore()` or create a new session.\n\n```typescript\ninterface ClearSessionResult {\n sessionId: string;\n cleared: boolean;\n}\n```\n\nThis is idempotent — calling `clear()` on an already expired session succeeds without error.\n\n## Error Handling\n\n```typescript\nimport { ApiError } from '@octavus/server-sdk';\n\ntry {\n const session = await client.agentSessions.getMessages(sessionId);\n} catch (error) {\n if (error instanceof ApiError) {\n if (error.status === 404) {\n // Session not found or expired\n console.log('Session expired, create a new one');\n } else {\n console.error('API Error:', error.message);\n }\n }\n throw error;\n}\n```\n",
|
|
34
|
+
"content": "\n# Sessions\n\nSessions represent conversations with an agent. They store conversation history, track resources and variables, and enable stateful interactions.\n\n## Creating Sessions\n\nCreate a session by specifying the agent ID and initial input variables:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\n// Create a session with the support-chat agent\nconst sessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n PRODUCT_NAME: 'Widget Pro',\n USER_ID: 'user-123', // Optional inputs\n});\n\nconsole.log('Session created:', sessionId);\n```\n\n## Getting Session Messages\n\nTo restore a conversation on page load, use `getMessages()` to retrieve UI-ready messages:\n\n```typescript\nconst session = await client.agentSessions.getMessages(sessionId);\n\nconsole.log({\n sessionId: session.sessionId,\n agentId: session.agentId,\n messages: session.messages.length, // UIMessage[] ready for frontend\n});\n```\n\nThe returned messages can be passed directly to the client SDK's `initialMessages` option.\n\n### UISessionState Interface\n\n```typescript\ninterface UISessionState {\n sessionId: string;\n agentId: string;\n messages: UIMessage[]; // UI-ready conversation history\n}\n```\n\n## Full Session State (Debug)\n\nFor debugging or internal use, you can retrieve the complete session state including all variables and internal message format:\n\n```typescript\nconst state = await client.agentSessions.get(sessionId);\n\nconsole.log({\n id: state.id,\n agentId: state.agentId,\n messages: state.messages.length, // ChatMessage[] (internal format)\n resources: state.resources,\n variables: state.variables,\n createdAt: state.createdAt,\n updatedAt: state.updatedAt,\n});\n```\n\n> **Note**: Use `getMessages()` for client-facing code. The `get()` method returns internal message format that includes hidden content not intended for end users.\n\n## Attaching to Sessions\n\nTo trigger actions on a session, you need to attach to it first:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n // Tool handlers (see Tools documentation)\n },\n resources: [\n // Resource watchers (optional)\n ],\n});\n```\n\n## Executing Requests\n\nOnce attached, execute requests on the session using `execute()`:\n\n```typescript\nimport { toSSEStream } from '@octavus/server-sdk';\n\n// execute() handles both triggers and client tool continuations\nconst events = session.execute(\n { type: 'trigger', triggerName: 'user-message', input: { USER_MESSAGE: 'Hello!' } },\n { signal: request.signal },\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### Request Types\n\nThe `execute()` method accepts a discriminated union:\n\n```typescript\ntype SessionRequest = TriggerRequest | ContinueRequest;\n\n// Start a new conversation turn\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n rollbackAfterMessageId?: string | null; // For retry: truncate messages after this ID\n}\n\n// Continue after client-side tool handling\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n```\n\nThis makes it easy to pass requests through from the client:\n\n```typescript\n// Simple passthrough from HTTP request body\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n /* ... */\n },\n });\n const events = session.execute(payload, { signal: request.signal });\n\n return new Response(toSSEStream(events));\n}\n```\n\n### Stop Support\n\nPass an abort signal to allow clients to stop generation:\n\n```typescript\nconst events = session.execute(request, {\n signal: request.signal, // Forward the client's abort signal\n});\n```\n\nWhen the client aborts the request, the signal propagates through to the LLM provider, stopping generation immediately. Any partial content is preserved.\n\n## WebSocket Handling\n\nFor WebSocket integrations, use `handleSocketMessage()` which manages abort controller lifecycle internally:\n\n```typescript\nimport type { SocketMessage } from '@octavus/server-sdk';\n\n// In your socket handler\nconn.on('data', async (rawData: string) => {\n const msg = JSON.parse(rawData);\n\n if (msg.type === 'trigger' || msg.type === 'continue' || msg.type === 'stop') {\n await session.handleSocketMessage(msg as SocketMessage, {\n onEvent: (event) => conn.write(JSON.stringify(event)),\n onFinish: async () => {\n // Fetch and persist messages to your database for restoration\n },\n });\n }\n});\n```\n\nThe `handleSocketMessage()` method:\n\n- Handles `trigger`, `continue`, and `stop` messages\n- Automatically aborts previous requests when a new one arrives\n- Streams events via the `onEvent` callback\n- Calls `onFinish` after streaming completes (not called if aborted)\n\nSee [Socket Chat Example](/docs/examples/socket-chat) for a complete implementation.\n\n## Session Lifecycle\n\n```mermaid\nflowchart TD\n A[1. CREATE] --> B[2. ATTACH]\n B --> C[3. TRIGGER]\n C --> C\n C --> D[4. RETRIEVE]\n D --> C\n C --> E[5. EXPIRE]\n C --> G[5b. CLEAR]\n G --> F\n E --> F{6. RESTORE?}\n F -->|Yes| C\n F -->|No| A\n\n A -.- A1[\"`**client.agentSessions.create()**\n Returns sessionId\n Initializes state`\"]\n\n B -.- B1[\"`**client.agentSessions.attach()**\n Configure tool handlers\n Configure resource watchers`\"]\n\n C -.- C1[\"`**session.execute()**\n Execute request\n Stream events\n Update state`\"]\n\n D -.- D1[\"`**client.agentSessions.getMessages()**\n Get UI-ready messages\n Check session status`\"]\n\n E -.- E1[\"`Sessions expire after\n 24 hours (configurable)`\"]\n\n G -.- G1[\"`**client.agentSessions.clear()**\n Programmatically clear state\n Session becomes expired`\"]\n\n F -.- F1[\"`**client.agentSessions.restore()**\n Restore from stored messages\n Or create new session`\"]\n```\n\n## Session Expiration\n\nSessions expire after a period of inactivity (default: 24 hours). When you call `getMessages()` or `get()`, the response includes a `status` field:\n\n```typescript\nconst result = await client.agentSessions.getMessages(sessionId);\n\nif (result.status === 'expired') {\n // Session has expired - restore or create new\n console.log('Session expired:', result.sessionId);\n} else {\n // Session is active\n console.log('Messages:', result.messages.length);\n}\n```\n\n### Response Types\n\n| Status | Type | Description |\n| --------- | --------------------- | ------------------------------------------------------------- |\n| `active` | `UISessionState` | Session is active, includes `messages` array |\n| `expired` | `ExpiredSessionState` | Session expired, includes `sessionId`, `agentId`, `createdAt` |\n\n## Persisting Chat History\n\nTo enable session restoration, store the chat messages in your own database after each interaction:\n\n```typescript\n// After each trigger completes, save messages\nconst result = await client.agentSessions.getMessages(sessionId);\n\nif (result.status === 'active') {\n // Store in your database\n await db.chats.update({\n where: { id: chatId },\n data: {\n sessionId: result.sessionId,\n messages: result.messages, // Store UIMessage[] as JSON\n },\n });\n}\n```\n\n> **Best Practice**: Store the full `UIMessage[]` array. This preserves all message parts (text, tool calls, files, etc.) needed for accurate restoration.\n\n## Restoring Sessions\n\nWhen a user returns to your app:\n\n```typescript\n// 1. Load stored data from your database\nconst chat = await db.chats.findUnique({ where: { id: chatId } });\n\n// 2. Check if session is still active\nconst result = await client.agentSessions.getMessages(chat.sessionId);\n\nif (result.status === 'active') {\n // Session is active - use it directly\n return {\n sessionId: result.sessionId,\n messages: result.messages,\n };\n}\n\n// 3. Session expired - restore from stored messages\nif (chat.messages && chat.messages.length > 0) {\n const restored = await client.agentSessions.restore(\n chat.sessionId,\n chat.messages,\n { COMPANY_NAME: 'Acme Corp' }, // Optional: same input as create()\n );\n\n if (restored.restored) {\n // Session restored successfully\n return {\n sessionId: restored.sessionId,\n messages: chat.messages,\n };\n }\n}\n\n// 4. Cannot restore - create new session\nconst newSessionId = await client.agentSessions.create('support-chat', {\n COMPANY_NAME: 'Acme Corp',\n});\n\nreturn {\n sessionId: newSessionId,\n messages: [],\n};\n```\n\n### Restore Response\n\n```typescript\ninterface RestoreSessionResult {\n sessionId: string;\n restored: boolean; // true if restored, false if session was already active\n}\n```\n\n## Complete Example\n\nHere's a complete session management flow:\n\n```typescript\nimport { OctavusClient } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nasync function getOrCreateSession(chatId: string, agentId: string, input: Record<string, unknown>) {\n // Load existing chat data\n const chat = await db.chats.findUnique({ where: { id: chatId } });\n\n if (chat?.sessionId) {\n // Check session status\n const result = await client.agentSessions.getMessages(chat.sessionId);\n\n if (result.status === 'active') {\n return { sessionId: result.sessionId, messages: result.messages };\n }\n\n // Try to restore expired session\n if (chat.messages?.length > 0) {\n const restored = await client.agentSessions.restore(chat.sessionId, chat.messages, input);\n if (restored.restored) {\n return { sessionId: restored.sessionId, messages: chat.messages };\n }\n }\n }\n\n // Create new session\n const sessionId = await client.agentSessions.create(agentId, input);\n\n // Save to database\n await db.chats.upsert({\n where: { id: chatId },\n create: { id: chatId, sessionId, messages: [] },\n update: { sessionId, messages: [] },\n });\n\n return { sessionId, messages: [] };\n}\n```\n\n## Clearing Sessions\n\nTo programmatically clear a session's state (e.g., for testing reset/restore flows), use `clear()`:\n\n```typescript\nconst result = await client.agentSessions.clear(sessionId);\nconsole.log(result.cleared); // true\n```\n\nAfter clearing, the session transitions to `expired` status. You can then restore it with `restore()` or create a new session.\n\n```typescript\ninterface ClearSessionResult {\n sessionId: string;\n cleared: boolean;\n}\n```\n\nThis is idempotent — calling `clear()` on an already expired session succeeds without error.\n\n## Error Handling\n\n```typescript\nimport { ApiError } from '@octavus/server-sdk';\n\ntry {\n const session = await client.agentSessions.getMessages(sessionId);\n} catch (error) {\n if (error instanceof ApiError) {\n if (error.status === 404) {\n // Session not found or expired\n console.log('Session expired, create a new one');\n } else {\n console.error('API Error:', error.message);\n }\n }\n throw error;\n}\n```\n",
|
|
35
35
|
"excerpt": "Sessions Sessions represent conversations with an agent. They store conversation history, track resources and variables, and enable stateful interactions. Creating Sessions Create a session by...",
|
|
36
36
|
"order": 2
|
|
37
37
|
},
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"section": "server-sdk",
|
|
59
59
|
"title": "CLI",
|
|
60
60
|
"description": "Command-line interface for validating and syncing agent definitions.",
|
|
61
|
-
"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.
|
|
61
|
+
"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.13.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` — Output as JSON (for CI/CD parsing)\n- `--quiet` — Suppress non-essential output\n\n**Example output:**\n\n```\nℹ Reading agent from ./agents/my-agent...\nℹ Syncing support-chat...\n✓ 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` — Validation passed\n- `1` — Validation errors\n- `2` — 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────────────────────────────────────────────────────────────────────────────\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` — Output as JSON (for CI/CD parsing)\n- `--quiet` — Suppress non-essential output\n\n**Example output:**\n\n```\nℹ Archiving support-chat...\n✓ 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├── settings.json # Required: Agent metadata\n├── protocol.yaml # Required: Agent protocol\n├── prompts/ # Optional: Prompt templates\n│ ├── system.md\n│ └── user-message.md\n└── references/ # Optional: Reference documents\n └── 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** — Create `settings.json`, `protocol.yaml`, and prompts\n2. **Validate** — Run `octavus validate ./my-agent` to check for errors\n3. **Sync** — Run `octavus sync ./my-agent` to push to platform\n4. **Store agent ID** — Save the output ID in an environment variable\n5. **Use in app** — 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
62
|
"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 ...",
|
|
63
63
|
"order": 5
|
|
64
64
|
},
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"section": "client-sdk",
|
|
86
86
|
"title": "Overview",
|
|
87
87
|
"description": "Introduction to the Octavus Client SDKs for building chat interfaces.",
|
|
88
|
-
"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`** — 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.11.0`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `2.11.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## 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\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) — HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) — WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n- [Client Tools](/docs/client-sdk/client-tools) — Interactive browser-side tool handling\n- [Operations](/docs/client-sdk/execution-blocks) — Showing agent progress\n- [Error Handling](/docs/client-sdk/error-handling) — Handling errors with type guards\n- [File Uploads](/docs/client-sdk/file-uploads) — Uploading images and documents\n- [Examples](/docs/examples/overview) — Complete working examples\n",
|
|
88
|
+
"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`** — 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.13.0`\n\n### Other Frameworks\n\n```bash\nnpm install @octavus/client-sdk\n```\n\n**Current version:** `2.13.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) — HTTP/SSE integration (recommended)\n- [Socket Transport](/docs/client-sdk/socket-transport) — WebSocket and SockJS integration\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n- [Client Tools](/docs/client-sdk/client-tools) — Interactive browser-side tool handling\n- [Operations](/docs/client-sdk/execution-blocks) — Showing agent progress\n- [Error Handling](/docs/client-sdk/error-handling) — Handling errors with type guards\n- [File Uploads](/docs/client-sdk/file-uploads) — Uploading images and documents\n- [Examples](/docs/examples/overview) — Complete working examples\n",
|
|
89
89
|
"excerpt": "Client SDK Overview Octavus provides two packages for frontend integration: | Package | Purpose | Use When | |...",
|
|
90
90
|
"order": 1
|
|
91
91
|
},
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"section": "client-sdk",
|
|
131
131
|
"title": "HTTP Transport",
|
|
132
132
|
"description": "Using HTTP/SSE for streaming with Octavus in Next.js, Express, and other frameworks.",
|
|
133
|
-
"content": "\n# HTTP Transport\n\nThe HTTP transport uses standard HTTP requests with Server-Sent Events (SSE) for streaming. This is the simplest and most compatible transport option.\n\n## When to Use HTTP Transport\n\n| Use Case | Recommendation |\n| ---------------------------------------------- | -------------------------------------------------------------- |\n| Next.js, Remix, or similar frameworks | ✅ Use HTTP |\n| Standard web apps without special requirements | ✅ Use HTTP |\n| Serverless deployments (Vercel, etc.) | ✅ Use HTTP |\n| Need custom real-time events | Consider [Socket Transport](/docs/client-sdk/socket-transport) |\n\n## Basic Setup\n\n### Client\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\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, stop } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n // ... render chat\n}\n```\n\n### Server (Next.js API Route)\n\n```typescript\n// app/api/trigger/route.ts\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n return { name: 'Demo User', plan: 'pro' };\n },\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## Session Creation\n\nSessions should be created server-side before rendering the chat. There are two patterns:\n\n### Pattern 1: Create Session on Page Load\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 fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: 'your-agent-id',\n input: { COMPANY_NAME: 'Acme Corp' },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <LoadingSpinner />;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n### Pattern 2: Server-Side Session Creation (App Router)\n\n```tsx\n// app/chat/page.tsx\nimport { octavus } from '@/lib/octavus';\nimport { Chat } from '@/components/Chat';\n\nexport default async function ChatPage() {\n // Create session server-side\n const sessionId = await octavus.agentSessions.create('your-agent-id', {\n COMPANY_NAME: 'Acme Corp',\n });\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\nThis pattern is cleaner as the session is ready before the component renders.\n\n## Error Handling\n\nHandle errors with structured error information:\n\n```tsx\nimport { isRateLimitError, isProviderError } from '@octavus/react';\n\nconst { messages, status, error, send } = useOctavusChat({\n transport,\n onError: (err) => {\n console.error('Stream error:', err.errorType, err.message);\n\n if (isRateLimitError(err)) {\n toast.error(`Rate limited. Try again in ${err.retryAfter}s`);\n } else if (isProviderError(err)) {\n toast.error('AI service temporarily unavailable');\n } else {\n toast.error('Something went wrong');\n }\n },\n});\n\n// Also check error state\nif (error) {\n return <ErrorMessage error={error} />;\n}\n```\n\nSee [Error Handling](/docs/client-sdk/error-handling) for comprehensive error handling patterns.\n\n## Stop Streaming\n\nAllow users to cancel ongoing streams. When `stop()` is called:\n\n1. The HTTP request is aborted via the signal\n2. Any partial content is preserved in the message\n3. Tool calls in progress are marked as `cancelled`\n4. Status changes to `idle`\n\n```tsx\nconst { send, stop, status } = useOctavusChat({\n transport,\n onStop: () => {\n console.log('User stopped generation');\n },\n});\n\nreturn (\n <button\n onClick={status === 'streaming' ? stop : () => sendMessage()}\n disabled={status === 'streaming' && !inputValue}\n >\n {status === 'streaming' ? 'Stop' : 'Send'}\n </button>\n);\n```\n\n> **Important**: For stop to work end-to-end, pass the `options.signal` to your `fetch()` call and forward `request.signal` to `session.execute()` on the server.\n\n## Express Server\n\nFor non-Next.js backends:\n\n```typescript\nimport express from 'express';\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst app = express();\napp.use(express.json());\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\napp.post('/api/trigger', async (req, res) => {\n const { sessionId, ...payload } = req.body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n // Server-side tool handlers only\n // Tools without handlers are forwarded to the client\n },\n });\n\n // execute() handles both triggers and continuations\n const events = session.execute(payload);\n const stream = toSSEStream(events);\n\n // Set SSE headers\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n\n // Pipe the stream to the response\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n } finally {\n reader.releaseLock();\n res.end();\n }\n});\n```\n\n## Transport Options\n\n```typescript\ninterface HttpTransportOptions {\n // Single request handler for both triggers and continuations\n request: (request: HttpRequest, options?: HttpRequestOptions) => Promise<Response>;\n}\n\ninterface HttpRequestOptions {\n signal?: AbortSignal;\n}\n\n// Discriminated union for request types\ntype HttpRequest = TriggerRequest | ContinueRequest;\n\n// Start a new conversation turn\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n}\n\n// Continue after client-side tool handling\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n```\n\nThe `request` function receives a discriminated union. Spread the request onto your payload:\n\n```typescript\nrequest: (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## Protocol\n\n### Request Format\n\nThe `request` function receives a discriminated union with `type` to identify the request kind:\n\n**Trigger Request** (start a new turn):\n\n```json\n{\n \"sessionId\": \"sess_abc123\",\n \"type\": \"trigger\",\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"Hello\"\n }\n}\n```\n\n**Continue Request** (after client tool handling):\n\n```json\n{\n \"sessionId\": \"sess_abc123\",\n \"type\": \"continue\",\n \"executionId\": \"exec_xyz789\",\n \"toolResults\": [\n {\n \"toolCallId\": \"call_abc\",\n \"toolName\": \"get-browser-location\",\n \"result\": { \"lat\": 40.7128, \"lng\": -74.006 }\n }\n ]\n}\n```\n\n### Response Format\n\nThe server responds with an SSE stream:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg_xyz\",\"executionId\":\"exec_xyz789\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\"Hello\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\" there!\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\nIf client tools are needed, the stream pauses with a `client-tool-request` event:\n\n```\ndata: {\"type\":\"client-tool-request\",\"executionId\":\"exec_xyz789\",\"toolCalls\":[...]}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"client-tool-calls\",\"executionId\":\"exec_xyz789\"}\n\ndata: [DONE]\n```\n\nThe client handles the tools and sends a `continue` request to resume.\n\nSee [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list of event types.\n\n## Next Steps\n\n- [Quick Start](/docs/getting-started/quickstart) — Complete Next.js integration guide\n- [Client Tools](/docs/client-sdk/client-tools) — Handling tools on the client side\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n- [Error Handling](/docs/client-sdk/error-handling) — Handling errors with type guards\n",
|
|
133
|
+
"content": "\n# HTTP Transport\n\nThe HTTP transport uses standard HTTP requests with Server-Sent Events (SSE) for streaming. This is the simplest and most compatible transport option.\n\n## When to Use HTTP Transport\n\n| Use Case | Recommendation |\n| ---------------------------------------------- | -------------------------------------------------------------- |\n| Next.js, Remix, or similar frameworks | ✅ Use HTTP |\n| Standard web apps without special requirements | ✅ Use HTTP |\n| Serverless deployments (Vercel, etc.) | ✅ Use HTTP |\n| Need custom real-time events | Consider [Socket Transport](/docs/client-sdk/socket-transport) |\n\n## Basic Setup\n\n### Client\n\n```tsx\nimport { useMemo } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\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, stop } = useOctavusChat({ transport });\n\n const sendMessage = async (text: string) => {\n await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });\n };\n\n // ... render chat\n}\n```\n\n### Server (Next.js API Route)\n\n```typescript\n// app/api/trigger/route.ts\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\nexport async function POST(request: Request) {\n const body = await request.json();\n const { sessionId, ...payload } = body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n return { name: 'Demo User', plan: 'pro' };\n },\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## Session Creation\n\nSessions should be created server-side before rendering the chat. There are two patterns:\n\n### Pattern 1: Create Session on Page Load\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 fetch('/api/sessions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n agentId: 'your-agent-id',\n input: { COMPANY_NAME: 'Acme Corp' },\n }),\n })\n .then((res) => res.json())\n .then((data) => setSessionId(data.sessionId));\n }, []);\n\n if (!sessionId) {\n return <LoadingSpinner />;\n }\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\n### Pattern 2: Server-Side Session Creation (App Router)\n\n```tsx\n// app/chat/page.tsx\nimport { octavus } from '@/lib/octavus';\nimport { Chat } from '@/components/Chat';\n\nexport default async function ChatPage() {\n // Create session server-side\n const sessionId = await octavus.agentSessions.create('your-agent-id', {\n COMPANY_NAME: 'Acme Corp',\n });\n\n return <Chat sessionId={sessionId} />;\n}\n```\n\nThis pattern is cleaner as the session is ready before the component renders.\n\n## Error Handling\n\nHandle errors with structured error information:\n\n```tsx\nimport { isRateLimitError, isProviderError } from '@octavus/react';\n\nconst { messages, status, error, send } = useOctavusChat({\n transport,\n onError: (err) => {\n console.error('Stream error:', err.errorType, err.message);\n\n if (isRateLimitError(err)) {\n toast.error(`Rate limited. Try again in ${err.retryAfter}s`);\n } else if (isProviderError(err)) {\n toast.error('AI service temporarily unavailable');\n } else {\n toast.error('Something went wrong');\n }\n },\n});\n\n// Also check error state\nif (error) {\n return <ErrorMessage error={error} />;\n}\n```\n\nSee [Error Handling](/docs/client-sdk/error-handling) for comprehensive error handling patterns.\n\n## Stop Streaming\n\nAllow users to cancel ongoing streams. When `stop()` is called:\n\n1. The HTTP request is aborted via the signal\n2. Any partial content is preserved in the message\n3. Tool calls in progress are marked as `cancelled`\n4. Status changes to `idle`\n\n```tsx\nconst { send, stop, status } = useOctavusChat({\n transport,\n onStop: () => {\n console.log('User stopped generation');\n },\n});\n\nreturn (\n <button\n onClick={status === 'streaming' ? stop : () => sendMessage()}\n disabled={status === 'streaming' && !inputValue}\n >\n {status === 'streaming' ? 'Stop' : 'Send'}\n </button>\n);\n```\n\n> **Important**: For stop to work end-to-end, pass the `options.signal` to your `fetch()` call and forward `request.signal` to `session.execute()` on the server.\n\n## Express Server\n\nFor non-Next.js backends:\n\n```typescript\nimport express from 'express';\nimport { OctavusClient, toSSEStream } from '@octavus/server-sdk';\n\nconst app = express();\napp.use(express.json());\n\nconst client = new OctavusClient({\n baseUrl: process.env.OCTAVUS_API_URL!,\n apiKey: process.env.OCTAVUS_API_KEY!,\n});\n\napp.post('/api/trigger', async (req, res) => {\n const { sessionId, ...payload } = req.body;\n\n const session = client.agentSessions.attach(sessionId, {\n tools: {\n // Server-side tool handlers only\n // Tools without handlers are forwarded to the client\n },\n });\n\n // execute() handles both triggers and continuations\n const events = session.execute(payload);\n const stream = toSSEStream(events);\n\n // Set SSE headers\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n\n // Pipe the stream to the response\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n } finally {\n reader.releaseLock();\n res.end();\n }\n});\n```\n\n## Transport Options\n\n```typescript\ninterface HttpTransportOptions {\n // Single request handler for both triggers and continuations\n request: (request: HttpRequest, options?: HttpRequestOptions) => Promise<Response>;\n}\n\ninterface HttpRequestOptions {\n signal?: AbortSignal;\n}\n\n// Discriminated union for request types\ntype HttpRequest = TriggerRequest | ContinueRequest;\n\n// Start a new conversation turn\ninterface TriggerRequest {\n type: 'trigger';\n triggerName: string;\n input?: Record<string, unknown>;\n rollbackAfterMessageId?: string | null; // For retry: truncate messages after this ID\n}\n\n// Continue after client-side tool handling\ninterface ContinueRequest {\n type: 'continue';\n executionId: string;\n toolResults: ToolResult[];\n}\n```\n\nThe `request` function receives a discriminated union. Spread the request onto your payload:\n\n```typescript\nrequest: (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## Protocol\n\n### Request Format\n\nThe `request` function receives a discriminated union with `type` to identify the request kind:\n\n**Trigger Request** (start a new turn):\n\n```json\n{\n \"sessionId\": \"sess_abc123\",\n \"type\": \"trigger\",\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"Hello\"\n }\n}\n```\n\n**Continue Request** (after client tool handling):\n\n```json\n{\n \"sessionId\": \"sess_abc123\",\n \"type\": \"continue\",\n \"executionId\": \"exec_xyz789\",\n \"toolResults\": [\n {\n \"toolCallId\": \"call_abc\",\n \"toolName\": \"get-browser-location\",\n \"result\": { \"lat\": 40.7128, \"lng\": -74.006 }\n }\n ]\n}\n```\n\n### Response Format\n\nThe server responds with an SSE stream:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg_xyz\",\"executionId\":\"exec_xyz789\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\"Hello\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"msg_xyz\",\"delta\":\" there!\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\nIf client tools are needed, the stream pauses with a `client-tool-request` event:\n\n```\ndata: {\"type\":\"client-tool-request\",\"executionId\":\"exec_xyz789\",\"toolCalls\":[...]}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"client-tool-calls\",\"executionId\":\"exec_xyz789\"}\n\ndata: [DONE]\n```\n\nThe client handles the tools and sends a `continue` request to resume.\n\nSee [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list of event types.\n\n## Next Steps\n\n- [Quick Start](/docs/getting-started/quickstart) — Complete Next.js integration guide\n- [Client Tools](/docs/client-sdk/client-tools) — Handling tools on the client side\n- [Messages](/docs/client-sdk/messages) — Working with message state\n- [Streaming](/docs/client-sdk/streaming) — Building streaming UIs\n- [Error Handling](/docs/client-sdk/error-handling) — Handling errors with type guards\n",
|
|
134
134
|
"excerpt": "HTTP Transport The HTTP transport uses standard HTTP requests with Server-Sent Events (SSE) for streaming. This is the simplest and most compatible transport option. When to Use HTTP Transport | Use...",
|
|
135
135
|
"order": 6
|
|
136
136
|
},
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"section": "client-sdk",
|
|
149
149
|
"title": "File Uploads",
|
|
150
150
|
"description": "Uploading images and files for vision models and document processing.",
|
|
151
|
-
"content": "\n# File Uploads\n\nThe Client SDK supports uploading images and documents that can be sent with messages. This enables vision model capabilities (analyzing images) and document processing.\n\n## Overview\n\nFile uploads follow a two-step flow:\n\n1. **Request upload URLs** from the platform via your backend\n2. **Upload files directly to S3** using presigned URLs\n3. **Send file references** with your message\n\nThis architecture keeps your API key secure on the server while enabling fast, direct uploads.\n\n## Setup\n\n### Backend: Upload URLs Endpoint\n\nCreate an endpoint that proxies upload URL requests to the Octavus platform:\n\n```typescript\n// app/api/upload-urls/route.ts (Next.js)\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### Client: Configure File Uploads\n\nPass `requestUploadUrls` to the chat hook:\n\n```tsx\nimport { useMemo, useCallback } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\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 // Request upload URLs from your backend\n const requestUploadUrls = useCallback(\n async (files: { filename: string; mediaType: string; size: number }[]) => {\n const response = await fetch('/api/upload-urls', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, files }),\n });\n return response.json();\n },\n [sessionId],\n );\n\n const { messages, status, send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n // ...\n}\n```\n\n## Uploading Files\n\n### Method 1: Upload Before Sending\n\nFor the best UX (showing upload progress), upload files first, then send:\n\n```tsx\nimport { useState, useRef } from 'react';\nimport type { FileReference } from '@octavus/react';\n\nfunction ChatInput({ sessionId }: { sessionId: string }) {\n const [pendingFiles, setPendingFiles] = useState<FileReference[]>([]);\n const [uploading, setUploading] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const { send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n async function handleFileSelect(event: React.ChangeEvent<HTMLInputElement>) {\n const files = event.target.files;\n if (!files?.length) return;\n\n setUploading(true);\n try {\n // Upload files with progress tracking\n const fileRefs = await uploadFiles(files, (fileIndex, progress) => {\n console.log(`File ${fileIndex}: ${progress}%`);\n });\n setPendingFiles((prev) => [...prev, ...fileRefs]);\n } finally {\n setUploading(false);\n }\n }\n\n async function handleSend(message: string) {\n await send(\n 'user-message',\n {\n USER_MESSAGE: message,\n FILES: pendingFiles.length > 0 ? pendingFiles : undefined,\n },\n {\n userMessage: {\n content: message,\n files: pendingFiles.length > 0 ? pendingFiles : undefined,\n },\n },\n );\n setPendingFiles([]);\n }\n\n return (\n <div>\n {/* File preview */}\n {pendingFiles.map((file) => (\n <img key={file.id} src={file.url} alt={file.filename} className=\"h-16\" />\n ))}\n\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*,.pdf\"\n multiple\n onChange={handleFileSelect}\n className=\"hidden\"\n />\n\n <button onClick={() => fileInputRef.current?.click()} disabled={uploading}>\n {uploading ? 'Uploading...' : 'Attach'}\n </button>\n </div>\n );\n}\n```\n\n### Method 2: Upload on Send (Automatic)\n\nFor simpler implementations, pass `File` objects directly:\n\n```tsx\nasync function handleSend(message: string, files?: File[]) {\n await send(\n 'user-message',\n { USER_MESSAGE: message, FILES: files },\n { userMessage: { content: message, files } },\n );\n}\n```\n\nThe SDK automatically uploads the files before sending. Note: This doesn't provide upload progress.\n\n## FileReference Type\n\nFile references contain metadata and URLs:\n\n```typescript\ninterface FileReference {\n /** Unique file ID (platform-generated) */\n id: string;\n /** IANA media type (e.g., 'image/png', 'application/pdf') */\n mediaType: string;\n /** Presigned download URL (S3) */\n url: string;\n /** Original filename */\n filename?: string;\n /** File size in bytes */\n size?: number;\n}\n```\n\n## Protocol Integration\n\nTo accept files in your agent protocol, use the `file[]` type:\n\n```yaml\ntriggers:\n user-message:\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n FILES:\n type: file[]\n optional: true\n description: User-attached images for vision analysis\n\nhandlers:\n user-message:\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input:\n - USER_MESSAGE\n files:\n - FILES # Attach files to the message\n display: hidden\n\n Respond to user:\n block: next-message\n```\n\nThe `file` type is a built-in type representing uploaded files. Use `file[]` for arrays of files.\n\n## Supported File Types\n\n| Type | Media Types |\n| --------- | -------------------------------------------------------------------- |\n| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |\n| Documents | `application/pdf`, `text/plain`, `text/markdown`, `application/json` |\n\n## File Limits\n\n| Limit | Value |\n| --------------------- | ---------- |\n| Max file size | 10 MB |\n| Max total per request | 50 MB |\n| Max files per request | 20 |\n| Upload URL expiry | 15 minutes |\n| Download URL expiry | 24 hours |\n\n## Rendering User Files\n\nUser-uploaded files appear as `UIFilePart` in user messages:\n\n```tsx\nfunction UserMessage({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'file') {\n if (part.mediaType.startsWith('image/')) {\n return (\n <img\n key={i}\n src={part.url}\n alt={part.filename || 'Uploaded image'}\n className=\"max-h-48 rounded-lg\"\n />\n );\n }\n return (\n <a key={i} href={part.url} className=\"text-blue-500\">\n 📄 {part.filename}\n </a>\n );\n }\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## Server SDK: Files API\n\nThe Server SDK provides direct access to the Files API:\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// Get presigned upload URLs\nconst { files } = await client.files.getUploadUrls(sessionId, [\n { filename: 'photo.jpg', mediaType: 'image/jpeg', size: 102400 },\n { filename: 'doc.pdf', mediaType: 'application/pdf', size: 204800 },\n]);\n\n// files[0].id - Use in FileReference\n// files[0].uploadUrl - PUT to this URL to upload\n// files[0].downloadUrl - Use as FileReference.url\n```\n\n## Complete Example\n\nHere's a full chat input component with file upload:\n\n```tsx\n'use client';\n\nimport { useState, useRef, useMemo, useCallback } from 'react';\nimport { useOctavusChat, createHttpTransport, type FileReference } from '@octavus/react';\n\ninterface PendingFile {\n file: File;\n id: string;\n status: 'uploading' | 'done' | 'error';\n progress: number;\n fileRef?: FileReference;\n error?: string;\n}\n\nexport function Chat({ sessionId }: { sessionId: string }) {\n const [input, setInput] = useState('');\n const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([]);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const fileIdCounter = useRef(0);\n\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 requestUploadUrls = useCallback(\n async (files: { filename: string; mediaType: string; size: number }[]) => {\n const res = await fetch('/api/upload-urls', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, files }),\n });\n return res.json();\n },\n [sessionId],\n );\n\n const { messages, status, send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n const isUploading = pendingFiles.some((f) => f.status === 'uploading');\n const hasErrors = pendingFiles.some((f) => f.status === 'error');\n const allReady = pendingFiles.every((f) => f.status === 'done');\n\n async function handleFileSelect(e: React.ChangeEvent<HTMLInputElement>) {\n const files = Array.from(e.target.files ?? []);\n if (!files.length) return;\n e.target.value = '';\n\n const newPending: PendingFile[] = files.map((file) => ({\n file,\n id: `pending-${++fileIdCounter.current}`,\n status: 'uploading',\n progress: 0,\n }));\n\n setPendingFiles((prev) => [...prev, ...newPending]);\n\n for (const pending of newPending) {\n try {\n const [fileRef] = await uploadFiles([pending.file], (_, progress) => {\n setPendingFiles((prev) =>\n prev.map((f) => (f.id === pending.id ? { ...f, progress } : f)),\n );\n });\n setPendingFiles((prev) =>\n prev.map((f) => (f.id === pending.id ? { ...f, status: 'done', fileRef } : f)),\n );\n } catch (err) {\n setPendingFiles((prev) =>\n prev.map((f) =>\n f.id === pending.id ? { ...f, status: 'error', error: String(err) } : f,\n ),\n );\n }\n }\n }\n\n async function handleSubmit() {\n if ((!input.trim() && !pendingFiles.length) || !allReady) return;\n\n const fileRefs = pendingFiles.filter((f) => f.fileRef).map((f) => f.fileRef!);\n\n await send(\n 'user-message',\n {\n USER_MESSAGE: input,\n FILES: fileRefs.length > 0 ? fileRefs : undefined,\n },\n {\n userMessage: {\n content: input,\n files: fileRefs.length > 0 ? fileRefs : undefined,\n },\n },\n );\n\n setInput('');\n setPendingFiles([]);\n }\n\n return (\n <div>\n {/* Messages */}\n {messages.map((msg) => (\n <div key={msg.id}>{/* ... render message */}</div>\n ))}\n\n {/* Pending files */}\n {pendingFiles.length > 0 && (\n <div className=\"flex gap-2\">\n {pendingFiles.map((f) => (\n <div key={f.id} className=\"relative\">\n <img\n src={URL.createObjectURL(f.file)}\n alt={f.file.name}\n className=\"h-16 w-16 object-cover rounded\"\n />\n {f.status === 'uploading' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/50\">\n <span className=\"text-white text-xs\">{f.progress}%</span>\n </div>\n )}\n <button\n onClick={() => setPendingFiles((prev) => prev.filter((p) => p.id !== f.id))}\n className=\"absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5\"\n >\n ×\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Input */}\n <div className=\"flex gap-2\">\n <input type=\"file\" ref={fileInputRef} onChange={handleFileSelect} hidden />\n <button onClick={() => fileInputRef.current?.click()}>📎</button>\n <input\n value={input}\n onChange={(e) => setInput(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1\"\n />\n <button onClick={handleSubmit} disabled={isUploading || hasErrors}>\n {isUploading ? 'Uploading...' : 'Send'}\n </button>\n </div>\n </div>\n );\n}\n```\n",
|
|
151
|
+
"content": "\n# File Uploads\n\nThe Client SDK supports uploading images and documents that can be sent with messages. This enables vision model capabilities (analyzing images) and document processing.\n\n## Overview\n\nFile uploads follow a two-step flow:\n\n1. **Request upload URLs** from the platform via your backend\n2. **Upload files directly to S3** using presigned URLs\n3. **Send file references** with your message\n\nThis architecture keeps your API key secure on the server while enabling fast, direct uploads.\n\n## Setup\n\n### Backend: Upload URLs Endpoint\n\nCreate an endpoint that proxies upload URL requests to the Octavus platform:\n\n```typescript\n// app/api/upload-urls/route.ts (Next.js)\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### Client: Configure File Uploads\n\nPass `requestUploadUrls` to the chat hook:\n\n```tsx\nimport { useMemo, useCallback } from 'react';\nimport { useOctavusChat, createHttpTransport } from '@octavus/react';\n\nfunction Chat({ sessionId }: { sessionId: string }) {\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 // Request upload URLs from your backend\n const requestUploadUrls = useCallback(\n async (files: { filename: string; mediaType: string; size: number }[]) => {\n const response = await fetch('/api/upload-urls', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, files }),\n });\n return response.json();\n },\n [sessionId],\n );\n\n const { messages, status, send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n // Optional: configure upload timeout and retry behavior\n uploadOptions: {\n timeoutMs: 60_000, // Per-file timeout (default: 60s, set to 0 to disable)\n maxRetries: 2, // Retry attempts on transient failures (default: 2)\n retryDelayMs: 1_000, // Delay between retries (default: 1s)\n },\n });\n\n // ...\n}\n```\n\n## Uploading Files\n\n### Method 1: Upload Before Sending\n\nFor the best UX (showing upload progress), upload files first, then send:\n\n```tsx\nimport { useState, useRef } from 'react';\nimport type { FileReference } from '@octavus/react';\n\nfunction ChatInput({ sessionId }: { sessionId: string }) {\n const [pendingFiles, setPendingFiles] = useState<FileReference[]>([]);\n const [uploading, setUploading] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const { send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n async function handleFileSelect(event: React.ChangeEvent<HTMLInputElement>) {\n const files = event.target.files;\n if (!files?.length) return;\n\n setUploading(true);\n try {\n // Upload files with progress tracking\n const fileRefs = await uploadFiles(files, (fileIndex, progress) => {\n console.log(`File ${fileIndex}: ${progress}%`);\n });\n setPendingFiles((prev) => [...prev, ...fileRefs]);\n } finally {\n setUploading(false);\n }\n }\n\n async function handleSend(message: string) {\n await send(\n 'user-message',\n {\n USER_MESSAGE: message,\n FILES: pendingFiles.length > 0 ? pendingFiles : undefined,\n },\n {\n userMessage: {\n content: message,\n files: pendingFiles.length > 0 ? pendingFiles : undefined,\n },\n },\n );\n setPendingFiles([]);\n }\n\n return (\n <div>\n {/* File preview */}\n {pendingFiles.map((file) => (\n <img key={file.id} src={file.url} alt={file.filename} className=\"h-16\" />\n ))}\n\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*,.pdf\"\n multiple\n onChange={handleFileSelect}\n className=\"hidden\"\n />\n\n <button onClick={() => fileInputRef.current?.click()} disabled={uploading}>\n {uploading ? 'Uploading...' : 'Attach'}\n </button>\n </div>\n );\n}\n```\n\n### Method 2: Upload on Send (Automatic)\n\nFor simpler implementations, pass `File` objects directly:\n\n```tsx\nasync function handleSend(message: string, files?: File[]) {\n await send(\n 'user-message',\n { USER_MESSAGE: message, FILES: files },\n { userMessage: { content: message, files } },\n );\n}\n```\n\nThe SDK automatically uploads the files before sending. Note: This doesn't provide upload progress.\n\n## Upload Reliability\n\nUploads include built-in timeout and retry logic for handling transient failures (network errors, server issues, mobile network switches).\n\n**Default behavior:**\n\n- **Timeout**: 60 seconds per file — prevents uploads from hanging on stalled connections\n- **Retries**: 2 automatic retries on transient failures (network errors, 5xx, 429)\n- **Retry delay**: 1 second between retries\n- **Non-retryable errors** (4xx like 403, 404) fail immediately without retrying\n\nOnly the S3 upload is retried — the presigned URL stays valid for 15 minutes. On retry, the progress callback resets to 0%.\n\nConfigure via `uploadOptions`:\n\n```typescript\nconst { send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n uploadOptions: {\n timeoutMs: 120_000, // 2 minutes for large files\n maxRetries: 3,\n retryDelayMs: 2_000,\n },\n});\n```\n\nTo disable timeout or retries:\n\n```typescript\nuploadOptions: {\n timeoutMs: 0, // No timeout\n maxRetries: 0, // No retries\n}\n```\n\n### Using `OctavusChat` Directly\n\nWhen using the `OctavusChat` class directly (without the React hook), pass `uploadOptions` in the constructor:\n\n```typescript\nconst chat = new OctavusChat({\n transport,\n requestUploadUrls,\n uploadOptions: { timeoutMs: 120_000, maxRetries: 3 },\n});\n```\n\n## FileReference Type\n\nFile references contain metadata and URLs:\n\n```typescript\ninterface FileReference {\n /** Unique file ID (platform-generated) */\n id: string;\n /** IANA media type (e.g., 'image/png', 'application/pdf') */\n mediaType: string;\n /** Presigned download URL (S3) */\n url: string;\n /** Original filename */\n filename?: string;\n /** File size in bytes */\n size?: number;\n}\n```\n\n## Protocol Integration\n\nTo accept files in your agent protocol, use the `file[]` type:\n\n```yaml\ntriggers:\n user-message:\n input:\n USER_MESSAGE:\n type: string\n description: The user's message\n FILES:\n type: file[]\n optional: true\n description: User-attached images for vision analysis\n\nhandlers:\n user-message:\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input:\n - USER_MESSAGE\n files:\n - FILES # Attach files to the message\n display: hidden\n\n Respond to user:\n block: next-message\n```\n\nThe `file` type is a built-in type representing uploaded files. Use `file[]` for arrays of files.\n\n## Supported File Types\n\n| Type | Media Types |\n| --------- | -------------------------------------------------------------------- |\n| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |\n| Video | `video/mp4`, `video/webm`, `video/quicktime`, `video/mpeg` |\n| Documents | `application/pdf`, `text/plain`, `text/markdown`, `application/json` |\n\n## File Limits\n\n| Limit | Value |\n| --------------------- | ---------- |\n| Max file size | 100 MB |\n| Max total per request | 200 MB |\n| Upload URL expiry | 15 minutes |\n| Download URL expiry | 24 hours |\n\n## Rendering User Files\n\nUser-uploaded files appear as `UIFilePart` in user messages:\n\n```tsx\nfunction UserMessage({ message }: { message: UIMessage }) {\n return (\n <div>\n {message.parts.map((part, i) => {\n if (part.type === 'file') {\n if (part.mediaType.startsWith('image/')) {\n return (\n <img\n key={i}\n src={part.url}\n alt={part.filename || 'Uploaded image'}\n className=\"max-h-48 rounded-lg\"\n />\n );\n }\n return (\n <a key={i} href={part.url} className=\"text-blue-500\">\n 📄 {part.filename}\n </a>\n );\n }\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## Server SDK: Files API\n\nThe Server SDK provides direct access to the Files API:\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// Get presigned upload URLs\nconst { files } = await client.files.getUploadUrls(sessionId, [\n { filename: 'photo.jpg', mediaType: 'image/jpeg', size: 102400 },\n { filename: 'doc.pdf', mediaType: 'application/pdf', size: 204800 },\n]);\n\n// files[0].id - Use in FileReference\n// files[0].uploadUrl - PUT to this URL to upload\n// files[0].downloadUrl - Use as FileReference.url\n```\n\n## Complete Example\n\nHere's a full chat input component with file upload:\n\n```tsx\n'use client';\n\nimport { useState, useRef, useMemo, useCallback } from 'react';\nimport { useOctavusChat, createHttpTransport, type FileReference } from '@octavus/react';\n\ninterface PendingFile {\n file: File;\n id: string;\n status: 'uploading' | 'done' | 'error';\n progress: number;\n fileRef?: FileReference;\n error?: string;\n}\n\nexport function Chat({ sessionId }: { sessionId: string }) {\n const [input, setInput] = useState('');\n const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([]);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const fileIdCounter = useRef(0);\n\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 requestUploadUrls = useCallback(\n async (files: { filename: string; mediaType: string; size: number }[]) => {\n const res = await fetch('/api/upload-urls', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, files }),\n });\n return res.json();\n },\n [sessionId],\n );\n\n const { messages, status, send, uploadFiles } = useOctavusChat({\n transport,\n requestUploadUrls,\n });\n\n const isUploading = pendingFiles.some((f) => f.status === 'uploading');\n const hasErrors = pendingFiles.some((f) => f.status === 'error');\n const allReady = pendingFiles.every((f) => f.status === 'done');\n\n async function handleFileSelect(e: React.ChangeEvent<HTMLInputElement>) {\n const files = Array.from(e.target.files ?? []);\n if (!files.length) return;\n e.target.value = '';\n\n const newPending: PendingFile[] = files.map((file) => ({\n file,\n id: `pending-${++fileIdCounter.current}`,\n status: 'uploading',\n progress: 0,\n }));\n\n setPendingFiles((prev) => [...prev, ...newPending]);\n\n for (const pending of newPending) {\n try {\n const [fileRef] = await uploadFiles([pending.file], (_, progress) => {\n setPendingFiles((prev) =>\n prev.map((f) => (f.id === pending.id ? { ...f, progress } : f)),\n );\n });\n setPendingFiles((prev) =>\n prev.map((f) => (f.id === pending.id ? { ...f, status: 'done', fileRef } : f)),\n );\n } catch (err) {\n setPendingFiles((prev) =>\n prev.map((f) =>\n f.id === pending.id ? { ...f, status: 'error', error: String(err) } : f,\n ),\n );\n }\n }\n }\n\n async function handleSubmit() {\n if ((!input.trim() && !pendingFiles.length) || !allReady) return;\n\n const fileRefs = pendingFiles.filter((f) => f.fileRef).map((f) => f.fileRef!);\n\n await send(\n 'user-message',\n {\n USER_MESSAGE: input,\n FILES: fileRefs.length > 0 ? fileRefs : undefined,\n },\n {\n userMessage: {\n content: input,\n files: fileRefs.length > 0 ? fileRefs : undefined,\n },\n },\n );\n\n setInput('');\n setPendingFiles([]);\n }\n\n return (\n <div>\n {/* Messages */}\n {messages.map((msg) => (\n <div key={msg.id}>{/* ... render message */}</div>\n ))}\n\n {/* Pending files */}\n {pendingFiles.length > 0 && (\n <div className=\"flex gap-2\">\n {pendingFiles.map((f) => (\n <div key={f.id} className=\"relative\">\n <img\n src={URL.createObjectURL(f.file)}\n alt={f.file.name}\n className=\"h-16 w-16 object-cover rounded\"\n />\n {f.status === 'uploading' && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/50\">\n <span className=\"text-white text-xs\">{f.progress}%</span>\n </div>\n )}\n <button\n onClick={() => setPendingFiles((prev) => prev.filter((p) => p.id !== f.id))}\n className=\"absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-5 h-5\"\n >\n ×\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Input */}\n <div className=\"flex gap-2\">\n <input type=\"file\" ref={fileInputRef} onChange={handleFileSelect} hidden />\n <button onClick={() => fileInputRef.current?.click()}>📎</button>\n <input\n value={input}\n onChange={(e) => setInput(e.target.value)}\n placeholder=\"Type a message...\"\n className=\"flex-1\"\n />\n <button onClick={handleSubmit} disabled={isUploading || hasErrors}>\n {isUploading ? 'Uploading...' : 'Send'}\n </button>\n </div>\n </div>\n );\n}\n```\n",
|
|
152
152
|
"excerpt": "File Uploads The Client SDK supports uploading images and documents that can be sent with messages. This enables vision model capabilities (analyzing images) and document processing. Overview File...",
|
|
153
153
|
"order": 8
|
|
154
154
|
},
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"section": "client-sdk",
|
|
158
158
|
"title": "Error Handling",
|
|
159
159
|
"description": "Handling errors in streaming responses with structured error types.",
|
|
160
|
-
"content": "\n# Error Handling\n\nOctavus provides structured error handling across all transports. Errors are categorized by type and source, enabling you to build appropriate UI responses and monitoring.\n\n## Error Types\n\nThe `onError` callback receives an `OctavusError` with structured information:\n\n```typescript\nimport { useOctavusChat, type OctavusError } from '@octavus/react';\n\nconst { error, status } = useOctavusChat({\n transport,\n onError: (err: OctavusError) => {\n console.error('Chat error:', {\n type: err.errorType, // Error classification\n message: err.message, // Human-readable message\n source: err.source, // Where the error originated\n retryable: err.retryable, // Can be retried\n retryAfter: err.retryAfter, // Seconds to wait (rate limits)\n code: err.code, // Machine-readable code\n provider: err.provider, // Provider details (if applicable)\n });\n },\n});\n```\n\n## Error Classification\n\n### Error Types\n\n| Type | Description | Typical Response |\n| ---------------------- | --------------------- | ------------------- |\n| `rate_limit_error` | Too many requests | Show retry timer |\n| `quota_exceeded_error` | Usage quota exceeded | Show upgrade prompt |\n| `authentication_error` | Invalid API key | Check configuration |\n| `permission_error` | No access to resource | Check permissions |\n| `validation_error` | Invalid request | Fix request data |\n| `provider_error` | LLM provider issue | Retry or show error |\n| `provider_overloaded` | Provider at capacity | Retry with backoff |\n| `provider_timeout` | Provider timed out | Retry |\n| `tool_error` | Tool execution failed | Show tool error |\n| `internal_error` | Platform error | Show generic error |\n\n### Error Sources\n\n| Source | Description |\n| ---------- | -------------------------------------------- |\n| `platform` | Octavus platform error |\n| `provider` | LLM provider error (OpenAI, Anthropic, etc.) |\n| `tool` | Tool execution error |\n| `client` | Client-side error (network, parsing) |\n\n## Type Guards\n\nUse type guards to handle specific error types:\n\n```typescript\nimport {\n useOctavusChat,\n isRateLimitError,\n isAuthenticationError,\n isProviderError,\n isToolError,\n isRetryableError,\n} from '@octavus/react';\n\nconst { error } = useOctavusChat({\n transport,\n onError: (err) => {\n if (isRateLimitError(err)) {\n // Show countdown timer\n showRetryTimer(err.retryAfter ?? 60);\n return;\n }\n\n if (isAuthenticationError(err)) {\n // Configuration issue - shouldn't happen in production\n reportConfigError(err);\n return;\n }\n\n if (isProviderError(err)) {\n // LLM service issue\n showProviderError(err.provider?.name ?? 'AI service');\n return;\n }\n\n if (isToolError(err)) {\n // Tool failed - already shown inline\n return;\n }\n\n if (isRetryableError(err)) {\n // Generic retryable error\n showRetryButton();\n return;\n }\n\n // Non-retryable error\n showGenericError(err.message);\n },\n});\n```\n\n## Provider Error Details\n\nWhen errors come from LLM providers, additional details are available:\n\n```typescript\nif (isProviderError(error) && error.provider) {\n console.log({\n name: error.provider.name, // 'anthropic', 'openai', 'google'\n model: error.provider.model, // Model that caused the error\n statusCode: error.provider.statusCode, // HTTP status code\n errorType: error.provider.errorType, // Provider's error type\n requestId: error.provider.requestId, // For support tickets\n });\n}\n```\n\n## Building Error UI\n\n```tsx\nimport {\n useOctavusChat,\n isRateLimitError,\n isAuthenticationError,\n isProviderError,\n} from '@octavus/react';\n\nfunction Chat() {\n const { error, status } = useOctavusChat({ transport });\n\n return (\n <div>\n {/* Error display */}\n {error && (\n <div className=\"bg-red-50 border border-red-200 rounded-lg p-4\">\n <div className=\"font-medium text-red-800\">{getErrorTitle(error)}</div>\n <p className=\"text-red-600 text-sm mt-1\">{error.message}</p>\n {isRateLimitError(error) && error.retryAfter && (\n <p className=\"text-red-500 text-sm mt-2\">\n Please try again in {error.retryAfter} seconds\n </p>\n )}\n {
|
|
160
|
+
"content": "\n# Error Handling\n\nOctavus provides structured error handling across all transports. Errors are categorized by type and source, enabling you to build appropriate UI responses and monitoring.\n\n## Error Types\n\nThe `onError` callback receives an `OctavusError` with structured information:\n\n```typescript\nimport { useOctavusChat, type OctavusError } from '@octavus/react';\n\nconst { error, status } = useOctavusChat({\n transport,\n onError: (err: OctavusError) => {\n console.error('Chat error:', {\n type: err.errorType, // Error classification\n message: err.message, // Human-readable message\n source: err.source, // Where the error originated\n retryable: err.retryable, // Can be retried\n retryAfter: err.retryAfter, // Seconds to wait (rate limits)\n code: err.code, // Machine-readable code\n provider: err.provider, // Provider details (if applicable)\n });\n },\n});\n```\n\n## Error Classification\n\n### Error Types\n\n| Type | Description | Typical Response |\n| ---------------------- | --------------------- | ------------------- |\n| `rate_limit_error` | Too many requests | Show retry timer |\n| `quota_exceeded_error` | Usage quota exceeded | Show upgrade prompt |\n| `authentication_error` | Invalid API key | Check configuration |\n| `permission_error` | No access to resource | Check permissions |\n| `validation_error` | Invalid request | Fix request data |\n| `provider_error` | LLM provider issue | Retry or show error |\n| `provider_overloaded` | Provider at capacity | Retry with backoff |\n| `provider_timeout` | Provider timed out | Retry |\n| `tool_error` | Tool execution failed | Show tool error |\n| `internal_error` | Platform error | Show generic error |\n\n### Error Sources\n\n| Source | Description |\n| ---------- | -------------------------------------------- |\n| `platform` | Octavus platform error |\n| `provider` | LLM provider error (OpenAI, Anthropic, etc.) |\n| `tool` | Tool execution error |\n| `client` | Client-side error (network, parsing) |\n\n## Type Guards\n\nUse type guards to handle specific error types:\n\n```typescript\nimport {\n useOctavusChat,\n isRateLimitError,\n isAuthenticationError,\n isProviderError,\n isToolError,\n isRetryableError,\n} from '@octavus/react';\n\nconst { error } = useOctavusChat({\n transport,\n onError: (err) => {\n if (isRateLimitError(err)) {\n // Show countdown timer\n showRetryTimer(err.retryAfter ?? 60);\n return;\n }\n\n if (isAuthenticationError(err)) {\n // Configuration issue - shouldn't happen in production\n reportConfigError(err);\n return;\n }\n\n if (isProviderError(err)) {\n // LLM service issue\n showProviderError(err.provider?.name ?? 'AI service');\n return;\n }\n\n if (isToolError(err)) {\n // Tool failed - already shown inline\n return;\n }\n\n if (isRetryableError(err)) {\n // Generic retryable error\n showRetryButton();\n return;\n }\n\n // Non-retryable error\n showGenericError(err.message);\n },\n});\n```\n\n## Provider Error Details\n\nWhen errors come from LLM providers, additional details are available:\n\n```typescript\nif (isProviderError(error) && error.provider) {\n console.log({\n name: error.provider.name, // 'anthropic', 'openai', 'google'\n model: error.provider.model, // Model that caused the error\n statusCode: error.provider.statusCode, // HTTP status code\n errorType: error.provider.errorType, // Provider's error type\n requestId: error.provider.requestId, // For support tickets\n });\n}\n```\n\n## Retrying After Errors\n\nUse `retry()` to re-execute the last trigger from the same starting point. Messages are rolled back, the user message is re-added (if any), and the agent re-executes. Files are reused without re-uploading.\n\n```tsx\nconst { error, canRetry, retry } = useOctavusChat({ transport });\n\n// Retry after any error\nif (canRetry) {\n await retry();\n}\n```\n\n`retry()` also works after stopping (cancellation) or when the result is unsatisfactory — not just errors.\n\n## Building Error UI\n\n```tsx\nimport {\n useOctavusChat,\n isRateLimitError,\n isAuthenticationError,\n isProviderError,\n} from '@octavus/react';\n\nfunction Chat() {\n const { error, status, retry, canRetry } = useOctavusChat({ transport });\n\n return (\n <div>\n {/* Error display */}\n {error && (\n <div className=\"bg-red-50 border border-red-200 rounded-lg p-4\">\n <div className=\"font-medium text-red-800\">{getErrorTitle(error)}</div>\n <p className=\"text-red-600 text-sm mt-1\">{error.message}</p>\n {isRateLimitError(error) && error.retryAfter && (\n <p className=\"text-red-500 text-sm mt-2\">\n Please try again in {error.retryAfter} seconds\n </p>\n )}\n {canRetry && (\n <button className=\"mt-3 text-red-700 underline\" onClick={() => void retry()}>\n Retry\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction getErrorTitle(error: OctavusError): string {\n if (isRateLimitError(error)) return 'Service is busy';\n if (isAuthenticationError(error)) return 'Configuration error';\n if (isProviderError(error)) return 'AI service unavailable';\n return 'Something went wrong';\n}\n```\n\n## Monitoring & Logging\n\nLog errors for monitoring and debugging:\n\n```typescript\nuseOctavusChat({\n transport,\n onError: (err) => {\n // Send to your monitoring service\n analytics.track('octavus_error', {\n errorType: err.errorType,\n source: err.source,\n retryable: err.retryable,\n code: err.code,\n provider: err.provider?.name,\n });\n\n // Log for debugging\n console.error('[Octavus]', {\n type: err.errorType,\n message: err.message,\n source: err.source,\n provider: err.provider,\n });\n },\n});\n```\n\n## Error State\n\nThe hook exposes error state directly:\n\n```typescript\nconst { error, status, retry, canRetry } = useOctavusChat({ transport });\n\n// status is 'error' when an error occurred\n// error contains the OctavusError object\n\n// Option 1: Retry the same trigger (rolls back messages, re-executes)\nif (canRetry) {\n await retry();\n}\n\n// Option 2: Send a new message (clears the error)\nawait send('user-message', { USER_MESSAGE: 'Try again' });\n```\n\n## Rate Limit Handling\n\nRate limits include retry information:\n\n```typescript\nif (isRateLimitError(error)) {\n const waitTime = error.retryAfter ?? 60; // Default to 60 seconds\n\n // Show countdown\n setCountdown(waitTime);\n const timer = setInterval(() => {\n setCountdown((c) => {\n if (c <= 1) {\n clearInterval(timer);\n return 0;\n }\n return c - 1;\n });\n }, 1000);\n}\n```\n\n## Error Event Structure\n\nFor custom transports or direct event handling, errors follow this structure:\n\n```typescript\ninterface ErrorEvent {\n type: 'error';\n errorType: ErrorType;\n message: string;\n source: ErrorSource;\n retryable: boolean;\n retryAfter?: number;\n code?: string;\n provider?: {\n name: string;\n model?: string;\n statusCode?: number;\n errorType?: string;\n requestId?: string;\n };\n tool?: {\n name: string;\n callId?: string;\n };\n}\n```\n\n## Tool Errors\n\nTool errors are handled differently—they appear inline on the tool call:\n\n```tsx\nfunction ToolCallPart({ part }: { part: UIToolCallPart }) {\n return (\n <div>\n <span>{part.toolName}</span>\n\n {part.status === 'error' && <div className=\"text-red-500 text-sm mt-1\">{part.error}</div>}\n </div>\n );\n}\n```\n\nTool errors don't trigger `onError`—they're captured on the tool call part itself.\n",
|
|
161
161
|
"excerpt": "Error Handling Octavus provides structured error handling across all transports. Errors are categorized by type and source, enabling you to build appropriate UI responses and monitoring. Error Types...",
|
|
162
162
|
"order": 9
|
|
163
163
|
},
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
"section": "protocol",
|
|
176
176
|
"title": "Overview",
|
|
177
177
|
"description": "Introduction to Octavus agent protocols.",
|
|
178
|
-
"content": "\n# Protocol Overview\n\nAgent protocols define how an AI agent behaves. They're written in YAML and specify inputs, triggers, tools, and execution handlers.\n\n## Why Protocols?\n\nProtocols provide:\n\n- **Declarative definition** — Define behavior, not implementation\n- **Portable agents** — Move agents between projects\n- **Versioning** — Track changes with git\n- **Validation** — Catch errors before runtime\n- **Visualization** — Debug execution flows\n\n## Agent Formats\n\nOctavus supports two agent formats:\n\n| Format | Use Case | Structure |\n| ------------- | ------------------------------ | --------------------------------- |\n| `interactive` | Chat and multi-turn dialogue | `triggers` + `handlers` + `agent` |\n| `worker` | Background tasks and pipelines | `steps` + `output` |\n\n**Interactive agents** handle conversations — they respond to triggers (like user messages) and maintain session state across interactions.\n\n**Worker agents** execute tasks — they run steps sequentially and return an output value. Workers can be called independently or composed into interactive agents.\n\nSee [Workers](/docs/protocol/workers) for the worker protocol reference.\n\n## Interactive Protocol Structure\n\n```yaml\n# Agent inputs (provided when creating a session)\ninput:\n COMPANY_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\n# Persistent resources the agent can read/write\nresources:\n CONVERSATION_SUMMARY:\n description: Summary for handoff\n default: ''\n\n# How the agent can be invoked\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n request-human:\n description: User clicks \"Talk to Human\"\n\n# Temporary variables for execution (with types)\nvariables:\n SUMMARY:\n type: string\n TICKET:\n type: unknown\n\n# Tools the agent can use\ntools:\n get-user-account:\n description: Looking up your account\n parameters:\n userId: { type: string }\n\n# Octavus skills (provider-agnostic code execution)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\n# Agent configuration (model, tools, etc.)\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account]\n skills: [qr-code] # Enable skills\n imageModel: google/gemini-2.5-flash-image # Enable image generation\n agentic: true # Allow multiple tool calls\n thinking: medium # Extended reasoning\n\n# What happens when triggers fire\nhandlers:\n user-message:\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond to user:\n block: next-message\n```\n\n## File Structure\n\nEach agent is a folder with:\n\n```\nmy-agent/\n├── protocol.yaml # Main logic (required)\n├── settings.json # Agent metadata (required)\n
|
|
178
|
+
"content": "\n# Protocol Overview\n\nAgent protocols define how an AI agent behaves. They're written in YAML and specify inputs, triggers, tools, and execution handlers.\n\n## Why Protocols?\n\nProtocols provide:\n\n- **Declarative definition** — Define behavior, not implementation\n- **Portable agents** — Move agents between projects\n- **Versioning** — Track changes with git\n- **Validation** — Catch errors before runtime\n- **Visualization** — Debug execution flows\n\n## Agent Formats\n\nOctavus supports two agent formats:\n\n| Format | Use Case | Structure |\n| ------------- | ------------------------------ | --------------------------------- |\n| `interactive` | Chat and multi-turn dialogue | `triggers` + `handlers` + `agent` |\n| `worker` | Background tasks and pipelines | `steps` + `output` |\n\n**Interactive agents** handle conversations — they respond to triggers (like user messages) and maintain session state across interactions.\n\n**Worker agents** execute tasks — they run steps sequentially and return an output value. Workers can be called independently or composed into interactive agents.\n\nSee [Workers](/docs/protocol/workers) for the worker protocol reference.\n\n## Interactive Protocol Structure\n\n```yaml\n# Agent inputs (provided when creating a session)\ninput:\n COMPANY_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\n# Persistent resources the agent can read/write\nresources:\n CONVERSATION_SUMMARY:\n description: Summary for handoff\n default: ''\n\n# How the agent can be invoked\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n request-human:\n description: User clicks \"Talk to Human\"\n\n# Temporary variables for execution (with types)\nvariables:\n SUMMARY:\n type: string\n TICKET:\n type: unknown\n\n# Tools the agent can use\ntools:\n get-user-account:\n description: Looking up your account\n parameters:\n userId: { type: string }\n\n# Octavus skills (provider-agnostic code execution)\nskills:\n qr-code:\n display: description\n description: Generating QR codes\n\n# Agent configuration (model, tools, etc.)\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account]\n skills: [qr-code] # Enable skills\n imageModel: google/gemini-2.5-flash-image # Enable image generation\n webSearch: true # Enable web search\n agentic: true # Allow multiple tool calls\n thinking: medium # Extended reasoning\n\n# What happens when triggers fire\nhandlers:\n user-message:\n Add user message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Respond to user:\n block: next-message\n```\n\n## File Structure\n\nEach agent is a folder with:\n\n```\nmy-agent/\n├── protocol.yaml # Main logic (required)\n├── settings.json # Agent metadata (required)\n├── prompts/ # Prompt templates (supports subdirectories)\n│ ├── system.md\n│ ├── user-message.md\n│ └── shared/\n│ ├── company-info.md\n│ └── formatting-rules.md\n└── references/ # On-demand context documents (optional)\n └── api-guidelines.md\n```\n\nPrompts can be organized in subdirectories. In the protocol, reference nested prompts by their path relative to `prompts/` (without `.md`): `shared/company-info`.\n\nReferences are markdown files with YAML frontmatter that the agent can fetch on demand during execution. See [References](/docs/protocol/references).\n\n### settings.json\n\n```json\n{\n \"slug\": \"my-agent\",\n \"name\": \"My Agent\",\n \"description\": \"What this agent does\",\n \"format\": \"interactive\"\n}\n```\n\n| Field | Required | Description |\n| ------------- | -------- | ----------------------------------------------- |\n| `slug` | Yes | URL-safe identifier (lowercase, digits, dashes) |\n| `name` | Yes | Human-readable name |\n| `description` | No | Brief description |\n| `format` | Yes | `interactive` (chat) or `worker` (background) |\n\n## Naming Conventions\n\n- **Slugs**: `lowercase-with-dashes`\n- **Variables**: `UPPERCASE_SNAKE_CASE`\n- **Prompts**: `lowercase-with-dashes.md` (paths use `/` for subdirectories)\n- **Tools**: `lowercase-with-dashes`\n- **Triggers**: `lowercase-with-dashes`\n\n## Variables in Prompts\n\nReference variables with `{{VARIABLE_NAME}}`:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a support agent for {{COMPANY_NAME}}.\n\nHelp users with their {{PRODUCT_NAME}} questions.\n\n## Support Policies\n\n{{SUPPORT_POLICIES}}\n```\n\nVariables are replaced with their values at runtime. If a variable is not provided, the placeholder is kept as-is.\n\n## Prompt Interpolation\n\nInclude other prompts inside a prompt with `{{@path.md}}`:\n\n```markdown\n<!-- prompts/system.md -->\n\nYou are a customer support agent.\n\n{{@shared/company-info.md}}\n\n{{@shared/formatting-rules.md}}\n\nHelp users with their questions.\n```\n\nThe referenced prompt content is inserted before variable interpolation, so variables in included prompts work the same way. Circular references are not allowed and will be caught during validation.\n\n## Next Steps\n\n- [Input & Resources](/docs/protocol/input-resources) — Defining agent inputs\n- [Triggers](/docs/protocol/triggers) — How agents are invoked\n- [Tools](/docs/protocol/tools) — External capabilities\n- [Skills](/docs/protocol/skills) — Code execution and knowledge packages\n- [References](/docs/protocol/references) — On-demand context documents\n- [Handlers](/docs/protocol/handlers) — Execution blocks\n- [Agent Config](/docs/protocol/agent-config) — Model and settings\n- [Workers](/docs/protocol/workers) — Worker agent format\n- [Provider Options](/docs/protocol/provider-options) — Provider-specific features\n- [Types](/docs/protocol/types) — Custom type definitions\n",
|
|
179
179
|
"excerpt": "Protocol Overview Agent protocols define how an AI agent behaves. They're written in YAML and specify inputs, triggers, tools, and execution handlers. Why Protocols? Protocols provide: - Declarative...",
|
|
180
180
|
"order": 1
|
|
181
181
|
},
|
|
@@ -202,8 +202,8 @@
|
|
|
202
202
|
"section": "protocol",
|
|
203
203
|
"title": "Tools",
|
|
204
204
|
"description": "Defining external tools implemented in your backend.",
|
|
205
|
-
"content": "\n# Tools\n\nTools extend what agents can do. Octavus supports multiple types:\n\n1. **External Tools** — Defined in the protocol, implemented in your backend (this page)\n2. **Provider Tools** —
|
|
206
|
-
"excerpt": "Tools Tools extend what agents can do. Octavus supports multiple types: 1. External Tools — Defined in the protocol, implemented in your backend (this page) 2.
|
|
205
|
+
"content": "\n# Tools\n\nTools extend what agents can do. Octavus supports multiple types:\n\n1. **External Tools** — Defined in the protocol, implemented in your backend (this page)\n2. **Built-in Tools** — Provider-agnostic tools managed by Octavus (web search, image generation)\n3. **Provider Tools** — Provider-specific tools executed by the provider (e.g., Anthropic's code execution)\n4. **Skills** — Code execution and knowledge packages (see [Skills](/docs/protocol/skills))\n\nThis page covers external tools. Built-in tools are enabled via agent config — see [Web Search](/docs/protocol/agent-config#web-search) and [Image Generation](/docs/protocol/agent-config#image-generation). For provider-specific tools, see [Provider Options](/docs/protocol/provider-options). For code execution, see [Skills](/docs/protocol/skills).\n\n## External Tools\n\nExternal tools are defined in the `tools:` section and implemented in your backend.\n\n## Defining Tools\n\n```yaml\ntools:\n get-user-account:\n description: Looking up your account information\n display: description\n parameters:\n userId:\n type: string\n description: The user ID to look up\n```\n\n### Tool Fields\n\n| Field | Required | Description |\n| ------------- | -------- | ------------------------------------------------------------ |\n| `description` | Yes | What the tool does (shown to LLM and optionally user) |\n| `display` | No | How to show in UI: `hidden`, `name`, `description`, `stream` |\n| `parameters` | No | Input parameters the tool accepts |\n\n### Display Modes\n\n| Mode | Behavior |\n| ------------- | ------------------------------------------- |\n| `hidden` | Tool runs silently, user doesn't see it |\n| `name` | Shows tool name while executing |\n| `description` | Shows description while executing (default) |\n| `stream` | Streams tool progress if available |\n\n## Parameters\n\nTool calls are always objects where each parameter name maps to a value. The LLM generates: `{ param1: value1, param2: value2, ... }`\n\n### Parameter Fields\n\n| Field | Required | Description |\n| ------------- | -------- | -------------------------------------------------------------------------------- |\n| `type` | Yes | Data type: `string`, `number`, `integer`, `boolean`, `unknown`, or a custom type |\n| `description` | No | Describes what this parameter is for |\n| `optional` | No | If true, parameter is not required (default: false) |\n\n> **Tip**: You can use [custom types](/docs/protocol/types) for complex parameters like `type: ProductFilter` or `type: SearchOptions`.\n\n### Array Parameters\n\nFor array parameters, define a [top-level array type](/docs/protocol/types#top-level-array-types) and use it:\n\n```yaml\ntypes:\n CartItem:\n productId:\n type: string\n quantity:\n type: integer\n\n CartItemList:\n type: array\n items:\n type: CartItem\n\ntools:\n add-to-cart:\n description: Add items to cart\n parameters:\n items:\n type: CartItemList\n description: Items to add\n```\n\nThe tool receives: `{ items: [{ productId: \"...\", quantity: 1 }, ...] }`\n\n### Optional Parameters\n\nParameters are **required by default**. Use `optional: true` to make a parameter optional:\n\n```yaml\ntools:\n search-products:\n description: Search the product catalog\n parameters:\n query:\n type: string\n description: Search query\n\n category:\n type: string\n description: Filter by category\n optional: true\n\n maxPrice:\n type: number\n description: Maximum price filter\n optional: true\n\n inStock:\n type: boolean\n description: Only show in-stock items\n optional: true\n```\n\n## Making Tools Available\n\nTools defined in `tools:` are available. To make them usable by the LLM, add them to `agent.tools`:\n\n```yaml\ntools:\n get-user-account:\n description: Look up user account\n parameters:\n userId: { type: string }\n\n create-support-ticket:\n description: Create a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string } # low, medium, high, urgent\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools:\n - get-user-account\n - create-support-ticket # LLM can decide when to call these\n agentic: true\n```\n\n## Tool Invocation Modes\n\n### LLM-Decided (Agentic)\n\nThe LLM decides when to call tools based on the conversation:\n\n```yaml\nagent:\n tools: [get-user-account, create-support-ticket]\n agentic: true # Allow multiple tool calls\n maxSteps: 10 # Max tool call cycles\n```\n\n### Deterministic (Block-Based)\n\nForce tool calls at specific points in the handler:\n\n```yaml\nhandlers:\n request-human:\n # Always create a ticket when escalating\n Create support ticket:\n block: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY # From variable\n priority: medium # Literal value\n output: TICKET # Store result\n```\n\n## Tool Results\n\n### In Prompts\n\nTool results are stored in variables. Reference the variable in prompts:\n\n```markdown\n<!-- prompts/ticket-directive.md -->\n\nA support ticket has been created:\n{{TICKET}}\n\nLet the user know their ticket has been created.\n```\n\nWhen the `TICKET` variable contains an object, it's automatically serialized as JSON in the prompt:\n\n```\nA support ticket has been created:\n{\n \"ticketId\": \"TKT-123ABC\",\n \"estimatedResponse\": \"24 hours\"\n}\n\nLet the user know their ticket has been created.\n```\n\n> **Note**: Variables use `{{VARIABLE_NAME}}` syntax with `UPPERCASE_SNAKE_CASE`. Dot notation (like `{{TICKET.ticketId}}`) is not supported. Objects are automatically JSON-serialized.\n\n### In Variables\n\nStore tool results for later use:\n\n```yaml\nhandlers:\n request-human:\n Get account:\n block: tool-call\n tool: get-user-account\n input:\n userId: USER_ID\n output: ACCOUNT # Result stored here\n\n Create ticket:\n block: tool-call\n tool: create-support-ticket\n input:\n summary: SUMMARY\n priority: medium\n output: TICKET\n```\n\n## Implementing Tools\n\nTools are implemented in your backend:\n\n```typescript\nconst session = client.agentSessions.attach(sessionId, {\n tools: {\n 'get-user-account': async (args) => {\n const userId = args.userId as string;\n const user = await db.users.findById(userId);\n\n return {\n name: user.name,\n email: user.email,\n plan: user.subscription.plan,\n createdAt: user.createdAt.toISOString(),\n };\n },\n\n 'create-support-ticket': async (args) => {\n const ticket = await ticketService.create({\n summary: args.summary as string,\n priority: args.priority as string,\n });\n\n return {\n ticketId: ticket.id,\n estimatedResponse: getEstimatedTime(args.priority),\n };\n },\n },\n});\n```\n\n## Tool Best Practices\n\n### 1. Clear Descriptions\n\n```yaml\ntools:\n # Good - clear and specific\n get-user-account:\n description: >\n Retrieves the user's account information including name, email,\n subscription plan, and account creation date. Use this when the\n user asks about their account or you need to verify their identity.\n\n # Avoid - vague\n get-data:\n description: Gets some data\n```\n\n### 2. Document Constrained Values\n\n```yaml\ntools:\n create-support-ticket:\n parameters:\n priority:\n type: string\n description: Ticket priority level (low, medium, high, urgent)\n```\n",
|
|
206
|
+
"excerpt": "Tools Tools extend what agents can do. Octavus supports multiple types: 1. External Tools — Defined in the protocol, implemented in your backend (this page) 2. Built-in Tools — Provider-agnostic...",
|
|
207
207
|
"order": 4
|
|
208
208
|
},
|
|
209
209
|
{
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
"section": "protocol",
|
|
230
230
|
"title": "Agent Config",
|
|
231
231
|
"description": "Configuring the agent model and behavior.",
|
|
232
|
-
"content": "\n# Agent Config\n\nThe `agent` section configures the LLM model, system prompt, tools, and behavior.\n\n## Basic Configuration\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account] # Available tools\n skills: [qr-code] # Available skills\n```\n\n## Configuration Options\n\n| Field | Required | Description |\n| ---------------- | -------- | --------------------------------------------------------- |\n| `model` | Yes | Model identifier or variable reference |\n| `system` | Yes | System prompt filename (without .md) |\n| `input` | No | Variables to pass to the system prompt |\n| `tools` | No | List of tools the LLM can call |\n| `skills` | No | List of Octavus skills the LLM can use |\n| `sandboxTimeout` | No | Skill sandbox timeout in ms (default: 5 min, max: 1 hour) |\n| `imageModel` | No | Image generation model (enables agentic image generation) |\n| `agentic` | No | Allow multiple tool call cycles |\n| `maxSteps` | No | Maximum agentic steps (default: 10) |\n| `temperature` | No | Model temperature (0-2) |\n| `thinking` | No | Extended reasoning level |\n| `anthropic` | No | Anthropic-specific options (tools, skills) |\n\n## Models\n\nSpecify models in `provider/model-id` format. Any model supported by the provider's SDK will work.\n\n### Supported Providers\n\n| Provider | Format | Examples |\n| --------- | ---------------------- | -------------------------------------------------------------------- |\n| Anthropic | `anthropic/{model-id}` | `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5` |\n| Google | `google/{model-id}` | `gemini-3-pro-preview`, `gemini-3-flash-preview`, `gemini-2.5-flash` |\n| OpenAI | `openai/{model-id}` | `gpt-5`, `gpt-4o`, `o4-mini`, `o3`, `o3-mini`, `o1` |\n\n### Examples\n\n```yaml\n# Anthropic Claude 4.5\nagent:\n model: anthropic/claude-sonnet-4-5\n\n# Google Gemini 3\nagent:\n model: google/gemini-3-flash-preview\n\n# OpenAI GPT-5\nagent:\n model: openai/gpt-5\n\n# OpenAI reasoning models\nagent:\n model: openai/o3-mini\n```\n\n> **Note**: Model IDs are passed directly to the provider SDK. Check the provider's documentation for the latest available models.\n\n### Dynamic Model Selection\n\nThe model field can also reference an input variable, allowing consumers to choose the model when creating a session:\n\n```yaml\ninput:\n MODEL:\n type: string\n description: The LLM model to use\n\nagent:\n model: MODEL # Resolved from session input\n system: system\n```\n\nWhen creating a session, pass the model:\n\n```typescript\nconst sessionId = await client.agentSessions.create('my-agent', {\n MODEL: 'anthropic/claude-sonnet-4-5',\n});\n```\n\nThis enables:\n\n- **Multi-provider support** — Same agent works with different providers\n- **A/B testing** — Test different models without protocol changes\n- **User preferences** — Let users choose their preferred model\n\nThe model value is validated at runtime to ensure it's in the correct `provider/model-id` format.\n\n> **Note**: When using dynamic models, provider-specific options (like `anthropic:`) may not apply if the model resolves to a different provider.\n\n## System Prompt\n\nThe system prompt sets the agent's persona and instructions. The `input` field controls which variables are available to the prompt — only variables listed in `input` are interpolated.\n\n```yaml\nagent:\n system: system # Uses prompts/system.md\n input:\n - COMPANY_NAME\n - PRODUCT_NAME\n```\n\nVariables in `input` can come from `protocol.input`, `protocol.resources`, or `protocol.variables`.\n\n### Input Mapping Formats\n\n```yaml\n# Array format (same name)\ninput:\n - COMPANY_NAME\n - PRODUCT_NAME\n\n# Array format (rename)\ninput:\n - CONTEXT: CONVERSATION_SUMMARY # Prompt sees CONTEXT, value comes from CONVERSATION_SUMMARY\n\n# Object format (rename)\ninput:\n CONTEXT: CONVERSATION_SUMMARY\n```\n\nThe left side (label) is what the prompt sees. The right side (source) is where the value comes from.\n\n### Example\n\n`prompts/system.md`:\n\n```markdown\nYou are a friendly support agent for {{COMPANY_NAME}}.\n\n## Your Role\n\nHelp users with questions about {{PRODUCT_NAME}}.\n\n## Guidelines\n\n- Be helpful and professional\n- If you can't help, offer to escalate\n- Never share internal information\n```\n\n## Agentic Mode\n\nEnable multi-step tool calling:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account, search-docs, create-ticket]\n agentic: true # LLM can call multiple tools\n maxSteps: 10 # Limit cycles to prevent runaway\n```\n\n**How it works:**\n\n1. LLM receives user message\n2. LLM decides to call a tool\n3. Tool executes, result returned to LLM\n4. LLM decides if more tools needed\n5. Repeat until LLM responds or maxSteps reached\n\n## Extended Thinking\n\nEnable extended reasoning for complex tasks:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n thinking: medium # low | medium | high\n```\n\n| Level | Token Budget | Use Case |\n| -------- | ------------ | ------------------- |\n| `low` | ~5,000 | Simple reasoning |\n| `medium` | ~10,000 | Moderate complexity |\n| `high` | ~20,000 | Complex analysis |\n\nThinking content streams to the UI and can be displayed to users.\n\n## Skills\n\nEnable Octavus skills for code execution and file 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] # Enable skills\n agentic: true\n```\n\nSkills provide provider-agnostic code execution in isolated sandboxes. When enabled, the LLM can execute Python/Bash code, run skill scripts, and generate files.\n\nSee [Skills](/docs/protocol/skills) for full documentation.\n\n## Image Generation\n\nEnable the LLM to generate images autonomously:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n imageModel: google/gemini-2.5-flash-image\n agentic: true\n```\n\nWhen `imageModel` is configured, the `octavus_generate_image` tool becomes available. The LLM can decide when to generate images based on user requests. The tool supports both text-to-image generation and image editing/transformation using reference images.\n\n### Supported Image Providers\n\n| Provider | Model Types | Examples |\n| -------- | --------------------------------------- | --------------------------------------------------------- |\n| OpenAI | Dedicated image models | `gpt-image-1` |\n| Google | Gemini native (contains \"image\") | `gemini-2.5-flash-image`, `gemini-3-flash-image-generate` |\n| Google | Imagen dedicated (starts with \"imagen\") | `imagen-4.0-generate-001` |\n\n> **Note**: Google has two image generation approaches. Gemini \"native\" models (containing \"image\" in the ID) generate images using the language model API with `responseModalities`. Imagen models (starting with \"imagen\") use a dedicated image generation API.\n\n### Image Sizes\n\nThe tool supports three image sizes:\n\n- `1024x1024` (default) — Square\n- `1792x1024` — Landscape (16:9)\n- `1024x1792` — Portrait (9:16)\n\n### Image Editing with Reference Images\n\nBoth the agentic tool and the `generate-image` block support reference images for editing and transformation. When reference images are provided, the prompt describes how to modify or use those images.\n\n| Provider | Models | Reference Image Support |\n| -------- | -------------------------------- | ----------------------- |\n| OpenAI | `gpt-image-1` | Yes |\n| Google | Gemini native (`gemini-*-image`) | Yes |\n| Google | Imagen (`imagen-*`) | No |\n\n### Agentic vs Deterministic\n\nUse `imageModel` in agent config when:\n\n- The LLM should decide when to generate or edit images\n- Users ask for images in natural language\n\nUse `generate-image` block (see [Handlers](/docs/protocol/handlers#generate-image)) when:\n\n- You want explicit control over image generation or editing\n- Building prompt engineering pipelines\n- Images are generated at specific handler steps\n\n## Temperature\n\nControl response randomness:\n\n```yaml\nagent:\n model: openai/gpt-4o\n temperature: 0.7 # 0 = deterministic, 2 = creative\n```\n\n**Guidelines:**\n\n- `0 - 0.3`: Factual, consistent responses\n- `0.4 - 0.7`: Balanced (good default)\n- `0.8 - 1.2`: Creative, varied responses\n- `> 1.2`: Very creative (may be inconsistent)\n\n## Provider Options\n\nEnable provider-specific features like Anthropic's built-in tools and skills:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n anthropic:\n tools:\n web-search:\n display: description\n description: Searching the web\n skills:\n pdf:\n type: anthropic\n description: Processing PDF\n```\n\nProvider options are validated against the model—using `anthropic:` with a non-Anthropic model will fail validation.\n\nSee [Provider Options](/docs/protocol/provider-options) for full documentation.\n\n## Thread-Specific Config\n\nOverride config for named threads:\n\n```yaml\nhandlers:\n request-human:\n Start summary thread:\n block: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5 # Different model\n thinking: low # Different thinking\n maxSteps: 1 # Limit tool calls\n system: escalation-summary # Different prompt\n skills: [data-analysis] # Thread-specific skills\n imageModel: google/gemini-2.5-flash-image # Thread-specific image model\n```\n\nEach thread can have its own skills and image model. Skills referenced here must be defined in the protocol's `skills:` section. Workers use this same pattern since they don't have a global `agent:` section.\n\n## Full Example\n\n```yaml\ninput:\n COMPANY_NAME: { type: string }\n PRODUCT_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\nresources:\n CONVERSATION_SUMMARY:\n type: string\n default: ''\n\ntools:\n get-user-account:\n description: Look up user account\n parameters:\n userId: { type: string }\n\n search-docs:\n description: Search help documentation\n parameters:\n query: { type: string }\n\n create-support-ticket:\n description: Create a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string } # low, medium, high\n\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 input:\n - COMPANY_NAME\n - PRODUCT_NAME\n tools:\n - get-user-account\n - search-docs\n - create-support-ticket\n skills: [qr-code] # Octavus skills\n agentic: true\n maxSteps: 10\n thinking: medium\n # Anthropic-specific options\n anthropic:\n tools:\n web-search:\n display: description\n description: Searching the web\n skills:\n pdf:\n type: anthropic\n description: Processing PDF\n\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n\n Respond:\n block: next-message\n```\n",
|
|
232
|
+
"content": "\n# Agent Config\n\nThe `agent` section configures the LLM model, system prompt, tools, and behavior.\n\n## Basic Configuration\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system # References prompts/system.md\n tools: [get-user-account] # Available tools\n skills: [qr-code] # Available skills\n references: [api-guidelines] # On-demand context documents\n```\n\n## Configuration Options\n\n| Field | Required | Description |\n| ---------------- | -------- | --------------------------------------------------------- |\n| `model` | Yes | Model identifier or variable reference |\n| `system` | Yes | System prompt filename (without .md) |\n| `input` | No | Variables to pass to the system prompt |\n| `tools` | No | List of tools the LLM can call |\n| `skills` | No | List of Octavus skills the LLM can use |\n| `references` | No | List of references the LLM can fetch on demand |\n| `sandboxTimeout` | No | Skill sandbox timeout in ms (default: 5 min, max: 1 hour) |\n| `imageModel` | No | Image generation model (enables agentic image generation) |\n| `webSearch` | No | Enable built-in web search tool (provider-agnostic) |\n| `agentic` | No | Allow multiple tool call cycles |\n| `maxSteps` | No | Maximum agentic steps (default: 10) |\n| `temperature` | No | Model temperature (0-2) |\n| `thinking` | No | Extended reasoning level |\n| `anthropic` | No | Anthropic-specific options (tools, skills) |\n\n## Models\n\nSpecify models in `provider/model-id` format. Any model supported by the provider's SDK will work.\n\n### Supported Providers\n\n| Provider | Format | Examples |\n| --------- | ---------------------- | -------------------------------------------------------------------- |\n| Anthropic | `anthropic/{model-id}` | `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5` |\n| Google | `google/{model-id}` | `gemini-3-pro-preview`, `gemini-3-flash-preview`, `gemini-2.5-flash` |\n| OpenAI | `openai/{model-id}` | `gpt-5`, `gpt-4o`, `o4-mini`, `o3`, `o3-mini`, `o1` |\n\n### Examples\n\n```yaml\n# Anthropic Claude 4.5\nagent:\n model: anthropic/claude-sonnet-4-5\n\n# Google Gemini 3\nagent:\n model: google/gemini-3-flash-preview\n\n# OpenAI GPT-5\nagent:\n model: openai/gpt-5\n\n# OpenAI reasoning models\nagent:\n model: openai/o3-mini\n```\n\n> **Note**: Model IDs are passed directly to the provider SDK. Check the provider's documentation for the latest available models.\n\n### Dynamic Model Selection\n\nThe model field can also reference an input variable, allowing consumers to choose the model when creating a session:\n\n```yaml\ninput:\n MODEL:\n type: string\n description: The LLM model to use\n\nagent:\n model: MODEL # Resolved from session input\n system: system\n```\n\nWhen creating a session, pass the model:\n\n```typescript\nconst sessionId = await client.agentSessions.create('my-agent', {\n MODEL: 'anthropic/claude-sonnet-4-5',\n});\n```\n\nThis enables:\n\n- **Multi-provider support** — Same agent works with different providers\n- **A/B testing** — Test different models without protocol changes\n- **User preferences** — Let users choose their preferred model\n\nThe model value is validated at runtime to ensure it's in the correct `provider/model-id` format.\n\n> **Note**: When using dynamic models, provider-specific options (like `anthropic:`) may not apply if the model resolves to a different provider.\n\n## System Prompt\n\nThe system prompt sets the agent's persona and instructions. The `input` field controls which variables are available to the prompt — only variables listed in `input` are interpolated.\n\n```yaml\nagent:\n system: system # Uses prompts/system.md\n input:\n - COMPANY_NAME\n - PRODUCT_NAME\n```\n\nVariables in `input` can come from `protocol.input`, `protocol.resources`, or `protocol.variables`.\n\n### Input Mapping Formats\n\n```yaml\n# Array format (same name)\ninput:\n - COMPANY_NAME\n - PRODUCT_NAME\n\n# Array format (rename)\ninput:\n - CONTEXT: CONVERSATION_SUMMARY # Prompt sees CONTEXT, value comes from CONVERSATION_SUMMARY\n\n# Object format (rename)\ninput:\n CONTEXT: CONVERSATION_SUMMARY\n```\n\nThe left side (label) is what the prompt sees. The right side (source) is where the value comes from.\n\n### Example\n\n`prompts/system.md`:\n\n```markdown\nYou are a friendly support agent for {{COMPANY_NAME}}.\n\n## Your Role\n\nHelp users with questions about {{PRODUCT_NAME}}.\n\n## Guidelines\n\n- Be helpful and professional\n- If you can't help, offer to escalate\n- Never share internal information\n```\n\n## Agentic Mode\n\nEnable multi-step tool calling:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [get-user-account, search-docs, create-ticket]\n agentic: true # LLM can call multiple tools\n maxSteps: 10 # Limit cycles to prevent runaway\n```\n\n**How it works:**\n\n1. LLM receives user message\n2. LLM decides to call a tool\n3. Tool executes, result returned to LLM\n4. LLM decides if more tools needed\n5. Repeat until LLM responds or maxSteps reached\n\n## Extended Thinking\n\nEnable extended reasoning for complex tasks:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n thinking: medium # low | medium | high\n```\n\n| Level | Token Budget | Use Case |\n| -------- | ------------ | ------------------- |\n| `low` | ~5,000 | Simple reasoning |\n| `medium` | ~10,000 | Moderate complexity |\n| `high` | ~20,000 | Complex analysis |\n\nThinking content streams to the UI and can be displayed to users.\n\n## Skills\n\nEnable Octavus skills for code execution and file 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] # Enable skills\n agentic: true\n```\n\nSkills provide provider-agnostic code execution in isolated sandboxes. When enabled, the LLM can execute Python/Bash code, run skill scripts, and generate files.\n\nSee [Skills](/docs/protocol/skills) for full documentation.\n\n## References\n\nEnable on-demand context loading via reference documents:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n references: [api-guidelines, error-codes]\n agentic: true\n```\n\nReferences are markdown files stored in the agent's `references/` directory. When enabled, the LLM can list available references and read their content using `octavus_reference_list` and `octavus_reference_read` tools.\n\nSee [References](/docs/protocol/references) for full documentation.\n\n## Image Generation\n\nEnable the LLM to generate images autonomously:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n imageModel: google/gemini-2.5-flash-image\n agentic: true\n```\n\nWhen `imageModel` is configured, the `octavus_generate_image` tool becomes available. The LLM can decide when to generate images based on user requests. The tool supports both text-to-image generation and image editing/transformation using reference images.\n\n### Supported Image Providers\n\n| Provider | Model Types | Examples |\n| -------- | --------------------------------------- | --------------------------------------------------------- |\n| OpenAI | Dedicated image models | `gpt-image-1` |\n| Google | Gemini native (contains \"image\") | `gemini-2.5-flash-image`, `gemini-3-flash-image-generate` |\n| Google | Imagen dedicated (starts with \"imagen\") | `imagen-4.0-generate-001` |\n\n> **Note**: Google has two image generation approaches. Gemini \"native\" models (containing \"image\" in the ID) generate images using the language model API with `responseModalities`. Imagen models (starting with \"imagen\") use a dedicated image generation API.\n\n### Image Sizes\n\nThe tool supports three image sizes:\n\n- `1024x1024` (default) — Square\n- `1792x1024` — Landscape (16:9)\n- `1024x1792` — Portrait (9:16)\n\n### Image Editing with Reference Images\n\nBoth the agentic tool and the `generate-image` block support reference images for editing and transformation. When reference images are provided, the prompt describes how to modify or use those images.\n\n| Provider | Models | Reference Image Support |\n| -------- | -------------------------------- | ----------------------- |\n| OpenAI | `gpt-image-1` | Yes |\n| Google | Gemini native (`gemini-*-image`) | Yes |\n| Google | Imagen (`imagen-*`) | No |\n\n### Agentic vs Deterministic\n\nUse `imageModel` in agent config when:\n\n- The LLM should decide when to generate or edit images\n- Users ask for images in natural language\n\nUse `generate-image` block (see [Handlers](/docs/protocol/handlers#generate-image)) when:\n\n- You want explicit control over image generation or editing\n- Building prompt engineering pipelines\n- Images are generated at specific handler steps\n\n## Web Search\n\nEnable the LLM to search the web for current information:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n webSearch: true\n agentic: true\n```\n\nWhen `webSearch` is enabled, the `octavus_web_search` tool becomes available. The LLM can decide when to search the web based on the conversation. Search results include source URLs that are emitted as citations in the UI.\n\nThis is a **provider-agnostic** built-in tool — it works with any LLM provider (Anthropic, Google, OpenAI, etc.). For Anthropic's own web search implementation, see [Provider Options](/docs/protocol/provider-options).\n\nUse cases:\n\n- Current events and real-time data\n- Fact verification and documentation lookups\n- Any information that may have changed since the model's training\n\n## Temperature\n\nControl response randomness:\n\n```yaml\nagent:\n model: openai/gpt-4o\n temperature: 0.7 # 0 = deterministic, 2 = creative\n```\n\n**Guidelines:**\n\n- `0 - 0.3`: Factual, consistent responses\n- `0.4 - 0.7`: Balanced (good default)\n- `0.8 - 1.2`: Creative, varied responses\n- `> 1.2`: Very creative (may be inconsistent)\n\n## Provider Options\n\nEnable provider-specific features like Anthropic's built-in tools and skills:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n anthropic:\n tools:\n web-search:\n display: description\n description: Searching the web\n skills:\n pdf:\n type: anthropic\n description: Processing PDF\n```\n\nProvider options are validated against the model—using `anthropic:` with a non-Anthropic model will fail validation.\n\nSee [Provider Options](/docs/protocol/provider-options) for full documentation.\n\n## Thread-Specific Config\n\nOverride config for named threads:\n\n```yaml\nhandlers:\n request-human:\n Start summary thread:\n block: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5 # Different model\n thinking: low # Different thinking\n maxSteps: 1 # Limit tool calls\n system: escalation-summary # Different prompt\n skills: [data-analysis] # Thread-specific skills\n references: [escalation-policy] # Thread-specific references\n imageModel: google/gemini-2.5-flash-image # Thread-specific image model\n webSearch: true # Thread-specific web search\n```\n\nEach thread can have its own skills, references, image model, and web search setting. Skills must be defined in the protocol's `skills:` section. References must exist in the agent's `references/` directory. Workers use this same pattern since they don't have a global `agent:` section.\n\n## Full Example\n\n```yaml\ninput:\n COMPANY_NAME: { type: string }\n PRODUCT_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\nresources:\n CONVERSATION_SUMMARY:\n type: string\n default: ''\n\ntools:\n get-user-account:\n description: Look up user account\n parameters:\n userId: { type: string }\n\n search-docs:\n description: Search help documentation\n parameters:\n query: { type: string }\n\n create-support-ticket:\n description: Create a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string } # low, medium, high\n\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 input:\n - COMPANY_NAME\n - PRODUCT_NAME\n tools:\n - get-user-account\n - search-docs\n - create-support-ticket\n skills: [qr-code] # Octavus skills\n references: [support-policies] # On-demand context\n webSearch: true # Built-in web search\n agentic: true\n maxSteps: 10\n thinking: medium\n # Anthropic-specific options\n anthropic:\n tools:\n web-search:\n display: description\n description: Searching the web\n skills:\n pdf:\n type: anthropic\n description: Processing PDF\n\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n\n Respond:\n block: next-message\n```\n",
|
|
233
233
|
"excerpt": "Agent Config The section configures the LLM model, system prompt, tools, and behavior. Basic Configuration Configuration Options | Field | Required | Description ...",
|
|
234
234
|
"order": 7
|
|
235
235
|
},
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"section": "protocol",
|
|
239
239
|
"title": "Provider Options",
|
|
240
240
|
"description": "Configuring provider-specific tools and features.",
|
|
241
|
-
"content": "\n# Provider Options\n\nProvider options let you enable provider-specific features like Anthropic's built-in tools and skills. These features run server-side on the provider's infrastructure.\n\n> **Note**: For provider-agnostic code execution, use [Octavus Skills](/docs/protocol/skills) instead. Octavus Skills work with any LLM provider and run in isolated sandbox environments.\n\n## Anthropic Options\n\nConfigure Anthropic-specific features when using `anthropic/*` models:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n anthropic:\n # Provider tools (server-side)\n tools:\n web-search:\n display: description\n description: Searching the web\n code-execution:\n display: description\n description: Running code\n\n # Skills (knowledge packages)\n skills:\n pdf:\n type: anthropic\n display: description\n description: Processing PDF document\n```\n\n> **Note**: Provider options are validated against the model provider. Using `anthropic:` options with non-Anthropic models will result in a validation error.\n\n## Provider Tools\n\nProvider tools are executed server-side by the provider (Anthropic). Unlike external tools that you implement, provider tools are built-in capabilities.\n\n### Available Tools\n\n| Tool | Description |\n| ---------------- | -------------------------------------------- |\n| `web-search` | Search the web for current information |\n| `code-execution` | Execute Python/Bash in a sandboxed container |\n\n### Tool Configuration\n\n```yaml\nanthropic:\n tools:\n web-search:\n display: description # How to show in UI\n description: Searching... # Custom display text\n```\n\n| Field | Required | Description |\n| ------------- | -------- | --------------------------------------------------------------------- |\n| `display` | No | `hidden`, `name`, `description`, or `stream` (default: `description`) |\n| `description` | No | Custom text shown to users during execution |\n\n### Web Search\n\nAllows the agent to search the web
|
|
241
|
+
"content": "\n# Provider Options\n\nProvider options let you enable provider-specific features like Anthropic's built-in tools and skills. These features run server-side on the provider's infrastructure.\n\n> **Note**: For provider-agnostic code execution, use [Octavus Skills](/docs/protocol/skills) instead. Octavus Skills work with any LLM provider and run in isolated sandbox environments.\n\n## Anthropic Options\n\nConfigure Anthropic-specific features when using `anthropic/*` models:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n anthropic:\n # Provider tools (server-side)\n tools:\n web-search:\n display: description\n description: Searching the web\n code-execution:\n display: description\n description: Running code\n\n # Skills (knowledge packages)\n skills:\n pdf:\n type: anthropic\n display: description\n description: Processing PDF document\n```\n\n> **Note**: Provider options are validated against the model provider. Using `anthropic:` options with non-Anthropic models will result in a validation error.\n\n## Provider Tools\n\nProvider tools are executed server-side by the provider (Anthropic). Unlike external tools that you implement, provider tools are built-in capabilities.\n\n### Available Tools\n\n| Tool | Description |\n| ---------------- | -------------------------------------------- |\n| `web-search` | Search the web for current information |\n| `code-execution` | Execute Python/Bash in a sandboxed container |\n\n### Tool Configuration\n\n```yaml\nanthropic:\n tools:\n web-search:\n display: description # How to show in UI\n description: Searching... # Custom display text\n```\n\n| Field | Required | Description |\n| ------------- | -------- | --------------------------------------------------------------------- |\n| `display` | No | `hidden`, `name`, `description`, or `stream` (default: `description`) |\n| `description` | No | Custom text shown to users during execution |\n\n### Web Search\n\nAllows the agent to search the web using Anthropic's built-in web search:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n anthropic:\n tools:\n web-search:\n display: description\n description: Looking up current information\n```\n\n> **Tip**: Octavus also provides a **provider-agnostic** web search via `webSearch: true` in the agent config. This works with any LLM provider and is the recommended approach for multi-provider agents. See [Web Search](/docs/protocol/agent-config#web-search) for details.\n\n### Code Execution\n\nEnables Python and Bash execution in a sandboxed container:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n anthropic:\n tools:\n code-execution:\n display: description\n description: Running analysis\n```\n\nUse cases:\n\n- Data analysis and calculations\n- File processing\n- Chart generation\n- Script execution\n\n> **Note**: Code execution is automatically enabled when skills are configured (skills require the container environment).\n\n## Skills\n\n> **Important**: This section covers **Anthropic's built-in skills** (provider-specific). For provider-agnostic skills that work with any LLM, see [Octavus Skills](/docs/protocol/skills).\n\nAnthropic skills are knowledge packages that give the agent specialized capabilities. They're loaded into Anthropic's code execution container at `/skills/{skill-id}/` and only work with Anthropic models.\n\n### Skill Configuration\n\n```yaml\nanthropic:\n skills:\n pdf:\n type: anthropic # 'anthropic' or 'custom'\n version: latest # Optional version\n display: description\n description: Processing PDF\n```\n\n| Field | Required | Description |\n| ------------- | -------- | --------------------------------------------------------------------- |\n| `type` | Yes | `anthropic` (built-in) or `custom` (uploaded) |\n| `version` | No | Skill version (default: `latest`) |\n| `display` | No | `hidden`, `name`, `description`, or `stream` (default: `description`) |\n| `description` | No | Custom text shown to users |\n\n### Built-in Skills\n\nAnthropic provides several built-in skills:\n\n| Skill ID | Purpose |\n| -------- | ----------------------------------------------- |\n| `pdf` | PDF manipulation, text extraction, form filling |\n| `xlsx` | Excel spreadsheet operations and analysis |\n| `docx` | Word document creation and editing |\n| `pptx` | PowerPoint presentation creation |\n\n### Using Skills\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n anthropic:\n skills:\n pdf:\n type: anthropic\n description: Processing your PDF\n xlsx:\n type: anthropic\n description: Analyzing spreadsheet\n```\n\nWhen skills are configured:\n\n1. Code execution is automatically enabled\n2. Skill files are loaded into the container\n3. The agent can read skill instructions and execute scripts\n\n### Custom Skills\n\nYou can create and upload custom skills to Anthropic:\n\n```yaml\nanthropic:\n skills:\n custom-analysis:\n type: custom\n version: latest\n description: Running custom analysis\n```\n\nCustom skills follow the [Agent Skills standard](https://agentskills.io) and contain:\n\n- `SKILL.md` with instructions and metadata\n- Optional `scripts/`, `references/`, and `assets/` directories\n\n### Octavus Skills vs Anthropic Skills\n\n| Feature | Anthropic Skills | Octavus Skills |\n| ----------------- | ------------------------ | ----------------------------- |\n| **Provider** | Anthropic only | Any (agnostic) |\n| **Execution** | Anthropic's container | Isolated sandbox |\n| **Configuration** | `agent.anthropic.skills` | `agent.skills` |\n| **Definition** | `anthropic:` section | `skills:` section |\n| **Use Case** | Claude-specific features | Cross-provider code execution |\n\nFor provider-agnostic code execution, use Octavus Skills defined in the protocol's `skills:` section and enabled via `agent.skills`. See [Skills](/docs/protocol/skills) for details.\n\n## Display Modes\n\nBoth tools and skills support display modes:\n\n| Mode | Behavior |\n| ------------- | ------------------------------- |\n| `hidden` | Not shown to users |\n| `name` | Shows the tool/skill name |\n| `description` | Shows the description (default) |\n| `stream` | Streams progress if available |\n\n## Full Example\n\n```yaml\ninput:\n COMPANY_NAME: { type: string }\n USER_ID: { type: string, optional: true }\n\ntools:\n get-user-account:\n description: Looking up your account\n parameters:\n userId: { type: string }\n\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n input: [COMPANY_NAME, USER_ID]\n tools: [get-user-account] # External tools\n agentic: true\n thinking: medium\n\n # Anthropic-specific options\n anthropic:\n # Provider tools (server-side)\n tools:\n web-search:\n display: description\n description: Searching the web\n code-execution:\n display: description\n description: Running code\n\n # Skills (knowledge packages)\n skills:\n pdf:\n type: anthropic\n display: description\n description: Processing PDF document\n xlsx:\n type: anthropic\n display: description\n description: Analyzing spreadsheet\n\ntriggers:\n user-message:\n input:\n USER_MESSAGE: { type: string }\n\nhandlers:\n user-message:\n Add message:\n block: add-message\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n display: hidden\n\n Respond:\n block: next-message\n```\n\n## Validation\n\nThe protocol validator enforces:\n\n1. **Model match**: Provider options must match the model provider\n - `anthropic:` options require `anthropic/*` model\n - Using mismatched options results in a validation error\n\n2. **Valid tool types**: Only recognized tools are accepted\n - `web-search` and `code-execution` for Anthropic\n\n3. **Valid skill types**: Only `anthropic` or `custom` are accepted\n\n### Error Example\n\n```yaml\n# This will fail validation\nagent:\n model: openai/gpt-4o # OpenAI model\n anthropic: # Anthropic options - mismatch!\n tools:\n web-search: {}\n```\n\nError: `\"anthropic\" options require an anthropic model. Current model provider: \"openai\"`\n",
|
|
242
242
|
"excerpt": "Provider Options Provider options let you enable provider-specific features like Anthropic's built-in tools and skills. These features run server-side on the provider's infrastructure. > Note: For...",
|
|
243
243
|
"order": 8
|
|
244
244
|
},
|
|
@@ -265,10 +265,19 @@
|
|
|
265
265
|
"section": "protocol",
|
|
266
266
|
"title": "Workers",
|
|
267
267
|
"description": "Defining worker agents for background and task-based execution.",
|
|
268
|
-
"content": "\n# Workers\n\nWorkers are agents designed for task-based execution. Unlike interactive agents that handle multi-turn conversations, workers execute a sequence of steps and return an output value.\n\n## When to Use Workers\n\nWorkers are ideal for:\n\n- **Background processing** — Long-running tasks that don't need conversation\n- **Composable tasks** — Reusable units of work called by other agents\n- **Pipelines** — Multi-step processing with structured output\n- **Parallel execution** — Tasks that can run independently\n\nUse interactive agents instead when:\n\n- **Conversation is needed** — Multi-turn dialogue with users\n- **Persistence matters** — State should survive across interactions\n- **Session context** — User context needs to persist\n\n## Worker vs Interactive\n\n| Aspect | Interactive | Worker |\n| ---------- | ---------------------------------- | ----------------------------- |\n| Structure | `triggers` + `handlers` + `agent` | `steps` + `output` |\n| LLM Config | Global `agent:` section | Per-thread via `start-thread` |\n| Invocation | Fire a named trigger | Direct execution with input |\n| Session | Persists across triggers (24h TTL) | Single execution |\n| Result | Streaming chat | Streaming + output value |\n\n## Protocol Structure\n\nWorkers use a simpler protocol structure than interactive agents:\n\n```yaml\n# Input schema - provided when worker is executed\ninput:\n TOPIC:\n type: string\n description: Topic to research\n DEPTH:\n type: string\n optional: true\n default: medium\n\n# Variables for intermediate results\nvariables:\n RESEARCH_DATA:\n type: string\n ANALYSIS:\n type: string\n description: Final analysis result\n\n# Tools available to the worker\ntools:\n web-search:\n description: Search the web\n parameters:\n query: { type: string }\n\n# Sequential execution steps\nsteps:\n Start research:\n block: start-thread\n thread: research\n model: anthropic/claude-sonnet-4-5\n system: research-system\n input: [TOPIC, DEPTH]\n tools: [web-search]\n maxSteps: 5\n\n Add research request:\n block: add-message\n thread: research\n role: user\n prompt: research-prompt\n input: [TOPIC, DEPTH]\n\n Generate research:\n block: next-message\n thread: research\n output: RESEARCH_DATA\n\n Start analysis:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n system: analysis-system\n\n Add analysis request:\n block: add-message\n thread: analysis\n role: user\n prompt: analysis-prompt\n input: [RESEARCH_DATA]\n\n Generate analysis:\n block: next-message\n thread: analysis\n output: ANALYSIS\n\n# Output variable - the worker's return value\noutput: ANALYSIS\n```\n\n## settings.json\n\nWorkers are identified by the `format` field:\n\n```json\n{\n \"slug\": \"research-assistant\",\n \"name\": \"Research Assistant\",\n \"description\": \"Researches topics and returns structured analysis\",\n \"format\": \"worker\"\n}\n```\n\n## Key Differences\n\n### No Global Agent Config\n\nInteractive agents have a global `agent:` section that configures a main thread. Workers don't have this — every thread must be explicitly created via `start-thread`:\n\n```yaml\n# Interactive agent: Global config\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [tool-a, tool-b]\n\n# Worker: Each thread configured independently\nsteps:\n Start thread A:\n block: start-thread\n thread: research\n model: anthropic/claude-sonnet-4-5\n tools: [tool-a]\n\n Start thread B:\n block: start-thread\n thread: analysis\n model: openai/gpt-4o\n tools: [tool-b]\n```\n\nThis gives workers flexibility to use different models, tools, skills, and settings at different stages.\n\n### Steps Instead of Handlers\n\nWorkers use `steps:` instead of `handlers:`. Steps execute sequentially, like handler blocks:\n\n```yaml\n# Interactive: Handlers respond to triggers\nhandlers:\n user-message:\n Add message:\n block: add-message\n # ...\n\n# Worker: Steps execute in sequence\nsteps:\n Add message:\n block: add-message\n # ...\n```\n\n### Output Value\n\nWorkers can return an output value to the caller:\n\n```yaml\nvariables:\n RESULT:\n type: string\n\nsteps:\n # ... steps that populate RESULT ...\n\noutput: RESULT # Return this variable's value\n```\n\nThe `output` field references a variable declared in `variables:`. If omitted, the worker completes without returning a value.\n\n## Available Blocks\n\nWorkers support the same blocks as handlers:\n\n| Block | Purpose |\n| ------------------ | -------------------------------------------- |\n| `start-thread` | Create a named thread with LLM configuration |\n| `add-message` | Add a message to a thread |\n| `next-message` | Generate LLM response |\n| `tool-call` | Call a tool deterministically |\n| `set-resource` | Update a resource value |\n| `serialize-thread` | Convert thread to text |\n| `generate-image` | Generate an image from a prompt variable |\n\n### start-thread (Required for LLM)\n\nEvery thread must be initialized with `start-thread` before using `next-message`:\n\n```yaml\nsteps:\n Start research:\n block: start-thread\n thread: research\n model: anthropic/claude-sonnet-4-5\n system: research-system\n input: [TOPIC]\n tools: [web-search]\n thinking: medium\n maxSteps: 5\n```\n\nAll LLM configuration goes here:\n\n| Field | Description |\n| ------------- | ------------------------------------------------- |\n| `thread` | Thread name (defaults to block name) |\n| `model` | LLM model to use |\n| `system` | System prompt filename (required) |\n| `input` | Variables for system prompt |\n| `tools` | Tools available in this thread |\n| `skills` | Octavus skills available in this thread |\n| `imageModel` | Image generation model |\n| `thinking` | Extended reasoning level |\n| `temperature` | Model temperature |\n| `maxSteps` | Maximum tool call cycles (enables agentic if > 1) |\n\n## Simple Example\n\nA worker that generates a title from a summary:\n\n```yaml\n# Input\ninput:\n CONVERSATION_SUMMARY:\n type: string\n description: Summary to generate a title for\n\n# Variables\nvariables:\n TITLE:\n type: string\n description: The generated title\n\n# Steps\nsteps:\n Start title thread:\n block: start-thread\n thread: title-gen\n model: anthropic/claude-sonnet-4-5\n system: title-system\n\n Add title request:\n block: add-message\n thread: title-gen\n role: user\n prompt: title-request\n input: [CONVERSATION_SUMMARY]\n\n Generate title:\n block: next-message\n thread: title-gen\n output: TITLE\n display: stream\n\n# Output\noutput: TITLE\n```\n\n## Advanced Example\n\nA worker with multiple threads, tools, and agentic behavior:\n\n```yaml\ninput:\n USER_MESSAGE:\n type: string\n description: The user's message to respond to\n USER_ID:\n type: string\n description: User ID for account lookups\n optional: true\n\ntools:\n get-user-account:\n description: Looking up account information\n parameters:\n userId: { type: string }\n create-support-ticket:\n description: Creating a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string }\n\nvariables:\n ASSISTANT_RESPONSE:\n type: string\n CHAT_TRANSCRIPT:\n type: string\n CONVERSATION_SUMMARY:\n type: string\n\nsteps:\n # Thread 1: Chat with agentic tool calling\n Start chat thread:\n block: start-thread\n thread: chat\n model: anthropic/claude-sonnet-4-5\n system: chat-system\n input: [USER_ID]\n tools: [get-user-account, create-support-ticket]\n thinking: medium\n maxSteps: 5\n\n Add user message:\n block: add-message\n thread: chat\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Generate response:\n block: next-message\n thread: chat\n output: ASSISTANT_RESPONSE\n display: stream\n\n # Serialize for summary\n Save conversation:\n block: serialize-thread\n thread: chat\n output: CHAT_TRANSCRIPT\n\n # Thread 2: Summary generation\n Start summary thread:\n block: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5\n system: summary-system\n thinking: low\n\n Add summary request:\n block: add-message\n thread: summary\n role: user\n prompt: summary-request\n input: [CHAT_TRANSCRIPT]\n\n Generate summary:\n block: next-message\n thread: summary\n output: CONVERSATION_SUMMARY\n display: stream\n\noutput: CONVERSATION_SUMMARY\n```\n\n## Skills and Image Generation\n\nWorkers can use Octavus skills and image generation, configured per-thread via `start-thread`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generate 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 imageModel: google/gemini-2.5-flash-image\n maxSteps: 10\n```\n\nWorkers define their own skills independently -- they don't inherit skills from a parent interactive agent. Each thread gets its own sandbox scoped to only its listed skills.\n\nSee [Skills](/docs/protocol/skills) for full documentation.\n\n## Tool Handling\n\nWorkers support the same tool handling as interactive agents:\n\n- **Server tools** — Handled by tool handlers you provide\n- **Client tools** — Pause execution, return tool request to caller\n\n```typescript\n// Non-streaming: get the output directly\nconst { output } = await client.workers.generate(\n agentId,\n { TOPIC: 'AI safety' },\n {\n tools: {\n 'web-search': async (args) => await searchWeb(args.query),\n },\n },\n);\n\n// Streaming: observe events in real-time\nconst events = client.workers.execute(\n agentId,\n { TOPIC: 'AI safety' },\n {\n tools: {\n 'web-search': async (args) => await searchWeb(args.query),\n },\n },\n);\n```\n\nSee [Server SDK Workers](/docs/server-sdk/workers) for tool handling details.\n\n## Stream Events\n\nWorkers emit the same events as interactive agents, plus worker-specific events:\n\n| Event | Description |\n| --------------- | ---------------------------------- |\n| `worker-start` | Worker execution begins |\n| `worker-result` | Worker completes (includes output) |\n\nAll standard events (text-delta, tool calls, etc.) are also emitted.\n\n## Calling Workers from Interactive Agents\n\nInteractive agents can call workers in two ways:\n\n1. **Deterministically** — Using the `run-worker` block\n2. **Agentically** — LLM calls worker as a tool\n\n### Worker Declaration\n\nFirst, declare workers in your interactive agent's protocol:\n\n```yaml\nworkers:\n generate-title:\n description: Generating conversation title\n display: description\n research-assistant:\n description: Researching topic\n display: stream\n tools:\n search: web-search # Map worker tool → parent tool\n```\n\n### run-worker Block\n\nCall a worker deterministically from a handler:\n\n```yaml\nhandlers:\n request-human:\n Generate title:\n block: run-worker\n worker: generate-title\n input:\n CONVERSATION_SUMMARY: SUMMARY\n output: CONVERSATION_TITLE\n```\n\n### LLM Tool Invocation\n\nMake workers available to the LLM:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n workers: [generate-title, research-assistant]\n agentic: true\n```\n\nThe LLM can then call workers as tools during conversation.\n\n### Display Modes\n\nControl how worker execution appears to users:\n\n| Mode | Behavior |\n| ------------- | --------------------------------- |\n| `hidden` | Worker runs silently |\n| `name` | Shows worker name |\n| `description` | Shows description text |\n| `stream` | Streams all worker events to user |\n\n### Tool Mapping\n\nMap parent tools to worker tools when the worker needs access to your tool handlers:\n\n```yaml\nworkers:\n research-assistant:\n description: Research topics\n tools:\n search: web-search # Worker's \"search\" → parent's \"web-search\"\n```\n\nWhen the worker calls its `search` tool, your `web-search` handler executes.\n\n## Next Steps\n\n- [Server SDK Workers](/docs/server-sdk/workers) — Executing workers from code\n- [Handlers](/docs/protocol/handlers) — Block reference for steps\n- [Agent Config](/docs/protocol/agent-config) — Model and settings\n",
|
|
268
|
+
"content": "\n# Workers\n\nWorkers are agents designed for task-based execution. Unlike interactive agents that handle multi-turn conversations, workers execute a sequence of steps and return an output value.\n\n## When to Use Workers\n\nWorkers are ideal for:\n\n- **Background processing** — Long-running tasks that don't need conversation\n- **Composable tasks** — Reusable units of work called by other agents\n- **Pipelines** — Multi-step processing with structured output\n- **Parallel execution** — Tasks that can run independently\n\nUse interactive agents instead when:\n\n- **Conversation is needed** — Multi-turn dialogue with users\n- **Persistence matters** — State should survive across interactions\n- **Session context** — User context needs to persist\n\n## Worker vs Interactive\n\n| Aspect | Interactive | Worker |\n| ---------- | ---------------------------------- | ----------------------------- |\n| Structure | `triggers` + `handlers` + `agent` | `steps` + `output` |\n| LLM Config | Global `agent:` section | Per-thread via `start-thread` |\n| Invocation | Fire a named trigger | Direct execution with input |\n| Session | Persists across triggers (24h TTL) | Single execution |\n| Result | Streaming chat | Streaming + output value |\n\n## Protocol Structure\n\nWorkers use a simpler protocol structure than interactive agents:\n\n```yaml\n# Input schema - provided when worker is executed\ninput:\n TOPIC:\n type: string\n description: Topic to research\n DEPTH:\n type: string\n optional: true\n default: medium\n\n# Variables for intermediate results\nvariables:\n RESEARCH_DATA:\n type: string\n ANALYSIS:\n type: string\n description: Final analysis result\n\n# Tools available to the worker\ntools:\n web-search:\n description: Search the web\n parameters:\n query: { type: string }\n\n# Sequential execution steps\nsteps:\n Start research:\n block: start-thread\n thread: research\n model: anthropic/claude-sonnet-4-5\n system: research-system\n input: [TOPIC, DEPTH]\n tools: [web-search]\n maxSteps: 5\n\n Add research request:\n block: add-message\n thread: research\n role: user\n prompt: research-prompt\n input: [TOPIC, DEPTH]\n\n Generate research:\n block: next-message\n thread: research\n output: RESEARCH_DATA\n\n Start analysis:\n block: start-thread\n thread: analysis\n model: anthropic/claude-sonnet-4-5\n system: analysis-system\n\n Add analysis request:\n block: add-message\n thread: analysis\n role: user\n prompt: analysis-prompt\n input: [RESEARCH_DATA]\n\n Generate analysis:\n block: next-message\n thread: analysis\n output: ANALYSIS\n\n# Output variable - the worker's return value\noutput: ANALYSIS\n```\n\n## settings.json\n\nWorkers are identified by the `format` field:\n\n```json\n{\n \"slug\": \"research-assistant\",\n \"name\": \"Research Assistant\",\n \"description\": \"Researches topics and returns structured analysis\",\n \"format\": \"worker\"\n}\n```\n\n## Key Differences\n\n### No Global Agent Config\n\nInteractive agents have a global `agent:` section that configures a main thread. Workers don't have this — every thread must be explicitly created via `start-thread`:\n\n```yaml\n# Interactive agent: Global config\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [tool-a, tool-b]\n\n# Worker: Each thread configured independently\nsteps:\n Start thread A:\n block: start-thread\n thread: research\n model: anthropic/claude-sonnet-4-5\n tools: [tool-a]\n\n Start thread B:\n block: start-thread\n thread: analysis\n model: openai/gpt-4o\n tools: [tool-b]\n```\n\nThis gives workers flexibility to use different models, tools, skills, and settings at different stages.\n\n### Steps Instead of Handlers\n\nWorkers use `steps:` instead of `handlers:`. Steps execute sequentially, like handler blocks:\n\n```yaml\n# Interactive: Handlers respond to triggers\nhandlers:\n user-message:\n Add message:\n block: add-message\n # ...\n\n# Worker: Steps execute in sequence\nsteps:\n Add message:\n block: add-message\n # ...\n```\n\n### Output Value\n\nWorkers can return an output value to the caller:\n\n```yaml\nvariables:\n RESULT:\n type: string\n\nsteps:\n # ... steps that populate RESULT ...\n\noutput: RESULT # Return this variable's value\n```\n\nThe `output` field references a variable declared in `variables:`. If omitted, the worker completes without returning a value.\n\n## Available Blocks\n\nWorkers support the same blocks as handlers:\n\n| Block | Purpose |\n| ------------------ | -------------------------------------------- |\n| `start-thread` | Create a named thread with LLM configuration |\n| `add-message` | Add a message to a thread |\n| `next-message` | Generate LLM response |\n| `tool-call` | Call a tool deterministically |\n| `set-resource` | Update a resource value |\n| `serialize-thread` | Convert thread to text |\n| `generate-image` | Generate an image from a prompt variable |\n\n### start-thread (Required for LLM)\n\nEvery thread must be initialized with `start-thread` before using `next-message`:\n\n```yaml\nsteps:\n Start research:\n block: start-thread\n thread: research\n model: anthropic/claude-sonnet-4-5\n system: research-system\n input: [TOPIC]\n tools: [web-search]\n thinking: medium\n maxSteps: 5\n```\n\nAll LLM configuration goes here:\n\n| Field | Description |\n| ------------- | ------------------------------------------------- |\n| `thread` | Thread name (defaults to block name) |\n| `model` | LLM model to use |\n| `system` | System prompt filename (required) |\n| `input` | Variables for system prompt |\n| `tools` | Tools available in this thread |\n| `skills` | Octavus skills available in this thread |\n| `imageModel` | Image generation model |\n| `webSearch` | Enable built-in web search tool |\n| `thinking` | Extended reasoning level |\n| `temperature` | Model temperature |\n| `maxSteps` | Maximum tool call cycles (enables agentic if > 1) |\n\n## Simple Example\n\nA worker that generates a title from a summary:\n\n```yaml\n# Input\ninput:\n CONVERSATION_SUMMARY:\n type: string\n description: Summary to generate a title for\n\n# Variables\nvariables:\n TITLE:\n type: string\n description: The generated title\n\n# Steps\nsteps:\n Start title thread:\n block: start-thread\n thread: title-gen\n model: anthropic/claude-sonnet-4-5\n system: title-system\n\n Add title request:\n block: add-message\n thread: title-gen\n role: user\n prompt: title-request\n input: [CONVERSATION_SUMMARY]\n\n Generate title:\n block: next-message\n thread: title-gen\n output: TITLE\n display: stream\n\n# Output\noutput: TITLE\n```\n\n## Advanced Example\n\nA worker with multiple threads, tools, and agentic behavior:\n\n```yaml\ninput:\n USER_MESSAGE:\n type: string\n description: The user's message to respond to\n USER_ID:\n type: string\n description: User ID for account lookups\n optional: true\n\ntools:\n get-user-account:\n description: Looking up account information\n parameters:\n userId: { type: string }\n create-support-ticket:\n description: Creating a support ticket\n parameters:\n summary: { type: string }\n priority: { type: string }\n\nvariables:\n ASSISTANT_RESPONSE:\n type: string\n CHAT_TRANSCRIPT:\n type: string\n CONVERSATION_SUMMARY:\n type: string\n\nsteps:\n # Thread 1: Chat with agentic tool calling\n Start chat thread:\n block: start-thread\n thread: chat\n model: anthropic/claude-sonnet-4-5\n system: chat-system\n input: [USER_ID]\n tools: [get-user-account, create-support-ticket]\n thinking: medium\n maxSteps: 5\n\n Add user message:\n block: add-message\n thread: chat\n role: user\n prompt: user-message\n input: [USER_MESSAGE]\n\n Generate response:\n block: next-message\n thread: chat\n output: ASSISTANT_RESPONSE\n display: stream\n\n # Serialize for summary\n Save conversation:\n block: serialize-thread\n thread: chat\n output: CHAT_TRANSCRIPT\n\n # Thread 2: Summary generation\n Start summary thread:\n block: start-thread\n thread: summary\n model: anthropic/claude-sonnet-4-5\n system: summary-system\n thinking: low\n\n Add summary request:\n block: add-message\n thread: summary\n role: user\n prompt: summary-request\n input: [CHAT_TRANSCRIPT]\n\n Generate summary:\n block: next-message\n thread: summary\n output: CONVERSATION_SUMMARY\n display: stream\n\noutput: CONVERSATION_SUMMARY\n```\n\n## Skills, Image Generation, and Web Search\n\nWorkers can use Octavus skills, image generation, and web search, configured per-thread via `start-thread`:\n\n```yaml\nskills:\n qr-code:\n display: description\n description: Generate 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 imageModel: google/gemini-2.5-flash-image\n webSearch: true\n maxSteps: 10\n```\n\nWorkers define their own skills independently -- they don't inherit skills from a parent interactive agent. Each thread gets its own sandbox scoped to only its listed skills.\n\nSee [Skills](/docs/protocol/skills) for full documentation.\n\n## Tool Handling\n\nWorkers support the same tool handling as interactive agents:\n\n- **Server tools** — Handled by tool handlers you provide\n- **Client tools** — Pause execution, return tool request to caller\n\n```typescript\n// Non-streaming: get the output directly\nconst { output } = await client.workers.generate(\n agentId,\n { TOPIC: 'AI safety' },\n {\n tools: {\n 'web-search': async (args) => await searchWeb(args.query),\n },\n },\n);\n\n// Streaming: observe events in real-time\nconst events = client.workers.execute(\n agentId,\n { TOPIC: 'AI safety' },\n {\n tools: {\n 'web-search': async (args) => await searchWeb(args.query),\n },\n },\n);\n```\n\nSee [Server SDK Workers](/docs/server-sdk/workers) for tool handling details.\n\n## Stream Events\n\nWorkers emit the same events as interactive agents, plus worker-specific events:\n\n| Event | Description |\n| --------------- | ---------------------------------- |\n| `worker-start` | Worker execution begins |\n| `worker-result` | Worker completes (includes output) |\n\nAll standard events (text-delta, tool calls, etc.) are also emitted.\n\n## Calling Workers from Interactive Agents\n\nInteractive agents can call workers in two ways:\n\n1. **Deterministically** — Using the `run-worker` block\n2. **Agentically** — LLM calls worker as a tool\n\n### Worker Declaration\n\nFirst, declare workers in your interactive agent's protocol:\n\n```yaml\nworkers:\n generate-title:\n description: Generating conversation title\n display: description\n research-assistant:\n description: Researching topic\n display: stream\n tools:\n search: web-search # Map worker tool → parent tool\n```\n\n### run-worker Block\n\nCall a worker deterministically from a handler:\n\n```yaml\nhandlers:\n request-human:\n Generate title:\n block: run-worker\n worker: generate-title\n input:\n CONVERSATION_SUMMARY: SUMMARY\n output: CONVERSATION_TITLE\n```\n\n### LLM Tool Invocation\n\nMake workers available to the LLM:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n workers: [generate-title, research-assistant]\n agentic: true\n```\n\nThe LLM can then call workers as tools during conversation.\n\n### Display Modes\n\nControl how worker execution appears to users:\n\n| Mode | Behavior |\n| ------------- | --------------------------------- |\n| `hidden` | Worker runs silently |\n| `name` | Shows worker name |\n| `description` | Shows description text |\n| `stream` | Streams all worker events to user |\n\n### Tool Mapping\n\nMap parent tools to worker tools when the worker needs access to your tool handlers:\n\n```yaml\nworkers:\n research-assistant:\n description: Research topics\n tools:\n search: web-search # Worker's \"search\" → parent's \"web-search\"\n```\n\nWhen the worker calls its `search` tool, your `web-search` handler executes.\n\n## Next Steps\n\n- [Server SDK Workers](/docs/server-sdk/workers) — Executing workers from code\n- [Handlers](/docs/protocol/handlers) — Block reference for steps\n- [Agent Config](/docs/protocol/agent-config) — Model and settings\n",
|
|
269
269
|
"excerpt": "Workers Workers are agents designed for task-based execution. Unlike interactive agents that handle multi-turn conversations, workers execute a sequence of steps and return an output value. When to...",
|
|
270
270
|
"order": 11
|
|
271
271
|
},
|
|
272
|
+
{
|
|
273
|
+
"slug": "protocol/references",
|
|
274
|
+
"section": "protocol",
|
|
275
|
+
"title": "References",
|
|
276
|
+
"description": "Using references for on-demand context loading in agents.",
|
|
277
|
+
"content": "\n# References\n\nReferences are markdown documents that agents can fetch on demand. Instead of loading everything into the system prompt upfront, references let the agent decide what context it needs and load it when relevant.\n\n## Overview\n\nReferences are useful for:\n\n- **Large context** — Documents too long to include in every system prompt\n- **Selective loading** — Let the agent decide which context is relevant\n- **Shared knowledge** — Reusable documents across threads\n\n### How References Work\n\n1. **Definition**: Reference files live in the `references/` directory alongside your agent\n2. **Configuration**: List available references in `agent.references` or `start-thread.references`\n3. **Discovery**: The agent sees reference names and descriptions in its system prompt\n4. **Fetching**: The agent calls reference tools to read the full content when needed\n\n## Creating References\n\nEach reference is a markdown file with YAML frontmatter in the `references/` directory:\n\n```\nmy-agent/\n├── settings.json\n├── protocol.yaml\n├── prompts/\n│ └── system.md\n└── references/\n ├── api-guidelines.md\n └── error-codes.md\n```\n\n### Reference Format\n\n```markdown\n---\ndescription: >\n API design guidelines including naming conventions,\n error handling patterns, and pagination standards.\n---\n\n# API Guidelines\n\n## Naming Conventions\n\nUse lowercase with dashes for URL paths...\n\n## Error Handling\n\nAll errors return a standard error envelope...\n```\n\nThe `description` field is required. It tells the agent what the reference contains so it can decide when to fetch it.\n\n### Naming Convention\n\nReference filenames use `lowercase-with-dashes`:\n\n- `api-guidelines.md`\n- `error-codes.md`\n- `coding-standards.md`\n\nThe filename (without `.md`) becomes the reference name used in the protocol.\n\n## Enabling References\n\nAfter creating reference files, specify which references are available in the protocol.\n\n### Interactive Agents\n\nList references in `agent.references`:\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n references: [api-guidelines, error-codes]\n agentic: true\n```\n\n### Workers and Named Threads\n\nList references per-thread in `start-thread.references`:\n\n```yaml\nsteps:\n Start thread:\n block: start-thread\n thread: worker\n model: anthropic/claude-sonnet-4-5\n system: system\n references: [api-guidelines]\n maxSteps: 10\n```\n\nDifferent threads can have different references.\n\n## Reference Tools\n\nWhen references are enabled, the agent has access to two tools:\n\n| Tool | Purpose |\n| ------------------------ | ----------------------------------------------- |\n| `octavus_reference_list` | List all available references with descriptions |\n| `octavus_reference_read` | Read the full content of a specific reference |\n\nThe agent also sees reference names and descriptions in its system prompt, so it knows what's available without calling `octavus_reference_list`.\n\n## Example\n\n```yaml\nagent:\n model: anthropic/claude-sonnet-4-5\n system: system\n tools: [review-pull-request]\n references: [coding-standards, api-guidelines]\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\nWith `references/coding-standards.md`:\n\n```markdown\n---\ndescription: >\n Team coding standards including naming conventions,\n code organization, and review checklist.\n---\n\n# Coding Standards\n\n## Naming Conventions\n\n- Files: kebab-case\n- Variables: camelCase\n- Constants: UPPER_SNAKE_CASE\n ...\n```\n\nWhen a user asks the agent to review code, the agent will:\n\n1. See \"coding-standards\" and \"api-guidelines\" in its system prompt\n2. Decide which references are relevant to the review\n3. Call `octavus_reference_read` to load the relevant reference\n4. Use the loaded context to provide an informed review\n\n## Validation\n\nThe CLI and platform validate references during sync and deployment:\n\n- **Undefined references** — Referencing a name that doesn't have a matching file in `references/`\n- **Unused references** — A reference file exists but isn't listed in any `agent.references` or `start-thread.references`\n- **Invalid names** — Names that don't follow the `lowercase-with-dashes` convention\n- **Missing description** — Reference files without the required `description` in frontmatter\n\n## References vs Skills\n\n| Aspect | References | Skills |\n| ------------- | ----------------------------- | ------------------------------- |\n| **Purpose** | On-demand context documents | Code execution and file output |\n| **Content** | Markdown text | Documentation + scripts |\n| **Execution** | Synchronous text retrieval | Sandboxed code execution (E2B) |\n| **Scope** | Per-agent (stored with agent) | Per-organization (shared) |\n| **Tools** | List and read (2 tools) | Read, list, run, code (6 tools) |\n\nUse **references** when the agent needs access to text-based knowledge. Use **skills** when the agent needs to execute code or generate files.\n\n## Next Steps\n\n- [Agent Config](/docs/protocol/agent-config) — Configuring references in agent settings\n- [Skills](/docs/protocol/skills) — Code execution and knowledge packages\n- [Workers](/docs/protocol/workers) — Using references in worker agents\n",
|
|
278
|
+
"excerpt": "References References are markdown documents that agents can fetch on demand. Instead of loading everything into the system prompt upfront, references let the agent decide what context it needs and...",
|
|
279
|
+
"order": 12
|
|
280
|
+
},
|
|
272
281
|
{
|
|
273
282
|
"slug": "api-reference/overview",
|
|
274
283
|
"section": "api-reference",
|
|
@@ -283,7 +292,7 @@
|
|
|
283
292
|
"section": "api-reference",
|
|
284
293
|
"title": "Sessions",
|
|
285
294
|
"description": "Session management API endpoints.",
|
|
286
|
-
"content": "\n# Sessions API\n\nSessions represent conversations with agents. They store conversation history, resources, and variables.\n\nAll session endpoints require an API key with the **Sessions** permission.\n\n## Create Session\n\nCreate a new agent session.\n\n```\nPOST /api/agent-sessions\n```\n\n### Request Body\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\",\n \"USER_ID\": \"user-123\"\n }\n}\n```\n\n| Field | Type | Required | Description |\n| --------- | ------ | -------- | ------------------------------------- |\n| `agentId` | string | Yes | Agent ID (the `id` field, not `slug`) |\n| `input` | object | No | Input variables for the agent |\n\n> **Getting the agent ID:** Copy the ID from the agent URL in the [platform](https://octavus.ai) (e.g., `octavus.ai/agents/clxyz123`), or use the [CLI](/docs/server-sdk/cli) (`octavus sync ./agents/my-agent`) for local development workflows.\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\"\n}\n```\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agent-sessions \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n }\n }'\n```\n\n## Get Session\n\nRetrieve session state. Returns UI-ready messages for active sessions, or expiration info for expired sessions.\n\n```\nGET /api/agent-sessions/:sessionId\n```\n\n### Query Parameters\n\n| Parameter | Type | Description |\n| --------- | ------ | ---------------------------------------------------- |\n| `format` | string | Optional. Use `format=ui` for UI-ready messages only |\n\n### Response (Active Session)\n\nWhen the session is active, the response includes `UIMessage` objects:\n\n```json\n{\n \"id\": \"cm5xyz123abc456def\",\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"status\": \"active\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n },\n \"variables\": {},\n \"resources\": {\n \"CONVERSATION_SUMMARY\": \"\"\n },\n \"messages\": [\n {\n \"id\": \"1702345800000-xyz789a\",\n \"role\": \"user\",\n \"parts\": [{ \"type\": \"text\", \"text\": \"How do I reset my password?\", \"status\": \"done\" }],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:00.000Z\"\n },\n {\n \"id\": \"1702345805000-def456b\",\n \"role\": \"assistant\",\n \"parts\": [\n { \"type\": \"text\", \"text\": \"I can help you reset your password...\", \"status\": \"done\" }\n ],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:05.000Z\"\n }\n ],\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"updatedAt\": \"2024-01-15T10:30:05Z\"\n}\n```\n\n### Response (Expired Session)\n\nWhen the session has expired, the response indicates the expiration status:\n\n```json\n{\n \"status\": \"expired\",\n \"sessionId\": \"cm5xyz123abc456def\",\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"createdAt\": \"2024-01-15T10:30:00Z\"\n}\n```\n\nUse the [Restore Session](#restore-session) endpoint to restore an expired session from stored messages.\n\n````\n\n### UIMessage Parts\n\nMessages contain typed `parts` that preserve content ordering:\n\n| Part Type | Description |\n|-----------|-------------|\n| `text` | Text content with `text` and `status` fields |\n| `reasoning` | Extended reasoning with `text` and `status` fields |\n| `tool-call` | Tool execution with `toolCallId`, `toolName`, `displayName`, `args`, `result`, `status` |\n| `operation` | Internal operations with `operationId`, `name`, `operationType`, `status` |\n| `file` | File attachment with `id`, `mediaType`, `url`, `filename`, `size` |\n| `source` | Source reference with `sourceType`, `id`, `url`, `title` |\n| `object` | Structured output with `id`, `typeName`, `object`, `status` |\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agent-sessions/:sessionId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n````\n\n## Restore Session\n\nRestore an expired session from stored messages. This allows you to continue a conversation after the server-side state has expired.\n\n```\nPOST /api/agent-sessions/:sessionId/restore\n```\n\n### Request Body\n\n```json\n{\n \"messages\": [\n {\n \"id\": \"1702345800000-xyz789a\",\n \"role\": \"user\",\n \"parts\": [{ \"type\": \"text\", \"text\": \"How do I reset my password?\", \"status\": \"done\" }],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:00.000Z\"\n }\n ],\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\"\n }\n}\n```\n\n| Field | Type | Required | Description |\n| ---------- | ----------- | -------- | -------------------------------------------------------------- |\n| `messages` | UIMessage[] | Yes | Previously stored chat history |\n| `input` | object | No | Session input for system prompt interpolation (same as create) |\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"restored\": true\n}\n```\n\n| Field | Type | Description |\n| ----------- | ------- | ----------------------------------------------------------------------- |\n| `sessionId` | string | The session ID |\n| `restored` | boolean | `true` if restored from messages, `false` if session was already active |\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agent-sessions/:sessionId/restore \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"messages\": [...],\n \"input\": { \"COMPANY_NAME\": \"Acme Corp\" }\n }'\n```\n\n> **Note**: Store the `UIMessage[]` array after each interaction to enable restoration. The restore endpoint reconstructs the conversation state from these messages.\n\n## Clear Session\n\nClear session state, transitioning it to `expired` status. The session can be restored afterwards with the [Restore Session](#restore-session) endpoint.\n\nThis is idempotent — clearing an already expired session succeeds without error.\n\n```\nDELETE /api/agent-sessions/:sessionId\n```\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"cleared\": true\n}\n```\n\n### Example\n\n```bash\ncurl -X DELETE https://octavus.ai/api/agent-sessions/:sessionId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Trigger Session\n\nExecute a trigger on a session. Returns a Server-Sent Events stream.\n\n```\nPOST /api/agent-sessions/:sessionId/trigger\n```\n\n### Request Body\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"How do I reset my password?\"\n },\n \"toolResults\": []\n}\n```\n\n| Field | Type | Required | Description |\n| ------------- | ------ | -------- | ---------------------------------------------- |\n| `triggerName` | string | Yes | Name of the trigger to execute |\n| `input` | object | No | Input variables for the trigger |\n| `toolResults` | array | No | Tool results for continuation (handled by SDK) |\n\n### Response\n\nReturns `text/event-stream` with SSE events:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg-123\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b1\",\"blockName\":\"Add user message\",\"blockType\":\"add-message\",\"display\":\"hidden\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b1\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b2\",\"blockName\":\"Respond to user\",\"blockType\":\"next-message\",\"display\":\"stream\",\"outputToChat\":true}\n\ndata: {\"type\":\"text-start\",\"id\":\"t1\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"I\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" can\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" help\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" you\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" reset\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" your\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" password\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"!\"}\n\ndata: {\"type\":\"text-end\",\"id\":\"t1\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b2\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\n### Event Types\n\n| Event | Description |\n| ----------------------- | ---------------------------------- |\n| `start` | Stream started |\n| `finish` | Execution complete |\n| `error` | Error occurred |\n| `block-start` | Execution block started |\n| `block-end` | Execution block completed |\n| `text-start` | Text generation started |\n| `text-delta` | Incremental text content |\n| `text-end` | Text generation ended |\n| `reasoning-start` | Extended reasoning started |\n| `reasoning-delta` | Reasoning content |\n| `reasoning-end` | Extended reasoning ended |\n| `tool-input-start` | Tool call initiated |\n| `tool-input-delta` | Tool arguments streaming |\n| `tool-input-end` | Tool arguments streaming ended |\n| `tool-input-available` | Tool input complete |\n| `tool-output-available` | Tool completed with result |\n| `tool-output-error` | Tool failed |\n| `tool-request` | Platform requesting tool execution |\n| `file-available` | File ready for display/download |\n| `resource-update` | Resource value changed |\n\n### Example\n\n```bash\ncurl -N -X POST https://octavus.ai/api/agent-sessions/:sessionId/trigger \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"How do I reset my password?\" }\n }'\n```\n\n## Tool Continuation\n\nWhen the agent calls external tools, you'll receive a `tool-request` event. Execute the tools and send results back:\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"...\" },\n \"toolResults\": [\n {\n \"toolCallId\": \"tc_123\",\n \"toolName\": \"get-user-account\",\n \"result\": {\n \"name\": \"Demo User\",\n \"email\": \"demo@example.com\"\n }\n }\n ]\n}\n```\n\nThe Server SDK handles this continuation pattern automatically.\n\n## Upload URLs\n\nGet presigned URLs for file uploads. Files are uploaded directly to S3.\n\n```\nPOST /api/files/upload-urls\n```\n\n### Request Body\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"files\": [\n {\n \"filename\": \"photo.jpg\",\n \"mediaType\": \"image/jpeg\",\n \"size\": 102400\n }\n ]\n}\n```\n\n| Field | Type | Required | Description |\n| ------------------- | ------ | -------- | ----------------------------------- |\n| `sessionId` | string | Yes | Session ID to associate files with |\n| `files` | array | Yes | Array of file metadata (1-20 files) |\n| `files[].filename` | string | Yes | Original filename |\n| `files[].mediaType` | string | Yes | MIME type (e.g., `image/png`) |\n| `files[].size` | number | Yes | File size in bytes |\n\n### Response\n\n```json\n{\n \"files\": [\n {\n \"id\": \"file-abc123\",\n \"uploadUrl\": \"https://s3.amazonaws.com/bucket/key?...\",\n \"downloadUrl\": \"https://s3.amazonaws.com/bucket/key?...\"\n }\n ]\n}\n```\n\n### Upload Flow\n\n1. Request upload URLs from the platform\n2. PUT file content to `uploadUrl` with `Content-Type` header\n3. Use `downloadUrl` as the `url` in `FileReference`\n4. Include `FileReference` in trigger input\n\n### Supported Types\n\n| Category | Media Types |\n| --------- | -------------------------------------------------------------------- |\n| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |\n| Documents | `application/pdf`, `text/plain`, `text/markdown`, `application/json` |\n\n### Limits\n\n| Limit | Value |\n| --------------------- | ---------- |\n| Max file size | 10 MB |\n| Max total per request | 50 MB |\n| Max files per request | 20 |\n| Upload URL expiry | 15 minutes |\n| Download URL expiry | 24 hours |\n",
|
|
295
|
+
"content": "\n# Sessions API\n\nSessions represent conversations with agents. They store conversation history, resources, and variables.\n\nAll session endpoints require an API key with the **Sessions** permission.\n\n## Create Session\n\nCreate a new agent session.\n\n```\nPOST /api/agent-sessions\n```\n\n### Request Body\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\",\n \"USER_ID\": \"user-123\"\n }\n}\n```\n\n| Field | Type | Required | Description |\n| --------- | ------ | -------- | ------------------------------------- |\n| `agentId` | string | Yes | Agent ID (the `id` field, not `slug`) |\n| `input` | object | No | Input variables for the agent |\n\n> **Getting the agent ID:** Copy the ID from the agent URL in the [platform](https://octavus.ai) (e.g., `octavus.ai/agents/clxyz123`), or use the [CLI](/docs/server-sdk/cli) (`octavus sync ./agents/my-agent`) for local development workflows.\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\"\n}\n```\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agent-sessions \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n }\n }'\n```\n\n## Get Session\n\nRetrieve session state. Returns UI-ready messages for active sessions, or expiration info for expired sessions.\n\n```\nGET /api/agent-sessions/:sessionId\n```\n\n### Query Parameters\n\n| Parameter | Type | Description |\n| --------- | ------ | ---------------------------------------------------- |\n| `format` | string | Optional. Use `format=ui` for UI-ready messages only |\n\n### Response (Active Session)\n\nWhen the session is active, the response includes `UIMessage` objects:\n\n```json\n{\n \"id\": \"cm5xyz123abc456def\",\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"status\": \"active\",\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\",\n \"PRODUCT_NAME\": \"Widget Pro\"\n },\n \"variables\": {},\n \"resources\": {\n \"CONVERSATION_SUMMARY\": \"\"\n },\n \"messages\": [\n {\n \"id\": \"1702345800000-xyz789a\",\n \"role\": \"user\",\n \"parts\": [{ \"type\": \"text\", \"text\": \"How do I reset my password?\", \"status\": \"done\" }],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:00.000Z\"\n },\n {\n \"id\": \"1702345805000-def456b\",\n \"role\": \"assistant\",\n \"parts\": [\n { \"type\": \"text\", \"text\": \"I can help you reset your password...\", \"status\": \"done\" }\n ],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:05.000Z\"\n }\n ],\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"updatedAt\": \"2024-01-15T10:30:05Z\"\n}\n```\n\n### Response (Expired Session)\n\nWhen the session has expired, the response indicates the expiration status:\n\n```json\n{\n \"status\": \"expired\",\n \"sessionId\": \"cm5xyz123abc456def\",\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"createdAt\": \"2024-01-15T10:30:00Z\"\n}\n```\n\nUse the [Restore Session](#restore-session) endpoint to restore an expired session from stored messages.\n\n````\n\n### UIMessage Parts\n\nMessages contain typed `parts` that preserve content ordering:\n\n| Part Type | Description |\n|-----------|-------------|\n| `text` | Text content with `text` and `status` fields |\n| `reasoning` | Extended reasoning with `text` and `status` fields |\n| `tool-call` | Tool execution with `toolCallId`, `toolName`, `displayName`, `args`, `result`, `status` |\n| `operation` | Internal operations with `operationId`, `name`, `operationType`, `status` |\n| `file` | File attachment with `id`, `mediaType`, `url`, `filename`, `size` |\n| `source` | Source reference with `sourceType`, `id`, `url`, `title` |\n| `object` | Structured output with `id`, `typeName`, `object`, `status` |\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agent-sessions/:sessionId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n````\n\n## Restore Session\n\nRestore an expired session from stored messages. This allows you to continue a conversation after the server-side state has expired.\n\n```\nPOST /api/agent-sessions/:sessionId/restore\n```\n\n### Request Body\n\n```json\n{\n \"messages\": [\n {\n \"id\": \"1702345800000-xyz789a\",\n \"role\": \"user\",\n \"parts\": [{ \"type\": \"text\", \"text\": \"How do I reset my password?\", \"status\": \"done\" }],\n \"status\": \"done\",\n \"createdAt\": \"2024-01-15T10:30:00.000Z\"\n }\n ],\n \"input\": {\n \"COMPANY_NAME\": \"Acme Corp\"\n }\n}\n```\n\n| Field | Type | Required | Description |\n| ---------- | ----------- | -------- | -------------------------------------------------------------- |\n| `messages` | UIMessage[] | Yes | Previously stored chat history |\n| `input` | object | No | Session input for system prompt interpolation (same as create) |\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"restored\": true\n}\n```\n\n| Field | Type | Description |\n| ----------- | ------- | ----------------------------------------------------------------------- |\n| `sessionId` | string | The session ID |\n| `restored` | boolean | `true` if restored from messages, `false` if session was already active |\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agent-sessions/:sessionId/restore \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"messages\": [...],\n \"input\": { \"COMPANY_NAME\": \"Acme Corp\" }\n }'\n```\n\n> **Note**: Store the `UIMessage[]` array after each interaction to enable restoration. The restore endpoint reconstructs the conversation state from these messages.\n\n## Clear Session\n\nClear session state, transitioning it to `expired` status. The session can be restored afterwards with the [Restore Session](#restore-session) endpoint.\n\nThis is idempotent — clearing an already expired session succeeds without error.\n\n```\nDELETE /api/agent-sessions/:sessionId\n```\n\n### Response\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"cleared\": true\n}\n```\n\n### Example\n\n```bash\ncurl -X DELETE https://octavus.ai/api/agent-sessions/:sessionId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Trigger Session\n\nExecute a trigger on a session. Returns a Server-Sent Events stream.\n\n```\nPOST /api/agent-sessions/:sessionId/trigger\n```\n\n### Request Body\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": {\n \"USER_MESSAGE\": \"How do I reset my password?\"\n },\n \"toolResults\": []\n}\n```\n\n| Field | Type | Required | Description |\n| ------------------------ | -------------- | -------- | -------------------------------------------------------------------------------------------------- |\n| `triggerName` | string | Yes | Name of the trigger to execute |\n| `input` | object | No | Input variables for the trigger |\n| `toolResults` | array | No | Tool results for continuation (handled by SDK) |\n| `rollbackAfterMessageId` | string \\| null | No | For retry: ID of the last message to keep. Messages after this are removed. `null` = truncate all. |\n\n### Response\n\nReturns `text/event-stream` with SSE events:\n\n```\ndata: {\"type\":\"start\",\"messageId\":\"msg-123\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b1\",\"blockName\":\"Add user message\",\"blockType\":\"add-message\",\"display\":\"hidden\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b1\"}\n\ndata: {\"type\":\"block-start\",\"blockId\":\"b2\",\"blockName\":\"Respond to user\",\"blockType\":\"next-message\",\"display\":\"stream\",\"outputToChat\":true}\n\ndata: {\"type\":\"text-start\",\"id\":\"t1\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"I\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" can\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" help\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" you\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" reset\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" your\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\" password\"}\n\ndata: {\"type\":\"text-delta\",\"id\":\"t1\",\"delta\":\"!\"}\n\ndata: {\"type\":\"text-end\",\"id\":\"t1\"}\n\ndata: {\"type\":\"block-end\",\"blockId\":\"b2\"}\n\ndata: {\"type\":\"finish\",\"finishReason\":\"stop\"}\n\ndata: [DONE]\n```\n\n### Event Types\n\n| Event | Description |\n| ----------------------- | ---------------------------------- |\n| `start` | Stream started |\n| `finish` | Execution complete |\n| `error` | Error occurred |\n| `block-start` | Execution block started |\n| `block-end` | Execution block completed |\n| `text-start` | Text generation started |\n| `text-delta` | Incremental text content |\n| `text-end` | Text generation ended |\n| `reasoning-start` | Extended reasoning started |\n| `reasoning-delta` | Reasoning content |\n| `reasoning-end` | Extended reasoning ended |\n| `tool-input-start` | Tool call initiated |\n| `tool-input-delta` | Tool arguments streaming |\n| `tool-input-end` | Tool arguments streaming ended |\n| `tool-input-available` | Tool input complete |\n| `tool-output-available` | Tool completed with result |\n| `tool-output-error` | Tool failed |\n| `tool-request` | Platform requesting tool execution |\n| `file-available` | File ready for display/download |\n| `resource-update` | Resource value changed |\n\n### Example\n\n```bash\ncurl -N -X POST https://octavus.ai/api/agent-sessions/:sessionId/trigger \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"How do I reset my password?\" }\n }'\n```\n\n## Tool Continuation\n\nWhen the agent calls external tools, you'll receive a `tool-request` event. Execute the tools and send results back:\n\n```json\n{\n \"triggerName\": \"user-message\",\n \"input\": { \"USER_MESSAGE\": \"...\" },\n \"toolResults\": [\n {\n \"toolCallId\": \"tc_123\",\n \"toolName\": \"get-user-account\",\n \"result\": {\n \"name\": \"Demo User\",\n \"email\": \"demo@example.com\"\n }\n }\n ]\n}\n```\n\nThe Server SDK handles this continuation pattern automatically.\n\n## Upload URLs\n\nGet presigned URLs for file uploads. Files are uploaded directly to S3.\n\n```\nPOST /api/files/upload-urls\n```\n\n### Request Body\n\n```json\n{\n \"sessionId\": \"cm5xyz123abc456def\",\n \"files\": [\n {\n \"filename\": \"photo.jpg\",\n \"mediaType\": \"image/jpeg\",\n \"size\": 102400\n }\n ]\n}\n```\n\n| Field | Type | Required | Description |\n| ------------------- | ------ | -------- | ----------------------------------- |\n| `sessionId` | string | Yes | Session ID to associate files with |\n| `files` | array | Yes | Array of file metadata (1-20 files) |\n| `files[].filename` | string | Yes | Original filename |\n| `files[].mediaType` | string | Yes | MIME type (e.g., `image/png`) |\n| `files[].size` | number | Yes | File size in bytes |\n\n### Response\n\n```json\n{\n \"files\": [\n {\n \"id\": \"file-abc123\",\n \"uploadUrl\": \"https://s3.amazonaws.com/bucket/key?...\",\n \"downloadUrl\": \"https://s3.amazonaws.com/bucket/key?...\"\n }\n ]\n}\n```\n\n### Upload Flow\n\n1. Request upload URLs from the platform\n2. PUT file content to `uploadUrl` with `Content-Type` header\n3. Use `downloadUrl` as the `url` in `FileReference`\n4. Include `FileReference` in trigger input\n\n### Supported Types\n\n| Category | Media Types |\n| --------- | -------------------------------------------------------------------- |\n| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |\n| Documents | `application/pdf`, `text/plain`, `text/markdown`, `application/json` |\n\n### Limits\n\n| Limit | Value |\n| --------------------- | ---------- |\n| Max file size | 10 MB |\n| Max total per request | 50 MB |\n| Max files per request | 20 |\n| Upload URL expiry | 15 minutes |\n| Download URL expiry | 24 hours |\n",
|
|
287
296
|
"excerpt": "Sessions API Sessions represent conversations with agents. They store conversation history, resources, and variables. All session endpoints require an API key with the Sessions permission. Create...",
|
|
288
297
|
"order": 2
|
|
289
298
|
},
|
|
@@ -292,8 +301,8 @@
|
|
|
292
301
|
"section": "api-reference",
|
|
293
302
|
"title": "Agents",
|
|
294
303
|
"description": "Agent management API endpoints.",
|
|
295
|
-
"content": "\n# Agents API\n\nManage agent definitions including protocols and
|
|
296
|
-
"excerpt": "Agents API Manage agent definitions including protocols and
|
|
304
|
+
"content": "\n# Agents API\n\nManage agent definitions including protocols, prompts, and references.\n\n## Permissions\n\n| Endpoint | Method | Permission Required |\n| ---------------------- | ------ | ------------------- |\n| `/api/agents` | GET | Agents OR Sessions |\n| `/api/agents/:id` | GET | Agents OR Sessions |\n| `/api/agents` | POST | Agents |\n| `/api/agents/:id` | PATCH | Agents |\n| `/api/agents/:id` | DELETE | Agents |\n| `/api/agents/validate` | POST | Agents |\n\nRead endpoints work with either permission since both the CLI (for sync) and Server SDK (for sessions) need to read agent definitions.\n\n## List Agents\n\nGet all agents in the project.\n\n```\nGET /api/agents\n```\n\n### Response\n\n```json\n{\n \"agents\": [\n {\n \"id\": \"cm5xvz7k80001abcd\",\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\",\n \"createdAt\": \"2024-01-10T08:00:00Z\",\n \"updatedAt\": \"2024-01-15T10:00:00Z\"\n }\n ]\n}\n```\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agents \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Get Agent\n\nGet a single agent by ID.\n\n```\nGET /api/agents/:id\n```\n\n### Response\n\n```json\n{\n \"id\": \"cm5xvz7k80001abcd\",\n \"settings\": {\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"You are a support agent for {{COMPANY_NAME}}...\"\n },\n {\n \"name\": \"user-message\",\n \"content\": \"{{USER_MESSAGE}}\"\n }\n ],\n \"references\": [\n {\n \"name\": \"api-guidelines\",\n \"description\": \"API design guidelines and conventions\",\n \"content\": \"# API Guidelines\\n\\nUse lowercase with dashes...\"\n }\n ]\n}\n```\n\n### Example\n\n```bash\ncurl https://octavus.ai/api/agents/:agentId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n> **Tip:** You can also view and edit agents directly in the [platform](https://octavus.ai), or use the [CLI](/docs/server-sdk/cli) (`octavus list`) for local workflows.\n\n## Create Agent\n\nCreate a new agent.\n\n```\nPOST /api/agents\n```\n\n### Request Body\n\n```json\n{\n \"settings\": {\n \"slug\": \"support-chat\",\n \"name\": \"Support Chat\",\n \"description\": \"Customer support agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"You are a support agent...\"\n }\n ],\n \"references\": [\n {\n \"name\": \"api-guidelines\",\n \"description\": \"API design guidelines and conventions\",\n \"content\": \"# API Guidelines\\n...\"\n }\n ]\n}\n```\n\n| Field | Type | Required | Description |\n| ---------------------- | ------ | -------- | ------------------------------------------------ |\n| `settings.slug` | string | Yes | URL-safe identifier |\n| `settings.name` | string | Yes | Display name |\n| `settings.description` | string | No | Agent description |\n| `settings.format` | string | Yes | `interactive` or `worker` |\n| `protocol` | string | Yes | YAML protocol definition |\n| `prompts` | array | Yes | Prompt files |\n| `references` | array | No | Reference documents (name, description, content) |\n\n### Response\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"message\": \"Agent created successfully\"\n}\n```\n\n### Example\n\n```bash\ncurl -X POST https://octavus.ai/api/agents \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"settings\": {\n \"slug\": \"my-agent\",\n \"name\": \"My Agent\",\n \"format\": \"interactive\"\n },\n \"protocol\": \"agent:\\n model: anthropic/claude-sonnet-4-5\\n system: system\",\n \"prompts\": [\n { \"name\": \"system\", \"content\": \"You are a helpful assistant.\" }\n ]\n }'\n```\n\n## Update Agent\n\nUpdate an existing agent.\n\n```\nPATCH /api/agents/:id\n```\n\n### Request Body\n\n```json\n{\n \"protocol\": \"input:\\n COMPANY_NAME: { type: string }\\n...\",\n \"prompts\": [\n {\n \"name\": \"system\",\n \"content\": \"Updated system prompt...\"\n }\n ],\n \"references\": [\n {\n \"name\": \"api-guidelines\",\n \"description\": \"Updated description\",\n \"content\": \"Updated content...\"\n }\n ]\n}\n```\n\nAll fields are optional. Only provided fields are updated.\n\n### Response\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"message\": \"Agent updated successfully\"\n}\n```\n\n### Example\n\n```bash\ncurl -X PATCH https://octavus.ai/api/agents/:agentId \\\n -H \"Authorization: Bearer YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"protocol\": \"agent:\\n model: anthropic/claude-sonnet-4-5\\n system: system\\n thinking: high\"\n }'\n```\n\n## Archive Agent\n\nArchive an agent (soft delete). The agent is removed from the active agent list and its slug is freed for reuse. Session history is preserved.\n\n```\nDELETE /api/agents/:id\n```\n\nSupports `?by=slug` query parameter to look up by slug instead of ID.\n\n### Response\n\n```json\n{\n \"agentId\": \"cm5xvz7k80001abcd\",\n \"message\": \"Agent archived successfully\"\n}\n```\n\n### Example\n\n```bash\n# Archive by ID\ncurl -X DELETE https://octavus.ai/api/agents/:agentId \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n\n# Archive by slug\ncurl -X DELETE https://octavus.ai/api/agents/support-chat?by=slug \\\n -H \"Authorization: Bearer YOUR_API_KEY\"\n```\n\n## Creating and Managing Agents\n\nThere are two ways to manage agents:\n\n### Platform UI\n\nCreate and edit agents directly at [octavus.ai](https://octavus.ai). The web editor provides real-time validation and is the easiest way to get started. Copy the agent ID from the URL to use in your application.\n\n### CLI (Local Development)\n\nFor version-controlled agent definitions, use the [Octavus CLI](/docs/server-sdk/cli):\n\n```bash\noctavus sync ./agents/support-chat\n```\n\nThis creates the agent if it doesn't exist, or updates it if it does. The CLI outputs the agent ID which you should store in an environment variable.\n\nFor CI/CD integration, see the [CLI documentation](/docs/server-sdk/cli#cicd-integration).\n",
|
|
305
|
+
"excerpt": "Agents API Manage agent definitions including protocols, prompts, and references. Permissions | Endpoint | Method | Permission Required | | ---------------------- | ------ |...",
|
|
297
306
|
"order": 3
|
|
298
307
|
},
|
|
299
308
|
{
|