⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⣶⣾⣿⣿⣿⣿⣿⣶⡆⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡏⢤⡎⣿⣿⢡⣶⢹⣧⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣶⣶⣇⣸⣷⣶⣾⣿⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢨⣿⣿⣿⢟⣿⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡏⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣜⠿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⣷⣿⡿⣷⣮⣙⠿⣿⣿⣿⣿⣿⡄⠀
⠀⠀⠠⢄⣀⡀⠀⠀⠀⠀⠀⠈⠫⡯⢿⣿⣿⣿⣶⣯⣿⣻⣿⣿⠀
⠀⠀⠤⢆⠆⠈⠉⠳⠤⣄⡀⠀⠀⠀⠙⢻⣿⣿⠿⠿⠿⢻⣿⠙⠇
⠠⠤⠀⣉⣁⣢⣄⣀⣀⣤⣿⠷⠦⠤⣠⡶⠿⣟⠀⠀⠀⠀⠻⡀⠀
⠀⠀⠔⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠃⠉⠉⠛⠛⠿⢷⡶⠀⠀
░██████ ░██ ░██
░██ ░██ ░██
░██ ░██ ░██ ░██
░██ ░██ ░██ ░██
░██ ░██ ░██ ░██
░██ ░██ ░██ ░██
░██████ ░██ ░██
A lightweight, terminal-based AI agent harness for local-first, vendor-agnostic setups: bundled/drop-in personas with scoped tool sets, MCP extensibility, and configurable agent pools the root agent can delegate to at runtime.
Most agent harnesses today sit at one of a few extremes: single-agent skill runners with no delegation story, programmatic multi-agent orchestration that depends on a frontier model writing its own orchestration code, or production infrastructure platforms built around always-on gateways and cloud providers. oli is built for a narrower, specific case: an interactive terminal session, running local models by default, where sub-agents are configurable, declarative, and inspectable.
Concretely, that means:
- Local-model-first, vendor-agnostic by design. Ollama is the default backend. OpenAI, HuggingFace, and local Transformers backends are supported as peers, not afterthoughts — and pool entries can mix all of them in a single run.
- Declarative dispatch, not code-written orchestration. Sub-agents are defined in
agents.yamland addressed through a singledispatchtool call. This is a deliberate reliability bet: tool-calling a fixed schema is something small local models handle far more consistently than authoring correct multi-agent orchestration code. - Portable, bundled personas. Profiles pair a system prompt and permission manifest with drop-in
AGENTS.md/SKILLS.mdcontent, aiming for compatibility with the open Agent Skills spec rather than a bespoke format.
- Multi-backend support — Ollama, OpenAI, HuggingFace (remote or local), and Transformers (local GPU/CPU). Switch at runtime.
- Agent pooling (optional) — with
--use-pool, the root agent can fan tasks out concurrently to vendor-agnostic sub-agents defined in agents.yaml via adispatchtool. Each pool entry binds a model and a backend, so dispatch decisions are also compute-location decisions — a frontier model can plan while sensitive work stays on a local model, or a local root can fan out to faster remote SLMs for latency-sensitive tool calls. - Agent profiles — drop-in system prompts with permission manifests, base-profile inheritance, and auto-generated profiles via
/profile create. Bundled profiles:default,coder,reviewer,writer,planner,researcher,analyst. - Rich built-in tool set — file ops, shell access, web search/fetch, Wikipedia/GitHub/arXiv search, Git, task tracking, reasoning scratchpad, notebook, and more. Sandbox-locked with shell allowlists, SSRF protection, and sensitive-file gating.
- Permission system — write operations and sensitive reads require user approval. Session grants, workspace scoping, and profile-level allow/deny lists.
- OpenAI-compatible API server — run the same agent harness behind
/v1/modelsand/v1/chat/completions(streaming + non-streaming) so any workflow that speaks the OpenAI wire protocol (theopenaiPython SDK, curl, or plain REST) can drive the agent. - MCP integration — add stdio or HTTP MCP servers at runtime for custom tools.
These aren't shipped yet, but they're the directions we think are the most genuinely differentiated, and where we're focusing next:
- Pool-scoped permissions. Extend the permission manifest so dispatch itself is a permissioned action — a profile could require approval before it's allowed to hand work to a given pool entry, and manifests could declare which other profiles they're allowed to dispatch to at all.
- Sensitivity-aware routing. A per-agent
data_sensitivity/egressfield inagents.yaml(e.g.local-only,redact-on-return,unrestricted) that governs not just where a task runs, but what's allowed to flow back into a remote root's context — the actual enforcement mechanism behind the "frontier root, local subagent" privacy pattern. - Per-dispatch cost/latency telemetry. A
/pool statscommand surfacing spend and time by agent over a session, useful specifically because oli's pools are expected to mix free local models with paid remote ones.
Requires Python 3.11+. Install the package (this adds the oli and
oli-server entry points to your PATH):
pip install -e . # editable dev install
# or, from PyPI once published:
pip install oli-bot# Run (defaults to Ollama with offline mode on)
oli
# Run with OpenAI
OLI_BACKEND=openai OLI_OPENAI_API_KEY=sk-... oli
# Run with a profile
oli --profile researcher| Profile | Write? | Shell? | Web? | Best for |
|---|---|---|---|---|
default |
✅ | ✅ | ✅ | General-purpose tasks |
coder |
✅ | ✅ | ✅ | Software development end-to-end |
reviewer |
❌ | ✅ | ❌ | Code review, quality analysis |
writer |
✅ | ❌ | ✅ | Docs, READMEs, changelogs, prose |
planner |
✅ | ❌ | ✅ | Roadmaps, task decomposition, saved plans |
researcher |
❌ | ❌ | ✅ | Web research with structured JSON output |
analyst |
❌ | ❌ | ✅ | Cross-source claim extraction and analysis |
See docs/PROFILES.md for the full manifest schema, permission layering, and how to create your own.
| Flag | Description |
|---|---|
--model |
Model to use (inherits from server config if available) |
--url |
API endpoint URL (defaults to Ollama http://localhost:11434) |
--profile |
Agent profile (default default) |
--resume-last |
Resume the most recent session on startup (mutually exclusive with -s/--load-session) |
-s, --load-session |
Load a specific session by UUID on startup (mutually exclusive with --resume-last) |
--dry-run |
Start in dry-run mode |
--offline |
Force offline mode ON (already the default) |
--no-offline |
Start with offline mode OFF |
--verify-offline |
Startup diagnostic -- warn if outbound calls are configured |
--use-pool |
Enable agent pooling (root agent can dispatch tasks to sub-agents in agents.yaml) |
Running with no session flags always starts a new session. On exit, the app
prints a hint (Resume this session with: -s <uuid> or --resume-last) so you
can pick up where you left off.
| Command | Description |
|---|---|
/help |
Show help |
/models [name] |
Pick or switch to a model |
/model large|small |
Switch between configured large/small model tiers |
/model set-large|set-small <name> |
Set per-server large/small model and switch |
/config |
Open the configuration screen |
/context |
Show current server, model, profile |
/servers add|list|remove|default|switch|use-model |
Manage API endpoints |
/mcp add|list|edit|remove |
Manage MCP servers |
/mode [ask|agent|chat|plan] |
Switch mode (ask=read-only, agent=all tools, chat=no tools, plan=research + save a plan) |
/profile list|load|create |
Manage agent profiles |
/sessions list|switch|delete|rename|purge |
Manage conversation sessions |
/workspace list|set|unset |
Manage workspace directory |
/offline |
Toggle offline mode |
/dry-run |
Toggle dry-run mode |
/clear |
Clear the conversation |
/home |
Return to the home screen |
Ctrl+Q / Ctrl+L / Ctrl+Y |
Quit / Clear / Copy last message |
| Document | Contents |
|---|---|
| docs/ARCHITECTURE.md | Architectural overview of the application, permission, configurations, and more |
| docs/CONFIGURE.md | Full configuration reference, settings precedence, sessions, workspace |
| docs/TOOLS.md | Built-in tools, permission system, security, dry-run/offline modes, truncation |
| docs/BACKENDS.md | Backend setup (Ollama, OpenAI, HuggingFace, Transformers), model tier switching |
| docs/PROFILES.md | Profile structure, manifests, built-in profiles, creating and loading profiles |
| docs/AGENT-POOLING.md | Agent pooling configuration, parsing, and usage |
| docs/SECURITY.md | Security precidence and settings |
The Compose file runs the OpenAI-compatible API server in a container. The API server runs its own independent agent instance and does not connect to any separate TUI agent.
Start the API server:
docker-compose up --buildThe API server listens on localhost:9734, mounts ./profiles and ~/.config/oli to persist state across restarts, and is ready to accept OpenAI-compatible chat completions requests.
Run the TUI agent locally (optional):
oliThe TUI agent is a separate, interactive terminal interface. It has its own independent agent instance and does not interact with the containerized API server. Use this if you prefer an interactive terminal session instead of or in addition to the API.
The agent harness can be exposed over an OpenAI-compatible REST API, so existing Python SDKs and HTTP callers can drive it without the TUI:
# From source (editable install)
oli-server
# Or configure endpoint via env
OLI_API_HOST=0.0.0.0 OLI_API_PORT=9734 oli-serverIt listens on 0.0.0.0:9734 by default and serves:
| Endpoint | Description |
|---|---|
GET /v1/models |
List the active model |
POST /v1/chat/completions |
Non-streaming chat completion |
POST /v1/chat/completions with "stream": true |
Server-sent-event (SSE) streaming completion |
GET /health |
Liveness probe |
Conversations are stateless (like real OpenAI): each /v1/chat/completions request carries its full message history. The server holds a single process-private Agent instance (backend, tool registrations, MCP wiring) shared across requests, and serializes concurrent in-flight requests in-process. Because there is no human to prompt at permission time, the API auto-allows permission scopes for the current request; offline and dry-run gating from AppConfig still apply.
# List models
curl http://localhost:9734/v1/models
# Non-streaming completion
curl -X POST http://localhost:9734/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "minimax-m3:cloud", "messages": [{"role": "user", "content": "Hello"}]}'
# Streaming completion
curl -N -X POST http://localhost:9734/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "minimax-m3:cloud", "stream": true, "messages": [{"role": "user", "content": "Count to three"}]}'from openai import OpenAI
client = OpenAI(base_url="http://localhost:9734/v1", api_key="not-needed")
resp = client.chat.completions.create(
model="minimax-m3:cloud",
messages=[{"role": "user", "content": "What can you do?"}],
)
print(resp.choices[0].message.content)
# Streaming
stream = client.chat.completions.create(
model="minimax-m3:cloud",
messages=[{"role": "user", "content": "Write a short poem"}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")The openai SDK needs an api_key; pass a placeholder — the server does not require one.
| Env var | Default | Description |
|---|---|---|
OLI_API_HOST |
0.0.0.0 |
Bind address |
OLI_API_PORT |
9734 |
Listen port |
OLI_API_PROFILE |
default |
Agent profile to load |
OLI_API_MODE |
agent |
Agent mode (agent, ask, plan, chat) |
max_tokens / temperature request fields are accepted but best-effort: the agent tool loop reads them from AppConfig (configured via OLI_* env or settings.json).
pip install -e '.[dev]'
pytestTests live under tests/ and cover: sub-agent scaffolding, config env-var precedence, session round-trip, permission matrix, truncation boundaries, security regressions, OpenAI-style tool-call flushing, and the OpenAI-compatible API server endpoints. tests/conftest.py clears stray OLI_* env vars so runs are hermetic.