@octavus/docs 1.0.0 → 2.0.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.
Files changed (46) hide show
  1. package/content/01-getting-started/02-quickstart.md +8 -5
  2. package/content/02-server-sdk/01-overview.md +22 -6
  3. package/content/02-server-sdk/02-sessions.md +51 -10
  4. package/content/02-server-sdk/03-tools.md +39 -14
  5. package/content/02-server-sdk/04-streaming.md +55 -7
  6. package/content/02-server-sdk/05-cli.md +9 -9
  7. package/content/03-client-sdk/01-overview.md +22 -9
  8. package/content/03-client-sdk/02-messages.md +6 -4
  9. package/content/03-client-sdk/03-streaming.md +7 -3
  10. package/content/03-client-sdk/05-socket-transport.md +31 -10
  11. package/content/03-client-sdk/06-http-transport.md +81 -17
  12. package/content/03-client-sdk/07-structured-output.md +3 -2
  13. package/content/03-client-sdk/08-file-uploads.md +6 -4
  14. package/content/03-client-sdk/10-client-tools.md +557 -0
  15. package/content/04-protocol/02-input-resources.md +12 -0
  16. package/content/04-protocol/03-triggers.md +8 -5
  17. package/content/04-protocol/06-handlers.md +10 -0
  18. package/content/04-protocol/07-agent-config.md +34 -1
  19. package/content/05-api-reference/01-overview.md +18 -0
  20. package/content/05-api-reference/02-sessions.md +2 -0
  21. package/content/05-api-reference/03-agents.md +12 -0
  22. package/content/06-examples/02-nextjs-chat.md +12 -7
  23. package/content/06-examples/03-socket-chat.md +27 -13
  24. package/content/07-migration/01-v1-to-v2.md +366 -0
  25. package/content/07-migration/_meta.md +4 -0
  26. package/dist/chunk-3ER2T7S7.js +663 -0
  27. package/dist/chunk-3ER2T7S7.js.map +1 -0
  28. package/dist/{chunk-WJ2W3DUC.js → chunk-HFF2TVGV.js} +13 -13
  29. package/dist/chunk-HFF2TVGV.js.map +1 -0
  30. package/dist/chunk-S5JUVAKE.js +1409 -0
  31. package/dist/chunk-S5JUVAKE.js.map +1 -0
  32. package/dist/chunk-TMJG4CJH.js +1409 -0
  33. package/dist/chunk-TMJG4CJH.js.map +1 -0
  34. package/dist/chunk-YJPO6KOJ.js +1435 -0
  35. package/dist/chunk-YJPO6KOJ.js.map +1 -0
  36. package/dist/chunk-ZSCRYD5P.js +1409 -0
  37. package/dist/chunk-ZSCRYD5P.js.map +1 -0
  38. package/dist/content.js +1 -1
  39. package/dist/docs.json +44 -26
  40. package/dist/index.js +1 -1
  41. package/dist/search-index.json +1 -1
  42. package/dist/search.js +1 -1
  43. package/dist/search.js.map +1 -1
  44. package/dist/sections.json +52 -26
  45. package/package.json +1 -1
  46. package/dist/chunk-WJ2W3DUC.js.map +0 -1
@@ -0,0 +1,557 @@
1
+ ---
2
+ title: Client Tools
3
+ description: Handling tool calls on the client side for interactive UI and browser-only operations.
4
+ ---
5
+
6
+ # Client Tools
7
+
8
+ By default, tools execute on your server where you have access to databases and APIs. However, some tools are better suited for client-side execution:
9
+
10
+ - **Browser-only operations** — Geolocation, clipboard, local storage
11
+ - **Interactive UIs** — Confirmation dialogs, form inputs, selections
12
+ - **Real-time feedback** — Progress indicators, approval workflows
13
+
14
+ ## How Client Tools Work
15
+
16
+ When the agent calls a tool, the Server SDK checks for a registered handler:
17
+
18
+ 1. **Handler exists** → Execute on server, continue automatically
19
+ 2. **No handler** → Forward to client as `client-tool-request` event
20
+
21
+ The client SDK receives pending tool calls, executes them (automatically or via user interaction), and sends results back to continue the conversation.
22
+
23
+ ```mermaid
24
+ sequenceDiagram
25
+ participant LLM
26
+ participant Platform
27
+ participant Server as Server SDK
28
+ participant Client as Client SDK
29
+ participant User
30
+
31
+ LLM->>Platform: Call get-location
32
+ Platform->>Server: tool-request
33
+ Note over Server: No handler for<br/>get-location
34
+ Server->>Client: client-tool-request
35
+ Client->>User: Request permission
36
+ User->>Client: Grant
37
+ Client->>Server: Tool results
38
+ Server->>Platform: Continue with results
39
+ Platform->>LLM: Process results
40
+ ```
41
+
42
+ ## Setup
43
+
44
+ ### Server Side
45
+
46
+ Define tools in your protocol but don't register handlers for client tools:
47
+
48
+ ```typescript
49
+ // Only register server-side tools
50
+ const session = client.agentSessions.attach(sessionId, {
51
+ tools: {
52
+ // Server tools have handlers
53
+ 'get-user-account': async (args) => {
54
+ return await db.users.findById(args.userId);
55
+ },
56
+ // Client tools have NO handler here
57
+ // 'get-browser-location' - handled on client
58
+ // 'request-feedback' - handled on client
59
+ },
60
+ });
61
+ ```
62
+
63
+ ### Client Side
64
+
65
+ Register client tool handlers when creating the chat:
66
+
67
+ ```typescript
68
+ const { messages, status, pendingClientTools } = useOctavusChat({
69
+ transport,
70
+ clientTools: {
71
+ // Automatic client tool
72
+ 'get-browser-location': async () => {
73
+ const pos = await new Promise((resolve, reject) => {
74
+ navigator.geolocation.getCurrentPosition(resolve, reject);
75
+ });
76
+ return { lat: pos.coords.latitude, lng: pos.coords.longitude };
77
+ },
78
+
79
+ // Interactive client tool (requires user action)
80
+ 'request-feedback': 'interactive',
81
+ },
82
+ });
83
+ ```
84
+
85
+ ## Automatic Client Tools
86
+
87
+ Automatic tools execute immediately when called. Use these for browser operations that don't require user input.
88
+
89
+ ### Example: Geolocation
90
+
91
+ ```typescript
92
+ const { messages, status } = useOctavusChat({
93
+ transport,
94
+ clientTools: {
95
+ 'get-browser-location': async (args, ctx) => {
96
+ // ctx provides toolCallId, toolName, and abort signal
97
+ const pos = await new Promise<GeolocationPosition>((resolve, reject) => {
98
+ navigator.geolocation.getCurrentPosition(resolve, reject, {
99
+ timeout: 10000,
100
+ });
101
+ });
102
+
103
+ return {
104
+ latitude: pos.coords.latitude,
105
+ longitude: pos.coords.longitude,
106
+ accuracy: pos.coords.accuracy,
107
+ };
108
+ },
109
+ },
110
+ });
111
+ ```
112
+
113
+ ### Example: Clipboard
114
+
115
+ ```typescript
116
+ clientTools: {
117
+ 'copy-to-clipboard': async (args) => {
118
+ await navigator.clipboard.writeText(args.text as string);
119
+ return { success: true };
120
+ },
121
+
122
+ 'read-clipboard': async () => {
123
+ const text = await navigator.clipboard.readText();
124
+ return { text };
125
+ },
126
+ }
127
+ ```
128
+
129
+ ### Context Object
130
+
131
+ Automatic handlers receive a context object:
132
+
133
+ ```typescript
134
+ interface ClientToolContext {
135
+ toolCallId: string; // Unique ID for this call
136
+ toolName: string; // Name of the tool
137
+ signal: AbortSignal; // Aborted if user stops generation
138
+ }
139
+ ```
140
+
141
+ Use the signal to cancel long-running operations:
142
+
143
+ ```typescript
144
+ 'fetch-external-data': async (args, ctx) => {
145
+ const response = await fetch(args.url, {
146
+ signal: ctx.signal, // Cancels if user stops
147
+ });
148
+ return await response.json();
149
+ }
150
+ ```
151
+
152
+ ## Interactive Client Tools
153
+
154
+ Interactive tools require user input before completing. Use these for confirmations, forms, or any UI that needs user action.
155
+
156
+ Mark a tool as interactive by setting its handler to `'interactive'`:
157
+
158
+ ```typescript
159
+ const { messages, status, pendingClientTools } = useOctavusChat({
160
+ transport,
161
+ clientTools: {
162
+ 'request-feedback': 'interactive',
163
+ 'confirm-action': 'interactive',
164
+ 'select-option': 'interactive',
165
+ },
166
+ });
167
+ ```
168
+
169
+ ### Accessing Pending Tools
170
+
171
+ Interactive tools appear in `pendingClientTools`, keyed by tool name:
172
+
173
+ ```typescript
174
+ // pendingClientTools structure:
175
+ {
176
+ 'request-feedback': [
177
+ {
178
+ toolCallId: 'call_abc123',
179
+ toolName: 'request-feedback',
180
+ args: { question: 'How would you rate this response?' },
181
+ submit: (result) => void, // Call with user's input
182
+ cancel: (reason?) => void, // Call if user dismisses
183
+ }
184
+ ],
185
+ 'confirm-action': [
186
+ // Multiple calls to same tool are possible
187
+ ]
188
+ }
189
+ ```
190
+
191
+ ### Rendering Interactive UIs
192
+
193
+ ```tsx
194
+ function Chat() {
195
+ const { messages, status, pendingClientTools, send } = useOctavusChat({
196
+ transport,
197
+ clientTools: {
198
+ 'request-feedback': 'interactive',
199
+ },
200
+ });
201
+
202
+ const feedbackTools = pendingClientTools['request-feedback'] ?? [];
203
+
204
+ return (
205
+ <div>
206
+ {/* Chat messages */}
207
+ <MessageList messages={messages} />
208
+
209
+ {/* Interactive tool UIs */}
210
+ {feedbackTools.map((tool) => (
211
+ <FeedbackModal
212
+ key={tool.toolCallId}
213
+ question={tool.args.question as string}
214
+ onSubmit={(rating, comment) => {
215
+ tool.submit({ rating, comment });
216
+ }}
217
+ onCancel={() => {
218
+ tool.cancel('User dismissed');
219
+ }}
220
+ />
221
+ ))}
222
+
223
+ {/* Input disabled while awaiting user action */}
224
+ <ChatInput disabled={status === 'awaiting-input'} />
225
+ </div>
226
+ );
227
+ }
228
+ ```
229
+
230
+ ### Example: Confirmation Dialog
231
+
232
+ ```tsx
233
+ function ConfirmationDialog({ tool }: { tool: InteractiveTool }) {
234
+ const { action, description } = tool.args as {
235
+ action: string;
236
+ description: string;
237
+ };
238
+
239
+ return (
240
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center">
241
+ <div className="bg-white p-6 rounded-lg max-w-md">
242
+ <h3 className="text-lg font-semibold">Confirm {action}</h3>
243
+ <p className="mt-2 text-gray-600">{description}</p>
244
+ <div className="mt-4 flex gap-3 justify-end">
245
+ <button onClick={() => tool.cancel()} className="px-4 py-2 border rounded">
246
+ Cancel
247
+ </button>
248
+ <button
249
+ onClick={() => tool.submit({ confirmed: true })}
250
+ className="px-4 py-2 bg-blue-500 text-white rounded"
251
+ >
252
+ Confirm
253
+ </button>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ );
258
+ }
259
+ ```
260
+
261
+ ### Example: Form Input
262
+
263
+ ```tsx
264
+ function FormInputTool({ tool }: { tool: InteractiveTool }) {
265
+ const [values, setValues] = useState<Record<string, string>>({});
266
+ const fields = tool.args.fields as { name: string; label: string; type: string }[];
267
+
268
+ const handleSubmit = (e: React.FormEvent) => {
269
+ e.preventDefault();
270
+ tool.submit(values);
271
+ };
272
+
273
+ return (
274
+ <form onSubmit={handleSubmit} className="p-4 border rounded-lg">
275
+ {fields.map((field) => (
276
+ <div key={field.name} className="mb-4">
277
+ <label className="block text-sm font-medium">{field.label}</label>
278
+ <input
279
+ type={field.type}
280
+ value={values[field.name] ?? ''}
281
+ onChange={(e) => setValues({ ...values, [field.name]: e.target.value })}
282
+ className="mt-1 w-full px-3 py-2 border rounded"
283
+ />
284
+ </div>
285
+ ))}
286
+ <div className="flex gap-2 justify-end">
287
+ <button type="button" onClick={() => tool.cancel()} className="px-4 py-2 border rounded">
288
+ Cancel
289
+ </button>
290
+ <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
291
+ Submit
292
+ </button>
293
+ </div>
294
+ </form>
295
+ );
296
+ }
297
+ ```
298
+
299
+ ## Status: awaiting-input
300
+
301
+ When interactive tools are pending, the chat status changes to `'awaiting-input'`:
302
+
303
+ ```typescript
304
+ type ChatStatus = 'idle' | 'streaming' | 'error' | 'awaiting-input';
305
+ ```
306
+
307
+ Use this to:
308
+
309
+ - Disable the send button
310
+ - Show "Waiting for input" indicators
311
+ - Prevent new messages until tools complete
312
+
313
+ ```tsx
314
+ function ChatInput({ status }: { status: ChatStatus }) {
315
+ const isDisabled = status === 'streaming' || status === 'awaiting-input';
316
+
317
+ return (
318
+ <div>
319
+ {status === 'awaiting-input' && (
320
+ <div className="text-amber-600 text-sm mb-2">
321
+ Please respond to the prompt above to continue
322
+ </div>
323
+ )}
324
+ <input disabled={isDisabled} placeholder="Type a message..." />
325
+ </div>
326
+ );
327
+ }
328
+ ```
329
+
330
+ ## Mixed Server and Client Tools
331
+
332
+ Tools can be split between server and client based on where they should execute:
333
+
334
+ ```typescript
335
+ // Server (API route)
336
+ const session = client.agentSessions.attach(sessionId, {
337
+ tools: {
338
+ // Server tools - data access, mutations
339
+ 'get-user-account': async (args) => db.users.findById(args.userId),
340
+ 'create-order': async (args) => orderService.create(args),
341
+ },
342
+ });
343
+
344
+ // Client
345
+ const chat = useOctavusChat({
346
+ transport,
347
+ clientTools: {
348
+ // Automatic - browser capabilities
349
+ 'get-browser-location': async () => getGeolocation(),
350
+
351
+ // Interactive - user confirmation
352
+ 'confirm-order': 'interactive',
353
+ },
354
+ });
355
+ ```
356
+
357
+ When the LLM calls multiple tools in one turn:
358
+
359
+ 1. Server tools execute first on the server
360
+ 2. Server results are included in the `client-tool-request` event
361
+ 3. Client tools execute (automatic immediately, interactive waits)
362
+ 4. All results are sent together to continue
363
+
364
+ ## HTTP Transport
365
+
366
+ The HTTP transport handles client tool continuation automatically via a unified request pattern:
367
+
368
+ ```typescript
369
+ const transport = createHttpTransport({
370
+ // Single request handler for both triggers and continuations
371
+ request: (payload, options) =>
372
+ fetch('/api/trigger', {
373
+ method: 'POST',
374
+ headers: { 'Content-Type': 'application/json' },
375
+ body: JSON.stringify({ sessionId, ...payload }),
376
+ signal: options?.signal,
377
+ }),
378
+ });
379
+ ```
380
+
381
+ Your API route handles both request types:
382
+
383
+ ```typescript
384
+ // app/api/trigger/route.ts
385
+ export async function POST(request: Request) {
386
+ const body = await request.json();
387
+ const { sessionId, ...payload } = body;
388
+
389
+ const session = client.agentSessions.attach(sessionId, {
390
+ tools: {
391
+ // Server tools only
392
+ },
393
+ });
394
+
395
+ // execute() handles both triggers and continuations
396
+ const events = session.execute(payload, { signal: request.signal });
397
+
398
+ return new Response(toSSEStream(events), {
399
+ headers: { 'Content-Type': 'text/event-stream' },
400
+ });
401
+ }
402
+ ```
403
+
404
+ ## Socket Transport
405
+
406
+ The socket transport sends a `continue` message with tool results:
407
+
408
+ ### Client → Server Messages
409
+
410
+ ```typescript
411
+ // Trigger (start new conversation turn)
412
+ { type: 'trigger', triggerName: string, input?: object }
413
+
414
+ // Continue (after client tool handling)
415
+ { type: 'continue', executionId: string, toolResults: ToolResult[] }
416
+
417
+ // Stop (cancel current operation)
418
+ { type: 'stop' }
419
+ ```
420
+
421
+ ### Server Handler
422
+
423
+ ```typescript
424
+ async function handleMessage(rawData: string, conn: Connection, session: AgentSession) {
425
+ const msg = JSON.parse(rawData);
426
+
427
+ if (msg.type === 'stop') {
428
+ abortController?.abort();
429
+ return;
430
+ }
431
+
432
+ // execute() handles both trigger and continue
433
+ const events = session.execute(msg, { signal: abortController?.signal });
434
+
435
+ for await (const event of events) {
436
+ conn.write(JSON.stringify(event));
437
+ }
438
+ }
439
+ ```
440
+
441
+ ## Error Handling
442
+
443
+ ### Automatic Tool Errors
444
+
445
+ Errors in automatic handlers are caught and sent back to the LLM:
446
+
447
+ ```typescript
448
+ clientTools: {
449
+ 'get-browser-location': async () => {
450
+ // If geolocation fails, the error is captured
451
+ const pos = await new Promise((resolve, reject) => {
452
+ navigator.geolocation.getCurrentPosition(resolve, reject);
453
+ });
454
+ return { lat: pos.coords.latitude, lng: pos.coords.longitude };
455
+ },
456
+ }
457
+ ```
458
+
459
+ The LLM receives: `"Tool error: User denied geolocation"`
460
+
461
+ ### Interactive Tool Cancellation
462
+
463
+ When users cancel interactive tools, provide a reason:
464
+
465
+ ```typescript
466
+ tool.cancel('User chose not to confirm');
467
+ ```
468
+
469
+ The LLM receives the cancellation reason and can respond appropriately.
470
+
471
+ ### Missing Handlers
472
+
473
+ If a client tool has no handler registered, an error is sent automatically:
474
+
475
+ ```
476
+ "No client handler for tool: some-tool-name"
477
+ ```
478
+
479
+ ## Best Practices
480
+
481
+ ### 1. Keep Server Tools on Server
482
+
483
+ Don't move database or API operations to client tools:
484
+
485
+ ```typescript
486
+ // Good - data access on server
487
+ // Server:
488
+ tools: { 'get-user': async (args) => db.users.find(args.id) }
489
+
490
+ // Bad - exposing data access to client
491
+ // Client:
492
+ clientTools: { 'get-user': async (args) => fetch('/api/users/' + args.id) }
493
+ ```
494
+
495
+ ### 2. Use Interactive for Confirmations
496
+
497
+ Any destructive or important action should confirm with the user:
498
+
499
+ ```typescript
500
+ clientTools: {
501
+ 'confirm-delete': 'interactive',
502
+ 'confirm-purchase': 'interactive',
503
+ 'confirm-send-email': 'interactive',
504
+ }
505
+ ```
506
+
507
+ ### 3. Handle Cancellation Gracefully
508
+
509
+ Always provide cancel buttons for interactive tools:
510
+
511
+ ```tsx
512
+ <Dialog>
513
+ <button onClick={() => tool.submit(result)}>Confirm</button>
514
+ <button onClick={() => tool.cancel()}>Cancel</button>
515
+ </Dialog>
516
+ ```
517
+
518
+ ### 4. Validate Results
519
+
520
+ Validate user input before submitting:
521
+
522
+ ```typescript
523
+ const handleSubmit = () => {
524
+ if (!rating || rating < 1 || rating > 5) {
525
+ setError('Please select a rating');
526
+ return;
527
+ }
528
+ tool.submit({ rating });
529
+ };
530
+ ```
531
+
532
+ ## Type Reference
533
+
534
+ ```typescript
535
+ // Handler types
536
+ type ClientToolHandler =
537
+ | ((args: Record<string, unknown>, ctx: ClientToolContext) => Promise<unknown>)
538
+ | 'interactive';
539
+
540
+ interface ClientToolContext {
541
+ toolCallId: string;
542
+ toolName: string;
543
+ signal: AbortSignal;
544
+ }
545
+
546
+ // Interactive tool (with bound methods)
547
+ interface InteractiveTool {
548
+ toolCallId: string;
549
+ toolName: string;
550
+ args: Record<string, unknown>;
551
+ submit: (result: unknown) => void;
552
+ cancel: (reason?: string) => void;
553
+ }
554
+
555
+ // Chat status
556
+ type ChatStatus = 'idle' | 'streaming' | 'error' | 'awaiting-input';
557
+ ```
@@ -59,6 +59,18 @@ const sessionId = await client.agentSessions.create('support-chat', {
59
59
  });
