Skip to main content

Package architecture

TEN packages are the fundamental building blocks of the TEN framework, providing a standardized structure for organizing applications, extensions, and system components. This guide explains the TEN package architecture, lifecycle management, and deployment strategies.

Understanding TEN packages is essential for effective development with the framework. Packages define how code, configuration, and dependencies are organized, while the extension lifecycle provides a predictable pattern for component initialization, operation, and cleanup across different programming languages.

Whether you're building standalone extensions, complete applications, or contributing to the framework itself, this guide covers the package structure and lifecycle concepts you need to develop robust TEN-based solutions.

Package structure and types

Common package structure

Every TEN package follows a standardized directory structure that ensures consistency across different package types and programming languages:


_17
.
_17
├── bin/
_17
├── src/
_17
├── manifest.json
_17
├── manifest-lock.json
_17
├── property.json
_17
└── ten_packages/
_17
├── extension/
_17
│ ├── <extension_foo>/
_17
│ └── <extension_bar>/
_17
├── extension_group/
_17
│ └── <extension_group_x>/
_17
├── protocol/
_17
│ └── <protocol_a>/
_17
└── system/
_17
├── <system_package_1>/
_17
└── <system_package_2>/

Language-specific files:

  • C++: BUILD.gn file for the ten_gn build system
  • Go: go.mod and go.sum files for dependency management
  • Python: requirements.txt file for package dependencies

Package types

TEN framework packages are organized into distinct types, each serving specific purposes in the application architecture:

  • App: Complete TEN applications that can run as standalone processes or embedded threads. Apps serve as containers for extensions and extension groups.

  • Extension: Individual functional components that implement specific business logic or capabilities. Extensions are the primary building blocks for TEN applications.

  • Extension group: Collections of related extensions that work together as a cohesive unit. Extension groups provide organizational structure and shared resources.

  • Protocol: Communication protocols that define how extensions interact and exchange messages. Protocols ensure consistent data flow between components.

  • System: Core framework components and runtime packages that provide essential TEN functionality. System packages include the runtime engine and fundamental services.

Each package type has specific responsibilities and usage patterns within the TEN ecosystem, allowing developers to build modular, scalable applications through composition.

App packages

Apps are containers that orchestrate extensions and extension groups to create complete TEN applications. Apps provide the runtime environment and can be deployed in multiple ways to suit different architectural needs.

Deployment modes

TEN apps have two deployment modes:

  • Process mode: Apps run as standalone processes, typically deployed in Docker containers for cloud environments. This mode provides complete isolation and is ideal for microservices architectures.

  • Thread mode: Apps run as embedded threads within existing applications. This mode enables incremental adoption of TEN without requiring complete application rewrites.

Run TEN as a process or a thread

Different modes of app running

App structure

TEN apps maintain consistent folder structures across all programming languages, ensuring uniformity and predictability regardless of implementation language:

TEN package folder structure

This consistency allows developers to work with TEN apps using familiar patterns, whether building in C++, Go, Python, or other supported languages.

Extension packages

Extension packages contain individual components that implement specific functionality within TEN applications. Extensions are the core building blocks that handle business logic, data processing, and external integrations.

Extension lifecycle

Extensions follow a predictable five-stage lifecycle that ensures proper initialization, operation, and cleanup. Each stage provides specific capabilities for message handling and inter-extension communication.

StagePurposeCompletion Function
on_configureSet initial property valueson_configure_done()
on_initInitialize extension resourceson_init_done()
on_startBegin normal operationon_start_done()
on_stopPrepare for shutdownon_stop_done()
on_deinitClean up resourceson_deinit_done()

You can send messages and receive command results at any lifecycle stage, enabling flexible inter-extension dependencies and communication patterns. You must call the corresponding completion function to complete each stage and advance to the next. The following diagram illustrates the extension lifecycle flow, showing how each stage transitions to its completion function and then to the next stage:

on_configure

Use this stage to set the initial values of your extension's properties. This allows you to use the get_property API from ten_env in other lifecycle stages to retrieve properties set during on_configure. Call on_configure_done() to mark the end of this stage.


_11
void on_configure(ten::ten_env_t &ten_env) override {
_11
// Set the initial values of the extension's properties.
_11
ten_env.init_property_from_json(
_11
R"({
_11
"ten": {
_11
"uri": "msgpack://127.0.0.1:8001/",
_11
"log_level": 2
_11
}
_11
})", nullptr);
_11
ten_env.on_configure_done();
_11
}

on_init

Use this stage to initialize your extension. The TEN runtime queues all incoming messages until you call on_init_done(), then delivers the queued messages once your extension is ready.

Exception: Command results from commands you send yourself are delivered immediately, since you must be prepared to handle results from your own commands.


_4
void on_init(ten::ten_env_t &ten_env) override {
_4
// Initialize extension resources
_4
ten_env.on_init_done();
_4
}

on_start

Use this stage to finalize startup operations before your extension begins receiving messages from other extensions. The TEN runtime starts delivering messages from other extensions after you call on_start_done().


_4
void on_start(ten::ten_env_t &ten_env) override {
_4
// Perform startup operations
_4
ten_env.on_start_done();
_4
}

on_stop

Use this stage to perform cleanup operations when your extension needs to stop, such as when the containing app is terminating. Call on_stop_done() to complete the shutdown process.


_4
void on_stop(ten::ten_env_t &ten_env) override {
_4
// Perform cleanup operations
_4
ten_env.on_stop_done();
_4
}

on_deinit

Use this stage for final resource cleanup. The TEN runtime will not deliver messages from other extensions during this stage since your extension's resources may no longer be fully available. Call on_deinit_done() to complete the lifecycle.


_4
void on_deinit(ten::ten_env_t &ten_env) override {
_4
// Final cleanup
_4
ten_env.on_deinit_done();
_4
}

Extension coordination

Extensions operate independently through their lifecycle stages with no built-in synchronization. Each extension progresses through its own lifecycle stages without waiting for other extensions. You must explicitly implement any dependencies between extensions.

To coordinate between extensions, use message passing during lifecycle stages. For example, if extension A needs extension B to complete initialization first:

  1. Extension A sends a command to extension B during its on_init stage
  2. Extension B completes initialization and responds to the command
  3. Extension A receives the response and calls on_init_done()

You can apply the same message-passing approach to coordinate extensions at any lifecycle stage.

Runtime interfaces

Extensions interact with the TEN runtime through three primary interfaces:

  • Lifecycle callbacks: Handle extension state transitions: on_init, on_deinit, on_start, on_stop, and on_configure.

  • Message receivers: Process incoming messages: on_cmd, on_data, on_audio_frame, and on_video_frame.

  • Message senders: Send outgoing messages: send_cmd, send_data, send_audio_frame, and send_video_frame.

These interfaces provide complete control over your extension's lifecycle and communication with other components in the TEN application.

Message handling by lifecycle stage

Each lifecycle stage has specific message handling capabilities that determine what your extension can send and receive:

  • on_configure to on_configure_done: Set up properties only. No message handling capabilities.

  • on_init to on_init_done: You can send messages and receive results from your own commands, but cannot receive messages from other extensions.

  • on_start to on_start_done: You can send messages and receive results from your own commands, but cannot receive messages from other extensions. Since properties are configured, you can perform actions that depend on these settings.

  • Normal operation (after on_start_done until on_stop): Full message handling capabilities. You can send and receive all types of messages and their results.

  • on_stop to on_stop_done: You can send messages and receive results from your own commands, but cannot receive messages from other extensions.

  • on_deinit to on_deinit_done: Similar to on_init. You can send messages and receive results from your own commands, but cannot receive messages from other extensions.

Multi-language support

You can implement extensions in C++, Go, and Python using the same conceptual approach across all languages. Once you learn extension development in one language, the concepts transfer easily to other supported languages.

Asynchronous message processing

Extensions process messages asynchronously, giving you flexibility in how and when to handle incoming data. When the TEN runtime delivers a message through callbacks like on_cmd, on_data, on_audio_frame, or on_video_frame, you can:

  • Process the message immediately within the callback
  • Delegate processing to other threads for parallel execution
  • Forward the message to other processes or machines for distributed processing

This asynchronous design enables full utilization of multi-core and distributed computing resources without blocking the TEN runtime.

Asynchronous Message Processing

After processing completes, send results back to the TEN runtime using functions like send_cmd, send_data, send_audio_frame, or send_video_frame. You can send results at any time after processing finishes; the callback functions don't need to wait for results before returning.

Python async extensions

TEN's Python async extensions provide a powerful way to handle long-running tasks asynchronously. By integrating Python's asyncio framework, these extensions ensure that operations such as network calls or file handling are efficient and non-blocking.

To wrap existing Python code that uses asyncio into a TEN extension, use the Python async extension. A Python async extension is structured as follows:


_32
import asyncio
_32
from ten_runtime import AsyncExtension, AsyncTenEnv
_32
_32
class DefaultAsyncExtension(AsyncExtension):
_32
async def on_configure(self, ten_env: AsyncTenEnv) -> None:
_32
# Mock async operation, e.g. network, file I/O.
_32
await asyncio.sleep(0.5)
_32
_32
async def on_init(self, ten_env: AsyncTenEnv) -> None:
_32
# Mock async operation, e.g. network, file I/O.
_32
await asyncio.sleep(0.5)
_32
_32
async def on_start(self, ten_env: AsyncTenEnv) -> None:
_32
# Mock async operation, e.g. network, file I/O.
_32
await asyncio.sleep(0.5)
_32
_32
async def on_deinit(self, ten_env: AsyncTenEnv) -> None:
_32
# Mock async operation, e.g. network, file I/O.
_32
await asyncio.sleep(0.5)
_32
_32
async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None:
_32
cmd_json = cmd.to_json()
_32
ten_env.log_debug(f"DefaultAsyncExtension on_cmd: {cmd_json}")
_32
_32
# Mock async operation, e.g. network, file I/O.
_32
await asyncio.sleep(0.5)
_32
_32
# Send a new command to other extensions and wait for the result. The
_32
# result will be returned to the original sender.
_32
new_cmd = Cmd.create("hello")
_32
cmd_result = await ten_env.send_cmd(new_cmd)
_32
ten_env.return_result(cmd_result, cmd)

Each method simulates a delay using await asyncio.sleep().

Async loop for event handling

To create an async event loop to handle continuous event processing:

  • Create a queue using asyncio.Queue
  • Create an async task for event handling in on_start
  • Signal shutdown by putting None in the queue during on_stop

_28
import asyncio
_28
from ten_runtime import AsyncExtension, AsyncTenEnv
_28
_28
class DefaultAsyncExtension(AsyncExtension):
_28
def __init__(self):
_28
super().__init__()
_28
self.queue = asyncio.Queue()
_28
self.loop = None
_28
_28
async def on_start(self, ten_env: AsyncTenEnv) -> None:
_28
self.loop = asyncio.get_event_loop()
_28
self.loop.create_task(self._consume())
_28
_28
async def on_stop(self, ten_env: AsyncTenEnv) -> None:
_28
await self.queue.put(None)
_28
_28
async def _consume(self) -> None:
_28
while True:
_28
try:
_28
value = await self.queue.get()
_28
if value is None:
_28
self.ten_env.log_info("async loop exit")
_28
break
_28
_28
# Code for processing values retrieved from the queue.
_28
_28
except Exception as e:
_28
self.ten_env.log_error(f"Failed to handle {e}")

Package deployment types

TEN packages come in two deployment formats:

  • Development packages: Include source code and build configurations for creating and modifying extensions
  • Release packages: Contain only compiled artifacts and runtime files for direct deployment

Development packages

Development packages are used for creating and modifying TEN components. They contain:

  • Complete source code
  • Build configurations
  • Development dependencies
  • Documentation and examples

Use development packages when you need to modify existing extensions or create new TEN components from scratch.

Release packages

Release packages are optimized for deployment and operation. They contain:

  • Compiled binaries and libraries
  • Runtime configurations
  • Essential dependencies only

Use release packages for production deployment where you only need to run existing functionality without modification. Release packages require no build steps and can be deployed immediately.