Multi-agent CLI that answers questions about your database and runs code in a sandbox.
Project description
Terno Agent
A multi-agent CLI that answers questions about your database. It plans, generates and executes SQL, and can write Python and run it in a sandbox (Docker by default) to analyze results.
Features
- Multi-agent: an Orchestrator plans and delegates to a Database specialist (SQL) and a Coder specialist (sandboxed Python).
- Provider-agnostic LLM: Anthropic Claude and OpenAI — pick at runtime.
- Any database: anything SQLAlchemy can talk to (Postgres, MySQL, SQLite, …) via a single URL.
- Sandboxed code execution: Docker by default (
--network none, read-only rootfs, mem/CPU caps); local subprocess fallback for dev. - Read-only by default: only
SELECT/WITH/EXPLAINallowed unless you opt in. - Streaming + typed events: assistant text streams live; tool calls and results render with syntax-highlighted panels and result tables.
- CLI + library:
terno ask "..."from the shell, orfrom terno_agent import Agentin Python.
Architecture
┌────────────────────────┐
user query → │ Orchestrator │ ← planner: decomposes into steps
└────────────┬───────────┘ and routes to a specialist
│
┌────────────┼────────────┐
▼ ▼
┌────────────────┐ ┌────────────────┐
│ DatabaseAgent │ │ CoderAgent │
│ • sql_query │ │ • run_python │
│ • list_tables │ │ (sandboxed) │
│ • describe │ │ │
└────────┬───────┘ └────────┬───────┘
│ │
▼ ▼
SQLAlchemy engine Docker / local
(any DB URL) sandbox runner
All cross-cutting boundaries are protocols, so each layer is swappable:
| Boundary | Protocol | Implementations |
|---|---|---|
| LLM | LLMClient |
Anthropic, OpenAI |
| Sandbox | Sandbox |
Docker, local subprocess |
| Tool | Tool |
sql_query, run_python, ... |
| Database | SQLAlchemy URL | Postgres, MySQL, SQLite, ... |
Install
You can install with either uv or plain pip. Both produce the same terno
CLI on your PATH and the same importable terno_agent package.
Optional extras
Pick only what you need (or use all to get everything):
| Extra | What it pulls in |
|---|---|
anthropic |
the anthropic SDK |
openai |
the openai SDK |
docker |
the docker SDK for sandboxing |
postgres |
psycopg[binary] |
mysql |
pymysql |
all |
all of the above |
dev |
pytest, ruff, mypy |
With uv (recommended)
# install globally as a uv tool — `terno` works from anywhere
uv tool install terno-agent
uv tool install "terno-agent[anthropic,docker,postgres]"
# editable install from a local checkout
git clone https://github.com/terno-ai/terno-agent.git
cd terno-agent
uv tool install --editable ".[all]"
# add it as a dependency of another uv project
uv add terno-agent
uv add "terno-agent[anthropic,docker]"
# from a local path or git
uv add /path/to/terno_agent
uv add "git+https://github.com/terno-ai/terno-agent.git"
Refresh after changing pyproject.toml:
uv tool install --editable ".[all]" --force
With pip
# from PyPI
pip install terno-agent
pip install "terno-agent[anthropic,docker,postgres]"
# from a local checkout (editable)
git clone https://github.com/terno-ai/terno-agent.git
cd terno-agent
python -m venv .venv && source .venv/bin/activate
pip install -e ".[all]"
# from git
pip install "git+https://github.com/terno-ai/terno-agent.git"
# from a built wheel
pip install ./dist/terno_agent-0.1.0-py3-none-any.whl
Tip: if you install into a project venv with plain
pip, you have to activate the venv (or use itsbin/terno) to run the CLI.uv tool installavoids this by giving the CLI its own isolated environment onPATH.
Configure
Configuration is read from environment variables, with .env auto-loaded from
your current working directory (or any parent). Process env wins over .env.
cp .env.example .env
# then edit:
ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY=
TERNO_LLM_PROVIDER=anthropic # anthropic | openai
TERNO_LLM_MODEL=claude-opus-4-7
TERNO_DATABASE_URL=sqlite:///./demo.db
TERNO_SANDBOX=docker # docker | local | none
Run terno config to print the effective settings (API keys masked).
SQLAlchemy URL examples
sqlite:///./relative.db # relative to CWD
sqlite:////absolute/path/to.db # absolute (4 slashes)
postgresql+psycopg://user:pass@host:5432/db
mysql+pymysql://user:pass@host:3306/db
Use the CLI
# one-shot question
terno ask "what were the top 10 customers by revenue last quarter?"
# interactive REPL
terno chat
# suppress streaming/activity, print only the final answer
terno -q ask "how many tracks are in the database?"
# show effective config
terno config
# show version
terno --version
If you installed with plain pip into a project venv and didn't activate it:
.venv/bin/terno ask "..."
# or
python -m terno_agent ask "..."
If you installed with uv into the current project rather than as a tool:
uv run terno ask "..."
Use as a library
from terno_agent import Agent
# Reads .env + env vars
agent = Agent.from_env()
result = agent.ask("how many active users signed up this week?")
print(result.answer)
Programmatic config (no env vars required):
from terno_agent import Agent
from terno_agent.config import Config
cfg = Config(
llm_provider="anthropic",
llm_model="claude-opus-4-7",
llm_api_key="sk-ant-...",
database_url="postgresql+psycopg://u:p@host/db",
sandbox="local", # "docker" | "local" | "none"
)
agent = Agent.from_config(cfg)
print(agent.ask("top 5 tables by row count").answer)
Stream events into your own UI:
from terno_agent import Agent
from terno_agent.core.events import TextDelta, ToolCallEvent, ToolResultEvent
def on_event(e):
if isinstance(e, TextDelta):
print(e.text, end="", flush=True)
elif isinstance(e, ToolCallEvent):
print(f"\n[tool] {e.call.name}({e.call.arguments})")
elif isinstance(e, ToolResultEvent):
print(f"[result] {e.result.content[:200]}")
agent = Agent.from_env(on_event=on_event)
agent.ask("describe the users table and count rows")
Project layout
src/terno_agent/
cli.py # argparse entry point + rich renderer
config.py # env + .env-driven Config
core/ # message/tool/event/exception types
llm/ # LLMClient protocol + Anthropic + OpenAI (streaming)
agents/ # orchestrator + specialist agents
tools/ # sql_query, run_python, list_tables, describe_table
sandbox/ # Docker + local runners
db/ # SQLAlchemy engine & inspector
prompts/ # system prompts per agent
tests/ # pytest suite
Develop
# clone + editable install with dev extras
git clone https://github.com/terno-ai/terno-agent.git
cd terno-agent
uv venv --python 3.12
uv pip install -e ".[dev,all]"
# tests
uv run pytest -q
# lint / format / typecheck
uv run ruff check .
uv run ruff format .
uv run mypy src
Or with plain pip:
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,all]"
pytest -q
License
MIT
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 terno_agent-0.1.0.tar.gz.
File metadata
- Download URL: terno_agent-0.1.0.tar.gz
- Upload date:
- Size: 525.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de18a7c53c92f01dc16e969b40755b685db8132c8e2f4fad2d1f0f6fc73f5e5c
|
|
| MD5 |
5ae18cddda9d7c42fe89ba5f0511ecf4
|
|
| BLAKE2b-256 |
286ec4a4101e75ab36483f81dda5651d791f0a0a4933710e06b72a486f453db4
|
File details
Details for the file terno_agent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: terno_agent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58816cb018e36b8161e8a2e813c9ed44dbc50d83ebd21236e8546712160b500f
|
|
| MD5 |
ee6f341717dc6184fd85af548b08a9fb
|
|
| BLAKE2b-256 |
1aee475df8611df4e733b36f53e677b6d22263089d946606add16348bf562837
|