# DeepTutor Best Practices: Optimizing the Two‑Layer Plugin Architecture

> Implement DeepTutor best practices for its two layer plugin architecture. Minimize tool sets, persist session IDs, and handle async events for efficient production deployments.

- Repository: [✨Data Intelligence Lab@HKU✨/DeepTutor](https://github.com/HKUDS/DeepTutor)
- Tags: best-practices
- Published: 2026-04-08

---

**DeepTutor's architecture separates single‑function tools from multi‑step capabilities, requiring developers to minimize enabled tool sets, persist session IDs, and handle asynchronous streaming events for production deployments.**

The HKUDS/DeepTutor repository implements a modular tutoring system built on a strict two‑layer plugin architecture that distinguishes between lightweight utilities and complex agent pipelines. Mastering these DeepTutor best practices ensures you leverage the `ChatOrchestrator` routing system and `UnifiedContext` data contract efficiently while minimizing token usage and latency. The platform streams all intermediate stages through an async `StreamBus`, making architectural awareness critical for responsive integrations.

## Understanding the Core Two‑Layer Architecture

DeepTutor organizes functionality into distinct layers managed by dedicated registries. This separation dictates how you should structure requests and extensions.

### The Entry Point and Data Flow

All interactions enter through `ChatOrchestrator` in [`deeptutor/runtime/orchestrator.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/runtime/orchestrator.py). This class routes incoming requests by examining `context.active_capability` (defaulting to **chat**) and retrieving the matching pipeline from `CapabilityRegistry`.

The execution flow follows this strict sequence:

1. Input is wrapped in a `UnifiedContext` (defined in [`deeptutor/core/context.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/core/context.py)) containing session metadata, enabled tools, knowledge bases, and configuration overrides.
2. `ChatOrchestrator.handle()` dispatches to the selected capability, instantiating a fresh `StreamBus` from [`deeptutor/core/stream_bus.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/core/stream_bus.py).
3. The capability invokes specific tools via `ToolRegistry` ([`deeptutor/runtime/registry/tool_registry.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/runtime/registry/tool_registry.py)), which provides OpenAI‑compatible schemas for each utility.
4. Tools return `ToolResult` objects, while the capability streams progress through `StreamEvent` instances.
5. Upon completion, the orchestrator emits a `CAPABILITY_COMPLETE` event on the global `EventBus`.

### Tools vs. Capabilities

Understanding the granularity difference prevents architectural misuse:

- **Tools** (Level 1): Single‑function utilities registered in `ToolRegistry`. Examples include `rag`, `code_execution`, or `web_search`. These perform isolated, stateless operations.
- **Capabilities** (Level 2): Multi‑step agent pipelines registered in `CapabilityRegistry` ([`deeptutor/runtime/registry/capability_registry.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/runtime/registry/capability_registry.py)). These orchestrate sequences of tool calls to accomplish complex workflows like `deep_solve` or `deep_question`.

Use **tools** for lightweight, on‑demand actions. Use **capabilities** for higher‑level reasoning workflows that require coordinated tool invocation.

## Optimizing Tool Selection and Configuration

Efficient DeepTutor usage requires strict control over which components load for each request.

### Minimize Enabled Tools

The orchestrator loads only tools specified in `context.enabled_tools` (or via the CLI `-t` flag). Loading unnecessary tools increases latency and token consumption.

```bash

# Efficient: Load only RAG for a knowledge retrieval task

deeptutor run chat "Explain quantum entanglement" -t rag --kb physics-kb

# Inefficient: Loading reasoning tools when not required

deeptutor run chat "Explain quantum entanglement" -t rag,reason,code_execution --kb physics-kb

```

### Leverage Knowledge Bases and Session Persistence

Attach knowledge bases (`--kb <name>`) to every turn requiring domain‑specific information. For multi‑turn interactions, ensure the `session_id` in `UnifiedContext` persists across requests so the memory module can accumulate a coherent learner profile.

In the interactive REPL (`deeptutor chat`), the system handles session continuity automatically:

```bash
deeptutor chat

```

```

/cap deep_solve          # Switch to multi-step reasoning capability

/tool rag,reason         # Enable specific tools for this session

/kb my-kb                # Set active knowledge base

/config temperature=0.7  # Override model parameters for current turn

```

### Use Configuration Overrides

Fine‑tune behavior per request using `context.config_overrides` or the `--config` CLI argument. This allows dynamic adjustment of temperature, max tokens, or system prompts without modifying global settings.

## Implementation Patterns for Different Interfaces

DeepTutor exposes three primary interfaces, each suited to specific operational contexts.

### CLI One‑Shot Execution

Use for atomic tasks where session continuity is unnecessary. The `deeptutor run` command (entry point in [`deeptutor_cli/main.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor_cli/main.py)) accepts capability names, tool lists, and configuration overrides as arguments.

```bash

# Deep problem solving with specific reasoning tools

deeptutor run deep_solve "Prove the fundamental theorem of calculus" -t reason

# Quiz generation with controlled output

deeptutor run deep_question "Linear algebra basics" --config num_questions=5 --kb math-kb

```

### Interactive REPL Sessions

The REPL maintains persistent `UnifiedContext` and `session_id` across commands, making it ideal for iterative tutoring scenarios. Switch capabilities and toolsets mid‑session using slash commands.

### Python SDK Integration

For embedding within larger agent systems, instantiate `ChatOrchestrator` directly and consume the async generator returned by `handle()`. This is the pattern used by the WebSocket endpoint in [`deeptutor/api/routers/unified_ws.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/api/routers/unified_ws.py).

```python
from deeptutor.runtime.orchestrator import ChatOrchestrator
from deeptutor.core.context import UnifiedContext

async def tutoring_query(question: str):
    ctx = UnifiedContext(
        user_message=question,
        enabled_tools=["rag", "code_execution"],
        knowledge_bases=["my-kb"],
        language="en",
        metadata={"origin": "auto_agent"}  # Trace request provenance

    )
    
    orchestrator = ChatOrchestrator()
    async for event in orchestrator.handle(ctx):
        if event.type == "content":
            print(event.payload)  # Stream partial results

        elif event.type == "result":
            print("Final:", event.payload)  # Structured completion

```

Handle `content` events for responsive UI feedback; reserve `result` events for final structured payloads.

## Extending DeepTutor with Custom Plugins

The system auto‑discovers extensions via [`deeptutor/plugins/loader.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/plugins/loader.py). Create custom tools by implementing the `BaseTool` protocol from [`deeptutor/core/tool_protocol.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/core/tool_protocol.py).

```python

# custom_tools/math_tool.py

from deeptutor.core.tool_protocol import BaseTool, ToolResult, ToolDefinition

class SquareTool(BaseTool):
    name = "square"
    description = "Returns the square of a number"
    
    async def execute(self, number: int) -> ToolResult:
        return ToolResult(output=str(number ** 2))
    
    def get_definition(self) -> ToolDefinition:
        return ToolDefinition(
            name=self.name,
            description=self.description,
            parameters={
                "type": "object",
                "properties": {"number": {"type": "integer"}},
                "required": ["number"]
            }
        )

```

Register at startup:

```python
from deeptutor.runtime.registry.tool_registry import get_tool_registry
from custom_tools.math_tool import SquareTool

get_tool_registry().register(SquareTool())

```

**Critical requirements:** Keep tool side effects pure and outputs JSON‑serializable to ensure safe transmission through `StreamBus`.

## Summary

- **Align granularity with needs**: Use capabilities for complex workflows, tools for single operations.
- **Minimize tool loading**: Specify only required tools via `-t` or `enabled_tools` to reduce latency.
- **Persist sessions**: Maintain consistent `session_id` values across related interactions for coherent memory profiles.
- **Stream events properly**: Handle `content` events for UX responsiveness and `result` events for final data.
- **Attach knowledge bases**: Always include `--kb` when domain context is required.
- **Extend via plugins**: Implement `BaseTool` for custom utilities; the loader discovers them automatically from the plugins directory.

## Frequently Asked Questions

### What is the difference between a tool and a capability in DeepTutor?

A **tool** is a single‑function utility (like `rag` or `code_execution`) registered in `ToolRegistry` that performs an isolated task and returns a `ToolResult`. A **capability** is a multi‑step pipeline (like `deep_solve`) registered in `CapabilityRegistry` that orchestrates multiple tool calls to complete complex objectives. Tools are the building blocks; capabilities are the workflows.

### How do I maintain context across multiple user interactions?

Persist the `session_id` field within `UnifiedContext` across sequential calls to `ChatOrchestrator.handle()`. The REPL interface handles this automatically, but when using the Python SDK, you must manually reuse the same session identifier to allow the memory module to build a continuous learner profile.

### Why should I limit the number of enabled tools per request?

Each enabled tool consumes tokens for its schema description and adds initialization overhead. The orchestrator in [`runtime/orchestrator.py`](https://github.com/HKUDS/DeepTutor/blob/main/runtime/orchestrator.py) only loads tools specified in `enabled_tools`, so keeping this list minimal reduces both latency and API costs while preventing context window saturation.

### How do I add custom functionality to DeepTutor?

Create a class inheriting from `BaseTool` in [`deeptutor/core/tool_protocol.py`](https://github.com/HKUDS/DeepTutor/blob/main/deeptutor/core/tool_protocol.py), implement `execute()` and `get_definition()`, and register it with `ToolRegistry` at startup. For complex workflows, package capabilities with a [`manifest.yaml`](https://github.com/HKUDS/DeepTutor/blob/main/manifest.yaml) file; the [`plugins/loader.py`](https://github.com/HKUDS/DeepTutor/blob/main/plugins/loader.py) module auto‑discovers these extensions at runtime.