Skip to content

hadriangateway/hagent

Repository files navigation

hagent

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.

Layout

  • 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. An AppEvent event 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). Same hagent-core, no terminal deps; published on crates.io. See ACP frontend.

Usage

# 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 --continue

The 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.

Configuration

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). In auth.mode = none, sending any Authorization header is rejected, so hagent omits the bearer when no key is configured.

Reasoning

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".

Conversation logging & resume

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.

File-editing skill (bootstrap)

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.

MCP servers & authentication

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.

Keys

  • 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.

ACP frontend (hagent-acp)

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-acp

Register 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.

Development

cargo test          # unit tests (protocol, state machine, render, input)
cargo clippy --all-targets
cargo fmt
cargo run -p hagent -- -p "hello"   # headless smoke

Logs go to ~/.cache/hagent/hagent.log (TUI mode owns the terminal); set HAGENT_LOG=debug to raise the level.

License

Dual-licensed under Apache 2.0 and MIT.

About

A thin TUI agent client for OpenResponses and OpenAI-compatible /v1/responses APIs

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors