@octavus/docs 2.10.0 → 2.11.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.
@@ -96,6 +96,22 @@ return new Response(toSSEStream(events), {
96
96
  });
97
97
  ```
98
98
 
99
+ ### Workers
100
+
101
+ Execute worker agents for task-based processing:
102
+
103
+ ```typescript
104
+ // Non-streaming: get the output directly
105
+ const { output } = await client.workers.generate(agentId, {
106
+ TOPIC: 'AI safety',
107
+ });
108
+
109
+ // Streaming: observe events in real-time
110
+ for await (const event of client.workers.execute(agentId, input)) {
111
+ // Handle stream events
112
+ }
113
+ ```
114
+
99
115
  ## API Reference
100
116
 
101
117
  ### OctavusClient
@@ -17,59 +17,56 @@ const client = new OctavusClient({
17
17
  apiKey: 'your-api-key',
18
18
  });
19
19
 
20
- // Execute a worker
21
- const events = client.workers.execute(agentId, {
20
+ const { output, sessionId } = await client.workers.generate(agentId, {
22
21
  TOPIC: 'AI safety',
23
22
  DEPTH: 'detailed',
24
23
  });
25
24
 
26
- // Process events
27
- for await (const event of events) {
28
- if (event.type === 'worker-start') {
29
- console.log(`Worker ${event.workerSlug} started`);
30
- }
31
- if (event.type === 'text-delta') {
32
- process.stdout.write(event.delta);
33
- }
34
- if (event.type === 'worker-result') {
35
- console.log('Output:', event.output);
36
- }
37
- }
25
+ console.log('Result:', output);
26
+ console.log(`Debug: ${client.baseUrl}/sessions/${sessionId}`);
38
27
  ```
39
28
 
40
29
  ## WorkersApi Reference
41
30
 
42
- ### execute()
31
+ ### generate()
43
32
 
44
- Execute a worker and stream the response.
33
+ Execute a worker and return the output directly.
45
34
 
46
35
  ```typescript
47
- async *execute(
36
+ async generate(
48
37
  agentId: string,
49
38
  input: Record<string, unknown>,
50
39
  options?: WorkerExecuteOptions
51
- ): AsyncGenerator<StreamEvent>
40
+ ): Promise<WorkerGenerateResult>
52
41
  ```
53
42
 
54
- **Parameters:**
55
-
56
- | Parameter | Type | Description |
57
- | --------- | ------------------------- | --------------------------- |
58
- | `agentId` | `string` | The worker agent ID |
59
- | `input` | `Record<string, unknown>` | Input values for the worker |
60
- | `options` | `WorkerExecuteOptions` | Optional configuration |
43
+ Runs the worker to completion and returns the output value. This is the simplest way to execute a worker.
61
44
 
62
- **Options:**
45
+ **Returns:**
63
46
 
64
47
  ```typescript
65
- interface WorkerExecuteOptions {
66
- /** Tool handlers for server-side tool execution */
67
- tools?: ToolHandlers;
68
- /** Abort signal to cancel the execution */
69
- signal?: AbortSignal;
48
+ interface WorkerGenerateResult {
49
+ /** The worker's output value */
50
+ output: unknown;
51
+ /** Session ID for debugging (usable for session URLs) */
52
+ sessionId: string;
70
53
  }
71
54
  ```
72
55
 
56
+ **Throws:** `WorkerError` if the worker fails or completes without producing output.
57
+
58
+ ### execute()
59
+
60
+ Execute a worker and stream the response. Use this when you need to observe intermediate events like text deltas, tool calls, or progress tracking.
61
+
62
+ ```typescript
63
+ async *execute(
64
+ agentId: string,
65
+ input: Record<string, unknown>,
66
+ options?: WorkerExecuteOptions
67
+ ): AsyncGenerator<StreamEvent>
68
+ ```
69
+
73
70
  ### continue()
74
71
 
75
72
  Continue execution after client-side tool handling.
@@ -85,19 +82,39 @@ async *continue(
85
82
 
86
83
  Use this when the worker has tools without server-side handlers. The execution pauses with a `client-tool-request` event, you execute the tools, then call `continue()` to resume.
87
84
 
85
+ ### Shared Options
86
+
87
+ All methods accept the same options:
88
+
89
+ ```typescript
90
+ interface WorkerExecuteOptions {
91
+ /** Tool handlers for server-side tool execution */
92
+ tools?: ToolHandlers;
93
+ /** Abort signal to cancel the execution */
94
+ signal?: AbortSignal;
95
+ }
96
+ ```
97
+
98
+ **Parameters:**
99
+
100
+ | Parameter | Type | Description |
101
+ | --------- | ------------------------- | --------------------------- |
102
+ | `agentId` | `string` | The worker agent ID |
103
+ | `input` | `Record<string, unknown>` | Input values for the worker |
104
+ | `options` | `WorkerExecuteOptions` | Optional configuration |
105
+
88
106
  ## Tool Handlers
89
107
 
90
108
  Provide tool handlers to execute tools server-side:
91
109
 
92
110
  ```typescript
93
- const events = client.workers.execute(
111
+ const { output } = await client.workers.generate(
94
112
  agentId,
95
113
  { TOPIC: 'AI safety' },
96
114
  {
97
115
  tools: {
98
116
  'web-search': async (args) => {
99
- const results = await searchWeb(args.query);
100
- return results;
117
+ return await searchWeb(args.query);
101
118
  },
102
119
  'get-user-data': async (args) => {
103
120
  return await db.users.findById(args.userId);
@@ -109,85 +126,141 @@ const events = client.workers.execute(
109
126
 
110
127
  Tools defined in the worker protocol but not provided as handlers become client tools — the execution pauses and emits a `client-tool-request` event.
111
128
 
112
- ## Stream Events
129
+ ## Error Handling
113
130
 
114
- Workers emit standard stream events plus worker-specific events.
131
+ ### WorkerError (generate)
115
132
 
116
- ### Worker Events
133
+ `generate()` throws a `WorkerError` on failure. The error includes an optional `sessionId` for constructing debug URLs:
117
134
 
118
135
  ```typescript
119
- // Worker started
120
- {
121
- type: 'worker-start',
122
- workerId: string, // Unique ID (also used as session ID for debug)
123
- workerSlug: string, // The worker's slug
124
- description?: string, // Display description for UI
136
+ import { OctavusClient, WorkerError } from '@octavus/server-sdk';
137
+
138
+ try {
139
+ const { output } = await client.workers.generate(agentId, input);
140
+ console.log('Result:', output);
141
+ } catch (error) {
142
+ if (error instanceof WorkerError) {
143
+ console.error('Worker failed:', error.message);
144
+ if (error.sessionId) {
145
+ console.error(`Debug: ${client.baseUrl}/sessions/${error.sessionId}`);
146
+ }
147
+ }
125
148
  }
149
+ ```
126
150
 
127
- // Worker completed
128
- {
129
- type: 'worker-result',
130
- workerId: string,
131
- output?: unknown, // The worker's output value
132
- error?: string, // Error message if worker failed
151
+ ### Stream Errors (execute)
152
+
153
+ When using `execute()`, errors appear as stream events:
154
+
155
+ ```typescript
156
+ for await (const event of client.workers.execute(agentId, input)) {
157
+ if (event.type === 'error') {
158
+ console.error(`Error: ${event.message}`);
159
+ console.error(`Type: ${event.errorType}`);
160
+ console.error(`Retryable: ${event.retryable}`);
161
+ }
162
+
163
+ if (event.type === 'worker-result' && event.error) {
164
+ console.error(`Worker failed: ${event.error}`);
165
+ }
133
166
  }
134
167
  ```
135
168
 
136
- ### Common Events
169
+ ### Error Types
137
170
 
138
- | Event | Description |
139
- | ----------------------- | --------------------------- |
140
- | `start` | Execution started |
141
- | `finish` | Execution completed |
142
- | `text-start` | Text generation started |
143
- | `text-delta` | Text chunk received |
144
- | `text-end` | Text generation ended |
145
- | `block-start` | Step started |
146
- | `block-end` | Step completed |
147
- | `tool-input-available` | Tool arguments ready |
148
- | `tool-output-available` | Tool result ready |
149
- | `client-tool-request` | Client tools need execution |
150
- | `error` | Error occurred |
171
+ | Type | Description |
172
+ | ------------------ | --------------------- |
173
+ | `validation_error` | Invalid input |
174
+ | `not_found_error` | Worker not found |
175
+ | `provider_error` | LLM provider error |
176
+ | `tool_error` | Tool execution failed |
177
+ | `execution_error` | Worker step failed |
151
178
 
152
- ## Extracting Output
179
+ ## Cancellation
153
180
 
154
- To get just the worker's output value:
181
+ Use an abort signal to cancel execution:
155
182
 
156
183
  ```typescript
157
- async function executeWorker(
158
- client: OctavusClient,
159
- agentId: string,
160
- input: Record<string, unknown>,
161
- ): Promise<unknown> {
162
- const events = client.workers.execute(agentId, input);
184
+ const { output } = await client.workers.generate(agentId, input, {
185
+ signal: AbortSignal.timeout(30_000),
186
+ });
187
+ ```
163
188
 
164
- for await (const event of events) {
165
- if (event.type === 'worker-result') {
166
- if (event.error) {
167
- throw new Error(event.error);
168
- }
169
- return event.output;
170
- }
189
+ With `execute()` and a manual controller:
190
+
191
+ ```typescript
192
+ const controller = new AbortController();
193
+ setTimeout(() => controller.abort(), 30000);
194
+
195
+ try {
196
+ for await (const event of client.workers.execute(agentId, input, {
197
+ signal: controller.signal,
198
+ })) {
199
+ // Process events
200
+ }
201
+ } catch (error) {
202
+ if (error.name === 'AbortError') {
203
+ console.log('Worker cancelled');
171
204
  }
205
+ }
206
+ ```
207
+
208
+ ## Streaming
172
209
 
173
- return undefined;
210
+ When you need real-time visibility into the worker's execution — text generation, tool calls, or progress — use `execute()` instead of `generate()`.
211
+
212
+ ### Basic Streaming
213
+
214
+ ```typescript
215
+ const events = client.workers.execute(agentId, {
216
+ TOPIC: 'AI safety',
217
+ DEPTH: 'detailed',
218
+ });
219
+
220
+ for await (const event of events) {
221
+ if (event.type === 'worker-start') {
222
+ console.log(`Worker ${event.workerSlug} started`);
223
+ }
224
+ if (event.type === 'text-delta') {
225
+ process.stdout.write(event.delta);
226
+ }
227
+ if (event.type === 'worker-result') {
228
+ console.log('Output:', event.output);
229
+ }
174
230
  }
231
+ ```
232
+
233
+ ### Streaming to HTTP Response
234
+
235
+ Convert worker events to an SSE stream:
236
+
237
+ ```typescript
238
+ import { toSSEStream } from '@octavus/server-sdk';
239
+
240
+ export async function POST(request: Request) {
241
+ const { agentId, input } = await request.json();
242
+
243
+ const events = client.workers.execute(agentId, input, {
244
+ tools: {
245
+ search: async (args) => await search(args.query),
246
+ },
247
+ });
175
248
 
176
- // Usage
177
- const analysis = await executeWorker(client, agentId, { TOPIC: 'AI' });
249
+ return new Response(toSSEStream(events), {
250
+ headers: { 'Content-Type': 'text/event-stream' },
251
+ });
252
+ }
178
253
  ```
179
254
 
180
- ## Client Tool Continuation
255
+ ### Client Tool Continuation
181
256
 
182
257
  When workers have tools without handlers, execution pauses:
183
258
 
184
259
  ```typescript
185
260
  for await (const event of client.workers.execute(agentId, input)) {
186
261
  if (event.type === 'client-tool-request') {
187
- // Execute tools client-side
188
262
  const results = await executeClientTools(event.toolCalls);
189
263
 
190
- // Continue execution
191
264
  for await (const ev of client.workers.continue(agentId, event.executionId, results)) {
192
265
  // Handle remaining events
193
266
  }
@@ -210,84 +283,87 @@ The `client-tool-request` event includes:
210
283
  }
211
284
  ```
212
285
 
213
- ## Streaming to HTTP Response
286
+ ### Stream Events
214
287
 
215
- Convert worker events to an SSE stream:
216
-
217
- ```typescript
218
- import { toSSEStream } from '@octavus/server-sdk';
288
+ Workers emit standard stream events plus worker-specific events.
219
289
 
220
- export async function POST(request: Request) {
221
- const { agentId, input } = await request.json();
290
+ #### Worker Events
222
291
 
223
- const events = client.workers.execute(agentId, input, {
224
- tools: {
225
- search: async (args) => await search(args.query),
226
- },
227
- });
292
+ ```typescript
293
+ // Worker started
294
+ {
295
+ type: 'worker-start',
296
+ workerId: string, // Unique ID (also used as session ID for debug)
297
+ workerSlug: string, // The worker's slug
298
+ description?: string, // Display description for UI
299
+ }
228
300
 
229
- return new Response(toSSEStream(events), {
230
- headers: { 'Content-Type': 'text/event-stream' },
231
- });
301
+ // Worker completed
302
+ {
303
+ type: 'worker-result',
304
+ workerId: string,
305
+ output?: unknown, // The worker's output value
306
+ error?: string, // Error message if worker failed
232
307
  }
233
308
  ```
234
309
 
235
- ## Cancellation
310
+ #### Common Events
236
311
 
237
- Use an abort signal to cancel execution:
312
+ | Event | Description |
313
+ | ----------------------- | --------------------------- |
314
+ | `start` | Execution started |
315
+ | `finish` | Execution completed |
316
+ | `text-start` | Text generation started |
317
+ | `text-delta` | Text chunk received |
318
+ | `text-end` | Text generation ended |
319
+ | `block-start` | Step started |
320
+ | `block-end` | Step completed |
321
+ | `tool-input-available` | Tool arguments ready |
322
+ | `tool-output-available` | Tool result ready |
323
+ | `client-tool-request` | Client tools need execution |
324
+ | `error` | Error occurred |
238
325
 
239
- ```typescript
240
- const controller = new AbortController();
326
+ ## Full Examples
241
327
 
242
- // Cancel after 30 seconds
243
- setTimeout(() => controller.abort(), 30000);
328
+ ### generate()
329
+
330
+ ```typescript
331
+ import { OctavusClient, WorkerError } from '@octavus/server-sdk';
244
332
 
245
- const events = client.workers.execute(agentId, input, {
246
- signal: controller.signal,
333
+ const client = new OctavusClient({
334
+ baseUrl: 'https://octavus.ai',
335
+ apiKey: process.env.OCTAVUS_API_KEY!,
247
336
  });
248
337
 
249
338
  try {
250
- for await (const event of events) {
251
- // Process events
252
- }
253
- } catch (error) {
254
- if (error.name === 'AbortError') {
255
- console.log('Worker cancelled');
256
- }
257
- }
258
- ```
259
-
260
- ## Error Handling
261
-
262
- Errors can occur at different levels:
263
-
264
- ```typescript
265
- for await (const event of client.workers.execute(agentId, input)) {
266
- // Stream-level error event
267
- if (event.type === 'error') {
268
- console.error(`Error: ${event.message}`);
269
- console.error(`Type: ${event.errorType}`);
270
- console.error(`Retryable: ${event.retryable}`);
271
- }
339
+ const { output, sessionId } = await client.workers.generate(
340
+ 'research-assistant-id',
341
+ {
342
+ TOPIC: 'AI safety best practices',
343
+ DEPTH: 'detailed',
344
+ },
345
+ {
346
+ tools: {
347
+ 'web-search': async ({ query }) => await performWebSearch(query),
348
+ },
349
+ signal: AbortSignal.timeout(120_000),
350
+ },
351
+ );
272
352
 
273
- // Worker-level error in result
274
- if (event.type === 'worker-result' && event.error) {
275
- console.error(`Worker failed: ${event.error}`);
353
+ console.log('Result:', output);
354
+ } catch (error) {
355
+ if (error instanceof WorkerError) {
356
+ console.error('Failed:', error.message);
357
+ if (error.sessionId) {
358
+ console.error(`Debug: ${client.baseUrl}/sessions/${error.sessionId}`);
359
+ }
276
360
  }
277
361
  }
278
362
  ```
279
363
 
280
- Error types include:
281
-
282
- | Type | Description |
283
- | ------------------ | --------------------- |
284
- | `validation_error` | Invalid input |
285
- | `not_found_error` | Worker not found |
286
- | `provider_error` | LLM provider error |
287
- | `tool_error` | Tool execution failed |
288
- | `execution_error` | Worker step failed |
364
+ ### execute()
289
365
 
290
- ## Full Example
366
+ For full control over streaming events and progress tracking:
291
367
 
292
368
  ```typescript
293
369
  import { OctavusClient, type StreamEvent } from '@octavus/server-sdk';
@@ -348,7 +424,6 @@ async function runResearchWorker(topic: string) {
348
424
  return output;
349
425
  }
350
426
 
351
- // Run the worker
352
427
  const result = await runResearchWorker('AI safety best practices');
353
428
  console.log('Result:', result);
354
429
  ```
@@ -105,12 +105,16 @@ Each agent is a folder with:
105
105
  my-agent/
106
106
  ├── protocol.yaml # Main logic (required)
107
107
  ├── settings.json # Agent metadata (required)
108
- └── prompts/ # Prompt templates
108
+ └── prompts/ # Prompt templates (supports subdirectories)
109
109
  ├── system.md
110
110
  ├── user-message.md
111
- └── escalation-summary.md
111
+ └── shared/
112
+ ├── company-info.md
113
+ └── formatting-rules.md
112
114
  ```
113
115
 
116
+ Prompts can be organized in subdirectories. In the protocol, reference nested prompts by their path relative to `prompts/` (without `.md`): `shared/company-info`.
117
+
114
118
  ### settings.json
115
119
 
116
120
  ```json
@@ -133,7 +137,7 @@ my-agent/
133
137
 
134
138
  - **Slugs**: `lowercase-with-dashes`
135
139
  - **Variables**: `UPPERCASE_SNAKE_CASE`
136
- - **Prompts**: `lowercase-with-dashes.md`
140
+ - **Prompts**: `lowercase-with-dashes.md` (paths use `/` for subdirectories)
137
141
  - **Tools**: `lowercase-with-dashes`
138
142
  - **Triggers**: `lowercase-with-dashes`
139
143
 
@@ -153,7 +157,25 @@ Help users with their {{PRODUCT_NAME}} questions.
153
157
  {{SUPPORT_POLICIES}}
154
158
  ```
155
159
 
156
- Variables are replaced with their values at runtime. If a variable is not provided, it's replaced with an empty string.
160
+ Variables are replaced with their values at runtime. If a variable is not provided, the placeholder is kept as-is.
161
+
162
+ ## Prompt Interpolation
163
+
164
+ Include other prompts inside a prompt with `{{@path.md}}`:
165
+
166
+ ```markdown
167
+ <!-- prompts/system.md -->
168
+
169
+ You are a customer support agent.
170
+
171
+ {{@shared/company-info.md}}
172
+
173
+ {{@shared/formatting-rules.md}}
174
+
175
+ Help users with their questions.
176
+ ```
177
+
178
+ The 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.
157
179
 
158
180
  ## Next Steps
159
181
 
@@ -61,24 +61,48 @@ skills:
61
61
 
62
62
  ## Enabling Skills
63
63
 
64
- After defining skills in the `skills:` section, specify which skills are available for the chat thread in `agent.skills`:
64
+ After defining skills in the `skills:` section, specify which skills are available. Skills work in both interactive agents and workers.
65
+
66
+ ### Interactive Agents
67
+
68
+ Reference skills in `agent.skills`:
65
69
 
66
70
  ```yaml
67
- # All skills available to this agent (defined once at protocol level)
68
71
  skills:
69
72
  qr-code:
70
73
  display: description
71
74
  description: Generating QR codes
72
75
 
73
- # Skills available for this chat thread
74
76
  agent:
75
77
  model: anthropic/claude-sonnet-4-5
76
78
  system: system
77
79
  tools: [get-user-account]
78
- skills: [qr-code] # Skills available for this thread
80
+ skills: [qr-code]
79
81
  agentic: true
80
82
  ```
81
83
 
84
+ ### Workers and Named Threads
85
+
86
+ Reference skills per-thread in `start-thread.skills`:
87
+
88
+ ```yaml
89
+ skills:
90
+ qr-code:
91
+ display: description
92
+ description: Generating QR codes
93
+
94
+ steps:
95
+ Start thread:
96
+ block: start-thread
97
+ thread: worker
98
+ model: anthropic/claude-sonnet-4-5
99
+ system: system
100
+ skills: [qr-code]
101
+ maxSteps: 10
102
+ ```
103
+
104
+ This also works for named threads in interactive agents, allowing different threads to have different skills.
105
+
82
106
  ## Skill Tools
83
107
 
84
108
  When skills are enabled, the LLM has access to these tools:
@@ -290,23 +314,35 @@ agent:
290
314
 
291
315
  ## Sandbox Timeout
292
316
 
293
- The default sandbox timeout is 5 minutes. For long-running operations, you can configure a custom timeout using `sandboxTimeout` in the agent config:
317
+ The default sandbox timeout is 5 minutes. You can configure a custom timeout using `sandboxTimeout` in the agent config or on individual `start-thread` blocks:
294
318
 
295
319
  ```yaml
320
+ # Agent-level timeout (applies to main thread)
296
321
  agent:
297
322
  model: anthropic/claude-sonnet-4-5
298
323
  skills: [data-analysis]
299
324
  sandboxTimeout: 1800000 # 30 minutes (in milliseconds)
300
325
  ```
301
326
 
302
- `sandboxTimeout` Maximum: 1 hour (3,600,000 ms)
327
+ ```yaml
328
+ # Thread-level timeout (overrides agent-level for this thread)
329
+ steps:
330
+ Start thread:
331
+ block: start-thread
332
+ thread: analysis
333
+ model: anthropic/claude-sonnet-4-5
334
+ skills: [data-analysis]
335
+ sandboxTimeout: 3600000 # 1 hour
336
+ ```
337
+
338
+ Thread-level `sandboxTimeout` takes priority over agent-level. Maximum: 1 hour (3,600,000 ms).
303
339
 
304
340
  ## Security
305
341
 
306
342
  Skills run in isolated sandbox environments:
307
343
 
308
344
  - **No network access** (unless explicitly configured)
309
- - **No persistent storage** (sandbox destroyed after execution)
345
+ - **No persistent storage** (sandbox destroyed after each `next-message` execution)
310
346
  - **File output only** via `/output/` directory
311
347
  - **Time limits** enforced (5-minute default, configurable via `sandboxTimeout`)
312
348
 
@@ -148,6 +148,9 @@ Start summary thread:
148
148
  maxSteps: 1 # Tool call limit
149
149
  system: escalation-summary # System prompt
150
150
  input: [COMPANY_NAME] # Variables for prompt
151
+ skills: [qr-code] # Octavus skills for this thread
152
+ sandboxTimeout: 600000 # Skill sandbox timeout (default: 5 min, max: 1 hour)
153
+ imageModel: google/gemini-2.5-flash-image # Image generation model
151
154
  ```
152
155
 
153
156
  The `model` field can also reference a variable for dynamic model selection: