Radarr-Agent is an autonomous, self-hosted AI agent architecture designed to manage local media libraries via natural language. Built with LangGraph, FastMCP, and Ollama, Radarr-Agent connects local large language models (LLMs) to a self-hosted Arr stack (Radarr, qBittorrent, Prowlarr, FlareSolverr).
The system features real-time state persistence, accurate ChatML token tracking, asynchronous non-blocking tool invocation, and dynamic MCP-to-LangChain tool remapping.
Radarr-Agent uses a stateful ReAct (Reasoning + Acting) loop powered by LangGraph. It processes user intent, inspects library state, queries external metadata indexes, and executes media pipeline mutations through Radarr's REST v3 API.
+-----------------------------+
| User CLI Interface |
+--------------+--------------+
|
v
+-----------------------------+
| LangGraph ReAct Loop |
| (agent.py) |
+--------------+--------------+
|
+-------------------------+-------------------------+
| |
v v
+-----------------------+ +--------------------+
| Ollama Model Engine | | FastMCP Tool Server|
| (qwen2.5:3b) | | (MCP Adapter) |
+-----------------------+ +---------+----------+
|
v
+--------------------+
| Async Radarr Client|
| (client.py) |
+---------+----------+
|
v
+--------------------+
| Docker Media Stack |
| Radarr | qBittorr. |
| Prowl. | FlareSol. |
+--------------------+
- Stateful ReAct Loop (
agent.py): Manages conversation history (AgentState) using LangGraph message annotations, directing flow conditionally between LLM evaluation and tool execution. - Dynamic FastMCP Conversion (
convert_fastmcp_to_langchain_tool): Maps FastMCP tool input schemas into Pydantic models at runtime, wrapping execution in asynchronous calls to prevent event loop deadlocks. - Exact Token Accounting (
count_qwen_tokens): Uses HuggingFace'sAutoTokenizerforQwen/Qwen2.5-3Bto calculate exact context window usage, accounting for ChatML format headers (<|im_start|>,<|im_end|>) and tool call JSON payloads. - Atomic State Dumping (
periodic_state_logger): Periodically serializes full conversation state and metadata intoagent_state.json. Uses atomic file replacements (.tmpwrite followed byos.replace) to ensure zero read-collision for external monitoring scripts. - Asynchronous Client Engine (
client.py): Built onhttpx.AsyncClientwith custom header management (X-Api-Key), handling non-blocking API communications with Radarr.
The media management stack is containerized using Docker Compose to ensure network isolation, persistent storage, and reproducible deployment.
| Service | Port | Description |
|---|---|---|
| Radarr | 7878 |
Movie library management, quality profiles, metadata tracking, and disk mapping |
| qBittorrent | 8081 (WebUI)6881 (Peer) |
BitTorrent download client for handling media retrieval tasks |
| Prowlarr | 9696 |
Indexer management platform connected to torrent and usenet providers |
| FlareSolverr | 8191 |
Cloudflare challenge solver proxy for Prowlarr indexers |
/Media/downloads: Shared volume between qBittorrent and Radarr for completed downloads./Media/Movies: Target library storage directory monitored and organized by Radarr.- Config directories (
/config): Stored locally per service to maintain persistent database state across container restarts.
.
βββ agent.py # LangGraph agent loop, FastMCP converter, and state logger
βββ client.py # Asynchronous Radarr v3 API HTTP client
βββ schemas.py # Pydantic state models, action responses, and lookup schemas
βββ prompts.py # System prompt, operational constraints, and few-shot examples
βββ log.py # Logging setup and log level suppression for HTTP clients
βββ docker-compose.yml # Container orchestration for media automation stack
βββ pyproject.toml # Dependency definitions and project metadata
βββ README.md # Technical documentation
- Linux / macOS / Windows with WSL2
- Python
>=3.13 - Docker and Docker Compose
- Ollama installed and running locally
git clone https://github.com/your-username/Radarr-Agent.git
cd Radarr-Agent
# Using uv (Recommended)
uv sync
# Using standard pip
pip install -r pyproject.tomldocker compose up -dVerify that all services are running correctly:
docker compose psCreate a .env file in the project root:
RADARR_URL=http://localhost:7878
RADARR_API=your_radarr_api_key_hereNote: Obtain your API Key from Radarr via Settings -> General -> API Key.
Pull the required language model into Ollama:
ollama pull qwen2.5:3bEnsure your FastMCP server hosting the Radarr tools is running at http://127.0.0.1:8000/mcp.
python -m agentRadarr-Agent operates under strict logic rules to ensure accurate state reporting and prevent unintended mutations:
-
"Ready to Watch" Distinction:
has_file == True: Movie is downloaded, stored on disk, and ready for immediate viewing.in_library == True&has_file == False: Movie is monitored/queued in Radarr, but currently downloading or missing files. Cannot be watched yet.in_library == False: Movie is not present in the system.
-
Targeted vs. Global Tool Invocation:
search_movie(term): Used for title lookup, status checking, or pre-addition verification.list_library_movies(): Used strictly when the user requests an overall summary of the collection.add_movie_to_library(tmdb_id): Requires explicit user confirmation before execution.delete_movie_from_library(id): Invoked strictly on explicit removal requests.
==================================================
π€ AI Chatbot Initialized (Type 'exit' or 'quit' to stop)
π Dumping state to 'agent_state.json'
==================================================
You: Do we have Inception ready to watch?
AI: Yes, Inception (2010) is in your library and ready to watch.
You: Is Gladiator downloaded?
AI: Gladiator (2000) is in your library, but it has not finished downloading yet and is not ready to watch.
You: Can we watch Interstellar tonight?
AI: Interstellar is currently not in your movie library. Would you like me to add it for you?
You: Yes, please add it.
AI: Interstellar (2014) has been added to Radarr and is now queued for downloading.
- MCP Client Connection Error: Ensure the FastMCP server process is active on port
8000before startingagent.py. - Out of Memory / Context Error: Ensure Ollama has sufficient RAM/VRAM allocated for
qwen2.5:3b. - API Authentication Failures: Double check
RADARR_APIin.envand verify Radarr is reachable athttp://localhost:7878/api/v3/system/status.
Building Radarr-Agent was a journey through the realities of stateful, local-first AI development. Moving away from standard cloud APIs toward running everything on local hardware introduced distinct engineering challenges:
- Handling Tool Schemas Dynamically: Adapting MCP schemas into LangChain tools without blocking the asyncio event loop required deep debugging into function signatures and dynamic type instantiation.
- Token Management Precision: Small local models like
qwen2.5:3bhave strict context windows. Relying on rough word counts led to unexpected truncation. Implementing exact token counting via HuggingFace's tokenizer was a key turning point for system reliability. - Async Synchronization: Coordinating HTTP clients, file logging, terminal user inputs, and LangGraph streams asynchronously required careful handling of locks and background tasks to keep execution smooth.
Ultimately, building Radarr-Agent demonstrated that with the right architecture, self-hosted local agents can deliver private, precise, and practical automation without relying on external cloud infrastructure.