A thin terminal agent client (TUI) for
Hadrian's OpenAI-compatible
/v1/responses API, in the Codex / Claude Code style.
Hadrian runs the entire agentic loop server-side: it executes the
shell, web_search, file_search and mcp tools (in containers, with
mountable skills), keeps conversation history when store: true, and performs
context compaction. So hagent is genuinely thin. It streams the response,
renders it, keeps a single previous_response_id between turns, and handles
the few interactive things the server can't: MCP approval requests and
OAuth login for remote MCP servers.
It deliberately does not use the client_passthrough shell runtime, so the
client executes nothing locally.
The repo also ships hagent-acp, an
Agent Client Protocol (ACP) frontend that
exposes the same server-side agent to editors like Zed and JetBrains; see
ACP frontend below.
hagent-core: protocol wire types, the SSE client, the conversation state machine, config, conversation logging, the bundled file-editing skill, and remote-MCP support (OAuth). No terminal dependencies; fully unit-tested.hagent: the Ratatui TUI. AnAppEventevent loop, a per-turn network task, a vendored inline-viewport terminal, and the renderer (Markdown plus syntax highlighting).hagent-acp: an ACP frontend over stdio for editors (Zed, JetBrains). Samehagent-core, no terminal deps; published on crates.io. See ACP frontend.
# Interactive TUI
HAGENT_API_KEY=gw_... hagent --model gpt-5.2
# Against a local dev Hadrian with `auth.mode = none` (no key needed)
hagent --model gpt-5.2
# One-shot headless (streams to stdout, no TUI)
hagent --model gpt-5.2 -p "Summarize the CSV in /mnt/data"
# Resume a previous conversation (by id, or the most recent one)
hagent --resume <id>
hagent --continueThe TUI renders the transcript into the terminal's native scrollback (it stays on the primary screen and does not capture the mouse), so you scroll and select/copy history with your terminal's own controls. The composer grows as you type and collapses large pastes to placeholders.
Precedence (highest first): CLI flags, then env vars, then
config.toml, then built-in defaults.
| Setting | Flag | Env | Default |
|---|---|---|---|
| Base URL | --base-url |
HAGENT_BASE_URL |
http://localhost:8080/api |
| API key | --api-key |
HAGENT_API_KEY |
(none; omits the auth header) |
| Model | --model |
HAGENT_MODEL |
gpt-5.2 |
| Tools | --tools |
(n/a) | all tools |
| Reasoning effort | --reasoning-effort |
HAGENT_REASONING_EFFORT |
high |
| Reasoning summary | --reasoning-summary |
HAGENT_REASONING_SUMMARY |
auto |
| Log dir | --log-dir |
HAGENT_LOG_DIR |
<cache>/conversations |
By default all server-side tools are enabled. Pass --tools shell,web_search
(or set tools = [...] in the config) to narrow the set, or --tools none
(tools = [] in the config) to disable them all. file_search is skipped unless
vector stores are configured, and mcp unless MCP servers are.
Other settings (MCP servers, vector stores, compaction, container reuse, the
system prompt) live in ~/.config/hagent/config.toml; see
config.example.toml and
.mcp.json.example.
Note on the base URL: Hadrian mounts its OpenAI-compatible API under
/api, so the OpenAI-style base URL is<host>:8080/api(the client appends/v1/responses). Inauth.mode = none, sending anyAuthorizationheader is rejected, sohagentomits the bearer when no key is configured.
Reasoning is on by default (effort = "high", summary = "auto"), so the
model's thinking shows dimmed in the transcript. Effort levels are
minimal | low | medium | high | xhigh | max | off (xhigh/max are the
adaptive Opus 4.7/4.8 plus GPT-5.x tiers above high; providers clamp
unsupported tiers down). Disable with --reasoning-effort off, the /effort
picker, or [reasoning] effort = "off".
On by default: each conversation writes its own directory under
<cache>/conversations/<id>/ containing log.ndjson (one redacted line per
request and response) and conversation.json (a resumable snapshot: chain
pointer, container, totals, transcript). Resume the chain with --resume <id>
(the directory name) or the most-recent one with --continue. On exit, hagent
prints a one-line usage summary and the exact command to resume the session.
Disable logging with --no-log-conversations or [logging] enabled = false.
--debug / HAGENT_DEBUG=1 turns off log redaction (writes the API key and
MCP bearers verbatim, for debugging auth only); it is distinct from
HAGENT_LOG, which sets the tracing level.
Hadrian executes the shell tool but exposes no first-class file-edit tool, so
hagent ships a small file-editing skill (view/create/str-replace/
insert, rendered as coloured diff blocks). The first time you use a server
with shell enabled, hagent offers a one-time bootstrap panel to upload and
activate it; the resulting skill id is cached per server in config.toml.
Suppress the offer with --no-bootstrap; force a re-upload (e.g. after the skill
changes) with --force-bootstrap or /bootstrap.
Remote (Streamable HTTP) MCP servers come from ./.mcp.json (Claude-Code
project scope) and config.toml [[mcp_servers]], merged by label
(config.toml wins). Auth kinds are none, bearer, header, and oauth.
OAuth discovery (PRM/AS metadata, dynamic client registration, PKCE) is done by
hagent directly against the MCP server; only the resulting bearer is handed
to Hadrian, and it is re-sent every turn. Tokens persist in the OS keychain
(with an owner-only file fallback).
Manage servers from the shell:
hagent mcp list
hagent mcp login <label>
hagent mcp logout <label>
hagent mcp add <url> [label] [--to project|user]In the TUI, /mcp opens an interactive menu (log in / out, add a server).
Mid-turn, an unauthenticated OAuth server prompts a login panel; tool calls that
need approval show an inline approve / deny panel.
- Enter: send. Shift+Enter / Alt+Enter: newline
- Esc: cancel the in-flight turn, or close the slash-command popup
- Ctrl-C / Ctrl-D: quit (press twice within 2s; a single Ctrl-C cancels a streaming turn)
- Ctrl-U: delete from the cursor to the start of the line
- Ctrl-L: clear the screen and jump the composer to the top
- Ctrl-G: edit the composer in
$EDITOR - Ctrl-T: toggle the detailed token / cost breakdown in the status line
- Up / Down: recall prior prompts (gated to the composer's first/last line)
/: open the slash-command popup (/resume,/rewind,/clear,/model,/effort,/server,/settings,/bootstrap,/mcp,/reasoning,/output)- Type while a turn streams to queue follow-up messages
- In an MCP approval panel: a approve, d deny
- In the bootstrap / MCP-login panel: y accept, n skip
Scrollback and text selection are native: use your terminal's own scroll and mouse; hagent does not capture the mouse.
The same server-side agent is also exposed over the
Agent Client Protocol (ACP) by the
hagent-acp crate, so editors that speak ACP (Zed, JetBrains, …) can drive
it as a coding agent. It reuses hagent-core unchanged and only translates
between the /v1/responses stream and ACP SessionUpdates. Execution stays
server-side, so it never runs anything on the editor's machine.
cargo install hagent-acpRegister it as an external agent. In Zed's settings.json:
{
"agent_servers": {
"hagent": {
"command": "hagent-acp",
"args": ["--base-url", "http://localhost:8080/api", "--model", "gpt-5.2"],
"env": { "HAGENT_API_KEY": "gw_..." }
}
}
}It shares hagent's configuration, MCP servers, and conversation logging, and
adds editor-driven controls: a model picker and tool on/off toggles (ACP
session config options), reasoning-effort session modes, and /model,
/effort, /status slash commands. MCP approvals and OAuth login surface
through ACP's permission / message channels. Pass --flat-model-list for
clients that don't render grouped dropdowns (JetBrains today; Zed groups the
model list by provider fine).
See hagent-acp/README.md for the full method /
capability table.
cargo test # unit tests (protocol, state machine, render, input)
cargo clippy --all-targets
cargo fmt
cargo run -p hagent -- -p "hello" # headless smokeLogs go to ~/.cache/hagent/hagent.log (TUI mode owns the terminal); set
HAGENT_LOG=debug to raise the level.
Dual-licensed under Apache 2.0 and MIT.