DeepTutor System Architecture: An Agent-Native Platform with Two-Layer Registry

DeepTutor implements a modular, agent-native architecture that decouples what the system can do from how it executes tasks, using a central ChatOrchestrator to route requests through separate registries for lightweight tools and multi-step capabilities.

The DeepTutor system architecture, as defined in the HKUDS/DeepTutor repository, centers on the ChatOrchestrator class in deeptutor/runtime/orchestrator.py. This design creates a clean separation between single-function utilities and complex reasoning pipelines, enabling developers to extend functionality without modifying core routing logic.

Core Architectural Components

The ChatOrchestrator

The ChatOrchestrator serves as the central router for all incoming requests. Regardless of whether a request originates from the CLI, WebSocket, or Python SDK, the orchestrator creates a UnifiedContext and streams events back to the caller. According to the source code in deeptutor/runtime/orchestrator.py, the orchestrator handles capability selection, tool injection, and event coordination through a unified async interface.

UnifiedContext Dataclass

Every request in DeepTutor travels through the system as an immutable UnifiedContext object defined in deeptutor/core/context.py. This dataclass encapsulates session state, including the user message, conversation history, enabled tools, knowledge base references, and the active capability. By passing a single context object through every layer, DeepTutor maintains consistent state without global variables.

The Two-Layer Registry System

DeepTutor separates functionality into two distinct registry layers to isolate simple utilities from complex workflows.

Level 1: Tool Registry

The ToolRegistry (deeptutor/runtime/registry/tool_registry.py) manages Level 1 components: lightweight, single-function tools such as rag, web_search, and code_execution. These tools inherit from BaseTool and declare schemas via ToolSchema. The registry handles discovery, registration, and automatic OpenAI-compatible schema generation for LLM function calling.

Level 2: Capability Registry

The CapabilityRegistry (deeptutor/runtime/registry/capability_registry.py) holds Level 2 multi-step pipelines like chat, deep_solve, and deep_question. Capabilities orchestrate sequences of tool calls and reasoning steps. The registry exposes these high-level behaviors to the orchestrator, which selects the appropriate capability based on context.active_capability (defaulting to chat when unspecified).

Execution Flow and Data Pipeline

The request lifecycle follows a strict pipeline from entry to completion:

  1. Context Creation: Entry points (CLI, WebSocket, SDK) instantiate a UnifiedContext with session metadata and user input.
  2. Capability Resolution: The orchestrator queries the CapabilityRegistry to load the requested capability.
  3. Tool Injection: The capability dynamically requests tools from the ToolRegistry as needed during execution.
  4. Streaming Output: The capability publishes StreamEvent objects to the StreamBus (deeptutor/core/stream_bus.py), which the orchestrator yields to the client in real-time.
  5. Completion Signaling: Upon finishing, the orchestrator posts a CAPABILITY_COMPLETE event to the global EventBus (deeptutor/events/event_bus.py) for analytics and logging.

Entry Points and Integration

DeepTutor exposes three primary entry points that uniformly invoke the orchestrator:

  • CLI: Implemented in deeptutor_cli/main.py using Typer for command-line interactions.
  • WebSocket: The /api/v1/ws endpoint defined in deeptutor/api/routers/unified_ws.py handles browser-based streaming connections.
  • Python SDK: Direct programmatic access via the ChatOrchestrator class for embedded applications.

All entry points transform their specific input formats into a UnifiedContext before calling ChatOrchestrator.handle().

Streaming and Event Architecture

Real-time communication relies on the StreamBus and EventBus components. Capabilities push StreamEvent instances containing content chunks, error messages, or termination signals (DONE) to the StreamBus. The orchestrator asynchronously forwards these events to the client. Simultaneously, the EventBus publishes lifecycle events like CAPABILITY_COMPLETE to decouple analytics from core execution.

Extensibility via Plugins

The Playground system in deeptutor/plugins/ allows third-party extensions. The plugin loader (deeptutor/plugins/loader.py) discovers additional capabilities at runtime, exposing them alongside built-in features like deep_research. This architecture ensures the core system remains lean while supporting experimental functionality through optional extensions.

Practical Implementation Examples

Invoking Capabilities via CLI

You can execute specific capabilities directly from the terminal using the Typer-based CLI:


# Install CLI dependencies

pip install -r requirements/cli.txt && pip install -e .

# Invoke the deep_solve capability with RAG tool enabled

deeptutor run deep_solve "Solve x^2 = 4" -t rag --kb my-kb

As documented in AGENTS.md, this command routes through the orchestrator to execute a planning-reasoning-writing pipeline.

Programmatic Usage with Python SDK

For embedded applications, instantiate the orchestrator directly and stream events:

from deeptutor.runtime.orchestrator import ChatOrchestrator
from deeptutor.core.context import UnifiedContext
import asyncio

async def demo():
    ctx = UnifiedContext(
        user_message="Explain the Fourier transform",
        enabled_tools=["rag"],
        knowledge_bases=["docs"],
        active_capability="chat",
    )
    
    orchestrator = ChatOrchestrator()
    
    async for event in orchestrator.handle(ctx):
        if event.type == "content":
            print(event.content)
        elif event.type == "error":
            print("Error:", event.content)

asyncio.run(demo())

This example demonstrates how ChatOrchestrator.handle() processes the context and yields StreamEvent objects defined in deeptutor/core/stream.py.

Registering Custom Tools

To add functionality, create a tool class and register it in the ToolRegistry:

from deeptutor.core.tool_protocol import BaseTool, ToolSchema

class MyTool(BaseTool):
    schema = ToolSchema(
        name="my_tool",
        description="Demonstrates a custom tool",
        parameters={},
    )

    async def run(self, context, bus):
        await bus.content("MyTool executed!", source=self.name)

After placing this in deeptutor/tools/builtin/my_tool.py and registering it in deeptutor/runtime/registry/tool_registry.py, the tool becomes selectable via context.enabled_tools.

Summary

  • DeepTutor employs an agent-native architecture separating tools (Level 1) from capabilities (Level 2) through distinct registries.
  • The ChatOrchestrator in deeptutor/runtime/orchestrator.py serves as the central dispatcher, managing execution flow and streaming responses.
  • UnifiedContext (deeptutor/core/context.py) provides immutable, comprehensive session state that travels through every processing layer.
  • Real-time communication uses the StreamBus for client-facing events and the EventBus for system-level analytics.
  • The system supports CLI, WebSocket, and SDK entry points uniformly, with plugin-based extensibility via deeptutor/plugins/loader.py.

Frequently Asked Questions

What is the difference between tools and capabilities in DeepTutor?

Tools are Level 1 single-function utilities (e.g., rag, web_search) managed by deeptutor/runtime/registry/tool_registry.py, while capabilities are Level 2 multi-step pipelines (e.g., deep_solve, chat) that orchestrate tool sequences from deeptutor/runtime/registry/capability_registry.py. Capabilities define how to solve problems; tools provide specific functions.

How does DeepTutor handle real-time streaming responses?

Capabilities emit StreamEvent objects to the StreamBus (deeptutor/core/stream_bus.py) during execution. The ChatOrchestrator asynchronously yields these events to the client, enabling real-time content delivery. Events include types for content chunks, errors, and completion signals.

Where are plugins loaded in the DeepTutor architecture?

The Plugin Loader in deeptutor/plugins/loader.py scans the deeptutor/plugins/ directory at runtime to discover and register third-party capabilities. These extensions integrate seamlessly with the CapabilityRegistry, appearing alongside built-in features like deep_research without requiring core code modifications.

What is the purpose of the UnifiedContext dataclass?

UnifiedContext (deeptutor/core/context.py) is an immutable dataclass that encapsulates all request state—including session ID, user message, enabled tools, and knowledge bases—ensuring consistent data propagation through every layer of the architecture. This design eliminates global state and enables thread-safe, asynchronous processing across the orchestrator, tools, and capabilities.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →