The Veyyon handbook
Veyyon runs in your terminal and edits real code. Bring your own model keys; the harness is tuned for coding work, not chat theater.
This handbook is for everyone who uses Veyyon or wants to understand it.
- Why Veyyon: value, design map, and benefits you should feel first.
- Install and first session: install, providers, quickstart, configuration.
- Everyday features: editing, sandbox, models, sessions, themes.
- Power features: plan mode, goals, branching, MCP, plugins, memory, profiles.
- How it works: deeper engineering account for contributors.
If you read nothing else: What Veyyon gives you, Getting started, Editing and repair, Models and providers.
What Veyyon is, in one paragraph
Veyyon is a fork of oh-my-pi: TypeScript and Bun for the CLI, TUI, tools, providers, and session loop; Rust natives for grep, PTY, and hashline edits. Install with bun install -g @veyyon/pi-coding-agent or bun dev from source. The command is veyyon (short alias vey). Config and state default to ~/.veyyon.
Shipped today: hashline edits, mnemopi memory, model roles, session trees, MCP, skills, and plan/goal modes.
Why it is different (shipped vs planned)
- Edits that land. Hashline and model-tuned edit tools with native verification; fewer retry loops on bad diffs.
- Explicit model slots. Pick the model you talk to, the model for subagents, and the model for compaction — three plain choices, no
default-chain indirection. - Honest interface. Veyyon Dark uses the silver palette; plan/goal modes and tool approval tiers are engine features.
Spec — not shipped: the full schema-based tool-call repair cascade, a unified single-write-path proof, and self-contained profiles. See What makes Veyyon different.
On honesty
This book states plainly what is built and what is planned. Pages marked Spec — not shipped describe target design not yet in the product. Credits: Acknowledgements.
Why Veyyon
This section explains why Veyyon exists and what it gives you. Veyyon is a coding agent that runs in your terminal, edits real code, and lets you bring your own model keys. The pages here are the value proposition: what changes, why it is faster and cheaper, and how the design behind it keeps the agent honest.
-
What Veyyon gives you introduces the problem Veyyon solves and the concrete benefits you get from a harness built around each model’s strengths.
-
What makes Veyyon different is the short design map behind those benefits: hashline-first editing, per-model harnesses, tiered tool approval, and stop-when-green control flow.
-
Performance shows how fewer retries, an edit format that open models emit reliably, and a lightweight harness add up to faster turns and lower cost.
-
Edits that land the first time explains how Veyyon repairs malformed tool calls and normalizes diffs so a change does not bounce back for a retry.
-
Lower token cost and faster turns details how bounded tools, compaction, and early verification keep the context focused on what still matters.
-
Model choice without lock-in covers the provider-agnostic engine and per-model profiles that let you point Veyyon at any supported model or API.
-
Safety and honesty you can see describes the visible sandbox, loud failures, and honest telemetry that make the agent safe to use in real repositories.
Where to go next: install Veyyon with Install, then read the Core concepts section to learn how sessions, context, and the harness fit together.
What Veyyon gives you
Veyyon runs in your terminal and edits real code on your keys. The harness is built so capable models waste fewer turns on bad edits and opaque scaffolding.
The problem Veyyon solves
Most coding agents wrap every model in the same generic scaffolding. A model trained for one edit shape is forced into another. The result is wasted turns, malformed tool calls, diffs that fail to apply, and token bills that climb with every retry. The weights are often capable; the scaffolding holds them back.
Veyyon (oh-my-pi lineage) cuts that waste: hashline edits tuned per model, native search and read paths, provider-agnostic configuration, and a TUI that keeps sandboxing and approvals visible.
What you get today
- Edits that land. Hashline and related edit tools with model-specific prompts; fewer retry loops on bad diffs.
- Bring your own key. Dozens of providers via the bundled catalog; no vendor lock-in.
- Per-role models. Assign fast, thinking, vision, task, and other roles without reconfiguring every session.
- A real agent loop. Read, search, edit, bash, LSP, browser, MCP, compaction, and subagents, all inside approval and sandbox policy you control.
- Session trees. Branch, fork, and navigate conversation history without losing abandoned paths.
- Memory. mnemopi-backed recall across sessions (see Memory).
- Lower cost. Fewer format retries and leaner reads mean fewer tokens for the same outcome.
Who Veyyon is for
- Developers who want a capable terminal agent on their own keys.
- Teams running open or gateway models with a harness tuned for coding, not chat-only UIs.
- Anyone who wants visible sandbox boundaries and honest tool failures.
Where to go next
What makes Veyyon different
These ideas set Veyyon apart. Each is shipped unless noted as Spec — not shipped.
For a reader-first pass, start with the benefit chapters. This page is the compact design map.
Hashline-first editing
Open models emit reliable hashline patches when the harness matches their training. Veyyon’s edit
and write tools apply hashline patches with verification before bytes hit disk, so an edit either
lands exactly or fails loudly with recovery hints.
See Editing and repair and The hashline edit engine.
Tool approval by tier (autonomy ladder)
tools.approvalMode (plan, ask, auto-edit, yolo; legacy always-ask / write aliases) and
per-tool tools.approval overrides gate read, write, and exec tiers. Bash can force prompts on
destructive patterns even in permissive modes.
See docs/approval-mode.md and /settings → Advanced → Safety.
Every model in the harness it fits
Model selection is three explicit slots — the interactive model (chosen with /model), the
subagent model, and the compaction model (both set in settings). No default model stands in
for the others. Optional named roles live in settings, scoped per profile, for anyone who wants
specific work types pinned to specific models. Prompts and tool exposure adapt per model and per agent
kind (main vs subagent).
Provider-agnostic runtime
The agent loop, TUI, session format, MCP, skills, hooks, and extensions are provider-neutral. You configure providers in ~/.veyyon/agent/config.yml or via /setup / /providers.
Control flow in the harness
Compaction, goal continuation, plan mode, vibe mode, and task subagents are engine features, not persona-only instructions. Goal mode can auto-continue idle sessions toward an objective; plan mode drafts a plan file before mutating the repo.
Spec — not shipped (documented elsewhere)
- A full schema-based tool-call repair cascade. General schema repair on tool calls is shipped; the broader cascade is planned. See Repair overview.
- Self-contained profiles that own isolated MCP/skills copies per profile. Today profiles relocate the
agent directory to
~/.veyyon/profiles/<name>/agent/. - A top-level
veyyon doctorinstall-health command. Useveyyon plugin doctorand/debugtoday.
Where to go next
Performance
Speed in a coding agent is not one number. It is the sum of many small choices: how often a change lands on the first try, how much text the model has to emit, how quickly the harness gets out of the way. Veyyon is built so those choices add up in your favor. This page explains what that means for you.
Fewer wasted round trips
The largest cost in an agent is not the model thinking. It is the model redoing work. A malformed tool call, a bad diff, a value in the wrong shape: each one normally costs a full extra round trip, where the model receives an error, apologizes, and tries again. Veyyon repairs the common mistakes before they reach you, so the work lands on the first attempt. Fewer round trips means a faster answer and a smaller bill.
An edit format that does not fight the model
When the edit format is hard to emit correctly, a weak model burns its turns on retries and its budget on extra output. Veyyon uses an edit format that open models produce reliably, and it normalizes and applies edits in a single pass over the file, so editing a large file with many changes stays fast as the file grows.
A harness that gets out of the way
Veyyon is a lightweight Bun and TypeScript harness with Rust natives on the hot paths (grep, PTY, hashline edits). It starts quickly, it streams output as it arrives, and it does the internal bookkeeping of a turn without copying your data more than once. The work the harness does between you and the model is kept to the minimum, so the time you wait is the model’s time, not the harness’s.
Measured, not asserted
Every speed claim in Veyyon is backed by a benchmark that runs on every change. A change that would make a hot path slower fails the build before it ships. We hold even a correct fallback path to a speed bound, because a path that is correct but slow is still a path that wastes your time. The internal record of each change and its measurement is kept by the team and is not part of this public book, but the rule it follows is stated here: a performance claim that is not measured is not made.
What this adds up to
A clean edit format, automatic repair, a control flow that stops when the work is done, and a harness that copies nothing it does not have to. Individually each is small. Together they are the difference between an agent that feels heavy and one that feels instant, at a fraction of the cost of a frontier model.
Where to go next
- What makes Veyyon different is the design behind these gains.
- Getting started puts it to work.
Edits that land the first time
The most visible difference in Veyyon is simple: fewer failed edit turns.
Most coding agents lose time in small, boring ways. A model sends a tool call with one field named wrongly. It wraps a JSON array in a string. It emits a patch in the format the backend cannot carry. The agent bounces the error back to the model, the model apologizes, and the same edit gets tried again.
Veyyon removes that waste at the harness layer.
What improves
- Hashline edits anchor on
read/grepsnapshot tags for verifiable hunks. edit,write, andapply_patchmodes share sandbox and approval gates.- Stale anchors fail with recovery hints instead of silent wrong edits.
- General schema repair coerces almost-right tool calls into shape before dispatch.
Where the details live
- Editing and repair explains the user-facing behavior.
- Why repair exists explains the repair seam.
- The hashline edit engine explains the shipped edit path.
Lower token cost and faster turns
Veyyon treats cost as a product feature. The cheapest token is the one the model never has to spend.
Cost drops when the harness avoids retries, bounds tool output, keeps the working context small, preserves cache-friendly continuity, and verifies before spending more turns. These are not separate tricks. They compound.
What improves
- Bounded
read,glob, andgreptools prevent one call from flooding the context. - Tool output says exactly when and how it was truncated.
- Compaction preserves the task state instead of dropping history silently.
- The deterministic file working set survives compaction.
- Reviewer hints and verification stop wasted turns before they become expensive loops.
Why it matters
A long coding task fails when the model loses the plot. It also gets expensive when every tool call returns more text than the next decision needs. Veyyon keeps the context focused on the files, failures, and diffs that still matter.
Where the details live
- Performance explains the product-level speed story.
- Bounded reads and instant search explains the tool-output bounds.
- Compaction and project memory explains long-task context.
- Goal state explains how the harness keeps long tasks coherent.
Model choice without lock-in
Veyyon’s terminal engine is provider and API agnostic. You can point it at a local server, a direct provider API, or a compatible gateway. In every path, the harness adapts to the selected API and model instead of forcing every model through one generic shape.
What improves
- Provider details live in a data catalog, not scattered through code.
- Each backend gets the wire API and edit form it can actually carry.
- Per-model prompt order and tool exposure are assembled from the catalog.
- You can pin a specific model when you want explicit control.
- Provider-agnostic improvements such as tool repair, path repair, edit verification, goal/context handling, prompt profiles, and output bounds work with any compatible API you choose.
Why it matters
Open models are not interchangeable. A model that is strong at one language or patch shape can be weak at another. A generic harness hides those differences and pays for them in retries. Veyyon makes the differences explicit and uses them.
Where the details live
- Models and providers explains the user-facing model choice.
- Providers explains model and provider configuration.
- Execution-order prompts explains the prompt shape.
- Per-model posture explains per-model repair posture via harness profiles (bounded repair telemetry is spec, not shipped).
Safety and honesty you can see
Veyyon is useful because it can act. That only works if the boundary is visible and the results are honest.
The harness is designed to fail loudly when it cannot honor a safety rule, a schema, a config file, or a tool contract. Silent degradation is treated as a product bug.
What improves
- File writes and commands run through approval and sandbox policy.
- Tool output records truncation instead of hiding it.
- Bad config fails with context instead of falling back silently.
- Repair abstains when the schema does not prove a safe fix.
- Observability records coarse, bounded signals without logging secrets.
Why it matters
An agent that silently weakens its boundary is hard to trust. An agent that tells you exactly what it did, what it refused, and what it could not prove is much easier to use in a real repository.
Where the details live
- Configuration explains the knobs.
- Soundness and telemetry explains repair refusal and signals.
- Observability explains runtime signals.
- Architecture at a glance explains the boundaries.
Install
Veyyon ships as the npm package @veyyon/pi-coding-agent and installs the veyyon
executable. It is a TypeScript + Bun agent loop with Rust natives (@veyyon/pi-natives) for hot paths
(grep, walker, shell/PTY, hashline edits). After install, run veyyon plugin doctor.
Requirements
- Bun (recommended runtime) or a recent Node.js.
- Git — most workflows expect a repository.
- On Linux: user-namespace support for bubblewrap-based shell isolation (WSL2 yes; WSL1 no).
- On macOS: Seatbelt is used for the shell sandbox.
Install (npm / Bun)
$ bun install -g @veyyon/pi-coding-agent
$ veyyon --version
npm works too:
$ npm install -g @veyyon/pi-coding-agent
$ veyyon --version
bun install also builds @veyyon/pi-natives. Config and state default to ~/.veyyon.
After install
The first interactive veyyon opens the setup ceremony (splash → providers → glyphs → theme → outro). Force it again with veyyon setup. Re-open providers inside a session with /setup or /providers. See Getting started.
Build from source
$ git clone https://github.com/santhsecurity/veyyon.git
$ cd veyyon
$ bun setup # installs workspace deps and builds @veyyon/pi-natives
$ bun dev --version
bun dev runs the in-repo build; use it while evaluating or contributing.
Shell completions
$ veyyon completions bash|zsh|fish
Verify the install
$ veyyon --version
$ veyyon plugin doctor
$ veyyon plugin doctor --fix
veyyon plugin doctor checks plugin health and warns when optional external binaries (sd, sg,
git) or common API keys are missing. See Diagnostics.
Spec — not shipped: a top-level
veyyon doctorwith install-wide--summary/--jsonself-test. Today diagnostics are scoped toveyyon plugin doctorand the TUI/debug.
Relocate the config directory
By default Unix uses ~/.veyyon. PI_CONFIG_DIR renames the home-relative config directory, and
PI_CODING_AGENT_DIR relocates the whole agent base (config.yml, agent.db, sessions, and more):
$ export PI_CODING_AGENT_DIR=/path/to/veyyon-agent
$ veyyon plugin doctor
Layout: File locations.
First credentials
On first interactive launch, the setup ceremony (or veyyon setup) walks sign-in and API keys.
Inside a session, use /setup / /providers to re-open that panel, /login (or /login <provider>)
for OAuth and key entry, or export the provider’s environment variable and skip the interactive step.
See Authentication and Configuring providers.
Uninstall
Remove the global package:
$ bun remove -g @veyyon/pi-coding-agent
$ # or: npm uninstall -g @veyyon/pi-coding-agent
Then remove state if you want a clean machine:
$ rm -rf ~/.veyyon # irreversible: config, secrets, sessions, plugins, skills, logs
$ # if you relocated the agent base:
$ rm -rf "$PI_CODING_AGENT_DIR"
Project-local files (AGENTS.md, .veyyon/ in a repo) are not removed by deleting the
home directory — clean those per repository if desired.
To keep projects but wipe only sessions:
$ rm -rf ~/.veyyon/agent/sessions
Next
- Getting started — first interactive edit with sample terminal output.
- Model contract — choose credentials with the harness boundary in mind.
- Safety — approvals and fail-closed behavior.
- Troubleshooting / FAQ — when the doctor is not enough.
Signing in
Veyyon authenticates to whichever provider you point it at and calls provider APIs directly with keys
you supply. It has no telemetry egress of its own. Logins are provider-scoped: authenticating
anthropic does not authenticate openai, and each provider tracks its own credentials.
Sign in from the TUI
Use the interactive slash commands inside a session:
/login— opens the OAuth/key selector./login <provider>— jumps straight to one provider, e.g./login anthropic,/login openai./login <redirect-url>— completes an OAuth flow that needs a pasted callback URL./logout— opens the provider selector to remove stored credentials.
On first run, the setup ceremony (veyyon setup, or /setup / /providers later) walks the same flow.
Headless and remote hosts
For CI, servers, or a shared team credential store, use the auth broker from the shell:
$ veyyon auth-broker login <provider>
$ veyyon auth-broker status
$ veyyon auth-broker list
$ veyyon auth-broker logout
import and migrate are also available. See Providers and docs/secrets.md
for the broker model.
Using an environment variable instead
Every provider reads one or more environment variables, so a key already exported in your shell (or in
a .env file) is used without an interactive sign-in.
| Provider | Environment variable |
|---|---|
openai | OPENAI_API_KEY |
anthropic | ANTHROPIC_API_KEY (or ANTHROPIC_OAUTH_TOKEN) |
google | GEMINI_API_KEY |
deepseek | DEEPSEEK_API_KEY |
moonshot | MOONSHOT_API_KEY |
zai | ZAI_API_KEY |
openrouter | OPENROUTER_API_KEY |
xai | XAI_API_KEY |
groq | GROQ_API_KEY |
mistral | MISTRAL_API_KEY |
The full provider → variable map lives in Providers. .env files are loaded
from <cwd>/.env, ~/.veyyon/agent/.env, ~/.veyyon/.env, and ~/.env, with earlier sources winning.
How keys are resolved
When a provider needs a key, Veyyon resolves it in order (first match wins):
- A runtime
--api-keyfor the current process (never persisted). - A
models.ymlapiKeyon a custom provider. - A stored API key in the auth store.
- A stored OAuth credential (refreshed as needed).
- The provider’s environment variable (including
.env).
Stored credentials live in the auth store at ~/.veyyon/agent/agent.db (or the configured auth-broker
snapshot in broker mode). PI_CODING_AGENT_DIR relocates the agent base, and the auth store moves with
it.
Provider data is data-driven
Provider identity (display name, env var, OAuth parameters) and endpoints (base URL, API kind) come
from the bundled model catalog plus your ~/.veyyon/agent/models.yml. A new BYOK provider becomes
selectable by adding a providers: entry — not by changing code. See
Configuring providers and docs/providers.md.
See also: Models and providers and the CLI reference.
Quickstart
From install to a first approved code change in about five minutes. Full path: Getting started.
Before you start
which veyyon
veyyon --version
If missing:
bun install -g @veyyon/pi-coding-agent
Or from source: bun setup && bun dev in the repo root.
Check the environment
Shipped today: plugin and extension health checks:
veyyon plugin doctor
Optional: /debug in the TUI for interactive diagnostics.
Spec — not shipped: a top-level
veyyon doctorinstall health command. Useveyyon plugin doctorand the TUI/debugtoday. See Diagnostics and health.
Config and sessions live under ~/.veyyon/agent/ by default (VEYYON_CONFIG_DIR / OMP_CONFIG_DIR / PI_CONFIG_DIR can rename the home-relative dir).
Start your first session
cd my-project
veyyon
First interactive launch shows the setup ceremony (splash → providers → glyphs → theme → outro), then the welcome screen and composer. Resume / VEYYON_SKIP_SETUP=1 skips it. Re-open providers later with /setup or /providers, or run veyyon setup from the shell.
After that you should see the TUI composer, model indicator, and workspace path.
Ask for a small edit
> Add a name argument to greet() in greet.py, default 'world'.
Veyyon reads the file, proposes an edit or hashline change, and may pause for approval depending on tools.approvalMode. Press y to accept when prompted.
Composer conveniences
@— file/skill/plugin completion/— slash commands (/help,/tree,/settings, …)Esc— interrupt a running turn?— shortcut hints on empty composer
Next steps
You now know the loop: start veyyon, ask, approve tools, inspect diffs.
Getting started
Install Veyyon, complete the first-run ceremony, and land your first approved edit. Budget about five minutes.
1. Install
npm / Bun (recommended)
$ bun install -g @veyyon/pi-coding-agent
$ veyyon --version
From source (repo root):
$ git clone https://github.com/santhsecurity/veyyon.git
$ cd veyyon
$ bun setup
$ bun dev --version
bun setup installs workspace deps and builds @veyyon/pi-natives. Config and state default to ~/.veyyon.
Shell completions: veyyon completions bash|zsh|fish. See Install.
2. First launch — the setup ceremony
The first interactive veyyon (or an explicit veyyon setup) opens a fullscreen ceremony:
- Splash — silver wordmark reveal
- Providers — sign in to a model provider; optional web search tab
- Glyphs — Nerd Font / Unicode / ASCII for your terminal
- Theme — Titanium (default dark), Light, or browse
- Outro — handoff into the session welcome
Re-run the provider panel later with /setup or /providers inside the TUI. Skip the ceremony with VEYYON_SKIP_SETUP=1 (or resume an existing session).
API key (example): set DEEPSEEK_API_KEY in the environment, then pick a DeepSeek model in /model.
Custom gateway — add a provider in ~/.veyyon/agent/models.yml:
providers:
my-gateway:
baseUrl: https://gateway.example.com/v1
api: openai-completions
apiKey: MY_GATEWAY_API_KEY
models:
- id: claude-sonnet
name: Claude Sonnet via Gateway
contextWindow: 200000
maxTokens: 8192
OAuth providers: /login or /login anthropic inside the TUI (same flows the Providers scene uses).
Details: Models and providers, Configuring providers, engine doc docs/providers.md.
Local Ollama (keyless when the daemon is up):
$ ollama serve
$ veyyon
Then /model and choose an ollama/… model from discovery.
3. Run your first task
$ cd ~/code/my-project
$ veyyon
Describe a small task:
Add a function add(a, b) in src/lib.rs and a unit test. Run the test.
Typical flow:
- Veyyon reads relevant files (
read,search, …). - It proposes an edit through hashline/
edit/writetools. - When policy requires it, you approve the tool call (
tools.approvalMode— see Safety). - The change lands; diffs appear in the TUI.
- If you asked for tests, approve
bashorcargo testas needed.
4. Work safely (defaults)
Tool approval tiers (read, write, exec) combine with tools.approvalMode:
| Mode | Auto-approves | Prompts for |
|---|---|---|
plan | read (proposes without writing) | everything that writes or runs |
ask | read | write, exec |
auto-edit | read, write | exec |
yolo (default) | all tiers | none (unless per-tool override or bash safety override) |
Legacy names always-ask (→ ask) and write (→ auto-edit) are still accepted.
Use /settings or config to tighten policy on unfamiliar repos. Deep dive: Sandbox, Safety, docs/approval-mode.md.
5. Three things to try next
- Multi-file change — ask for a refactor across modules; watch hashline edits batch paths.
- Session tree —
/treeto jump to an earlier user message and branch in the same session file. - Switch models —
/modelfor the model you talk to; set the subagent and compaction models in settings. See Models, roles, and profiles.
Where to go next
- Quickstart — shorter walkthrough.
- Configuration
- Sessions
- Memory — mnemopi backend
- Diagnostics — plugin doctor and debug tools
Configuring providers
Copy-paste setups for bring-your-own-key (BYOK) and local providers. For the day-to-day switching guide, see Models and providers. For the harness/provider boundary, see Model contract.
Custom providers live under providers: in ~/.veyyon/agent/models.yml. Keys are resolved from the
environment, stored auth, OAuth, or a models.yml apiKey (see Providers
and docs/providers.md).
Anatomy of a provider entry
# ~/.veyyon/agent/models.yml
providers:
acme:
baseUrl: https://api.acme.example/v1
api: openai-completions
apiKey: ACME_API_KEY # env-var name if set, else literal text
models:
- id: acme-coder
name: ACME Coder
contextWindow: 128000
maxTokens: 8192
| Field | Meaning |
|---|---|
baseUrl | OpenAI-compatible API root |
api | Request shape, e.g. openai-completions |
apiKey | Env-var name or literal; prefix with ! to run a shell command and use its stdout |
auth: none | Mark a keyless local provider |
authHeader: true | Inject the resolved key as Authorization: Bearer <key> |
models | List of { id, name, contextWindow, maxTokens } entries |
Notes worth knowing:
- Custom providers are merged alongside built-ins; they do not silently replace
openai. - A custom
ollama/lm-studio/llama.cppentry replaces that engine’s built-in discovery. - A YAML or schema error makes the registry skip the file loudly — validate with
veyyon models.
After editing, restart the session or /reload.
OpenAI (API key)
$ export OPENAI_API_KEY=sk-...
$ veyyon --model openai/gpt-5
Managed OpenAI sign-in is also available with /login openai inside the TUI, so no key is pasted into
the shell. See Authentication.
DeepSeek
providers:
deepseek:
baseUrl: https://api.deepseek.com
api: openai-completions
apiKey: DEEPSEEK_API_KEY
models:
- id: deepseek-chat
name: DeepSeek Chat
contextWindow: 128000
maxTokens: 8192
$ export DEEPSEEK_API_KEY=sk-...
$ veyyon --model deepseek/deepseek-chat
deepseek is also a built-in catalog provider; the env var alone is enough if you do not need a custom
endpoint.
OpenRouter (OpenAI-compatible gateway)
$ export OPENROUTER_API_KEY=...
$ veyyon --model openrouter/anthropic/claude-sonnet-4
Model ids are whatever OpenRouter lists; Veyyon discovers them at runtime.
Anthropic
Anthropic is a built-in provider. Sign in with /login anthropic (OAuth) or set ANTHROPIC_API_KEY:
$ export ANTHROPIC_API_KEY=sk-ant-...
$ veyyon --model anthropic/claude-sonnet-4-5
To reach Anthropic models through a gateway instead, add an OpenAI-compatible custom provider (OpenRouter, LiteLLM, a team proxy) and select the gateway’s model id.
Other OpenAI-compatible hosts
Any host that speaks Chat Completions works the same way — only baseUrl, api, and apiKey change:
providers:
my-proxy:
baseUrl: https://llm-proxy.example.com/v1
api: openai-completions
apiKey: PROXY_API_KEY
authHeader: true
models:
- id: coder-large
name: Org Coder Large
contextWindow: 200000
maxTokens: 8192
$ export PROXY_API_KEY=...
$ veyyon --model my-proxy/coder-large
Amazon Bedrock
Bedrock is a built-in provider. Use the usual AWS credential chain (AWS_PROFILE, instance role, or
AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY) expected by the AWS SDK on your machine.
Ollama (local)
Ollama is discovered automatically and is keyless when the daemon is running. Default base URL
http://127.0.0.1:11434 (override with OLLAMA_BASE_URL).
$ ollama serve
$ ollama pull llama3.2
$ veyyon # then /model and pick an ollama/… entry from discovery
LM Studio (local)
LM Studio (lm-studio) is also discovered automatically and keyless by default. Default base URL
http://127.0.0.1:1234/v1 (override with LM_STUDIO_BASE_URL).
$ lms server start
$ veyyon # then /model and pick an lm-studio/… entry
Pinning models for roles and CI
Set background roles under modelRoles in config.yml:
# ~/.veyyon/agent/config.yml
modelRoles:
default: openai/gpt-5
smol: openai/gpt-5-mini
task: deepseek/deepseek-chat
For a hermetic CI run, pass an explicit model and a one-shot config overlay:
$ veyyon --config ./ci-settings.yml --model openai/gpt-5-mini \
"summarize the staged diff in five bullets"
Verify
$ veyyon models
$ veyyon --model <provider>/<id> "reply with the model name you are"
If discovery or auth fails, the error names the provider and the missing key or unreachable base URL — fix that rather than retrying with a different silent default.
See also
Core concepts
This chapter is the mental model for how Veyyon runs. Read it when you need the vocabulary behind sessions, permissions, and the BYOK model boundary. Day-to-day commands live in Using Veyyon; feature guides live under Features.
What this chapter covers
Veyyon is a terminal coding harness. The CLI is veyyon. You point it at a model endpoint with a key you
supply, and the harness drives a loop of read, edit, verify, and stop. The concepts below explain the
units of that loop and the contracts that keep it predictable across providers.
| Page | What it defines |
|---|---|
| Sessions, turns, and threads | The runtime units: a session is the persisted run, a turn is one request plus the agent loop, and a thread is the active path through the session tree. |
| Permission model | How approval modes and the OS sandbox combine. The sandbox is the hard boundary (Landlock/seccomp on Linux, Seatbelt on macOS); tools.approvalMode decides when Veyyon asks first. |
| Model contract | The BYOK boundary: endpoint + model + key. What the harness owns versus what the provider owns, Freeform vs Function tools, and how system prompts and tool schemas are presented. |
Foundations that pair with these pages
Before or after this chapter, the foundations pages give the design spine without repeating the day-to-day workflow:
- Core concepts (foundations) defines session, thread, turn, rollout JSONL, and the state database in more detail.
- Architecture overview maps subsystems to responsibilities.
- Provider and model configuration: Providers and
docs/providers.md.
How the pieces fit
you ──► veyyon (TUI or a one-shot prompt)
│
├─ session / thread / turn (concepts/sessions-turns-threads)
├─ sandbox + approvals (concepts/permission-model)
└─ model call (concepts/model-contract)
│
├─ system prompt + tool schemas (harness)
├─ endpoint + key (your provider)
└─ model id (discovered or pinned)
The harness stays the same when you change providers. You change the endpoint, the key, and optionally the model id. Tool repair, edit verification, sandboxing, and context compaction remain harness behavior. See Configuring providers for copy-paste setups and Models and providers for day-to-day model selection.
Related reading
- Permission model and Sandbox for Landlock/Seatbelt details.
- Prefer
--approval-mode auto-editfor bounded automation. - Non-interactive mode for scripted
veyyonlaunch patterns.
Sessions, turns, and threads
A Veyyon run is a loop of user requests and agent responses. The session holds the whole run. Each turn is one request and the agent loop that answers it. A thread is one path through the session tree. These three ideas are the foundation for branching, plan mode, and long-running context.
Lifecycle of a run
start session (veyyon / veyyon "prompt" / resume)
│
▼
compose prompt ──► turn begins
│
├─ assemble context (instructions, goal card, recent history, tools)
├─ call model
├─ dispatch / repair tool calls (edit, exec, MCP, …)
├─ approvals + sandbox gate
└─ final reply ──► turn ends (or Esc abort)
│
▼
append rollout entry ──► update active leaf
│
├─ next user message ──► next turn
├─ /compact when the window is tight
└─ /fork / /branch / /tree when exploring branches
Interactive veyyon, a non-interactive veyyon "prompt" run, and resume paths all share this loop.
The difference is who supplies the next prompt and whether a TUI is attached.
What a session is
A session is the unit of interactive work. Start one with veyyon in the repository you want to change. The session records every turn, tool call, approval, edit, and verification result.
Sessions are stored as append-only rollout JSONL files. Each line is an entry with an id and a parent_id. Those two fields make the file a tree, not just a log. The session never rewrites history. It only appends new entries or pointer moves.
What a turn is
A turn is one user request plus the agent loop that responds to it. The loop calls the model, dispatches any tool calls, and produces the final reply. A turn ends when the model stops or when the harness decides to stop it.
While a turn runs you can steer it with Enter or queue a follow-up with Tab or alt+enter. A queued follow-up becomes a new turn after the current one finishes. Interrupting with Esc aborts the turn and returns queued messages to the composer.
Threads and the active leaf
At any moment, one path through the session tree is active. That path is the thread. The active leaf is the current entry at the end of that path.
Branching creates siblings in the tree. /tree browses every entry, including abandoned branches.
/branch copies history up to a chosen user message into a new session file. /fork duplicates the
entire current session into a new file (no entry picker). There is no /clone command. The
original session is never modified.
Context pressure and compaction
Models have a finite token window. As a session grows, the raw transcript may no longer fit. Veyyon compacts history into a smaller, information-preserving summary instead of truncating it.
Compaction preserves the goal card, active user instructions, recent turns, and a deterministic working-set of files touched. This lets a resumed session show the model what matters without forcing it to read the entire transcript.
Prefer /compact when you need a summary to retain state. Prefer the new_context tool when prior transcript is no longer useful and you want a clean window without summarization. See Tools reference.
Where the details live
- For session commands and storage, see Sessions.
- For branching, forking, and cloning, see Session branching.
- For plan mode and goal tracking, see Plan mode and goals.
- For how compaction works, see Compaction and project memory.
- For goal state and long sessions, see Goal state and long sessions.
- For contributor-facing internals, see Session and turn internals.
Permission model
Veyyon separates two questions when a tool wants to act:
- When must Veyyon ask you first? That is the approval mode.
- What may a shell command actually touch? That is the OS sandbox.
Together they decide whether a call runs, runs restricted, or pauses for your yes.
Approval modes
The approval mode is an autonomy ladder set with tools.approvalMode, overridable per run with
--approval-mode <mode> (and --auto-approve / --yolo).
| Mode | Auto-approves | Prompts for |
|---|---|---|
plan | Read-only; proposes changes without writing | Everything that writes or runs |
ask | Read-only tools | write, exec |
auto-edit | Read + workspace write | exec |
yolo (default) | All tiers | Nothing (unless a per-tool override or bash-safety override applies) |
The legacy names always-ask (→ ask) and write (→ auto-edit) are still accepted.
Per-tool policy is a second layer: tools.approval maps a tool name to allow, deny, or prompt
and wins over the mode for that tool. For example veyyon config set tools.approval '{"bash":"prompt"}'
always prompts for bash even in yolo.
# ~/.veyyon/agent/config.yml
tools:
approvalMode: auto-edit
approval:
bash: prompt
read: allow
OS sandbox for shell commands
When Veyyon runs a shell command it can confine that process at the OS level so it can only touch what the task needs. Enforcement is platform-native:
- Linux — Landlock and seccomp, with bubblewrap providing the mount (and, when needed, network)
namespace. A bundled
bwrapis used when the system one is missing; Veyyon warns so you can install the OS package. User namespaces are required — WSL1 cannot create them, so use WSL2. - macOS — a Seatbelt profile.
OS-level isolation is enforced by native code. If enforcement cannot be established, Veyyon fails closed — that is an error, not a silent run without confinement.
How they combine
The sandbox is the hard boundary; the approval mode is the interaction. A command the sandbox forbids does not run regardless of approvals. A command the sandbox permits may still pause for your approval, depending on the mode. Choose a permissive approval mode only alongside a sandbox you trust for the task.
Fail-closed behavior
When enforcement cannot be established, Veyyon fails closed rather than silently running unsandboxed.
Silent fallback to a weaker boundary is treated as a bug. After changing approval or sandbox settings,
verify enforcement with veyyon plugin doctor.
Spec — not shipped: the named sandbox policies (
read-only,workspace-write,danger-full-access,external-sandbox) and the-a/--ask-for-approvalpolicy names (untrusted,on-request,granular,never). Veyyon ships approval modes (tools.approvalMode) plus OS-level shell isolation, not asandbox_policyconfig key.
Where the details live
- Sandbox and approvals for the deep reference and runtime commands.
- Using Veyyon safely for the honesty guarantees and the checks to run.
- CLI reference for the launch flags.
Model contract
The terminal engine is provider and API agnostic. You choose an endpoint, choose a model when that endpoint exposes model choice, provide the key, and Veyyon calls that API directly. The endpoint can be a local server (Ollama, LM Studio), a direct provider API (OpenAI, Anthropic, Google), or any OpenAI-compatible gateway.
This page is the contract between the harness and the model. For copy-paste provider setup, see Configuring providers. For day-to-day model switching, see Models and providers.
The three things you bring
A BYOK (bring-your-own-key) run needs three facts:
| Fact | What it is | Where it lives |
|---|---|---|
| Endpoint | Base URL and API kind | A built-in provider, or a custom provider under providers: in ~/.veyyon/agent/models.yml |
| Model | The model id the endpoint understands | Pinned with --model / /model, or discovered from the provider |
| Key | Credential the endpoint accepts | A provider environment variable, /login, or a models.yml apiKey |
Veyyon does not route your requests through a service of its own for BYOK providers and does not add telemetry egress. The key is used only to talk to the provider you chose.
Example shape
# ~/.veyyon/agent/models.yml
providers:
deepseek:
baseUrl: https://api.deepseek.com
api: openai-completions
apiKey: DEEPSEEK_API_KEY # env-var name or literal
models:
- id: deepseek-chat
name: DeepSeek Chat
contextWindow: 128000
maxTokens: 8192
$ export DEEPSEEK_API_KEY=sk-...
$ veyyon --model deepseek/deepseek-chat
What the harness owns
These behaviors stay constant no matter which endpoint you point at:
- The workflow: read, edit, verify, stop when the work is done.
- Tool dispatch, argument handling, and edit verification through the hashline edit engine
(with
apply_patch/patch/replaceavailable viaedit.mode). - OS-level shell isolation (Landlock/seccomp on Linux, Seatbelt on macOS) and approval modes.
- Context compaction, goal cards, session branching, and rollout persistence.
- Per-model prompt order and tool-form selection once a model (or API kind) is known.
You are not locked to any provider. Point the harness at any compatible API and keep the same commands.
What the provider owns
The provider owns the wire protocol, auth scheme, model list, rate limits, and the tokens it returns.
Veyyon adapts to that surface through the provider’s api kind:
- Chat-Completions-style endpoints (
api: openai-completions) talk/chat/completions. - Responses-style and native provider endpoints use their own request shape.
- Model ids come from the provider’s discovery endpoint when discovery runs. There is no hardcoded allowlist for BYOK providers, and discovery fails loud rather than serving a silent empty list.
Everything beyond the built-in catalog is data in models.yml — see
Providers and docs/providers.md.
System prompts and tool schemas
Each turn the harness builds a request that includes:
- Base instructions for the active model or backend (execution order, stop-when-green, format-neutral tool guidance). See Execution-order prompts.
- User and project instructions (
AGENTS.md/SYSTEM.mdlayers and any session steers). - Tool schemas the model is allowed to call on this turn (bash, edit/write, web search, MCP tools,
skills, and so on), filtered by approval mode,
disallowed_tools, and feature flags. - Conversation context for the active thread, possibly compacted.
The model is expected to call tools using the schemas it was given. When arguments are almost right but malformed, hashline returns recovery hints so the model can retry inside the same turn budget.
Freeform vs Function tools
Veyyon advertises the structured edit tool in one of two shapes. The payload (the patch or edit body) is the same; only the transport differs.
| Form | How the model calls it | Typical API kind |
|---|---|---|
| Freeform | A custom / grammar tool. The raw body is the tool payload (for example a full *** Begin Patch envelope). | Responses-style |
| Function | A JSON-schema function tool. Arguments are a JSON object (for example {"input": "<envelope>"}). | Chat Completions |
The form is derived from the provider’s API kind by default; an optional catalog override can pin the
apply_patch tool to function, freeform, or none. The Function form makes a structured edit tool
real for chat-wire endpoints (Ollama, LM Studio, DeepSeek, and similar) that historically saw no
Freeform tool at all. See The hashline edit engine for the default edit wire format.
Harness vs provider: a clear split
┌──────────────────────── harness (veyyon) ─────────────────────┐
│ session / turn loop │
│ prompts, tool schemas, edit, sandbox, approvals, compaction │
└────────────────────────────┬──────────────────────────────────┘
│ HTTPS / local HTTP
▼
┌──────────────────────── provider ─────────────────────────────┐
│ endpoint auth + model discovery + completions/responses │
│ model weights, rate limits, provider-side refusals │
└───────────────────────────────────────────────────────────────┘
If something fails, ask which side owns it:
- Config rejected at load, malformed
models.yml, missing key → harness / your config. - HTTP 401 / 429 / empty model list → provider or key.
- Patch applied but tests red → harness did its job; the change still needs work.
- Sandbox denial → permission model, not the model provider.
Provider data at load time
For BYOK providers, model and provider entries are data in models.yml:
- A YAML or schema error makes the registry skip the custom file loudly; it does not silently drop models.
- Custom providers are merged alongside the built-in catalog. A custom entry with the same id as an
implicit local engine (
ollama,lm-studio,llama.cpp) replaces that engine’s discovery. - Provider availability requires the id not be in
disabledProvidersand the provider be keyless or have resolvable credentials.
Malformed provider data fails at load. Silent fallback to a weaker provider is treated as a bug.
Per-role models
The conversation model (/model or --model) is separate from background roles. Roles are configured
under modelRoles:
modelRoles.task— default for spawned subagents unless an agent definition pins its own model.modelRoles.tiny(orsmol) — lightweight background work (titles, memory, auto-thinking).
Precedence for subagents is explicit: an agent definition’s own model pattern wins, otherwise
modelRoles.task, otherwise the conversation model. There is no silent blend. /status and
veyyon plugin doctor report the effective values. See
Models and providers.
Automation note
For non-interactive runs, pass the prompt and pick an approval mode that matches your trust boundary:
$ veyyon --approval-mode auto-edit "run the unit tests and fix failures"
Use --yolo (auto-approve everything) only in trusted automation, ideally with an outer sandbox.
What stays constant
- The workflow stays the same: read, edit, verify, and stop when the work is done.
- Edit verification, OS isolation, approvals, and context handling are harness behavior.
- You are not locked to any provider; point the harness at any compatible API.
Next
- Configuring providers — Ollama, LM Studio, Anthropic, and custom OpenAI-compatible endpoints.
- Models and providers — choosing and switching models in a session.
- Safety — boundaries around tool use and model output.
- Permission model — approval modes + OS sandbox.
- Signing in — interactive and env-var auth paths.
Core concepts
Veyyon is built around a few persistent, composable units. This page defines the units that shape a conversation and explains how they fit together.
Session
A session is the persisted unit of work. It holds a thread, a working directory, optional goal state, and a JSONL log on disk. When you run veyyon inside a repository, you start a new session. When you resume from the picker, you pick up an existing one.
Sessions are durable: they survive a TUI restart, a context compaction, or a machine reboot. The file on disk is the source of truth, not the in-memory state.
See Sessions for day-to-day commands and File locations for where the files live.
Thread
A thread is the conversation lineage inside a session. It is the sequence of turns from the start of the conversation to the current point. A session can contain more than one thread if you branch or fork.
The thread is the path the harness follows when it builds context for the next turn. It is not the same as the raw log file: the log file contains every entry, including branches you are no longer on.
Turn
A turn is one model pass. It starts with a prompt, runs through model inference, tool calls, approvals, and edits, and ends with a final response. The TUI and CLI display each turn as one back-and-forth block.
During a turn, Veyyon can dispatch tools, apply edits, run verification, and wait for user approval. A single turn may contain many tool calls, but it is still one turn because it is one top-level model invocation with a single final response.
How context history is preserved
Context history is preserved in two layers.
Rollout / JSONL storage
Every event in a session is appended to a rollout file as a JSONL line. The rollout file lives under the sessions directory below the config home (~/.veyyon/.../sessions/). Each line is a rollout item: a user message, an agent response, a tool call, a compaction, a goal update, or a leaf move.
Rollout files are append-only. Nothing is ever rewritten. This property makes branching and resume safe and auditable. See Sessions for day-to-day commands and Session and turn internals for the persistence format.
State database
Thread metadata, goal cards, and queued follow-ups are also mirrored into a local SQLite index. The index lets the harness list and resume sessions quickly without replaying the entire rollout log.
How context history is updated
A turn updates the thread by appending new entries to the rollout file. The in-memory view is rebuilt from the thread’s path through the log.
When a turn grows too long, the harness may compact the transcript. Compaction summarizes older turns into a smaller form while preserving the goal, recent user messages, file working sets, and verification facts. The compacted prefix is then used as the older context for later turns, while the recent tail remains intact. See Compaction & project memory for how summaries are built and what survives.
Relationships
- A session owns one or more threads and stores them on disk.
- A thread is a path through the session’s tree of turns.
- A turn is one step on that path.
- The rollout is the append-only log that contains every turn, branch, and system event.
- The state database is the runtime index for resume and active metadata.
- The goal card is a separate context slot that carries the current objective across turns and compactions. See Goal state.
Branching and forking
Because rollout files are append-only, branching does not delete or rewrite history. A branch is recorded as a new entry whose parent points to an earlier entry. The active leaf can be moved to any existing entry, and new turns start from there.
Use /tree to browse the session tree, /fork to copy history into a new session, /clone to duplicate the current branch, and /side to open a short-lived side thread. See Session branching for commands and behavior.
Plan mode
Plan mode changes how a turn behaves. In Plan mode, the agent is constrained to non-mutating investigation and planning; it cannot edit files or run mutating commands. It produces a <proposed_plan> that you approve before implementation begins. This makes a plan a first-class turn result, not just prose in the transcript. See Plan mode and goals.
What to remember
- A session is the saved work unit.
- A thread is the conversation path inside that session.
- A turn is one model pass with a beginning, middle, and end.
- Rollout files are append-only JSONL; the state database is the runtime index.
- Compaction preserves task-critical facts while shrinking the transcript.
- Branching and forking are safe because history is never rewritten.
How to read this book
This handbook has three kinds of chapters. Knowing which is which keeps the record honest.
Chapter status labels
Every chapter opens with a status line:
- Built & verified, the feature is shipped and works end to end.
- In progress, partially built; the chapter says exactly what works and what does not yet.
- Spec — not shipped, a mechanism with a clear target shape, documented with the failure mode it fixes. Spec chapters do not claim the work is done.
Conventions
- Provenance. When a technique is adapted from prior art, the source and its license are named briefly and collected in Acknowledgements. Veyyon adapts MIT/Apache code with attribution and studies proprietary code clean-room.
- Numbers have sources. A quoted measurement names who measured it. Veyyon’s own numbers are marked as such; cited third-party numbers are marked as theirs.
- The lever. Each optimization names which thesis lever it moves, edit format, control flow, or a supporting concern (cost, robustness, coherence). If it doesn’t move a lever, it doesn’t belong.
Features
Veyyon’s features are split into two groups: everyday interactions you use while working, and power-user customization tools that shape how the agent behaves.
Everyday features
- Cockpit customizes the status line and monitors multi-agent work; a terminal title composer and TUI pet are spec only.
- Keybindings remaps shortcuts and toggles Vim mode in the composer.
- Composer conveniences (prompt history,
@//completion, empty-state hints, Esc interrupt) are documented in Quickstart and Keybindings. - Web search lets the model look up current information from the web.
Power features
- Plan mode plans complex changes through grounded conversation before editing code.
- Skills are reusable capabilities defined on the filesystem and shared across projects.
- Plugins bundle skills, MCP servers, hooks, apps, and TUI customizations from marketplaces.
- Hooks run commands or inject context in response to lifecycle events.
- MCP connects Veyyon to external tools and data sources via the Model Context Protocol.
- Branching explores alternative paths by forking, cloning, or branching the session tree.
- Memory collects guidance and decisions from past runs to keep future threads consistent.
- Profiles bundle and switch between groups of configuration settings.
- Personalities changes the agent’s communication style without changing its capabilities.
- Export and import saves sessions to files and migrates settings from Claude Code.
- Connectors reach provider-hosted apps and data sources behind your account.
Task-oriented guides
Feature pages are reference-shaped. For goal-shaped recipes that stitch hooks, exec, MCP, skills, plugins, memory, and branching together, start with Task guides.
Where to go next
For command and file reference, see the Reference chapter. For how Veyyon is designed, see Foundations.
Editing and repair
Editing reliably is the core of a coding agent. Veyyon’s default edit surface is hashline:
numbered lines from read/grep, snapshot tags, and edit with SWAP/DEL/INS operations.
For design background see Edits that land the first time and The hashline edit engine.
Why this matters
Models often emit slightly wrong tool JSON or stale line anchors. Hashline detects stale [path#TAG]
tags and returns recovery hints. General schema repair runs on all tool calls before validation —
see Repair overview.
Write path vs edit path
| Path | Applier | Role |
|---|---|---|
edit | @veyyon/hashline (default) | Surgical edits via snapshot tags and hashline ops |
write | Whole-file writer | Create or overwrite files; mints new snapshot tags in hashline mode |
apply_patch / patch / replace | Mode-specific parsers | Compatibility modes via edit.mode |
There is one hashline edit applier for anchored edits; write is intentionally separate for whole-file
creation. Both honor the same approval and sandbox policy.
Tools
| Tool | What the model sends | Use for |
|---|---|---|
edit | Hashline input (default) or mode-specific payload | Surgical edits |
write | path + full content | New files or full rewrites |
apply_patch | V4A envelope | When edit.mode: apply_patch |
Set edit.mode to hashline, apply_patch, patch, or replace in config.yml, or
PI_EDIT_VARIANT for one-shot overrides.
Hashline workflow
read(orgrep) returns[relative/path#TAG]andLINE:textrows.- Model calls
editwith sections anchored on the sameTAG. - On success, output includes a fresh
[path#NEW_TAG]and a compact diff.
write strips pasted hashline prefixes when appropriate and can mint new tags after whole-file writes.
Safety
Edits honor approval mode and sandbox roots like bash. Denied tools are removed from the model’s tool
list via disallowed_tools / plan mode.
Spec — not shipped: a single V4A applier that routes every mutation through a
make_update_patchenvelope. Veyyon uses hashline as the primary path;apply_patchis a compatibility mode.
Sandbox and approvals
Spec — not shipped: the OS-enforced sandbox described on this page — Landlock/seccomp on Linux, Seatbelt on macOS, bundled
bwrap, theread-only/workspace-write/danger-full-access/external-sandboxpolicies, and the-s/--sandbox,--full-auto, and--dangerously-bypass-approvals-and-sandboxflags. Veyyon does not ship an OS-level command sandbox yet. What ships today is thetools.approvalModeautonomy ladder that decides when the agent pauses before write and exec tools:plan(read-tier tools only),ask(prompt before write/exec),auto-edit(auto write, prompt exec),yolo(auto-approve all). Set it with--approval-mode <mode>,--auto-approve(alias--yolo), ortools.approvalModeinconfig.yml. Read the rest of this page as the reference for the target sandbox model.
Veyyon separates two questions: what a command is allowed to touch (the sandbox) and when Veyyon must ask you first (the approval policy). Together they decide whether a tool call runs automatically, runs inside a restricted environment, or waits for your yes.
This page is the deep reference. For the short mental model, see Permission model. For the approval UX walkthrough, see Permissions and approvals. For the honesty guarantees, see Safety.
Two controls, one decision
| Control | Question it answers | Soft or hard? |
|---|---|---|
| Sandbox policy | What may this process read, write, and reach on the network? | Hard boundary (OS-enforced where available). |
| Approval policy | Must a human say yes before this action runs? | Interaction policy (can pause; cannot widen the sandbox). |
A command the sandbox forbids never runs, regardless of approvals. A command the sandbox permits may still pause for your approval. Choose a permissive approval policy only alongside a sandbox you trust for the task.
Sandbox policies
The sandbox bounds filesystem and network access for commands the agent runs.
| Policy | Filesystem | Network | Best for |
|---|---|---|---|
read-only | Read anywhere; write nothing. | Off by default (opt in per policy / permissions). | Exploring code you do not fully trust. |
workspace-write | Read anywhere; write within the workspace and configured writable roots. | Off by default. | Everyday coding in a trusted project. |
danger-full-access | No filesystem restrictions from Veyyon. | Unrestricted. | Disposable environments that truly need full access. |
external-sandbox | Full disk access from Veyyon’s point of view, because an outer sandbox already contains the process. | Honors the provided setting. | Docker, a VM, or a CI job that already sandboxes the runner. |
Set it in config.yml, with -s / --sandbox <policy>, or per run with -c sandbox_policy=....
Additional writable roots for a single run:
$ veyyon --print --sandbox workspace-write --add-dir /tmp/veyyon-scratch "…"
Defaults you should expect
- Interactive
veyyon: prefer a bounded sandbox (workspace-writefor normal coding) plus an approval policy that still asks for risky work (on-requestoruntrusted). - Headless
veyyon --print: approvals default to never (no TTY to answer), so the sandbox is the containment. Prefer--sandbox workspace-write(orread-only) explicitly in CI. - Network egress is off by default for
read-onlyandworkspace-write. Enabling network is a deliberate permission change, not an ambient side effect of “running a command.”
How enforcement works per OS
Veyyon does not implement a toy “please don’t touch that file” filter as its only defense. It builds and applies a platform sandbox so a command can only touch what the active policy permits. If enforcement cannot be established, Veyyon fails closed — that is an error, not a silent run-without-confinement.
Linux — Landlock, seccomp, and bubblewrap
On Linux the platform sandbox type is Linux seccomp (with Landlock / bubblewrap in the helper):
- Veyyon self-invokes a helper (
veyyon-linux-sandbox) with the permission profile, policy cwd, and command cwd. - The helper applies the profile: bubblewrap provides the mount (and, when needed, network) namespace; seccomp restricts syscalls; Landlock (including a legacy Landlock path) further constrains filesystem access when selected.
- A bundled
bwrapis used when the system bubblewrap is missing; Veyyon warns so you can install the OS package. If user namespaces are unavailable, you get an explicit warning — sandboxing needs them. - WSL1 cannot create the required user namespaces; use WSL2 for sandboxed shell commands. Veyyon surfaces this instead of pretending confinement worked.
Managed / proxy-only networking (when configured) requires bubblewrap’s isolated network namespace and the helper flag that allows proxy egress only — not a silent hole in the default deny.
macOS — Seatbelt
On macOS the platform sandbox type is Seatbelt. Veyyon launches the command under a Seatbelt profile derived from the permission policy (base profile plus network policy). Filesystem and network isolation come from the OS sandbox, not from hope.
Windows — restricted token (when enabled)
On Windows, when Windows sandboxing is enabled, Veyyon can use a restricted-token sandbox. Unsupported configurations fail with an explicit reason rather than falling back to full access.
external-sandbox
Use this when the process is already inside Docker, a VM, or another outer jail. Veyyon will not double-wrap; it trusts the outer boundary and still honors the network setting you provide. Pair with headless approvals carefully — the outer jail is doing the hard work.
Network egress
Network policy is separate from “can the shell start.”
| Mode | Meaning |
|---|---|
| Restricted (default for bounded sandboxes) | Outbound network is off unless the permission profile enables it. |
| Enabled | Network is allowed under the active sandbox / proxy rules. |
Practical guidance:
- Keep network off for pure local edit/test loops.
- Enable network only when the task needs package downloads, API calls, or live search — and prefer
asking (
untrusted/on-request) the first time in an unfamiliar repo. - In CI, prefer a runner firewall or
external-sandboxplus an explicit allowlist at the job layer; do not rely on the model to “be careful.”
Approval policies
The approval policy decides when a command or sensitive tool action is shown to you before it runs.
Set it with -a, --ask-for-approval <policy> or in config.
| Policy | Behavior | Best for |
|---|---|---|
untrusted | Only known-safe, read-only commands run automatically; everything else asks. | Unfamiliar repositories or high-risk work. |
on-request | The model decides when to ask; it requests approval for anything it considers risky. | Everyday coding where you want the model to judge. |
granular | Fine-grained per-category control: shell commands, execpolicy prompts, skill scripts, the request_permissions tool, and MCP elicitations are each allowed or auto-rejected. | Teams that want explicit rules per tool class. |
never | Never ask. Denied or failed commands return to the model rather than escalating to you. | Trusted automation / veyyon --print (the headless default). |
What the approval prompt looks like
Veyyon would like to run a shell command
Command: npm test
Directory: /home/you/proj
Sandbox: workspace-write
[y] yes [n] no [a] always for this session [p] show policy
Veyyon would like to edit a file
File: src/main.rs
Sandbox: workspace-write
Change: update the function signature and add a null check
[y] yes [n] no [a] always for this session [p] show policy
y— run once.a— allow this kind of action for the rest of the session.n— deny; the model is told so it can try another approach.p— show the active policy details.
Trust/allowlists also come from execpolicy .rules files (user and project). In automation you
can skip loading them with veyyon --print --ignore-rules when you want a hermetic policy (see
Non-interactive mode).
How they combine (recipes)
| Goal | Sandbox | Approval |
|---|---|---|
| Explore an unfamiliar repo | read-only | untrusted |
| Everyday coding | workspace-write | on-request |
| Trusted CI automation | workspace-write or read-only | never (exec default) + explicit --sandbox … |
| Already inside Docker/CI jail | external-sandbox | never |
| Need full disk, still want human gates | danger-full-access | untrusted |
| Disposable env, no humans, no jail | danger-full-access | never — only if inputs and the machine are disposable |
Never pair danger-full-access with never unless the environment is disposable and the inputs are
fully trusted. That combination removes both containment and human review.
Deprecated: --full-auto
On veyyon --print, --full-auto is a legacy compatibility trap. It prints a warning and maps to
--sandbox workspace-write behavior. Prefer the explicit flag:
$ veyyon --print --sandbox workspace-write "run the unit tests and summarize failures"
Do not document new automation as --full-auto.
Dangerous escape hatch
$ veyyon --print --dangerously-bypass-approvals-and-sandbox "…"
(--yolo is an alias.) This skips confirmation prompts and sandboxing. Intended solely for
environments that are externally sandboxed. Prefer external-sandbox when you can express the
outer jail cleanly.
Extending the boundary at runtime
You can widen read access or escalate without editing config.yml mid-thought:
| Action | Effect |
|---|---|
/sandbox-add-read-dir <absolute_path> | Grant the sandbox read access to a directory for the current session. |
/elevate-sandbox | Walk through setting up the elevated agent sandbox when a task needs more than the default. |
/approve | Approve a specific pending request. |
/reload | Re-read configuration after you edit policies on disk. |
-c sandbox_policy=… / -a … | Override for a single run. |
--add-dir <DIR> | Extra writable root for this invocation. |
Use profiles for repeatable combinations (for example a reviewer
profile with read-only + untrusted).
Fail-closed behavior
Security-control failures are operator-visible:
- Missing Linux sandbox helper → error, not unsandboxed execution.
- Seatbelt unavailable off macOS → unsupported operation, not a silent skip.
- Bubblewrap / user-namespace problems → warning or hard failure depending on whether the policy requires a platform sandbox.
- WSL1 → explicit unsupported message pointing at WSL2.
Treat a silent fallback to a weaker boundary as a bug. After changing sandbox or approval
settings, confirm the active tools.approvalMode in /settings (Advanced → Safety).
Choosing settings (checklist)
- Is the repo trusted? If no →
read-only+untrusted. - Does the task need writes? If yes →
workspace-write, keep network off until needed. - Are you headless? If yes → set
--sandboxexplicitly; do not rely on interactive approvals. - Is there an outer jail? If yes →
external-sandbox(or--dangerously-bypass-…only when you understand the outer jail). - Would a wrong
rm -rfhurt? If yes → never combine full access withnever.
See also
- Safety — honesty guarantees and what to verify.
- Permissions explainer — approval UX.
- Non-interactive mode — CI flags (
--ignore-user-config,--ignore-rules, sandbox). - CLI reference — launch flags.
- Exit codes — how failures surface in scripts.
Permissions and approvals
Spec — not shipped: the named sandbox-policy vocabulary on this page (
read-only/workspace-write/danger-full-access/external-sandbox) and the-s/--sandboxand-a/--ask-for-approvalflags. The shipped control is thetools.approvalModeautonomy ladder —plan,ask,auto-edit,yolo— set with--approval-mode <mode>,--auto-approve/--yolo, ortools.approvalModeinconfig.yml. The approval-flow UX below is accurate; the sandbox-policy tables are the target shape. See Sandbox and approvals for the same distinction.
Veyyon can run commands and edit files on your behalf. Two controls shape that power: the sandbox (spec) decides what a command is allowed to touch, and the approval policy decides when Veyyon pauses to ask you first. This page explains the user-facing flow, the choices, and how to combine them. For the complete reference, see Sandbox and approvals.
Why two controls?
The sandbox is a hard boundary. It says “this command may read here, write there, and reach the network, and nothing else.” The approval policy is an interaction choice. It says “this kind of action should stop for a yes before it runs.” A command that the sandbox forbids never runs, even if the approval policy would allow it. A command that the sandbox allows may still ask you, depending on the policy.
Separating the two lets you pick the right trade-off for the task. You can be strict on both, permissive on one but strict on the other, or fully hands-off for trusted automation.
The approval flow
When Veyyon wants to run a command or edit a file, it shows a short prompt. The prompt tells you what is about to happen, where, and under which policy. You choose whether to allow it once, allow it for the session, or deny it.
A typical command prompt looks like this:
Veyyon would like to run a shell command
Command: cargo test
Directory: /home/user/my-project
Sandbox: workspace-write
[y] yes [n] no [a] always for this session [p] show policy
A file-edit prompt looks like this:
Veyyon would like to edit a file
File: src/main.rs
Sandbox: workspace-write
Change: update the function signature and add a null check
[y] yes [n] no [a] always for this session [p] show policy
If you choose y, the action runs once. If you choose a, the same kind of action in the same
session runs without asking again. If you choose n, the action is cancelled and Veyyon tells the
model so it can try a different approach. p shows the active policy details.
Sandbox backends
The sandbox bounds filesystem and network access. There are four backends.
| Policy | What it can touch | Best for |
|---|---|---|
read-only | Read anywhere; write nothing. Network off by default. | Exploring code you do not fully trust. |
workspace-write | Read anywhere; write within the workspace and configured roots. Network off by default. | Everyday coding in a trusted project. |
danger-full-access | No filesystem or network restrictions. | Disposable environments or tasks that truly need full access. |
external-sandbox | Full disk access, because the process is already contained by an outer sandbox. Network follows the provided setting. | Running inside Docker, a VM, or another sandbox you trust. |
On Linux, read-only and workspace-write are enforced with Landlock and seccomp. On macOS, they use
the Seatbelt profile. Where the system sandbox is unavailable, Veyyon uses a bundled bwrap mount
namespace. If enforcement cannot be established, Veyyon fails closed rather than running unsandboxed.
Approval policies
The approval policy decides when Veyyon stops for permission. Set it in
configuration or per run with -a, --ask-for-approval <policy>.
| Policy | Behavior | Best for |
|---|---|---|
untrusted | Known-safe, read-only actions run automatically; everything else asks. | Unfamiliar repositories or high-risk work. |
on-request | The model requests approval for anything it considers risky. | Everyday coding where you want the model to judge. |
granular | Fine-grained per-category control: shell commands, execpolicy prompts, skill scripts, request_permissions, and MCP elicitations can be allowed or auto-rejected independently. | Teams that want explicit rules for each tool class. |
never | Never ask. Denied or failed actions return to the model. | Trusted automation only. |
The granular policy is useful when you want one behavior for some tools and another for others. For
example, you can let tests run automatically while still requiring approval for file edits or network
calls.
How the two combine
The sandbox is the last line of defense; the approval policy is the speed bump. Choose a permissive approval policy only when the sandbox already limits damage.
- Exploring an unfamiliar repo:
read-only+untrusted. Veyyon can read and search, but cannot write or run anything risky without asking. - Everyday coding:
workspace-write+on-request. Veyyon can edit files in the project and run commands, but it asks for anything risky. - Trusted CI automation:
workspace-writeorread-onlywith headlessneverask (the default forveyyon --print). The run completes without interruption, and the sandbox still limits what can be touched. - Dangerous work:
danger-full-access+untrusted. The sandbox is gone, so every risky action must be approved by you.
Never pair danger-full-access with never unless the environment is disposable and the inputs are
fully trusted. That combination removes both containment and human review.
Tuning trust for a session
You can change the boundary without editing a file.
-c sandbox_policy=read-onlyor-a untrustedoverrides the policy for a single run.veyyon --printdefaults toneverask in headless mode with the active sandbox, so automation runs unattended but still bounded.--full-autois deprecated; prefer explicit--sandbox./sandbox-add-read-dir <absolute_path>grants read access to a directory for the current session./elevate-sandboxwalks you through setting up the elevated sandbox when a task needs more than the default./reloadre-reads the configuration file after you edit it, so you can update policies without restarting Veyyon.
Use profiles for repeatable combinations. For example, a
reviewer profile can use read-only + untrusted, while a deploy profile uses workspace-write +
never inside a CI runner that already has an external sandbox.
What to verify
After changing approval settings, confirm the active tools.approvalMode in /settings
(Advanced → Safety). A silent fallback to a less restrictive state is treated as a bug. See
Safety for the broader guarantees and how to inspect what Veyyon has done.
Safety
Veyyon is allowed to act on your machine, so the boundary has to be explicit — and visible.
Commands and file writes run through approval policy and sandbox policy. The approval policy decides when Veyyon pauses for permission. The sandbox policy decides what commands may read, write, or reach on the network. If Veyyon cannot prove the requested behavior fits the active boundary, it fails closed or asks. Silent degradation is treated as a product bug.
Deep policy reference: Sandbox and approvals. Mental model: Permission model. Product framing: Safety and honesty you can see.
The honesty story
An agent that silently weakens its boundary is hard to trust. Veyyon is designed so you can see:
| Situation | What you should see |
|---|---|
| Command or edit needs permission | An approval prompt with command/path, cwd, and active sandbox. |
| Sandbox denies an action | A clear denial / tool failure — not a partial side effect. |
| Sandbox cannot be established | An error or explicit unsupported warning (for example WSL1 + bwrap). |
| Tool call is malformed but repairable | The call is repaired; the model gets coaching on the correct shape. |
| Tool call is ambiguous or unrepairable | Refusal + coaching; no guessed dispatch. |
| Output was truncated | Truncation is recorded, not hidden. |
| Config / provider data is bad | Load fails with file and value context — no silent empty catalog. |
Tool calls follow the same rule as shell. A recoverable malformed call can be repaired against the advertised schema; an unrepairable call is reported back to the model with coaching and is not dispatched as garbage. Output truncation, disabled safety nets, bad configuration, and missing provider data are operator-visible conditions.
What Veyyon refuses
Veyyon would rather miss a fix than invent one:
- Ambiguous argument repair — if two repairs are plausible, the call fails instead of guessing.
- Stale patch hunks — update context that no longer matches the file is rejected before write.
- Non-unique exact edits —
oldTextthat matches many places is rejected with a count and coaching. - Sandbox / helper setup failures — no “run unsandboxed just this once” fallback.
- Destructive Git as a way to “clean uncertainty” — treat a dirty worktree as normal active
context; inspect with
git status --short,git diff, andgit diff --cached. Do not ask Veyyon to clear uncertainty withgit reset --hard,git checkout --,git restore, orgit clean.
Approvals you can see
When a command or edit needs approval, the prompt shows what is about to run, where, and under which sandbox. You can allow once, allow for the session, deny, or inspect the policy. Denied actions return to the model so it can change approach — they do not silently become success.
In headless veyyon --print, approvals default to never (there is no interactive prompt). Containment is
then the sandbox you pass (--sandbox workspace-write is the usual CI choice). See
Non-interactive mode.
Trust, allowlists, and rules
If you decline directory trust during onboarding, Veyyon exits with next-step advice instead of a silent quit: project-local config, hooks, and exec policies stay unloaded until you trust the workspace. You can re-run and accept trust, or explore without trusting via veyyon -a untrusted -c sandbox_policy=read-only. On Windows, when sandboxing is disabled by config, the trust prompt says so and continues in standard mode rather than implying a sandbox will be created immediately.
On first-run welcome, if a newer install is already known from the update cache, Veyyon shows an Update available badge while the background version probe refreshes.
- Keep approvals enabled when working in repositories you do not fully trust.
- Prefer exact workspace roots and explicit provider config over ambient shell state.
- Execpolicy
.rulesfiles (user and project) can allowlist or require prompts for command classes. For hermetic CI,veyyon --print --ignore-rulesskips loading them;--ignore-user-configskips$VEYYON_HOME/config.ymlwhile auth still usesVEYYON_HOME. - Network egress stays off by default in bounded sandboxes; turn it on deliberately.
What to check
- Run
veyyon plugin doctorafter install and after changing provider or sandbox configuration. It exits non-zero when a check fails — treat that as a real setup problem. - Confirm the active sandbox and approval policy (
/status, approvalp, or the exec startup line). - Prefer
workspace-write(orread-only) overdanger-full-accessunless the machine is disposable. - Never combine
danger-full-accesswithneveroutside a disposable, externally controlled environment. - Treat a silent fallback as a bug: if confinement or credentials disappeared without a message, that is not “working as intended.”
Risk surfaced in the loop
Risk is not only a startup checkbox:
- Before — policy + sandbox chosen; doctor can verify readiness.
- During — approvals, sandbox denials, repair refusals, and truncated outputs appear in the turn.
- After — turn diffs and (for
veyyon --print --json) structured events record what ran, what failed, and what the final message was.
Next
- Configuration — set the policies Veyyon will enforce.
- Sandbox and approvals — OS backends, egress, recipes.
- Editing and repair — why refused repairs protect your tree.
- Diagnostics — diagnostics options and exit status.
Models and providers
This page is the day-to-day guide for choosing and switching models. Veyyon is provider/API agnostic: you choose the endpoint, choose the model when that endpoint exposes model choice, supply the key, and Veyyon calls that API directly. The endpoint can be a local server, a direct provider API, or a compatible gateway.
- Contract (what the harness owns vs the provider): Model contract
- Copy-paste provider setups: Configuring providers
- Built-in provider stack internals: Provider stack and BYOK
Bring your own key
When you use a non-managed BYOK provider, Veyyon does not route your requests through a service of its own and does not add telemetry egress. It uses your key only to talk to the provider you chose.
Set the key through:
- The provider’s environment variable (see Providers for the full map), or
/logininside the TUI, which stores the credential in the auth store, or- A
models.ymlapiKeyon a custom provider (env-var name or literal).
See Signing in for storage modes and Configuring providers
for full models.yml examples.
Minimal BYOK shape
# ~/.veyyon/agent/models.yml
providers:
deepseek:
baseUrl: https://api.deepseek.com
api: openai-completions
apiKey: DEEPSEEK_API_KEY
models:
- id: deepseek-chat
name: DeepSeek Chat
contextWindow: 128000
maxTokens: 8192
$ export DEEPSEEK_API_KEY=sk-...
$ veyyon --model deepseek/deepseek-chat
Built-in providers
Veyyon ships a large built-in catalog (Anthropic, OpenAI, Google, Groq, OpenRouter, Mistral, xAI,
Bedrock, and many hosted gateways) plus three auto-discovered local engines. A provider becomes
selectable when it is not in disabledProviders and it is keyless or has resolvable credentials.
| Provider id | Notes |
|---|---|
anthropic, openai, google, groq, … | Cloud providers; sign in with /login <id> or set the env var. |
amazon-bedrock | Uses the AWS credential chain (AWS_PROFILE, instance role, …). |
ollama, lm-studio, llama.cpp | Local engines, discovered automatically and keyless by default. |
Once a provider is available, model ids come from its discovery endpoint — there is no hardcoded BYOK allowlist. Discovery fails loud rather than serving a silent empty list.
Local models: Ollama and LM Studio
Both are discovered automatically once the engine is running; no models.yml entry and no key are
required.
$ ollama serve
$ ollama pull llama3.2
$ veyyon # then /model and choose an ollama/… entry
$ lms server start
$ veyyon # then /model and choose an lm-studio/… entry
Override the base URL with OLLAMA_BASE_URL / LM_STUDIO_BASE_URL if a daemon listens elsewhere. An
explicit models.yml entry for one of these ids replaces its built-in discovery.
Mid-session model switch
| Action | What it changes | What it does not change |
|---|---|---|
/model (or restart with --model) | The interactive model for subsequent turns | The subagent and compaction models |
Switching the interactive model mid-session never blends through a fallback chain into the subagent or
compaction model. /status shows all effective models. veyyon plugin doctor warns about missing
external binaries and keys.
$ veyyon --model openai/gpt-5
# later, inside the TUI:
/model deepseek/deepseek-chat
/status
Model selection: three explicit slots
Veyyon separates the model you talk to from the models that work in the background. There are three
explicit slots, each set on its own — no default model stands in for the others:
- Interactive model — the conversation you are in. Chosen with
/model(or--modelfor a run). - Subagent model — task subagents. Set once in settings (
subagent.model). - Compaction model — context compaction. Set once in settings (
compaction.model).
Leave a background slot unset and it uses the interactive model. Switching the interactive model
mid-session never blends through a fallback chain into the background slots. /status shows all
effective models.
# ~/.veyyon/agent/config.yml
model: openai/gpt-5 # interactive
subagent:
model: deepseek/deepseek-chat
compaction:
model: openai/gpt-5-mini
Roles (optional)
If you want named model assignments for specific work types (planning, review, …), configure roles
in settings → Models → Roles. Roles are optional, scoped per profile, and live in settings — not the
model picker. default is not a role or a model. See
Models, roles, and profiles.
The harness adapts to the model
A model does not run in a generic harness. Prompt order, repair enablement, and tool exposure can be tuned per model via harness profiles (MVP) and model roles. You choose the model; Veyyon applies defaults that match how that model behaves. See Execution-order prompts and Model contract.
Per-model harness profiles (MVP)
Optional overrides in config.yml or ~/.veyyon/agent/harness-profiles.yml:
harness:
profiles:
"openai/gpt-4.1":
repair: true
tools: ["read", "edit", "grep", "bash", "write"]
Keys: exact provider/model-id or provider/*. See Per-model repair posture.
Full backends.toml posture tables remain Spec.
Switching providers
Switching providers does not change how you work. Set the key for the new provider, choose its model, and continue. Your workflow, tools, sandbox, and approvals stay the same. There is no lock-in, because there is nothing holding you to one vendor.
$ export OPENROUTER_API_KEY=...
$ veyyon --model openrouter/anthropic/claude-sonnet-4
Comparison guidance: how to choose
Use this as a starting heuristic, not a benchmark claim. Always verify against your own tasks.
| Priority | Prefer | Why |
|---|---|---|
| Strongest agentic coding / tool use | Frontier hosted model you already trust | Best first-attempt edits and planning on hard refactors |
| Cost of long sessions | Cheaper model for the subagent and compaction slots | Keeps interactive quality; shrinks background spend |
| Latency / iteration speed | Fast local or flash-tier cloud model | Tight edit-test loops; hashline recovery still helps weaker models |
| Air-gapped / private code | Ollama or LM Studio | Keys and weights stay on your machine |
| CI automation | Stable mid-tier cloud id pinned with --model | Predictable cost; pair with --approval-mode auto-edit |
| Mixed team | Strong interactive model + a cheaper subagent model | Reviewer stays strong while workers stay cheap |
Rough capability vs cost/latency trade-off:
capability
^
| frontier hosted
| *
| mid-tier hosted
| *
| local 30B+
| *
| small local / flash
| *
+----------------------------------------> cost / latency
(pay more / wait more) (cheap / fast)
Pin models explicitly in CI and shared profiles (--model, modelRoles). Floating “latest” aliases
are convenient interactively and risky in automation.
Where to go next
- Configuring providers — full copy-paste setups.
- Model contract — harness vs provider boundary.
- Getting started — first key and first task.
- Configuration — model defaults and overrides.
- Authentication — login, logout, secret storage.
Sessions
A Veyyon session is the unit of interactive work. Start one in the repository you want to modify:
veyyon
The session records turns, tool activity, approvals, edits, and verification output. Long-running work should survive context pressure through explicit goal state, compacted history, working-set facts, and resume metadata rather than relying on the model to remember everything from raw transcript text.
Common session actions
- Start fresh with
veyyon. - Continue saved work from the session picker on launch, or
/resumeinside the TUI. - Branch a previous conversation with
/branch(from a chosen user message) or duplicate the whole session with/fork. - Manage saved sessions with
/session; garbage-collect old artifacts withveyyon gc. - Run a bounded non-interactive task by passing a prompt:
veyyon "…".
Spec — not shipped: top-level
veyyon resume/fork/archiveverbs. Veyyon resumes from the launch picker or/resume, and branches with/branch//fork(there is no/clone).
Long work
For large tasks, make the desired outcome explicit. The harness should preserve active instructions, recent turns, working files, verification facts, and unresolved blockers through compaction. When a session resumes, Veyyon should make the important state visible to the next model turn instead of presenting a clean-looking summary that dropped the real constraint.
Session files are trees
A session file (~/.veyyon/agent/sessions/**/<timestamp>_<id>.jsonl) is an append-only log, but its entries form
a tree. Every recorded line carries an id and a parent_id, and a leaf_move line moves the
session’s active leaf to any earlier entry, so the next recorded entry starts a sibling branch from
that point. Nothing is ever rewritten: branches you navigate away from stay in the file, and resuming
a session materializes only the entries along the active path (for a session with no branches, that
is the entire file, exactly as before).
Two properties are guaranteed by the storage layer:
- No history rewriting. Branching appends a pointer move; abandoned entries remain addressable.
- Fail closed on corruption. A file whose linkage is broken (duplicate ids, unknown parents, a leaf move to a missing entry) refuses to load with an error naming the bad entry, rather than silently flattening or truncating the conversation.
Session files written by older Veyyon versions have no linkage fields; they load as a linear chain, which is the exact shape they recorded.
Navigating the tree
Run /tree in the TUI to browse every entry of the session, including branches you previously
abandoned. Picking an entry opens a small action menu:
- Jump here continues from that point. For a user message the jump lands just before it and places the full message text in the composer, ready to edit and resubmit; the start of conversation resets to an empty conversation with the original prompt recalled; anything else (an agent reply, a compaction) branches from that entry with an empty composer.
- Label… attaches a short free-text label to the entry so you can find it again later. Labels
render as
[label]tags in the tree; the correspondingtuioption inconfig.ymlalso shows when each label was set. Submitting empty text, or picking Clear label, removes it.
The tree view filter modes (treeFilterMode in config.yml, also toggled in the /tree UI) are:
| Mode | What it shows |
|---|---|
default | Conversation entries (hides low-signal noise) |
no-tools | default plus hides tool-result-only assistant messages |
user-only | User messages only |
labeled-only | Entries with labels |
all | Every raw entry |
Typing filters rows by preview and label text. There is no separate Conversation/User/Labeled/All tab chrome beyond these filter modes — see Branching.
Forking and branching to a new file
/fork and /branch both create a new session file and never modify the original; /tree
navigation above stays inside the current file.
/forkduplicates the entire current session — every entry, including sibling branches — into a new persisted file. There is no entry picker; for a slice from a chosen point, use/branch.veyyon --fork <session-id>does the same at startup, and the launch session picker forks a recorded session the same way./branchpicks an earlier user message and copies the history up to that point (or resets to a fresh root if the picked message is the first one) into a new session file, then recalls the message text into the composer for edit-and-resubmit.
There is no /clone slash command in the shipped registry.
Labels are stored in the session file itself as append-only bookkeeping lines (last write wins), so they survive resume and never rewrite history.
Exporting a session
/export writes a copy of the current session to a file you keep, for backup, inspection, or
moving a conversation between machines. The copy is the session’s rollout file (append-only JSONL,
Veyyon’s own portable session format), so it is faithful and self-contained.
/export: copy to the session’s working directory under the rollout’s own file name./export <path>: copy to<path>. The path may be absolute, relative to the working directory, or~-prefixed; if it names an existing directory, the rollout’s file name is used inside it.
The command reports the destination and the number of bytes written, and never modifies the live session.
Programmatic access uses the Agent Control Protocol (veyyon acp) or SDK embedding; no separate daemon
is required. Session tree operations in the TUI use /tree, /branch, and /fork.
Typing while the agent works
Input entered during a running turn goes to one of two places, and the bottom pane always shows which:
- Steer (
Enter). The message is injected into the current turn: the model sees it at the next tool boundary and adjusts course without abandoning its work. - Queue a follow-up (
Tab, oralt+enter). The message is queued on the server and starts a new turn once the current one finishes. Follow-ups survive TUI restarts and session resumes because the queue lives with the session, not the client. Slash commands and!shell escapes queue client-side instead; they are local actions, not model input.
Queued follow-ups render under the composer (“Follow-ups queued to run after this turn settles”)
until they are delivered. They are never delivered after an interrupt: pressing Esc aborts the
turn and pulls every queued follow-up back into the composer so nothing you typed is lost. To edit
a queued follow-up without interrupting, press the edit-queued-message chord (alt+up by default,
also shift+left): the most recent follow-up returns to the composer and older ones stay queued.
Delivery is governed by steeringMode and followUpMode (both one-at-a-time by default; set to
all to deliver every queued message at the next boundary):
# ~/.veyyon/agent/config.yml
steeringMode: all
followUpMode: all
Programmatic clients use turn/followUp to queue a follow-up on an active thread and
thread/followUps/recall to take every queued follow-up back off the queue (the response returns
the recalled messages). Queueing an empty message is refused loudly.
Next
Read Examples for concrete prompts and workflows.
Cockpit: monitoring multi-agent work
Veyyon’s interactive TUI is the primary cockpit. Today it shows one session at a time with a configurable status line, session tree navigation, background jobs, and optional swarm orchestration. A full IRC-style live multi-agent dashboard (model-per-subagent, drill-in panels) is not shipped yet.
Status line (built)
The bottom status bar summarizes model, path, git, tokens, cost, context, subagents, and related
session state. Configure it in Settings → Appearance → Status Line, reached via /statusline
(jumps straight to this group) or /settings, or in config.yml:
| Key | Purpose |
|---|---|
statusLine.preset | default, minimal, compact, full, nerd, ascii, or custom |
statusLine.leftSegments / statusLine.rightSegments | Segment lists when preset: custom |
statusLine.separator | powerline, pipe, slash, block, none, ascii, … |
statusLine.sessionAccent | Color the bar from the session accent |
statusLine.showHookStatus | Show active hook status when hooks run |
There are 24 built-in segment IDs (StatusLineSegmentId): pi, model, mode (plan/goal/loop
indicators), path, git, pr, subagents, token_in, token_out, token_total,
token_rate, cost, context_pct, context_total, time_spent, time, session, hostname,
cache_read, cache_write, cache_hit, session_name, usage, and collab.
Shipped today: /statusline opens Settings pre-focused on the Status Line group (preset,
separator, and toggles), and preset: custom + leftSegments/rightSegments in config.yml give
full control over which of the 24 IDs appear and in what order.
Spec — not shipped: an in-TUI interactive picker that lets you toggle/reorder individual segment IDs without hand-editing
config.yml(no per-segment checkbox/drag UI exists), a terminal title composer (/title), and terminal pets (/pets).
Session tree and branching (built)
| Command | What it does |
|---|---|
/tree | Browse the session entry tree and jump or label entries |
/branch | Pick an earlier user message and branch a new session file from it |
/fork | Duplicate the entire current session into a new file (no entry picker) |
/session info | Session metadata and stats |
/agents | Configure task subagent definitions (bundled/project/user) |
/cockpit | Live multi-agent monitor: status, model per agent, drill-in transcript |
/jobs | List background async tool jobs |
Session files are append-only JSONL trees under ~/.veyyon/agent/sessions/. See
Sessions.
Inter-agent messaging (built)
Subagents and the main agent can use the irc tool (send, wait, inbox, list) over a
process-global mailbox. /btw runs an ephemeral side question; /tan and /omfg spawn background
agents for tangential work.
Swarm extension (built)
@veyyon/swarm-extension runs multi-agent DAG workflows from YAML: pipeline, parallel, or
sequential modes. Each agent is a full subagent with normal tools.
Standalone: veyyon-swarm path/to/swarm.yaml (bin from @veyyon/swarm-extension).
In the TUI: add the package to extensions, then:
/swarm run path/to/swarm.yaml
/swarm status <name>
/swarm help
State and logs persist under <workspace>/.swarm_<name>/ (state/pipeline.json, logs/*.log).
IRC-style live cockpit
Shipped: /cockpit (alias /hub) opens the Agent Hub overlay: every registered agent except Main,
with status, unread IRC count, model badge, and drill-in transcript chat. Same surface as the
app.agents.hub keybinding.
Spec — not shipped: full IRC-style dashboard with channel tabs, terminal title composer, and an
interactive per-segment status-line picker (/statusline opens settings, not a picker — see
above). Use /cockpit, /jobs, /tree, swarm status files, and the irc tool for multi-agent
visibility today.
Keybindings and Vim mode
Remap TUI shortcuts from ~/.veyyon/agent/keybindings.yml (YAML map of action ID → chord or chord
list). Run /hotkeys in a session to see active bindings.
Customize keybindings
app.model.cycleForward: Ctrl+P
app.model.selectTemporary: Alt+P
app.plan.toggle: Alt+Shift+P
app.history.search: [] # disable
Chord names match the UI (Ctrl+P, Alt+Shift+P, Shift+Enter). Older keybindings.json files migrate
to .yml on load.
Common action IDs include app.model.cycleForward, app.model.select, app.plan.toggle,
app.history.search, app.tools.expand, app.thinking.toggle, app.thinking.cycle (Shift+Tab),
app.editor.external (Ctrl+G), app.message.followUp, app.retry, app.display.reset, and
app.clipboard.pasteImage.
Engineering detail: docs/keybindings.md.
Slash commands
| Command | Action |
|---|---|
/hotkeys | Show active chords |
/settings | Settings UI (includes keymap-related options) |
Spec — not shipped: a
/keymapinteractive remapper and nested[tui.keymap.*]tables. Veyyon useskeybindings.yml+/hotkeysinstead.
Vim mode
When enabled in settings, the composer supports Normal/Insert modal editing (i, Esc, motions,
operators). Toggle via the settings UI if exposed in your build.
Web search
Web search lets the model look things up while it works, using the provider’s native web_search
tool. It is useful for current library versions, API details, and error messages that need a
reference the model was not trained on.
Modes
Web search has four modes, set with the web_search config value:
| Mode | Behavior |
|---|---|
disabled | No web access. |
cached | The default. Uses cached results only, so no live network call is made for a search. |
indexed | Uses an indexed corpus. |
live | Full live web search: the model may issue real-time queries. |
Enabling it
For a single session, override the config value at launch:
$ veyyon -c web_search=live
$ veyyon -p -c web_search=live "what is the current stable Rust release?"
The web_search tool is then available to the model without a per-call approval prompt.
To set it persistently, put it in config.yml:
web_search: live
You can also scope it to a profile, so one profile searches live and another
stays offline. A profile stores its own settings under its agent dir; set the key in that
profile’s config.yml:
# ~/.veyyon/profiles/research/agent/config.yml
web_search: live
Provider support
Web search relies on the provider exposing the web_search tool. It is available on providers and
models that implement it; on providers that do not, the setting has no effect. See
Models and providers.
Approvals
With web search enabled, the tool runs without a per-call approval prompt, because it reads public
web content rather than touching your machine. It is bounded by the selected mode: cached never
makes a live request, live does. If you want the model to never reach the network, use disabled.
See Sandbox and approvals.
Code review
Spec — not shipped: a standalone
veyyon reviewsubcommand and theapprovals_reviewer/auto_review/review_modelconfig keys. What ships today is the interactive/reviewslash command and the passive advisor runtime (--advisor, which reviews each turn and injects notes). For non-interactive or CI review, drive/reviewthroughveyyon --printwith a review prompt. The/reviewand advisor sections below are real; theveyyon reviewCLI and auto-review config are the target shape.
Veyyon can review code changes as a first-class task, separate from an ordinary chat turn. A review reads a set of changes, reasons about them against review instructions, and reports the issues it finds.
Non-interactive review (spec: veyyon review)
The veyyon review subcommand below is the target CLI shape. It selects the
changes to review with mutually exclusive flags:
| Flag | Changes reviewed |
|---|---|
--uncommitted | Staged, unstaged, and untracked changes in the working tree. |
--base <BRANCH> | The diff of the current branch against <BRANCH>. |
--commit <SHA> | The changes introduced by one commit. |
--title <TITLE> | Optional commit title to show in the summary (only with --commit). |
You can also pass a positional prompt with custom review instructions; pass - to read
the instructions from stdin. The change-selection flags and a custom prompt are mutually
exclusive, so a run either targets a change set or follows a free-form instruction.
$ veyyon review --uncommitted
$ veyyon review --base main
$ veyyon review --commit 1a2b3c4 --title "Cache the parsed config"
$ echo "focus on error handling and missing tests" | veyyon review -
Like veyyon --print, review runs are scriptable and exit non-zero on failure, so
you can gate CI on them. Prefer --sandbox workspace-write when you need bounded
automation; --full-auto on exec is deprecated.
Worked walkthrough: review uncommitted work
Suppose you are mid-change on a config parser and want a second pass before you commit.
- Confirm the working tree has the changes you care about:
$ git status -sb
## feature/config-cache
M crates/core/src/config/mod.rs
?? crates/core/tests/suite/config_cache.rs
- Run a review against that working tree:
$ veyyon review --uncommitted
- Veyyon loads the staged, unstaged, and untracked diffs, runs the review model, and prints a human-readable summary. A typical successful run looks like this:
Overall: the cache path is sound, but the new helper fails closed incorrectly when the
override file is missing, and the unit test never exercises a malformed TOML value.
Full review comments:
- Missing file should not fail the whole load: crates/core/src/config/mod.rs:214-228
When `model_catalog_json` points at a path that does not exist, the loader returns a
hard error. Prefer a clear config diagnostic that names the path and continues with the
bundled catalog, matching the other override keys.
- Add a malformed-TOML regression: crates/core/tests/suite/config_cache.rs:1-40
The new test covers the happy path only. Add a case with a trailing comma (or similar
invalid value) and assert the surfaced error names the file and key.
What to notice:
- The summary leads with an overall explanation, then a findings block.
- Each finding has a short title and a
path:start-endlocation, then a body explaining the risk and the smallest fix. - Empty findings still produce a fallback message rather than silent success with no output; treat a clean review as an explicit “nothing to report” result when you automate.
- Fix the named issues, then re-run the same command until the review is clean enough to commit. For CI, capture the process exit status rather than scraping prose:
$ veyyon review --uncommitted
$ echo $?
0
Worked walkthrough: review a branch against main
Use --base when the interesting surface is “everything this branch introduces,” not
just the dirty working tree.
- Make sure you are on the feature branch and that
main(or your integration branch) is reachable:
$ git switch feature/config-cache
$ git fetch origin main
- Review the branch delta:
$ veyyon review --base main
- Sample output for a branch that still needs tightening:
Overall: the branch improves cold-start latency, but the cache key ignores the active
profile, so two profiles can silently share stale catalog data.
Full review comments:
- Include profile in the cache key: crates/core/src/config/mod.rs:88-101
`load_catalog_cached` hashes cwd and catalog path only. Two profiles with different
`model_catalog_json` overrides can collide. Fold the active profile name (or the
resolved override path) into the key.
- Document the invalidation rule: docs/handbook/src/using/models.md:40-52
Operators need to know that changing the active profile busts the cache. Add one
sentence next to the catalog override docs.
Compared with --uncommitted:
| Goal | Flag |
|---|---|
| Review what you are about to commit or amend | --uncommitted |
| Review the whole PR / branch delta | --base <BRANCH> |
| Review a single landed or local commit | --commit <SHA> |
For pull-request bots, --base origin/main (or your repo’s default branch) is usually
the right choice. Keep the working tree clean or stash local noise first if you want the
review to match the pushed commits exactly; otherwise dirty files are not included unless
you also use --uncommitted.
Custom instructions
When the default review rubric is too broad, pass a prompt instead of a change-selection flag:
$ veyyon review "focus on authz boundaries and secret handling in the diff against main"
$ echo "ignore style nits; report only correctness and missing tests" | veyyon review -
The prompt form is mutually exclusive with --uncommitted, --base, and --commit. If
you need both a change set and custom instructions, prefer /review … in the cockpit, or
phrase the prompt so it names the change set explicitly.
Interactive review (/review)
Inside the cockpit, /review reviews your current changes and surfaces the issues it
finds without leaving the session. You can pass inline instructions to focus the review,
for example /review look at the auth changes for missing input validation. The findings
are reported in the conversation so you can act on them immediately.
/review is the right tool when you are already mid-session and want findings in context.
veyyon review is the right tool for scripts, git hooks, and CI.
Auto-review guardian (Spec — not shipped)
Spec — not shipped: everything in this section is the target shape, not shipped behavior. There is no
approvals_reviewer/auto_review/review_modelconfig key, no continuous auto-review approval path, and no/auto-reviewor/approvecommand in the shipped registry today. Approvals are governed bytools.approvalMode(always-ask/write/yolo) and per-tool policy overrides; see Safety.
The target design would turn review into a continuous guardian. Instead of you approving
each sensitive action, an auto-reviewer would inspect the agent’s proposed actions against
a policy and approve or deny them, selected by setting the approvals reviewer to
auto_review.
approvals_reviewer (target) | Who reviews approvals |
|---|---|
user | You approve prompts interactively (default; shipped today via tools.approvalMode). |
auto_review | The guardian would review proposed actions automatically (not shipped). |
Target config shape (not read by Veyyon today):
# TARGET SHAPE — not implemented. Route approval decisions to the guardian
# instead of prompting the user.
approvals_reviewer: auto_review
# Extra policy instructions inserted into the guardian's prompt.
auto_review:
policy: "Deny any command that deletes files outside the workspace."
The model used for auto-review would be configurable through review_model, defaulting to
a dedicated review model rather than the main session model so the guardian’s judgment is
independent of the agent it is reviewing.
When the guardian denies an action, the design calls for a retry path rather than silently
dropping it: a /auto-review command (also /approve) would let the action proceed once
without changing the standing policy. None of this exists in the shipped command
registry.
See also: Safety for how review fits the shipped approval model
(tools.approvalMode), Connectors and Apps for tool approval tiers, and
Slash commands for the full command list.
Non-interactive mode (veyyon --print)
Spec — not shipped: a
veyyon execsubcommand. Non-interactive mode is the-p):veyyon -p "prompt". JSON output is--json, and resume is--resume/--continue. Theveyyon exec resume/veyyon exec reviewsubcommand forms and several flags (--output-schema,--oss) below are the target shape; runveyyon --helpfor the current flag set. The JSON event schema and the recipe shapes are accurate.
veyyon --print (short -p) runs Veyyon without the interactive cockpit: give it one prompt, it does
the work and exits. It is how you drive Veyyon from scripts, CI, git hooks, and other programs.
$ veyyon -p "add a unit test for parse_config and run it"
$ echo "summarize the diff on this branch" | veyyon -p
$ veyyon -p - <<'EOF'
Review src/auth.rs for missing error handling.
EOF
The prompt can be an argument or read from stdin. If stdin is piped and a prompt argument is also
provided, stdin is appended as a <stdin> block.
Run veyyon --help for the generated, always-current flag set. This page documents the intended
integration surface and recipes.
Flag reference
Session and config
| Option | Effect |
|---|---|
--strict-config | Error when config.yml contains fields this version does not recognize. |
--skip-git-repo-check | Allow running outside a Git repository. |
--ephemeral | Do not persist the session under VEYYON_HOME/sessions. |
--ignore-user-config | Do not load $VEYYON_HOME/config.yml. Auth still uses VEYYON_HOME. |
--ignore-rules | Do not load user or project execpolicy .rules files. |
--profile <name> | Activate a profile: relocate the agent dir to $VEYYON_HOME/profiles/<name>/agent/ (see Profiles). Note: -p is --print, not --profile. |
-c key=value | Override any config value for this run (repeatable). |
-C, --cd <DIR> | Working root for the agent. |
--add-dir <DIR> | Extra writable root alongside the workspace (repeatable). |
--system-prompt <TEXT> | Replace the default system prompt (context files / skills still append). |
--append-system-prompt <TEXT> | Append to the effective system prompt. |
Model and local OSS
| Option | Effect |
|---|---|
-m, --model <MODEL> | Pin the conversation model for the run. |
--oss | Use the open-source / local provider path. |
--local-provider <ollama|lmstudio> | Which local provider to use with --oss. |
Sandbox and trust
| Option | Effect |
|---|---|
-s, --sandbox <policy> | Sandbox policy for model-generated shell commands (read-only, workspace-write, danger-full-access, …). |
--dangerously-bypass-approvals-and-sandbox | Skip confirmations and sandboxing. Alias: --yolo. Only for externally sandboxed environments. |
--dangerously-bypass-hook-trust | Run enabled hooks without persisted hook trust for this invocation. |
Headless exec defaults approval policy to never (no TTY to answer). The sandbox is therefore
your containment — set it explicitly in CI.
--full-auto is deprecated
--full-auto is a legacy compatibility trap. It still parses on veyyon --print, prints:
warning: `--full-auto` is deprecated; use `--sandbox workspace-write` instead.
and maps onto workspace-write sandbox behavior. Do not use it in new scripts:
# preferred
$ veyyon --print --sandbox workspace-write "run unit tests and fix failures"
# deprecated (warns)
$ veyyon --print --full-auto "run unit tests and fix failures"
Output
| Option | Effect |
|---|---|
--json | Emit a JSONL event stream on stdout (one event per line). Alias: --experimental-json. Stable integration surface. |
-o, --output-last-message <FILE> | Write just the final assistant message to a file. |
--output-schema <FILE> | Constrain the final message to a JSON Schema so downstream code can parse a known shape. |
--color <auto|always|never> | ANSI color in the human-readable stream (default auto). |
-i, --image <FILE> | Attach an image to the prompt (repeatable / comma-separated). |
Combine them: --json for the full trace, --output-schema when you need a fixed answer shape, -o
when you only care about the final text.
Subcommands
veyyon exec resume
Resume a previous session by id or pick the most recent.
$ veyyon exec resume <SESSION_ID> "continue from the failing test"
$ veyyon exec resume --last "try the next approach"
$ veyyon exec resume --last --all "…" # do not filter sessions by cwd
$ veyyon exec resume --last -i shot.png "what is in this screenshot?"
| Option / arg | Effect |
|---|---|
SESSION_ID | Conversation/session id (UUID) or thread name. UUIDs win if the value parses. |
--last | Resume the newest recorded session without specifying an id. If --last is set and there is no separate prompt arg, the positional is treated as the prompt, not an id. |
--all | Show / consider all sessions (disables cwd filtering). |
-i, --image <FILE> | Images to attach to the prompt sent after resume. |
PROMPT | Prompt after resume; - reads stdin. |
veyyon exec review
Run a code review against the current repository (non-interactive).
$ veyyon exec review --uncommitted
$ veyyon exec review --base main
$ veyyon exec review --commit abcdef1 --title "fix auth timeout"
$ veyyon exec review "Focus on unsafe blocks and unwrap()"
| Option / arg | Effect |
|---|---|
--uncommitted | Review staged, unstaged, and untracked changes. Conflicts with --base / --commit / prompt-only modes as defined by the CLI. |
--base <BRANCH> | Review changes against the given base branch. |
--commit <SHA> | Review the changes introduced by a commit. |
--title <TITLE> | Optional commit title for the review summary (requires --commit). |
PROMPT | Custom review instructions; - reads stdin. |
For a richer interactive review workflow, see Review.
--json event schema
Events are JSON Lines (type tag). Top-level ThreadEvent variants:
type | When |
|---|---|
thread.started | First event; includes thread_id (use with resume). |
turn.started | A new prompt turn began. |
turn.completed | Turn finished; includes usage token counts. |
turn.failed | Turn failed; includes error.message. |
item.started / item.updated / item.completed | Lifecycle for an item in the thread. |
error | Unrecoverable stream error. |
Item payloads (item → type, snake_case) include:
Item type | Meaning |
|---|---|
agent_message | Assistant text (or JSON string when --output-schema constrained the final message). |
reasoning | Reasoning summary text. |
command_execution | Shell command, aggregated output, optional exit_code, status. |
file_change | Patch apply: list of path/kind (add|delete|update) + status. |
mcp_tool_call | MCP invocation lifecycle. |
collab_tool_call | Collab tool lifecycle. |
web_search | Web search request/results. |
todo_list | Agent plan / todo updates. |
error | Non-fatal item-level error. |
Example lines:
{"type":"thread.started","thread_id":"01234567-89ab-cdef-0123-456789abcdef"}
{"type":"turn.started"}
{"type":"item.completed","item":{"id":"item_3","type":"file_change","changes":[{"path":"src/lib.rs","kind":"update"}],"status":"completed"}}
{"type":"item.completed","item":{"id":"item_4","type":"agent_message","text":"Done."}}
{"type":"turn.completed","usage":{"input_tokens":1200,"cached_input_tokens":800,"output_tokens":200,"reasoning_output_tokens":0}}
Pipe into jq:
$ veyyon --print --json "…" | tee run.jsonl \
| jq -r 'select(.type=="item.completed" and .item.type=="agent_message") | .item.text'
Exit codes
veyyon --print follows the handbook exit code contract:
| Code | Meaning |
|---|---|
0 | Success. |
1 | Veyyon error (config, auth, missing session, unrecoverable runtime) — or fallback when a child ended without a reportable status. |
2 | Usage / clap parse error. |
N | Child process exit code passed through when applicable. |
128 + signal | Child killed by signal (POSIX shell convention). |
A failure is never reported as 0, so veyyon --print … && next-step is safe in a pipeline.
CI and automation recipes
1. Hermetic unit-test fix loop
$ veyyon --print --sandbox workspace-write \
--ignore-user-config \
--ephemeral \
--skip-git-repo-check \
-m gpt-5-mini \
"Run cargo test -p mycrate. Fix compile and test failures only. Stop when green."
2. PR review bot with JSON artifact
$ veyyon exec review --base origin/main --json \
| tee review.jsonl
$ jq -r 'select(.type=="item.completed" and .item.type=="agent_message") | .item.text' \
review.jsonl > review.md
Or constrain the final message:
$ cat > /tmp/review-schema.json <<'EOF'
{
"type": "object",
"required": ["findings", "verdict"],
"properties": {
"verdict": { "enum": ["approve", "request_changes", "comment"] },
"findings": {
"type": "array",
"items": {
"type": "object",
"required": ["severity", "path", "summary"],
"properties": {
"severity": { "enum": ["P0", "P1", "P2"] },
"path": { "type": "string" },
"summary": { "type": "string" }
}
}
}
}
}
EOF
$ veyyon --print --sandbox read-only --json \
--output-schema /tmp/review-schema.json \
-o review-out.json \
"Review the staged diff; return JSON matching the schema."
3. Pre-commit hook (staged diff only)
#!/usr/bin/env bash
set -euo pipefail
veyyon --print --sandbox workspace-write --ephemeral --json \
"Review the staged diff. Fail the turn if you find a P0 bug; otherwise summarize." \
| tee /tmp/veyyon-pre-commit.jsonl \
| jq -e 'select(.type=="turn.failed") | length == 0' >/dev/null
4. Batch refactor across a path
$ veyyon --print --sandbox workspace-write -C "$REPO" \
--add-dir /tmp/veyyon-scratch \
"Rename FooConfig -> AppConfig under src/config/. Update call sites. Run cargo check."
5. Resume a long CI job
$ THREAD=$(veyyon --print --json --ephemeral "start the migration plan" \
| jq -r 'select(.type=="thread.started") | .thread_id')
# … later …
$ veyyon exec resume "$THREAD" "continue; finish applying the plan"
(Omit --ephemeral if you need the session on disk for resume.)
Safety notes for automation
- Prefer
--sandbox workspace-writeorread-onlyover bypass flags. - Use
--ignore-user-config/--ignore-ruleswhen the runner’s home directory must not affect the job. --dangerously-bypass-approvals-and-sandboxis for outer jails only — preferexternal-sandboxwhen you can express that cleanly (Sandbox).- Never commit API keys into CI logs; use the runner secret store and
env_key.
See also
- Sessions — resuming and branching.
- Sandbox and approvals — policies for CI.
- Exit codes
- ACP / SDK for programmatic control.
- CLI reference
Themes and identity
Veyyon’s interface is built around near-black, near-white, and a persistent Veyyon silver accent (#B8BDC7).
Shipped themes
| File | Name | Notes |
|---|---|---|
dark.json | Veyyon Dark | Rebranded: #050505 / #FAFAFA / silver #B8BDC7 |
light.json | Light | Upstream oh-my-pi palette (teal accent); not yet silver-rebranded |
Spec — not shipped: Veyyon Light with inverted black/white poles and a persistent silver accent.
Changing theme
- Settings UI:
/settings→ Appearance → theme (or the theme picker on first run). - Config:
themein~/.veyyon/agent/config.yml(profile-specific when using--profile). - Custom themes: drop JSON under
~/.veyyon/agent/themes/; schema indocs/theme.md.
Terminal capability detection maps the same hierarchy for truecolor, ANSI-256, ANSI-16, unknown background, and no-color modes. Reduced-motion settings remove decorative animation without hiding state changes.
What the theme covers
The contract applies to onboarding, composer, menus, dialogs, status line, markdown, tables, diffs, tool output, approvals, progress, and errors — not only the chat pane.
Identity elsewhere
- CLI binary:
veyyon - Config root:
~/.veyyon(PI_CONFIG_DIRoverrides the directory name; XDG paths afterveyyon config init-xdg) - npm packages:
@veyyon/*
Full brand rules: Brand and identity and the repo docs/brand-system.md.
Examples
Use Veyyon from the repository root for tasks where the harness can inspect files, edit, and verify.
Understand a code path
Explain how model/provider configuration is loaded and where an invalid setting is surfaced to the user.
Veyyon should read the relevant configuration files in your project, name the boundary where state enters, and point at tests or missing tests.
Make a small fix
Fix the config error so it names the invalid file and the setting to change. Add the regression test.
Veyyon should edit through hashline or write, run the focused test (bun test in the relevant
package), and stop when the test proves the behavior.
Improve docs with code truth
Make the MCP setup page match the MCP config loader in this project. Verify against the schema.
Inspect the live schema source, update the handbook, and avoid claims not backed by code. Engineering
notes live under docs/.
Review a change
Review the staged diff for correctness, security, missing tests, and public/private boundary leaks.
A useful review names concrete files and lines, separates correctness from style, and recommends the
smallest fix that makes the behavior true. Use /advisor when a second model should comment on each
turn.
Recover a malformed tool call
Use the edit tool with a stale hashline tag and observe the mismatch error.
Malformed tool JSON is repaired when the fix is unambiguous, otherwise it fails loudly with coaching rather than dispatching garbage. Hashline returns actionable stale-tag errors. See Repair overview.
Continue through long context
Keep the security requirement, touched files, and next action intact after compaction.
Use /compact with a focus string; goal mode (/goal) preserves objectives across compaction when
enabled. See Compaction and memory.
Verify before claiming done
Run the package test gate for the area you changed.
Example: bun run test in packages/coding-agent or the project’s documented cargo_full / CI
script from STANDARD.md when touching Rust natives.
Use the hosted model contract
Point Veyyon at a hosted model and rely on the same behavior contract every provider must satisfy.
See Model contract and Providers.
Plan mode and goals
Veyyon ships plan mode and goal mode as engine features. They are separate modes with different tools and continuation behavior.
Spec — not shipped: three enforced chat phases,
<proposed_plan>cards,Shift+Tabcollaboration cycling, and aplan_mode_reasoning_effortconfig key. Veyyon’s plan mode works as described below.
Plan mode (shipped)
Plan mode is read-focused exploration that drafts a plan file before you approve implementation.
Enabling
- Setting:
plan.enabled(defaulttrue; toggle in/settingsAdvanced → Plan). - Slash:
/plantoggles plan mode;/plan <prompt>enters plan mode and submits the prompt. - Related:
/plan-reviewreopens plan review while plan mode is active.
Plan mode blocks goal mode and vibe mode (and vice versa).
Behavior
When plan mode is on:
- Veyyon selects a plan file path (per-session) and sets mode state on the session.
- Active tools are adjusted:
resolveis added for plan approval; built-inwritemay be activated so the agent can draft the plan file. - The agent explores and writes the plan using read-oriented work plus plan-file
write/editas configured. - Finalization uses the
resolvetool with plan approval semantics (plan_approval), not a<proposed_plan>XML tag. - Exiting:
/planagain (with confirmation if a draft exists) pauses or disables plan mode; session recordsmodeentries in the session file.
Plan mode uses the plan model role when configured.
Goals (shipped)
Goal mode tracks a persistent objective on a saved session and can auto-continue when idle.
Enabling
- Setting:
goal.enabled(defaulttrue). - Slash commands:
/goal set <objective>— create or replace goal/goal show— status, tokens, budget/goal pause//goal resume/goal drop— remove goal/goal budget <N|off>— token budget
/guided-goal— interview flow before enabling goal mode
Goal mode blocks plan mode and vibe mode.
Goal state
Stored on the session. Fields include:
id, objective, status, tokenBudget?, tokensUsed, timeUsedSeconds, createdAt, updatedAt
Statuses: active, paused, budget-limited, complete, dropped.
Goal tool
When goal mode is active, the agent can call the goal tool with ops: create, get, complete, resume, drop. Continuation prompts inject on idle turns per goal.continuationModes.
Walkthrough (real commands)
$ veyyon
/goal set Add workspace-write default for headless runs and document it in the handbook
Work in normal mode; use /goal show for progress. When blocked, /goal pause. When done, the agent should complete via the goal tool or you /goal drop.
Spec — not shipped (goal card richness)
Goal state and long sessions describes a richer goal card (verification ledger, working-set fields, reviewer carry-forward) as a future expansion layer. The shipped card is the bounded objective + budget + status model above.
See also
- Sessions — goals require a persisted session
- Context: goal state — architecture notes
Skills
Skills are reusable capabilities Veyyon can draw on. They are defined as data on the filesystem rather than compiled into the Veyyon binary. This structure lets you add, customize, and share capabilities without editing code.
For general information on Veyyon extension capabilities, see Tools, skills, and extension data.
Skill locations
Veyyon loads skills from several locations depending on the desired scope.
| Scope | Location | Description |
|---|---|---|
| System | $VEYYON_HOME/skills/.system | Embedded first-party skills unpacked from the binary at startup. |
| Admin | /etc/veyyon/skills | Machine-wide skills configured by administrators (on Unix-like systems). |
| User | $HOME/.agents/skills | User-installed skills. $VEYYON_HOME/skills is also supported as a legacy fallback. |
| Project | .veyyon/skills | Project-scoped skills placed at the root of a repository. |
| Repository | .agents/skills | Local skills discovered incrementally in directories between the working directory and the project root. |
Other tools’ skills and config (on by default)
By default Veyyon also discovers skills, context files (CLAUDE.md, standalone
AGENTS.md), rules, and MCP servers authored for other AI coding tools —
Claude, Codex, Gemini, Cursor, opencode, Windsurf, Cline, and similar — found on
disk. Your global CLAUDE.md and existing external skills load as a shared base
layer, so Veyyon works with the config you already have.
To run Veyyon on its own config only, turn off the single toggle in
Settings › Providers › Discovery › Import Other Tools’ Config, or set it in
config.yml:
discovery:
importForeignConfig: false
When it is off, those foreign sources are skipped entirely — they never appear
in /skills, in the enable/disable list, or in the model’s context. When it is
on (the default), the per-source toggles under skills.* (for example
skills.enableClaudeUser) give finer control. Veyyon’s own AGENTS.md lives in
.veyyon/ (project) and $HOME/.veyyon/agent (user); those are always read and
are not affected by this toggle.
Profiles isolate skills
Each profile is a separate config root
($HOME/.veyyon/profiles/<name>/agent). Every skill source Veyyon owns — user
skills, managed (auto-learn) skills, and plugin skills — resolves under that
root, so profiles never share a skill directory:
- Switching profiles re-homes user and managed skills to the active profile.
- The
discovery.importForeignConfigtoggle and allskills.*settings are stored per profile, so one profile can import other tools’ skills while another stays clean.
Two things are shared on purpose: project skills (.veyyon/skills next to
your code) belong to the repository, not a profile; and another tool’s own skill
directory ($HOME/.claude/skills, …) is global to the machine — a profile
cannot relocate it, so per-profile isolation there means each profile decides
independently whether to import it (via discovery.importForeignConfig, which
is stored per profile and on by default).
Skill structure
Each skill is defined in its own subdirectory containing a SKILL.md file.
The skill file (SKILL.md)
The SKILL.md file defines the skill’s system prompt instructions and must start with a YAML frontmatter block delimited by ---.
Here is an example SKILL.md file.
---
name: my-custom-skill
description: Performs a custom code audit or analysis.
metadata:
short-description: Audit code for typical issues.
---
# My Custom Skill
Use this skill when analyzing source files. Ensure you focus on:
1. Logic errors.
2. Unhandled edge cases.
The frontmatter contains these fields.
name: The name of the skill (optional). Defaults to the name of the parent folder.description: A description of what the skill does (optional).metadata: Nested metadata block (optional).short-description: A short summary of the skill (optional).
Optional configuration (agents/openai.yaml)
You can configure dependencies, policy settings, and interface preferences by adding an agents/openai.yaml file in the skill’s subdirectory. This file name is inherited from oh-my-pi’s upstream skill format.
Here is an example agents/openai.yaml file.
interface:
display_name: "Code Auditor"
short_description: "Audit code for typical issues"
brand_color: "#B8BDC7"
default_prompt: "Audit the files in the current workspace"
dependencies:
tools:
- type: "command"
value: "cargo check"
description: "Checks Rust project compilation"
policy:
allow_implicit_invocation: true
products:
- veyyon
The following fields are available in agents/openai.yaml.
interface: TUI presentation settings (optional).display_name: The display name shown in TUI lists (optional).short_description: A short description (optional).icon_small/icon_large: Filesystem paths to icons (optional).brand_color: A hex color code or color name (optional).default_prompt: Pre-filled text when launching the skill (optional).
dependencies: List of tools needed for the skill (optional).tools: A list of dependency blocks. Each block can specify atype(for example,commandorurl), avalue(for example, the command name or URL), adescription, an optionaltransport, an optionalcommandpath, and an optionalurl.
policy: Restrict how the skill is invoked (optional).allow_implicit_invocation: A boolean (defaults totrue). Iffalse, the skill will not be implicitly suggested or automatically injected by the model.products: A list of product names to restrict the skill to (for example,veyyon). If set, the skill only loads for matching products.
Configuration
Skills are configured in the skills block of Veyyon’s config.yml file.
Include instructions
By default, Veyyon formats and appends system instructions for all active skills to the system rules block on every turn. You can disable this automatic injection.
skills:
include_instructions: false
Enable or disable bundled skills
To disable all embedded system skills, use this configuration.
skills:
bundled:
enabled: false
Manage individual skills
You can selectively enable or disable individual skills by name or by their absolute path.
skills:
config:
- name: my-custom-skill
enabled: false
- path: /home/user/.agents/skills/other-skill/SKILL.md
enabled: true
Interactive TUI controls
In the terminal user interface, you can manage and list skills interactively.
Slash commands
/skillsopens a selection menu with these choices.- List skills shows all active skills. Typing
@or$(depending on whether thementions_v2feature is active) in the composer opens the mentions list directly. - Enable/Disable Skills opens a toggle list of all discovered skills. You can select individual skills to turn them on or off.
- List skills shows all active skills. Typing
When you close the toggle list, the TUI displays a status message stating how many skills were enabled or disabled.
Related recipes
For goal-shaped “give the agent a new capability” flows that stitch skills with MCP and plugins, see Task guides.
Skills authoring
A skill is a folder that adds a reusable capability to Veyyon. This page explains how to write one from scratch. For background on how skills are discovered and loaded, see Skills.
Directory structure
Skills are placed on the filesystem according to the scope where they should apply. Veyyon loads skills from these locations in order:
| Scope | Directory | Purpose |
|---|---|---|
| System | $VEYYON_HOME/skills/.system | Bundled first-party skills unpacked from the Veyyon binary. |
| Admin | /etc/veyyon/skills | Machine-wide skills managed by administrators. |
| User | $HOME/.agents/skills | Skills available to the current user. $VEYYON_HOME/skills is also supported as a legacy path. |
| Project | .veyyon/skills | Skills shipped with a project. Placed at the project root. |
| Repository | .agents/skills | Local skills discovered incrementally in directories between the working directory and the project root. |
Create a new skill by making a directory inside one of these scopes and adding a SKILL.md file. The name of the directory is the default name of the skill.
A skill directory may contain additional files:
my-skill/
├── SKILL.md
├── agents/
│ └── openai.yaml
└── scripts/, references/, assets/ ...optional
Only SKILL.md is required. The other files are loaded when the skill is active or when the model explicitly asks for them.
SKILL.md frontmatter
Every SKILL.md must begin with a YAML frontmatter block between --- lines.
---
name: my-skill
description: Describe what this skill does and when to use it.
metadata:
short-description: Short summary shown in skill lists.
---
The frontmatter fields are:
name: The skill identifier. Optional; defaults to the parent directory name. Use lowercase letters, digits, and hyphens. Keep it under 64 characters.description: A clear explanation of what the skill does and when it should be triggered. This is the main signal the model uses to decide whether to invoke the skill.metadata.short-description: A short summary shown in the TUI and other skill lists. Optional; keep it to one line.
Be specific in the description. A vague description makes the skill less likely to be selected at the right moment.
Writing the body
The body of SKILL.md is a Markdown document that contains the instructions, context, and workflow for the skill. The body is loaded only after the skill has been selected, so the frontmatter acts as the gate and the body acts as the guide.
Guidelines for the body:
- State the purpose at the top.
- List the conditions that trigger this skill.
- Provide a step-by-step workflow or a set of rules the model should follow.
- Include examples of inputs and expected outputs.
- Mention any bundled scripts, references, or assets and when to use them.
- Keep it concise. Long skills consume context and may be ignored. Split detailed reference material into files under
references/and link to them fromSKILL.md.
Example body:
# Code review
Use this skill when the user asks for a review of a code change or pull request.
1. Check for logic errors, unhandled edge cases, and test coverage.
2. Verify that the change matches the project style and conventions.
3. Flag any breaking changes or missing documentation.
4. Report findings as a numbered list with file paths and line numbers.
Do not leave comments on external platforms unless the user explicitly asks for it.
Optional agents/openai.yaml
The agents/openai.yaml file controls how the skill appears in the TUI and how it may be invoked. It is optional but recommended for skills that users interact with directly.
Example:
interface:
display_name: "Code Review"
short_description: "Review code for quality and correctness"
brand_color: "#B8BDC7"
default_prompt: "Review the current diff"
dependencies:
tools:
- type: "command"
value: "git diff"
description: "Inspect local changes"
policy:
allow_implicit_invocation: true
products:
- veyyon
Available fields:
interface: Presentation settings.display_name: Name shown in the TUI skill list.short_description: One-line description shown in the TUI.icon_small/icon_large: Paths to optional icons relative to the skill directory.brand_color: A hex color or color name for the skill chip.default_prompt: Pre-filled text when the skill is opened from the TUI.
dependencies: Tools the skill needs.tools: A list of dependency blocks. Each block may specifytype,value,description,transport,command, andurl.
policy: Invocation restrictions.allow_implicit_invocation: Whether the skill may be suggested or injected automatically. Defaults totrue. Set tofalseto require explicit selection.products: A list of product names that may load this skill (for example,veyyon). If omitted, the skill loads for all products that support it.
Registering in config.yml
Skills are configured under the skills section of config.yml. You can control whether skill instructions are included automatically, whether bundled skills are enabled, and which individual skills are on or off.
Disable automatic inclusion of all skill instructions:
skills:
include_instructions: false
Disable all bundled system skills:
skills:
bundled:
enabled: false
Enable or disable individual skills by name or by absolute path:
skills:
config:
- name: my-skill
enabled: true
- path: /home/user/.agents/skills/other-skill/SKILL.md
enabled: false
Worked example: a project-local skill
This example creates a skill inside a project that adds a custom onboarding check.
Create the skill directory:
mkdir -p .veyyon/skills/onboarding-check
Create .veyyon/skills/onboarding-check/SKILL.md:
---
name: onboarding-check
description: Review the project for missing onboarding files and recommend improvements.
metadata:
short-description: Check onboarding completeness.
---
# Onboarding check
Use this skill when the user asks whether the project is ready for a new contributor.
1. Check that the project has a README, CONTRIBUTING guide, and LICENSE file.
2. Verify that the build command is documented and can be run from the README.
3. List any missing or incomplete files.
4. Suggest concrete additions that would help a new contributor start quickly.
Report the result as a short checklist with `done` or `missing` for each item.
Create .veyyon/skills/onboarding-check/agents/openai.yaml:
interface:
display_name: "Onboarding Check"
short_description: "Check project onboarding completeness"
default_prompt: "Is this project ready for a new contributor?"
policy:
allow_implicit_invocation: true
products:
- veyyon
Register it in the project config.yml:
skills:
config:
- name: onboarding-check
enabled: true
Invoking the skill
In the TUI, you can invoke the skill in two ways:
- Type
/skillsand select List skills, then choose Onboarding Check from the list. Thedefault_promptwill be pre-filled if one is configured. - Type a natural request such as “Is this project ready for a new contributor?” in the composer. The model reads the skill description and selects the skill automatically when the request matches.
From a command-line invocation, refer to the skill by its name. The exact command depends on the Veyyon CLI version; run veyyon --help or see the CLI reference for the current syntax.
Plugins
Veyyon supports extending the agent’s capabilities with plugins. A plugin can add custom skills, Model Context Protocol (MCP) servers, applications, lifecycle hooks, and terminal user interface (TUI) customizations.
Plugin Structure
Every plugin is a directory with a .veyyon-plugin/plugin.json manifest file. The manifest describes the plugin’s metadata and lists its integration points.
Plugin Manifest (plugin.json)
The following fields are defined in the plugin.json schema:
| Field | Type | Description |
|---|---|---|
name | String | The unique name of the plugin. Only ASCII alphanumeric characters, hyphens, and underscores are allowed. |
version | String | The version of the plugin (optional). Defaults to "local". |
description | String | A description of the plugin (optional). |
keywords | Array of Strings | Keywords used to index and search for the plugin (optional). |
skills | String or Array of Strings | Path or paths to directories containing skill definitions (optional). |
mcpServers | String or Object | Path to a file or an inline object defining the plugin’s MCP servers (optional). |
apps | String | Path to a file defining the plugin’s custom applications and connectors (optional). |
hooks | String or Array of Strings or Object | Path or paths to hook definition files, or inline hook objects (optional). |
interface | Object | Presentation metadata for the TUI (optional). Details like display name, descriptions, default prompt, developer name, category, website, privacy policy, and logo files. |
Marketplaces
Marketplaces are collections of plugins. A marketplace is a directory or Git repository containing a marketplace.json catalog manifest.
Veyyon checks the following relative paths under a marketplace root to locate its catalog manifest:
.agents/plugins/marketplace.json(canonical path).agents/plugins/api_marketplace.json.claude-plugin/marketplace.json
The marketplace catalog defines:
- A
nameand optional presentation metadata. - A
pluginslist. Each entry contains the plugin name, installation policy, authentication policy, supported products, and its source. The source can point to a local directory or a Git repository (with optional branch, tag, commit ref, or subdirectory path).
File Locations
Veyyon stores plugins and marketplaces under the home directory (VEYYON_HOME). If VEYYON_HOME is not set, it defaults to ~/.veyyon.
| Path | Description |
|---|---|
VEYYON_HOME/.tmp/marketplaces/<marketplace_name> | Snapshots/clones of Git marketplaces. |
VEYYON_HOME/plugins/cache/<marketplace_name>/<plugin_name>/<version> | Installed plugin files. |
VEYYON_HOME/plugins/data/<plugin_name>-<marketplace_name> | Persistent data generated by the plugin. |
VEYYON_HOME/plugins/.marketplace-plugin-source-staging | Staging area for downloading remote plugins. |
Command Line Interface
You can manage plugins and marketplaces using the veyyon plugin and veyyon plugin marketplace command groups.
Managing Plugins
Add a Plugin
Install a plugin from a configured marketplace. Specify the plugin as plugin_name@marketplace_name, or use the --marketplace option.
$ veyyon plugin add sample@debug
$ veyyon plugin add sample --marketplace debug
Use the --json flag to print the installation result as JSON.
List Plugins
List installed plugins and their statuses.
$ veyyon plugin list
Options:
-m, --marketplace <name>: Filter listing to a specific marketplace.--json: Print the output as JSON.--available: Include uninstalled but available plugins from the marketplaces (requires--json).
Remove a Plugin
Uninstall a plugin from local cache and config.
$ veyyon plugin remove sample@debug
Use the --json flag to return the removal result as JSON.
Managing Marketplaces
Add a Marketplace
Add a local path or Git repository to your configured marketplace sources.
$ veyyon plugin marketplace add ./path/to/marketplace
$ veyyon plugin marketplace add owner/repo --ref main
$ veyyon plugin marketplace add https://github.com/owner/repo --sparse plugins/foo
Options:
--ref <ref>: Git branch, tag, or commit SHA to fetch.--sparse <path>: Limits the Git clone to a specific subdirectory. Can be repeated.--json: Print the result as JSON.
List Marketplaces
List all configured marketplaces and their filesystem root directories.
$ veyyon plugin marketplace list
Use the --json flag to output the list as JSON.
Upgrade Marketplaces
Fetch the latest revisions for configured Git marketplaces. Omit the marketplace name to upgrade all configured Git marketplaces.
$ veyyon plugin marketplace upgrade
$ veyyon plugin marketplace upgrade debug
Use the --json flag to output the upgrade result as JSON.
Remove a Marketplace
Remove a configured marketplace by name.
$ veyyon plugin marketplace remove debug
Use the --json flag to output the result as JSON.
TUI Integration
Veyyon TUI integrates plugin management directly.
Slash Commands
/plugins: Opens the interactive plugins catalog popup. You can browse all available plugins, install or uninstall them, and toggle plugins on or off./apps: Opens the apps and connectors status and configuration popup.
TUI Keybindings
When the /plugins popup is open, you can use the following keyboard shortcuts on the selected marketplace tab:
Ctrl+R: Remove the selected configured marketplace.Ctrl+U: Upgrade the selected configured Git marketplace.
Configuration File
Marketplaces and plugin settings are stored persistently in your config.yml file.
Marketplaces Section
Marketplaces are registered under the marketplaces map, keyed by marketplace name.
marketplaces:
debug:
source_type: git
source: https://github.com/owner/repo.git
ref: main
sparse_paths: ["plugins/"]
last_updated: "2026-07-13T10:00:00Z"
Plugins Section
Each installed plugin has an entry under the plugins map keyed by <plugin_name>@<marketplace_name>. You can toggle the plugin or configure tool settings for its contributed MCP servers.
plugins:
"sample@debug":
enabled: true
mcp_servers:
sample_server:
enabled: true
default_tools_approval_mode: always
enabled_tools: ["compile", "test"]
disabled_tools: ["delete_all"]
Related recipes
Install and discovery tools (list_available_plugins_to_install, request_plugin_install)
are documented in Tools reference. For task-shaped recipes that
combine plugins with MCP and skills, see Task guides.
Hooks
Veyyon supports lifecycle hooks via the extension runner. The shipped model is a TypeScript
hook module: files discovered under .veyyon/hooks/ are imported by the Bun runtime and export a
factory that registers pi.on(...) handlers.
Spec — not shipped: the JSON
hooks.jsonfile,config.ymlhooks:tables, and the{ "type": "command" }external-subprocess model with thePreToolUse/PostToolUse/ matcher / trust-level semantics documented on the rest of this page. Veyyon does not load hooks from JSON or run them as external subprocess commands. Those shapes are documented as the target compatibility surface; to write a hook that runs today, use a TypeScript module that callspi.on(...).
Event Lifecycle
| Event Name | Supports Matcher | Matcher Target | Description |
|---|---|---|---|
PreToolUse | Yes | Tool name | Fires before Veyyon executes a tool. |
PermissionRequest | Yes | Tool name | Fires when Veyyon requests permission to run a tool. |
PostToolUse | Yes | Tool name | Fires after Veyyon completes tool execution. |
PreCompact | Yes | Compaction trigger | Fires before Veyyon compacts the conversation history. |
PostCompact | Yes | Compaction trigger | Fires after Veyyon compacts the conversation history. |
SessionStart | Yes | Startup source | Fires when a Veyyon session starts. |
UserPromptSubmit | No | None | Fires when the user submits a new prompt. |
SubagentStart | Yes | Subagent type | Fires when a subagent starts. |
SubagentStop | Yes | Subagent type | Fires when a subagent finishes. |
Stop | No | None | Fires when Veyyon exits. |
Startup and Compaction Matchers
For SessionStart, the matcher filters against the startup source. The possible values are:
startup(a new Veyyon session starts)resume(resuming a saved session)clear(starting a clean conversation)compact(starting a compaction turn)
For PreCompact and PostCompact, the matcher filters against the trigger name.
Configuration
Spec — not shipped: the two JSON/YAML configuration paths in this section describe the target model, not the shipped TS
pi.on(...)loader. They are documented for compatibility only.
The spec model configures hooks in two ways:
- In
config.yml(either system-level, user-level, or project-local config) under ahookssection. - In a standalone
hooks.jsonfile inside the config folder (such as the project’s.veyyon/hooks.jsonor the user’s$VEYYON_HOME/hooks.json).
Hook Handler Schema
Every hook handler configuration supports the following parameters:
| Field Name | Type | Description |
|---|---|---|
type | String | Must be "command". (Prompt and agent hook types are reserved but not supported yet). |
command | String | The command string to execute in the shell. |
commandWindows | String | (Optional) A Windows-specific command override. |
timeout | Integer | (Optional) The execution timeout in seconds. Defaults to 10 minutes. |
async | Boolean | (Optional) Whether to execute the hook asynchronously. |
statusMessage | String | (Optional) A message to show in the TUI while the hook is running. |
Configuration Examples
In config.yml (spec model)
hooks:
PreToolUse:
- matcher: "^Bash$"
hooks:
- type: command
command: "echo 'Running bash command'"
timeout: 30
In hooks.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "echo 'Running bash command'",
"timeout": 30
}
]
}
]
}
}
Security and Trust Model
Veyyon requires user-level and project-local hooks to be trusted before they can execute. Every hook handler has a calculated cryptographic hash derived from its event name, matcher, and normalized settings.
Upon discovery, Veyyon classifies hooks into one of four trust levels:
- Managed: Hooks loaded from administrator-enforced requirements layers (such as
/etc/veyyon/config.ymlor MDM profiles) are always trusted. - Trusted: User-level or project-local hooks whose calculated hash matches a trusted hash stored under the
hooks.statesection in$VEYYON_HOME/config.yml. - Modified: Hooks that were previously trusted, but their command or matcher settings have been modified.
- Untrusted: Newly discovered hooks that have no trust record.
Veyyon disables project-local configuration, hooks, and execution policies in untrusted project directories. You can bypass trust verification by running Veyyon with the --dangerously-bypass-hook-trust command-line flag.
Execution Mechanics
Command hooks run as subprocesses. On Unix-like systems, Veyyon runs commands using the shell specified in the SHELL environment variable, falling back to /bin/sh with the -lc arguments. On Windows, Veyyon uses the shell program in the COMSPEC environment variable, falling back to cmd.exe with the /C argument.
Hook Input (Standard Input)
Veyyon writes a JSON object to standard input (stdin) containing the event details. Every event payload shares these properties:
{
"sessionId": "a-unique-session-id",
"turnId": "active-turn-id",
"agentId": "active-subagent-id",
"agentType": "active-subagent-type",
"transcriptPath": "/absolute/path/to/transcript.jsonl",
"cwd": "/absolute/path/to/cwd",
"hookEventName": "PreToolUse",
"model": "model-name",
"permissionMode": "permission-mode"
}
Event-specific stdin properties include:
PreToolUse:toolName(string),toolInput(JSON arguments),toolUseId(string)PermissionRequest:toolName(string),toolInput(JSON arguments)PostToolUse:toolName(string),toolInput(JSON arguments),toolResponse(JSON output),toolUseId(string)PreCompact&PostCompact:trigger(string)SessionStart:source(string)SubagentStart:agentId(string),agentType(string)UserPromptSubmit:prompt(string)Stop:stopHookActive(boolean),lastAssistantMessage(string or null)SubagentStop:stopHookActive(boolean),agentId(string),agentType(string),lastAssistantMessage(string or null),agentTranscriptPath(string or null)
Hook Output (Standard Output)
Hooks communicate their results to Veyyon by writing to standard output (stdout):
- If stdout is empty, Veyyon proceeds normally.
- If stdout is not valid JSON, Veyyon treats it as a plain-text message and injects it into the LLM context.
- If stdout is JSON, it must conform to the hook schema.
Universal JSON Output Properties
{
"continue": true,
"stopReason": "Optional explanation",
"suppressOutput": false,
"systemMessage": "Optional warning text to show in the TUI"
}
If continue is set to false, Veyyon aborts the action (this is ignored by SubagentStart).
Event-Specific JSON Output Properties
Hooks can return structured updates under the hookSpecificOutput key or direct properties:
-
PreToolUse:decision(string): Set to"block"or"approve".reason(string): Required ifdecisionis"block".hookSpecificOutput:hookEventName:"PreToolUse".permissionDecision(string): Set to"allow","deny", or"ask".permissionDecisionReason(string).updatedInput(JSON): Rewrites the arguments of the tool before Veyyon runs it.additionalContext(string): Text to append to the LLM context.
-
PermissionRequest:hookSpecificOutput:hookEventName:"PermissionRequest".decision:behavior(string): Set to"allow"or"deny".message(string). (Note:updated_input,updated_permissions, or settinginterrupttotruewill cause the hook to fail closed).
-
PostToolUse:decision(string): Set to"block".reason(string): Required ifdecisionis"block".hookSpecificOutput:hookEventName:"PostToolUse".additionalContext(string): Text to append to the LLM context.updatedMCPToolOutput(JSON): Rewrites the output returned by the MCP tool.
-
SessionStart,SubagentStart,UserPromptSubmit:hookSpecificOutput:hookEventName: Match the event name.additionalContext(string): Text to append to the LLM context.
-
Stop,SubagentStop:decision(string): Set to"block".reason(string): Required ifdecisionis"block".
The /hooks TUI Command
You can view and manage lifecycle hooks using the /hooks slash command in the Veyyon TUI. Running /hooks fetches the active hooks configuration and opens the hooks browser in the bottom pane.
Interface Navigation
Use the following keyboard shortcuts in the hooks browser:
- Arrow Up / Down: Navigate through the list of events or individual handlers.
- Page Up / Down: Scroll by page.
- Enter (on Events page): Opens the handlers configured for the selected event.
- Enter or Space (on Handlers page): Toggles the enabled state of the selected trusted hook.
- t (on Handlers page): Trusts the selected hook.
- t (on Events page): Trusts all untrusted or modified hooks discovered in the current directory.
- Escape (on Handlers page): Returns to the events list.
- Escape (on Events page): Closes the hooks browser.
Related recipes
For a worked “run a check after every edit” flow that pairs hooks with non-interactive
veyyon --print runs, see
Task guides.
Custom hooks guide
Spec — not shipped: this guide documents the target hook model — subprocess command hooks configured in JSON/YAML and driven by
PreToolUse/PostToolUse/ matcher / trust-level semantics (the same shape as Claude Code). Veyyon loads hooks as TypeScript modules that callpi.on(...), not from JSON and not as external subprocess commands. Read this page as the compatibility reference for the target model; to write a hook that runs today, author a TS module.
Hooks let you run logic at specific points in Veyyon’s lifecycle. You can use them to guard tool calls, enrich context after a tool runs, log events, or enforce local policy.
For a full reference of events, fields, and output formats, see Hooks.
What hooks are
A hook is a command that Veyyon runs when a lifecycle event occurs. The hook receives event details as JSON on stdin and can respond on stdout to approve, block, modify, or annotate the event. Most hooks can use a regular expression matcher so they only run when the context matches, for example only for Bash tool calls or only for compact events.
Use hooks for tasks like:
- Blocking or approving specific tools before they run.
- Adding policy warnings to the context after a tool runs.
- Injecting project-specific instructions when a session starts.
- Logging or auditing tool usage outside the TUI.
The ten lifecycle events
Hooks can attach to ten events. Eight of them support a matcher.
| Event | Matcher target | When it fires |
|---|---|---|
PreToolUse | Tool name | Before Veyyon executes a tool. |
PermissionRequest | Tool name | When Veyyon asks for permission to run a tool. |
PostToolUse | Tool name | After a tool finishes. |
PreCompact | Compaction trigger | Before compacting the conversation. |
PostCompact | Compaction trigger | After compacting the conversation. |
SessionStart | Startup source | When a session starts. |
SubagentStart | Subagent type | When a subagent starts. |
SubagentStop | Subagent type | When a subagent finishes. |
UserPromptSubmit | None | When you submit a prompt. |
Stop | None | When Veyyon exits. |
UserPromptSubmit and Stop do not support matchers. The other events can use a regex to decide whether the hook should run.
Regex matchers
A matcher is a regular expression that filters the event. The hook only runs when the matcher matches the target value for that event.
| Event | Matcher target | Example values |
|---|---|---|
PreToolUse, PermissionRequest, PostToolUse | Tool name | ^Bash$, ^Grep$, ^WriteFile$ |
PreCompact, PostCompact | Compaction trigger | ^context-limit$ |
SessionStart | Startup source | ^startup$, ^resume$, ^clear$, ^compact$ |
SubagentStart, SubagentStop | Subagent type | ^security-auditor$ |
Match patterns are anchored where you want them. ^Bash$ runs only for the Bash tool, not BashBackground or ReadOnlyBash.
Authoring a command hook
A command hook has this schema:
| Field | Type | Description |
|---|---|---|
type | string | Must be "command". |
command | string | The shell command to run. |
commandWindows | string | Optional Windows override. |
timeout | integer | Optional timeout in seconds, default 600. |
async | boolean | Optional. Runs the hook without blocking. |
statusMessage | string | Optional message shown in the TUI while the hook runs. |
In config.yml (spec model)
hooks:
PreToolUse:
- matcher: "^Bash$"
hooks:
- type: command
command: "python3 ~/.veyyon/hooks/audit_bash.py"
timeout: 30
statusMessage: "Auditing Bash command..."
In hooks.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "python3 ~/.veyyon/hooks/audit_bash.py",
"timeout": 30,
"statusMessage": "Auditing Bash command..."
}
]
}
]
}
}
Stdin and stdout payload formats
Input
Veyyon writes a JSON object to the hook’s stdin. All events share these fields:
{
"sessionId": "uuid",
"turnId": "turn-id",
"agentId": "agent-id",
"agentType": "agent-type",
"transcriptPath": "/path/to/transcript.jsonl",
"cwd": "/path/to/cwd",
"hookEventName": "PreToolUse",
"model": "model-name",
"permissionMode": "permission-mode"
}
Event-specific fields are added on top. For example, PreToolUse includes toolName, toolInput, and toolUseId. See the Hooks reference for the full list.
Output
The hook prints to stdout:
- Empty stdout: Veyyon continues as if the hook did not intervene.
- Non-JSON stdout: Veyyon treats the text as
additionalContextand appends it to the LLM context. - JSON stdout: Veyyon parses it as a structured response.
Shared optional fields on JSON responses:
{
"continue": true,
"stopReason": "Optional reason",
"suppressOutput": false,
"systemMessage": "Optional message shown in the TUI"
}
Do not use continue: false on PreToolUse or PermissionRequest. Those events reject it as unsupported. Block a PreToolUse call with one of:
{
"decision": "block",
"reason": "Forbidden destructive command detected."
}
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Forbidden destructive command detected."
}
}
PostToolUse and other events have different fields; see Hooks for the full matrix.
Verification scripts
Write and test hooks outside the TUI first. A simple test harness is to run the script with a sample JSON payload:
# test_pre_hook.sh
python3 - <<'PY'
import json, sys
payload = json.load(sys.stdin)
if payload.get("toolName") == "Bash":
tool_input = payload.get("toolInput", {})
command = tool_input.get("command", "")
if "rm -rf /" in command:
print(json.dumps({
"decision": "block",
"reason": "Refusing destructive command.",
"systemMessage": "Blocked a destructive Bash command."
}))
sys.exit(0)
print("")
PY
Run it manually with a sample payload:
cat <<'JSON' | bash test_pre_hook.sh
{
"hookEventName": "PreToolUse",
"toolName": "Bash",
"toolInput": { "command": "rm -rf /" }
}
JSON
If the output is valid JSON and the behavior matches your intent, register the hook and trust it using /hooks or by recording its hash.
Trust levels
Veyyon assigns each hook a trust level before it runs:
| Level | Meaning |
|---|---|
| Managed | Loaded from administrator-enforced layers like /etc/veyyon/config.yml or MDM profiles. Always trusted. |
| Trusted | User or project hooks whose hash matches a recorded trusted hash in the hooks.state config section. |
| Modified | Previously trusted, but the command or matcher changed. |
| Untrusted | Newly discovered hooks with no trust record. |
Untrusted hooks do not run. Veyyon also disables project-local config, hooks, and execution policies in untrusted directories. You can review and trust hooks inside the TUI with the /hooks command. You can bypass trust verification entirely with --dangerously-bypass-hook-trust, but only for local testing.
Worked example: PreToolUse guard
This hook blocks Bash commands that contain a destructive pattern.
.veyyon/hooks.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "python3 .veyyon/hooks/block_destructive.py",
"timeout": 10,
"statusMessage": "Checking Bash command..."
}
]
}
]
}
}
.veyyon/hooks/block_destructive.py:
import json, sys
payload = json.load(sys.stdin)
if payload.get("toolName") != "Bash":
sys.exit(0)
command = payload.get("toolInput", {}).get("command", "")
forbidden = ["rm -rf /", "mkfs.", ":(){ :|:& };:", "> /dev/sda"]
if any(pattern in command for pattern in forbidden):
print(json.dumps({
"decision": "block",
"reason": "Forbidden destructive command detected.",
"systemMessage": "This Bash command was blocked by a local hook."
}))
sys.exit(0)
When a Bash command matches the forbidden list, Veyyon blocks the tool using the decision/reason pair and shows the optional systemMessage in the TUI.
Worked example: PostToolUse enrichment
This hook adds a project note after the Read tool runs, so the model sees the note in the next turn.
.veyyon/hooks.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "^Read$",
"hooks": [
{
"type": "command",
"command": "python3 .veyyon/hooks/read_notes.py",
"timeout": 10
}
]
}
]
}
}
.veyyon/hooks/read_notes.py:
import json, sys, os
payload = json.load(sys.stdin)
if payload.get("toolName") != "Read":
sys.exit(0)
file_path = payload.get("toolInput", {}).get("path", "")
if os.path.basename(file_path) == "Cargo.toml":
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "When editing Cargo.toml, remember to run `cargo check` afterward."
}
}))
The additionalContext text is appended to the model context after the Read tool completes, so it influences the next assistant response.
Managing hooks with the /hooks command
Open the hook browser at any time by typing /hooks in the TUI. The browser shows events and their handlers, lets you enable or disable trusted hooks, and lets you trust new or modified hooks. You can also trust hooks by recording their hash in the hooks.state config section in your user config, but /hooks is the fastest way to review and approve hook changes.
MCP
Model Context Protocol (MCP) lets Veyyon connect to external tools and data sources. Veyyon is an MCP client by default; agent control protocol (ACP) and SDK paths can expose Veyyon as a server.
Transports
| Transport | Use when |
|---|---|
stdio | Local executable (Node, Python, binary) |
| HTTP / SSE | Remote hosted MCP service |
Configure servers
Preferred: JSON files managed by Veyyon:
| Scope | Path |
|---|---|
| Project | .veyyon/mcp.json |
| User | ~/.veyyon/agent/mcp.json |
Example:
{
"$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
"mcpServers": {
"sqlite": {
"type": "stdio",
"command": "node",
"args": ["/path/to/sqlite-mcp/index.js"],
"env": { "DB_PATH": "/var/data/app.db" }
}
}
}
Veyyon also discovers MCP entries from Claude, Cursor, VS Code, OpenCode, and related tool configs.
Setup walkthrough: MCP server setup.
Engineering detail: docs/mcp-config.md.
In the TUI
| Command | Purpose |
|---|---|
/mcp | List servers, connection/auth status, exposed tools |
/mcp add | Add a server (wizard) |
/mcp list | List configured servers |
/mcp remove <name> | Remove a server |
/mcp test <name> | Test connectivity |
/mcp reauth <name> | Refresh OAuth |
Tool names appear namespaced as mcp__<server>__<tool>.
Running Veyyon as an MCP server
Use the ACP integration or SDK embedding rather than a separate veyyon mcp-server subcommand unless
your build registers it.
Spec — not shipped: a
[mcp_servers.*]config table and aveyyon mcpCLI. Veyyon configures MCP throughmcp.jsonand the/mcpcommand instead.
MCP server setup
Veyyon can connect to third-party Model Context Protocol (MCP) servers so external tools and data sources become available to the agent. This guide explains how to register those servers, choose a transport, authenticate, and fix the most common connection problems.
For an overview of what MCP does in Veyyon, see MCP. Engineering reference:
docs/mcp-config.md.
Where servers are configured
MCP servers are configured as JSON in mcp.json, not in config.yml:
| Scope | Path |
|---|---|
| Project | .veyyon/mcp.json |
| User | ~/.veyyon/agent/mcp.json (profile: ~/.veyyon/profiles/<name>/agent/mcp.json) |
Veyyon also discovers MCP entries from Claude, Cursor, VS Code, and OpenCode configs. The easiest way
to add a server is /mcp add in the TUI, which writes to mcp.json for you.
File shape
{
"$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
"mcpServers": {
"sqlite": {
"type": "stdio",
"command": "node",
"args": ["/path/to/sqlite-mcp-server/index.js"]
}
},
"disabledServers": []
}
Top-level keys: mcpServers (map of name to config) and disabledServers (names to turn off). Server
names match ^[a-zA-Z0-9_.-]{1,100}$. Shared per-server fields: enabled, timeout (milliseconds;
0 disables the client-side timeout), auth, and oauth.
Choose a transport
type | Use when | Fields |
|---|---|---|
stdio (default) | Local executable, script, or binary. | command (required), args, env, cwd |
http | Remote streamable-HTTP service. | url (required), headers |
sse | Legacy SSE service (prefer http). | url (required), headers |
type is optional for stdio because it is inferred from command.
A minimal streamable HTTP server:
{
"mcpServers": {
"analytics": {
"type": "http",
"url": "https://analytics.example.com/mcp"
}
}
}
Pass environment variables
Local stdio servers often need environment variables:
{
"mcpServers": {
"sqlite": {
"type": "stdio",
"command": "node",
"args": ["/path/to/sqlite-mcp-server/index.js"],
"env": { "DB_PATH": "/var/data/app.db", "SQLITE_LOG_LEVEL": "warn" }
}
}
}
For HTTP servers, pass credentials or account ids as headers:
{
"mcpServers": {
"analytics": {
"type": "http",
"url": "https://analytics.example.com/mcp",
"headers": { "Authorization": "Bearer ${ANALYTICS_MCP_TOKEN}", "X-Account-Id": "acct_123" }
}
}
}
Authenticate
Bearer token via header
Keep the token in the environment and reference it from a header rather than committing the raw value:
$ export ANALYTICS_MCP_TOKEN="your-token-value"
OAuth
Some HTTP servers require an interactive OAuth flow. Add an oauth block, then authenticate from the
TUI with /mcp reauth <name>:
{
"mcpServers": {
"crm": {
"type": "http",
"url": "https://crm.example.com/mcp",
"oauth": { "clientId": "veyyon-crm-client" }
}
}
}
Approve tools
Every tool an MCP server exposes appears namespaced as mcp__<server>__<tool> and is governed by the
global tools.approvalMode plus per-tool tools.approval. To require a prompt for a specific server’s
tools, set a per-tool policy:
# ~/.veyyon/agent/config.yml
tools:
approval:
mcp__sqlite__query: prompt
To turn a server off entirely, add its name to disabledServers in mcp.json.
In the TUI
| Command | Purpose |
|---|---|
/mcp | List servers, connection/auth status, and exposed tools |
/mcp add | Add a server (writes mcp.json) |
/mcp list | List configured servers |
/mcp remove <name> | Remove a server |
/mcp test <name> | Test connectivity |
/mcp reauth <name> | Refresh OAuth |
Run /mcp verbose to see exactly which tools, resources, and templates Veyyon registered.
Resolve common errors
Server not found
For a stdio server, the command is usually not on PATH or the path is wrong. Check it directly:
$ node /path/to/sqlite-mcp-server/index.js
Fix the path, or add the directory to env.PATH. For an http server, check the URL with curl:
$ curl -i https://analytics.example.com/mcp
Timeout
If /mcp shows the server but tool calls time out, raise the per-server timeout (milliseconds), or
set VEYYON_MCP_TIMEOUT_MS (legacy OMP_MCP_TIMEOUT_MS) for the whole process:
{ "mcpServers": { "analytics": { "type": "http", "url": "…", "timeout": 60000 } } }
Authentication failure
For header tokens, confirm the environment variable is set in the same shell that starts Veyyon. For
OAuth, run /mcp reauth <name> again. If a 401/403 persists, the token may have expired or the server
may require additional headers.
Model cannot see the tools
If a server is connected but the model never uses its tools, check that the server is not in
disabledServers, that enabled is not false, and that no tools.approval entry denies the
namespaced tool. Run /mcp verbose to see what was registered.
Where to go next
- MCP for the full feature overview, including running Veyyon as an MCP server.
- Configuration for config file layout and precedence.
- Tools, skills, and extension data for other ways to extend Veyyon.
Connectors and Apps
Spec — not shipped: provider-hosted connectors (
/apps,[apps]config tables, account-gated app tools). Veyyon does not ship this subsystem today.
What ships instead
Extend Veyyon with tools that are implemented and documented today:
| Integration | Purpose |
|---|---|
| MCP | Attach MCP servers; tools appear as mcp__… with approval tiers |
| Plugins | Install extensions; veyyon plugin install … |
| Hooks | Event-driven automation in the agent loop |
| Skills | Bundled instructions and tool patterns |
| OAuth providers | /login, /setup / /providers for supported APIs |
Tool policy uses tools.approvalMode and tools.approval.<tool> — same machinery for bash, MCP, and custom tools (docs/approval-mode.md).
Roadmap note
If provider-hosted connectors ship, they will add a feature flag, account discovery, an /apps UI, and
[apps] config. Until then, apps tables are not current behavior.
See also
Session branching
Veyyon sessions are trees, not linear chat logs. Each entry has id and parentId; the active position is the current leaf. Branching appends from the chosen parent without deleting sibling branches.
Engine reference: docs/tree.md, docs/session-tree-plan.md.
Navigate in place: /tree
/tree opens the session tree selector (TreeSelectorComponent). It moves the leaf within the same session file — no new session id.
Also opens via:
- Keybinding
app.session.tree - Double-Escape when
doubleEscapeAction = "tree"(default) /branchwhendoubleEscapeAction = "tree"(routes to tree instead of user-message branch picker)
Filters
treeFilterMode setting (cycle with Ctrl+O / Shift+Ctrl+O, or Alt+D/T/U/L/A shortcuts):
| Mode | Shows |
|---|---|
default | Conversation nodes; hides label/custom/model_change/thinking bookkeeping |
no-tools | default plus hides tool-result-only assistant messages |
user-only | User messages only |
labeled-only | Entries with labels |
all | Every entry type |
Search is fuzzy, case-insensitive, AND across tokens.
Selection behavior
- User / custom_message: leaf moves to parent; message text prefills composer for edit/resubmit.
- Other entry types: leaf becomes selected node; empty composer.
- Current leaf: no-op.
Labels: Shift+L set/clear; stored as append-only label entries.
Optional branch summary when branchSummary.enabled is true: after picking a node, choose summarize abandoned path before continuing.
New session file: /branch and /fork
| Command | Behavior |
|---|---|
/branch | Pick a user message; copy history up to that boundary into a new session file (or reset root); prefills composer. When doubleEscapeAction = "tree", opens /tree instead. |
/fork | Duplicate the entire current session — every entry, including sibling branches — into a new persisted file (handleForkCommand). No entry picker; for a slice from a chosen point, use /branch. |
| CLI | veyyon --fork <session-id> at startup |
/fork and /branch change session files. /tree does not.
There is no /clone slash command in the shipped registry.
Ephemeral side questions: /btw
/btw <question> (not /side) runs an ephemeral side thread with inherited context, then returns. See implementation handleBtwCommand in interactive-mode.ts.
/tan runs tangential background agent work — separate from /btw.
Configuration
doubleEscapeAction: tree # tree | branch | none
treeFilterMode: default # default | no-tools | user-only | labeled-only | all
branchSummary:
enabled: false
reserveTokens: 16384
Command availability
Branching commands require a started session (at least one turn). Some commands are disabled while tasks run; /btw may remain available for steering — see TUI status when blocked.
See also
Memory
Veyyon can remember durable project context across sessions. Memory is off by default; pick a
backend in config.yml or /settings.
Backends (built)
| Backend | Storage | Best for |
|---|---|---|
off | — | No memory injection or retention |
local | Markdown under the agent memories dir (MEMORY.md, memory_summary.md, skills/) | Curated summaries from past session files |
mnemopi | SQLite via @veyyon/pi-mnemopi | Vector + FTS recall, auto-retain, compaction hooks |
hindsight | Hindsight backend (when configured) | Alternative structured memory |
Enable in config:
memory:
backend: mnemopi # or local, hindsight, off
Mnemopi (recommended for long-running work)
With memory.backend: mnemopi, Veyyon:
- Opens scoped SQLite banks (
global,per-project, orper-project-tagged). - Recalls relevant memories into a
<memories>block on the first turn (and refreshes the base prompt when recall runs fromagent_start). - Retains completed turns on a configurable interval (
mnemopi.retainEveryNTurns, default 4). - Supplies pre-compaction context from the memory backend when compaction runs.
Key settings: mnemopi.scoping, mnemopi.recallLimit, mnemopi.autoRecall, mnemopi.autoRetain,
mnemopi.polyphonicRecall, mnemopi.noEmbeddings. See
docs/mnemosyne-memory-backend.md.
Dedicated tools when enabled: memory_recall, memory_retain, memory_reflect, memory_edit.
The /memory slash command exposes view, stats, diagnose, clear, and enqueue.
Local summary pipeline
With memory.backend: local, a background pipeline at startup extracts durable signal from past
session JSONL files, then consolidates into MEMORY.md, memory_summary.md, and optional
skills/. The agent reads artifacts via memory:// URLs on the read tool.
Engineering detail: docs/memory.md.
Compaction (three knobs)
Context compaction is separate from durable memory. Settings → Compaction (or config.yml) exposes
exactly three fields:
| Setting | Key | Values |
|---|---|---|
| Threshold | compaction.thresholdPercent (docs alias: compaction.threshold) | percent of the context window |
| Type | compaction.strategy (docs alias: compaction.type) | handoff or snap |
| Model | compaction.model | model id; unset uses the interactive model |
handoff writes a structured session transfer; snap archives history via the snapcompact engine.
Run on demand with /compact. See Compaction and project memory.
What the model sees
Recalled or summarized memory is background context, not instructions. Current user messages, tool output, and repo state win on conflict. The agent should cite memory paths when memory changes a plan and pair citations with fresh repo evidence.
Configuration
Use /memory or /settings (Memory group), or set keys under memory.*, mnemopi.*,
hindsight.*, or memories.* depending on the active backend.
Per-profile: the active backend, its settings, and its stored data (mnemopi SQLite path, local Markdown artifacts, hindsight bank id) are all scoped to the active profile (
VEYYON_PROFILE). Two profiles never share memory — switching profiles switches to an independentmemory.backendand an independent database/artifact set under that profile’s agent directory.
Spec — not shipped: rollout-DB memory phases,
<oai-mem-citation>blocks,rollout_summaries/, and a/memoriessettings pane. Veyyon uses the backends above instead.
Profiles
A profile is a named config root that relocates Veyyon’s user-level agent directory. It lets you keep separate settings, sessions, MCP config, skills, and hooks for different kinds of work (for example work vs bounty) while sharing one veyyon binary.
For the mental model and roles, see Roles and profiles.
What a profile owns (shipped)
When --profile <name> is active (or VEYYON_PROFILE is set), native Veyyon paths under ~/.veyyon/agent/ resolve to:
~/.veyyon/profiles/<name>/agent/
That relocation is uniform across settings, sessions, blobs, slash commands, rules, prompts, hooks, tools, extensions, skills, MCP, keybindings, theme, and top-level instruction files (SYSTEM.md, RULES.md, AGENTS.md) discovered from the user agent dir. A named profile does not read another profile’s agent/ tree at runtime.
Keybindings: each profile owns agent/keybindings.*. New profiles seeded with veyyon profile new --from default copy the default profile’s keybindings once. On first launch of an older named profile that has no keybindings file, Veyyon performs the same one-time seed and logs it. There is no live merge from the default profile after that.
Project-level dirs (<cwd>/.veyyon, .claude, etc.) are not profile-scoped; they follow the working directory.
Other tools’ config (skills and CLAUDE.md/AGENTS.md written for Claude, Codex, and similar) is on by default and controlled per profile by discovery.importForeignConfig, so one profile can keep importing it while another opts out to run native-only. Another tool’s own global dir (~/.claude/skills, …) cannot be relocated into a profile — see Skills › Profiles isolate skills.
Activating a profile
- CLI:
veyyon --profile <name>(no short form;-pis--print). - Env:
VEYYON_PROFILE=<name>(legacy:OMP_PROFILE,PI_PROFILE). - Shell alias:
veyyon --profile work --alias myworkinstalls a managed block in your shell rc (seecli/profile-alias.ts).
Profiles are chosen at process start. There is no /profile slash command in the shipped TUI; start a new veyyon invocation to switch.
Creating and managing profiles
$ veyyon profile list
$ veyyon profile new work
$ veyyon profile new bounty --from blank
$ veyyon profile rm work --yes
newcreates~/.veyyon/profiles/<name>/agent/with the expected identity dirs (skills/,commands/, …).--from default(default) seedsconfig.yml, keybindings, MCP, skills, and other identity files from the default profile. Sessions, blobs, and databases are not copied.--from blankcreates an empty agent tree.rmrefuses the default profile, the active profile, and destructive deletes without--yes.
You can still create a profile implicitly by running veyyon --profile <name> once; use profile new when you want seeding without launching the TUI.
Do not document inline [profiles.<name>] tables or standalone <name>.config.yml files as shipped; settings use config.yml under the active agent dir.
Model slots and roles (per profile)
Each profile’s config.yml owns the three model slots and optional roles:
model: openai/gpt-5 # interactive (also set live with /model)
subagent:
model: deepseek/deepseek-chat
compaction:
model: openai/gpt-5-mini
strategy: handoff # or snap
thresholdPercent: 80
modelRoles: # optional; settings → Models → Roles
plan: openai/o3
default is not a model or role. Switching profiles switches these assignments with the profile.
See also
Models, roles, and profiles
Veyyon keeps model selection explicit and simple. Three ideas stay separate:
- the model you talk to — chosen live,
- roles — optional named model assignments, configured in settings and scoped per profile,
- profiles — separate user config trees.
Three model slots
Veyyon gives you three explicit model choices. Each is set on its own — there is no default model
that silently stands in for the others.
| Slot | Runs | Where you set it |
|---|---|---|
| Interactive model | the conversation you are in | /model (live), --model, config.yml model |
| Subagent model | task subagents | settings → Models |
| Compaction model | context compaction | settings → Models |
Leave the subagent or compaction model unset and that slot uses your interactive model.
Picking the interactive model
Use /model, the model carousel, or the CLI (--model). Your choice is saved to the active profile’s
config.yml and applies to the conversation. This is the only model you choose from the picker.
Subagent and compaction models
Set them once in settings → Models, or in config.yml:
model: anthropic/claude-sonnet-5 # interactive
subagent:
model: anthropic/claude-haiku-4-5
compaction:
model: openai/gpt-5
They are plain fields — one model each, no per-role matrix and no indirection. When a task subagent
spawns, it runs on subagent.model. When context is compacted, it runs on compaction.model.
Roles (optional)
A role is a named model assignment for a kind of work (task, plan, advisor, …). Roles are
optional. Edit them in /settings → Model → Roles → Role Models: pick a role, then use the
searchable model selector (auth / local / no auth shown on each row). Never edit config.yml for
this — settings writes the active profile’s assignments for you.
default is not a role and not a model. The model you pick with /model is simply your interactive
model; nothing falls back through a default slot.
Task subagents
The main agent spawns task subagents for parallel scoped work. They run on subagent.model.
Task isolation (task.isolation.*) and concurrency (task.maxConcurrency) are settings — not a
surface you configure every turn.
Compaction settings
Compaction has exactly three settings, in settings → Models → Compaction:
- Auto-compaction threshold (
compaction.thresholdPercent) — when to compact. - Type (
compaction.strategy) —handofforsnap. - Model (
compaction.model) — the compaction model above.
See Memory and compaction for what each type does.
Profiles
A profile relocates ~/.veyyon/agent/ to ~/.veyyon/profiles/<name>/agent/ for native Veyyon
config (settings, sessions, MCP, skills, hooks, and your role assignments and model slots). Activate
with veyyon --profile <name> or VEYYON_PROFILE.
Profiles are chosen at process start, not mid-session. See Profiles for the
lifecycle commands (veyyon profile list/new/rm).
Tool approval
Behavior is governed by tools.approvalMode (plan, ask, auto-edit, yolo) and per-tool
tools.approval overrides. Configure in /settings → Safety or --approval-mode on launch.
Where each choice lives
| You want to | Where |
|---|---|
| Change the interactive model | /model, config.yml model |
| Set the subagent model | settings → Models (subagent.model) |
| Set the compaction model | settings → Models (compaction.model) |
| Assign models to roles (optional) | settings → Models → Roles, per profile |
| Separate user config trees | veyyon --profile <name> |
| Tighten tool prompts | tools.approvalMode, tools.approval |
See also
Personalities
Spec — not shipped: a
/personalityslash command. Configure personality via/settingsor thepersonalitykey inconfig.yml(below).
Personalities change how the agent writes replies, not which tools it has or what sandbox/approval policy applies.
Available personalities (shipped)
A personality injects a <personality> block into the system prompt when enabled, changing tone only.
| Personality | Config value | Effect |
|---|---|---|
| Default | default | Standard Veyyon tone |
| Pragmatic | pragmatic | Concise, task-focused |
| Friendly | friendly | Warm, collaborative |
| None | none | No personality block |
Schema default: personality: default in settings-schema.ts. The setting is a free-form string, not a closed enum — see Extending the catalog.
Configuring personality
- Settings UI:
/settings→ Model tab → Prompt group → Personality. Options are resolved at render time (built-ins + your~/.veyyon/personalitiesand project.veyyon/personalitiesfiles); changing the value refreshes the base system prompt immediately. - Config file: in
~/.veyyon/agent/config.yml(or profile agent dir):
personality: pragmatic
There is no /personality slash command in the shipped registry. Subagents use none regardless of the main setting (sdk.ts).
Extending the catalog
The 3 shipped personalities are seeds, not a closed set. Add a <name>.md file and its filename stem becomes a selectable personality name; the file body is injected verbatim as the <personality> block:
- User-level:
~/.veyyon/personalities/<name>.md— available in every project. - Project-level:
.veyyon/personalities/<name>.md— available only in that project, and overrides a user or built-in personality of the same name.
Precedence for a given name is project > user > built-in. For example, dropping ~/.veyyon/personalities/pirate.md with the body You speak like a pirate. and setting personality: pirate renders <personality>You speak like a pirate.</personality> with no rebuild. A project .veyyon/personalities/default.md overrides the built-in default for that project only.
Edge cases:
noneis a reserved sentinel that disables the block; a file literally namednone.mdis ignored (it can never shadow the disable behavior).- An empty or whitespace-only personality file is treated as absent — the next tier (or the built-in) is used instead, so the block is never emitted empty.
- Setting
personalityto a name that resolves to nothing (no built-in, user, or project file) falls back todefaultand prints a visible warning; the<personality>block is never silently emitted empty for a real (non-none) request.
See packages/coding-agent/src/personality/resolver.ts for the resolver implementation.
Boundaries
- Personality does not grant tools, change
tools.approvalMode, or bypass sandboxing. - Personality text is escaped and injected as a bounded system-prompt section; it cannot override project rules or tool policy.
See also
- Configuration
- System prompt customization (engine doc)
Export and import
Session export (built)
/export writes the current session transcript. Target format depends on the extension:
| Target | Format |
|---|---|
.jsonl or default | Native session JSONL (append-only entries) |
.html / .htm | Standalone offline HTML transcript |
Paths resolve relative to the working directory, ~/, or absolute paths. Directory targets receive
the default session filename inside the folder.
Migration from Claude Code (built)
/import is not in the builtin slash registry; Claude migration may be available through setup
flows or extensions. When import runs, it merges compatible settings from .claude/ into Veyyon’s
config tree (.veyyon/, ~/.veyyon/agent/), including MCP, hooks, skills, and agents.
Import works on local sessions; no background daemon is required.
Typical migrated items:
- Settings from
.claude/settings.json→config.yml - MCP servers →
mcp.json - Hooks →
.veyyon/hooks.jsonor extension hooks - Skills →
.agents/skills/.veyyon/skills - Subagents →
.veyyon/agents CLAUDE.md→AGENTS.md
See Migration guide.
Task guides
Short, goal-shaped recipes for common jobs. Each guide points at the deeper feature pages; use those when you need full schemas or edge cases.
Related references: Hooks guide, Non-interactive mode, MCP, Skills, Memory, Branching, Sandbox.
Automate a check on every edit (hooks)
Goal: every time the agent finishes an edit, run a deterministic check and fail closed when it breaks.
The shipped hook model is a TypeScript module discovered under .veyyon/hooks/ (project) or
~/.veyyon/agent/hooks/ (user). The module exports a factory that registers handlers with pi.on(...).
// .veyyon/hooks/post-edit-check.ts
export default (pi) => {
pi.on("PostToolUse", async (event) => {
if (!/^(edit|write)$/.test(event.tool)) return;
// run your check (spawn a test/linter) and block on failure
});
};
The Bun runtime imports the module at startup; restart or /reload to pick up changes. See
Hooks guide for the event names and handler contract.
Spec — not shipped: JSON
hooks.json/config.ymlhooks:tables and the{ type: "command" }subprocess model withPreToolUse/PostToolUsematchers. Veyyon runs TSpi.on(...)modules, not external subprocess commands.
Run a bounded task from a script or CI
Use a one-shot prompt when the trigger lives outside the agent (pre-commit, CI, entr, watchexec):
$ veyyon --approval-mode auto-edit \
"Run the focused tests for the files changed in the last commit and fail if any regress"
The prompt can be an argument or piped on stdin. Pick auto-edit for unattended write access with an
exec prompt, or --yolo to auto-approve everything in a trusted, ideally externally-sandboxed
environment. Keep the smoke set small; put heavy suites in CI.
Spec — not shipped: a dedicated
veyyon execsubcommand with--jsonevent streams,-ofinal-message capture, andveyyon review --uncommitted. See Non-interactive mode for the current scripted surface, and use/reviewin the TUI for a review pass.
Give the agent a new tool (MCP or skills)
Goal: teach Veyyon a capability you do not want to bake into the binary.
Choose the surface
| Need | Use |
|---|---|
| Talk to an external system (DB, SaaS, browser bridge) over a protocol | MCP server |
| Package reusable instructions, scripts, and examples as data | Skill (SKILL.md) |
Path 1: add an MCP server
Add it from the TUI, which writes mcp.json for you:
/mcp add
Or edit ~/.veyyon/agent/mcp.json (user) / .veyyon/mcp.json (project) directly:
{
"mcpServers": {
"database": {
"type": "stdio",
"command": "node",
"args": ["/path/to/db-mcp-server/index.js"],
"env": { "DB_PATH": "/var/data/app.db" }
}
}
}
Confirm discovery with /mcp (or /mcp list), then ask the agent to use the new tool by name. If the
server needs OAuth, run /mcp reauth <name>. Details: MCP,
MCP setup.
Path 2: author a skill
Create a skill directory in user or project scope, for example
~/.veyyon/agent/skills/audit-config/SKILL.md (or .veyyon/skills/… in a repo):
---
name: audit-config
description: Audit Veyyon config.yml for unsafe approval and tool-policy combinations.
metadata:
short-description: Config safety audit
---
# Audit config
When asked to audit configuration:
1. Read the active config.yml and any project overrides.
2. Flag `yolo` approval paired with broad tool allow-lists on untrusted repos.
3. Prefer concrete remediations over generic advice.
Restart or open a new session so skill discovery picks it up. Skills are data — you can version them in
git and share them without shipping a new veyyon build. Prefer a skill when the “tool” is mostly
prompting and local scripts; prefer MCP when the capability is a long-lived external process. See
Skills.
Share context across sessions (memory and branching)
Goal: keep decisions, conventions, and alternate explorations available without pasting transcripts by hand.
Memory: carry guidance into new threads
Cross-session memory is off by default. Turn on a backend with memory.backend in config.yml:
# ~/.veyyon/agent/config.yml
memory:
backend: mnemopi # off (default), local, hindsight, mnemopi
Operate it from the TUI with /memory (/memory stats, /memory diagnose). Keep memory on for repos
where conventions matter; leave it off for throwaway scratch sessions. See Memory.
Branching: explore without losing the main line
Use the session tree when you need parallel context inside one problem.
| Intent | Command |
|---|---|
| Inspect the tree / jump to a prior turn | /tree |
| Copy history into a new session from a user message | /fork |
| Duplicate the current leaf immediately | /clone |
Typical flow: reach a decision point, /fork or /clone to try an alternate approach, and keep the
branch worth keeping. Full behavior: Branching and Sessions.
Memory vs branching
- Memory shares durable, consolidated knowledge across different sessions and days.
- Branching shares live transcript context within or beside the current problem.
- Use both: branch to explore, then let memory capture the decision you kept.
See also
- Configuration for the keys these guides touch
- Examples for prompt-shaped tasks
Configuration
Veyyon is configurable without being fussy. Defaults are sensible; change only what you care about.
This page is organized by goal, not by alphabetized key name. For provider setup see
Models and providers and Authentication. For the full settings
catalog, precedence rules, and every key’s type and default, see docs/settings.md.
Where settings live
Settings are YAML mappings. Persistent settings live in config.yml; custom model providers live in
models.yml; MCP servers live in mcp.json.
| Scope | Path | Notes |
|---|---|---|
| Global | ~/.veyyon/agent/config.yml | The main persistent file. /settings and veyyon config set write here. |
| Project | <repo>/.veyyon/config.yml | Loaded when the cwd has a non-empty config dir. Edit by hand. |
| CLI overlay | any file passed with --config <file> | Process-local, repeatable, never persisted. |
Precedence, low to high:
defaults <- global config <- project config <- --config overlays <- runtime flags
Read and write from a shell with veyyon config:
$ veyyon config list # all settings with effective values
$ veyyon config get tools.approvalMode
$ veyyon config set compaction.type snap
$ veyyon config path # print the active agent directory
/settings does the same inside a live session. Keys must match a schema path exactly
(theme.dark, not theme).
Pick models and providers
Three explicit model slots, each set on its own:
| Goal | What to set |
|---|---|
| Choose the model you talk to | --model / /model (saved as model) |
| Model for spawned subagents | subagent.model |
| Model for context compaction | compaction.model |
| Named model assignments (optional) | modelRoles, per profile (settings → Models → Roles) |
| Add a local or BYOK provider | a providers: entry in models.yml (see Models) |
# ~/.veyyon/agent/config.yml
model: openai/gpt-5 # interactive
subagent:
model: deepseek/deepseek-chat
compaction:
model: openai/gpt-5-mini
/model changes only the interactive model. /status shows every effective model. default is not a
model — nothing falls back through a default slot.
Stay safe (approvals and sandbox)
| Goal | What to set |
|---|---|
| When Veyyon asks before acting | tools.approvalMode: plan, ask, auto-edit, yolo (default); legacy always-ask/write accepted |
| Per-tool policy | tools.approval: map a tool to allow / deny / prompt |
| Advisor review pass | advisor.enabled + modelRoles.advisor |
tools:
approvalMode: auto-edit
approval:
bash: prompt
read: allow
Per run, --approval-mode <mode> and --auto-approve / --yolo override the mode. The OS shell
sandbox (Landlock/seccomp/Seatbelt) is a separate hard boundary — see
Sandbox and approvals and Safety.
Run unattended or in CI
| Goal | What to pass |
|---|---|
| Non-interactive one-shot | veyyon --approval-mode auto-edit "…" (prompt as arg or piped stdin) |
| Auto-approve everything (trusted only) | --yolo |
| Temporary settings for one run | --config ./ci-settings.yml (repeatable) |
Lifecycle automation inside sessions uses hooks.
Control context, memory, and compaction
Compaction summarizes older history instead of truncating it. It has exactly three settings:
| Goal | What to set |
|---|---|
| Auto-compaction threshold | compaction.thresholdPercent (alias: compaction.threshold) |
| Compaction type | compaction.strategy: handoff or snap (alias: compaction.type) |
| Compaction model | compaction.model (unset = your interactive model) |
| Cross-session memory backend | memory.backend: off (default), local, hindsight, mnemopi |
compaction:
thresholdPercent: 80
strategy: handoff
model: openai/gpt-5-mini
memory:
backend: mnemopi
See Compaction and project memory and Memory.
Restrict tools for a repo or role
Deny a tool with per-tool policy, or disable a built-in tool entirely:
tools:
approval:
bash: deny
edit: deny
bash:
enabled: false
Plan mode and agent definitions can narrow the tool set further. Enforcement removes the tool from both the model-visible set and the dispatch registry.
Use profiles for different kinds of work
A profile relocates the user agent directory so you can keep separate settings, sessions, MCP, and
skills for different work (for example work vs bounty) while sharing one veyyon binary. With
--profile <name> active, ~/.veyyon/agent/… resolves to ~/.veyyon/profiles/<name>/agent/….
$ veyyon --profile work
$ # edit ~/.veyyon/profiles/work/agent/config.yml
Activate with --profile <name> (no short form — -p is --print), or VEYYON_PROFILE=<name> (legacy OMP_PROFILE /
PI_PROFILE). Profiles are chosen at process start; there is no /profile switch mid-session. See
Profiles.
Spec — not shipped: self-contained
<name>.config.ymlprofile files (a full environment per file,[profiles.<name>]tables). Veyyon relocates the agent directory and readsconfig.ymlunder it.
Wire MCP servers and hooks
MCP servers are configured as JSON, not in config.yml:
// ~/.veyyon/agent/mcp.json
{
"mcpServers": {
"database": {
"type": "stdio",
"command": "node",
"args": ["/path/to/db-mcp-server/index.js"]
}
}
}
Hooks are discovered from hooks/pre/* and hooks/post/* under a config dir. Recipes:
Task guides. Schemas: MCP, Hooks.
Where to go next
- Getting started runs Veyyon with the defaults.
- Task guides for hooks, MCP/skills, and memory/branching recipes.
- Safety for how approval mode and the sandbox affect real tool use.
- Extending for tools, skills, and extension data.
- CLI reference for flags.
Feature flags
Spec — not shipped: a standalone
veyyon featuressubcommand, the--enable-feature/--disable-featurelaunch flags, and a[features]config table. Today feature listing is apluginsubcommand —veyyon plugin features— experimental toggles are driven from/experimentalin the TUI, and persistent toggles live in thefeaturesmap ofconfig.yml.
Veyyon gates optional and in-progress capabilities behind named feature flags. A flag lets you turn a capability on or off without a new build, and it lets the project ship a capability in stages before it becomes a default. Each flag has a stage that tells you how finished it is.
| Stage | Meaning |
|---|---|
under development | Incomplete; on only for development. |
experimental | Usable but may change; off by default. |
stable | Finished and on by default (unless noted). |
deprecated | Still works but slated for removal. |
removed | No longer available; the flag is inert. |
Listing and toggling
veyyon plugin features prints every known feature with its stage and whether it is
currently enabled for your configuration. That list is broader than the product surface:
many entries are internal switches used for migrations, protocol experiments, or staged
rollout. Treat most of them as engineering cruft unless this page (or /experimental)
presents them as user-facing.
$ veyyon plugin features
Persistent toggles write the features config map:
features:
memories: true
prevent_idle_sleep: true
For a single run, override the config value with -c features.<name>=true|false:
$ veyyon -c features.memories=true "summarize prior decisions in this repo"
Inside the cockpit, /experimental opens a view for experimental toggles on the running
session. Persistent changes belong in the features config map.
Unknown feature keys are rejected rather than silently accepted.
User-facing toggles
Documented here: flags operators and end users actually turn for product behavior. Omit
the long tail of internal keys (item_ids, deferred_executor, removed protocol shims,
sandbox migration leftovers, and similar). If veyyon plugin features shows a name that is
not below, assume it is internal unless a release note says otherwise.
Session intelligence
| Key | Stage | Default | What it does |
|---|---|---|---|
memories | experimental | off | Generate and reuse cross-session memories (/memories, [memories]). |
goals | stable | on | Long-running goal tracking (/goal, goal tools). |
workspace_dependencies | stable | on | Scan Cargo/Rust workspace roots for extra project context. |
personality | stable | on | Personality / tone controls in the product UI. |
fast_mode | stable | on | Fast-mode collaboration preset. |
mentions_v2 | stable | on | Updated @-mention behavior in the cockpit. |
Automation and tools
| Key | Stage | Default | What it does |
|---|---|---|---|
hooks | stable | on | Lifecycle hooks ([hooks] / hooks.json). |
unified_exec | stable | on (non-Windows) | Unified exec path for shell tool runs. |
shell_tool | stable | on | Expose the shell tool surface. |
skill_mcp_dependency_install | stable | on | Assist installing MCP dependencies declared by skills. |
tool_suggest | stable | on | Suggest tools when the model may need them. |
sleep_tool | under development | off | Model-callable sleep (input-interruptible). |
Spec — not shipped: there is no
guardian_approvalflag and no guardian / auto-review approval path in the shipped flag registry or config schema. See Auto-review guardian (Spec — not shipped).
Apps, plugins, and collaboration
| Key | Stage | Default | What it does |
|---|---|---|---|
apps | stable | on | Apps / connectors surface. |
plugins | stable | on | Plugin loading and management. |
plugin_sharing | stable | on | Share / distribute plugins. |
multi_agent | stable | on | Multi-agent collaboration runtime. |
multi_agent_v2 | under development | off | Next multi-agent runtime and role-model overrides. |
enable_mcp_apps | under development | off | MCP-backed apps path. |
Browser, media, and realtime
| Key | Stage | Default | What it does |
|---|---|---|---|
in_app_browser | stable | on | In-app browser surface (requirements may still apply). |
browser_use | stable | on | Browser-use tool path. |
browser_use_external | stable | on | External browser-use variant. |
computer_use | stable | on | Computer-use tool path. |
image_generation | stable | on | Image generation tools. |
realtime_conversation | under development | off | Experimental realtime voice conversation in the TUI. |
prevent_idle_sleep | experimental (platform-gated) | off | Keep the machine awake while a thread runs (/experimental). |
Network and auth storage
| Key | Stage | Default | What it does |
|---|---|---|---|
network_proxy | experimental | off | Extra network proxy restrictions for sandboxed sessions that already have network; enable from /experimental and restart. |
secret_auth_storage | stable | on for Windows | Prefer secret/auth storage backend wiring for credentials. |
enable_request_compression | stable | on | Compress outbound provider requests when supported. |
tool_call_mcp_elicitation | stable | on | MCP elicitation during tool calls. |
Deprecated (still toggles, avoid new dependence)
| Key | Notes |
|---|---|
web_search_request / web_search_cached | Deprecated web-search wiring; prefer current search docs. |
use_legacy_landlock | Deprecated Linux sandbox path; leave off unless you are debugging a migration. |
What not to document as a product feature
veyyon plugin features also surfaces removed or under-development keys that are not product
features: protocol experiments (responses_websockets*, item_ids),
removed sandboxes (experimental_windows_sandbox, use_linux_sandbox_bwrap), dead tool
search shims, and similar. Enabling them will not give you a supported workflow. Prefer
this page, /experimental, and release notes over raw enum archaeology.
See also: Configuration for the features config map and
Slash commands.
Tools, skills, and extension data
Veyyon does real work through tools, and you can extend what it knows and what it can reach. This page covers the built in tools, skills, extension data, and external tools through the Model Context Protocol.
Tools
Tools are the actions Veyyon can take: reading a file, searching the tree, editing code, running a command, fetching a page, and more. Every tool runs through the same approval and sandbox model, so a tool cannot exceed the boundary you set. Editing tools in particular all flow through one verified write path, so a change is checked and recorded the same way no matter which tool made it.
Skills
Skills are reusable capabilities Veyyon can draw on, defined as data rather than baked into the binary. Because a skill is data, you can add a skill by dropping in a file, and you can share a skill with others. Veyyon validates a skill when it loads and tells you clearly if a skill needs a tool that is not available, rather than failing in a way you cannot diagnose.
Plugin bundles
Plugin bundles are the package shape for larger extension sets. Treat plugin installation as available
only when your veyyon build exposes the matching install, list, and remove commands. Until then, use
skills and MCP servers for local extension, because those are the shipped operator paths documented on
this page.
External tools through the Model Context Protocol
Veyyon speaks the Model Context Protocol, so it can use tools served by an external server and can serve its own tools to other clients. This lets you connect Veyyon to the wider ecosystem of context servers without custom integration work. An external server is added as data, and its tools appear to the model exactly where you intend.
The principle behind extension
Everything here follows one principle: capability grows through data and through a clear boundary, never through a hidden hardcoded list and never past the safety boundary you set. You can always see what Veyyon can do, and you can always extend it without editing code.
Where to go next
- Configuration covers approvals and the sandbox that bound every tool.
- What makes Veyyon different explains the one write path that every editing tool shares.
Migration guide
This guide walks through upgrading Veyyon and recovering when an upgrade does not go as planned. Veyyon stores all user data under VEYYON_HOME (by default ~/.veyyon on Unix and the Veyyon application directory on Windows), so most upgrades are safe if you back up that directory first.
Before you upgrade
-
Close all running Veyyon sessions and TUI instances. Writes may still happen while the binary is running, and a backup taken during activity can be inconsistent.
-
Back up
VEYYON_HOME:cp -R ~/.veyyon ~/.veyyon-backup-$(date +%Y%m%d)Keep this backup until you have verified the new version with
veyyon plugin doctorand completed one normal session. -
Read the release notes for the version you are installing. They list required config changes, renamed fields, and any new dependencies.
Config schema updates
Veyyon reads your settings from VEYYON_HOME/config.yml and validates them against the versioned config schema. If a new release requires a new key or renames an existing one, the binary tells you exactly which file and line are affected.
Common schema changes
- New required keys are added when a new feature is on by default. The error message names the missing key and the section it belongs in. Add it to
config.ymlor disable the related feature if you do not need it. - Renamed fields are reported as unknown keys. The old name is usually accepted during a short migration window, but you should rename it to the current spelling.
- Removed fields are ignored unless you run with
--strict-config, in which case unknown keys are treated as errors. Use--strict-configin CI to keep your config clean; leave it off during normal use so upgrades do not immediately break.
Updating your config
-
Open
VEYYON_HOME/config.ymlin an editor. -
Run the new binary once to see any validation errors:
veyyon --version veyyon plugin doctor -
Fix each reported line. If you are unsure what a key does, see the sample configuration that ships with Veyyon or read Configuration.
-
After editing, run
veyyon plugin doctoragain to confirm the file loads cleanly.
You do not need to rewrite the whole file. Most upgrades only add or rename a few keys, and the rest of your settings stay the same.
Session and state data
Veyyon keeps your session data in two places under VEYYON_HOME:
sessions/contains the append-only rollout files, one per session, in JSONL format. These are the source of truth for conversation history, branching, and undo.- A local SQLite state database mirrors key events for fast lookups and diagnostics. It is rebuilt from the rollout files if it is missing or out of sync.
This means you usually do not need a manual database migration. When you start the new binary, it reads the rollout files and updates the state database as needed. If you see a warning about a stale state database, the binary repairs it automatically on startup.
If you need to force a state rebuild
- Close Veyyon.
- Remove the state database file (see File locations for the exact path on your platform).
- Restart Veyyon. The state database is rebuilt from
sessions/andarchived_sessions/.
Never delete sessions/ or archived_sessions/ to fix a state problem. Those are the durable history; the state database is only a cache.
Rolling back a binary
If the new binary does not work for you, you can go back to the previous version without losing data.
- Close all Veyyon processes.
- Restore the previous binary from your system package manager, the release archive, or your own backup.
- Restore your
VEYYON_HOME/config.ymlfrom the backup you made before upgrading, if the new version modified it in ways the old version cannot read. - Leave
sessions/,archived_sessions/, and the state database in place. Rollout files are forward-compatible for recent releases, and the old binary will rebuild the state database if needed. - Start Veyyon and run
veyyon plugin doctorto confirm the environment is healthy.
If you used a new feature that wrote settings the old binary does not recognize, remove or rename those keys before starting the old binary. The error message will point you to the right lines.
Checking health after an upgrade
After every upgrade, confirm the install is healthy:
veyyon --version
veyyon plugin doctor
veyyon plugin doctor checks extension health and warns about missing optional binaries or provider
keys; it exits non-zero when a check reports an error. Start a normal interactive session and run
/debug and /memory diagnose to confirm the runtime and memory backend are working.
Treat every failed check as actionable. Fix the reported line, then re-run. If a check fails after a
rollback, compare your VEYYON_HOME/config.yml against the backup from before the upgrade. See
Troubleshooting for the common failure modes and
Diagnostics and health for the full diagnostics surface.
Spec — not shipped: a top-level
veyyon doctorinstall-health command with--summary/--jsonreports. Useveyyon plugin doctorand the TUI/debugtoday.
Where to go next
- Configuration explains the settings that change between releases.
- File locations lists every path under
VEYYON_HOME. - Troubleshooting walks through common upgrade failures.
- Diagnostics and health covers the diagnostics surface in detail.
Reference
This part is the lookup desk: command flags, environment variables, exit codes, and the files Veyyon creates on your system. Use it when you need the exact name of a flag, the location of a config file, or the meaning of a return code.
- CLI reference — the
veyyoncommand and every subcommand, plus common flags and config overrides. - Slash commands — every
/command available inside an interactive session, grouped by task. - Tools reference — model-facing tools (
sleep, context, plugins, MCP resources, edit/exec) with params and safety. - Environment variables — supported day-to-day variables grouped by purpose (location, auth, catalog, TLS, install, repair, terminal), plus descoped names.
- Exit codes — the return codes Veyyon uses and how child-process exit status passes through.
- File locations — where Veyyon stores config, sessions, logs, and credentials under
~/.veyyon.
Where to go next: if something is failing, see Troubleshooting; for definitions of terms used throughout the book, see the Glossary.
CLI reference
The command is veyyon. Run veyyon with no subcommand to start an interactive session; use a
registered subcommand for everything else. veyyon --help and per-command --help are the
generated source of truth.
Starting a session
$ veyyon
$ veyyon "fix the failing test in auth.rs"
Common launch options:
| Option | Purpose |
|---|---|
[PROMPT] | Optional initial user prompt |
-c, --config key=value | Override config for one run (repeatable) |
--approval-mode <policy> | When to ask before running commands |
--profile <name> | Use an isolated profile agent directory |
--model <id> | Interactive model (provider/model) |
--subagent-model <id> | Model for spawned task subagents |
--compaction-model <id> | Model for context compaction |
--no-alt-screen | Inline mode (preserve scrollback) |
Config precedence: CLI flags → -c overrides → config.yml → defaults. See
Configuration.
Registered subcommands
Unknown first tokens route to launch as a prompt:
| Command | Aliases | Purpose |
|---|---|---|
launch | (default) | Interactive or prompted session |
acp | Agent Control Protocol server mode | |
agents | Manage agent definitions | |
auth-broker | Shared auth broker (headless login) | |
auth-gateway | Auth gateway helper | |
bench | Benchmark harness | |
commit | Agentic commit workflow | |
completions | Shell completion scripts | |
config | List/get/set settings | |
dry-balance | Token balance probe | |
gc | Garbage-collect session artifacts | |
grep | Test grep tool (esp. Windows) | |
gallery | TUI gallery / fixtures | |
grievances | Internal grievance reporter | |
install | Install / bootstrap | |
join | Join collab session | |
models | List models and providers | |
plugin | Plugin lifecycle (list, install, …) | |
read | Read-tool CLI probe | |
say | TTS one-shot | |
search | q | Web search probe |
setup | First-run setup wizard | |
shell | Native shell probe | |
ssh | SSH host configuration | |
tiny-models | On-device tiny model utilities | |
token | Token utilities | |
ttsr | Time-traveling stream rules test | |
update | Self-update | |
usage | Provider usage limits | |
worktree | wt | Git worktree helpers |
Hidden worker selectors and --smoke-test are for CI/packaging, not daily use.
Spec — not shipped:
veyyon app-server,exec-server,execpolicy,responses-api-proxy, andresume/fork/archiveas top-level CLI verbs. Use/resume,/fork, and/sessionin the TUI, or the session picker on launch.
Exit codes
See Exit codes.
Slash commands
Slash commands run inside an interactive Veyyon session. Type / in the composer to open the
picker. Commands below are the builtin set; extensions may add more.
Session and navigation
| Command | Purpose |
|---|---|
/new, /fresh | New session (fresh may reset provider stream state) |
/resume | Resume another saved session |
/fork, /branch, /tree | Branching and session tree UI |
/rename <title> | Rename session |
/move <dir> | Move session to another working directory |
/export [path] | Export session JSONL or HTML |
/dump | Dump debug artifacts |
/session info, /session delete | Session metadata or delete |
/exit, /quit, /pause | Leave or pause |
Model, modes, and behavior
| Command | Purpose |
|---|---|
/model [id] | Select the interactive model only (no role cycle; roles live in settings) |
/switch | Same interactive model picker |
/fast on|off|status | Fast mode |
/plan | Toggle plan mode |
/plan-review | Re-open plan review |
/goal … | Goal set/show/pause/resume/drop/budget |
/guided-goal | Guided goal wizard |
/loop | Loop mode controls |
/prewalk | Prewalk edit path |
/advisor … | Secondary reviewer model |
/settings, /setup | Settings UI; /setup / /providers opens provider sign-in |
/statusline | Settings UI, jumped to Status Line (preset/segments/separator) |
/reload-plugins | Reload extensions |
/force | Force-submit queued work |
Tools, context, and jobs
| Command | Purpose |
|---|---|
/compact [focus] | Compact context now (compaction.model + type); optional focus string |
/shake [elide|images] | Shake tool-result bulk |
/handoff | Compaction handoff helper |
/context | Context usage report |
/tools | Tools visible to the model |
/jobs | Background async jobs |
/todo … | Todo list CRUD |
/browser … | Browser tool mode |
/memory … | Memory backend view/stats/clear/enqueue |
/copy | Copy last response |
Auth and usage
| Command | Purpose |
|---|---|
/login [provider|url] | OAuth / API key login |
/logout [provider] | Log out |
/usage show|reset | Provider rate limits |
/changelog [full] | Release notes |
Extensions
| Command | Purpose |
|---|---|
/mcp … | MCP server management |
/plugins … | Plugin browser |
/extensions | Extension dashboard |
/agents | Switch agent threads |
/ssh … | SSH host setup |
/hotkeys | Active keybinding chords |
/collab …, /join, /leave, /share | Collab sessions |
Side agents and misc
| Command | Purpose |
|---|---|
/btw | Ephemeral side question |
/tan, /omfg | Background agent spawns |
/vibe | Vibe-mode subagents |
/retry | Retry failed turn |
/debug | Debug overlays |
/queue | Queue follow-up message |
/drop | Drop queued message |
Extension packages (e.g. swarm) add commands such as /swarm run.
Spec — not shipped:
/pets,/title,/personality,/permissions,/profile,/import,/archive,/delete,/diff,/init,/mention,/vim,/keymap,/apps,/ide,/whoami,/memories. Use/settings,/memory,/hotkeys, and status-line presets instead./statusline(shipped) jumps into the Status Line settings group, not a standalone interactive segment picker — see Cockpit.
Tools reference
Model-facing tools are advertised to the model per turn. Availability depends on settings, sandbox, plan mode, memory backend, and feature flags.
For sandbox and approvals see Sandbox. For MCP tools see MCP.
Per-tool engineering specs live under docs/tools/.
Core loop
- Model emits a tool call (JSON arguments per schema).
- Veyyon validates arguments; handlers run after approval/sandbox checks.
- Text or structured output returns to the conversation.
General schema repair runs before dispatch on all schema-bearing tool calls; tool-specific leniency (e.g. hashline parsing) is layered on top. See Repair overview.
Edit and write (built)
| Tool | Purpose |
|---|---|
edit | Apply changes — default hashline input string (edit.mode) |
write | Create or overwrite a whole file |
apply_patch | V4A envelope when edit.mode: apply_patch |
Hashline flow: read/grep mint [path#TAG] anchors → model copies tags into edit →
@veyyon/hashline applies ops. See Edit engine and
docs/tools/edit.md.
Read and search (built)
| Tool | Purpose |
|---|---|
read | Files, dirs, URLs, archives, SQLite, memory://, skill://, … |
grep | Ripgrep-backed search; hashline headers in hashline display mode |
glob | Path globbing |
search_tool_bm25 | Discover tools by description (when enabled) |
Shell and execution (built)
| Tool | Purpose |
|---|---|
bash | Shell commands with sandbox + approval |
ssh | Remote commands via configured hosts |
eval | JS/Python/Julia/Ruby eval cells (when enabled) |
debug | Debugger integration |
browser | Browser automation |
job | Background job control |
Agent coordination (built)
| Tool | Purpose |
|---|---|
task | Spawn subagents |
irc | Inter-agent messaging |
todo | Structured task lists |
goal | Goal card updates (with goal mode) |
ask | User questions |
yield | Yield turn for follow-ups |
Memory (when backend enabled)
| Tool | Purpose |
|---|---|
memory_recall, memory_retain, memory_reflect, memory_edit | Mnemopi/hindsight surfaces |
learn | Autolearn (when autolearn.enabled) |
Other builtins
Includes web_search, github, lsp, ast_edit, ast_grep, checkpoint, rewind, resolve,
manage_skill, launch, inspect_image, generate_image, tts, report_finding, and MCP tools
(mcp__*). Extension hooks may register more.
Keybindings reference
Quick lookup for the default Veyyon TUI shortcuts. For the full guide on customizing, remapping, and Vim mode, see Keybindings and Vim mode.
Global
| Binding | Action |
|---|---|
ctrl-t | Open transcript scrollback |
ctrl-g | Open external editor for the current draft |
ctrl-o | Copy the last agent response to the clipboard |
ctrl-l | Clear TUI history |
alt-r | Toggle raw scrollback mode |
| (unbound) | Toggle Vim mode |
| (unbound) | Toggle Fast mode |
Chat
| Binding | Action |
|---|---|
esc | Interrupt the running turn |
alt-, or shift-down | Decrease reasoning effort |
alt-. or shift-up | Increase reasoning effort |
alt-up or shift-left | Edit the most recently queued message |
Composer
| Binding | Action |
|---|---|
enter | Submit the current message |
tab | Queue the current message while a task is running |
alt-enter | Queue the current message as a follow-up turn |
? or shift-? | Toggle the composer shortcut helper |
ctrl-r | Open reverse history search or move to the previous match |
ctrl-s | Move to the next match in reverse history search |
Editor
| Binding | Action |
|---|---|
ctrl-j, ctrl-m, enter, shift-enter, alt-enter | Insert a new line |
left or ctrl-b | Move cursor left |
right or ctrl-f | Move cursor right |
up or ctrl-p | Move cursor up |
down or ctrl-n | Move cursor down |
alt-b, alt-left, ctrl-left | Move cursor left by one word |
alt-f, alt-right, ctrl-right | Move cursor right by one word |
home or ctrl-a | Move cursor to the start of the line |
end or ctrl-e | Move cursor to the end of the line |
backspace, shift-backspace, ctrl-h | Delete the character to the left |
delete, shift-delete, ctrl-d | Delete the character to the right |
alt-backspace, ctrl-backspace, ctrl-shift-backspace, ctrl-w, ctrl-alt-h | Delete the word to the left |
alt-delete, ctrl-delete, ctrl-shift-delete, alt-d | Delete the word to the right |
ctrl-u | Delete from the cursor to the start of the line |
ctrl-k | Delete from the cursor to the end of the line |
ctrl-y | Paste the deleted text buffer |
Pager
| Binding | Action |
|---|---|
up or k | Scroll up one row |
down or j | Scroll down one row |
page-up, shift-space, or ctrl-b | Scroll up one page |
page-down, space, or ctrl-f | Scroll down one page |
ctrl-u | Scroll up half a page |
ctrl-d | Scroll down half a page |
home | Jump to the beginning |
end | Jump to the end |
q or ctrl-c | Close the pager |
ctrl-t | Close the transcript view |
List
| Binding | Action |
|---|---|
up, ctrl-p, ctrl-k, k | Move selection up |
down, ctrl-n, ctrl-j, j | Move selection down |
left or ctrl-h | Move selection left |
right or ctrl-l | Move selection right |
page-up or ctrl-b | Move up one page |
page-down or ctrl-f | Move down one page |
home | Jump to the first item |
end | Jump to the last item |
enter | Accept the current selection |
esc | Cancel and close the list |
Approval
| Binding | Action |
|---|---|
ctrl-a or ctrl-shift-a | Open the fullscreen approval view |
o | Open the requesting thread |
y | Approve the current request |
a | Approve similar requests for the rest of the session |
p | Approve similar requests matching the command prefix |
d | Deny the request |
esc or n | Decline the request and prompt for feedback |
c | Cancel the elicitation prompt |
Vim mode
Vim mode adds modal editing to the composer. Type /vim to toggle it, or bind toggle_vim_mode in your config.
| Mode | Binding | Action |
|---|---|---|
| Normal | i | Enter Insert mode |
| Any | esc | Return to Normal mode |
Normal-mode basics:
| Category | Bindings |
|---|---|
| Motions | h, j, k, l, w, b, e, 0, $ |
| Operators | d, y, c |
| Line operations | dd, yy, cc |
| Text objects | w, W, (, ), [, ], {, }, ", ', ` |
For the full list of motions, operators, and text objects, see Keybindings and Vim mode.
Customizing (real path: keybindings.yml)
Custom bindings are shipped — but the config surface is its own file, not a tui.keymap block in
config.yml. Set bindings by action ID in ~/.veyyon/agent/keybindings.yml (YAML map of action ID
→ chord or chord list). A single string, a list of chords, or an empty list (disables the action) are
all valid values:
app.model.cycleForward: Ctrl+P
app.history.search: [] # disables the action
app.clipboard.copyLine: [Ctrl+C, Alt+C]
Action IDs are namespaced (app.model.cycleForward, app.plan.toggle, tui.select.pageUp, …), not the
global/clear_terminal-style context keys shown in older drafts of this page. Older flat legacy names
and keybindings.json files migrate automatically to the namespaced .yml form on load. Run
/hotkeys in a session to see active chords.
Spec — not shipped: a
/keymapinteractive remapper and nestedtui.keymap.<context>tables insideconfig.yml. The customization surface that actually ships is the flatkeybindings.ymlfile above.
For the full action-ID list and Vim mode details, see Keybindings and Vim mode
and the engineering reference docs/keybindings.md.
Environment variables
This page covers the day-to-day surface only: identity/profile selection, provider auth, and the
handful of VEYYON_* variables that are actually read by the runtime today. Veyyon also reads a large
number of PI_* debug/behavior-toggle variables (timing, startup tracing, TUI flags, eval-runtime
toggles, and more) that are not day-to-day configuration. For the complete, code-grounded reference —
including every provider credential var, precedence chains, and internal toggles — see
docs/environment-variables.md.
Location and identity
There is no VEYYON_HOME. The config directory name (not a full path) is overridable, and the
active profile is selected by its own variable:
| Variable | Purpose |
|---|---|
VEYYON_CONFIG_DIR | Overrides the config directory name under $HOME (default .veyyon). Does not accept a full path. Legacy aliases OMP_CONFIG_DIR and PI_CONFIG_DIR are still accepted (first set wins: Veyyon → OMP → PI). |
PI_CODING_AGENT_DIR | Full override for the agent directory (default ~/<config-dir-name>/agent). |
VEYYON_PROFILE | Selects the active named profile (~/.veyyon/profiles/<name>/agent). Legacy aliases OMP_PROFILE and PI_PROFILE are still accepted. |
VEYYON_PACKAGE_DIR | Override package directory for bundled assets (Nix/Guix). Legacy OMP_PACKAGE_DIR / PI_PACKAGE_DIR. |
VEYYON_NO_PTY | Set to 1 to disable PTY-based interactive bash. Legacy OMP_NO_PTY / PI_NO_PTY. |
VEYYON_NO_TITLE | Set to disable auto terminal-title updates. Legacy OMP_NO_TITLE / PI_NO_TITLE. |
On Linux, veyyon config init-xdg migrates state under $XDG_DATA_HOME/$XDG_STATE_HOME/$XDG_CACHE_HOME
when those are set; unmigrated installs stay under ~/.veyyon. See
packages/utils/src/dirs.ts.
There is no separate SQLite-state-directory override; state lives under the resolved agent directory above.
Authentication
Provider BYOK uses each provider’s native key variable — there is no Veyyon-branded API key or access
token (a VEYYON_API_KEY/VEYYON_ACCESS_TOKEN legacy alias does not exist in the current runtime).
When a provider’s key variable is set, it is used without an interactive sign-in. For providers with
OAuth (Anthropic, xAI, Qwen, Cursor, and others), the OAuth token variable takes precedence over the
plain API key — see the provider tables below and
docs/environment-variables.md.
OAuth sign-in itself is interactive: run /login inside the TUI (or --provider <id> at startup) to
open the OAuth selector. There is no veyyon login --with-api-key/--with-access-token CLI subcommand;
piping a key into a login command is not part of the shipped CLI surface.
Provider keys
Each model provider reads its own standard key variable (or the name in
[model_providers.<id>].env_key for a custom provider). When set, it is used without an interactive
sign-in and wins over a stored key.
| Provider | Variable |
|---|---|
| OpenAI | OPENAI_API_KEY |
| Anthropic | ANTHROPIC_API_KEY (or ANTHROPIC_OAUTH_TOKEN, which takes precedence) |
| DeepSeek | DEEPSEEK_API_KEY |
| Moonshot | MOONSHOT_API_KEY |
| Z.AI | ZAI_API_KEY |
| OpenRouter | OPENROUTER_API_KEY |
| Google Gemini | GEMINI_API_KEY |
| xAI | XAI_API_KEY (or XAI_OAUTH_TOKEN, which takes precedence for xai-oauth) |
| Groq | GROQ_API_KEY |
| Mistral | MISTRAL_API_KEY |
| Cursor | CURSOR_ACCESS_TOKEN |
A custom provider uses whatever variable its [model_providers.<id>].env_key names. See
Configuration and the full provider table in
docs/environment-variables.md
(30+ providers, cloud auth chains for Bedrock/Vertex/Azure, and web-search provider keys).
Local and self-hosted providers
| Variable | Purpose |
|---|---|
OLLAMA_BASE_URL / OLLAMA_HOST | Ollama discovery base URL (defaults to http://127.0.0.1:11434). |
LM_STUDIO_BASE_URL | LM Studio discovery base URL (defaults to http://127.0.0.1:1234/v1). |
LLAMA_CPP_BASE_URL | llama.cpp discovery base URL (defaults to http://127.0.0.1:8080). |
LITELLM_BASE_URL | LiteLLM proxy base URL fallback (defaults to http://localhost:4000/v1). |
PI_EDIT_VARIANT | Force edit tool variant: hashline, apply_patch, patch, replace. |
There is no VEYYON_OSS_BASE_URL/VEYYON_OSS_PORT; each local backend has its own discovery variable
above.
TLS and certificates
| Variable | Purpose |
|---|---|
NODE_EXTRA_CA_CERTS | Extra CA bundle (path or inline PEM) merged into the trust root for every provider fetch (OpenAI-compatible, Codex, Ollama, Azure Responses, Google, Anthropic). |
CLAUDE_CODE_CLIENT_CERT / CLAUDE_CODE_CLIENT_KEY | mTLS client certificate/key, used in Anthropic Foundry gateway mode (CLAUDE_CODE_USE_FOUNDRY=1). |
There is no VEYYON_CA_CERTIFICATE or SSL_CERT_FILE support; NODE_EXTRA_CA_CERTS is the real
override, honored across providers because Bun’s fetch does not read it natively (Veyyon merges it
into RequestInit.tls.ca itself).
Install and updates
| Variable | Purpose |
|---|---|
VEYYON_INSTALL_DIR | Overrides the install script’s target directory (default ~/.local/bin on Unix, %LOCALAPPDATA%\veyyon on Windows). Legacy alias PI_INSTALL_DIR is still accepted. |
There is no VEYYON_NON_INTERACTIVE or VEYYON_INSTALL_URL; the install scripts (scripts/install.sh,
scripts/install.ps1) do not read those names today.
MCP
Any MCP server names its own bearer-token secret via [mcp_servers.<name>].bearer_token_env_var in
config.yml — this points at any env var you choose (for example plain GITHUB_PERSONAL_ACCESS_TOKEN),
not a fixed VEYYON_* name. There is no VEYYON_GITHUB_PERSONAL_ACCESS_TOKEN or VEYYON_CONNECTORS_TOKEN
convention in the current runtime.
| Variable | Purpose |
|---|---|
VEYYON_MCP_TIMEOUT_MS | Overrides the MCP client request timeout (ms) for every server; 0 disables client-side timeouts. Default 30000. Legacy alias OMP_MCP_TIMEOUT_MS. |
See Configuration for bearer_token_env_var examples.
Remote auth broker (optional)
Real, shipped VEYYON_* variables that switch credential resolution from local SQLite to a remote
broker host:
| Variable | Purpose |
|---|---|
VEYYON_AUTH_BROKER_URL | Base URL of the remote auth-broker; selects broker mode. Legacy alias OMP_AUTH_BROKER_URL. |
VEYYON_AUTH_BROKER_TOKEN | Bearer token sent to the broker. Legacy alias OMP_AUTH_BROKER_TOKEN. |
VEYYON_AUTH_BROKER_SNAPSHOT_TTL_MS | Freshness window (ms) for the encrypted local snapshot cache; default 3600000. |
VEYYON_AUTH_BROKER_SNAPSHOT_CACHE | Path to the encrypted local snapshot cache. |
Most installs never set these. Details: docs/auth-broker-gateway.md.
Repair
| Variable | Purpose |
|---|---|
VEYYON_REPAIR_DISABLE | Truthy disables the shipped tool-call schema repair (see Repair overview) at the tool-dispatch seam. |
There is no VEYYON_REPAIR_LOG; per-(model,tool,shape) repair telemetry is Spec — not shipped.
Terminal behavior
| Variable | Purpose |
|---|---|
NO_COLOR | When set (to any value), Veyyon renders without color; hierarchy comes through emphasis, spacing, and glyphs instead. |
TERM / COLORTERM | Read to detect terminal capabilities (truecolor, ANSI-256, ANSI-16) and pick the matching palette mapping. |
PI_HARDWARE_CURSOR | Truthy enables hardware cursor mode. |
PI_TUI_WRITE_LOG | When set, logs TUI writes to the given file (debugging). |
There is no VEYYON_TUI_DISABLE_KEYBOARD_ENHANCEMENT, VEYYON_TUI_RECORD_SESSION, or
VEYYON_TUI_SESSION_LOG_PATH; see
docs/environment-variables.md
for the real PI_*-prefixed TUI flags.
Removed / does not exist
| Name | Status |
|---|---|
VEYYON_HOME | Never existed. Config location is VEYYON_CONFIG_DIR (dirname override, legacy OMP_CONFIG_DIR/PI_CONFIG_DIR) + optional XDG migration, not a single home-path variable. |
VEYYON_SQLITE_HOME | Never existed. No separate SQLite-state override; state lives under the resolved agent directory. |
VEYYON_API_KEY / VEYYON_ACCESS_TOKEN | Never existed as a Veyyon-branded credential; use each provider’s native key variable. |
VEYYON_AUTHAPI_BASE_URL | Never existed. The ChatGPT AuthAPI host used by personal-access-token whoami is not overridable via env today. |
VEYYON_APP_SERVER_LOGIN_ISSUER | Belonged to the removed app-server daemon; no equivalent exists in this runtime. |
VEYYON_MANAGED_BY_NPM / VEYYON_MANAGED_BY_BUN / VEYYON_MANAGED_PACKAGE_ROOT | Never existed. |
VEYYON_SANDBOX / VEYYON_SANDBOX_NETWORK_DISABLED / VEYYON_THREAD_ID | Never existed under these names. |
Config values can also be overridden per run with -c key=value, which is usually clearer than an
environment variable; see the CLI reference.
Exit codes
Veyyon follows the standard shell conventions so it composes cleanly in scripts and CI.
| Code | Meaning |
|---|---|
0 | Success. |
1 | A Veyyon error (bad config, auth failure, no such session, an unrecoverable runtime error) — or the fallback when a child process ended without a reportable status. |
2 | Usage error from argument parsing (an unknown flag or missing value); the standard clap convention. |
N | When Veyyon runs a child process (for example a sandboxed shell command), the child’s own exit code passes through unchanged. |
128 + signal | On Unix, a child killed by a signal is reported as 128 + signal (the POSIX shell convention): SIGKILL (9) → 137, SIGTERM (15) → 143. |
Two guarantees hold everywhere:
- A failure is never reported as
0. An unknown or missing child status falls back to1, never success. - A signal death is surfaced as a distinct non-zero code, never swallowed.
For the machine-readable event stream (including per-turn and per-tool outcomes), use the
Agent Control Protocol mode (veyyon acp); see the CLI reference.
File locations
Everything Veyyon stores lives under the config home — ~/.veyyon on Unix by default, or the Veyyon
application directory on Windows. Override the directory name with PI_CONFIG_DIR; on Linux the XDG
layout is available after veyyon config migrate.
Layout
| Path | Contents |
|---|---|
config.yml | Your configuration (config.yaml also accepted). See Configuration. |
auth.json | The managed OpenAI/ChatGPT credential (in file credential-store mode). |
local.age | The encrypted managed-secrets store (general secrets, including BYOK provider keys). |
veyyon_auth.age | The encrypted auth-token store (in secrets credential-store mode). |
sessions/ | Saved session rollouts (JSONL), one per thread. |
archived_sessions/ | Sessions moved aside by /archive. |
logs/ | Log files, including the login log. |
history.jsonl | Composer input history. |
plugins/ | Installed plugins. |
skills/ | Installed and local skills. |
hooks/ | Lifecycle hook configuration. |
agents/ | Agent definitions. |
attachments/, avatars/ | Session attachments and account avatars. |
Credential storage modes
Where credentials land depends on cli_auth_credentials_store_mode in config.yml:
file—auth.json, mode0600, written atomically.keyring— the OS keyring (with the encryptedsecretsbackend as one keyring option).auto— keyring when available, falling back to the file.ephemeral— in-memory only; nothing is written to disk.
BYOK provider keys always go to the encrypted managed-secrets store (local.age), never to plaintext
config.yml; see Signing in.
Project-local files
Alongside your project (not under VEYYON_HOME):
| Path | Purpose |
|---|---|
AGENTS.md | Project instructions Veyyon auto-loads. See AGENTS.md. |
.veyyon/ | Optional per-project overrides and data. |
The thesis: the harness is the lever
Veyyon exists because the same model weights score wildly differently depending on the agent harness around them. The model is capable; scaffolding restricts or unlocks it.
The evidence
Benchmarks and production traces show tool shape, edit shape, context handling, and control flow can swing outcomes dramatically. Veyyon’s oh-my-pi lineage adds hashline editing, role-based models, compaction, and explicit modes (plan, goal, vibe) as harness levers, not only prompt text.
The two dominant levers
- Edit format / first-attempt edits. When the edit format is hard to emit, models burn turns on retries. Hashline and model-tuned edit prompts are the biggest swing in Veyyon.
- Control flow. Stop when verification passes; do not loop on repeated failures; budget context and subagent fan-out. Goal and plan modes encode some of this in the engine.
What this implies for the design
- Hashline and native edit tools as the primary write path in
packages/coding-agent(see engine docs underdocs/). - Per-model and per-role configuration via
modelRoles, thinking levels, and catalog selectors. - Engine-enforced modes (plan file + approval, goal continuation, tool approval tiers).
- Evidence discipline. Claims in this book must match tests and engine docs, or be labeled Spec — not shipped.
Harness improvements are runtime work
Dogfood traces, benchmark failures, and user corrections drive small runtime improvements: better tool hints, compaction, cache-stable prefixes, clearer progress, and bounded outputs. Name the lever each change moves.
Where to go next
- What makes Veyyon different
- Repair (note: the full repair cascade is Spec — not shipped)
- The hashline edit engine
Architecture at a glance
Veyyon ships as the veyyon CLI (Bun + TypeScript, Rust helpers). This chapter maps subsystems;
each has a handbook page and matching engineering notes under docs/.
The request path
┌──────────────────────────────────────────────────────────────┐
prompt ──► │ veyyon (packages/coding-agent) │
│ │ │
│ ▼ │
│ AgentSession turn loop │
│ │ model stream → tools (read, bash, edit, …) │
│ ▼ │
│ hashline / handlers ──► filesystem + sandbox │
└──────────────────────────────────────────────────────────────┘
Subsystems that matter
| Subsystem | Responsibility | Chapter |
|---|---|---|
Edit engine (@veyyon/hashline) | Default hashline edit path | Edit engine |
| Sessions | Session trees, compaction | Compaction & memory |
| MCP | MCP client integration | MCP |
| Config | Settings and profiles | Config |
| Memory | mnemopi / local memory | Memory |
| Goals | Goal cards and budgets | Goal state |
Design rules
- Prefer one obvious path. Hashline is the default edit surface; alternate
edit.modevalues exist for compatibility. - Fail loud. Invalid config, stale hashline tags, and sandbox violations surface actionable errors. No silent fallback to weaker behavior.
Spec — not shipped: the full ordered repair rule cascade (alias maps, strict unknown-key rejection, per-
(model,tool,shape)telemetry), an app-server or exec-server daemon, and a Tier-Bbackends.tomlcatalog as a separate subsystem. Provider and model configuration is documented in Providers against the shipped provider registry. Basic schema repair on tool calls is shipped today as a TypeScript module at the tool-dispatch seam (packages/coding-agent/src/repair/schema-repair.ts) — see Repair.
Architecture overview
Veyyon is a Bun/TypeScript coding agent (fork of oh-my-pi) with Rust hot paths (@veyyon/hashline,
native grep, PTY). The shipped CLI binary is veyyon. There is no separate app-server daemon in
the product surface.
The request path
prompt ──► veyyon
│
▼
AgentSession turn loop
│
▼
model stream + tool calls
│
▼
tool handlers (read, bash, edit, …) ──► results back to model
Interactive mode runs in the TUI. Non-interactive work uses veyyon with a prompt or subcommands such
as commit, grep, and models.
Subsystem map
| Area | Responsibility | Handbook |
|---|---|---|
| Sessions | JSONL trees, resume, fork, compact | Sessions |
| Edit | Hashline patches (default) | Edit engine |
| Sandbox | Approval + filesystem/network bounds | Sandbox |
| Config | Layered config.yml, profiles | Config |
| MCP | External tool servers | MCP |
| Providers | Model registry + auth | Providers |
| Memory | mnemopi / local backends | Memory |
Spec — not shipped: a standalone exec-server process, guardian auto-review as a subsystem, and a separate backend-catalog subsystem. Behavior that ships is covered under the rows above.
Sandbox
The sandbox bounds what tool and shell commands may read, write, and reach on the host. When Veyyon is unsure whether an action is safe, it asks the user instead of assuming.
Responsibility
- Map approval mode and sandbox policy to concrete restrictions for
bash,edit,write, and related tools. - Enforce workspace roots and network policy per session settings.
- Surface permission prompts in the TUI before mutating or risky commands run.
Public boundary
Settings keys such as approvalMode, sandbox-related enums in config.yml, and CLI flags on launch
(--approval-mode and sandbox overrides) resolve into concrete restrictions applied to the bash,
edit, and write tools, with plan-mode guards on top.
There is no standalone exec-server process in the shipped product; commands run in-process after policy resolution.
Key concepts
| Concept | Meaning |
|---|---|
| Approval mode | When to prompt before tool execution (on-request, never, …) |
| Sandbox policy | Filesystem/network posture for command tools |
| Plan mode | Restricts mutating tools until plan is approved (/plan) |
User-facing guide: Sandbox and approvals.
Session and turn
Sessions are JSONL conversation trees; each turn is one user prompt through model streaming, tool calls, and the final assistant message.
Responsibility
- Persist append-only session entries with
id/parentIdlinkage - Track the active leaf for branching (
/tree,/branch,/fork) - Drive compaction when context limits approach (
/compact, auto-compact settings) - Coordinate tool execution, approvals, and subagent spawns per turn
Public boundary
- The
AgentSessionowns the turn loop. - On-disk layout:
~/.veyyon/agent/sessions/<dir-encoded>/<timestamp>_<id>.jsonl - Blob store:
~/.veyyon/agent/blobs/<sha256>
Sessions run in-process; there is no separate session daemon.
User guide: Sessions.
Config
Configuration controls models, sandbox, memory, MCP, extensions, and TUI behavior. Veyyon loads layered YAML/JSON from the project and user agent directories.
Responsibility
- Resolve config roots (
.veyyon, plus Claude/Codex/Gemini compatibility paths) - Merge project + user settings; apply profiles (
veyyon --profile <name>) - Validate against
settings-schema.ts; support CLI-c key=valueoverrides - Feed resolved settings to sessions, tools, and discovery (skills, hooks, MCP, extensions)
Public boundary
- Primary user file:
~/.veyyon/agent/config.yml(or profile path under~/.veyyon/profiles/) - Project overrides:
.veyyon/config.yml - CLI:
veyyon config list|get|set,/settings,/reload(re-read without restart)
Config loading is part of the harness.
Everyday guide: Configuration.
Engineering detail: docs/config-usage.md,
docs/settings.md.
MCP
Model Context Protocol (MCP) connects Veyyon to external tools and data. Veyyon is both an MCP
client (consumes configured servers) and can run as an MCP server (veyyon acp / SDK paths).
Responsibility
- Discover MCP servers from project and user config files
- Connect over stdio or HTTP (streamable HTTP / SSE-style transports)
- Register tools as namespaced names (
mcp__<server>__<tool>) - Handle OAuth for remote servers and persist credentials per profile
Implementation (TypeScript)
| Module | Role |
|---|---|
packages/coding-agent/src/mcp/ | Config load, manager, OAuth, tool wiring |
packages/coding-agent/src/discovery/mcp-json.ts | Standalone mcp.json discovery |
packages/coding-agent/src/modes/controllers/mcp-command-controller.ts | /mcp TUI commands |
Primary config files:
- Project:
.veyyon/mcp.json - User:
~/.veyyon/agent/mcp.json(profile-scoped when using--profile)
Veyyon also ingests MCP definitions from other tools (.cursor/mcp.json, .vscode/mcp.json,
Claude/Codex/Gemini configs) when discovery is enabled.
Engineering detail:
docs/mcp-config.md,
docs/mcp-runtime-lifecycle.md,
docs/mcp-protocol-transports.md.
Providers
The providers subsystem connects Veyyon to model APIs and normalizes their auth, request, and response formats.
Responsibility
- Maintain the catalog of supported model providers and their capabilities.
- Resolve a model slug to a provider and its
ModelInfo. - Authenticate requests with API keys, access tokens, or OAuth credentials.
- Translate between the provider-specific wire format and the engine’s protocol types.
Implementation
The provider stack lives in the @veyyon/ai package.
| Component | Role |
|---|---|
| Provider adapters | Per-provider connection and wire-format adapters |
| API client registry | OpenAI-compatible API client registry |
| Provider details | Provider metadata, auth mode, and endpoints |
| Model catalog | Model catalog and per-model capabilities |
| Model registry | Slug resolution to provider + model info |
Key concepts
- Provider metadata — a provider’s auth mode and endpoint configuration.
- Model info — per-model capabilities such as context window and vision support.
- Auth material — resolved from API keys, access tokens, or OAuth credentials.
See Models and providers and Provider stack and bring-your-own-key for how to add your own keys and choose models.
Testing and verification
Veyyon’s docs make product claims only when the harness has a way to prove them. This chapter explains the shape of that proof so the detailed pages are easier to read.
What a proof looks like
A proving test asserts behavior, not just shape. For a file edit, that means the exact file bytes, the diff, the error text, and the approval path when relevant.
Where the main proof lives
- The hashline edit path uses round-trip tests so generated patches apply to the intended content.
- Tool-output bounds are tested with real limits so truncation is visible and actionable.
- Architecture gates protect layering, re-exports, weak tests, uncovered tools, unfinished markers, and vendored trees.
Spec — not shipped: the full schema-based repair cascade is a planned proof surface. Its target shape is exact-value unit tests plus large property tests that validate repaired calls against the schema, including whether ambiguous input is rejected. That work is not shipped yet.
How to read status labels
The status label at the top of a deep-dive chapter names the implemented surface and the proof. When a chapter says work is in progress, it names the part that works and the part still gated by measurement or operator surface.
Where to go next
- The repair cascade shows the planned repair rules and their proof style (Spec).
- The hashline edit engine shows edit invariants.
- Fleet verification gates are defined in the Santh
STANDARD.mddocument (not duplicated here).
Why repair exists
Status: Mostly shipped. Schema-based tool-call repair runs in the agent loop before validation, including parse leniency, ambiguity refusal, alias/typo key rename, and strict unknown-key rejection. Per-model posture, telemetry, and per-shape backends remain Spec — not shipped — see The repair cascade.
OSS backends malform tool-call arguments in model-specific ways: stringified JSON, truncated JSON, trailing commas, ambiguous field names. Each malformation, left alone, costs a whole turn.
Repair makes recoverable calls land on the first attempt. It coerces malformed-but-clear JSON into an object and re-validates; when a call genuinely cannot be made valid, it fails loud back to the model with coaching, never dispatched as garbage.
The lever
Edit format / first-attempt success. Repair is the safety net under every schema-bearing tool call, most importantly the edit path, where a malformed call is a failed edit.
Shipped behavior
- Seam: argument repair runs in the agent loop, before argument validation.
- Fix-if-clear: trailing commas, parse sentinels (
__parseError/__rawJson), stringified JSON objects. - Refuse-if-ambiguous: missing required strings with multiple plausible sources → unrepairable (no invent).
- Alias/typo rename: unknown keys that clearly alias a declared property are renamed; refuses on genuine ambiguity instead of guessing.
- Strict unknown-key rejection:
additionalProperties: falseschemas refuse leftover unrecognized keys after alias resolution. - Bounded: inputs over 1 MiB refuse repair.
- Disable:
VEYYON_REPAIR_DISABLE=1or per-modelharness.profileswithrepair: false.
Not shipped yet
- Per-tool shape tables beyond the generic alias/strict rules
- Per-
(model,tool,shape)telemetry store - Per-model posture knobs
The shipped rule cascade and its conformance suite live in the TypeScript repair module
(packages/coding-agent/src/repair/schema-repair.ts, tested by
packages/coding-agent/test/repair/schema-repair.test.ts) at the tool-dispatch seam — this stays a
TS module, not a separate Rust crate.
See The repair cascade, Per-model posture, and Soundness and telemetry for the target design. For the shipped edit engine, see The hashline edit engine.
The repair cascade
Status: Mostly shipped — parse leniency, alias/typo rename, and strict unknown-key rejection all run at the tool-dispatch seam with a conformance suite. Per-model posture and telemetry remain Spec — not shipped.
Shipped today
Before argument validation, the agent loop runs argument repair
(packages/coding-agent/src/repair/schema-repair.ts), in this order:
- Parse leniency — trailing commas / relaxed JSON; stringified argument blobs.
- Ambiguity guard — refuse when required string fields have multiple plausible donors.
- Alias/typo key rename — unknown keys that match a common alias (
filepath→path,contents→content) or a casing/separator typo of a declared property are renamed to the declared name; refuses rather than guesses when a rename would be ambiguous (two unknown keys alias to the same property, a single unknown key matches more than one declared property, or the alias target already has a value). - Strict unknown-key rejection — when the tool’s schema declares
additionalProperties: false, any key left over after alias resolution is refused rather than silently dropped or passed through. - Outcome —
clean,repaired(canonical args + hints), orunrepairable(error tool result, no dispatch).
Covered by the conformance suite in packages/coding-agent/test/repair/schema-repair.test.ts
(alias renames, ambiguity refusals, strict-mode refusals, and a regression guard that strict
rejection never fires on ArkType/Zod-authored tools whose wire schema synthesizes
additionalProperties: false for closed-object emission, not as an authorial strictness opt-in).
Spec — not shipped
- Per-tool shape tables — richer per-tool repair rules beyond the generic alias/strict rules above.
- Per-model posture — strictness knobs aligned with Per-model harness profiles.
- Telemetry — per-
(model,tool,shape)counters (Soundness and telemetry).
The shipped TypeScript module at the single tool-dispatch seam
(packages/coding-agent/src/repair/schema-repair.ts) is a TS module in packages/coding-agent, not
a standalone Rust crate. Veyyon extends that capability incrementally at the same seam.
See Why repair exists and Repair on edits.
Per-model repair posture
Status: Partial — repair shipped; per-model knobs are an early cut via harness profiles.
Shipped
- Repair threads the active model through the repair hook, so per-model posture can vary.
- Harness profiles:
harness.profilesinconfig.ymlorharness-profiles.ymlin the agent dir. Keys matchprovider/model-idorprovider/*wildcards.
harness:
profiles:
"anthropic/claude-sonnet-4-20250514":
repair: true
tools: ["read", "edit", "grep", "bash"]
promptSectionOrder: ["activeRepo", "system", "project"]
"google/*":
repair: false
repair: falseskips schema repair for that model.tools: [...]filters the initial tool allowlist (MVP hint surface).promptSectionOrder: [...]reorders the top-level system-prompt blocks for that model. The three addressable blocks aresystem(the renderedsystem-prompt.md/custom prompt),project(the environment/workspace footer), andactiveRepo(nested-repo notice, when applicable). Named blocks move to the front in the order given; unlisted blocks keep their default relative order after the named ones. This reorders whole blocks only — it does not reorder content withinsystem-prompt.md(e.g. moving “Delegation” before “Tool Policy”), because that template renders as one piece, not a list of named sections.
Disable all repair: VEYYON_REPAIR_DISABLE=1.
Spec — not shipped
- Per-
(model,tool,shape)telemetry counters - Strictness tables (refuse earlier vs coerce more) beyond the ambiguity guard
- Full
backends.tomlthree-backend tuning - Reordering within
system-prompt.md(Skills vs. Tool Policy vs. Delegation, etc.) — only the three coarse top-level blocks above are addressable today; the template itself is monolithic.
See Why repair exists and Models.
Soundness and telemetry
Status: Partial. Veyyon emits usage and session statistics, and the repair seam is shipped; the bounded repair telemetry and proptest soundness guarantees are Spec — not shipped.
Shipped observability
/statsandveyyon stats— usage dashboards when enabled- Session token accounting on the status line (
token_*,context_pct,cost) - Structured logging via the coding-agent logger
Target
When the bounded repair telemetry ships:
- Every repair attempt records
(model, tool, outcome)with fixed cardinality - Optional file sink for repair shape fingerprints (not metric labels)
- Property tests over parse/repair generators (no panics; repaired JSON strict-validates)
See Observability for what exists today.
The hashline edit engine
Status: Built. Default edit mode is hashline (
edit.mode: hashlineinconfig.yml), implemented in the@veyyon/hashlinepackage.
Veyyon applies file changes through the edit tool (hashline patch language by default). The
model copies [PATH#TAG] anchors from read / grep / write output, then emits SWAP, DEL,
and INS operations against numbered lines. Snapshot tags detect stale anchors and drive recovery.
Alternate modes (apply_patch, patch, replace) exist for compatibility; hashline is the
default and the path Veyyon optimizes for.
How a hashline edit works
readorgreprecords a whole-file snapshot and prints[relative/path#TAG]plusLINE:contentrows (TAGis a four-hex snapshot id).- The model sends
editwith aninputstring: one or more[PATH#TAG]sections and hashline ops (SWAP N.=M:,DEL N.=M,INS.POST N:, block opsSWAP.BLK/DEL.BLK/INS.BLK.POST, plusINS.HEAD/INS.TAIL). @veyyon/hashlineparses, verifies the tag against the snapshot store, applies ops, and returns a fresh[path#TAG]header plus a compact diff preview.writecan create or overwrite whole files; in hashline display mode it also mints snapshot headers for the next edit.
edit.mode and PI_EDIT_VARIANT select among hashline, apply_patch, patch, and replace.
Invariants
| Property | Behavior |
|---|---|
| Stale anchor | Mismatch errors name the tag; snapshot recovery can suggest the current file hash |
| Line numbers | 1-indexed; body rows use +TEXT prefix |
| Order | Non-overlapping hunks; overlapping regions fail loudly |
| Encoding | Applies to normalized content; BOM and dominant line ending preserved on write |
Further reading
- User guide: Editing and repair
- Tool contract:
docs/tools/edit.md - Read/grep anchors:
docs/tools/read.md,docs/tools/grep.md - Settings:
edit.modeindocs/settings.md
Spec — not shipped: a
veyyon-editRust crate, a V4A-only write path, andmake_update_patchenvelope routing. General schema-based tool-call repair is shipped — see Repair overview.
Repair on edits
Status: Partial. General schema repair runs on all tools including
editbefore validation. Hashline-specific lenient parsing inside@veyyon/hashlineremains separate.
When a model emits a malformed edit or apply_patch call:
- Schema repair attempts JSON recovery and ambiguity refusal at the agent-loop seam.
- If repair succeeds, arguments proceed to hashline / apply_patch validation and dispatch.
- If repair cannot disambiguate, the loop returns a loud error with coaching hints (no dispatch).
- Hashline still tolerates envelope stripping and bare-body piping inside
@veyyon/hashline.
See Repair overview and The hashline edit engine.
Edit-path completeness: BOM/CRLF, multi-edit, hashline
This chapter covers the correctness properties an edit path must hold no matter which edit format the
model uses, and where Veyyon stands on each. The shipped edit engine is
hashline (@veyyon/hashline, TypeScript); apply_patch, patch, and replace remain
as compatibility modes.
BOM and line-ending preservation — Built
An edit must not silently rewrite a file’s encoding. Veyyon strips a leading UTF-8 BOM before matching and restores it afterward, and it detects the file’s dominant line ending (CRLF or LF) and restores it on write. Matching only ever runs against a normalized-to-LF copy of the body. Without this, a single edit rewrites a CRLF file to LF or eats a BOM, which surfaces as a spurious whole-file diff and breaks Windows checkouts. Lever: robustness.
Multi-edit in one call — Built
The edit tool applies several disjoint changes to a file in one call rather than one round-trip per
change. Every anchor is matched against the original file, not incrementally, and replacements are
applied so that a growing earlier edit cannot invalidate a later match. Ambiguous anchors, overlapping
regions, and no-op edits fail loudly with a message the model can act on, never a silently-wrong edit.
The tool description coaches the model to keep anchors small but unique and to merge nearby changes,
which measurably reduces wasted attempts. Lever: edit format.
Hashline — the token-lean form — Built
The token cost of an edit is dominated by echoing the surrounding text. Hashline removes that cost: the
model references spans by the [PATH#TAG] snapshot anchors that read, grep, and write already
emit, then sends only the operations (SWAP, DEL, INS) and the new text. Stale anchors are caught
by the snapshot tag and drive recovery instead of a wrong edit. Hashline is the default edit mode
(edit.mode: hashline in config.yml). See The hashline edit engine. Lever: edit
format / output tokens.
Concurrent-edit serialization
Non-parallel tools take an exclusive lock, so file mutations from one turn are globally serialized. A per-path mutation queue that lets independent files edit in parallel while still serializing same-file writes matters once subagents edit concurrently. Lever: robustness.
Spec — not shipped: A byte-exact write path that preserves a missing trailing newline on the final line. The current write model normalizes by appending one; the case is documented and covered by a test.
The provider stack and bring-your-own-key
Status: Built. The harness owns the model registry and provider auth.
A provider is the API namespace (anthropic, openai, google, custom gateways, local
ollama, …). A model is provider/model-id. Veyyon assembles the selectable catalog from:
- Bundled pi-catalog models
~/.veyyon/agent/models.ymlcustom providers and models- Runtime discovery (Ollama, LM Studio, discovery-enabled gateways)
- Extension-registered providers
A model is available when its provider is not disabled and credentials resolve (or the provider is keyless/local).
Credentials
Resolution order (first match wins):
- CLI
--api-key(ephemeral) models.ymlapiKeyon a custom provider- Stored API key / OAuth in the agent auth store (
~/.veyyon/agent/agent.db) - Provider environment variables (see
docs/providers.md) - Custom fallback resolvers in
models.yml
Use /login, /logout, or veyyon OAuth flows in setup. Provider-scoped logins do not cross
providers.
Custom providers
Add OpenAI- or Anthropic-compatible endpoints as data:
# ~/.veyyon/agent/models.yml
providers:
my-gateway:
baseUrl: https://api.example.com/v1
api: openai-completions
apiKey: MY_GATEWAY_API_KEY
models:
- id: claude-sonnet
name: Claude Sonnet via Gateway
contextWindow: 200000
maxTokens: 8192
Validate with veyyon models list and /model.
Local engines
ollama, llama.cpp, and lm-studio are treated as keyless when the engine responds. Each has its
own discovery variable, not a shared VEYYON_OSS_* pair: OLLAMA_BASE_URL (or OLLAMA_HOST),
LLAMA_CPP_BASE_URL, LM_STUDIO_BASE_URL — see Environment variables.
User guides: Models, Configuring providers.
Spec — not shipped: a
backends.tomlTier-B catalog as a separate subsystem. Veyyon usesmodels.ymlplus the bundled catalog instead.
Execution-order prompts
Status: Built. The harness assembles system and developer prompts and adapts them per provider.
The harness encodes control-flow discipline in base instructions: explore → plan → edit → verify →
STOP. Plan mode (/plan) and goal mode (/goal) add gating on top of the default prompt stack.
Delivery
- A default system prompt plus per-tool prompts
- Per-provider streaming and tool wire format
- Skills and rules inject additional context via discovery
Edit tool prompts switch with edit.mode (the hashline prompt when hashline is active).
Spec — not shipped: a
backends.toml-driven catalog with per-backend prompt tuning and anapply_patch-forms prompt as the default edit surface. Veyyon uses hashline by default.
Bounded reads & instant search
Status: Built. The
read,glob,grep, andwritetools ship as TypeScript modules inpackages/coding-agent/src/tools/{read,glob,grep,write}.ts. There is noexperimental_toolsorbackends.tomlgating — every tool below is always on. This chapter describes their real parameter shapes and behavioral contracts.
These tools fight token blowup and latency — supporting concerns that compound into control-flow failures on long trajectories, the long-context runs where a model stops making progress because its window is full of raw dumps.
The read tool (tools/read.ts)
A model that cats a 20k-line file blows its whole context on one call. The read tool takes a single
path string (no separate offset/limit arguments) and bounds every read to a budget:
- One parameter, inline selectors.
read {path}, wherepathcan carry a line-range selector appended after a colon:src/foo.ts:50-200(inclusive range),src/foo.ts:50/:50-(from line 50 on),src/foo.ts:50+150(150 lines from line 50), orsrc/foo.ts:5-16,960-973(multiple ranges in one call).:rawreads verbatim with no anchors or line prefixes. - Dual budget, whichever is hit first: a line cap (
DEFAULT_MAX_LINES = 3000) and a byte cap (DEFAULT_MAX_BYTES = 50 KB), defined insession/streaming-output.ts. A file that is short in lines but huge in bytes (minified JS, a data blob) is bounded by bytes; a file with many short lines is bounded by lines. - Structural summaries for parseable code. A read with no selector on a parseable source file
returns declarations with bodies elided (
…), and the footer names the recovery selector so the model re-issues only the ranges it actually needs instead of re-reading the whole file. - Truncation is never silent. A summary footer or a
[Showing lines …]-style notice always names the exact continuation selector. - Beyond plain text files: the same tool also reads directories (depth-limited listing), archives
(
.tar,.tar.gz,.zip, viaarchive.zip:path/inside), SQLite databases (file.db:table, with pagination andwhere/orderfilters), PDF/Word/PowerPoint/Excel/EPUB (extracted text), Jupyter notebooks (editable cell text), images, URLs (reader-mode by default), and internal URI schemes (memory://,skill://,artifact://,mcp://,ssh://, and others).
Text reading is intentionally separate from image inspection: image files go through view_image or
a vision prepass rather than being bundled into ordinary text reads.
The glob tool (tools/glob.ts)
There is no separate find or ls tool — pattern matching and directory listing are both the glob
tool. A model that runs find . -name '*.rs' or ls -R in the shell gets back an unbounded dump that
includes target/, node_modules/, and .git/; glob is bounded and gitignore-aware instead:
- Glob matching, or a bare directory/file path.
glob {path?, hidden?, gitignore?, limit?}.pathaccepts a glob, a single file, a directory (recursed), or a semicolon-delimited list of any of those (src/**/*.ts; test/**/*.ts); omitted, it searches the workspace root. gitignore(defaulttrue) hides.gitignorematches; setfalseto find.env*, build output, or anything the repo ignores.hidden(defaulttrue) includes dotfiles.- Bounded by result count, default and max
200(DEFAULT_LIMIT/MAX_LIMITinglob.ts) — not a byte cap. Every truncation is surfaced as an actionable notice. - Sorted by mtime, newest first (not lexicographic), grouped under
# <dir>/headers with basenames below; directories get a trailing/. .gitandnode_modulesare never descended, and traversal goes through the same filesystem abstractionread/grepuse, so it is correct inside a sandbox or remote container, not just on the host.
The grep tool (tools/grep.ts)
A model that runs grep -r / rg in the shell can get back tens of thousands of matching lines. The
grep tool is always regex (Rust regex / PCRE2 syntax; no literal-match flag) and paginates by file
count on top of the same gitignore-aware traversal glob uses:
grep {pattern, path?, case?, gitignore?, skip?}.pathscopes the search (single path, semicolon-delimited list, or afile:line-rangeselector on one target);caseenables case-sensitivity (default case-insensitive is not assumed — see the tool description for the exact default);skippages past files already returned once a call hits the file limit.- Bounded by file count, not match count. Results are paginated at
DEFAULT_FILE_LIMIT = 20files per call, with an internal total cap of2000matches (grep.ts);skipcontinues from where the previous call left off. - Output is per-file, line-number-prefixed, with context rows around each match when the harness runs in line-number mode.
- Cross-line patterns are detected from a literal
\n/\\ninpattern. - The tool description explicitly forbids shelling out to
grep/rg/ripgrep/ag/ack/git grepvia Bash — the built-in tool is the only sanctioned path.
The write tool (tools/write.ts)
read/glob/grep are the read side; write {path, content} creates or replaces a whole file. It
shares infrastructure with the edit engine rather than touching the filesystem directly:
- Shared verified pipeline.
write.tsimports the same file-snapshot store and LF-normalization helpers as the edit path (../edit/file-snapshot-store,../edit/normalize) and formats hashline headers via@veyyon/hashline, so writes inherit LSP diagnostics writethrough and diff/verification behavior rather than bypassing it. - Exclusive concurrency. The tool declares
concurrency: "exclusive", so nothing else can create or change the target file mid-call. - Steers to
editfor surgery. The tool description tells the model to prefereditfor a surgical change to an existing file, keepingwritefrom becoming a “re-emit the whole file” habit that burns tokens.
Sanitizing exec output for the model
Bash/exec tool output is sanitized before it reaches the model, via sanitizeText()
(packages/utils/src/sanitize-text.ts), used from session/streaming-output.ts and the interactive PTY
capture path (tools/bash-interactive.ts):
- ANSI stripping is Bun-native, not a hand-rolled parser.
sanitizeText()calls Bun’s built-inBun.stripANSI()when an ESC byte is present, then strips C0/C1 control bytes and DEL with a single regex pass. The function is a TypeScript replacement for a former Rust native (crates/pi-natives/src/text.rs::sanitize_text, noted in the current source comment) — there is no live Rust ECMA-48 grammar walker in this path today. - Keep
\nand\t, drop the rest. The control regex covers C0 (excluding tab/newline),\r, DEL, and the C1 range;\nand\tare the two explicit exclusions. - Model-facing only. Sanitizing happens on the text that becomes tool output for the model. The TUI renders exec output from its own delta stream and keeps its colors, so the operator’s view is untouched.
- Zero-cost when clean. Well-formed input with no control/ANSI bytes returns the original string
reference after one regex probe; only output that actually carries escapes pays for
Bun.stripANSI().
Why these are grouped with context
A read that bounds and a search that bounds its output are both about keeping the working context small and relevant. Long trajectories degrade when context fills with raw file dumps; these tools plus compaction & project memory are how a long task stays coherent.
Goal state and long sessions
Status: Durable goal mode is shipped in Veyyon: per-session objective, token budget, continuation on idle, and
goaltool ops. The richer goal card (verification ledger, working-set ledger, reviewer-finding carry-forward, retrieved-detail slots) below is Spec — not shipped — expansion layer only.
Long context fails when the agent loses the objective inside a large transcript. Veyyon’s shipped answer is goal mode: a structured objective injected outside the raw conversation tail, plus compaction for history.
Shipped goal card (session-backed)
id:
objective:
status: # active | paused | budget-limited | complete | dropped
token_budget: # optional
tokens_used:
time_used_seconds:
created_at / updated_at:
The harness owns persistence on the session. The model updates via /goal commands and the goal tool (create, get, complete, resume, drop). User objective text is escaped before it is injected into the prompt.
Token accounting includes input, output, and cache-write deltas relevant to provider billing.
Aspirational richer card (Spec — not shipped)
constraints:
known_decisions:
blockers:
completion_criteria:
current_plan:
files_read / files_modified / commands_run:
verification_state:
reviewer_open_findings:
context_budget:
last_material_user_instruction:
retrieved_detail_handles:
Do not assume these fields exist in storage or prompts until a release note says otherwise.
How it fits the context window (target model)
Each turn should eventually assemble named slots: system rules, goal card, active instructions, fresh tail, compaction prefix, retrieved detail. The 256k-class ceiling is a maximum, not a target. Required slots should fit or the harness should report overflow — not silently drop constraints.
Today, shipped slots are goal injection + compaction + standard session context.
What goal mode buys today
- Objective visible across turns without rereading the entire transcript.
- Idle continuation toward the objective when
goal.continuationModesallows. - Token budget steering when
token_budgetis set.
See also
Compaction and project memory
Long sessions need three records: the goal (when enabled), the recent transcript, and compacted history. Compaction compresses older history instead of truncating it, keeping task-critical context.
Context compaction
Compaction has exactly three settings (settings → Models → Compaction, or config.yml):
- Threshold (
compaction.thresholdPercent; docs aliascompaction.threshold) — percent of the context window at which auto-compaction runs. Also on demand with/compact. - Type (
compaction.strategy; docs aliascompaction.type) — how history is compressed:handoff— writes a structured handoff summary that preserves the task, pending questions, and recent decisions, then continues from it. Best for long task continuity.snap— archives history via the snapcompact engine (dense image snapshot path).
- Model (
compaction.model) — the model that performs LLM compaction / handoff. Unset uses your interactive model. See Models, roles, and profiles.
/compact <focus> steers a run with an “Additional focus:” directive. Recent user messages are
retained verbatim up to the type’s budget.
Memory backends (built)
When memory.backend is mnemopi or local, compaction can request pre-compaction context
from the active memory backend so summaries retain project facts. See Memory.
Goals (partial)
Goal cards and budgets are implemented (/goal, /guided-goal).
Spec — not shipped: the richer goal-card verification ledgers described in Goal state and long sessions. The shipped card is the bounded objective + budget + status model.
Role policy
Status: Built (partial). Role and subagent machinery is part of the harness. Roles are configuration and spawn parameters, not a fixed pipeline.
Intra-harness role policy chooses which model, prompt, and tool surface fits a subagent or specialized pass. Veyyon is provider-agnostic: roles are configuration + spawn parameters, not hard-coded provider assumptions.
What exists today
- Subagents via the
tasktool (packages/coding-agent/src/task/executor.ts) and/agentsthread switching. - Explicit model slots, not a role→model matrix: the interactive model (
/model), plus plainsubagent.modelandcompaction.modelfields in settings.defaultis not a model or a role. Named roles (modelRoles, scoped per profile) exist for anyone who wants specific work types pinned to specific models, but editing them lives in a settings group, not the model picker. See Compaction & project memory and Models, roles, and profiles. - Plan / goal modes alter prompts and tool gating (
/plan,/goal). There is no/advisorslash command — the advisor watchdog (advisor.enabledand related settings, inpackages/coding-agent/src/advisor/) is a background continuous-review mechanism, not a mode you invoke. Seedocs/advisor-watchdog.md. - Addressed inter-agent messaging via the
irctool (packages/coding-agent/src/tools/irc.ts,packages/coding-agent/src/irc/bus.ts):send/wait/inbox/listops over a process-global bus.sendis fire-and-forget with delivery receipts; the bus wakes an idle recipient with a real turn, revives a parked one, or injects a non-interrupting aside into a busy one — the shipped analogue of wake-now-vs-defer message routing.wait(orsend await:true) observes the recipient’s reply as a real turn. Gated byisIrcEnabled: available to every subagent and to a top-level session that can still spawn subagents.
Target pipeline (Spec — not shipped)
A fixed role pipeline (plan → implement → verify → repair) is the target shape. Today Veyyon uses
lighter-weight spawn, model-slot, and irc messaging patterns instead of a fixed pipeline — there is no
staged plan/implement/verify/repair handoff enforced by the harness itself.
Pair role choice with execution-order prompts: explore → plan → edit → verify.
Observability
Status: Partial. Usage stats and
/statsexist; OpenTelemetry metric export is Spec — not shipped.
Shipped
- Status line token and cost segments during interactive sessions
veyyon stats//stats— usage dashboards (@veyyon/statswhen enabled)- Structured logging in the coding-agent logger
- Repair telemetry: the repair seam is shipped, but bounded repair counters are not yet active (see Soundness and telemetry)
Target telemetry (Spec — not shipped)
When OTEL export is wired, metrics should use bounded label sets and fail loud on misconfigured exporters.
For session-level debugging: /dump, /context, /debug, and veyyon grep test harnesses.
Troubleshooting
Most Veyyon failures should tell you what failed and what to change. This page gives the public reading path for the common cases.
Install or startup fails
Run:
veyyon --version
veyyon plugin doctor
veyyon plugin doctor checks extension health and warns about missing optional binaries or provider
keys. Treat a non-zero result as actionable: fix the line it reports, then run the command again.
A provider does not work
Check the provider key first. The supported environment variables are listed in Models and providers. Confirm that the configured base URL and API key are correct for your chosen provider.
A command or edit is blocked
Check Configuration. The approval policy decides when Veyyon must ask before acting, and the sandbox policy decides what a command may read, write, or reach. Veyyon should fail closed when a safety decision is unclear.
Output looks truncated
Truncation is intentional when output exceeds a tool budget. The output should include the next action, such as increasing a limit, using an offset, or narrowing a search. See Lower token cost and faster turns and Bounded reads and instant search.
Where to go next
- Safety and honesty you can see explains the fail-loud design.
- Observability explains runtime signals and exporter configuration.
Frequently asked questions
This page answers common questions and errors. For a guided diagnostic path, see Troubleshooting.
Setup
veyyon plugin doctor fails. What do I fix?
veyyon plugin doctor exits non-zero when a check reports an error, and it prints the failed check and the next action. Fix the line it reports, then run it again. For the full diagnostics surface, see Diagnostics and health.
The sandbox is missing or not enforced
Veyyon fails closed when it cannot establish a sandbox. On Linux, the sandbox uses Landlock and seccomp, with a bundled bwrap fallback where the system sandbox is unavailable. On macOS it uses the Seatbelt profile. If you see a sandbox error, check that your kernel and user privileges support one of these mechanisms, or use /elevate-sandbox to walk through the elevated sandbox setup. See Sandbox and approvals for the policies and how they combine with approval settings.
Database and session locking
“Session file is locked” or “another Veyyon process is running”
Veyyon uses file locking to prevent two processes from writing the same session file at once. Only one process may hold a session lock at a time. If you see this error:
- Check that no other
veyyonprocess is holding the same session file lock. - If a previous process crashed, the lock may be stale. Restarting the machine or waiting for the process table to clear usually releases it.
- Do not delete or edit the session file while a process might still hold it.
For how sessions are stored and resumed, see Sessions.
Model authentication
“Invalid API key” or “Authentication failed”
Veyyon is provider-agnostic and does not proxy your requests. The key is sent directly to the provider you configured. Check that the environment variable or config key matches the provider’s expected name, that it is not expired, and that it has the required permissions. The supported variables are covered in Models and providers.
“Unsupported region” or endpoint errors
The base URL you configured must match the provider region and product endpoint. A model id that exists in one region may not exist in another, and the same hostname may host different model catalogs. Verify the endpoint URL in your provider dashboard and compare it with the base_url in your config. Models and providers explains how provider configuration is resolved.
Why is my model not listed?
Veyyon discovers model ids from the provider’s /models endpoint rather than maintaining a hardcoded allowlist. If a model is not listed, the provider endpoint may not expose it, or your key may not have access to it. Check the provider catalog and your key scopes first.
Workflow
Why did my edit ask for approval?
The approval policy decides when Veyyon must ask before acting. The default policy asks for anything that changes files or runs commands outside the trusted set. If a file edit paused for approval, it means the current policy classifies that edit as needing your yes. You can change the policy with -a, --ask-for-approval or in config.yml. The sandbox and approval policy work together: the sandbox is the hard boundary, and the approval policy is the interaction layer. See Sandbox and approvals.
How do I resume a session?
Run veyyon --continue to continue the most recent session, or veyyon --resume <SESSION_ID> to resume a specific one. The session stores turns, tool activity, and queued follow-ups, so a resumed session should keep its context and any pending work. For branching, forking, or exporting a session, see Sessions.
What happened to my queued follow-up?
Follow-ups queued during a turn are stored server-side with the session, so they survive TUI restarts and session resumes. If you press Esc to interrupt the current turn, queued follow-ups are pulled back into the composer so nothing is lost. See Sessions for the full queue behavior.
Why does my output look truncated?
Output is intentionally truncated when it exceeds a tool budget. The truncation should include a next action, such as increasing a limit, using an offset, or narrowing the search. See Troubleshooting for the public path.
Where to go next
- Troubleshooting for the guided diagnostic path.
- Models and providers for provider keys, endpoints, and model selection.
- Sandbox and approvals for sandbox and approval policies.
- Sessions for resume, fork, branch, and export.
Diagnostics and health
Veyyon’s shipped diagnostics today are scoped to plugins and interactive debug — not a
full install-wide veyyon doctor command yet.
Plugin doctor (shipped)
$ veyyon plugin doctor
$ veyyon plugin doctor --fix
Checks plugin installation health. With --fix, it attempts automatic repairs where implemented.
Additional plugin-environment checks warn when optional external binaries (sd, sg, git) or common
API keys are missing — these are warnings, not hard failures.
TUI debug (shipped)
/debug
Opens the debug tools selector in the interactive session.
Memory diagnostics (shipped)
/memory diagnose
/memory stats
Operate the mnemopi memory backend from the TUI. See Memory.
Spec — not shipped: top-level veyyon doctor
Older handbook pages described:
$ veyyon doctor
$ veyyon doctor --json
$ veyyon doctor --summary --all
That full install health reporter (binary, sandbox, auth, terminal capabilities, feature flags, update feed, custom CA env vars) is not implemented as a root CLI subcommand today. Any mention of veyyon doctor for install health is a target, not current behavior.
Workaround today
veyyon --versionand a normal interactive session start.veyyon plugin doctorfor extension health./debugand/memory diagnoseinside the TUI.- Troubleshooting for common setup failures.
Exit status (plugin doctor)
veyyon plugin doctor exits non-zero when checks report error status. Warnings may still exit zero.
See also
Acknowledgements
Veyyon stands on the work of others, and we credit it plainly. The handbook keeps this as a footnote on purpose: Veyyon’s public docs explain Veyyon’s behavior first, while detailed competitive study stays in private research notes.
- oh-my-pi (can1357/oh-my-pi), under the MIT license. Veyyon
is a fork of oh-my-pi: the TypeScript/Bun agent loop and TUI, the
pi-*Rust natives (grep, PTY, hashline edits), provider breadth, role routing, session-tree work, and edit ergonomics all carry forward from it. Incorporated MIT code keeps its permission notice; see the repositoryLICENSE. - codex, by OpenAI, under the Apache 2.0 license. oh-my-pi and Veyyon carry forward the codex
apply_patchpatch format and parts of the agent-loop shape as an independent TypeScript reimplementation — seeNOTICEfor exactly which files are format-compatible versus which actually vendor Apache 2.0 code (the OpenAI wire types and the Playwright ARIA-snapshot bundle do; theapply_patchparser and the Codex backend client do not). - OpenCode, under the MIT license. Veyyon studies its plan/build workflow, project memory, compact command, and file-context UI ideas.
- Lossless Claw, under the MIT license. Veyyon studies its summary DAG, fresh-tail compaction, and compacted-history inspection tools.
- command-code, by Langbase. command-code is proprietary. Veyyon only studies observable mechanisms clean-room, copying no code or bundled implementation text.
The ideas here are reimplemented in Veyyon’s own design, tested to Veyyon’s own bar, and extended past
where we found them. Legal provenance and upstream notices live in the repository LICENSE, NOTICE,
and UPSTREAM.md.
Glossary
A concise vocabulary of the primitives that shape Veyyon’s runtime behavior.
-
apply_patch: Veyyon’s structured edit tool. The model emits a
*** Begin Patch … *** End Patchenvelope, and the harness applies it through one verified path that checks for a unique match, respects the sandbox, and records the diff. It is delivered as a Freeform tool on the Responses API or as a Function tool with an{input}JSON field on Chat Completions. -
approval policy: The rule that decides when Veyyon must ask you before running a command. Policies include
untrusted,on-request,granular, andnever. The approval policy is a soft gate; the sandbox policy is the hard boundary. -
backend catalog: The Tier-B data file that maps each model slug to a provider, wire API, and connection facts. Veyyon selects the model contract against this catalog rather than hardcoding provider lists.
-
compaction: The compression layer that summarizes a long trajectory into a smaller, information-preserving form instead of truncating it. Compaction preserves the goal card, recent user messages, and deterministic working-set facts across successive windows.
-
edit / write: The
editandwritetools change files on disk.writecreates or replaces a file;editreplaces exact text with new text. Both route through the same verified path asapply_patchand respect the sandbox. -
Freeform tool / Function tool: The two tool shapes Veyyon advertises to a model. A Freeform tool emits a raw grammar-shaped body; a Function tool emits JSON arguments matching a schema. The choice depends on the backend wire API.
-
goal state: A structured goal card owned by the harness and stored in the state database. It keeps the objective, constraints, blockers, and completion criteria in a named context slot separate from the conversation tail so compaction cannot drop the user’s intent.
-
hook: A user-configured lifecycle handler that runs a command in response to events such as
PreToolUse,PostToolUse,PreCompact, orSessionStart. Hooks can inject context, rewrite tool input, or block an action. -
MCP: Model Context Protocol, the bridge between Veyyon and external tools or data sources. Veyyon can consume MCP servers and can also run as an MCP server so other clients can delegate programming tasks to it.
-
model contract / BYOK: The model contract is your chosen endpoint, model, and credentials. BYOK (bring-your-own-key) means you supply your own provider or local-endpoint key, so Veyyon calls the API directly without telemetry egress.
-
personality: A style-only setting that changes how the agent writes replies without altering its tools, permissions, or sandbox. Built-in personalities include
pragmatic,friendly, andnone. -
plugin: A directory with a
.veyyon-plugin/plugin.jsonmanifest that adds skills, MCP servers, apps, hooks, or TUI customizations to Veyyon. Plugins are discovered through marketplaces. -
profile: A named configuration group (a per-profile
config.ymlunder the agent directory) that bundles model, provider, sandbox, approval policy, personality, and other runtime settings. Activate a profile at launch with--profileor at runtime with/profile. -
prompt-cache discipline: The practice of keeping stable prompt prefixes stable and treating cache behavior as measured runtime policy. Veyyon orders context and compacts in ways that preserve prefix stability across turns.
-
repair: The layer that coerces a malformed-but-recoverable tool call into schema shape before dispatch, or fails loud with coaching when the call is unrepairable. It is protocol-agnostic and driven by the same schema the model was shown.
-
repair cascade: The ordered set of sound transforms the repair engine applies to a tool call. Each rule returns a coerced value, a rule name for telemetry, and a coaching hint so the model stops re-malforming.
-
rollout: The append-only JSONL log of a session’s entries. Each entry carries an
idandparent_id; aleaf_moveline branches the active leaf to any earlier entry without rewriting history. -
sandbox policy: The hard boundary on what a command can touch. Policies include
read-only,workspace-write,danger-full-access, andexternal-sandbox. When enforcement cannot be established, Veyyon fails closed. -
session: The unit of interactive work in Veyyon. A session records turns, tool activity, approvals, edits, and verification output, and survives context pressure through goal state and compaction.
-
skill: A reusable capability defined as data on the filesystem in a directory with a
SKILL.mdfile. Skills are loaded from system, admin, user, project, and repository scopes and their instructions are injected into the system rules block. -
thread / active leaf: A thread is a linear sequence of messages within a session. The active leaf is the currently selected tip of the session tree that receives the next turn; branching moves the leaf without erasing sibling history.
-
tool call / repair: A tool call is a model message that invokes a tool; repair is the harness layer that fixes malformed calls before dispatch so they land on the first attempt.
-
turn: One complete model-invocation cycle in a session: the harness assembles context, the model responds, and any resulting tool calls are executed and repaired until the turn resolves.
-
verifier / stop-when-green: The verifier checks whether a goal or task is satisfied. Stop-when-green is the policy of ending the turn loop once the verifier passes, so the agent does not waste budget after winning.
See also: Sessions, turns, and threads, Permission model, Model contract, Repair overview, and Compaction and memory.
Brand and identity
Cross-product identity: workspace brand system. This page is how that identity shows up in the Veyyon harness (CLI, TUI, packages, config paths, docs).
Product identity
| Item | Value |
|---|---|
| Product name | Veyyon (only name — not “Veyyon Code”) |
| Primary command | veyyon |
| Short alias | vey |
| npm scope | @veyyon/* (e.g. @veyyon/pi-coding-agent) |
| Config home | ~/.veyyon (VEYYON_CONFIG_DIR / legacy OMP_CONFIG_DIR / PI_CONFIG_DIR) |
| Profile env | VEYYON_PROFILE (also accepts legacy OMP_PROFILE / PI_PROFILE) |
Veyyon forks oh-my-pi (MIT): TypeScript/Bun agent loop and TUI, Rust natives for hot paths (grep, PTY, hashline). Upstream provenance is license and history — not a tagline to paste into Veyyon UI or docs.
Features marked > **Spec — not shipped:** are target design, not current behavior.
Legal provenance: LICENSE, NOTICE, UPSTREAM.md, Acknowledgements. OpenAI-compatible wire formats are protocol facts, not product branding.
Voice
Exact, restrained, technical. Name the product Veyyon. Prefer short factual sentences. No inherited slogans (“IDE wired in”), no second product name for the CLI, no mascot language, no unbacked capability claims.
Interface palette
Titanium (titanium.json) is the shipped default dark theme (theme.dark defaults to titanium). Veyyon Dark (dark.json) is an alternative on the same brand rules. Light (light.json) is still the upstream oh-my-pi palette until a silver light theme ships.
| Theme | Status | Ground | Primary text | Brand accent | Highlight |
|---|---|---|---|---|---|
| Titanium (default) | Shipped | Pitch black #000000 | #e5e5e7 | Silver #B8BDC7 | Deep blue #4A84C9 |
| Veyyon Dark | Shipped | Pitch black #000000 | #FAFAFA | Silver #B8BDC7 | Deep blue #4A84C9 |
| Light | Shipped (upstream) | Terminal / light gray chrome | Terminal default | Teal #5a8080 | — |
Spec — not shipped: Veyyon Light with
#FAFAFAground-inverse and silver#B8BDC7(brand-system target).
The ground is pitch black. On it, two colors do two jobs:
- Silver
#B8BDC7is the brand accent — wordmark, labels, rules, focus, progress, primary actions. - Deep blue
#4A84C9is a highlight only — one accent per view (link, caret, active state). Never a primary fill.
Green, red, and yellow only when meaning fits (success, error, warning).
| Token | Value | Use |
|---|---|---|
| Silver | #B8BDC7 | Brand accent |
| Silver bright | #E1E4E9 | Emphasis on dark surfaces |
| Silver dark | #747B86 | Secondary labels |
| Deep blue | #4A84C9 | Highlight |
| Blue bright | #6BA3E8 | Highlight hover |
Every background is pure black #000000. Hierarchy from silver hairlines and text weight — not raised panels or tinted fills. No cyan/orange/purple/rainbow chrome. No gradients. No glow. Sharp edges.
Onboarding and installers
Fullscreen setup: Veyyon wordmark + silver progress. No secondary product name and no upstream tagline under the mark. Install/upgrade copy uses Veyyon, commands veyyon / vey only.
Session welcome is a single hero card (not a dual-column dashboard): wordmark, one value line (Hashline edits that land. Your keys.), action rows with right-aligned shortcuts, optional recent sessions. Settings is a width-capped centered panel.
Documentation contract
- Identity and palette: brand system + this page.
- Engine behavior: handbook pages reconciled to shipped code.
- Spec — not shipped: target design until a release ships it.
See also: Themes and identity, TUI design language.
TUI design language
North-star for Veyyon terminal UX. Implementation lives in packages/tui (rendering) and packages/coding-agent/src/modes/theme/ (themes and tokens).
Brand palette in the TUI
Veyyon Dark and Veyyon Light follow the brand system:
| Role | Dark (shipped) | Light (shipped, upstream) |
|---|---|---|
| Surface | #050505 | Terminal / #e0e0e0 status chrome |
| Primary text | #FAFAFA | Terminal default |
| Brand accent (dark only today) | #B8BDC7 | Teal #5a8080 in light.json |
Veyyon Dark follows the brand system. Light theme JSON is still the upstream oh-my-pi palette until a silver light theme lands (Spec — not shipped).
Theme JSON is validated in theme.ts (themeJsonSchema). User overrides can live under ~/.veyyon/agent/themes/. See Themes and identity and engine doc docs/theme.md.
Spacing scale
Use a 4-cell rhythm:
| Token | Cells | Use |
|---|---|---|
space-0 | 0 | Flush edges |
space-1 | 1 | Inline gap, chip padding |
space-2 | 2 | Between stacked rows |
space-3 | 3 | Section break inside a pane |
space-4 | 4 | Pane padding from terminal edge |
Prefer space-1 / space-2 in dense tool UIs. One-off paddings are bugs.
Color and emphasis
| Role | Rule |
|---|---|
| Primary text | Theme text token (#FAFAFA / #050505) |
| Secondary / meta | dim or muted tokens |
| Emphasis | Bold on primary; silver accent for focus and selection |
| Links / paths | Theme accent (silver in first-party themes) |
| Danger / deny | Theme error (red) |
| Success / approved | Theme success (green) |
| Warning | Theme warning (yellow) |
| MCP / external tools | Distinct marker glyph; consistent hue within MCP cells |
Never rely on color alone. Pair hue with a glyph or word (ok, err, mcp). Respect NO_COLOR / --no-color.
Call sites route through packages/coding-agent/src/modes/theme/theme.ts helpers — not raw ANSI literals at widget sites.
Motion
| Kind | Budget |
|---|---|
| Spinner / shimmer | Low effective FPS; no 30 FPS frames for short blinks |
| Cursor blink | ~600ms period |
| Status pulse | Slow, interruptible |
No gratuitous animation on static content.
Empty / loading / error
- Empty: one quiet hint + example prompts; hide once the user types.
- Loading: spinner + short verb (
thinking,running,compacting). - Error: cause first, remediation second. No stack dumps in the composer.
Tool-call rendering
- Header: glyph + tool name + status word.
- Arguments: syntax-aware JSON when applicable; wrap with expand affordance for large bodies.
- Output: collapse large bodies; keep a one-line summary visible.
- MCP tools: visually distinct from local shell/file tools.
Iconography
Prefer ASCII-safe glyphs with Unicode upgrades when width is known (theme.symbols presets: unicode, nerd, ascii). Width math uses grapheme-aware helpers in @veyyon/pi-tui, not byte length.
Composer and chrome
- Empty composer hints
?for shortcuts and/for commands. - Mid-turn:
esc to interruptwhile a turn runs. - Picker gutters use
›(not>) for the selected row caret. - Tree connectors (
├─,└─) use themetree.*symbols consistently in session tree and tool groups.
Conformance
When touching TUI polish, name the token (spacing, theme color, motion budget). Hardcoded hex or ANSI at call sites outside theme.ts is a design-system bug.
Retained coordination and prompt patterns
Product decision. Do not “clean up” the coordination + prompt keepers below. Do not confuse them with settings UX that was cut (model-selection matrix, role-based routing popups — replaced by the shipped model-slots-plus-3-knob-compaction design, see Compaction & project memory and Models, roles, and profiles).
This page is a keep/cut ledger carried forward from the pre-fork planning notes. The keep/cut
decisions are sound; the module pointers below have been re-grounded on the real TypeScript tree —
the original notes pointed at an archived Rust/codex layout (crates/core/src/...,
multi_agents_v2, RoleModelSetting) that was never the shipped engine for this product.
KEEP — coordination + prompts
These are genuinely good patterns. Refactors must preserve behavior.
- Handoff / compaction prompt —
packages/agent/src/compaction/compaction.ts(renderHandoffPrompt,generateHandoffFromContext), called frompackages/coding-agent/src/session/agent-session.ts. Preserves task continuity across compaction whencompaction.typeishandoff(the other user-facing type issnap; see Compaction & project memory). Engineering detail:docs/handoff-generation-pipeline.md. - Subagent spawn model —
packages/coding-agent/src/task/executor.ts(spawn, per-model concurrency semaphore, soft output-budget steering notice, background output capture) pluspackages/coding-agent/src/task/agents.ts(bundled subagent definitions) and thetasktool registered inpackages/coding-agent/src/tools/index.ts. - Addressed inter-agent messaging — the
irctool (packages/coding-agent/src/tools/irc.ts, bus inpackages/coding-agent/src/irc/bus.ts):send/wait/inbox/listops, delivery receipts, reply-to threading. This is the shipped equivalent of the old point-to-pointInterAgentCommunication/send_message/waitmodel —sendwakes an idle recipient with a real turn, revives a parked one via the lifecycle manager, or injects a non-interrupting aside into a busy one (the wake-now-vs-defer split the old notes calledsteeringQueue/followUpQueue). Parent steering-hint prompt text lives inpackages/coding-agent/src/prompts/steering/{parent-irc,user-interjection}.md. A richer IRC-style, full multi-agent dashboard (channels, not just the message bus) remains Spec — not shipped (BACKLOGU4-10, beyond the/cockpitMVP) — the messaging primitive itself is built; the dashboard UI around it is not. - Subagent + todo-list interaction model — the
todotool (packages/coding-agent/src/tools/todo.ts) plus plan-mode guardrails (packages/coding-agent/src/tools/plan-mode-guard.ts). Agents maintain a checklist across multi-step work; this model is frozen and any TUI presentation work extends it without replacing it.
CUT — confusing settings / model-selection UX
These are not keepers. Do not preserve them when condensing settings; they were already replaced by the shipped model-slots-plus-3-knob-compaction design (see Compaction & project memory).
- Role-based model-selection matrix and per-role popups. Superseded by the shipped design: the
interactive model via
/model, plainsubagent.modelandcompaction.modelfields in settings.defaultis not a model or a role — seepackages/coding-agent/src/config/model-roles.tsandpackages/coding-agent/src/config/model-resolver.ts. - Silent per-role heuristic routing. The primary model drives everything unless the plain subagent/compaction fields say otherwise; there is no hidden per-role auto-pick behind the scenes.
- Overlapping compaction settings UX. Condensed to three fields: threshold, type
(
handoff/snap), model — seepackages/coding-agent/src/config/compaction-strategy.tsand thecompaction.*group inpackages/coding-agent/src/config/settings-schema.ts. Not a parallel model-picker maze. - Hosted Cloud/Ultra task-list backend types. Out of scope for this product; do not conflate with
the
todotool above.
Coordination vs settings — the confusion line
| Layer | KEEP or CUT | Where it lives |
|---|---|---|
| How agents are spawned and report back | KEEP | task/executor.ts, task tool |
| How agents message each other directly (send/wait/inbox) | KEEP | tools/irc.ts, irc/bus.ts |
| How agents track work (todo/checklist discipline) | KEEP | tools/todo.ts + prompt guidance |
| How compaction hands off context | KEEP | packages/agent/src/compaction/compaction.ts |
| Which model runs for which role (matrix + popups) | CUT | Replaced by plain subagent.model / compaction.model fields |
| Forced role reassignment on model change | CUT | /model only changes the interactive model |
| Compaction/subagent model popups | CUT | Plain settings fields (model tab) |
| Full IRC-style multi-agent dashboard (channels) | Spec — not shipped | Tracked as BACKLOG U4-10; the messaging tool itself is built |
Rule for future refactors: if a change touches subagent spawning, irc messaging, or the compaction
handoff prompt, preserve behavior. If it touches model-routing settings knobs, follow the shipped
model-slots design above — do not resurrect a role→model matrix because an old fork had one.