chuk-tool-processor 0.9.2__py3-none-any.whl → 0.10__py3-none-any.whl

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.

Potentially problematic release.


This version of chuk-tool-processor might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chuk-tool-processor
3
- Version: 0.9.2
3
+ Version: 0.10
4
4
  Summary: Async-native framework for registering, discovering, and executing tools referenced in LLM responses
5
5
  Author-email: CHUK Team <chrishayuk@somejunkmailbox.com>
6
6
  Maintainer-email: CHUK Team <chrishayuk@somejunkmailbox.com>
@@ -20,71 +20,153 @@ Classifier: Framework :: AsyncIO
20
20
  Classifier: Typing :: Typed
21
21
  Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
- Requires-Dist: chuk-mcp>=0.7.1
23
+ Requires-Dist: chuk-mcp>=0.8.1
24
24
  Requires-Dist: dotenv>=0.9.9
25
25
  Requires-Dist: psutil>=7.0.0
26
26
  Requires-Dist: pydantic>=2.11.3
27
27
  Requires-Dist: uuid>=1.30
28
28
 
29
- # CHUK Tool Processor
29
+ # CHUK Tool Processor — Production-grade execution for LLM tool calls
30
30
 
31
31
  [![PyPI](https://img.shields.io/pypi/v/chuk-tool-processor.svg)](https://pypi.org/project/chuk-tool-processor/)
32
32
  [![Python](https://img.shields.io/pypi/pyversions/chuk-tool-processor.svg)](https://pypi.org/project/chuk-tool-processor/)
33
33
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
34
+ [![Type Checked](https://img.shields.io/badge/type%20checked-PEP%20561-blue.svg)](https://www.python.org/dev/peps/pep-0561/)
35
+ [![Wheels](https://img.shields.io/badge/wheels-macOS%20%7C%20Linux%20%7C%20Windows-blue.svg)](https://pypi.org/project/chuk-tool-processor/)
36
+ [![OpenTelemetry](https://img.shields.io/badge/observability-OpenTelemetry%20%7C%20Prometheus-blue.svg)](docs/OBSERVABILITY.md)
34
37
 
35
- **The missing link between LLM tool calls and reliable execution.**
38
+ **Reliable tool execution for LLMs timeouts, retries, caching, rate limits, circuit breakers, and MCP integration — in one composable layer.**
36
39
 
37
- CHUK Tool Processor is a focused, production-ready framework that solves one problem exceptionally well: **processing tool calls from LLM outputs**. It's not a chatbot framework or LLM orchestration platform—it's the glue layer that bridges LLM responses and actual tool execution.
40
+ ---
38
41
 
39
- ## The Problem
42
+ ## The Missing Layer for Reliable Tool Execution
40
43
 
41
- When you build LLM applications, you face a gap:
44
+ LLMs are good at *calling* tools. The hard part is **executing** those tools reliably.
42
45
 
43
- 1. **LLM generates tool calls** in various formats (XML tags, OpenAI `tool_calls`, JSON)
44
- 2. **??? Mystery step ???** where you need to:
45
- - Parse those calls reliably
46
- - Handle timeouts, retries, failures
47
- - Cache expensive results
48
- - Rate limit API calls
49
- - Run untrusted code safely
50
- - Connect to external tool servers
51
- - Log everything for debugging
52
- 3. **Get results back** to continue the LLM conversation
46
+ **CHUK Tool Processor:**
47
+ - Parses tool calls from any model (Anthropic XML, OpenAI `tool_calls`, JSON)
48
+ - Executes them with **timeouts, retries, caching, rate limits, circuit breaker, observability**
49
+ - Runs tools locally, in **isolated subprocesses**, or **remote via MCP**
53
50
 
54
- Most frameworks give you steps 1 and 3, but step 2 is where the complexity lives. CHUK Tool Processor **is** step 2.
51
+ CHUK Tool Processor is the execution layer between LLM responses and real tools.
55
52
 
56
- ## Why chuk-tool-processor?
53
+ It sits **below** agent frameworks and prompt orchestration, and **above** raw tool implementations.
57
54
 
58
- ### It's a Building Block, Not a Framework
55
+ ```
56
+ LLM Output
57
+
58
+ CHUK Tool Processor
59
+
60
+ ┌──────────────┬────────────────────┐
61
+ │ Local Tools │ Remote Tools (MCP) │
62
+ └──────────────┴────────────────────┘
63
+ ```
59
64
 
60
- Unlike full-fledged LLM frameworks (LangChain, LlamaIndex, etc.), CHUK Tool Processor:
65
+ **How it works internally:**
61
66
 
62
- - ✅ **Does one thing well**: Process tool calls reliably
63
- - ✅ **Plugs into any LLM app**: Works with any framework or no framework
64
- - ✅ **Composable by design**: Stack strategies and wrappers like middleware
65
- - **No opinions about your LLM**: Bring your own OpenAI, Anthropic, local model
66
- - ❌ **Doesn't manage conversations**: That's your job
67
- - ❌ **Doesn't do prompt engineering**: Use whatever prompting you want
68
- - ❌ **Doesn't bundle an LLM client**: Use any client library you prefer
67
+ ```
68
+ LLM Output
69
+
70
+ Parsers (XML / OpenAI / JSON)
71
+
72
+ ┌─────────────────────────────┐
73
+ │ Execution Middleware │
74
+ │ (Applied in this order) │
75
+ │ • Cache │
76
+ │ • Rate Limit │
77
+ │ • Retry (with backoff) │
78
+ │ • Circuit Breaker │
79
+ └─────────────────────────────┘
80
+
81
+ Execution Strategy
82
+ ┌──────────────────────┐
83
+ │ • InProcess │ ← Fast, trusted
84
+ │ • Isolated/Subprocess│ ← Safe, untrusted
85
+ │ • Remote via MCP │ ← Distributed
86
+ └──────────────────────┘
87
+ ```
69
88
 
70
- ### It's Built for Production
89
+ Works with OpenAI, Anthropic, local models (Ollama/MLX/vLLM), and any framework (LangChain, LlamaIndex, custom).
90
+
91
+ ## Executive TL;DR
71
92
 
72
- Research code vs production code is about handling the edges:
93
+ * **Parse any format:** `XML` (Anthropic), `OpenAI tool_calls`, or raw `JSON`
94
+ * **Execute with production policies:** timeouts/retries/cache/rate-limits/circuit-breaker/idempotency
95
+ * **Run anywhere:** locally (fast), isolated (subprocess sandbox), or remote via MCP (HTTP/STDIO/SSE)
73
96
 
74
- - **Timeouts**: Every tool execution has proper timeout handling
75
- - **Retries**: Automatic retry with exponential backoff and deadline awareness
76
- - **Rate Limiting**: Global and per-tool rate limits with sliding windows
77
- - **Caching**: Intelligent result caching with TTL and idempotency key support
78
- - **Circuit Breakers**: Prevent cascading failures with automatic fault detection
79
- - **Error Handling**: Machine-readable error codes with structured details
80
- - **Observability**: Structured logging, metrics, request tracing
81
- - **Safety**: Subprocess isolation for untrusted code
82
- - **Type Safety**: Pydantic validation with LLM-friendly argument coercion
83
- - **Tool Discovery**: Formal schema export (OpenAI, Anthropic, MCP formats)
97
+ ```python
98
+ import asyncio
99
+ from chuk_tool_processor import ToolProcessor, register_tool, initialize
84
100
 
85
- ### It's About Stacks
101
+ @register_tool(name="weather")
102
+ class WeatherTool:
103
+ async def execute(self, city: str) -> dict:
104
+ return {"temp": 72, "condition": "sunny", "city": city}
86
105
 
87
- CHUK Tool Processor uses a **composable stack architecture**:
106
+ async def main():
107
+ await initialize()
108
+ async with ToolProcessor(enable_caching=True, enable_retries=True) as p:
109
+ # Works with OpenAI, Anthropic, or JSON formats
110
+ result = await p.process('<tool name="weather" args=\'{"city": "SF"}\'/>')
111
+ print(result[0].result) # {'temp': 72, 'condition': 'sunny', 'city': 'SF'}
112
+
113
+ asyncio.run(main())
114
+ ```
115
+
116
+ > **If you only remember three things:**
117
+ >
118
+ > 1. **Parse** `XML`, `OpenAI tool_calls`, or raw `JSON` automatically
119
+ > 2. **Execute** with timeouts/retries/cache/rate-limits/circuit-breaker
120
+ > 3. **Run** tools locally, isolated (subprocess), or remote via MCP
121
+
122
+ ## When to Use This
123
+
124
+ Use **CHUK Tool Processor** when:
125
+ - Your LLM calls tools or APIs
126
+ - You need **retries, timeouts, caching, or rate limits**
127
+ - You need to **run untrusted tools safely**
128
+ - Your tools are **local or remote (MCP)**
129
+
130
+ Do **not** use this if:
131
+ - You want an agent framework
132
+ - You want conversation flow/memory orchestration
133
+
134
+ **This is the execution layer, not the agent.**
135
+
136
+ > **Not a framework.**
137
+ > If LangChain/LlamaIndex help decide *which* tool to call,
138
+ > CHUK Tool Processor makes sure the tool call **actually succeeds**.
139
+
140
+ ## Table of Contents
141
+
142
+ - [The Problem](#the-problem)
143
+ - [Why chuk-tool-processor?](#why-chuk-tool-processor)
144
+ - [Compatibility Matrix](#compatibility-matrix)
145
+ - [Developer Experience Highlights](#developer-experience-highlights)
146
+ - [Quick Start](#quick-start)
147
+ - [Documentation Quick Reference](#documentation-quick-reference)
148
+ - [Choose Your Path](#choose-your-path)
149
+ - [Core Concepts](#core-concepts)
150
+ - [Getting Started](#getting-started)
151
+ - [Advanced Topics](#advanced-topics)
152
+ - [Configuration](#configuration)
153
+ - [Architecture Principles](#architecture-principles)
154
+ - [Examples](#examples)
155
+ - [FAQ](#faq)
156
+ - [Comparison with Other Tools](#comparison-with-other-tools)
157
+ - [Development & Publishing](#development--publishing)
158
+ - [Stability & Versioning](#stability--versioning)
159
+ - [Contributing & Support](#contributing--support)
160
+
161
+ ## The Problem
162
+
163
+ LLMs generate tool calls. **The hard part is executing them reliably.**
164
+
165
+ CHUK Tool Processor **is that execution layer.**
166
+
167
+ ## Why chuk-tool-processor?
168
+
169
+ **Composable execution layers:**
88
170
 
89
171
  ```
90
172
  ┌─────────────────────────────────┐
@@ -104,7 +186,7 @@ CHUK Tool Processor uses a **composable stack architecture**:
104
186
  ├─────────────────────────────────┤
105
187
  │ Execution Strategy │ ← How to run tools
106
188
  │ • InProcess (fast) │
107
- │ • Subprocess (isolated) │
189
+ │ • Isolated (subprocess) │
108
190
  ├─────────────────────────────────┤
109
191
  │ Tool Registry │ ← Your registered tools
110
192
  └─────────────────────────────────┘
@@ -112,6 +194,70 @@ CHUK Tool Processor uses a **composable stack architecture**:
112
194
 
113
195
  Each layer is **optional** and **configurable**. Mix and match what you need.
114
196
 
197
+ ### It's a Building Block, Not a Framework
198
+
199
+ Unlike full-fledged LLM frameworks (LangChain, LlamaIndex, etc.), CHUK Tool Processor:
200
+
201
+ - ✅ **Does one thing well**: Process tool calls reliably
202
+ - ✅ **Plugs into any LLM app**: Works with any framework or no framework
203
+ - ✅ **Composable by design**: Stack strategies and wrappers like middleware
204
+ - ✅ **No opinions about your LLM**: Bring your own OpenAI, Anthropic, local model
205
+ - ❌ **Doesn't manage conversations**: That's your job
206
+ - ❌ **Doesn't do prompt engineering**: Use whatever prompting you want
207
+ - ❌ **Doesn't bundle an LLM client**: Use any client library you prefer
208
+
209
+ ### It's Built for Production
210
+
211
+ Research code vs production code is about handling the edges. CHUK Tool Processor includes:
212
+
213
+ - ✅ **Timeouts** — Every tool execution has proper timeout handling
214
+ - ✅ **Retries** — Automatic retry with exponential backoff and deadline awareness
215
+ - ✅ **Rate Limiting** — Global and per-tool rate limits with sliding windows → [CONFIGURATION.md](docs/CONFIGURATION.md)
216
+ - ✅ **Caching** — Intelligent result caching with TTL and idempotency key support
217
+ - ✅ **Circuit Breakers** — Prevent cascading failures with automatic fault detection
218
+ - ✅ **Idempotency** — SHA256-based deduplication of LLM retry quirks
219
+ - ✅ **Error Handling** — Machine-readable error codes with structured details → [ERRORS.md](docs/ERRORS.md)
220
+ - ✅ **Observability** — Structured logging, metrics, OpenTelemetry tracing → [OBSERVABILITY.md](docs/OBSERVABILITY.md)
221
+ - ✅ **Safety** — Subprocess isolation for untrusted code (zero crash blast radius)
222
+ - ✅ **Type Safety** — PEP 561 compliant with full mypy support
223
+ - ✅ **Resource Management** — Context managers for automatic cleanup
224
+ - ✅ **Tool Discovery** — Formal schema export (OpenAI, Anthropic, MCP formats)
225
+ - ✅ **Cancellation** — Cooperative cancellation with request-scoped deadlines
226
+
227
+ ## Compatibility Matrix
228
+
229
+ Runs the same on macOS, Linux, and Windows — locally, serverside, and inside containers.
230
+
231
+ | Component | Supported Versions | Notes |
232
+ |-----------|-------------------|-------|
233
+ | **Python** | 3.11, 3.12, 3.13 | Python 3.11+ required |
234
+ | **Operating Systems** | macOS, Linux, Windows | All platforms fully supported |
235
+ | **LLM Providers** | OpenAI, Anthropic, Local models | Any LLM that outputs tool calls |
236
+ | **MCP Transports** | HTTP Streamable, STDIO, SSE | All MCP 1.0 transports |
237
+ | **MCP Servers** | Notion, SQLite, Atlassian, Echo, Custom | Any MCP-compliant server |
238
+
239
+ **Tested Configurations:**
240
+ - ✅ macOS 14+ (Apple Silicon & Intel)
241
+ - ✅ Ubuntu 20.04+ / Debian 11+
242
+ - ✅ Windows 10+ (native & WSL2)
243
+ - ✅ Python 3.11.0+, 3.12.0+, 3.13.0+
244
+ - ✅ OpenAI GPT-4, GPT-4 Turbo
245
+ - ✅ Anthropic Claude 3 (Opus, Sonnet, Haiku)
246
+ - ✅ Local models (Ollama, LM Studio)
247
+
248
+ ## Developer Experience Highlights
249
+
250
+ **What makes CHUK Tool Processor easy to use:**
251
+
252
+ * **Auto-parsing**: XML (Claude), OpenAI `tool_calls`, direct JSON—all work automatically
253
+ * **One call**: `process()` handles multiple calls & formats in a single invocation
254
+ * **Auto-coercion**: Pydantic-powered argument cleanup (whitespace, type conversion, extra fields ignored)
255
+ * **Safe defaults**: timeouts, retries, caching toggles built-in
256
+ * **Observability in one line**: `setup_observability(...)` for traces + metrics
257
+ * **MCP in one call**: `setup_mcp_http_streamable|stdio|sse(...)` connects to remote tools instantly
258
+ * **Context managers**: `async with ToolProcessor() as p:` ensures automatic cleanup
259
+ * **Full type safety**: PEP 561 compliant—mypy, pyright, and IDEs get complete type information
260
+
115
261
  ## Quick Start
116
262
 
117
263
  ### Installation
@@ -124,21 +270,111 @@ pip install chuk-tool-processor
124
270
 
125
271
  # Using uv (recommended)
126
272
  uv pip install chuk-tool-processor
273
+ ```
274
+
275
+ <details>
276
+ <summary><strong>Install from source or with extras</strong></summary>
127
277
 
128
- # Or from source
278
+ ```bash
279
+ # From source
129
280
  git clone https://github.com/chrishayuk/chuk-tool-processor.git
130
281
  cd chuk-tool-processor
131
282
  uv pip install -e .
283
+
284
+ # With observability extras (OpenTelemetry + Prometheus)
285
+ pip install chuk-tool-processor[observability]
286
+
287
+ # With MCP extras
288
+ pip install chuk-tool-processor[mcp]
289
+
290
+ # All extras
291
+ pip install chuk-tool-processor[all]
292
+ ```
293
+
294
+ </details>
295
+
296
+ <details>
297
+ <summary><strong>Type Checking Support (PEP 561 compliant)</strong></summary>
298
+
299
+ CHUK Tool Processor includes **full type checking support**:
300
+
301
+ ```python
302
+ # mypy, pyright, and IDEs get full type information!
303
+ from chuk_tool_processor import ToolProcessor, ToolCall, ToolResult
304
+
305
+ async with ToolProcessor() as processor:
306
+ # Full autocomplete and type checking
307
+ results: list[ToolResult] = await processor.process(llm_output)
308
+ tools: list[str] = await processor.list_tools()
309
+ ```
310
+
311
+ **Features:**
312
+ - ✅ `py.typed` marker for PEP 561 compliance
313
+ - ✅ Comprehensive type hints on all public APIs
314
+ - ✅ Works with mypy, pyright, pylance
315
+ - ✅ Full IDE autocomplete support
316
+
317
+ **No special mypy configuration needed** - just import and use!
318
+
319
+ </details>
320
+
321
+ ## 60-Second Quick Start
322
+
323
+ ### From raw LLM output to safe execution in 3 lines
324
+
325
+ ```python
326
+ from chuk_tool_processor import ToolProcessor, initialize
327
+
328
+ await initialize()
329
+ async with ToolProcessor() as p:
330
+ results = await p.process('<tool name="calculator" args=\'{"operation":"multiply","a":15,"b":23}\'/>')
331
+ ```
332
+
333
+ **Note:** This assumes you've registered a "calculator" tool. See complete example below.
334
+
335
+ ### Works with Both OpenAI and Anthropic (No Adapters Needed)
336
+
337
+ ```python
338
+ from chuk_tool_processor import ToolProcessor, register_tool, initialize
339
+
340
+ @register_tool(name="search")
341
+ class SearchTool:
342
+ async def execute(self, query: str) -> dict:
343
+ return {"results": [f"Found: {query}"]}
344
+
345
+ await initialize()
346
+ async with ToolProcessor() as p:
347
+ # OpenAI format
348
+ openai_response = {"tool_calls": [{"type": "function", "function": {"name": "search", "arguments": '{"query": "Python"}'}}]}
349
+
350
+ # Anthropic format
351
+ anthropic_response = '<tool name="search" args=\'{"query": "Python"}\'/>'
352
+
353
+ # Both work identically
354
+ results_openai = await p.process(openai_response)
355
+ results_anthropic = await p.process(anthropic_response)
356
+ ```
357
+
358
+ **Absolutely minimal example** → See `examples/01_getting_started/hello_tool.py`:
359
+
360
+ ```bash
361
+ python examples/01_getting_started/hello_tool.py
132
362
  ```
133
363
 
364
+ Single file that demonstrates:
365
+ - Registering a tool
366
+ - Parsing OpenAI & Anthropic formats
367
+ - Executing and getting results
368
+
369
+ Takes 60 seconds to understand, 3 minutes to master.
370
+
134
371
  ### 3-Minute Example
135
372
 
136
373
  Copy-paste this into a file and run it:
137
374
 
138
375
  ```python
139
376
  import asyncio
140
- from chuk_tool_processor.core.processor import ToolProcessor
141
- from chuk_tool_processor.registry import initialize, register_tool
377
+ from chuk_tool_processor import ToolProcessor, register_tool, initialize
142
378
 
143
379
  # Step 1: Define a tool
144
380
  @register_tool(name="calculator")
@@ -152,40 +388,166 @@ class Calculator:
152
388
  # Step 2: Process LLM output
153
389
  async def main():
154
390
  await initialize()
155
- processor = ToolProcessor()
156
391
 
157
- # Your LLM returned this tool call
158
- llm_output = '<tool name="calculator" args=\'{"operation": "multiply", "a": 15, "b": 23}\'/>'
392
+ # Use context manager for automatic cleanup
393
+ async with ToolProcessor() as processor:
394
+ # Your LLM returned this tool call
395
+ llm_output = '<tool name="calculator" args=\'{"operation": "multiply", "a": 15, "b": 23}\'/>'
159
396
 
160
- # Process it
161
- results = await processor.process(llm_output)
397
+ # Process it
398
+ results = await processor.process(llm_output)
162
399
 
163
- # Each result is a ToolExecutionResult with: tool, args, result, error, duration, cached
164
- # results[0].result contains the tool output
165
- # results[0].error contains any error message (None if successful)
166
- if results[0].error:
167
- print(f"Error: {results[0].error}")
168
- else:
169
- print(results[0].result) # {'result': 345}
400
+ # Each result is a ToolResult with: tool, result, error, duration, cached
401
+ if results[0].error:
402
+ print(f"Error: {results[0].error}")
403
+ else:
404
+ print(results[0].result) # {'result': 345}
405
+
406
+ # Processor automatically cleaned up!
170
407
 
171
408
  asyncio.run(main())
172
409
  ```
173
410
 
174
- **That's it.** You now have production-ready tool execution with timeouts, retries, and caching.
411
+ **That's it.** You now have production-ready tool execution with:
412
+ - ✅ Automatic timeouts, retries, and caching
413
+ - ✅ Clean resource management (context manager)
414
+ - ✅ Full type checking support
175
415
 
176
416
  > **Why not just use OpenAI tool calls?**
177
- > OpenAI's function calling is great for parsing, but you still need: parsing multiple formats (Anthropic XML, etc.), timeouts, retries, rate limits, caching, subprocess isolation, and connecting to external MCP servers. CHUK Tool Processor **is** that missing middle layer.
417
+ > OpenAI's function calling is great for parsing, but you still need: parsing multiple formats (Anthropic XML, etc.), timeouts, retries, rate limits, caching, subprocess isolation, connecting to external MCP servers, and **per-tool** policy control with cross-provider parsing and MCP fan-out. CHUK Tool Processor **is** that missing middle layer.
418
+
419
+ ## Quick Decision Tree (Commit This to Memory)
420
+
421
+ ```
422
+ ╭──────────────────────────────────────────╮
423
+ │ Do you trust the code you're executing? │
424
+ │ ✅ Yes → InProcessStrategy │
425
+ │ ⚠️ No → IsolatedStrategy (sandboxed) │
426
+ │ │
427
+ │ Where do your tools live? │
428
+ │ 📦 Local → @register_tool │
429
+ │ 🌐 Remote → setup_mcp_http_streamable │
430
+ ╰──────────────────────────────────────────╯
431
+ ```
432
+
433
+ **That's all you need to pick the right pattern.**
434
+
435
+ ## Registry & Processor Lifecycle
436
+
437
+ Understanding the lifecycle helps you use CHUK Tool Processor correctly:
438
+
439
+ 1. **`await initialize()`** — loads the global registry; call **once per process** at application startup
440
+ 2. Create a **`ToolProcessor(...)`** (or use the one returned by `setup_mcp_*`)
441
+ 3. Use **`async with ToolProcessor() as p:`** to ensure cleanup
442
+ 4. **`setup_mcp_*`** returns `(processor, manager)` — reuse that `processor`
443
+ 5. If you need a custom registry, pass it explicitly to the strategy
444
+ 6. You rarely need `get_default_registry()` unless you're composing advanced setups
445
+
446
+ **⚠️ Important:** `initialize()` must run **once per process**, not once per request or processor instance. Running it multiple times will duplicate tools in the registry.
447
+
448
+ ```python
449
+ # Standard pattern
450
+ await initialize() # Step 1: Register tools
451
+
452
+ async with ToolProcessor() as p: # Step 2-3: Create + auto cleanup
453
+ results = await p.process(llm_output)
454
+ # Step 4: Processor automatically cleaned up on exit
455
+ ```
456
+
457
+ ## Production Features by Example
458
+
459
+ ### Idempotency & Deduplication
460
+
461
+ Automatically deduplicate LLM retry quirks using SHA256-based idempotency keys:
462
+
463
+ ```python
464
+ from chuk_tool_processor import ToolProcessor, initialize
465
+
466
+ await initialize()
467
+ async with ToolProcessor(enable_caching=True, cache_ttl=300) as p:
468
+ # LLM retries the same call (common with streaming or errors)
469
+ call1 = '<tool name="search" args=\'{"query": "Python"}\'/>'
470
+ call2 = '<tool name="search" args=\'{"query": "Python"}\'/>' # Identical
471
+
472
+ results1 = await p.process(call1) # Executes
473
+ results2 = await p.process(call2) # Cache hit! (idempotency key match)
474
+
475
+ assert results1[0].cached == False
476
+ assert results2[0].cached == True
477
+ ```
478
+
479
+ ### Cancellation & Deadlines
480
+
481
+ Cooperative cancellation with request-scoped deadlines:
482
+
483
+ ```python
484
+ import asyncio
485
+ from chuk_tool_processor import ToolProcessor, initialize
486
+
487
+ async def main():
488
+ await initialize()
489
+ async with ToolProcessor(default_timeout=60.0) as p:
490
+ try:
491
+ # Hard deadline for the whole batch (e.g., user request budget)
492
+ async with asyncio.timeout(5.0):
493
+ async for event in p.astream('<tool name="slow_report" args=\'{"n": 1000000}\'/>'):
494
+ print("chunk:", event)
495
+ except TimeoutError:
496
+ print("Request cancelled: deadline exceeded")
497
+ # Processor automatically cancels the tool and cleans up
498
+
499
+ asyncio.run(main())
500
+ ```
501
+
502
+ ### Per-Tool Policy Overrides
503
+
504
+ Override timeouts, retries, and rate limits per tool:
505
+
506
+ ```python
507
+ from chuk_tool_processor import ToolProcessor, initialize
508
+
509
+ await initialize()
510
+ async with ToolProcessor(
511
+ default_timeout=30.0,
512
+ enable_retries=True,
513
+ max_retries=2,
514
+ enable_rate_limiting=True,
515
+ global_rate_limit=120, # 120 requests/min across all tools
516
+ tool_rate_limits={
517
+ "expensive_api": (5, 60), # 5 requests per 60 seconds
518
+ "fast_local": (1000, 60), # 1000 requests per 60 seconds
519
+ }
520
+ ) as p:
521
+ # Tools run with their specific policies
522
+ results = await p.process('''
523
+ <tool name="expensive_api" args='{"q":"abc"}'/>
524
+ <tool name="fast_local" args='{"data":"xyz"}'/>
525
+ ''')
526
+ ```
527
+
528
+ ## Documentation Quick Reference
529
+
530
+ | Document | What It Covers |
531
+ |----------|----------------|
532
+ | 📘 [CONFIGURATION.md](docs/CONFIGURATION.md) | **All config knobs & defaults**: ToolProcessor options, timeouts, retry policy, rate limits, circuit breakers, caching, environment variables |
533
+ | 🚨 [ERRORS.md](docs/ERRORS.md) | **Error taxonomy**: All error codes, exception classes, error details structure, handling patterns, retryability guide |
534
+ | 📊 [OBSERVABILITY.md](docs/OBSERVABILITY.md) | **Metrics & tracing**: OpenTelemetry setup, Prometheus metrics, spans reference, PromQL queries |
535
+ | 🔌 [examples/01_getting_started/hello_tool.py](examples/01_getting_started/hello_tool.py) | **60-second starter**: Single-file, copy-paste-and-run example |
536
+ | 🎯 [examples/](examples/) | **20+ working examples**: MCP integration, OAuth flows, streaming, production patterns |
178
537
 
179
538
  ## Choose Your Path
180
539
 
540
+ **Use this when OpenAI/Claude tool calling is not enough** — because you need retries, caching, rate limits, subprocess isolation, or MCP integration.
541
+
181
542
  | Your Goal | What You Need | Where to Look |
182
543
  |-----------|---------------|---------------|
183
- | ☕ **Just process LLM tool calls** | Basic tool registration + processor | [3-Minute Example](#3-minute-example) |
544
+ | ☕ **Just process LLM tool calls** | Basic tool registration + processor | [60-Second Quick Start](#60-second-quick-start) |
184
545
  | 🔌 **Connect to external tools** | MCP integration (HTTP/STDIO/SSE) | [MCP Integration](#5-mcp-integration-external-tools) |
185
- | 🛡️ **Production deployment** | Timeouts, retries, rate limits, caching | [Production Configuration](#using-the-processor) |
186
- | 🔒 **Run untrusted code safely** | Subprocess isolation strategy | [Subprocess Strategy](#using-subprocess-strategy) |
187
- | 📊 **Monitor and observe** | OpenTelemetry + Prometheus | [Observability](#opentelemetry--prometheus-drop-in-observability) |
546
+ | 🛡️ **Production deployment** | Timeouts, retries, rate limits, caching | [CONFIGURATION.md](docs/CONFIGURATION.md) |
547
+ | 🔒 **Run untrusted code safely** | Isolated strategy (subprocess) | [Isolated Strategy](#using-isolated-strategy) |
548
+ | 📊 **Monitor and observe** | OpenTelemetry + Prometheus | [OBSERVABILITY.md](docs/OBSERVABILITY.md) |
188
549
  | 🌊 **Stream incremental results** | StreamingTool pattern | [StreamingTool](#streamingtool-real-time-results) |
550
+ | 🚨 **Handle errors reliably** | Error codes & taxonomy | [ERRORS.md](docs/ERRORS.md) |
189
551
 
190
552
  ### Real-World Quick Start
191
553
 
@@ -194,8 +556,7 @@ Here are the most common patterns you'll use:
194
556
  **Pattern 1: Local tools only**
195
557
  ```python
196
558
  import asyncio
197
- from chuk_tool_processor.core.processor import ToolProcessor
198
- from chuk_tool_processor.registry import initialize, register_tool
559
+ from chuk_tool_processor import ToolProcessor, register_tool, initialize
199
560
 
200
561
  @register_tool(name="my_tool")
201
562
  class MyTool:
@@ -204,20 +565,22 @@ class MyTool:
204
565
 
205
566
  async def main():
206
567
  await initialize()
207
- processor = ToolProcessor()
208
568
 
209
- llm_output = '<tool name="my_tool" args=\'{"arg": "hello"}\'/>'
210
- results = await processor.process(llm_output)
211
- print(results[0].result) # {'result': 'Processed: hello'}
569
+ async with ToolProcessor() as processor:
570
+ llm_output = '<tool name="my_tool" args=\'{"arg": "hello"}\'/>'
571
+ results = await processor.process(llm_output)
572
+ print(results[0].result) # {'result': 'Processed: hello'}
212
573
 
213
574
  asyncio.run(main())
214
575
  ```
215
576
 
577
+ <details>
578
+ <summary><strong>More patterns: MCP integration (local + remote tools)</strong></summary>
579
+
216
580
  **Pattern 2: Mix local + remote MCP tools (Notion)**
217
581
  ```python
218
582
  import asyncio
219
- from chuk_tool_processor.registry import initialize, register_tool
220
- from chuk_tool_processor.mcp import setup_mcp_http_streamable
583
+ from chuk_tool_processor import register_tool, initialize, setup_mcp_http_streamable
221
584
 
222
585
  @register_tool(name="local_calculator")
223
586
  class Calculator:
@@ -247,10 +610,13 @@ async def main():
247
610
  print(f"Local result: {results[0].result}")
248
611
  print(f"Notion result: {results[1].result}")
249
612
 
613
+ # Clean up
614
+ await manager.close()
615
+
250
616
  asyncio.run(main())
251
617
  ```
252
618
 
253
- See `examples/notion_oauth.py` for complete OAuth flow.
619
+ See `examples/04_mcp_integration/notion_oauth.py` for complete OAuth flow.
254
620
 
255
621
  **Pattern 3: Local SQLite database via STDIO**
256
622
  ```python
@@ -289,7 +655,9 @@ async def main():
289
655
  asyncio.run(main())
290
656
  ```
291
657
 
292
- See `examples/stdio_sqlite.py` for complete working example.
658
+ See `examples/04_mcp_integration/stdio_sqlite.py` for complete working example.
659
+
660
+ </details>
293
661
 
294
662
  ## Core Concepts
295
663
 
@@ -302,8 +670,10 @@ The **registry** is where you register tools for execution. Tools can be:
302
670
  - **StreamingTool** for real-time incremental results
303
671
  - **Functions** registered via `register_fn_tool()`
304
672
 
673
+ > **Note:** The registry is global, processors are scoped.
674
+
305
675
  ```python
306
- from chuk_tool_processor.registry import register_tool
676
+ from chuk_tool_processor import register_tool
307
677
  from chuk_tool_processor.models.validated_tool import ValidatedTool
308
678
  from pydantic import BaseModel, Field
309
679
 
@@ -329,18 +699,16 @@ class WeatherTool(ValidatedTool):
329
699
  | Strategy | Use Case | Trade-offs |
330
700
  |----------|----------|------------|
331
701
  | **InProcessStrategy** | Fast, trusted tools | Speed ✅, Isolation ❌ |
332
- | **SubprocessStrategy** | Untrusted or risky code | Isolation ✅, Speed ❌ |
702
+ | **IsolatedStrategy** | Untrusted or risky code | Isolation ✅, Speed ❌ |
333
703
 
334
704
  ```python
335
705
  import asyncio
336
- from chuk_tool_processor.core.processor import ToolProcessor
337
- from chuk_tool_processor.execution.strategies.subprocess_strategy import SubprocessStrategy
338
- from chuk_tool_processor.registry import get_default_registry
706
+ from chuk_tool_processor import ToolProcessor, IsolatedStrategy, get_default_registry
339
707
 
340
708
  async def main():
341
709
  registry = await get_default_registry()
342
710
  processor = ToolProcessor(
343
- strategy=SubprocessStrategy(
711
+ strategy=IsolatedStrategy(
344
712
  registry=registry,
345
713
  max_workers=4,
346
714
  default_timeout=30.0
@@ -351,6 +719,8 @@ async def main():
351
719
  asyncio.run(main())
352
720
  ```
353
721
 
722
+ **Note:** `IsolatedStrategy` is an alias of `SubprocessStrategy` for backwards compatibility. Use `IsolatedStrategy` for clarity—it better communicates the security boundary intent.
723
+
354
724
  ### 3. Execution Wrappers (Middleware)
355
725
 
356
726
  **Wrappers** add production features as composable layers:
@@ -416,6 +786,8 @@ Connect to **remote tool servers** using the [Model Context Protocol](https://mo
416
786
 
417
787
  #### HTTP Streamable (⭐ Recommended for Cloud Services)
418
788
 
789
+ **Use for:** Cloud SaaS services (OAuth, long-running streams, resilient reconnects)
790
+
419
791
  Modern HTTP streaming transport for cloud-based MCP servers like Notion:
420
792
 
421
793
  ```python
@@ -444,8 +816,13 @@ results = await processor.process(
444
816
  )
445
817
  ```
446
818
 
819
+ <details>
820
+ <summary><strong>Other MCP Transports (STDIO for local tools, SSE for legacy)</strong></summary>
821
+
447
822
  #### STDIO (Best for Local/On-Device Tools)
448
823
 
824
+ **Use for:** Local/embedded tools and databases (SQLite, file systems, local services)
825
+
449
826
  For running local MCP servers as subprocesses—great for databases, file systems, and local tools:
450
827
 
451
828
  ```python
@@ -484,6 +861,8 @@ results = await processor.process(
484
861
 
485
862
  #### SSE (Legacy Support)
486
863
 
864
+ **Use for:** Legacy compatibility only. Prefer HTTP Streamable for new integrations.
865
+
487
866
  For backward compatibility with older MCP servers using Server-Sent Events:
488
867
 
489
868
  ```python
@@ -505,6 +884,8 @@ processor, manager = await setup_mcp_sse(
505
884
  )
506
885
  ```
507
886
 
887
+ </details>
888
+
508
889
  **Transport Comparison:**
509
890
 
510
891
  | Transport | Use Case | Real Examples |
@@ -513,6 +894,18 @@ processor, manager = await setup_mcp_sse(
513
894
  | **STDIO** | Local tools, databases | SQLite (`mcp-server-sqlite`), Echo (`chuk-mcp-echo`) |
514
895
  | **SSE** | Legacy cloud services | Atlassian (`mcp.atlassian.com`) |
515
896
 
897
+ **How MCP fits into the architecture:**
898
+
899
+ ```
900
+ LLM Output
901
+
902
+ Tool Processor
903
+
904
+ ┌──────────────┬────────────────────┐
905
+ │ Local Tools │ Remote Tools (MCP) │
906
+ └──────────────┴────────────────────┘
907
+ ```
908
+
516
909
  **Relationship with [chuk-mcp](https://github.com/chrishayuk/chuk-mcp):**
517
910
  - `chuk-mcp` is a low-level MCP protocol client (handles transports, protocol negotiation)
518
911
  - `chuk-tool-processor` wraps `chuk-mcp` to integrate external tools into your execution pipeline
@@ -526,7 +919,7 @@ CHUK Tool Processor supports multiple patterns for defining tools:
526
919
 
527
920
  #### Simple Function-Based Tools
528
921
  ```python
529
- from chuk_tool_processor.registry.auto_register import register_fn_tool
922
+ from chuk_tool_processor import register_fn_tool
530
923
  from datetime import datetime
531
924
  from zoneinfo import ZoneInfo
532
925
 
@@ -584,17 +977,26 @@ class FileProcessor(StreamingTool):
584
977
 
585
978
  ```python
586
979
  import asyncio
587
- from chuk_tool_processor.core.processor import ToolProcessor
588
- from chuk_tool_processor.registry import initialize
980
+ from chuk_tool_processor import ToolProcessor, initialize
589
981
 
590
982
  async def main():
591
983
  await initialize()
592
984
  processor = ToolProcessor()
593
- async for event in processor.astream('<tool name="file_processor" args=\'{"file_path":"README.md"}\'/>'):
594
- # 'event' is a streamed chunk (either your Result model instance or a dict)
595
- line = event["line"] if isinstance(event, dict) else getattr(event, "line", None)
596
- content = event["content"] if isinstance(event, dict) else getattr(event, "content", None)
597
- print(f"Line {line}: {content}")
985
+
986
+ # Stream can be cancelled by breaking or raising an exception
987
+ try:
988
+ async for event in processor.astream('<tool name="file_processor" args=\'{"file_path":"README.md"}\'/>'):
989
+ # 'event' is a streamed chunk (either your Result model instance or a dict)
990
+ line = event["line"] if isinstance(event, dict) else getattr(event, "line", None)
991
+ content = event["content"] if isinstance(event, dict) else getattr(event, "content", None)
992
+ print(f"Line {line}: {content}")
993
+
994
+ # Example: cancel after 100 lines
995
+ if line and line > 100:
996
+ break # Cleanup happens automatically
997
+ except asyncio.CancelledError:
998
+ # Stream cleanup is automatic even on cancellation
999
+ pass
598
1000
 
599
1001
  asyncio.run(main())
600
1002
  ```
@@ -603,23 +1005,32 @@ asyncio.run(main())
603
1005
 
604
1006
  #### Basic Usage
605
1007
 
606
- Call `await initialize()` once at startup to load your registry.
1008
+ Call `await initialize()` once at startup to load your registry. Use context managers for automatic cleanup:
607
1009
 
608
1010
  ```python
609
1011
  import asyncio
610
- from chuk_tool_processor.core.processor import ToolProcessor
611
- from chuk_tool_processor.registry import initialize
1012
+ from chuk_tool_processor import ToolProcessor, initialize
612
1013
 
613
1014
  async def main():
614
1015
  await initialize()
615
- processor = ToolProcessor()
616
- llm_output = '<tool name="calculator" args=\'{"operation":"add","a":2,"b":3}\'/>'
617
- results = await processor.process(llm_output)
618
- for result in results:
619
- if result.error:
620
- print(f"Error: {result.error}")
621
- else:
622
- print(f"Success: {result.result}")
1016
+
1017
+ # Context manager automatically handles cleanup
1018
+ async with ToolProcessor() as processor:
1019
+ # Discover available tools
1020
+ tools = await processor.list_tools()
1021
+ print(f"Available tools: {tools}")
1022
+
1023
+ # Process LLM output
1024
+ llm_output = '<tool name="calculator" args=\'{"operation":"add","a":2,"b":3}\'/>'
1025
+ results = await processor.process(llm_output)
1026
+
1027
+ for result in results:
1028
+ if result.error:
1029
+ print(f"Error: {result.error}")
1030
+ else:
1031
+ print(f"Success: {result.result}")
1032
+
1033
+ # Processor automatically cleaned up here!
623
1034
 
624
1035
  asyncio.run(main())
625
1036
  ```
@@ -627,21 +1038,32 @@ asyncio.run(main())
627
1038
  #### Production Configuration
628
1039
 
629
1040
  ```python
630
- from chuk_tool_processor.core.processor import ToolProcessor
1041
+ from chuk_tool_processor import ToolProcessor, initialize
1042
+ import asyncio
631
1043
 
632
- processor = ToolProcessor(
633
- # Execution settings
634
- default_timeout=30.0,
635
- max_concurrency=20,
1044
+ async def main():
1045
+ await initialize()
636
1046
 
637
- # Production features
638
- enable_caching=True,
639
- cache_ttl=600,
640
- enable_rate_limiting=True,
641
- global_rate_limit=100,
642
- enable_retries=True,
643
- max_retries=3
644
- )
1047
+ # Use context manager with production config
1048
+ async with ToolProcessor(
1049
+ # Execution settings
1050
+ default_timeout=30.0,
1051
+ max_concurrency=20,
1052
+
1053
+ # Production features
1054
+ enable_caching=True,
1055
+ cache_ttl=600,
1056
+ enable_rate_limiting=True,
1057
+ global_rate_limit=100,
1058
+ enable_retries=True,
1059
+ max_retries=3
1060
+ ) as processor:
1061
+ # Use processor...
1062
+ results = await processor.process(llm_output)
1063
+
1064
+ # Automatic cleanup on exit
1065
+
1066
+ asyncio.run(main())
645
1067
  ```
646
1068
 
647
1069
  ### Advanced Production Features
@@ -653,7 +1075,7 @@ Beyond basic configuration, CHUK Tool Processor includes several advanced featur
653
1075
  Prevent cascading failures by automatically opening circuits for failing tools:
654
1076
 
655
1077
  ```python
656
- from chuk_tool_processor.core.processor import ToolProcessor
1078
+ from chuk_tool_processor import ToolProcessor
657
1079
 
658
1080
  processor = ToolProcessor(
659
1081
  enable_circuit_breaker=True,
@@ -695,8 +1117,8 @@ assert call1.idempotency_key == call2.idempotency_key
695
1117
 
696
1118
  # Used automatically by caching layer
697
1119
  processor = ToolProcessor(enable_caching=True)
698
- results1 = await processor.execute([call1]) # Executes
699
- results2 = await processor.execute([call2]) # Cache hit!
1120
+ results1 = await processor.process([call1]) # Executes
1121
+ results2 = await processor.process([call2]) # Cache hit!
700
1122
  ```
701
1123
 
702
1124
  **Benefits:**
@@ -704,6 +1126,8 @@ results2 = await processor.execute([call2]) # Cache hit!
704
1126
  - Deterministic cache keys
705
1127
  - No manual key management needed
706
1128
 
1129
+ **Cache scope:** In-memory per-process by default. Cache backend is pluggable—see [CONFIGURATION.md](docs/CONFIGURATION.md) for custom cache backends.
1130
+
707
1131
  #### Tool Schema Export
708
1132
 
709
1133
  Export tool definitions to multiple formats for LLM prompting:
@@ -750,7 +1174,9 @@ mcp_format = spec.to_mcp() # For MCP servers
750
1174
 
751
1175
  #### Machine-Readable Error Codes
752
1176
 
753
- Structured error handling with error codes for programmatic responses:
1177
+ Structured error handling with error codes for programmatic responses.
1178
+
1179
+ **Error Contract:** Every error includes a machine-readable code, human-readable message, and structured details:
754
1180
 
755
1181
  ```python
756
1182
  from chuk_tool_processor.core.exceptions import (
@@ -832,22 +1258,20 @@ result = await tool.execute(**llm_output)
832
1258
 
833
1259
  ## Advanced Topics
834
1260
 
835
- ### Using Subprocess Strategy
1261
+ ### Using Isolated Strategy
836
1262
 
837
- Use `SubprocessStrategy` when running untrusted, third-party, or potentially unsafe code that shouldn't share the same process as your main app.
1263
+ Use `IsolatedStrategy` when running untrusted, third-party, or potentially unsafe code that shouldn't share the same process as your main app.
838
1264
 
839
1265
  For isolation and safety when running untrusted code:
840
1266
 
841
1267
  ```python
842
1268
  import asyncio
843
- from chuk_tool_processor.core.processor import ToolProcessor
844
- from chuk_tool_processor.execution.strategies.subprocess_strategy import SubprocessStrategy
845
- from chuk_tool_processor.registry import get_default_registry
1269
+ from chuk_tool_processor import ToolProcessor, IsolatedStrategy, get_default_registry
846
1270
 
847
1271
  async def main():
848
1272
  registry = await get_default_registry()
849
1273
  processor = ToolProcessor(
850
- strategy=SubprocessStrategy(
1274
+ strategy=IsolatedStrategy(
851
1275
  registry=registry,
852
1276
  max_workers=4,
853
1277
  default_timeout=30.0
@@ -858,6 +1282,10 @@ async def main():
858
1282
  asyncio.run(main())
859
1283
  ```
860
1284
 
1285
+ > **Security & Isolation — Threat Model**
1286
+ >
1287
+ > Untrusted tool code runs in subprocesses; faults and crashes don't bring down your app. **Zero crash blast radius.** For hard CPU/RAM/network limits, run the processor inside a container with `--cpus`, `--memory`, and egress filtering. Secrets are never injected by default—pass them explicitly via tool arguments or scoped environment variables.
1288
+
861
1289
  ### Real-World MCP Examples
862
1290
 
863
1291
  #### Example 1: Notion Integration with OAuth
@@ -867,7 +1295,7 @@ Complete OAuth flow connecting to Notion's MCP server:
867
1295
  ```python
868
1296
  from chuk_tool_processor.mcp import setup_mcp_http_streamable
869
1297
 
870
- # After completing OAuth flow (see examples/notion_oauth.py for full flow)
1298
+ # After completing OAuth flow (see examples/04_mcp_integration/notion_oauth.py for full flow)
871
1299
  processor, manager = await setup_mcp_http_streamable(
872
1300
  servers=[{
873
1301
  "name": "notion",
@@ -888,6 +1316,9 @@ results = await processor.process(
888
1316
  )
889
1317
  ```
890
1318
 
1319
+ <details>
1320
+ <summary><strong>Click to expand more MCP examples (SQLite, Echo Server)</strong></summary>
1321
+
891
1322
  #### Example 2: Local SQLite Database Access
892
1323
 
893
1324
  Run SQLite MCP server locally for database operations:
@@ -959,10 +1390,15 @@ results = await processor.process(
959
1390
  )
960
1391
  ```
961
1392
 
962
- See `examples/notion_oauth.py`, `examples/stdio_sqlite.py`, and `examples/stdio_echo.py` for complete working implementations.
1393
+ </details>
1394
+
1395
+ See `examples/04_mcp_integration/notion_oauth.py`, `examples/04_mcp_integration/stdio_sqlite.py`, and `examples/04_mcp_integration/stdio_echo.py` for complete working implementations.
963
1396
 
964
1397
  #### OAuth Token Refresh
965
1398
 
1399
+ <details>
1400
+ <summary><strong>Click to expand OAuth token refresh guide</strong></summary>
1401
+
966
1402
  For MCP servers that use OAuth authentication, CHUK Tool Processor supports automatic token refresh when access tokens expire. This prevents your tools from failing due to expired tokens during long-running sessions.
967
1403
 
968
1404
  **How it works:**
@@ -1031,7 +1467,9 @@ processor, manager = await setup_mcp_sse(
1031
1467
  - Token refresh is attempted only once per tool call (no infinite retry loops)
1032
1468
  - After successful refresh, the updated headers are used for all subsequent calls
1033
1469
 
1034
- See `examples/notion_oauth.py` for a complete OAuth 2.1 implementation with PKCE and automatic token refresh.
1470
+ See `examples/04_mcp_integration/notion_oauth.py` for a complete OAuth 2.1 implementation with PKCE and automatic token refresh.
1471
+
1472
+ </details>
1035
1473
 
1036
1474
  ### Observability
1037
1475
 
@@ -1100,24 +1538,32 @@ asyncio.run(main())
1100
1538
 
1101
1539
  #### OpenTelemetry & Prometheus (Drop-in Observability)
1102
1540
 
1103
- **Why Telemetry Matters**: In production, you need to know *what* your tools are doing, *how long* they take, *when* they fail, and *why*. CHUK Tool Processor provides **enterprise-grade telemetry** that operations teams expect—with zero manual instrumentation.
1541
+ <details>
1542
+ <summary><strong>Click to expand complete observability guide</strong></summary>
1104
1543
 
1105
- **One function call. Full observability.**
1544
+ **3-Line Setup:**
1106
1545
 
1107
1546
  ```python
1108
1547
  from chuk_tool_processor.observability import setup_observability
1109
1548
 
1110
- # Enable everything
1111
1549
  setup_observability(
1112
1550
  service_name="my-tool-service",
1113
- enable_tracing=True, # OpenTelemetry distributed tracing
1114
- enable_metrics=True, # Prometheus metrics endpoint
1115
- metrics_port=9090 # HTTP endpoint at :9090/metrics
1551
+ enable_tracing=True, # OpenTelemetry traces
1552
+ enable_metrics=True, # Prometheus metrics at :9090/metrics
1553
+ metrics_port=9090
1116
1554
  )
1117
-
1118
- # Every tool execution is now automatically traced and metered!
1555
+ # That's it! Every tool execution is now automatically traced and metered.
1119
1556
  ```
1120
1557
 
1558
+ **What you get automatically:**
1559
+ - ✅ Distributed traces (Jaeger, Zipkin, any OTLP collector)
1560
+ - ✅ Prometheus metrics (error rate, latency P50/P95/P99, cache hit rate)
1561
+ - ✅ Circuit breaker state monitoring
1562
+ - ✅ Retry attempt tracking
1563
+ - ✅ Zero code changes to your tools
1564
+
1565
+ **Why Telemetry Matters**: In production, you need to know *what* your tools are doing, *how long* they take, *when* they fail, and *why*. CHUK Tool Processor provides **enterprise-grade telemetry** that operations teams expect—with zero manual instrumentation.
1566
+
1121
1567
  **What You Get (Automatically)**
1122
1568
 
1123
1569
  ✅ **Distributed Traces** - Understand exactly what happened in each tool call
@@ -1151,13 +1597,14 @@ pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp prom
1151
1597
  uv pip install chuk-tool-processor --group observability
1152
1598
  ```
1153
1599
 
1600
+ > **⚠️ SRE Note**: Observability packages are **optional**. If not installed, all observability calls are no-ops—your tools run normally without tracing/metrics. Zero crashes, zero warnings. Safe to deploy without observability dependencies.
1601
+
1154
1602
  **Quick Start: See Your Tools in Action**
1155
1603
 
1156
1604
  ```python
1157
1605
  import asyncio
1158
1606
  from chuk_tool_processor.observability import setup_observability
1159
- from chuk_tool_processor.core.processor import ToolProcessor
1160
- from chuk_tool_processor.registry import initialize, register_tool
1607
+ from chuk_tool_processor import ToolProcessor, initialize, register_tool
1161
1608
 
1162
1609
  @register_tool(name="weather_api")
1163
1610
  class WeatherTool:
@@ -1375,7 +1822,7 @@ export OTEL_EXPORTER_OTLP_ENDPOINT=http://datadog-agent:4317
1375
1822
  - Testing observability features
1376
1823
  - Environment variable configuration
1377
1824
 
1378
- 🎯 **Working Example**: See `examples/observability_demo.py` for a complete demonstration with retries, caching, and circuit breakers
1825
+ 🎯 **Working Example**: See `examples/02_production_features/observability_demo.py` for a complete demonstration with retries, caching, and circuit breakers
1379
1826
 
1380
1827
  **Benefits**
1381
1828
 
@@ -1386,6 +1833,8 @@ export OTEL_EXPORTER_OTLP_ENDPOINT=http://datadog-agent:4317
1386
1833
  ✅ **Optional** - Gracefully degrades if packages not installed
1387
1834
  ✅ **Zero-overhead** - No performance impact when disabled
1388
1835
 
1836
+ </details>
1837
+
1389
1838
  ### Error Handling
1390
1839
 
1391
1840
  ```python
@@ -1403,8 +1852,7 @@ for result in results:
1403
1852
 
1404
1853
  ```python
1405
1854
  import pytest
1406
- from chuk_tool_processor.core.processor import ToolProcessor
1407
- from chuk_tool_processor.registry import initialize
1855
+ from chuk_tool_processor import ToolProcessor, initialize
1408
1856
 
1409
1857
  @pytest.mark.asyncio
1410
1858
  async def test_calculator():
@@ -1418,6 +1866,40 @@ async def test_calculator():
1418
1866
  assert results[0].result["result"] == 8
1419
1867
  ```
1420
1868
 
1869
+ **Fake tool pattern for testing:**
1870
+
1871
+ ```python
1872
+ import pytest
1873
+ from chuk_tool_processor import ToolProcessor, register_tool, initialize
1874
+
1875
+ @register_tool(name="fake_tool")
1876
+ class FakeTool:
1877
+ """No-op tool for testing processor behavior."""
1878
+ call_count = 0
1879
+
1880
+ async def execute(self, **kwargs) -> dict:
1881
+ FakeTool.call_count += 1
1882
+ return {"called": True, "args": kwargs}
1883
+
1884
+ @pytest.mark.asyncio
1885
+ async def test_processor_with_fake_tool():
1886
+ await initialize()
1887
+ processor = ToolProcessor()
1888
+
1889
+ # Reset counter
1890
+ FakeTool.call_count = 0
1891
+
1892
+ # Execute fake tool
1893
+ results = await processor.process(
1894
+ '<tool name="fake_tool" args=\'{"test_arg": "value"}\'/>'
1895
+ )
1896
+
1897
+ # Assert behavior
1898
+ assert FakeTool.call_count == 1
1899
+ assert results[0].result["called"] is True
1900
+ assert results[0].result["args"]["test_arg"] == "value"
1901
+ ```
1902
+
1421
1903
  ## Configuration
1422
1904
 
1423
1905
  ### Timeout Configuration
@@ -1428,6 +1910,7 @@ CHUK Tool Processor uses a unified timeout configuration system that applies to
1428
1910
  from chuk_tool_processor.mcp.transport import TimeoutConfig
1429
1911
 
1430
1912
  # Create custom timeout configuration
1913
+ # (Defaults are: connect=30, operation=30, quick=5, shutdown=2)
1431
1914
  timeout_config = TimeoutConfig(
1432
1915
  connect=30.0, # Connection establishment, initialization, session discovery
1433
1916
  operation=30.0, # Normal operations (tool calls, listing tools/resources/prompts)
@@ -1555,7 +2038,7 @@ CHUK Tool Processor provides multiple layers of safety:
1555
2038
  | Concern | Protection | Configuration |
1556
2039
  |---------|------------|---------------|
1557
2040
  | **Timeouts** | Every tool has a timeout | `default_timeout=30.0` |
1558
- | **Process Isolation** | Run tools in separate processes | `strategy=SubprocessStrategy()` |
2041
+ | **Process Isolation** | Run tools in separate processes | `strategy=IsolatedStrategy()` |
1559
2042
  | **Rate Limiting** | Prevent abuse and API overuse | `enable_rate_limiting=True` |
1560
2043
  | **Input Validation** | Pydantic validation on arguments | Use `ValidatedTool` |
1561
2044
  | **Error Containment** | Failures don't crash the processor | Built-in exception handling |
@@ -1567,13 +2050,58 @@ CHUK Tool Processor provides multiple layers of safety:
1567
2050
  - **Resource Limits**: For hard CPU/memory caps, use OS-level controls (cgroups on Linux, Job Objects on Windows, or Docker resource limits).
1568
2051
  - **Secrets**: Never injected automatically. Pass secrets explicitly via tool arguments or environment variables, and prefer scoped env vars for subprocess tools to minimize exposure.
1569
2052
 
2053
+ #### OS-Level Hardening
2054
+
2055
+ For production deployments, add these hardening measures:
2056
+
2057
+ | Concern | Docker/Container Solution | Direct Example |
2058
+ |---------|--------------------------|----------------|
2059
+ | **CPU/RAM caps** | `--cpus`, `--memory` flags | `docker run --cpus="1.5" --memory="512m" myapp` |
2060
+ | **Network egress** | Deny-by-default with firewall rules | `--network=none` or custom network with egress filtering |
2061
+ | **Filesystem** | Read-only root + writable scratch | `--read-only --tmpfs /tmp:rw,size=100m` |
2062
+
2063
+ **Example: Run processor in locked-down container**
2064
+
2065
+ ```bash
2066
+ # Dockerfile
2067
+ FROM python:3.11-slim
2068
+ WORKDIR /app
2069
+ COPY requirements.txt .
2070
+ RUN pip install -r requirements.txt --no-cache-dir
2071
+ COPY . .
2072
+ USER nobody # Run as non-root
2073
+ CMD ["python", "app.py"]
2074
+
2075
+ # Run with resource limits and network restrictions
2076
+ docker run \
2077
+ --cpus="2" \
2078
+ --memory="1g" \
2079
+ --memory-swap="1g" \
2080
+ --read-only \
2081
+ --tmpfs /tmp:rw,size=200m,mode=1777 \
2082
+ --network=custom-net \
2083
+ --cap-drop=ALL \
2084
+ myapp:latest
2085
+ ```
2086
+
2087
+ **Network egress controls (deny-by-default)**
2088
+
2089
+ ```bash
2090
+ # Create restricted network with no internet access (for local-only tools)
2091
+ docker network create --internal restricted-net
2092
+
2093
+ # Or use iptables for per-tool CIDR allowlists
2094
+ iptables -A OUTPUT -d 10.0.0.0/8 -j ACCEPT # Allow private ranges
2095
+ iptables -A OUTPUT -d 172.16.0.0/12 -j ACCEPT
2096
+ iptables -A OUTPUT -d 192.168.0.0/16 -j ACCEPT
2097
+ iptables -A OUTPUT -j DROP # Deny everything else
2098
+ ```
2099
+
1570
2100
  Example security-focused setup for untrusted code:
1571
2101
 
1572
2102
  ```python
1573
2103
  import asyncio
1574
- from chuk_tool_processor.core.processor import ToolProcessor
1575
- from chuk_tool_processor.execution.strategies.subprocess_strategy import SubprocessStrategy
1576
- from chuk_tool_processor.registry import get_default_registry
2104
+ from chuk_tool_processor import ToolProcessor, IsolatedStrategy, get_default_registry
1577
2105
 
1578
2106
  async def create_secure_processor():
1579
2107
  # Maximum isolation for untrusted code
@@ -1581,7 +2109,7 @@ async def create_secure_processor():
1581
2109
  registry = await get_default_registry()
1582
2110
 
1583
2111
  processor = ToolProcessor(
1584
- strategy=SubprocessStrategy(
2112
+ strategy=IsolatedStrategy(
1585
2113
  registry=registry,
1586
2114
  max_workers=4,
1587
2115
  default_timeout=10.0
@@ -1599,6 +2127,25 @@ async def create_secure_processor():
1599
2127
  # - Use read-only filesystems where possible
1600
2128
  ```
1601
2129
 
2130
+ ## Design Goals & Non-Goals
2131
+
2132
+ **What CHUK Tool Processor does:**
2133
+ - ✅ Parse tool calls from any LLM format (XML, OpenAI, JSON)
2134
+ - ✅ Execute tools with production policies (timeouts, retries, rate limits, caching)
2135
+ - ✅ Isolate untrusted code in subprocesses
2136
+ - ✅ Connect to remote tool servers via MCP (HTTP/STDIO/SSE)
2137
+ - ✅ Provide composable execution layers (strategies + wrappers)
2138
+ - ✅ Export tool schemas for LLM prompting
2139
+
2140
+ **What CHUK Tool Processor explicitly does NOT do:**
2141
+ - ❌ Manage conversations or chat history
2142
+ - ❌ Provide prompt engineering or prompt templates
2143
+ - ❌ Bundle an LLM client (bring your own OpenAI/Anthropic/local)
2144
+ - ❌ Implement agent frameworks or chains
2145
+ - ❌ Make decisions about which tools to call
2146
+
2147
+ **Why this matters:** CHUK Tool Processor stays focused on reliable tool execution. It's a building block, not a framework. This makes it composable with any LLM application architecture.
2148
+
1602
2149
  ## Architecture Principles
1603
2150
 
1604
2151
  1. **Composability**: Stack strategies and wrappers like middleware
@@ -1612,26 +2159,26 @@ async def create_secure_processor():
1612
2159
  Check out the [`examples/`](examples/) directory for complete working examples:
1613
2160
 
1614
2161
  ### Getting Started
1615
- - **Quick start**: `examples/quickstart_demo.py` - Basic tool registration and execution
1616
- - **Execution strategies**: `examples/execution_strategies_demo.py` - InProcess vs Subprocess
1617
- - **Production wrappers**: `examples/wrappers_demo.py` - Caching, retries, rate limiting
1618
- - **Streaming tools**: `examples/streaming_demo.py` - Real-time incremental results
1619
- - **Observability**: `examples/observability_demo.py` - OpenTelemetry + Prometheus integration
2162
+ - **60-second hello**: `examples/01_getting_started/hello_tool.py` - Absolute minimal example (copy-paste-run)
2163
+ - **Quick start**: `examples/01_getting_started/quickstart_demo.py` - Basic tool registration and execution
2164
+ - **Execution strategies**: `examples/01_getting_started/execution_strategies_demo.py` - InProcess vs Subprocess
2165
+ - **Production wrappers**: `examples/02_production_features/wrappers_demo.py` - Caching, retries, rate limiting
2166
+ - **Streaming tools**: `examples/03_streaming/streaming_demo.py` - Real-time incremental results
2167
+ - **Streaming tool calls**: `examples/03_streaming/streaming_tool_calls_demo.py` - Handle partial tool calls from streaming LLMs
2168
+ - **Schema helper**: `examples/05_schema_and_types/schema_helper_demo.py` - Auto-generate schemas from typed tools (Pydantic → OpenAI/Anthropic/MCP)
2169
+ - **Observability**: `examples/02_production_features/observability_demo.py` - OpenTelemetry + Prometheus integration
1620
2170
 
1621
2171
  ### MCP Integration (Real-World)
1622
- - **Notion + OAuth**: `examples/notion_oauth.py` - Complete OAuth 2.1 flow with HTTP Streamable
2172
+ - **Notion + OAuth**: `examples/04_mcp_integration/notion_oauth.py` - Complete OAuth 2.1 flow with HTTP Streamable
1623
2173
  - Shows: Authorization Server discovery, client registration, PKCE flow, token exchange
1624
- - **SQLite Local**: `examples/stdio_sqlite.py` - Local database access via STDIO
2174
+ - **SQLite Local**: `examples/04_mcp_integration/stdio_sqlite.py` - Local database access via STDIO
1625
2175
  - Shows: Command/args passing, environment variables, file paths, initialization timeouts
1626
- - **Echo Server**: `examples/stdio_echo.py` - Minimal STDIO transport example
2176
+ - **Echo Server**: `examples/04_mcp_integration/stdio_echo.py` - Minimal STDIO transport example
1627
2177
  - Shows: Simplest possible MCP integration for testing
1628
- - **Atlassian + OAuth**: `examples/atlassian_sse.py` - OAuth with SSE transport (legacy)
2178
+ - **Atlassian + OAuth**: `examples/04_mcp_integration/atlassian_sse.py` - OAuth with SSE transport (legacy)
1629
2179
 
1630
2180
  ### Advanced MCP
1631
- - **HTTP Streamable**: `examples/mcp_http_streamable_example.py`
1632
- - **STDIO**: `examples/mcp_stdio_example.py`
1633
- - **SSE**: `examples/mcp_sse_example.py`
1634
- - **Plugin system**: `examples/plugins_builtins_demo.py`, `examples/plugins_custom_parser_demo.py`
2181
+ - **Plugin system**: `examples/06_plugins/plugins_builtins_demo.py`, `examples/06_plugins/plugins_custom_parser_demo.py`
1635
2182
 
1636
2183
  ## FAQ
1637
2184
 
@@ -1656,18 +2203,20 @@ A: Use pytest with `@pytest.mark.asyncio`. See [Testing Tools](#testing-tools) f
1656
2203
  **Q: Does this work with streaming LLM responses?**
1657
2204
  A: Yes—as tool calls appear in the stream, extract and process them. The processor handles partial/incremental tool call lists.
1658
2205
 
1659
- **Q: What's the difference between InProcess and Subprocess strategies?**
1660
- A: InProcess is faster (same process), Subprocess is safer (isolated process). Use InProcess for trusted code, Subprocess for untrusted.
2206
+ **Q: What's the difference between InProcess and Isolated strategies?**
2207
+ A: InProcess is faster (same process), Isolated is safer (separate subprocess). Use InProcess for trusted code, Isolated for untrusted.
1661
2208
 
1662
2209
  ## Comparison with Other Tools
1663
2210
 
1664
2211
  | Feature | chuk-tool-processor | LangChain Tools | OpenAI Tools | MCP SDK |
1665
2212
  |---------|-------------------|-----------------|--------------|---------|
1666
2213
  | **Async-native** | ✅ | ⚠️ Partial | ✅ | ✅ |
1667
- | **Process isolation** | ✅ SubprocessStrategy | ❌ | ❌ | ⚠️ |
2214
+ | **Process isolation** | ✅ IsolatedStrategy | ❌ | ❌ | ⚠️ |
1668
2215
  | **Built-in retries** | ✅ | ❌ † | ❌ | ❌ |
1669
2216
  | **Rate limiting** | ✅ | ❌ † | ⚠️ ‡ | ❌ |
1670
2217
  | **Caching** | ✅ | ⚠️ † | ❌ ‡ | ❌ |
2218
+ | **Idempotency & de-dup** | ✅ SHA256 keys | ❌ | ❌ | ❌ |
2219
+ | **Per-tool policies** | ✅ (timeouts/retries/limits) | ⚠️ | ❌ | ❌ |
1671
2220
  | **Multiple parsers** | ✅ (XML, OpenAI, JSON) | ⚠️ | ✅ | ✅ |
1672
2221
  | **Streaming tools** | ✅ | ⚠️ | ⚠️ | ✅ |
1673
2222
  | **MCP integration** | ✅ All transports | ❌ | ❌ | ✅ (protocol only) |
@@ -1696,6 +2245,73 @@ A: InProcess is faster (same process), Subprocess is safer (isolated process). U
1696
2245
  - Use directly if you need protocol-level control
1697
2246
  - Use chuk-tool-processor if you want high-level tool execution
1698
2247
 
2248
+ ## Development & Publishing
2249
+
2250
+ ### For Contributors
2251
+
2252
+ Development setup:
2253
+
2254
+ ```bash
2255
+ # Clone repository
2256
+ git clone https://github.com/chrishayuk/chuk-tool-processor.git
2257
+ cd chuk-tool-processor
2258
+
2259
+ # Install development dependencies
2260
+ uv sync --dev
2261
+
2262
+ # Run tests
2263
+ make test
2264
+
2265
+ # Run all quality checks
2266
+ make check
2267
+ ```
2268
+
2269
+ ### For Maintainers: Publishing Releases
2270
+
2271
+ The project uses **fully automated CI/CD** for releases. Publishing is as simple as:
2272
+
2273
+ ```bash
2274
+ # 1. Bump version
2275
+ make bump-patch # or bump-minor, bump-major
2276
+
2277
+ # 2. Commit version change
2278
+ git add pyproject.toml
2279
+ git commit -m "version X.Y.Z"
2280
+ git push
2281
+
2282
+ # 3. Create release (automated)
2283
+ make publish
2284
+ ```
2285
+
2286
+ This will:
2287
+ - Create and push a git tag
2288
+ - Trigger GitHub Actions to create a release with auto-generated changelog
2289
+ - Run tests across all platforms and Python versions
2290
+ - Build and publish to PyPI automatically
2291
+
2292
+ For detailed release documentation, see:
2293
+ - **[RELEASING.md](RELEASING.md)** - Complete release process guide
2294
+ - **[docs/CI-CD.md](docs/CI-CD.md)** - Full CI/CD pipeline documentation
2295
+
2296
+ ## Stability & Versioning
2297
+
2298
+ CHUK Tool Processor follows **[Semantic Versioning 2.0.0](https://semver.org/)** for predictable upgrades:
2299
+
2300
+ * **Breaking changes** = **major** version bump (e.g., 1.x → 2.0)
2301
+ * **New features** (backward-compatible) = **minor** version bump (e.g., 1.2 → 1.3)
2302
+ * **Bug fixes** (backward-compatible) = **patch** version bump (e.g., 1.2.3 → 1.2.4)
2303
+
2304
+ **Public API surface**: Everything exported via the package root (`from chuk_tool_processor import ...`) is considered public API and follows semver guarantees.
2305
+
2306
+ **Deprecation policy**: Deprecated APIs will:
2307
+ 1. Log a warning for **one minor release**
2308
+ 2. Be removed in the **next major release**
2309
+
2310
+ **Upgrading safely**:
2311
+ * Patch and minor updates are **safe to deploy** without code changes
2312
+ * Major updates may require migration—see release notes
2313
+ * Pin to `chuk-tool-processor~=1.2` for minor updates only, or `chuk-tool-processor==1.2.3` for exact versions
2314
+
1699
2315
  ## Contributing & Support
1700
2316
 
1701
2317
  - **GitHub**: [chrishayuk/chuk-tool-processor](https://github.com/chrishayuk/chuk-tool-processor)