olbrain-python-sdk 0.3.2__tar.gz → 0.5.1__tar.gz

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 (38) hide show
  1. {olbrain_python_sdk-0.3.2/olbrain_python_sdk.egg-info → olbrain_python_sdk-0.5.1}/PKG-INFO +35 -29
  2. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/README.md +34 -28
  3. olbrain_python_sdk-0.5.1/olbrain/AGENTS.md +266 -0
  4. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain/__init__.py +12 -4
  5. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain/client.py +46 -205
  6. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain/exceptions.py +22 -0
  7. olbrain_python_sdk-0.5.1/olbrain/olbrain.py +242 -0
  8. olbrain_python_sdk-0.5.1/olbrain/research.py +200 -0
  9. olbrain_python_sdk-0.5.1/olbrain/streaming.py +95 -0
  10. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1/olbrain_python_sdk.egg-info}/PKG-INFO +35 -29
  11. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain_python_sdk.egg-info/SOURCES.txt +4 -0
  12. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/setup.py +1 -1
  13. olbrain_python_sdk-0.5.1/tests/conftest.py +7 -0
  14. olbrain_python_sdk-0.5.1/tests/test_client.py +107 -0
  15. olbrain_python_sdk-0.5.1/tests/test_olbrain.py +220 -0
  16. olbrain_python_sdk-0.3.2/olbrain/AGENTS.md +0 -167
  17. olbrain_python_sdk-0.3.2/olbrain/streaming.py +0 -143
  18. olbrain_python_sdk-0.3.2/tests/test_client.py +0 -131
  19. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/.gitignore +0 -0
  20. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/LICENSE +0 -0
  21. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/MANIFEST.in +0 -0
  22. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/examples/advanced_features.py +0 -0
  23. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/examples/basic_usage.py +0 -0
  24. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/examples/error_handling.py +0 -0
  25. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/examples/session_management.py +0 -0
  26. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/examples/streaming_responses.py +0 -0
  27. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain/session.py +0 -0
  28. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain/utils.py +0 -0
  29. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain_python_sdk.egg-info/dependency_links.txt +0 -0
  30. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain_python_sdk.egg-info/not-zip-safe +0 -0
  31. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain_python_sdk.egg-info/requires.txt +0 -0
  32. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/olbrain_python_sdk.egg-info/top_level.txt +0 -0
  33. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/pyproject.toml +0 -0
  34. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/requirements.txt +0 -0
  35. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/setup.cfg +0 -0
  36. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/tests/__init__.py +0 -0
  37. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/tests/test_exceptions.py +0 -0
  38. {olbrain_python_sdk-0.3.2 → olbrain_python_sdk-0.5.1}/tests/test_session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: olbrain-python-sdk
3
- Version: 0.3.2
3
+ Version: 0.5.1
4
4
  Summary: Official Python SDK for Olbrain AI agents
5
5
  Home-page: https://github.com/Olbrain/olbrain-sdk
6
6
  Author: Olbrain Team
@@ -86,7 +86,7 @@ pytest tests/
86
86
 
87
87
  Get your credentials from the Olbrain dashboard:
88
88
  - **Agent ID**: Your unique agent identifier
89
- - **API Key**: Starts with `sk_live_` (production) or `sk_` (test)
89
+ - **API Key**: an access key (`ak_…`) from Noesis → Settings → Organization → Access keys, or from the finish page Alchemist links to when it hands an agent over (that key works only for that agent)
90
90
 
91
91
  ### Basic Example
92
92
 
@@ -96,7 +96,7 @@ from olbrain import AgentClient
96
96
  # Initialize client
97
97
  client = AgentClient(
98
98
  agent_id="your-agent-id",
99
- api_key="sk_live_your_api_key"
99
+ api_key="ak_your_api_key"
100
100
  )
101
101
 
102
102
  # Create a session and send a message
@@ -115,7 +115,7 @@ client.close()
115
115
  from olbrain import AgentClient
116
116
 
117
117
  # Automatically closes client when done
118
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
118
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
119
119
  session_id = client.create_session(title="My Chat")
120
120
  response = client.send_and_wait(session_id, "What is machine learning?")
121
121
  print(response.text)
@@ -129,6 +129,21 @@ with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client
129
129
  - **Token Tracking** - Monitor usage and costs per request
130
130
  - **Model Override** - Switch models per-message
131
131
  - **Error Handling** - Comprehensive exception hierarchy