60
60
  ```
61
61
 
62
+ Inputs can also be used for [dynamic model selection](/docs/protocol/agent-config#dynamic-model-selection):
63
+
64
+ ```yaml
65
+ input:
66
+ MODEL:
67
+ type: string
68
+ description: The LLM model to use
69
+
70
+ agent:
71
+ model: MODEL # Resolved from session input
72
+ ```
73
+
62
74
  In prompts, reference with `{{INPUT_NAME}}`:
63
75
 
64
76
  ```markdown
@@ -89,11 +89,12 @@ function Chat({ sessionId }: { sessionId: string }) {
89
89
  const transport = useMemo(
90
90
  () =>
91
91
  createHttpTransport({
92
- triggerRequest: (triggerName, input) =>
92
+ request: (payload, options) =>
93
93
  fetch('/api/trigger', {
94
94
  method: 'POST',
95
95
  headers: { 'Content-Type': 'application/json' },
96
- body: JSON.stringify({ sessionId, triggerName, input }),
96
+ body: JSON.stringify({ sessionId, ...payload }),
97
+ signal: options?.signal,
97
98
  }),
98
99
  }),
99
100
  [sessionId],
@@ -115,9 +116,11 @@ function Chat({ sessionId }: { sessionId: string }) {
115
116
  ### From Server SDK
116
117
 
117
118
  ```typescript
118
- // trigger() returns an async generator of events
119
- const events = session.trigger('user-message', {
120
- USER_MESSAGE: 'Help me with billing',
119
+ // execute() returns an async generator of events
120
+ const events = session.execute({
121
+ type: 'trigger',
122
+ triggerName: 'user-message',
123
+ input: { USER_MESSAGE: 'Help me with billing' },
121
124
  });
122
125
 
123
126
  // Iterate events directly
@@ -150,6 +150,16 @@ Start summary thread:
150
150
  input: [COMPANY_NAME] # Variables for prompt
151
151
  ```
152
152
 
153
+ The `model` field can also reference a variable for dynamic model selection:
154
+
155
+ ```yaml
156
+ Start summary thread:
157
+ block: start-thread
158
+ thread: summary
159
+ model: SUMMARY_MODEL # Resolved from input variable
160
+ system: escalation-summary
161
+ ```
162
+
153
163
  ### serialize-thread
154
164
 
155
165
  Convert conversation to text:
@@ -21,7 +21,7 @@ agent:
21
21
 
22
22
  | Field | Required | Description |
23
23
  | ------------- | -------- | --------------------------------------------------------- |
24
- | `model` | Yes | Model identifier (provider/model-id) |
24
+ | `model` | Yes | Model identifier or variable reference |
25
25
  | `system` | Yes | System prompt filename (without .md) |
26
26
  | `input` | No | Variables to interpolate in system prompt |
27
27
  | `tools` | No | List of tools the LLM can call |
@@ -67,6 +67,39 @@ agent:
67
67
 
68
68
  > **Note**: Model IDs are passed directly to the provider SDK. Check the provider's documentation for the latest available models.
69
69
 
70
+ ### Dynamic Model Selection
71
+
72
+ The model field can also reference an input variable, allowing consumers to choose the model when creating a session:
73
+
74
+ ```yaml
75
+ input:
76
+ MODEL:
77
+ type: string
78
+ description: The LLM model to use
79
+
80
+ agent:
81
+ model: MODEL # Resolved from session input
82
+ system: system
83
+ ```
84
+
85
+ When creating a session, pass the model:
86
+
87
+ ```typescript
88
+ const sessionId = await client.agentSessions.create('my-agent', {
89
+ MODEL: 'anthropic/claude-sonnet-4-5',
90
+ });
91
+ ```
92
+
93
+ This enables:
94
+
95
+ - **Multi-provider support** — Same agent works with different providers
96
+ - **A/B testing** — Test different models without protocol changes
97
+ - **User preferences** — Let users choose their preferred model
98
+
99
+ The model value is validated at runtime to ensure it's in the correct `provider/model-id` format.
100
+
101
+ > **Note**: When using dynamic models, provider-specific options (like `anthropic:`) may not apply if the model resolves to a different provider.
102
+
70
103
  ## System Prompt
71
104
 
72
105
  The system prompt sets the agent's persona and instructions:
@@ -24,6 +24,24 @@ curl -H "Authorization: Bearer YOUR_API_KEY" \
24
24
 
25
25
  API keys can be created in the Octavus Platform under your project's **API Keys** page.
26
26
 
27
+ ## API Key Permissions
28
+
29
+ API keys have two permission scopes:
30
+
31
+ | Permission | Description | Used By |
32
+ | ------------ | -------------------------------------------------------- | ---------- |
33
+ | **Sessions** | Create and manage sessions, trigger agents, upload files | Server SDK |
34
+ | **Agents** | Create, update, and validate agent definitions | CLI |
35
+
36
+ Both permissions allow reading agent definitions (needed by CLI for sync and Server SDK for sessions).
37
+
38
+ **Recommended setup:** Use separate API keys for different purposes:
39
+
40
+ - **CLI key** with only "Agents" permission for CI/CD and development
41
+ - **Server key** with only "Sessions" permission for production applications
42
+
43
+ This limits the blast radius if a key is compromised.
44
+
27
45
  ## Response Format
28
46
 
29
47
  All responses are JSON. Success responses return the data directly (not wrapped in a `data` field).
@@ -7,6 +7,8 @@ description: Session management API endpoints.
7
7
 
8
8
  Sessions represent conversations with agents. They store conversation history, resources, and variables.
9
9
 
10
+ All session endpoints require an API key with the **Sessions** permission.
11
+
10
12
  ## Create Session
11
13
 
12
14
  Create a new agent session.