2026-04-19 09:42:17 +00:00
# Python SDK
2026-04-19 09:51:54 +00:00
Use nanobot as a library — no CLI, no gateway, just Python.
2026-06-10 00:36:22 +08:00
Before debugging SDK code, prove the same config works from the CLI:
```bash
nanobot agent -m "Hello!"
```
`Nanobot.from_config()` reuses your normal `~/.nanobot/config.json` , so provider, model, tools, and workspace behavior match the CLI unless you override them.
2026-04-19 09:51:54 +00:00
## Quick Start
```python
import asyncio
from nanobot import Nanobot
async def main () -> None :
2026-06-06 15:29:13 +08:00
async with Nanobot . from_config () as bot :
result = await bot . run ( "What time is it in Tokyo?" )
2026-04-19 09:51:54 +00:00
print ( result . content )
asyncio . run ( main ())
```
2026-06-06 15:29:13 +08:00
Use `async with` when possible so MCP connections and background cleanup work are closed before the event loop exits. If you manage the instance manually, call `await bot.aclose()` in a `finally` block.
2026-04-19 09:51:54 +00:00
## Common Patterns
### Use a specific config or workspace
2026-04-19 09:42:17 +00:00
```python
from nanobot import Nanobot
2026-04-19 09:51:54 +00:00
bot = Nanobot . from_config (
config_path = "~/.nanobot/config.json" ,
workspace = "/my/project" ,
)
2026-04-19 09:42:17 +00:00
```
2026-04-19 09:51:54 +00:00
### Isolate conversations with `session_key`
Different session keys keep independent conversation history:
2026-04-19 09:42:17 +00:00
```python
await bot . run ( "hi" , session_key = "user-alice" )
await bot . run ( "hi" , session_key = "task-42" )
```
2026-04-19 09:51:54 +00:00
### Attach hooks for observability
Hooks let you inspect tool calls, streaming, and iteration state without modifying nanobot internals:
2026-04-19 09:42:17 +00:00
```python
from nanobot.agent import AgentHook , AgentHookContext
2026-04-19 09:51:54 +00:00
2026-04-19 09:42:17 +00:00
class AuditHook ( AgentHook ):
2026-04-19 09:51:54 +00:00
async def before_execute_tools ( self , context : AgentHookContext ) -> None :
for tc in context . tool_calls :
2026-04-19 09:42:17 +00:00
print ( f "[tool] { tc . name } " )
2026-04-19 09:51:54 +00:00
result = await bot . run ( "Review this change" , hooks = [ AuditHook ()])
```
## API Reference
### `Nanobot.from_config(config_path=None, *, workspace=None)`
Create a `Nanobot` instance from a config file.
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `config_path` | `str \| Path \| None` | `None` | Path to `config.json` . Defaults to `~/.nanobot/config.json` . |
| `workspace` | `str \| Path \| None` | `None` | Override the workspace directory from config. |
Raises `FileNotFoundError` if an explicit config path does not exist.
### `await bot.run(message, *, session_key="sdk:default", hooks=None)`
Run the agent once and return a `RunResult` .
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `message` | `str` | *(required)* | The user message to process. |
| `session_key` | `str` | `"sdk:default"` | Session identifier for conversation isolation. Different keys get independent history. |
| `hooks` | `list[AgentHook] \| None` | `None` | Lifecycle hooks for this run only. |
2026-06-06 15:29:13 +08:00
### `await bot.aclose()`
Release resources held by the SDK instance, including MCP connections. The async context manager calls this automatically:
```python
async with Nanobot . from_config () as bot :
result = await bot . run ( "Summarize this repo" )
```
2026-04-19 09:51:54 +00:00
### `RunResult`
| Field | Type | Description |
|-------|------|-------------|
| `content` | `str` | The agent's final text response. |
| `tools_used` | `list[str]` | Reserved for richer SDK introspection; may be empty in current versions. |
| `messages` | `list[dict]` | Reserved for richer SDK introspection; may be empty in current versions. |
## Hooks
Hooks let you observe or customize the agent loop. Subclass `AgentHook` and override the methods you need.
### Hook lifecycle
| Method | When |
|--------|------|
| `wants_streaming()` | Return `True` if you want token-by-token `on_stream()` callbacks |
| `before_iteration(context)` | Before each LLM call |
| `on_stream(context, delta)` | On each streamed token when streaming is enabled |
| `on_stream_end(context, *, resuming)` | When streaming finishes |
| `before_execute_tools(context)` | Before tool execution |
| `after_iteration(context)` | After each iteration |
| `finalize_content(context, content)` | Transform final output text |
Useful fields on `AgentHookContext` include:
- `iteration`
- `messages`
- `response`
- `usage`
- `tool_calls`
- `tool_results`
- `tool_events`
- `final_content`
- `stop_reason`
- `error`
### Example: audit tool calls
```python
from nanobot.agent import AgentHook , AgentHookContext
class AuditHook ( AgentHook ):
def __init__ ( self ) -> None :
super () . __init__ ()
self . calls : list [ str ] = []
async def before_execute_tools ( self , context : AgentHookContext ) -> None :
for tc in context . tool_calls :
self . calls . append ( tc . name )
print ( f "[audit] { tc . name } ( { tc . arguments } )" )
```
```python
hook = AuditHook ()
result = await bot . run ( "List files in /tmp" , hooks = [ hook ])
print ( result . content )
print ( f "Tools observed: { hook . calls } " )
```
### Example: receive streaming tokens
```python
from nanobot.agent import AgentHook , AgentHookContext
class StreamingHook ( AgentHook ):
def wants_streaming ( self ) -> bool :
return True
async def on_stream ( self , context : AgentHookContext , delta : str ) -> None :
print ( delta , end = "" , flush = True )
async def on_stream_end ( self , context : AgentHookContext , * , resuming : bool ) -> None :
print ()
```
### Compose multiple hooks
Pass multiple hooks when you want to combine behaviors:
```python
result = await bot . run ( "hi" , hooks = [ AuditHook (), MetricsHook ()])
```
Async hook methods are fan-out with error isolation. `finalize_content` is a pipeline: each hook receives the previous hook's output.
### Example: post-process final content
```python
from nanobot.agent import AgentHook
class Censor ( AgentHook ):
def finalize_content ( self , context , content ):
return content . replace ( "secret" , "***" ) if content else content
```
## Full Example
```python
import asyncio
import time
from nanobot import Nanobot
from nanobot.agent import AgentHook , AgentHookContext
class TimingHook ( AgentHook ):
def __init__ ( self ) -> None :
super () . __init__ ()
self . _started_at = 0.0
async def before_iteration ( self , context : AgentHookContext ) -> None :
self . _started_at = time . perf_counter ()
async def after_iteration ( self , context : AgentHookContext ) -> None :
elapsed_ms = ( time . perf_counter () - self . _started_at ) * 1000
print ( f "[timing] iteration { context . iteration } took { elapsed_ms : .1f } ms" )
async def main () -> None :
bot = Nanobot . from_config ( workspace = "/my/project" )
result = await bot . run (
"Explain the main function" ,
session_key = "sdk:demo" ,
hooks = [ TimingHook ()],
)
print ( result . content )
asyncio . run ( main ())
2026-04-19 09:42:17 +00:00
```