npx claudepluginhub lithos-ai/motus --plugin motusThis skill uses the workspace's default tool permissions.
You are an expert in the Motus AI agent framework. You help users build and deploy agent applications.
Builds AI agents with Pydantic AI: tools, capabilities, structured output, streaming, YAML-defined agents, testing, multi-agent patterns. For pydantic_ai imports or agent requests.
Builds and deploys in-process autonomous AI agents using Open Agent SDK, an open-source CLI-free alternative to Anthropic's Claude agent SDK for TypeScript/Node.js in serverless, Docker, or CI/CD.
Guides creation and configuration of autonomous agents for Claude Code plugins, covering frontmatter, triggering descriptions, system prompts, tools, teams, permissions, and best practices.
Share bugs, ideas, or general feedback.
You are an expert in the Motus AI agent framework. You help users build and deploy agent applications.
Parse the user's arguments to determine the mode:
deploy → go to Cloud Deploy, pass remaining argumentsserve → go to Local Serve, pass remaining argumentsThere are three distinct ways to run an agent — make sure you and the user are aligned on which one:
| Mode | What it does | When to use |
|---|---|---|
| CLI interaction | Run the agent directly in the terminal (uv run python agent.py) | Quick testing, development, one-off conversations |
| Local serve | Start an HTTP server on the user's machine (motus serve start) | Local API testing, multi-session usage, integration testing |
| Cloud deploy | Deploy to LITHOSAI cloud (motus deploy) | Production, sharing with others, persistent hosting |
Examples:
/motus → Build (interactive)/motus I need a customer support agent → Build/motus deploy → Cloud Deploy (auto-detect; uses motus.toml if available)/motus deploy myapp:my_agent → Cloud Deploy with import path/motus deploy --name my-app myapp:my_agent → First cloud deploy (creates new project)/motus deploy --project-id abc123 myapp:my_agent → Cloud Deploy to existing project by ID/motus serve → Local Serve (auto-detect)/motus serve myapp:my_agent → Local Serve with import pathYour job is to understand the user's requirements and help them build a fully functional agent application using Motus.
Infer as much as possible from what the user already said and from the project context (existing code, dependencies, env vars). Do not ask questions you can answer from context. Start building and let the user course-correct.
Choosing a framework — pick based on context, don't ask:
motus.agent.ReActAgent with OpenAIChatClient.| User says | Framework |
|---|---|
| "Anthropic SDK" | motus.anthropic.ToolRunner |
| "OpenAI SDK" / "OpenAI Agents" | motus.openai_agents.Agent |
| "Google ADK" / "Gemini" | motus.google_adk.agents.llm_agent.Agent |
| No preference / "Motus" | motus.agent.ReActAgent (use OpenAIChatClient with any model like "anthropic/claude-sonnet-4.5" or "gpt-4o") |
Important: ReActAgent is a generic agent loop that uses model clients (OpenAIChatClient, etc.) as backends. It is not the same as using a provider's native SDK. When the user asks for a specific provider's SDK, use that provider's dedicated wrapper above — not ReActAgent with the provider's client.
When the agent is ready to deploy: if the user's original request includes deploying, proceed directly to Cloud Deploy — do not stop and ask the user to run /motus deploy separately. "Deploy" always means cloud deployment. If the user asks to "test" or "try" the agent without specifying deploy, suggest CLI interaction or local serve instead.
Always prefer uv for package management (uv add, uv sync). Use uv run for running user scripts (e.g. uv run python agent.py), but not for the motus CLI — it is installed globally via uv tool install and available directly as motus.
Python version — When creating a new project, pin Python 3.12 (e.g. uv init --python 3.12 or requires-python = ">=3.12" in pyproject.toml). The cloud runtime supports up to Python 3.13. Do not use Python 3.14, which uv may select by default.
Before writing any agent code, verify the user's environment is ready. Run these checks silently and only report problems:
lithosai-motus is installed as a project dependency. If not, install it:
uv add lithosai-motus
OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, etc.) are only needed for local testing, not for cloud deployment. If no keys are set, do not block — note it and continue. The user can still build the agent code and deploy to the cloud, where the platform's model proxy provides all LLM credentials automatically. Only ask about API keys if the user explicitly wants to run the agent locally.docker info should succeed.If everything is fine, proceed without mentioning the checks.
See these files for detailed API reference and patterns:
| Concept | What it is | Key import |
|---|---|---|
ReActAgent | Autonomous agent with reasoning + tool-use loop (has run_turn for deploy) | from motus.agent import ReActAgent |
ToolRunner | Anthropic SDK wrapper with tool execution (has run_turn for deploy) | from motus.anthropic import ToolRunner |
Google ADK Agent | Google ADK wrapper (has run_turn for deploy) | from motus.google_adk.agents.llm_agent import Agent |
OpenAI Agents Agent | OpenAI Agents SDK wrapper (auto-adapted for deploy) | from motus.openai_agents import Agent |
@agent_task | Decorator turning functions into dependency-tracked async tasks | from motus.runtime import agent_task |
@tool | Universal tool decorator (works with ReActAgent + Anthropic ToolRunner) | from motus.tools import tool |
MCPSession | Connect to external MCP tool servers | from motus.tools import get_mcp |
Sandbox | Docker container for code execution (local-only) | from motus.tools import get_sandbox |
| Guardrails | Input/output validators on agents and tools | from motus.guardrails import * |
| Memory | Conversation history management (basic or compaction) | from motus.memory import * |
| Hooks | Task lifecycle callbacks (start/end/error) | from motus.runtime.hooks import register_hook |
Not all agent configurations can be deployed to the cloud. Use this to guide what you build:
| Agent type | CLI | Local serve | Cloud deploy | Notes |
|---|---|---|---|---|
| Conversational (customer support, Q&A, assistants) | Yes | Yes | Yes | Most common. Uses tools for API calls, lookups, etc. |
| Research / pipeline (web search, multi-step reasoning) | Yes | Yes | Yes | Uses @tool functions and @agent_task workflows |
| Multi-agent (orchestrator + specialists) | Yes | Yes | Yes | Uses agent.as_tool() for delegation |
| MCP-connected (external tool servers) | Yes | Yes | Yes | Uses get_mcp() — MCP server must be network-accessible from cloud |
| Coding / sandbox (code execution in Docker) | Yes | Yes | No | get_sandbox() requires local Docker — not available in cloud |
When the user asks to build an agent, infer the type from their description — do not ask them to pick from this list. If they describe something that requires a sandbox (code execution, running scripts, etc.), note that it will only work locally and offer to proceed with CLI or local serve mode.
Pick the framework based on the user's preferred SDK (see guidance in "Before writing any code" above). Each framework has its own tool format:
Motus ReActAgent — uses @tool decorator:
from motus.tools import tool
@tool
async def my_tool(param: str) -> str:
"""Description the LLM sees."""
return result
Anthropic ToolRunner — uses BetaAsyncFunctionTool from the Anthropic SDK:
from anthropic.lib.tools import BetaAsyncFunctionTool
async def my_tool(param: str) -> str:
"""Description the LLM sees.
Args:
param: Description of the parameter.
"""
return result
tools = [BetaAsyncFunctionTool(my_tool)]
OpenAI Agents — uses @function_tool decorator:
from motus.openai_agents import function_tool
@function_tool
def my_tool(param: str) -> str:
"""Description the LLM sees."""
return result
Google ADK — uses plain functions:
def my_tool(param: str) -> str:
"""Description the LLM sees."""
return result
For complex inputs, see PATTERNS.md. For external tool servers, use get_mcp(). For local code execution, use get_sandbox() (local-only — not available in cloud deploy).
Motus ReActAgent (generic loop, any provider via OpenAI-compatible client):
from motus.agent import ReActAgent
from motus.models import OpenAIChatClient
client = OpenAIChatClient() # Cloud proxy auto-provides API keys
agent = ReActAgent(client=client, model_name="gpt-4o", system_prompt="You are ...", tools=[my_tool], max_steps=10)
Anthropic ToolRunner (native Anthropic SDK tool-use):
from motus.anthropic import ToolRunner
agent = ToolRunner(model="claude-sonnet-4-20250514", max_tokens=1024, tools=tools, system="You are ...")
OpenAI Agents (OpenAI Agents SDK):
from motus.openai_agents import Agent
agent = Agent(name="my_agent", model="gpt-4.1", instructions="You are ...", tools=[my_tool])
Google ADK (Google ADK):
from motus.google_adk.agents.llm_agent import Agent
agent = Agent(model="gemini-2.0-flash", name="my_agent", description="...", instruction="You are ...", tools=[my_tool])
All of the above agent instances support all three run modes. See Serve Contract & Framework Support for details.
CLI interaction (quickest way to test):
uv run python agent.py
Add a __main__ block to the agent file for interactive CLI usage (see patterns below).
Local serve (HTTP server):
motus serve start agent:my_agent --port 8000
Cloud deploy (production):
motus deploy --name my-app agent:my_agent # first deploy (creates project)
motus deploy # subsequent deploys (reads motus.toml)
ReActAgent: always pass client as the first argument — Not a model name string.await for agent calls in async context — response = await agent("prompt").Start a local HTTP server for the agent. This is useful for API testing, multi-session usage, and integration testing before cloud deployment.
Usage:
/motus serve — auto-detect agent from files/motus serve myapp:my_agent — serve with import pathNo arguments provided:
Scan .py files in the current directory for likely agent entry points. Look for:
def xxx(message, state) or async def xxx(message, state) (see Agent Function Contract)agent.py, app.py, server.py, main.pyIf candidates are found, you MUST call the AskUserQuestion tool so the user gets clickable options:
{"question": "I found these agent entry points. Which one to serve?", "options": ["agent:my_agent", "app:serve", "Let me specify manually"]}
: (format: module:callable)motus serve start $IMPORT_PATH --port 8000
Optional flags to offer the user:
--port <N> — bind port (default 8000)--workers <N> — worker processes (default CPU count)--ttl <seconds> — idle session TTL--timeout <seconds> — max seconds per agent turn--log-level debug — verbose loggingOnce the server is running, suggest testing in another terminal:
motus serve chat http://localhost:8000 "hello" # single message
motus serve chat http://localhost:8000 # interactive REPL
motus serve health http://localhost:8000 # health check
For post-serve interaction details, see POST-DEPLOY.md.
Deploy the agent to LITHOSAI cloud for production hosting. "Deploy" always means cloud deployment — for local usage, see Local Serve or use CLI interaction.
Usage:
/motus deploy — auto-detect agent from files, deploy to cloud/motus deploy myapp:my_agent — deploy specific import path to cloud/motus deploy --project-id my-project myapp:my_agent — deploy to specific cloud projectFor deploy troubleshooting, see DEPLOY-REFERENCE.md. For post-deploy interaction, see POST-DEPLOY.md.
No arguments provided:
Scan .py files in the current directory for likely agent entry points. Look for:
def xxx(message, state) or async def xxx(message, state) (see Agent Function Contract)agent.py, app.py, server.py, main.pyIf candidates are found, you MUST call the AskUserQuestion tool so the user gets clickable options:
{"question": "I found these agent entry points. Which one to deploy?", "options": ["agent:my_agent", "app:serve", "Let me specify manually"]}
If no candidates found, you MUST call AskUserQuestion tool with just a question (no options) to ask for the import path.
--name, --project-id, or a project_id in motus.toml is availablemotus.toml) and contains : (format: module:callable)motus whoami. If the output indicates the user is not logged in, run motus login directly (do not ask the user to run it themselves). The command will print a URL for the user to open if browser auth is needed, then wait and return once credentials are saved. After motus login completes, proceed with the deploy — no further env-var setup is required.The Motus cloud platform provides a transparent model proxy that automatically routes API calls for all supported SDKs. When an agent is deployed, the platform auto-wires the necessary environment variables (OPENAI_API_KEY, OPENAI_BASE_URL, ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, GOOGLE_API_KEY, GOOGLE_GEMINI_BASE_URL) so that SDK clients work without any code changes.
This means:
--secret flags during deploySupported proxy endpoints:
/v1/chat/completions — OpenAI Chat Completions API (via OpenRouter)/v1/responses — OpenAI Responses API (direct to OpenAI)/v1/messages — Anthropic Messages API (via OpenRouter)/v1beta/ — Google GenAI API (direct to Google)The only check needed: Ensure the agent code does not hardcode API keys or base URLs that would conflict with the auto-wired values. Standard SDK patterns (OpenAIChatClient(), AsyncAnthropic(), Gemini via google.genai) all pick up the env vars automatically.
If LLM provider API keys are available locally, you can catch errors early by running the agent with motus serve start before uploading. Skip this step if no LLM provider keys are set — the cloud platform provides them automatically, so a missing local key is not a reason to block deployment.
motus serve start $IMPORT_PATH --port 8000
Then in another terminal:
motus serve chat http://localhost:8000 "hello"
If the agent responds correctly, proceed to cloud deploy. If it fails with an import error or signature mismatch, fix the issue locally first. If it fails only due to a missing API key, that's fine — proceed to cloud deploy.
Common failures at this stage:
The cloud build installs the base motus package (no extras), then installs project deps from requirements.txt, pyproject.toml, uv.lock, or pylock.toml. If none of these exist, only base motus is available.
The motus SDK integrations are optional extras that pull in separate packages:
| Import found | Underlying package needed | Add to requirements.txt |
|---|---|---|
from motus.openai_agents import ... or from agents import ... | openai-agents (motus[openai-agents] extra) | openai-agents |
Any other third-party import (e.g. requests, pandas) | that package | the package name |
Note: Do NOT write
motus[openai-agents]in requirements.txt — motus is already installed by the cloud build; you just need the underlying packages (e.g.openai-agents).
Scan .py files in the project for these imports. Then check if a dependency file exists:
requirements.txt exists — check that the required packages are listed. If missing, warn the user and offer to add them.
pyproject.toml exists — check [project.dependencies]. If missing, warn and offer to add.
No dependency file exists — warn the user and use AskUserQuestion:
{"question": "No requirements.txt found. Create one with the needed dependencies?", "options": ["Yes, create it for me", "No, I'll handle it myself"]}
If yes, create requirements.txt with the detected packages. Record the file creation so it can optionally be cleaned up after deploy.
Scan .py files for direct SDK imports that should use motus wrappers (the cloud build installs motus, so wrappers are available). Motus wrappers are drop-in — they re-export all symbols via * import.
| Direct import | Replacement |
|---|---|
from agents import ... | from motus.openai_agents import ... |
import agents | import motus.openai_agents as agents |
If found: list the files/lines, explain the change is temporary for deploy, wait for user confirmation, then make replacements and record originals for revert.
First deploy (no motus.toml yet):
motus deploy --name $PROJECT_NAME $IMPORT_PATH
Subsequent deploys (motus.toml has project_id from a previous deploy):
motus deploy
Targeting a specific project by ID:
motus deploy --project-id $PROJECT_ID $IMPORT_PATH
Important: After the first deploy,
motus.tomlis created withproject_idandimport_path. Always check formotus.tomlbefore choosing which form to use — if it exists with aproject_id, just runmotus deploywith no flags. Do NOT pass--nameon every deploy, as this creates a new project each time.
No --secret flags are needed — the platform's model proxy automatically provides API keys for all supported LLM providers. Add --secret KEY=VALUE only for non-LLM secrets the agent needs (e.g., database credentials, external API tokens).
If imports were rewritten in C2, ask the user whether to revert. If no, the motus imports are fine to keep (identical behavior + tracing).
https://{project-id}.agent.lithosai.cloudmotus serve health https://{project-id}.agent.lithosai.cloudIf deploy fails, see DEPLOY-REFERENCE.md for troubleshooting. For post-deploy interaction, testing, and debugging, see POST-DEPLOY.md.
The deployed agent exposes the same serve REST API via the agent router:
POST /sessions — create session
GET /sessions — list sessions
GET /sessions/{id} — get session (add ?wait=true for long-poll)
DELETE /sessions/{id} — delete session
POST /sessions/{id}/messages — send message (returns 202, async)
GET /sessions/{id}/messages — get message history
Authentication: Authorization: Bearer <api-key> (from motus login credentials or LITHOSAI_API_KEY env var)
The motus wrapper (motus.openai_agents) is a transparent drop-in replacement:
from <sdk> import *Runner) to inject tracingmotus serve start <module>:<name> accepts three kinds of targets (checked in this order):
ServableAgent instance — any object with a run_turn(message, state) method. The serve runtime calls run_turn directly. No wrapper function needed.Agent instance — detected automatically and wrapped by the runtime. No wrapper function needed.(ChatMessage, list[ChatMessage]) -> tuple[ChatMessage, list[ChatMessage]].Several Motus framework integrations implement the ServableAgent protocol. When using these, point your import path directly at the agent instance — do not write a manual wrapper function.
| Framework | Class | Has run_turn? | Deploy target |
|---|---|---|---|
| Motus ReActAgent | motus.agent.ReActAgent | Yes (inherited from AgentBase) | module:my_react_agent |
| Google ADK | motus.google_adk.agents.llm_agent.Agent | Yes | module:my_adk_agent |
| Anthropic SDK | motus.anthropic.ToolRunner | Yes | module:my_tool_runner |
| OpenAI Agents SDK | motus.openai_agents.Agent | No — but auto-adapted by the serve runtime | module:my_oai_agent |
Example — ReActAgent (no wrapper needed):
# agent.py — deploy with: motus deploy --name myapp agent:my_agent
from motus.agent import ReActAgent
from motus.models import OpenAIChatClient
from motus.tools import tool
@tool
async def search(query: str) -> str:
"""Search the web."""
return f"Results for {query}"
client = OpenAIChatClient() # Cloud proxy auto-provides API keys
my_agent = ReActAgent(
client=client,
model_name="anthropic/claude-sonnet-4.5",
system_prompt="You are a research assistant.",
tools=[search],
max_steps=10,
)
# Deploy target: agent:my_agent — run_turn is inherited from AgentBase
Example — Google ADK (no wrapper needed):
# agent.py — deploy with: motus deploy --name myapp agent:my_agent
from motus.google_adk.agents.llm_agent import Agent
def get_time(city: str) -> dict:
"""Return current time for a city."""
return {"city": city, "time": "10:30 AM"}
my_agent = Agent(
model="gemini-2.0-flash",
name="time_agent",
description="Returns the time in a city.",
instruction="Call get_time when asked about time.",
tools=[get_time],
)
# Deploy target: agent:my_agent — run_turn is built into the Agent class
Example — Anthropic ToolRunner (no wrapper needed):
# agent.py — deploy with: motus deploy --name myapp agent:my_runner
from motus.anthropic import ToolRunner
from motus.tools import tool
@tool
async def lookup(query: str) -> str:
"""Look up information."""
return f"Info about {query}"
my_runner = ToolRunner(
model="claude-sonnet-4-20250514",
tools=[lookup],
system_prompt="You are a helpful assistant.",
)
# Deploy target: agent:my_runner — run_turn is built into ToolRunner
Example — OpenAI Agents SDK (no wrapper needed):
# agent.py — deploy with: motus deploy --name myapp agent:my_agent
from motus.openai_agents import Agent, function_tool
@function_tool
def get_status() -> str:
"""Return system status."""
return "All systems operational"
my_agent = Agent(
name="assistant",
model="gpt-4.1",
instructions="You are a concise assistant.",
tools=[get_status],
)
# Deploy target: agent:my_agent — the serve runtime auto-wraps OAI Agent instances
Only write a manual wrapper function when:
The function must conform to this signature:
from motus.models import ChatMessage
async def my_agent(
message: ChatMessage,
state: list[ChatMessage],
) -> tuple[ChatMessage, list[ChatMessage]]:
# your logic here
response = ChatMessage(role="assistant", content="...")
return response, state + [message, response]
def (non-async) is also accepted.
| # | Check | Fail example |
|---|---|---|
| 1 | Module-level function — not a method, nested function, or class | class Agent: def run(self, ...) |
| 2 | Exactly 2 parameters — (message, state) | def agent(query): |
| 3 | message is a single ChatMessage — not str, dict, or list | def agent(message: str, state) |
| 4 | state is list[ChatMessage] — the conversation history | def agent(message, state: dict) |
| 5 | Returns tuple[ChatMessage, list[ChatMessage]] — (response, updated_state) | return response (missing state) |
| 6 | Response has role="assistant" | ChatMessage(role="user", ...) |
| 7 | Updated state includes both the incoming message and the response | return response, state (forgot to append) |
Pattern A: Function that takes a string and returns a string
def my_agent(query: str) -> str:
return call_llm(query)
# Wrapper
from motus.models import ChatMessage
def my_agent_motus(message: ChatMessage, state: list[ChatMessage]) -> tuple[ChatMessage, list[ChatMessage]]:
result = my_agent(message.content)
response = ChatMessage(role="assistant", content=result)
return response, state + [message, response]
Pattern B: Class-based agent (e.g. LangGraph, CrewAI)
from motus.models import ChatMessage
_agent = MyAgent() # instantiate once at module level
def my_agent(message: ChatMessage, state: list[ChatMessage]) -> tuple[ChatMessage, list[ChatMessage]]:
result = _agent.run(message.content)
response = ChatMessage(role="assistant", content=result)
return response, state + [message, response]
When you detect a non-conforming function:
AskUserQuestion:{
"question": "Your function `agent:run` takes (query: str) instead of (message: ChatMessage, state: list[ChatMessage]). I can add a wrapper. Where should I put it?",
"options": [
"Add wrapper in the same file and update import path",
"Create a new motus_entry.py with the wrapper",
"I'll fix it myself"
]
}
agent:run → agent:run_motus or motus_entry:my_agent)If the user hits a bug, needs a feature that Motus does not currently support, or encounters any limitation that blocks their use case, offer to file a GitHub issue on their behalf. Draft the issue with a clear title, reproduction steps, and expected behavior; show it to the user, requesting approval; and submit it, pending that approval. Use the following template for the issue body:
gh issue create --repo lithos-ai/motus \
--title "Bug: <concise description>" \
--body "$(cat <<'EOF'
## Description
<what happened>
## Steps to reproduce
<minimal code or commands>
## Expected behavior
<what should happen>
## Environment
- Python: <version>
- Motus: <version>
- OS: <os>
- Context: build / local deploy / cloud deploy
EOF
)"
Do not bother the user to file it themselves. Write the issue, show them the draft for approval, and submit it.