My coding agent
  • Rust 99.8%
  • Shell 0.2%
Find a file
Sarat Chandra 629626020d chore(acp): remove stderr diagnostics
Drop the stderr logging added for debugging. The run_handler/spawn_handler
wrappers stay (they dedup the respond/spawn boilerplate) but no longer log;
the panic hook, per-event tracing, and TUG_ACP_LOG gating are gone.
2026-06-25 11:13:16 +05:30
.tug feat(tools): add ast_grep structural search tool 2026-06-18 11:38:54 +05:30
benches bench: add fd vs fff-search comparison for find 2026-06-10 22:20:32 +05:30
crates/tug-tui fix(tui): use frame width for validation to avoid resize-race crash 2026-06-22 23:15:54 +05:30
plans feat(tui): add settings overlay to edit and rewrite config.toml 2026-06-14 22:48:54 +05:30
scripts chore(repo): add local subagents, plans, and llama-server helper 2026-06-14 14:58:04 +05:30
src chore(acp): remove stderr diagnostics 2026-06-25 11:13:16 +05:30
.gitignore feat(config): support custom named providers 2026-06-13 21:50:37 +05:30
AGENTS.md feat(auth): add Google Antigravity OAuth provider 2026-06-22 23:16:50 +05:30
Cargo.lock feat(acp): upgrade ACP server to agent-client-protocol 0.14 2026-06-24 16:05:52 +05:30
Cargo.toml feat(acp): upgrade ACP server to agent-client-protocol 0.14 2026-06-24 16:05:52 +05:30
README.md feat(auth): add Google Antigravity OAuth provider 2026-06-22 23:16:50 +05:30
tug.deepseek.example.toml feat: add DeepSeek catalog provider 2026-06-10 22:52:20 +05:30
tug.example.toml feat(tools): make edit tool default to classic replacement, gate hashline behind setting 2026-06-17 17:38:02 +05:30
tug.zai.example.toml feat: add Tug terminal coding assistant 2026-04-04 10:49:59 +05:30

Tug

Tug is a terminal-native coding assistant built in Rust with:

  • ratatui for the TUI
  • rig-core for model and tool calling
  • OpenAI-compatible and Anthropic provider support

It is designed around a single model per session, streaming responses, local code tools, and a fast keyboard-driven workflow.

Current Capabilities

  • OpenAI-compatible chat completions
  • OpenAI Codex subscription auth and backend support
  • Google Antigravity auth and backend support (Gemini 3, Claude, GPT-OSS sandbox)
  • Streaming assistant output
  • Multi-turn chat history
  • Tool calling for read, edit, write, grep, find, bash, python3, terminal_* (tmux-backed background sessions), and task/wait (subagent spawning)
  • Subagent orchestration via the task tool (configurable allowlist and dedicated model per agent)
  • Agent Client Protocol (ACP) support
  • Interruptible runs
  • Multiline composer with cursor movement
  • @ file and directory mentions with inline picker
  • External editor handoff via $EDITOR
  • Shell shortcuts: !command runs in the user's default shell and adds output to future model context; !!command runs without adding context
  • Config file loading with environment overrides
  • Session model switching backed by models.dev
  • Persisted JSONL sessions with resume support
  • Cache usage visibility when the provider reports cached tokens

Install

cargo build

Run Tug with:

cargo run

Configuration

Tug loads configuration from the first available location:

  1. TUG_CONFIG
  2. ./tug.toml
  3. $XDG_CONFIG_HOME/tug/config.toml
  4. ~/.config/tug/config.toml

Environment variables override file values:

  • TUG_PROVIDER or TUG_TYPE
  • TUG_MODEL
  • TUG_THINKING
  • TUG_BASE_URL or OPENAI_BASE_URL
  • TUG_API_KEY or OPENAI_API_KEY

When no environment overrides are present, Tug also restores the last selected session target from session.json in the Tug config directory:

  • $XDG_CONFIG_HOME/tug/session.json
  • ~/.config/tug/session.json

That persisted state stores the last selected provider, model, endpoint, thinking level, active models.dev selector, and the current session file path when one exists. API keys are not stored there.

Example config:

type = "openai"
model = "gpt-4.1-mini"
thinking = "off"

[openai]
model = "gpt-4.1-mini"
base_url = "https://api.openai.com/v1"
api_key = "replace-me"

[anthropic]
model = "claude-sonnet-4-0"
base_url = "https://api.anthropic.com"
api_key = "replace-me"

[openai_codex]
model = "gpt-5.5"
base_url = "https://chatgpt.com/backend-api"

