An intelligent agent for task automation and assistance
Project description
OnIt
OnIt — the AI is working on the given task and will deliver the results shortly.
OnIt is an intelligent agent framework for task automation and assistance. It is built on MCP (Model Context Protocol) for tool integration and supports the A2A (Agent-to-Agent) protocol for multi-agent communication. OnIt connects to LLMs via any OpenAI-compatible API (private vLLM servers or OpenRouter.ai) and orchestrates tasks through modular MCP servers.
Design Philosophy
OnIt is intended as lean AI agent framework. The design philosophy adheres to the following principles:
- Portable — Minimal dependencies beyond the core AI model library and MCP. Deployable from embedded devices to GPU servers.
- Modular — Clear separation of AI logic, tasks, and UIs. Easily extendable with new MCP servers and tools.
- Scalable — From a single tool to complex multi-server setups.
- Redundant — Multiple ways to solve a problem, sense the world, and execute actions. Let the AI decide the optimal path.
- Configurable — Edit a YAML file and you are good to go. Applies to both the agent and MCP servers.
- Responsive — Safety routines can interrupt running tasks at any time.
Features
- Interactive chat — Rich terminal UI with input history, theming, and execution logs
- Web UI — Gradio-based browser interface with file upload, copy buttons, and real-time polling
- MCP tool integration — Automatic tool discovery from any number of MCP servers (web search, bash, office documents, Google Workspace)
- A2A protocol — Run OnIt as an A2A server so other agents can send tasks and receive responses
- Loop mode — Execute a fixed task on a configurable timer (useful for monitoring and periodic workflows)
- Prompt templates — Customizable YAML-based instruction templates per persona
- Session logging — All tasks and responses are saved as JSONL for audit and replay
- Safety queue — Press Enter or Ctrl+C to interrupt any running task
Architecture
┌─────────────────────────────────────────────────────┐
│ onit CLI │
│ (argparse + YAML config) │
└────────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ OnIt (src/onit.py) │
│ │
│ ┌─────────-┐ ┌──────────┐ ┌──────────┐ │
│ │ ChatUI │ │ WebChatUI│ │ A2A │ │
│ │(terminal)│ │ (Gradio) │ │ Server │ │
│ └────┬────-┘ └────┬─────┘ └────┬─────┘ │
│ └─────────┬───┘ │ │
│ ▼ ▼ │
│ client_to_agent() / process_task() │
│ │ │
│ ▼ │
│ MCP Prompt Engineering (FastMCP) │
│ │ │
│ ▼ │
│ chat() ◄──── Tool Registry │
│ (vLLM / OpenRouter) (auto-discovered) │
└─────────────────────────────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────┐ ┌──────────┐
│ Web Search│ │ Bash │ │ Office │ ...
│ MCP Server│ │MCP Server│ │MCP Server│
└───────────┘ └──────────┘ └──────────┘
Key components:
| Component | Path | Description |
|---|---|---|
OnIt |
src/onit.py |
Core agent class. Manages config, tool discovery, chat loop, A2A server, and session logging. |
ChatUI |
src/ui/text.py |
Rich terminal UI with chat history, input history (arrow keys), execution logs panel, and theming. |
WebChatUI |
src/ui/web.py |
Gradio web interface with file upload, async polling, and file download. |
Chat |
src/model/serving/chat.py |
LLM interface via OpenAI-compatible API. Supports private vLLM and OpenRouter.ai models. Handles tool calling loops, thinking mode, retries, and safety interrupts. |
Tool discovery |
src/lib/tools.py |
Connects to each MCP server URL, discovers available tools, and builds a unified tool registry. |
Prompts |
src/mcp/prompts/prompts.py |
FastMCP-based prompt engineering. Supports custom YAML templates per persona. |
MCP servers |
src/mcp/servers/ |
Pre-built MCP servers for web search, bash, Microsoft Office, and Google Workspace. |
Installation
From source (recommended for development)
git clone https://github.com/sibyl-oracles/onit.git
cd onit
pip install -e .
With optional dependencies
# Web UI (Gradio)
pip install -e ".[web]"
# Everything
pip install -e ".[all]"
From pip
pip install onit
or install everything
pip install onit[all]
Docker
Build the image
docker build -t onit .
Run the container
Interactive terminal mode:
docker run -it --rm --env-file .env onit
Web UI (Gradio on port 9000):
docker run -it --rm -p 9000:9000 --env-file .env onit --web --web-port 9000
A2A server:
docker run -it --rm -p 9001:9001 --env-file .env onit --a2a --a2a-port 9001
With a custom config:
docker run -it --rm -v $(pwd)/configs:/app/configs --env-file .env onit --config configs/default.yaml
Docker Compose
Start the MCP servers, web UI, and A2A server together:
docker compose up --build
This launches three services defined in docker-compose.yml:
- onit-mcp — MCP servers on ports 18200-18204
- onit-web — Web UI on port 9000 (depends on MCP servers)
- onit-a2a — A2A server on port 9001 (depends on MCP servers)
The web and A2A services automatically use --mcp-host onit-mcp to route MCP requests to the MCP container via Docker networking.
Note: Pass API keys via an
.envfile or individual-e KEY=valueflags. Never bake secrets into the image.
Quick Start
1. Set up environment variables
All environment variables must be set before running any OnIt component.
LLM serving host — OnIt works with any OpenAI-compatible API (auto-detected from URL):
# Private vLLM server
export ONIT_HOST=http://localhost:8000/v1
# Or OpenRouter.ai
export ONIT_HOST=https://openrouter.ai/api/v1
export OPENROUTER_API_KEY=sk-or-v1-your-key-here
MCP server API keys:
| Variable | Description | Get Key |
|---|---|---|
OLLAMA_API_KEY |
API key for Ollama web search | ollama.com |
OPENWEATHER_API_KEY |
API key for weather data | openweathermap.org |
export OLLAMA_API_KEY=your_ollama_api_key
export OPENWEATHER_API_KEY=your_openweather_api_key
Or configure everything in configs/default.yaml:
serving:
host: https://openrouter.ai/api/v1
host_key: sk-or-v1-your-key-here # or set OPENROUTER_API_KEY env var
model: google/gemini-2.5-pro
The provider is auto-detected: if the host URL contains
openrouter.ai, the API key is read fromhost_keyin the config or theOPENROUTER_API_KEYenvironment variable. All other hosts default to vLLM with no key required.
2. Start MCP servers
MCP servers must be running before launching the agent. Start all enabled servers with:
# Default config (src/mcp/servers/configs/default.yaml)
onit --mcp
# Custom config
onit --mcp --config path/to/mcp_servers.yaml
# With debug logging
onit --mcp --config path/to/mcp_servers.yaml --mcp-log-level DEBUG
This starts all enabled MCP servers defined in the config file. Each server runs in its own process. The MCP server config structure is:
servers:
- name: WebSearchMCPServer
module: tasks.web.search
description: "MCP server for web search"
enabled: true
host: 0.0.0.0
port: 18201
path: /search
transport: 'streamable-http'
See src/mcp/servers/configs/default.yaml for the full default configuration.
3. Run the agent
With environment variables set and MCP servers running, launch the agent:
Terminal chat:
onit
Web UI (Gradio):
onit --web --web-port 9000
Loop mode (repeat a task on a timer):
onit --a2a-loop --a2a-task "Check the weather in Manila" --a2a-period 60
A2A server (accept tasks from other agents):
onit --a2a --a2a-port 9001
Client mode (send a task to a remote OnIt A2A server):
onit --a2a-client --a2a-host http://127.0.0.1:9001 --a2a-task "what is the weather"
CLI Options
General:
| Flag | Description | Default |
|---|---|---|
--config |
Path to YAML configuration file | configs/default.yaml |
--host |
LLM serving host URL (overrides config and ONIT_HOST env var) |
— |
--model |
Model name (overrides serving.model in config) |
— |
--verbose |
Enable verbose logging | false |
--timeout |
Request timeout in seconds (-1 = none) |
600 |
--template-path |
Path to custom prompt template YAML file | — |
Text UI:
| Flag | Description | Default |
|---|---|---|
--text-theme |
Text UI theme (white or dark) |
dark |
--text-show-logs |
Show execution logs panel | false |
Web UI:
| Flag | Description | Default |
|---|---|---|
--web |
Launch Gradio web UI | false |
--web-port |
Gradio web UI port | 9000 |
A2A (Agent-to-Agent):
| Flag | Description | Default |
|---|---|---|
--a2a |
Run as an A2A protocol server | false |
--a2a-port |
A2A server port | 9001 |
--a2a-client |
Client mode: send a task to a remote A2A server | false |
--a2a-host |
A2A server URL for client mode | http://localhost:9001 |
--a2a-task |
Task string for A2A loop or client mode | — |
--a2a-file |
File to upload to the A2A server with the task | — |
--a2a-image |
Image file to send for vision processing | — |
--a2a-loop |
Enable A2A loop mode | false |
--a2a-period |
Seconds between A2A loop iterations | 10 |
MCP (Model Context Protocol):
| Flag | Description | Default |
|---|---|---|
--mcp |
Run MCP servers | false |
--mcp-host |
Override the host/IP in all MCP server URLs (e.g. 192.168.1.100) |
— |
--mcp-log-level |
MCP server log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
INFO |
Configuration
All options can be set in the YAML config file and overridden via CLI flags. The full configuration structure:
# LLM serving
serving:
# host: resolved from --host flag > config value > ONIT_HOST env var
model: Qwen/Qwen3-30B-A3B-Instruct-2507
think: true
max_tokens: 262144
# Agent behavior
persona: "assistant"
verbose: false
show_logs: false
theme: dark
timeout: 600
# Paths
session_path: "~/.onit/sessions"
template_path: # optional path to custom prompt template YAML
# Web UI settings
web_title: "OnIt Chat"
web: false
web_port: 9000
web_share: false
# Google OAuth2 Authentication (optional)
web_google_client_id:
web_google_client_secret:
# MCP servers the agent connects to as a client
mcp:
# mcp_host: 192.168.1.100 # override host/IP in all server URLs (or use --mcp-host)
servers:
- name: PromptsMCPServer
description: Provides prompt templates for instruction generation
url: http://127.0.0.1:18200/prompts
enabled: true
- name: WebSearchHandler
description: Handles web, news and weather search queries
url: http://127.0.0.1:18201/search
enabled: true
Custom Prompt Templates
Create a YAML file with an instruction_template field:
# my_template.yaml
instruction_template: |
You are a research assistant. Think step by step.
<task>
{task}
</task>
Save all results to `{data_path}`.
Session ID: {session_id}
Then use it:
onit --config my_config.yaml
With template_path: my_template.yaml in the config, or set it directly in the config file.
See example templates in src/mcp/prompts/prompt_templates/.
MCP Servers
Pre-built servers
| Server | Module | Description |
|---|---|---|
| Web Search | tasks.web.search |
Web, news, and weather search (uses Ollama Search API) |
| Bash | tasks.os.bash |
Execute shell commands |
| Document Search | tasks.os.filesystem |
Search patterns in documents (text, PDF, markdown) with table extraction |
| Microsoft Office | tasks.office.microsoft |
Create Word, Excel, PowerPoint documents |
| Google Workspace | tasks.office.google |
Create Google Docs, Sheets, Slides (disabled by default) |
Google Workspace: The Google Workspace MCP server is disabled by default in the agent config. To enable it, first set up the Google Workspace API by following the Google Workspace and OAuth guide, then set
enabled: trueforGoogleWorkspaceMCPServerin yourconfigs/default.yaml.
Running MCP servers
All servers are configured via src/mcp/servers/configs/default.yaml and launched with:
onit --mcp
# Or with a custom config
onit --mcp --config path/to/mcp_servers.yaml
A2A Protocol
OnIt can run as an A2A server, allowing other agents to send tasks and receive responses.
Start the A2A server
onit --a2a --a2a-port 9001
The agent card is available at http://localhost:9001/.well-known/agent.json.
Send a task (Python)
import httpx, asyncio
async def send_task():
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "What is 2 + 2?"}],
"messageId": "test-001",
}
},
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post("http://localhost:9001", json=payload)
print(resp.json())
asyncio.run(send_task())
With an image (VLM):
import httpx, asyncio, base64, os
async def send_image_task():
image_path = "assets/rambutan_calamansi.jpg"
with open(image_path, "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [
{"kind": "text", "text": "Are the rambutans ripe enough to be eaten?"},
{
"kind": "file",
"file": {
"bytes": image_data,
"mimeType": "image/jpeg",
"name": os.path.basename(image_path),
},
},
],
"messageId": "vlm-001",
}
},
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post("http://localhost:9001", json=payload)
print(resp.json())
asyncio.run(send_image_task())
Send a task (A2A SDK)
from a2a.client import ClientFactory, create_text_message_object
from a2a.types import Role
async def main():
client = await ClientFactory.connect("http://localhost:9001")
message = create_text_message_object(role=Role.user, content="What is the weather?")
async for event in client.send_message(message):
print(event)
asyncio.run(main())
With an image (VLM):
import asyncio, base64, os, uuid
from a2a.client import ClientFactory
from a2a.types import FilePart, FileWithBytes, Message, Part, Role, TextPart
async def main():
image_path = "assets/rambutan_calamansi.jpg"
with open(image_path, "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
message = Message(
role=Role.user,
message_id=str(uuid.uuid4()),
parts=[
Part(root=TextPart(text="Are the rambutans ripe enough to be eaten?")),
Part(root=FilePart(file=FileWithBytes(
bytes=image_data,
mime_type="image/jpeg",
name=os.path.basename(image_path),
))),
],
)
client = await ClientFactory.connect("http://localhost:9001")
async for event in client.send_message(message):
print(event)
asyncio.run(main())
A2A protocol tests are included in the test suite: pytest src/test/test_a2a.py -v.
Send a task (OnIt client)
The simplest way to send a task to a remote OnIt A2A server:
# Basic usage
onit --a2a-client --a2a-host http://192.168.86.101:9001 --a2a-task "what is the weather"
# With a longer timeout (default is 120s)
onit --a2a-client --a2a-host http://192.168.86.101:9001 --a2a-task "summarize this report" --timeout 300
The command prints the response and exits. No config file, model serving, or UI is needed.
Model Serving
OnIt works with any OpenAI-compatible API. The provider is auto-detected from the host URL.
Private vLLM
Use vLLM to serve models locally:
# Serve Qwen3-30B with tool calling support
CUDA_VISIBLE_DEVICES=0,1,2,3 vllm serve Qwen/Qwen3-30B-A3B-Instruct-2507 \
--max-model-len 262144 --port 8000 \
--enable-auto-tool-choice --tool-call-parser qwen3_xml \
--reasoning-parser qwen3 --tensor-parallel-size 4 \
--chat-template-content-format string
Then point the agent at your server:
export ONIT_HOST=http://localhost:8000/v1
onit
Serving VLM
OnIt supports vision-language models (VLMs) for image understanding tasks over the A2A protocol.
A2A server:
onit --a2a --host <ONIT_HOST> --model Qwen/Qwen3-VL-8B-Instruct
Client:
onit --a2a-client --a2a-task "are the rambutans ripe enough to be eaten?" --a2a-image assets/rambutan_calamansi.jpg
OpenRouter.ai
OpenRouter gives access to models from OpenAI, Google, Meta, Anthropic, and others through a single API.
- Create an account at openrouter.ai and generate an API key.
- Set the key and host:
export OPENROUTER_API_KEY=sk-or-v1-your-key-here
export ONIT_HOST=https://openrouter.ai/api/v1
onit
Or configure in configs/default.yaml:
serving:
host: https://openrouter.ai/api/v1
host_key: sk-or-v1-your-key-here
model: google/gemini-2.5-pro
think: true
max_tokens: 262144
Browse available models at openrouter.ai/models and use the model ID (e.g. google/gemini-2.5-pro, meta-llama/llama-4-maverick, openai/gpt-4.1) as the model value.
Project Structure
onit/
├── configs/
│ └── default.yaml # Agent configuration
├── pyproject.toml # Package configuration
├── src/
│ ├── __init__.py # Exports OnIt
│ ├── cli.py # CLI entry point
│ ├── onit.py # Core agent class
│ ├── lib/
│ │ ├── text.py # Text utilities
│ │ └── tools.py # MCP tool discovery
│ ├── mcp/
│ │ ├── prompts/
│ │ │ ├── prompts.py # Prompt engineering (FastMCP)
│ │ │ └── prompt_templates/ # YAML templates
│ │ └── servers/
│ │ ├── run.py # Multi-process server launcher
│ │ ├── configs/ # Server config YAMLs
│ │ └── tasks/ # Task servers (web, bash, office)
│ ├── model/
│ │ └── serving/
│ │ └── chat.py # LLM interface (vLLM + OpenRouter)
│ ├── type/
│ │ └── tools.py # Type definitions
│ ├── ui/
│ ├── text.py # Rich terminal UI
│ ├── utils.py # UI utilities
│ └── web.py # Gradio web UI
│ └── test/ # Test suite (pytest)
│ ├── test_onit.py # Core agent tests
│ ├── test_cli.py # CLI tests
│ ├── test_a2a.py # A2A protocol tests
│ ├── test_chat.py # LLM chat tests
│ ├── test_chat_ui.py # Terminal UI tests
│ ├── test_web_ui.py # Web UI tests
│ └── ... # Additional test modules
Testing
Run the full test suite:
pip install -e ".[test]"
pytest src/test/ -v
Documentation
- Google Workspace and OAuth for Gmail — Service account setup, domain-wide delegation, Gmail, and Web UI OAuth
- OAuth2 Redirect Flow — Full OAuth2 redirect flow implementation details
- OAuth Quick Start — Quick setup checklist for Google OAuth
- Web Authentication — Web UI authentication reference
- Web Deployment — Production deployment with HTTP/HTTPS via nginx or Caddy
License
Apache License 2.0. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file onit-0.1.1a0.tar.gz.
File metadata
- Download URL: onit-0.1.1a0.tar.gz
- Upload date:
- Size: 131.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e217049a6afe3bbd6eec0c707af18696596d9b5704e8bd66a0266a18d895cce
|
|
| MD5 |
9472d75841ae825ac63dfd1d6e8c0151
|
|
| BLAKE2b-256 |
73d5aca8310086b27f10892b1a3caaebf134cac73a13bd55cf2a1aa1fcbc6048
|
Provenance
The following attestation bundles were made for onit-0.1.1a0.tar.gz:
Publisher:
publish.yml on sibyl-oracles/onit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onit-0.1.1a0.tar.gz -
Subject digest:
0e217049a6afe3bbd6eec0c707af18696596d9b5704e8bd66a0266a18d895cce - Sigstore transparency entry: 972522607
- Sigstore integration time:
-
Permalink:
sibyl-oracles/onit@5910c60090a6a8f85973290d702567056d54068e -
Branch / Tag:
refs/tags/v0.1.1a - Owner: https://github.com/sibyl-oracles
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5910c60090a6a8f85973290d702567056d54068e -
Trigger Event:
release
-
Statement type:
File details
Details for the file onit-0.1.1a0-py3-none-any.whl.
File metadata
- Download URL: onit-0.1.1a0-py3-none-any.whl
- Upload date:
- Size: 143.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11e565912e29baecd4faef6f9fbfae2de013db6ff65dc94ae3f38cb45850388f
|
|
| MD5 |
2a18cb3ab02aadfe3c762259c9f7144a
|
|
| BLAKE2b-256 |
91c470cc48641f191077b4c374d1883b14840b8a7446465d258dcdac702aebe4
|
Provenance
The following attestation bundles were made for onit-0.1.1a0-py3-none-any.whl:
Publisher:
publish.yml on sibyl-oracles/onit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onit-0.1.1a0-py3-none-any.whl -
Subject digest:
11e565912e29baecd4faef6f9fbfae2de013db6ff65dc94ae3f38cb45850388f - Sigstore transparency entry: 972522614
- Sigstore integration time:
-
Permalink:
sibyl-oracles/onit@5910c60090a6a8f85973290d702567056d54068e -
Branch / Tag:
refs/tags/v0.1.1a - Owner: https://github.com/sibyl-oracles
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5910c60090a6a8f85973290d702567056d54068e -
Trigger Event:
release
-
Statement type: