A Rust terminal application that bridges Neovim and AI agents via libghostty's VT engine.
Via is my attempt at making terminal-only AI-assisted coding better. There are plenty of Neovim plugins that put the AI assistant in a side pane, but my experience working with them wasn't very enjoyable: too much flickering, weird pane positioning, and so on. I thought wrapping Neovim + an agent in a dedicated terminal window could deliver a smoother UX.
The idea is simple: glue the best code editor together with the AI coding harness of your choice, and add a few features that make the combination feel like a lightweight IDE.
By default you get an editor pane and an agent pane that automatically adjust when the window is resized, plus simple shortcuts to switch panes or give either one fullscreen focus. The agent pane starts with keyboard focus. Neovim restores the saved session for this directory when one exists, including sessions written by LazyVim's persistence plugin.
It is currently my daily driver for AI-assisted coding. I use it mostly with cursor-agent (~80%) and opencode (~20%), and I also test other agents like claude-code and crush.
- Send the current selection or buffer with
<leader>ab(or:ViaBufferSend) - Multiple agents via the ACP protocol
- Agent orchestration (e.g. spawn a reviewer, take the feedback, apply changes)
- Hold Ctrl in the agent pane to highlight clickable filenames, symbols, and OSC 8 hyperlinks.
- Ctrl+click on a filename to open that file in Neovim and focus the Neovim pane (enter fullscreen Neovim if the agent was fullscreen; otherwise keep the split).
- Ctrl+click on a symbol to open the symbol search pane in Neovim with the same focus behavior.
- Ctrl+click on an external OSC 8 hyperlink to open it in the system browser.
The default layout is Neovim plus one interactive PTY agent (--agent opencode). For everyday edits you work in
that pane; no ACP process runs at startup.
Orchestration is opt-in. Spawn an ACP orchestrator and helpers when you need automatic multi-agent handoff:
via agent spawn --id orchestrator # preset role: orchestrator
via agent spawn --id reviewer # preset role: reviewer
via agent spawn --id coder # preset role: coder
via agent send --to reviewer -m "review this diff"Spawned agents resolve to ACP when the configured driver supports it (opencode → opencode acp). If your main driver
doesn't support ACP (e.g. claude, crush), you can pick a different agent for orchestration with --acp-agent. The
primary PTY pane keeps the id agent; the coordinator is orchestrator.
Design policy: Via provides the transports (panes, bus, ACP). Agents orchestrate themselves using skills and the
via agent CLI; multi-agent workflows are not encoded in the mediator. Install the via-editor, via-agents, and
via-orc skills yourself (see Build). Orchestration policy — main interactive agent stays the human point of
contact — lives in via-orc.
Spawned helpers are independent ACP panes. Each [agents.<id>] preset can set a role, an optional model, and an
optional command — so you can mix drivers and models per role (e.g. a planning orchestrator on a strong model, a
fast reviewer, a coding specialist on Composer).
Configure defaults in ~/.config/via/via.conf:
# Primary PTY agent (interactive pane). Not an ACP process.
agent = "agent"
# Default ACP driver for spawned helpers when a preset omits `command`.
# Used when `agent` is not in the built-in ACP table (opencode, agent, cursor-agent).
# acp_agent = "cursor-agent acp"
[agents.orchestrator]
role = "orchestrator"
model = "claude-opus-4-8-thinking-high"
[agents.reviewer]
role = "reviewer"
model = "gpt-5.3-codex-fast"
[agents.coder]
role = "coder"
model = "composer-2.5"
# command = "opencode" # optional; see “Command resolution” belowCommand resolution (you usually do not need command = "… acp"):
Preset command |
Resolved spawn |
|---|---|
| (omitted) | Primary agent value, upgraded to ACP when known (opencode → opencode acp, agent → agent acp) |
opencode or agent |
Same binary with acp appended automatically |
opencode acp |
Used as-is (explicit is fine) |
cursor-agent acp |
Used as-is — pick a different driver than the primary PTY agent |
Built-in ACP upgrade covers opencode, agent, and cursor-agent only. For other primaries (e.g. claude, crush),
set acp_agent globally or command = "… acp" on each helper preset.
Model resolution (applied after session/new via ACP session/set_config_option):
via agent spawn --model …/via agent assign --model …(one-shot override)[agents.<id>] model = "…"invia.conf- Agent binary default
The active model is shown in the helper pane header. Requires an ACP-capable spawn and an agent that exposes a model
config option (support varies).
Listing model slugs (via does not ship a model picker yet — ask the driver):
agent models # cursor-agent slugs, e.g. composer-2.5
opencode models # opencode ids, e.g. opencode/claude-opus-4-8Use the slug (left column / full id), not the display name, in via.conf or --model.
One-shot overrides (orchestrator or any pane with VIA_SESSION):
via agent spawn --id coder --model composer-2.5
via agent assign --id coder --model gpt-5.3-codex --task abc -m "implement fix"--model on assign applies only when via spawns the pane; it does not retune an already-running helper. Terminate
and respawn to change model.
Example: mixed setup in one session
agent = "agent" # you work in the PTY pane
[agents.orchestrator]
role = "orchestrator"
model = "claude-opus-4-8-thinking-high"
[agents.reviewer]
role = "reviewer"
model = "gpt-5.3-codex-fast"
[agents.coder]
role = "coder"
model = "composer-2.5"
command = "opencode" # coder uses opencode ACP while primary stays cursor-agentvia agent spawn --id orchestrator # opus planner
via agent spawn --id reviewer # fast codex reviewer
via agent spawn --id coder # composer on opencode
# or override just this coder:
via agent spawn --id coder --model gpt-5.3-codex-highNavigation
Alt+1focuses the editor. Pressing it again while the editor is already focused switches the editor between 50% and 2/3 of the split (the agent is then 50% or 1/3): width in a vertical split, height in a horizontal split.Alt+2..9focuses the corresponding agent pane (Alt+2 is the first agent). Pressing that shortcut again while the same agent is already focused switches the agent pane between 50% and 33% of the split, on the same axis.Alt+Shift+1..9maximizes that pane (Alt+Shift+1 for the editor, Alt+Shift+2 for the first agent, etc.).Alt+Jtoggles the split direction.
Lua API for plugins
require('via') is available inside any via-launched Neovim session (the module is injected into
~/.local/share/via/lua/ at startup). Example usage:
local via = require('via')
via.agent.spawn("reviewer", "reviewer") -- spawn a reviewer pane
via.agent.spawn("coder", "coder", nil, "composer") -- optional 4th arg: model slug
via.agent.del("reviewer") -- terminate a sub-agent when done
via.agent.send("reviewer", "please review this diff", false) -- send without stealing focus
via.agent.send("orchestrator", "hello orchestrator") -- send after spawning orchestrator
for _, agent in ipairs(via.agent.list()) do print(agent.id) end -- discover running agentsAgent-to-agent communication (the agent bus)
Agents running inside via can discover, spawn, and message each other through the via agent CLI (documented for agents
in the via-agents skill; orchestration policy in via-orc). Each agent pane gets VIA_AGENT_ID and VIA_AGENT_ROLE
in its environment.
via agent whoami # this agent's id/role/session
via agent list # agents running in this session
via agent spawn --id reviewer --role reviewer # ask via to open a reviewer pane
via agent spawn --id coder --model composer # override model for this spawn
via agent assign --id coder --model composer --task abc -m "implement"
via agent send --to reviewer -m "review this" # queue a message + deliver it
via agent inbox # read (and clear) your mailboxCoordination notes:
- PTY panes (
agent, or explicit non-ACP spawns) are mailbox-only on send. - ACP spawned agents receive prompts automatically.
- Orchestration spawns require a known ACP mapping for the configured driver.
This is still an experimental project. Although I use it as my daily "IDE", it has some rough edges. Some things I have planned:
- Review process: make it easier to switch between agent/review and send feedback to the agent directly from the Neovim pane (may use an existing Neovim plugin for this)
via is primarily developed and tested on Linux (Wayland compositors such as Hyprland on Omarchy, with an X11 fallback via winit). Other Linux distributions and operating systems (macOS, Windows) are not regularly tested; you will likely need to build from source. See CONTRIBUTING.md for prerequisites and platform notes.
- Rust
- Zig 0.15.2 — required by the vendored
libghostty-vtbuild. git(used by thelibghostty-vt-sysbuild script to fetch ghostty sources).
If you use mise, the project's mise.toml pins the
correct Zig version automatically:
mise installOtherwise install Zig 0.15.2 manually and put it on your PATH.
cargo build --release
./target/release/vialibghostty-vt is statically linked into the binary, so no runtime library search path setup is needed.
Via ships three agent skills under skills/ (via-editor, via-agents, via-orc). Via does not
install them — pick whatever fits your toolchain:
# Node (vercel-labs/skills)
npx skills add filipenf/via -g
# No Node — Rust CLI (https://github.com/olamedia/skills-rs)
skills add filipenf/via --global
# Manual: clone or curl, then copy into agent skill roots
git clone https://github.com/filipenf/via.git /tmp/via
cp -R /tmp/via/skills/via-* ~/.agents/skills/
# also e.g. ~/.cursor/skills/, ~/.claude/skills/, ~/.config/opencode/skills/
via plugin status # check presence across known roots
via plugin path # list those rootsFork and edit installed copies freely. Startup warns if any core skill is missing for the session agent; it never writes skill files.
cargo test # unit tests live next to the code
cargo fmt -- --check
cargo clippy -- -D warningsSee CONTRIBUTING.md for the full development guide (prerequisites, how to run a local session, good first areas, benchmarks, etc.) and ARCHITECTURE.md for an overview of the Mediator/UI/ACP data flows and performance-sensitive surfaces.
cargo test is the primary regression guard today; we are expanding Criterion micro-benchmarks for layout calculations
and link/OSC 8 scanning (the two most user-visible hot paths).
User-facing settings can be provided as CLI flags, environment variables, or in ~/.config/via/via.conf using TOML
syntax. Precedence is:
CLI flags > environment variables > via.conf > built-in defaults
Example config:
nvim = "nvim"
agent = "agent" # PTY primary; spawned helpers resolve to ACP separately
# acp_agent = "cursor-agent acp" # when primary is not ACP-capable (claude, crush, …)
agent_pane_cols = "80:120"
review_backend = "nvim"
[agents.orchestrator]
model = "claude-opus-4-8-thinking-high"
[agents.reviewer]
model = "gpt-5.3-codex-fast"
[agents.coder]
model = "composer-2.5"
# Optional: extend ACP permission auto-approve (built-ins always on).
[auto_approve]
commands = ["echo"]
kinds = ["fetch"]Per-agent presets (role, optional command, optional model) are documented in
Multi-agent orchestration — including how to mix drivers, list model slugs, and override
with --model at spawn time.
When spawned ACP helpers call session/request_permission, via can answer safe requests automatically so orchestration
does not stall. Built-in allows (always on, not configurable):
- Any shell command whose executable is
via(all subcommands) - ACP tool kinds
readandsearch - Read-only shell:
ls,pwd,cat,head,tail,rg,fd, andgitwithstatus,diff,log, orshow
Everything else still opens the permission modal (via never auto-denies). Extend allows with [auto_approve] in
via.conf — extra commands (base executable names) and kinds only add to the built-in list. There are no CLI flags
for auto-approve today; configure it in via.conf only.
Equivalent CLI/env names:
--nvim/VIA_NVIM--agent/VIA_AGENT--acp-agent/VIA_ACP_AGENT--agent-pane-cols/VIA_AGENT_PANE_COLS--review-backend/VIA_REVIEW_BACKEND
Use --persist to write the resolved user-facing config to via.conf before running. For example, this writes
agent = "opencode" (PTY primary) plus defaults:
via --agent opencode --persistNeovim bridge scripts (nvim/*.lua) are embedded at compile time; the context bridge is written to via's data directory
($XDG_DATA_HOME/via, or ~/.local/share/via) when needed. Override with VIA_NVIM_CONTEXT_BRIDGE if you want to load
a custom script from disk during development.
Create and publish a GitHub release for a tag such as v0.1.0. The release workflow builds via in release mode,
packages the binary and README into via-<tag>-linux-x86_64.tgz, and uploads the archive plus its SHA-256 checksum to
the release.
On Linux, via detaches to avoid keeping the terminal waiting for it to finish. Runtime files for each live process live
under $XDG_DATA_HOME/via/instances/<pid>/ (default ~/.local/share/via/instances/<pid>/). Stale instance directories
can be pruned in bulk from that folder.
The runtime root is also exposed as VIA_RUNTIME_ROOT for scripts. To skip detaching and keep the terminal attached
(for example during development), set VIA_FOREGROUND to any value.
With a PTY agent, vertical split mode keeps the agent at its minimum width (default 80 columns, up to 100) and gives any extra columns to the editor. Focusing the agent again switches it between 50% and 33% of the window. Focusing the editor again switches it between 50% and 2/3, so the agent is 50% or 1/3 (see Navigation). Override the automatic width with:
VIA_AGENT_PANE_COLS=60:120 cargo run
cargo run -- --agent-pane-cols 100A single value pins the agent pane to that width; min:max gives a range.
The editor pane keeps at least 80 columns. When the window cannot fit both that and the agent minimum (default 80 + 80 = 160 columns total), via collapses to editor fullscreen. Widening enough to fit both restores the split unless you chose fullscreen manually (Alt+Shift+1).
via follows the window scale factor reported by winit/Wayland when converting Ghostty's point-based font-size into
physical pixels. On fractional-scale setups this can differ from the compositor scale you configured, so font output can
change significantly between displays.
Use these environment variables to test font rendering without code changes:
VIA_FONT_SCALE=1.6 cargo run
VIA_FONT_HINTING=enabled cargo run
VIA_FONT_COVERAGE_BOOST=0 cargo runPossible tweaks:
VIA_FONT_SCALE: overrides the reported window scale used for font DPI, e.g.1.33,1.6, or2.0.VIA_FONT_PIXEL_SCALE: multiplies the computed glyph pixel size after DPI conversion.VIA_CELL_WIDTH_SCALE: multiplies the computed terminal cell width only.VIA_CELL_HEIGHT_SCALE: multiplies the computed terminal cell height only.VIA_BASELINE_RATIO: controls baseline placement inside each cell. Default:0.73.VIA_FONT_HINTING: sets cosmic-text metrics hinting. Values:enabledordisabled.VIA_FONT_COVERAGE_BOOST: controls via's glyph coverage boost. Default:0.2; use0to disable.VIA_FONT_SHAPING: selects cosmic-text shaping. Values:advancedorbasic.
See CONTRIBUTING.md for build instructions, testing, architecture pointers, and how to submit changes. Issues and PRs are welcome — small, focused contributions (layout, links, ACP surface, review backends, diagnostics, docs, tests) are especially appreciated.
Licensed under the Apache License, Version 2.0. See the LICENSE file for details.