[models_dev.overrides.openai]
base_url = "https://shared-openai-compatible-endpoint.example/v1"

[models_dev.overrides."openai:gpt-4.1-mini"]
base_url = "https://your-openai-compatible-endpoint.example/v1"

type selects the provider. Supported values:

  • openai
  • anthropic
  • openai-codex
  • google-antigravity

For environment overrides:

  • OpenAI: TUG_API_KEY or OPENAI_API_KEY
  • DeepSeek models selected from the catalog: TUG_API_KEY or DEEPSEEK_API_KEY
  • Anthropic: TUG_API_KEY or ANTHROPIC_API_KEY
  • OpenAI Codex: TUG_API_KEY or /login
  • Google Antigravity: /login antigravity (OAuth only; no env key)

The shipped examples are:

[models_dev.overrides] applies only to models selected from the models.dev catalog. It does not change your normal configured model, and Tug does not infer that a custom configured model is a models.dev model. Provider-wide overrides can use openai, anthropic:, or another supported catalog provider key such as deepseek: style keys, and exact provider:model overrides take precedence over provider-wide ones.

Custom providers

Declare your own named providers under [providers.<name>] to expose models that are not in the models.dev catalog — for example a local llama.cpp server. Unlike a plain [openai_compatible] block (which only sets the default endpoint), custom providers are first-class: switch to them in-session with /model set <name>:<model>, the model picker, or /model search, and the choice persists across restarts.

[providers.gemma]
name = "Local Gemma (llama.cpp)"      # optional display name
type = "openai"                       # wire protocol: openai | anthropic | openai-codex | google-antigravity (default openai)
base_url = "http://127.0.0.1:8080/v1"
api_key = "local"                     # optional literal key (local servers ignore it but it must be non-empty)
# api_key_env = "GEMMA_API_KEY"       # optional: read the key from this env var instead
models = ["gemma-4-26B-A4B"]          # model ids this provider exposes
context_window = 65536                # optional, shown in the picker

Then, from any directory where this config applies:

/model set gemma:gemma-4-26B-A4B

A custom provider rides on the wire protocol named by type, so an OpenAI-compatible server needs no other plumbing. Put the block in ~/.config/tug/config.toml to make the provider available globally (in any project without its own ./tug.toml). To start a local server, see scripts/llama-gemma.sh, which serves Gemma via llama.cpp with -hf/-hfd and MTP speculative decoding.

The built-in system prompt lives in src/prompt.rs.

Skills

At startup, Tug scans these skill directories in order and keeps the first skill for each duplicate name:

  1. ./.tug/skills/
  2. ~/.tug/skills/
  3. ~/.agents/skills/
  4. ~/.claude/skills/
  5. ~/.config/opencode/skills/
  6. ~/.codex/skills/
  7. ~/.pi/agent/skills/

Each skill directory must contain a SKILL.md file with Agent Skills YAML frontmatter. Tug loads the name, description, and exact SKILL.md path at startup and appends them to the system prompt as:

<available_skills>
  <skill>
    <name>skill-name</name>
    <description>What the skill does and when to use it.</description>
    <location>/absolute/path/to/skill-name/SKILL.md</location>
  </skill>
</available_skills>

Tug also instructs the model to use the read tool to load a skill file when the task matches its description, and to resolve relative paths inside a skill against the skill directory.

Tug also includes the session working directory and current date in the system prompt for new sessions. When you resume an existing session, Tug reuses the original persisted system prompt instead of regenerating it, so prompt caching is not invalidated by a new date string.

Prompt Templates

Tug supports Pi-style prompt templates. Place Markdown templates in:

  1. ./.tug/prompts/
  2. ~/.config/tug/prompts/

Type / followed by the template name in the composer to expand it before Tug persists or sends the prompt. review.md becomes /review.

Templates support the same substitutions as Pi:

  • $1, $2, ... for positional arguments
  • $@ or $ARGUMENTS for all arguments joined with spaces
  • ${@:N} for arguments starting at position N (1-indexed)
  • ${@:N:L} for L arguments starting at position N

Example:

---
description: Review a file
---
Review this file carefully: $1

Typing /review src/main.rs expands to Review this file carefully: src/main.rs.

Z.AI Coding Plan

Tug works with Z.AI through its OpenAI-compatible coding endpoint.

Example local config:

type = "openai"

[openai]
model = "glm-5.1"
base_url = "https://api.z.ai/api/coding/paas/v4"

Then export your key:

export TUG_API_KEY="your-zai-key"
cargo run

The repos local tug.toml is set up this way by default.

DeepSeek

Tug works with DeepSeek through its OpenAI-compatible API.

Example local config:

type = "openai"
thinking = "off"

[openai_compatible]
model = "deepseek-chat"
base_url = "https://api.deepseek.com"

Then export your key:

export DEEPSEEK_API_KEY="your-deepseek-key"
cargo run

DeepSeek is also available through /model search deepseek and /model set deepseek:<model>. The catalog models currently include deepseek-chat, deepseek-reasoner, deepseek-v4-flash, and deepseek-v4-pro. For deepseek-v4-flash and deepseek-v4-pro, Tug maps /thinking off to DeepSeek thinking.type = disabled, /thinking low|medium|high to reasoning_effort = high, and /thinking xhigh to reasoning_effort = max. Older reasoning models such as deepseek-reasoner reason by model choice and do not expose an effort control in Tug.

OpenCode Go thinking dialects

opencode-go is an OpenAI-compatible gateway in front of several non-OpenAI model families, and each family speaks a different thinking dialect. Tug picks the request shape per model rather than sending OpenAI's nested reasoning object to all of them (the gateway rejects that with extra parameter: reasoning):

  • GLM, MiniMax, MiMo, and Kimi models use the flat chat-completions reasoning_effort = low|medium|high field (xhigh clamps to high).
  • deepseek-v4-pro and deepseek-v4-flash use the DeepSeek dialect (thinking.type plus reasoning_effort), so effort control works the same as on api.deepseek.com.
  • qwen3.* models use Qwen's enable_thinking toggle.

Models served through opencode-go's Anthropic-messages endpoint (qwen3.7-max, qwen3.7-plus, minimax-m3) use the Anthropic thinking controls described below.

Anthropic

Tug also supports Anthropics API directly.

Example config:

type = "anthropic"
model = "claude-sonnet-4-0"

[anthropic]
base_url = "https://api.anthropic.com"

Then export your key:

export ANTHROPIC_API_KEY="your-anthropic-key"
cargo run

Tug also supports /thinking off|low|medium|high|xhigh for Anthropic sessions, plus Ctrl+X then L for the in-app thinking effort picker. For Claude 4.6 models this maps to Anthropic's native effort plus adaptive thinking. For older Claude models Tug uses extended thinking with a mapped token budget. Tug renders returned thinking inline in the transcript, persists it in session JSONL files, and replays it on resume so follow-up turns can resend the same assistant reasoning blocks.

OpenAI Codex

Tug can talk to the ChatGPT Codex backend using saved OAuth credentials.

Example config:

type = "openai-codex"
model = "gpt-5.5"

[openai_codex]
base_url = "https://chatgpt.com/backend-api"

Then start Tug and run:

/login

Current behavior:

  • Tug stores the Codex OAuth credential in ~/.config/tug/auth.json or $XDG_CONFIG_HOME/tug/auth.json
  • Tug refreshes the access token automatically when it expires
  • Tug supports /model set openai-codex:<id> for a built-in list of Codex models
  • Tug supports /thinking off|low|medium|high|xhigh and Ctrl+X then L for selecting Codex reasoning effort, sent on each turn
  • Tug renders Codex reasoning inline, persists it in session transcripts, and reuses it on resumed turns
  • the in-app login flow currently depends on the browser callback reaching http://localhost:1455/auth/callback
  • manual paste fallback is not implemented in the TUI flow yet

Google Antigravity

Tug can talk to the Google Cloud Code Assist ("Antigravity") sandbox, which exposes Gemini 3, Claude, and GPT-OSS models through a Google account OAuth login.

Example config:

type = "google-antigravity"
model = "gemini-3-flash"

[google_antigravity]
base_url = "https://daily-cloudcode-pa.sandbox.googleapis.com"

Then start Tug and run:

/login antigravity

Current behavior:

  • Tug stores the Antigravity OAuth credential (access token, refresh token, and Cloud Code Assist projectId) in ~/.config/tug/auth.json or $XDG_CONFIG_HOME/tug/auth.json under the google-antigravity key
  • Tug refreshes the access token automatically when it expires
  • Tug supports /model set google-antigravity:<id> for a built-in list of models
  • Tug supports /thinking off|low|medium|high|xhigh, mapped to Gemini 3 thinkingLevel and to a token budget for other models
  • Tug renders Antigravity reasoning inline and round-trips tool calls across turns
  • the in-app login flow depends on the browser callback reaching http://localhost:51121/oauth-callback
  • on 403/404 the provider cascades across the daily → autopush → prod Cloud Code Assist endpoints

Model Switching

Tug supports live model switching through slash commands backed by the public models.dev catalog.

Available commands:

  • /model: list tool-capable models for the current provider catalog
  • /model search <query>: search across supported catalogs
  • /model set <provider>:<id>: switch the live session to that provider, endpoint, and model
  • /model set <id>: same as above when the model id is unambiguous across supported catalogs
  • Ctrl+L: open the in-app model picker and switch without leaving the TUI
  • Ctrl+X then L: open the in-app thinking effort picker

Important behavior:

  • model switching is session-only; it does not rewrite tug.toml
  • Tug saves the last selected session target to session.json in the Tug config directory
  • Tug may save the current session file there, but startup still begins with a fresh session
  • Tug uses the provider api, env, and doc metadata from models.dev
  • switching providers also switches the live API base URL and API-key lookup rules
  • you can override the endpoint for a specific models.dev selector with [models_dev.overrides."provider:model"]
  • you can also set a provider-wide override with [models_dev.overrides.provider] or [models_dev.overrides."provider:"]
  • custom configured models remain separate from models.dev; Tug only marks a model as current in the catalog when it was explicitly selected from the catalog
  • Tug caches the downloaded catalog in ~/.config/tug/models.json or $XDG_CONFIG_HOME/tug/models.json
  • on startup, if that file does not exist yet, Tug downloads it immediately
  • the cache is reused for up to 12 hours and refreshed from the network after that
  • if refresh fails and a cached catalog exists, Tug falls back to the cached catalog

Sessions

Tug now stores chat sessions as append-only JSONL files under the Tug config directory, using the same broad session-file shape as pi-mono:

  • $XDG_CONFIG_HOME/tug/sessions/--<cwd>--/*.jsonl
  • ~/.config/tug/sessions/--<cwd>--/*.jsonl

Each session file starts with a session header and then appends message entries for user/assistant turns plus Tug-specific custom entries for tool rows, usage accounting, compaction checkpoints, and session target changes.

Available session controls:

  • /session resume: open the in-app session picker for the current workspace
  • /resume: alias for /session resume
  • /session new: start a fresh empty session with the current model
  • /new: alias for /session new
  • /compact: summarize older conversation into a reusable checkpoint immediately
  • /thinking: show the current thinking level
  • /thinking off|low|medium|high|xhigh: change the session thinking level
  • /login: open the login provider picker (Codex or Antigravity)
  • /login codex: start OpenAI Codex OAuth login directly
  • /login antigravity: start Google Antigravity OAuth login directly
  • /logout: clear saved OpenAI Codex OAuth credentials
  • /logout antigravity: clear saved Google Antigravity OAuth credentials
  • Ctrl+R: open the in-app session picker

Tug also supports Pi-style automatic compaction. When a completed turn reports context usage close to the model window, Tug summarizes older conversation into a checkpoint and future turns send that summary plus the recent messages. The defaults are:

  • reserve_tokens = 16384
  • keep_recent_tokens = 20000
  • context_window is looked up from the models.dev catalog at startup and after model switches; the hardcoded fallback is 200000 for Anthropic and 128000 for OpenAI and Codex

You can override those values in tug.toml:

[compaction]
enabled = true
reserve_tokens = 16384
keep_recent_tokens = 20000
# context_window = 128000

Tug also shows an inline slash-command picker directly from the composer when you start typing /. Use Up / Down to move through matches, Tab to complete a command into the composer, and Enter to complete or run an exact command.

On startup, Tug always begins with a fresh in-memory session. The JSONL file is created only after the first user message is sent. Older sessions remain available through /resume, /session resume, or Ctrl+R.

Supported catalogs today:

  • openai
  • anthropic
  • openai-codex
  • google-antigravity
  • zai-coding-plan
  • zai
  • zhipuai
  • opencode-go
  • crof

API key lookup for /model set follows this order:

  • TUG_API_KEY
  • provider env vars from models.dev such as OPENAI_API_KEY, ANTHROPIC_API_KEY, ZHIPU_API_KEY, CROF_API_KEY, or OPENCODE_API_KEY
  • provider-specific fallback env vars already supported by Tug
  • the current session API key, but only when the provider type stays the same

So if you switch from OpenAI to Anthropic, Tug will not silently reuse your OpenAI key.

Tools

Tug exposes the following tools to the model:

  • read: read a text file with optional offset and limit
  • edit: exact text replacement in a single file, modeled after Pis edit contract (set edit_hashline = true in config to switch to the hashline line-anchored patch form)
  • write: write a full file, creating it or replacing its contents
  • grep: code search using rg
  • find: file and directory name search using fd
  • bash: shell commands in the current workspace
  • python3: execute Python 3 code in the current workspace
  • terminal_start: start a long-running interactive terminal session in the background (tmux-backed)
  • terminal_read: read recent output from a background terminal session
  • terminal_send: send text or tmux-style keys (e.g. C-c, Enter) to a session
  • terminal_stop: stop a background terminal session
  • terminal_list: list active tool-managed terminal sessions
  • task: spawn a subagent with its own agent loop and context; returns an id to collect the result later
  • wait: block until one or more background subagents finish and return their results

Notes:

  • edit is intentionally strict. Replacements must match exactly and must not overlap.
  • edit_hashline mode swaps the edit tool for hashline patches anchored by tags emitted from read/grep; it is opt-in and off by default.
  • grep depends on rg being available on the system.
  • find depends on fd being available on the system.
  • bash runs inside the current workspace and returns combined command output.
  • terminal_* tools depend on tmux being available on the system.
  • python3 depends on python3 (or python) being available on the system.
  • task and wait are subagent tools. Subagents run their own agent loop with isolated context and cannot spawn further subagents. Each subagent definition lives in a YAML-frontmattered markdown file and can specify a dedicated model and optional tool allowlist.

Keyboard Controls

  • Enter: send prompt (steers the running agent when a response is in progress)
  • Alt+Enter: queue a follow-up prompt to send after the running agent finishes
  • Alt+Up: pop the latest queued follow-up back into the composer
  • Shift+Enter: newline
  • Ctrl+J: newline fallback
  • Ctrl+G: open draft in $EDITOR
  • !command: run a local shell command and add its output to future model context
  • !!command: run a local shell command without adding its output to model context
  • Ctrl+L: open the model picker
  • Ctrl+R: open the session picker
  • Tab: complete the selected inline slash command
  • Esc: clear queued steering first, then interrupt current model run
  • Ctrl+C: clear prompt, then quit on a second consecutive press
  • Ctrl+U: clear prompt
  • Left / Right / Up / Down: move inside the composer
  • Up: recall the previous sent prompt when the cursor is already on the first draft line
  • Down: move toward newer recalled prompts, then restore the current draft
  • Home / End: jump within the current line
  • PgUp / PgDn: scroll the conversation

Caching

Tug surfaces cache usage in the footer when the provider returns token usage with cached prompt information.

Important distinction:

  • For OpenAI, prompt caching is automatic on supported models.
  • For generic OpenAI-compatible providers, caching behavior is provider-specific.
  • Tug enables Anthropic request-side automatic caching for Anthropic models through rig-core.
  • In rig-core (currently 0.37.0), Anthropic caching is handled on the request path for Anthropic sessions; the OpenAI-compatible chat-completions path does not have the same explicit caching APIs.

So Tug can show cache hits and cache writes when the backend reports them, enables automatic caching for Anthropic sessions, and still does not force caching on for third-party OpenAI-compatible providers.

Architecture

  • src/main.rs: terminal lifecycle, event loop, and command dispatch
  • src/app.rs: app state, feed state, input handling, and AgentEvent processing
  • src/ui.rs: message feed and footer rendering
  • src/agent.rs: model runtime, streamed tool events, and subagent spawning
  • src/tools.rs: local tool implementations (read, edit, write, grep, find, bash, python3, terminal_*, task, wait)
  • src/config.rs: config resolution and environment overrides
  • src/prompt.rs: built-in system prompt, AGENTS.md loading, and skill attachment
  • src/session.rs: append-only JSONL session persistence and resume
  • src/compaction.rs: automatic and manual context-window compaction
  • src/models.rs: models.dev catalog caching, model resolution, and context-window lookup
  • src/search.rs: fff-search index backing grep and find tools
  • src/subagents.rs: subagent definitions, YAML frontmatter loading, and runtime manager
  • src/codex.rs: OpenAI Codex provider (OAuth, SSE, WebSocket streaming)
  • src/antigravity.rs: Google Antigravity provider (Google OAuth, Cloud Code Assist streaming)
  • src/acp.rs: Agent Client Protocol (ACP) server implementation

Status

Active development. Most core features are in place. Areas still evolving:

  • richer edit previews and approvals
  • provider-specific caching knobs where available
  • ACP server stability and feature coverage
  • more subagent orchestration options