132
+ - **Workflow Agents** - Run workflow agents, read and control their runs, resolve approvals (`Olbrain`)
133
+ - **Research Agents** - Sessions, streamed turns, plans, reports and review (`Olbrain.research`, sign-ins only)
134
+
135
+ ### Workflow agents
136
+
137
+ ```python
138
+ import os
139
+ from olbrain import Olbrain
140
+
141
+ olbrain = Olbrain(api_key=os.environ["OLBRAIN_API_KEY"]) # keep the key on your server
142
+ run = olbrain.workflows.run("your-agent-id", payload={"url": "https://example.com"})
143
+ print(olbrain.runs.get("your-agent-id", run["run_id"]))
144
+ ```
145
+
146
+ See [`olbrain/AGENTS.md`](olbrain/AGENTS.md) for runs, approvals and research agents.
132
147
 
133
148
  ## Usage
134
149
 
@@ -139,7 +154,7 @@ Send a message and wait for the complete response:
139
154
  ```python
140
155
  from olbrain import AgentClient
141
156
 
142
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
157
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
143
158
  # Create a new chat session
144
159
  session_id = client.create_session(title="My Chat")
145
160
 
@@ -154,12 +169,12 @@ with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client
154
169
 
155
170
  ### 2. Real-Time Streaming (Callback Pattern)
156
171
 
157
- Receive messages in real-time as they arrive:
172
+ Receive new messages as they arrive (the client polls the session every 2 seconds):
158
173
 
159
174
  ```python
160
175
  from olbrain import AgentClient
161
176
 
162
- client = AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key")
177
+ client = AgentClient(agent_id="your-agent-id", api_key="ak_your_key")
163
178
 
164
179
  # Define message handler
165
180
  def on_message(msg):
@@ -191,7 +206,7 @@ Create, retrieve, and manage chat sessions:
191
206
  ```python
192
207
  from olbrain import AgentClient
193
208
 
194
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
209
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
195
210
  # Create session with metadata
196
211
  session = client.create_session(
197
212
  title="Support Chat",
@@ -210,15 +225,9 @@ with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client
210
225
  for msg in messages.get('messages', []):
211
226
  print(f"{msg['role']}: {msg['content']}")
212
227
 
213
- # Update session
214
- updated = client.update_session(session, title="Updated Chat Title")
215
-
216
228
  # Get session statistics
217
229
  stats = client.get_session_stats(session)
218
230
  print(f"Stats: {stats}")
219
-
220
- # Archive/delete session
221
- client.delete_session(session)
222
231
  ```
223
232
 
224
233
  ### 4. Model Override
@@ -228,7 +237,7 @@ Send messages using a specific model:
228
237
  ```python
229
238
  from olbrain import AgentClient
230
239
 
231
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
240
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
232
241
  session_id = client.create_session()
233
242
 
234
243
  # Use specific model for this request
@@ -248,7 +257,7 @@ Maintain conversation context:
248
257
  ```python
249
258
  from olbrain import AgentClient
250
259
 
251
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
260
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
252
261
  session_id = client.create_session(title="Q&A Session")
253
262
 
254
263
  # First message
@@ -279,7 +288,7 @@ from olbrain.exceptions import (
279
288
  )
280
289
 
281
290
  try:
282
- client = AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key")
291
+ client = AgentClient(agent_id="your-agent-id", api_key="ak_your_key")
283
292
  session_id = client.create_session()
284
293
  response = client.send_and_wait(session_id, "Hello")
285
294
 
@@ -307,24 +316,21 @@ finally:
307
316
 
308
317
  ### API Key
309
318
 
310
- The SDK validates API keys. Valid prefixes are:
311
- - `sk_live_` - Production keys
312
- - `org_live_` - Organization keys
313
- - `sk_` - Test keys
314
- - `org_` - Test organization keys
319
+ The SDK validates API keys. Access keys (`ak_`) are the only kind issued today; the older
320
+ `sk_live_`, `org_live_`, `sk_` and `org_` prefixes still pass validation.
315
321
 
316
322
  ```python
317
323
  # Valid API key
318
324
  client = AgentClient(
319
325
  agent_id="your-agent-id",
320
- api_key="sk_live_your_api_key_here"
326
+ api_key="ak_your_api_key_here"
321
327
  )
322
328
  ```
323
329
 
324
330
  ### Environment Variables
325
331
 
326
332
  ```bash
327
- export OLBRAIN_API_KEY="sk_live_your_api_key"
333
+ export OLBRAIN_API_KEY="ak_your_api_key"
328
334
  export OLBRAIN_AGENT_ID="your-agent-id"
329
335
  ```
330
336
 
@@ -350,10 +356,10 @@ client = AgentClient(agent_id="...", api_key="...")
350
356
  ### Common Issues
351
357
 
352
358
  **"Invalid API key" error**
