The self-managed motus exposes any agent as an HTTP server with session-based conversations usingDocumentation Index
Fetch the complete documentation index at: https://docs.motus.lithosai.com/llms.txt
Use this file to discover all available pages before exploring further.
motus serve. Each message spawns a fresh worker subprocess, so your agent runs in complete isolation. No shared state between requests, and a crash in one turn never affects another.
Quick start
Define your agent in a Python file, then start the server:myapp.py
Agent types
Every agent type follows the same turn contract: receive aChatMessage and the session’s prior state, return a response ChatMessage and updated state. All agent types run in worker subprocesses and must be importable from the module level.
| Parameter | Type | Description |
|---|---|---|
message | ChatMessage | The new user message (constructed by the server from the HTTP request). |
state | list[ChatMessage] | The session’s state from the previous turn (empty list on first turn). |
tuple[ChatMessage, list[ChatMessage]] the response message (surfaced to the HTTP client) and the updated state (stored in the session). The agent owns the state and can append, compact, or restructure it freely.
- ServableAgent
- Google ADK
- Anthropic SDK
- OpenAI Agents SDK
- Callable function
Any object with a conforming Built-in implementations include
run_turn method can be served directly. This is a runtime checkable. Protocolinheritance is not required.AgentBase and all of its subclasses (such as ReActAgent).Session lifecycle
Each conversation is a session. A session moves through the following states:| Status | Description |
|---|---|
idle | Waiting for input. Initial state after creation. |
running | Processing a message. Concurrent sends are rejected with 409. |
error | Agent raised an exception. The error field contains the message. |
A session in
error state can receive new messages and transitions back to running. Sessions are held in memory and do not persist across server restarts.--ttl is set, idle and errored sessions whose last activity exceeds the TTL are swept by a background task. When --timeout is set, agent turns that exceed the limit are killed and the session transitions to error with an "Agent timed out" message.
Architecture
Each message spawns a fresh subprocess viamultiprocessing.Process with pipe-based IPC. An asyncio.Semaphore limits concurrency to max_workers. Processes are not reused: each one starts, runs the agent function, sends the result over the pipe, and exits. On timeout or cancellation, the process is killed immediately.
This subprocess isolation model means:
- A crash in one agent turn never affects other sessions or the server itself.
- No shared state leaks between requests.
- Resource cleanup is automatic — when the process exits, all memory is reclaimed.
File structure
File structure
Server options
Start options formotus serve start:
| Flag | Default | Description |
|---|---|---|
--host | 0.0.0.0 | Bind address |
--port | 8000 | Port |
--workers | CPU count | Max concurrent worker processes |
--ttl | 0 | TTL for idle/error sessions in seconds (0 = disabled) |
--timeout | 0 | Max seconds per agent turn before the worker is killed (0 = no limit) |
--max-sessions | 0 | Maximum concurrent sessions (0 = unlimited) |
--shutdown-timeout | 0 | Seconds to wait for in-flight tasks on shutdown before cancelling (0 = wait indefinitely) |
--allow-custom-ids | false | Enable PUT /sessions/{id} for client-specified session IDs |
--log-level | info | Log verbosity: debug, info, warning, error |
CLI reference
motus serve chat
Send a message or enter an interactive REPL:
motus serve delete <url> <session-id> to delete manually.
| Flag | Default | Description |
|---|---|---|
--session | — | Resume an existing session instead of creating a new one |
--param | — | KEY=VALUE per-request parameter passed as user_params (repeatable) |
Other commands
| Command | Description |
|---|---|
motus serve health <url> | Check server status and worker counts |
motus serve create <url> | Create a new session |
motus serve sessions <url> | List all active sessions |
motus serve get <url> <id> [--wait] | Get session details; --wait blocks until not running |
motus serve delete <url> <id> | Delete a session |
motus serve messages <url> <id> | Get the full conversation history |
motus serve send <url> <id> <msg> [--wait] | Send a message; --wait blocks for completion |
Python API
UseAgentServer to embed the server in your own Python application:
agent_fn are keyword-only):
| Parameter | Type | Default | Description | |
|---|---|---|---|---|
agent_fn | `Callable | str` | required | A ServableAgent, OpenAI Agent, callable, or import path string (e.g., "myapp:my_agent") |
max_workers | `int | None` | None | Max concurrent worker processes. Defaults to os.cpu_count(), fallback 4. |
ttl | float | 0 | TTL for idle/error sessions in seconds. 0 disables expiry. | |
timeout | float | 0 | Max seconds per turn before the worker is killed. 0 disables. | |
max_sessions | int | 0 | Maximum concurrent sessions. 0 means unlimited. | |
shutdown_timeout | float | 0 | Seconds to wait for in-flight tasks on shutdown. 0 waits indefinitely. | |
allow_custom_ids | bool | False | Enable PUT /sessions/{id} for client-specified IDs. |
run(host, port, log_level) -> None — starts the server (blocking).
| Parameter | Type | Default | Description |
|---|---|---|---|
host | str | "0.0.0.0" | Bind address |
port | int | 8000 | Port |
log_level | str | "info" | Uvicorn log level |
server.app property exposes the underlying FastAPI application for testing or mounting in a larger application.
