Skip to main content

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 .env file or individual -e KEY=value flags. 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 from host_key in the config or the OPENROUTER_API_KEY environment 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: true for GoogleWorkspaceMCPServer in your configs/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.

  1. Create an account at openrouter.ai and generate an API key.
  2. 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

License

Apache License 2.0. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

onit-0.1.1a0.tar.gz (131.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

onit-0.1.1a0-py3-none-any.whl (143.8 kB view details)

Uploaded Python 3

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

Hashes for onit-0.1.1a0.tar.gz
Algorithm Hash digest
SHA256 0e217049a6afe3bbd6eec0c707af18696596d9b5704e8bd66a0266a18d895cce
MD5 9472d75841ae825ac63dfd1d6e8c0151
BLAKE2b-256 73d5aca8310086b27f10892b1a3caaebf134cac73a13bd55cf2a1aa1fcbc6048

See more details on using hashes here.

Provenance

The following attestation bundles were made for onit-0.1.1a0.tar.gz:

Publisher: publish.yml on sibyl-oracles/onit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for onit-0.1.1a0-py3-none-any.whl
Algorithm Hash digest
SHA256 11e565912e29baecd4faef6f9fbfae2de013db6ff65dc94ae3f38cb45850388f
MD5 2a18cb3ab02aadfe3c762259c9f7144a
BLAKE2b-256 91c470cc48641f191077b4c374d1883b14840b8a7446465d258dcdac702aebe4

See more details on using hashes here.

Provenance

The following attestation bundles were made for onit-0.1.1a0-py3-none-any.whl:

Publisher: publish.yml on sibyl-oracles/onit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page