353
- - Ensure your API key starts with `sk_live_`, `org_live_`, `sk_`, or `org_`
359
+ - Ensure your API key starts with `ak_` (or one of the older `sk_live_`, `org_live_`, `sk_`, `org_` prefixes)
354
360
  - Check that you copied the key correctly (no extra spaces)
355
361
 
356
- **"Stream connection failed" error**
362
+ **"Message poll failed" in the logs**
357
363
  - Check that your agent ID is correct
358
364
  - Verify you have a stable internet connection
359
365
  - Try restarting the client
@@ -399,8 +405,8 @@ The test will:
399
405
  |--------|-----------|---------|-------------|
400
406
  | `create_session()` | `title`, `user_id`, `metadata`, `on_message`, `mode`, `description` | `str` (session_id) | Create a new chat session |
401
407
  | `get_session(session_id)` | `session_id` | `SessionInfo` | Get session details |
402
- | `update_session(session_id, ...)` | `session_id`, `title`, `metadata`, `status` | `SessionInfo` | Update session properties |
403
- | `delete_session(session_id)` | `session_id` | `Dict` | Archive/delete a session |
408
+ | `update_session(session_id, ...)` | `session_id`, `title`, `metadata`, `status` | - | Not supported: raises `OlbrainError` (the Olbrain API has no endpoint for it) |
409
+ | `delete_session(session_id)` | `session_id` | - | Not supported: raises `OlbrainError` (the Olbrain API has no endpoint for it) |
404
410
  | `get_messages(session_id, ...)` | `session_id`, `limit`, `offset` | `Dict` | Get message history |
405
411
  | `get_session_stats(session_id)` | `session_id` | `Dict` | Get session statistics |
406
412
 
@@ -410,7 +416,7 @@ The test will:
410
416
  |--------|-----------|---------|-------------|
411
417
  | `send(session_id, message, ...)` | `session_id`, `message`, `user_id`, `metadata`, `model` | `Dict` | Send message (async with callback) |
412
418
  | `send_and_wait(session_id, message, ...)` | `session_id`, `message`, `user_id`, `metadata`, `model`, `timeout` | `ChatResponse` | Send and wait for response |
413
- | `listen(session_id, on_message)` | `session_id`, `on_message` | - | Start listening to session |
419
+ | `listen(session_id, on_message)` | `session_id`, `on_message` | - | Poll the session every 2 s and call `on_message` for each new message |
414
420
  | `run()` | - | - | Block and process streaming messages |
415
421
  | `close()` | - | - | Close client and cleanup resources |
416
422
 
@@ -38,7 +38,7 @@ pytest tests/
38
38
 
39
39
  Get your credentials from the Olbrain dashboard:
40
40
  - **Agent ID**: Your unique agent identifier
41
- - **API Key**: Starts with `sk_live_` (production) or `sk_` (test)
41
+ - **API Key**: an access key (`ak_…`) from Noesis → Settings → Organization → Access keys, or from the finish page Alchemist links to when it hands an agent over (that key works only for that agent)
42
42
 
43
43
  ### Basic Example
44
44
 
@@ -48,7 +48,7 @@ from olbrain import AgentClient
48
48
  # Initialize client
49
49
  client = AgentClient(
50
50
  agent_id="your-agent-id",
51
- api_key="sk_live_your_api_key"
51
+ api_key="ak_your_api_key"
52
52
  )
53
53
 
54
54
  # Create a session and send a message
@@ -67,7 +67,7 @@ client.close()
67
67
  from olbrain import AgentClient
68
68
 
69
69
  # Automatically closes client when done
70
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
70
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
71
71
  session_id = client.create_session(title="My Chat")
72
72
  response = client.send_and_wait(session_id, "What is machine learning?")
73
73
  print(response.text)
@@ -81,6 +81,21 @@ with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client
81
81
  - **Token Tracking** - Monitor usage and costs per request
82
82
  - **Model Override** - Switch models per-message
83
83
  - **Error Handling** - Comprehensive exception hierarchy
84
+ - **Workflow Agents** - Run workflow agents, read and control their runs, resolve approvals (`Olbrain`)
85
+ - **Research Agents** - Sessions, streamed turns, plans, reports and review (`Olbrain.research`, sign-ins only)
86
+
87
+ ### Workflow agents
88
+
89
+ ```python
90
+ import os
91
+ from olbrain import Olbrain
92
+
93
+ olbrain = Olbrain(api_key=os.environ["OLBRAIN_API_KEY"]) # keep the key on your server
94
+ run = olbrain.workflows.run("your-agent-id", payload={"url": "https://example.com"})
95
+ print(olbrain.runs.get("your-agent-id", run["run_id"]))
96
+ ```
97
+
98
+ See [`olbrain/AGENTS.md`](olbrain/AGENTS.md) for runs, approvals and research agents.
84
99
 
85
100
  ## Usage
86
101
 
@@ -91,7 +106,7 @@ Send a message and wait for the complete response:
91
106
  ```python
92
107
  from olbrain import AgentClient
93
108
 
94
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
109
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
95
110
  # Create a new chat session
96
111
  session_id = client.create_session(title="My Chat")
97
112
 
@@ -106,12 +121,12 @@ with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client
106
121
 
107
122
  ### 2. Real-Time Streaming (Callback Pattern)
108
123
 
109
- Receive messages in real-time as they arrive:
124
+ Receive new messages as they arrive (the client polls the session every 2 seconds):
110
125
 
111
126
  ```python
112
127
  from olbrain import AgentClient
113
128
 
114
- client = AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key")
129
+ client = AgentClient(agent_id="your-agent-id", api_key="ak_your_key")
115
130
 
116
131
  # Define message handler
117
132
  def on_message(msg):
@@ -143,7 +158,7 @@ Create, retrieve, and manage chat sessions:
143
158
  ```python
144
159
  from olbrain import AgentClient
145
160
 
146
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
161
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
147
162
  # Create session with metadata
148
163
  session = client.create_session(
149
164
  title="Support Chat",
@@ -162,15 +177,9 @@ with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client
162
177
  for msg in messages.get('messages', []):
163
178
  print(f"{msg['role']}: {msg['content']}")
164
179
 
165
- # Update session
166
- updated = client.update_session(session, title="Updated Chat Title")
167
-
168
180
  # Get session statistics
169
181
  stats = client.get_session_stats(session)
170
182
  print(f"Stats: {stats}")
171
-
172
- # Archive/delete session
173
- client.delete_session(session)
174
183
  ```
175
184
 
176
185
  ### 4. Model Override
@@ -180,7 +189,7 @@ Send messages using a specific model:
180
189
  ```python
181
190
  from olbrain import AgentClient
182
191
 
183
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
192
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
184
193
  session_id = client.create_session()
185
194
 
186
195
  # Use specific model for this request
@@ -200,7 +209,7 @@ Maintain conversation context:
200
209
  ```python
201
210
  from olbrain import AgentClient
202
211
 
203
- with AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key") as client:
212
+ with AgentClient(agent_id="your-agent-id", api_key="ak_your_key") as client:
204
213
  session_id = client.create_session(title="Q&A Session")
205
214
 
206
215
  # First message
@@ -231,7 +240,7 @@ from olbrain.exceptions import (
231
240
  )
232
241
 
233
242
  try:
234
- client = AgentClient(agent_id="your-agent-id", api_key="sk_live_your_key")
243
+ client = AgentClient(agent_id="your-agent-id", api_key="ak_your_key")
235
244
  session_id = client.create_session()
236
245
  response = client.send_and_wait(session_id, "Hello")
237
246
 
@@ -259,24 +268,21 @@ finally:
259
268
 
260
269
  ### API Key
261
270
 
262
- The SDK validates API keys. Valid prefixes are:
263
- - `sk_live_` - Production keys
264
- - `org_live_` - Organization keys
265
- - `sk_` - Test keys
266
- - `org_` - Test organization keys
271
+ The SDK validates API keys. Access keys (`ak_`) are the only kind issued today; the older
272
+ `sk_live_`, `org_live_`, `sk_` and `org_` prefixes still pass validation.
267
273
 
268
274
  ```python
269
275
  # Valid API key
270
276
  client = AgentClient(
271
277
  agent_id="your-agent-id",
272
- api_key="sk_live_your_api_key_here"
278
+ api_key="ak_your_api_key_here"
273
279
  )
274
280
  ```
275
281
 
276
282
  ### Environment Variables
277
283
 
278
284
  ```bash
279
- export OLBRAIN_API_KEY="sk_live_your_api_key"
285
+ export OLBRAIN_API_KEY="ak_your_api_key"
280
286
  export OLBRAIN_AGENT_ID="your-agent-id"
281
287
  ```
282
288
 
@@ -302,10 +308,10 @@ client = AgentClient(agent_id="...", api_key="...")
302
308
  ### Common Issues
303
309
 
304
310
  **"Invalid API key" error**
305
- - Ensure your API key starts with `sk_live_`, `org_live_`, `sk_`, or `org_`
311
+ - Ensure your API key starts with `ak_` (or one of the older `sk_live_`, `org_live_`, `sk_`, `org_` prefixes)
306
312
  - Check that you copied the key correctly (no extra spaces)
307
313
 
308
- **"Stream connection failed" error**
314
+ **"Message poll failed" in the logs**
309
315
  - Check that your agent ID is correct
310
316
  - Verify you have a stable internet connection
311
317
  - Try restarting the client
@@ -351,8 +357,8 @@ The test will:
351
357
  |--------|-----------|---------|-------------|
352
358
  | `create_session()` | `title`, `user_id`, `metadata`, `on_message`, `mode`, `description` | `str` (session_id) | Create a new chat session |
353
359
  | `get_session(session_id)` | `session_id` | `SessionInfo` | Get session details |
354
- | `update_session(session_id, ...)` | `session_id`, `title`, `metadata`, `status` | `SessionInfo` | Update session properties |
355
- | `delete_session(session_id)` | `session_id` | `Dict` | Archive/delete a session |
360
+ | `update_session(session_id, ...)` | `session_id`, `title`, `metadata`, `status` | - | Not supported: raises `OlbrainError` (the Olbrain API has no endpoint for it) |
361
+ | `delete_session(session_id)` | `session_id` | - | Not supported: raises `OlbrainError` (the Olbrain API has no endpoint for it) |
356
362
  | `get_messages(session_id, ...)` | `session_id`, `limit`, `offset` | `Dict` | Get message history |
357
363
  | `get_session_stats(session_id)` | `session_id` | `Dict` | Get session statistics |
358
364
 
@@ -362,7 +368,7 @@ The test will:
362
368
  |--------|-----------|---------|-------------|
363
369
  | `send(session_id, message, ...)` | `session_id`, `message`, `user_id`, `metadata`, `model` | `Dict` | Send message (async with callback) |
364
370
  | `send_and_wait(session_id, message, ...)` | `session_id`, `message`, `user_id`, `metadata`, `model`, `timeout` | `ChatResponse` | Send and wait for response |
365
- | `listen(session_id, on_message)` | `session_id`, `on_message` | - | Start listening to session |
371
+ | `listen(session_id, on_message)` | `session_id`, `on_message` | - | Poll the session every 2 s and call `on_message` for each new message |
366
372
  | `run()` | - | - | Block and process streaming messages |
367
373
  | `close()` | - | - | Close client and cleanup resources |
368
374
 
@@ -0,0 +1,266 @@
1
+ # olbrain-python-sdk — reference for coding assistants
2
+
3
+ This file ships inside the installed package (`site-packages/olbrain/AGENTS.md`).
4
+ It is the complete, offline reference for the public API. Everything here is
5
+ taken from the source. If it disagrees with the web docs, this file is right.
6
+
7
+ Find it from Python with:
8
+ `python -c "import olbrain, os; print(os.path.join(os.path.dirname(olbrain.__file__), 'AGENTS.md'))"`
9
+
10
+ ## Pick the right client
11
+
12
+ | You want to… | Use | Auth |
13
+ |---|---|---|
14
+ | Chat with a conversational agent (send a message, get a reply) | `AgentClient` | `api_key="ak_…"` (the only kind issued today; `sk_live_`, `org_live_`, `sk_` and `org_` still pass validation) |
15
+ | Trigger workflow agents, read/control runs, resolve approvals | `Olbrain` | `api_key="ak_…"` **or** `get_id_token` (Olbrain sign-in) |
16
+ | Drive a research agent (sessions, streamed turns, plans, reports) | `Olbrain.research` | `get_id_token` only. An `ak_` key gets `NotFoundError` (`agent_not_found`) |
17
+
18
+ These are two separate HTTP clients with different error rules. Both default
19
+ to `https://webhook.olbrain.com`. The JavaScript SDK (`@olbrain/js-sdk`) has
20
+ the same two clients plus a browser `ChatWidget`.
21
+
22
+ Keep the `ak_` key on your server (an environment variable such as
23
+ `OLBRAIN_API_KEY`). Never send it to a browser.
24
+
25
+ ## Install and import
26
+
27
+ ```bash
28
+ pip install olbrain-python-sdk # import name: olbrain. Requires Python 3.7+ and requests.
29
+ ```
30
+
31
+ ```python
32
+ from olbrain import AgentClient, Olbrain, OlbrainError
33
+ ```
34
+
35
+ Both clients are synchronous and use `requests`.
36
+
37
+ ---
38
+
39
+ ## 1. `AgentClient` — conversational agents
40
+
41
+ ```python
42
+ import os
43
+ from olbrain import AgentClient
44
+
45
+ with AgentClient(agent_id="your-agent-id", api_key=os.environ["OLBRAIN_API_KEY"]) as client:
46
+ session_id = client.create_session(title="Support chat", user_id="u_42")
47
+ reply = client.send_and_wait(session_id, "Hello!")
48
+ if reply.success:
49
+ print(reply.text)
50
+ else:
51
+ print("agent error:", reply.error)
52
+ ```
53
+
54
+ ### `AgentClient(agent_id, api_key, agent_url=None)`
55
+
56
+ - `agent_id`: required. An empty value raises `ValueError`.
57
+ - `api_key`: must start with `ak_`, `sk_live_`, `org_live_`, `sk_` or `org_`,
58
+ or the constructor raises `ValueError`.
59
+ - `agent_url`: optional. Defaults to the `OLBRAIN_AGENT_URL` environment
60
+ variable if set, otherwise `https://webhook.olbrain.com`.
61
+
62
+ Chat calls are `POST {agent_url}/api/agents/webhook/{agent_id}` with
63
+ `Authorization: Bearer <api_key>`. Session reads are
64
+ `GET {agent_url}/api/agents/{agent_id}/sessions/{session_id}[/messages]`. The
65
+ client is a context manager, and `__exit__` calls `close()`.
66
+
67
+ ### Methods
68
+
69
+ | Method | Returns | Notes |
70
+ |---|---|---|
71
+ | `create_session(on_message=None, title=None, user_id=None, metadata=None, mode="production", description=None)` | `str` session id | Sends `title` (default `"New Session"`) to the agent **as the first message**. `mode` must be `"development"`, `"testing"` or `"production"`, otherwise `ValueError`, but it is not sent: the API always serves the published agent. `description` is stored as `metadata["session_description"]`. If you pass `on_message`, it also calls `listen()`. Timeout 30 s. |
72
+ | `send_and_wait(session_id, message, user_id=None, metadata=None, model=None, timeout=120)` | `ChatResponse` | Waits for the reply. `timeout` is in **seconds**. `model` overrides the agent's model. Empty or whitespace-only messages raise `ValueError`. |
73
+ | `send(session_id, message, user_id=None, metadata=None, model=None, mode="production")` | `dict` (raw JSON) | Same request, but returns the raw response dict (`response`, `token_usage`, `success`, …). It still blocks, with a 120 s timeout. |
74
+ | `listen(session_id, on_message)` | `None` | Starts a background daemon thread that polls the session's messages every 2 s and calls `on_message` once per **new** message (messages already there are skipped). Calling it again for the same session does nothing. |
75
+ | `run()` | `None` | Blocks until Ctrl+C, then calls `close()`. Use it to keep a script alive for `listen` callbacks. |
76
+ | `close()` | `None` | Stops all listeners. |
77
+ | `get_session(session_id)` | `SessionInfo` | `SessionNotFoundError` if it doesn't exist (or belongs to another agent). |
78
+ | `get_session_stats(session_id)` | `dict` | `message_count`, `token_usage`, `cost`, `created_at`, `last_message_at`. |
79
+ | `get_messages(session_id, limit=20, offset=0)` | `dict` | `{"messages": [...], "limit", "offset"}`, oldest first; `limit` at most 200. |
80
+ | `update_session(...)`, `delete_session(...)` | — | Always raise `OlbrainError`: the API has no endpoint for them. |
81
+
82
+ `on_message` gets each message as a raw dict (`role`, `content`, …). It runs
83
+ on the listener thread, so keep it fast and don't raise from it.
84
+
85
+ ### Data classes (`olbrain.session`, re-exported from `olbrain`)
86
+
87
+ ```python
88
+ @dataclass
89
+ class ChatResponse:
90
+ text: str
91
+ session_id: str
92
+ success: bool = True
93
+ token_usage: Optional[TokenUsage] = None # None if the server sent none
94
+ model_used: Optional[str] = None
95
+ response_time_ms: Optional[int] = None
96
+ metadata: Optional[dict] = None
97
+ error: Optional[str] = None
98
+ # ChatResponse.from_dict(data, session_id=""), .to_dict()
99
+
100
+ @dataclass
101
+ class TokenUsage:
102
+ prompt_tokens: int = 0
103
+ completion_tokens: int = 0
104
+ total_tokens: int = 0
105
+ cost: float = 0.0
106
+
107
+ @dataclass
108
+ class SessionInfo:
109
+ session_id: str
110
+ title: str = ""
111
+ status: str = "active" # "active" | "archived"
112
+ created_at: str = ""
113
+ updated_at: str = ""
114
+ message_count: int = 0
115
+ user_id: Optional[str] = None
116
+ channel: str = "api"
117
+ metadata: dict = {}
118
+ ```
119
+
120
+ `token_usage` can be `None`, so guard before reading it:
121
+ `reply.token_usage.total_tokens if reply.token_usage else 0`. Always check
122
+ `reply.success` too. A 200 response can carry `success=False` and an `error`.
123
+
124
+ Avoid the exported `Message` and `Session` classes. Nothing in the client
125
+ returns them.
126
+
127
+ ### Errors (`AgentClient`)
128
+
129
+ | Condition | Raised |
130
+ |---|---|
131
+ | Bad constructor args, empty message, bad `mode`/`status` | `ValueError` (**not** `ValidationError`) |
132
+ | HTTP 401 | `AuthenticationError` |
133
+ | HTTP 404 whose detail mentions "session" | `SessionNotFoundError` (a subclass of `SessionError`) |
134
+ | Other HTTP 404 | `OlbrainError` ("Endpoint not found (404) …") |
135
+ | HTTP 429 | `RateLimitError` (`.retry_after` from `detail.retry_after`, may be `None`) |
136
+ | Any other non-200 (403, 5xx…) | `OlbrainError("Failed to <operation>: <status> - <detail>")` |
137
+ | Connection error or timeout | `NetworkError` |
138
+
139
+ ---
140
+
141
+ ## 2. `Olbrain` — workflow agents, runs, approvals
142
+
143
+ ```python
144
+ olbrain = Olbrain(api_key=os.environ["OLBRAIN_API_KEY"]) # server / script
145
+ # olbrain = Olbrain(get_id_token=lambda: current_user_id_token()) # signed-in Olbrain user (Firebase ID token)
146
+ ```
147
+
148
+ `Olbrain(api_key=None, get_id_token=None, base_url=None, timeout=30)`:
149
+ `api_key` must start `ak_`; `get_id_token` is a callable returning the
150
+ token and is called before **every** request; `base_url` defaults to the
151
+ `OLBRAIN_AGENT_URL` environment variable if set, otherwise
152
+ `https://webhook.olbrain.com`; `timeout` is in seconds. Give **exactly one** of
153
+ `api_key` or `get_id_token`, or the constructor raises `ValidationError`.
154
+
155
+ Every method takes the agent id first and returns the API's JSON (snake_case
156
+ dicts and lists).
157
+
158
+ | Method | HTTP | Returns |
159
+ |---|---|---|
160
+ | `workflows.run(agent_id, payload=None, variables=None)` | `POST /api/workflows/{agent_id}/trigger` | `{"run_id", "status"}` |
161
+ | `runs.list(agent_id, status=None, limit=None)` | `GET /api/agents/{agent_id}/runs` | list of runs |
162
+ | `runs.get(agent_id, run_id)` | `GET …/runs/{run_id}` | run |
163
+ | `runs.cancel / pause / retry / resume(agent_id, run_id)` | `POST …/runs/{run_id}/{verb}` | dict |
164
+ | `approvals.list(agent_id, run_id)` | `GET …/runs/{run_id}/approvals` | list of approvals |
165
+ | `approvals.resolve(agent_id, run_id, exception_id, resolution, modified_data=None)` | `POST …/approvals/resolve` | `{"exception_id", "resolution", "all_resolved", "resumed"}` |
166
+
167
+ A run has `run_id`, `status`, and optionally `progress_percent`, `started_at`,
168
+ `completed_at`, `duration_seconds`, `error` and `summary`
169
+ (`total_processed`, `successful`, `failed`, `exceptions`, `skipped`). An
170
+ approval has `exception_id`, `step_id`, `item_id`, `data`, `error`,
171
+ `requires_human_review` and `blocking`. `resolution` is `"approved"`,
172
+ `"rejected"` or `"modified"`; send `modified_data` only with `"modified"`.
173
+
174
+ There is no SDK helper for waiting on a run, so poll `runs.get` yourself:
175
+
176
+ ```python
177
+ import os, time
178
+ from olbrain import Olbrain
179
+
180
+ olbrain = Olbrain(api_key=os.environ["OLBRAIN_API_KEY"])
181
+ run_id = olbrain.workflows.run(agent_id, payload={"url": url})["run_id"]
182
+ DONE = {"completed", "failed", "canceled", "cancelled"}
183
+ run = olbrain.runs.get(agent_id, run_id)
184
+ while run["status"] not in DONE:
185
+ for a in olbrain.approvals.list(agent_id, run_id):
186
+ olbrain.approvals.resolve(agent_id, run_id, a["exception_id"], "approved")
187
+ time.sleep(3)
188
+ run = olbrain.runs.get(agent_id, run_id)
189
+ ```
190
+
191
+ The terminal status strings in that loop are an assumption. Check the values
192
+ your agent actually returns.
193
+
194
+ ### Errors (`Olbrain`, including `research`)
195
+
196
+ The error code and message come from the response body: `{error:{code,message}}`
197
+ or FastAPI's `{detail}`.
198
+
199
+ | Condition | Raised |
200
+ |---|---|
201
+ | code is `insufficient_funds`, `credit_limit_exceeded`, `subscription_suspended`, `subscription_inactive` or `billing_unavailable` (any status) | `BillingError` (`.status`, `.code`) |
202
+ | 401 | `AuthenticationError` |
203
+ | 404 | `NotFoundError` (`.status = 404`, `.code`, e.g. `agent_not_found`) |
204
+ | 429 | `RateLimitError` (`.retry_after` seconds from the `Retry-After` header, or `None`) |
205
+ | any other non-2xx (403, 409, 422, 5xx…) | `ApiError` (`.status`, `.code`, `.message`, `.detail` = raw `detail`) |
206
+ | connection failure, timeout, or a dropped stream | `NetworkError` |
207
+
208
+ `BillingError` and `NotFoundError` subclass `ApiError`, and every error class
209
+ subclasses `OlbrainError`, so put the narrower `except` clauses first.
210
+
211
+ ---
212
+
213
+ ## 3. `Olbrain.research` — research agents (sign-ins only)
214
+
215
+ ```python
216
+ olbrain = Olbrain(get_id_token=lambda: user_id_token)
217
+ r = olbrain.research
218
+ ```
219
+
220
+ All paths sit under `/api/agents/{agent_id}/research`. The agent must be a
221
+ research agent that is bound to a screen in the user's org. Payloads are
222
+ returned untyped (dicts and lists) because their shape belongs to the
223
+ research-runtime service.
224
+
225
+ | Method | |
226
+ |---|---|
227
+ | `sessions.create(agent_id)` | session (has `session_id`) |
228
+ | `sessions.list(agent_id, include_archived=False)`, `sessions.get / delete(agent_id, session_id)` | |
229
+ | `sessions.patch(agent_id, session_id, patch)` | partial session fields, passed through |
230
+ | `sessions.models(agent_id, session_id)` | available models |
231
+ | `sessions.set_model / set_review_model(agent_id, session_id, model)` | `None` resets to the default |
232
+ | `sessions.stream_message(agent_id, session_id, content, attachments=None, effort=None, escalates_run_id=None, command=None, target_run_id=None)` | generator of frames |
233
+ | `plans.approve(agent_id, session_id, message_id, body=None)`, `plans.approve_by_id(agent_id, session_id, plan_id, body=None)` | |
234
+ | `attachments.add(agent_id, session_id, files)` | multipart upload of `(filename, file or bytes[, content_type])` tuples; returns refs to pass as `attachments` |
235
+ | `attachments.upload_url(agent_id, session_id, storage_path)` | `{"url"}` |
236
+ | `runs.cancel / retry(agent_id, run_id)`, `runs.active_job(agent_id, run_id)` (or `None`), `runs.resolve_claims(agent_id, run_id, resolutions)` | |
237
+ | `reports.get(agent_id, run_id, version=None)`, `reports.artifact_url(agent_id, run_id, profile_id, fmt)`, `reports.evidence(agent_id, run_id)` | |
238
+ | `reports.share.get / revoke(agent_id, run_id)`, `reports.share.set(agent_id, run_id, visibility)` | |
239
+ | `review.get(agent_id, run_id)`; `review.run(agent_id, run_id)`, `review.apply(agent_id, run_id, finding_ids)` | the last two block, up to 600 s |
240
+ | `review.stream(agent_id, run_id)`, `review.stream_apply(agent_id, run_id, finding_ids)` | generators of frames |
241
+ | `templates.versions(agent_id)` | |
242
+ | `messages.older(agent_id, session_id, before_ts)` | one page of older messages, oldest first |
243
+
244
+ Stream frames come straight from research-runtime, unchanged: `{"type": "text_delta", "text"}`,
245
+ `{"type": "tool_use_start", …}`, `{"type": "complete", …}` and `{"type": "error", …}`.
246
+ The request is sent when you start iterating, and it can wait up to 600 s for
247
+ its first frame while the agent searches. To cancel, stop iterating (`break`,
248
+ or `.close()` the generator); the connection is closed.
249
+
250
+ ```python
251
+ for ev in r.sessions.stream_message(agent_id, sid, "Size the Indian EV charger market"):
252
+ if ev.get("type") == "text_delta":
253
+ print(ev["text"], end="", flush=True)
254
+ elif ev.get("type") == "error":
255
+ raise RuntimeError(ev.get("message", "research error"))
256
+ ```
257
+
258
+ The JavaScript SDK's `live.*` polling subscriptions are not in Python. Poll
259
+ `sessions.get` / `reports.get` yourself.
260
+
261
+ ---
262
+
263
+ ## Logging
264
+
265
+ `AgentClient` logs through `logging.getLogger("olbrain")`. Turn on
266
+ `logging.basicConfig(level=logging.DEBUG)` to see requests and listener events.