Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 doctor install-health command. Use veyyon plugin doctor and /debug today.

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

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/grep snapshot tags for verifiable hunks.
  • edit, write, and apply_patch modes 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

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, and grep tools 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

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

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

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 doctor with install-wide --summary / --json self-test. Today diagnostics are scoped to veyyon plugin doctor and 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

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.

ProviderEnvironment variable
openaiOPENAI_API_KEY
anthropicANTHROPIC_API_KEY (or ANTHROPIC_OAUTH_TOKEN)
googleGEMINI_API_KEY
deepseekDEEPSEEK_API_KEY
moonshotMOONSHOT_API_KEY
zaiZAI_API_KEY
openrouterOPENROUTER_API_KEY
xaiXAI_API_KEY
groqGROQ_API_KEY
mistralMISTRAL_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):

  1. A runtime --api-key for the current process (never persisted).
  2. A models.yml apiKey on a custom provider.
  3. A stored API key in the auth store.
  4. A stored OAuth credential (refreshed as needed).
  5. 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 doctor install health command. Use veyyon plugin doctor and the TUI /debug today. 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:

  1. Splash — silver wordmark reveal
  2. Providers — sign in to a model provider; optional web search tab
  3. Glyphs — Nerd Font / Unicode / ASCII for your terminal
  4. Theme — Titanium (default dark), Light, or browse
  5. 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:

  1. Veyyon reads relevant files (read, search, …).
  2. It proposes an edit through hashline/edit/write tools.
  3. When policy requires it, you approve the tool call (tools.approvalMode — see Safety).
  4. The change lands; diffs appear in the TUI.
  5. If you asked for tests, approve bash or cargo test as needed.

4. Work safely (defaults)

Tool approval tiers (read, write, exec) combine with tools.approvalMode:

ModeAuto-approvesPrompts for
planread (proposes without writing)everything that writes or runs
askreadwrite, exec
auto-editread, writeexec
yolo (default)all tiersnone (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

  1. Multi-file change — ask for a refactor across modules; watch hashline edits batch paths.
  2. Session tree/tree to jump to an earlier user message and branch in the same session file.
  3. Switch models/model for the model you talk to; set the subagent and compaction models in settings. See Models, roles, and profiles.

Where to go next

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
FieldMeaning
baseUrlOpenAI-compatible API root
apiRequest shape, e.g. openai-completions
apiKeyEnv-var name or literal; prefix with ! to run a shell command and use its stdout
auth: noneMark a keyless local provider
authHeader: trueInject the resolved key as Authorization: Bearer <key>
modelsList 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.cpp entry 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.

PageWhat it defines
Sessions, turns, and threadsThe 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 modelHow 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 contractThe 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:

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.

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

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

ModeAuto-approvesPrompts for
planRead-only; proposes changes without writingEverything that writes or runs
askRead-only toolswrite, exec
auto-editRead + workspace writeexec
yolo (default)All tiersNothing (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 bwrap is 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-approval policy names (untrusted, on-request, granular, never). Veyyon ships approval modes (tools.approvalMode) plus OS-level shell isolation, not a sandbox_policy config key.

Where the details live

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:

FactWhat it isWhere it lives
EndpointBase URL and API kindA built-in provider, or a custom provider under providers: in ~/.veyyon/agent/models.yml
ModelThe model id the endpoint understandsPinned with --model / /model, or discovered from the provider
KeyCredential the endpoint acceptsA 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 / replace available via edit.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:

  1. Base instructions for the active model or backend (execution order, stop-when-green, format-neutral tool guidance). See Execution-order prompts.
  2. User and project instructions (AGENTS.md / SYSTEM.md layers and any session steers).
  3. 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.
  4. 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.

FormHow the model calls itTypical API kind
FreeformA custom / grammar tool. The raw body is the tool payload (for example a full *** Begin Patch envelope).Responses-style
FunctionA 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 disabledProviders and 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 (or smol) — 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

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

PathApplierRole
edit@veyyon/hashline (default)Surgical edits via snapshot tags and hashline ops
writeWhole-file writerCreate or overwrite files; mints new snapshot tags in hashline mode
apply_patch / patch / replaceMode-specific parsersCompatibility 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

ToolWhat the model sendsUse for
editHashline input (default) or mode-specific payloadSurgical edits
writepath + full contentNew files or full rewrites
apply_patchV4A envelopeWhen 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

  1. read (or grep) returns [relative/path#TAG] and LINE:text rows.
  2. Model calls edit with sections anchored on the same TAG.
  3. 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_patch envelope. Veyyon uses hashline as the primary path; apply_patch is 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, the read-only / workspace-write / danger-full-access / external-sandbox policies, and the -s / --sandbox, --full-auto, and --dangerously-bypass-approvals-and-sandbox flags. Veyyon does not ship an OS-level command sandbox yet. What ships today is the tools.approvalMode autonomy 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), or tools.approvalMode in config.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

ControlQuestion it answersSoft or hard?
Sandbox policyWhat may this process read, write, and reach on the network?Hard boundary (OS-enforced where available).
Approval policyMust 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.

PolicyFilesystemNetworkBest for
read-onlyRead anywhere; write nothing.Off by default (opt in per policy / permissions).Exploring code you do not fully trust.
workspace-writeRead anywhere; write within the workspace and configured writable roots.Off by default.Everyday coding in a trusted project.
danger-full-accessNo filesystem restrictions from Veyyon.Unrestricted.Disposable environments that truly need full access.
external-sandboxFull 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-write for normal coding) plus an approval policy that still asks for risky work (on-request or untrusted).
  • Headless veyyon --print: approvals default to never (no TTY to answer), so the sandbox is the containment. Prefer --sandbox workspace-write (or read-only) explicitly in CI.
  • Network egress is off by default for read-only and workspace-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):

  1. Veyyon self-invokes a helper (veyyon-linux-sandbox) with the permission profile, policy cwd, and command cwd.
  2. 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.
  3. A bundled bwrap is 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.
  4. 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.”

ModeMeaning
Restricted (default for bounded sandboxes)Outbound network is off unless the permission profile enables it.
EnabledNetwork 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-sandbox plus 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.

PolicyBehaviorBest for
untrustedOnly known-safe, read-only commands run automatically; everything else asks.Unfamiliar repositories or high-risk work.
on-requestThe model decides when to ask; it requests approval for anything it considers risky.Everyday coding where you want the model to judge.
granularFine-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.
neverNever 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)

GoalSandboxApproval
Explore an unfamiliar reporead-onlyuntrusted
Everyday codingworkspace-writeon-request
Trusted CI automationworkspace-write or read-onlynever (exec default) + explicit --sandbox …
Already inside Docker/CI jailexternal-sandboxnever
Need full disk, still want human gatesdanger-full-accessuntrusted
Disposable env, no humans, no jaildanger-full-accessneveronly 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:

ActionEffect
/sandbox-add-read-dir <absolute_path>Grant the sandbox read access to a directory for the current session.
/elevate-sandboxWalk through setting up the elevated agent sandbox when a task needs more than the default.
/approveApprove a specific pending request.
/reloadRe-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)

  1. Is the repo trusted? If no → read-only + untrusted.
  2. Does the task need writes? If yes → workspace-write, keep network off until needed.
  3. Are you headless? If yes → set --sandbox explicitly; do not rely on interactive approvals.
  4. Is there an outer jail? If yes → external-sandbox (or --dangerously-bypass-… only when you understand the outer jail).
  5. Would a wrong rm -rf hurt? If yes → never combine full access with never.

See also

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 / --sandbox and -a / --ask-for-approval flags. The shipped control is the tools.approvalMode autonomy ladder — plan, ask, auto-edit, yolo — set with --approval-mode <mode>, --auto-approve / --yolo, or tools.approvalMode in config.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.

PolicyWhat it can touchBest for
read-onlyRead anywhere; write nothing. Network off by default.Exploring code you do not fully trust.
workspace-writeRead anywhere; write within the workspace and configured roots. Network off by default.Everyday coding in a trusted project.
danger-full-accessNo filesystem or network restrictions.Disposable environments or tasks that truly need full access.
external-sandboxFull 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>.

PolicyBehaviorBest for
untrustedKnown-safe, read-only actions run automatically; everything else asks.Unfamiliar repositories or high-risk work.
on-requestThe model requests approval for anything it considers risky.Everyday coding where you want the model to judge.
granularFine-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.
neverNever 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-write or read-only with headless never ask (the default for veyyon --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-only or -a untrusted overrides the policy for a single run.
  • veyyon --print defaults to never ask in headless mode with the active sandbox, so automation runs unattended but still bounded. --full-auto is deprecated; prefer explicit --sandbox.
  • /sandbox-add-read-dir <absolute_path> grants read access to a directory for the current session.
  • /elevate-sandbox walks you through setting up the elevated sandbox when a task needs more than the default.
  • /reload re-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:

SituationWhat you should see
Command or edit needs permissionAn approval prompt with command/path, cwd, and active sandbox.
Sandbox denies an actionA clear denial / tool failure — not a partial side effect.
Sandbox cannot be establishedAn error or explicit unsupported warning (for example WSL1 + bwrap).
Tool call is malformed but repairableThe call is repaired; the model gets coaching on the correct shape.
Tool call is ambiguous or unrepairableRefusal + coaching; no guessed dispatch.
Output was truncatedTruncation is recorded, not hidden.
Config / provider data is badLoad 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 editsoldText that 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, and git diff --cached. Do not ask Veyyon to clear uncertainty with git reset --hard, git checkout --, git restore, or git 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 .rules files (user and project) can allowlist or require prompts for command classes. For hermetic CI, veyyon --print --ignore-rules skips loading them; --ignore-user-config skips $VEYYON_HOME/config.yml while auth still uses VEYYON_HOME.
  • Network egress stays off by default in bounded sandboxes; turn it on deliberately.

What to check

  • Run veyyon plugin doctor after 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, approval p, or the exec startup line).
  • Prefer workspace-write (or read-only) over danger-full-access unless the machine is disposable.
  • Never combine danger-full-access with never outside 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:

  1. Before — policy + sandbox chosen; doctor can verify readiness.
  2. During — approvals, sandbox denials, repair refusals, and truncated outputs appear in the turn.
  3. After — turn diffs and (for veyyon --print --json) structured events record what ran, what failed, and what the final message was.

Next

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.

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
  • /login inside the TUI, which stores the credential in the auth store, or
  • A models.yml apiKey on 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 idNotes
anthropic, openai, google, groq, …Cloud providers; sign in with /login <id> or set the env var.
amazon-bedrockUses the AWS credential chain (AWS_PROFILE, instance role, …).
ollama, lm-studio, llama.cppLocal 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

ActionWhat it changesWhat it does not change
/model (or restart with --model)The interactive model for subsequent turnsThe 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 --model for 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.

PriorityPreferWhy
Strongest agentic coding / tool useFrontier hosted model you already trustBest first-attempt edits and planning on hard refactors
Cost of long sessionsCheaper model for the subagent and compaction slotsKeeps interactive quality; shrinks background spend
Latency / iteration speedFast local or flash-tier cloud modelTight edit-test loops; hashline recovery still helps weaker models
Air-gapped / private codeOllama or LM StudioKeys and weights stay on your machine
CI automationStable mid-tier cloud id pinned with --modelPredictable cost; pair with --approval-mode auto-edit
Mixed teamStrong interactive model + a cheaper subagent modelReviewer 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

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 /resume inside 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 with veyyon gc.
  • Run a bounded non-interactive task by passing a prompt: veyyon "…".

Spec — not shipped: top-level veyyon resume / fork / archive verbs. 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.

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 corresponding tui option in config.yml also 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:

ModeWhat it shows
defaultConversation entries (hides low-signal noise)
no-toolsdefault plus hides tool-result-only assistant messages
user-onlyUser messages only
labeled-onlyEntries with labels
allEvery 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.

  • /fork duplicates 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.
  • /branch picks 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, or alt+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:

KeyPurpose
statusLine.presetdefault, minimal, compact, full, nerd, ascii, or custom
statusLine.leftSegments / statusLine.rightSegmentsSegment lists when preset: custom
statusLine.separatorpowerline, pipe, slash, block, none, ascii, …
statusLine.sessionAccentColor the bar from the session accent
statusLine.showHookStatusShow 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)

CommandWhat it does
/treeBrowse the session entry tree and jump or label entries
/branchPick an earlier user message and branch a new session file from it
/forkDuplicate the entire current session into a new file (no entry picker)
/session infoSession metadata and stats
/agentsConfigure task subagent definitions (bundled/project/user)
/cockpitLive multi-agent monitor: status, model per agent, drill-in transcript
/jobsList 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

CommandAction
/hotkeysShow active chords
/settingsSettings UI (includes keymap-related options)

Spec — not shipped: a /keymap interactive remapper and nested [tui.keymap.*] tables. Veyyon uses keybindings.yml + /hotkeys instead.

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:

ModeBehavior
disabledNo web access.
cachedThe default. Uses cached results only, so no live network call is made for a search.
indexedUses an indexed corpus.
liveFull 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 review subcommand and the approvals_reviewer / auto_review / review_model config keys. What ships today is the interactive /review slash command and the passive advisor runtime (--advisor, which reviews each turn and injects notes). For non-interactive or CI review, drive /review through veyyon --print with a review prompt. The /review and advisor sections below are real; the veyyon review CLI 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:

FlagChanges reviewed
--uncommittedStaged, 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.

  1. 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
  1. Run a review against that working tree:
$ veyyon review --uncommitted
  1. 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-end location, 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.
  1. 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.

  1. 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
  1. Review the branch delta:
$ veyyon review --base main
  1. 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:

GoalFlag
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_model config key, no continuous auto-review approval path, and no /auto-review or /approve command in the shipped registry today. Approvals are governed by tools.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
userYou approve prompts interactively (default; shipped today via tools.approvalMode).
auto_reviewThe 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 exec subcommand. Non-interactive mode is the --print launch flag (short -p): veyyon -p "prompt". JSON output is --json, and resume is --resume / --continue. The veyyon exec resume / veyyon exec review subcommand forms and several flags (--output-schema, --oss) below are the target shape; run veyyon --help for 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

OptionEffect
--strict-configError when config.yml contains fields this version does not recognize.
--skip-git-repo-checkAllow running outside a Git repository.
--ephemeralDo not persist the session under VEYYON_HOME/sessions.
--ignore-user-configDo not load $VEYYON_HOME/config.yml. Auth still uses VEYYON_HOME.
--ignore-rulesDo 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=valueOverride 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

OptionEffect
-m, --model <MODEL>Pin the conversation model for the run.
--ossUse the open-source / local provider path.
--local-provider <ollama|lmstudio>Which local provider to use with --oss.

Sandbox and trust

OptionEffect
-s, --sandbox <policy>Sandbox policy for model-generated shell commands (read-only, workspace-write, danger-full-access, …).
--dangerously-bypass-approvals-and-sandboxSkip confirmations and sandboxing. Alias: --yolo. Only for externally sandboxed environments.
--dangerously-bypass-hook-trustRun 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

OptionEffect
--jsonEmit 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 / argEffect
SESSION_IDConversation/session id (UUID) or thread name. UUIDs win if the value parses.
--lastResume 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.
--allShow / consider all sessions (disables cwd filtering).
-i, --image <FILE>Images to attach to the prompt sent after resume.
PROMPTPrompt 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 / argEffect
--uncommittedReview 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).
PROMPTCustom 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:

typeWhen
thread.startedFirst event; includes thread_id (use with resume).
turn.startedA new prompt turn began.
turn.completedTurn finished; includes usage token counts.
turn.failedTurn failed; includes error.message.
item.started / item.updated / item.completedLifecycle for an item in the thread.
errorUnrecoverable stream error.

Item payloads (itemtype, snake_case) include:

Item typeMeaning
agent_messageAssistant text (or JSON string when --output-schema constrained the final message).
reasoningReasoning summary text.
command_executionShell command, aggregated output, optional exit_code, status.
file_changePatch apply: list of path/kind (add|delete|update) + status.
mcp_tool_callMCP invocation lifecycle.
collab_tool_callCollab tool lifecycle.
web_searchWeb search request/results.
todo_listAgent plan / todo updates.
errorNon-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:

CodeMeaning
0Success.
1Veyyon error (config, auth, missing session, unrecoverable runtime) — or fallback when a child ended without a reportable status.
2Usage / clap parse error.
NChild process exit code passed through when applicable.
128 + signalChild 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-write or read-only over bypass flags.
  • Use --ignore-user-config / --ignore-rules when the runner’s home directory must not affect the job.
  • --dangerously-bypass-approvals-and-sandbox is for outer jails only — prefer external-sandbox when you can express that cleanly (Sandbox).
  • Never commit API keys into CI logs; use the runner secret store and env_key.

See also

Themes and identity

Veyyon’s interface is built around near-black, near-white, and a persistent Veyyon silver accent (#B8BDC7).

Shipped themes

FileNameNotes
dark.jsonVeyyon DarkRebranded: #050505 / #FAFAFA / silver #B8BDC7
light.jsonLightUpstream 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: theme in ~/.veyyon/agent/config.yml (profile-specific when using --profile).
  • Custom themes: drop JSON under ~/.veyyon/agent/themes/; schema in docs/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_DIR overrides the directory name; XDG paths after veyyon 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+Tab collaboration cycling, and a plan_mode_reasoning_effort config 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 (default true; toggle in /settings Advanced → Plan).
  • Slash: /plan toggles plan mode; /plan <prompt> enters plan mode and submits the prompt.
  • Related: /plan-review reopens plan review while plan mode is active.

Plan mode blocks goal mode and vibe mode (and vice versa).

Behavior

When plan mode is on:

  1. Veyyon selects a plan file path (per-session) and sets mode state on the session.
  2. Active tools are adjusted: resolve is added for plan approval; built-in write may be activated so the agent can draft the plan file.
  3. The agent explores and writes the plan using read-oriented work plus plan-file write/edit as configured.
  4. Finalization uses the resolve tool with plan approval semantics (plan_approval), not a <proposed_plan> XML tag.
  5. Exiting: /plan again (with confirmation if a draft exists) pauses or disables plan mode; session records mode entries 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 (default true).
  • 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

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.

ScopeLocationDescription
System$VEYYON_HOME/skills/.systemEmbedded first-party skills unpacked from the binary at startup.
Admin/etc/veyyon/skillsMachine-wide skills configured by administrators (on Unix-like systems).
User$HOME/.agents/skillsUser-installed skills. $VEYYON_HOME/skills is also supported as a legacy fallback.
Project.veyyon/skillsProject-scoped skills placed at the root of a repository.
Repository.agents/skillsLocal 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.importForeignConfig toggle and all skills.* 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 a type (for example, command or url), a value (for example, the command name or URL), a description, an optional transport, an optional command path, and an optional url.
  • policy: Restrict how the skill is invoked (optional).
    • allow_implicit_invocation: A boolean (defaults to true). If false, 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

  • /skills opens a selection menu with these choices.
    • List skills shows all active skills. Typing @ or $ (depending on whether the mentions_v2 feature 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.

When you close the toggle list, the TUI displays a status message stating how many skills were enabled or disabled.

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:

ScopeDirectoryPurpose
System$VEYYON_HOME/skills/.systemBundled first-party skills unpacked from the Veyyon binary.
Admin/etc/veyyon/skillsMachine-wide skills managed by administrators.
User$HOME/.agents/skillsSkills available to the current user. $VEYYON_HOME/skills is also supported as a legacy path.
Project.veyyon/skillsSkills shipped with a project. Placed at the project root.
Repository.agents/skillsLocal 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 from SKILL.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 specify type, value, description, transport, command, and url.
  • policy: Invocation restrictions.
    • allow_implicit_invocation: Whether the skill may be suggested or injected automatically. Defaults to true. Set to false to 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:

  1. Type /skills and select List skills, then choose Onboarding Check from the list. The default_prompt will be pre-filled if one is configured.
  2. 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:

FieldTypeDescription
nameStringThe unique name of the plugin. Only ASCII alphanumeric characters, hyphens, and underscores are allowed.
versionStringThe version of the plugin (optional). Defaults to "local".
descriptionStringA description of the plugin (optional).
keywordsArray of StringsKeywords used to index and search for the plugin (optional).
skillsString or Array of StringsPath or paths to directories containing skill definitions (optional).
mcpServersString or ObjectPath to a file or an inline object defining the plugin’s MCP servers (optional).
appsStringPath to a file defining the plugin’s custom applications and connectors (optional).
hooksString or Array of Strings or ObjectPath or paths to hook definition files, or inline hook objects (optional).
interfaceObjectPresentation 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:

  1. .agents/plugins/marketplace.json (canonical path)
  2. .agents/plugins/api_marketplace.json
  3. .claude-plugin/marketplace.json

The marketplace catalog defines:

  • A name and optional presentation metadata.
  • A plugins list. 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.

PathDescription
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-stagingStaging 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"]

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.json file, config.yml hooks: tables, and the { "type": "command" } external-subprocess model with the PreToolUse / 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 calls pi.on(...).

Event Lifecycle

Event NameSupports MatcherMatcher TargetDescription
PreToolUseYesTool nameFires before Veyyon executes a tool.
PermissionRequestYesTool nameFires when Veyyon requests permission to run a tool.
PostToolUseYesTool nameFires after Veyyon completes tool execution.
PreCompactYesCompaction triggerFires before Veyyon compacts the conversation history.
PostCompactYesCompaction triggerFires after Veyyon compacts the conversation history.
SessionStartYesStartup sourceFires when a Veyyon session starts.
UserPromptSubmitNoNoneFires when the user submits a new prompt.
SubagentStartYesSubagent typeFires when a subagent starts.
SubagentStopYesSubagent typeFires when a subagent finishes.
StopNoNoneFires 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:

  1. In config.yml (either system-level, user-level, or project-local config) under a hooks section.
  2. In a standalone hooks.json file inside the config folder (such as the project’s .veyyon/hooks.json or the user’s $VEYYON_HOME/hooks.json).

Hook Handler Schema

Every hook handler configuration supports the following parameters:

Field NameTypeDescription
typeStringMust be "command". (Prompt and agent hook types are reserved but not supported yet).
commandStringThe command string to execute in the shell.
commandWindowsString(Optional) A Windows-specific command override.
timeoutInteger(Optional) The execution timeout in seconds. Defaults to 10 minutes.
asyncBoolean(Optional) Whether to execute the hook asynchronously.
statusMessageString(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.yml or MDM profiles) are always trusted.
  • Trusted: User-level or project-local hooks whose calculated hash matches a trusted hash stored under the hooks.state section 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 if decision is "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 setting interrupt to true will cause the hook to fail closed).
  • PostToolUse:

    • decision (string): Set to "block".
    • reason (string): Required if decision is "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 if decision is "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.

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 call pi.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.

EventMatcher targetWhen it fires
PreToolUseTool nameBefore Veyyon executes a tool.
PermissionRequestTool nameWhen Veyyon asks for permission to run a tool.
PostToolUseTool nameAfter a tool finishes.
PreCompactCompaction triggerBefore compacting the conversation.
PostCompactCompaction triggerAfter compacting the conversation.
SessionStartStartup sourceWhen a session starts.
SubagentStartSubagent typeWhen a subagent starts.
SubagentStopSubagent typeWhen a subagent finishes.
UserPromptSubmitNoneWhen you submit a prompt.
StopNoneWhen 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.

EventMatcher targetExample values
PreToolUse, PermissionRequest, PostToolUseTool name^Bash$, ^Grep$, ^WriteFile$
PreCompact, PostCompactCompaction trigger^context-limit$
SessionStartStartup source^startup$, ^resume$, ^clear$, ^compact$
SubagentStart, SubagentStopSubagent 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:

FieldTypeDescription
typestringMust be "command".
commandstringThe shell command to run.
commandWindowsstringOptional Windows override.
timeoutintegerOptional timeout in seconds, default 600.
asyncbooleanOptional. Runs the hook without blocking.
statusMessagestringOptional 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 additionalContext and 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:

LevelMeaning
ManagedLoaded from administrator-enforced layers like /etc/veyyon/config.yml or MDM profiles. Always trusted.
TrustedUser or project hooks whose hash matches a recorded trusted hash in the hooks.state config section.
ModifiedPreviously trusted, but the command or matcher changed.
UntrustedNewly 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

TransportUse when
stdioLocal executable (Node, Python, binary)
HTTP / SSERemote hosted MCP service

Configure servers

Preferred: JSON files managed by Veyyon:

ScopePath
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

CommandPurpose
/mcpList servers, connection/auth status, exposed tools
/mcp addAdd a server (wizard)
/mcp listList 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 a veyyon mcp CLI. Veyyon configures MCP through mcp.json and the /mcp command 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:

ScopePath
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

typeUse whenFields
stdio (default)Local executable, script, or binary.command (required), args, env, cwd
httpRemote streamable-HTTP service.url (required), headers
sseLegacy 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

CommandPurpose
/mcpList servers, connection/auth status, and exposed tools
/mcp addAdd a server (writes mcp.json)
/mcp listList 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

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:

IntegrationPurpose
MCPAttach MCP servers; tools appear as mcp__… with approval tiers
PluginsInstall extensions; veyyon plugin install …
HooksEvent-driven automation in the agent loop
SkillsBundled 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.

/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)
  • /branch when doubleEscapeAction = "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):

ModeShows
defaultConversation nodes; hides label/custom/model_change/thinking bookkeeping
no-toolsdefault plus hides tool-result-only assistant messages
user-onlyUser messages only
labeled-onlyEntries with labels
allEvery 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

CommandBehavior
/branchPick 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.
/forkDuplicate 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.
CLIveyyon --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)

BackendStorageBest for
offNo memory injection or retention
localMarkdown under the agent memories dir (MEMORY.md, memory_summary.md, skills/)Curated summaries from past session files
mnemopiSQLite via @veyyon/pi-mnemopiVector + FTS recall, auto-retain, compaction hooks
hindsightHindsight backend (when configured)Alternative structured memory

Enable in config:

memory:
  backend: mnemopi   # or local, hindsight, off

With memory.backend: mnemopi, Veyyon:

  1. Opens scoped SQLite banks (global, per-project, or per-project-tagged).
  2. Recalls relevant memories into a <memories> block on the first turn (and refreshes the base prompt when recall runs from agent_start).
  3. Retains completed turns on a configurable interval (mnemopi.retainEveryNTurns, default 4).
  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:

SettingKeyValues
Thresholdcompaction.thresholdPercent (docs alias: compaction.threshold)percent of the context window
Typecompaction.strategy (docs alias: compaction.type)handoff or snap
Modelcompaction.modelmodel 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 independent memory.backend and 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 /memories settings 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; -p is --print).
  • Env: VEYYON_PROFILE=<name> (legacy: OMP_PROFILE, PI_PROFILE).
  • Shell alias: veyyon --profile work --alias mywork installs a managed block in your shell rc (see cli/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
  • new creates ~/.veyyon/profiles/<name>/agent/ with the expected identity dirs (skills/, commands/, …).
  • --from default (default) seeds config.yml, keybindings, MCP, skills, and other identity files from the default profile. Sessions, blobs, and databases are not copied.
  • --from blank creates an empty agent tree.
  • rm refuses 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.

SlotRunsWhere you set it
Interactive modelthe conversation you are in/model (live), --model, config.yml model
Subagent modeltask subagentssettings → Models
Compaction modelcontext compactionsettings → 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:

  1. Auto-compaction threshold (compaction.thresholdPercent) — when to compact.
  2. Type (compaction.strategy) — handoff or snap.
  3. 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 toWhere
Change the interactive model/model, config.yml model
Set the subagent modelsettings → Models (subagent.model)
Set the compaction modelsettings → Models (compaction.model)
Assign models to roles (optional)settings → Models → Roles, per profile
Separate user config treesveyyon --profile <name>
Tighten tool promptstools.approvalMode, tools.approval

See also

Personalities

Spec — not shipped: a /personality slash command. Configure personality via /settings or the personality key in config.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.

PersonalityConfig valueEffect
DefaultdefaultStandard Veyyon tone
PragmaticpragmaticConcise, task-focused
FriendlyfriendlyWarm, collaborative
NonenoneNo 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: /settingsModel tab → Prompt group → Personality. Options are resolved at render time (built-ins + your ~/.veyyon/personalities and project .veyyon/personalities files); 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:

  • none is a reserved sentinel that disables the block; a file literally named none.md is 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 personality to a name that resolves to nothing (no built-in, user, or project file) falls back to default and 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

Export and import

Session export (built)

/export writes the current session transcript. Target format depends on the extension:

TargetFormat
.jsonl or defaultNative session JSONL (append-only entries)
.html / .htmStandalone 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.jsonconfig.yml
  • MCP servers → mcp.json
  • Hooks → .veyyon/hooks.json or extension hooks
  • Skills → .agents/skills / .veyyon/skills
  • Subagents → .veyyon/agents
  • CLAUDE.mdAGENTS.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.yml hooks: tables and the { type: "command" } subprocess model with PreToolUse / PostToolUse matchers. Veyyon runs TS pi.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 exec subcommand with --json event streams, -o final-message capture, and veyyon review --uncommitted. See Non-interactive mode for the current scripted surface, and use /review in 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

NeedUse
Talk to an external system (DB, SaaS, browser bridge) over a protocolMCP server
Package reusable instructions, scripts, and examples as dataSkill (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.

IntentCommand
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

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.

ScopePathNotes
Global~/.veyyon/agent/config.ymlThe main persistent file. /settings and veyyon config set write here.
Project<repo>/.veyyon/config.ymlLoaded when the cwd has a non-empty config dir. Edit by hand.
CLI overlayany 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:

GoalWhat to set
Choose the model you talk to--model / /model (saved as model)
Model for spawned subagentssubagent.model
Model for context compactioncompaction.model
Named model assignments (optional)modelRoles, per profile (settings → Models → Roles)
Add a local or BYOK providera 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)

GoalWhat to set
When Veyyon asks before actingtools.approvalMode: plan, ask, auto-edit, yolo (default); legacy always-ask/write accepted
Per-tool policytools.approval: map a tool to allow / deny / prompt
Advisor review passadvisor.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

GoalWhat to pass
Non-interactive one-shotveyyon --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:

GoalWhat to set
Auto-compaction thresholdcompaction.thresholdPercent (alias: compaction.threshold)
Compaction typecompaction.strategy: handoff or snap (alias: compaction.type)
Compaction modelcompaction.model (unset = your interactive model)
Cross-session memory backendmemory.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.yml profile files (a full environment per file, [profiles.<name>] tables). Veyyon relocates the agent directory and reads config.yml under 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

Feature flags

Spec — not shipped: a standalone veyyon features subcommand, the --enable-feature / --disable-feature launch flags, and a [features] config table. Today feature listing is a plugin subcommand — veyyon plugin features — experimental toggles are driven from /experimental in the TUI, and persistent toggles live in the features map of config.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.

StageMeaning
under developmentIncomplete; on only for development.
experimentalUsable but may change; off by default.
stableFinished and on by default (unless noted).
deprecatedStill works but slated for removal.
removedNo 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

KeyStageDefaultWhat it does
memoriesexperimentaloffGenerate and reuse cross-session memories (/memories, [memories]).
goalsstableonLong-running goal tracking (/goal, goal tools).
workspace_dependenciesstableonScan Cargo/Rust workspace roots for extra project context.
personalitystableonPersonality / tone controls in the product UI.
fast_modestableonFast-mode collaboration preset.
mentions_v2stableonUpdated @-mention behavior in the cockpit.

Automation and tools

KeyStageDefaultWhat it does
hooksstableonLifecycle hooks ([hooks] / hooks.json).
unified_execstableon (non-Windows)Unified exec path for shell tool runs.
shell_toolstableonExpose the shell tool surface.
skill_mcp_dependency_installstableonAssist installing MCP dependencies declared by skills.
tool_suggeststableonSuggest tools when the model may need them.
sleep_toolunder developmentoffModel-callable sleep (input-interruptible).

Spec — not shipped: there is no guardian_approval flag 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

KeyStageDefaultWhat it does
appsstableonApps / connectors surface.
pluginsstableonPlugin loading and management.
plugin_sharingstableonShare / distribute plugins.
multi_agentstableonMulti-agent collaboration runtime.
multi_agent_v2under developmentoffNext multi-agent runtime and role-model overrides.
enable_mcp_appsunder developmentoffMCP-backed apps path.

Browser, media, and realtime

KeyStageDefaultWhat it does
in_app_browserstableonIn-app browser surface (requirements may still apply).
browser_usestableonBrowser-use tool path.
browser_use_externalstableonExternal browser-use variant.
computer_usestableonComputer-use tool path.
image_generationstableonImage generation tools.
realtime_conversationunder developmentoffExperimental realtime voice conversation in the TUI.
prevent_idle_sleepexperimental (platform-gated)offKeep the machine awake while a thread runs (/experimental).

Network and auth storage

KeyStageDefaultWhat it does
network_proxyexperimentaloffExtra network proxy restrictions for sandboxed sessions that already have network; enable from /experimental and restart.
secret_auth_storagestableon for WindowsPrefer secret/auth storage backend wiring for credentials.
enable_request_compressionstableonCompress outbound provider requests when supported.
tool_call_mcp_elicitationstableonMCP elicitation during tool calls.

Deprecated (still toggles, avoid new dependence)

KeyNotes
web_search_request / web_search_cachedDeprecated web-search wiring; prefer current search docs.
use_legacy_landlockDeprecated 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

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

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

  2. 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 doctor and completed one normal session.

  3. 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.yml or 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-config in CI to keep your config clean; leave it off during normal use so upgrades do not immediately break.

Updating your config

  1. Open VEYYON_HOME/config.yml in an editor.

  2. Run the new binary once to see any validation errors:

    veyyon --version
    veyyon plugin doctor
    
  3. Fix each reported line. If you are unsure what a key does, see the sample configuration that ships with Veyyon or read Configuration.

  4. After editing, run veyyon plugin doctor again 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

  1. Close Veyyon.
  2. Remove the state database file (see File locations for the exact path on your platform).
  3. Restart Veyyon. The state database is rebuilt from sessions/ and archived_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.

  1. Close all Veyyon processes.
  2. Restore the previous binary from your system package manager, the release archive, or your own backup.
  3. Restore your VEYYON_HOME/config.yml from the backup you made before upgrading, if the new version modified it in ways the old version cannot read.
  4. 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.
  5. Start Veyyon and run veyyon plugin doctor to 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 doctor install-health command with --summary / --json reports. Use veyyon plugin doctor and the TUI /debug today.

Where to go next

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 veyyon command 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:

OptionPurpose
[PROMPT]Optional initial user prompt
-c, --config key=valueOverride 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-screenInline 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:

CommandAliasesPurpose
launch(default)Interactive or prompted session
acpAgent Control Protocol server mode
agentsManage agent definitions
auth-brokerShared auth broker (headless login)
auth-gatewayAuth gateway helper
benchBenchmark harness
commitAgentic commit workflow
completionsShell completion scripts
configList/get/set settings
dry-balanceToken balance probe
gcGarbage-collect session artifacts
grepTest grep tool (esp. Windows)
galleryTUI gallery / fixtures
grievancesInternal grievance reporter
installInstall / bootstrap
joinJoin collab session
modelsList models and providers
pluginPlugin lifecycle (list, install, …)
readRead-tool CLI probe
sayTTS one-shot
searchqWeb search probe
setupFirst-run setup wizard
shellNative shell probe
sshSSH host configuration
tiny-modelsOn-device tiny model utilities
tokenToken utilities
ttsrTime-traveling stream rules test
updateSelf-update
usageProvider usage limits
worktreewtGit 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, and resume / fork / archive as top-level CLI verbs. Use /resume, /fork, and /session in 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

CommandPurpose
/new, /freshNew session (fresh may reset provider stream state)
/resumeResume another saved session
/fork, /branch, /treeBranching and session tree UI
/rename <title>Rename session
/move <dir>Move session to another working directory
/export [path]Export session JSONL or HTML
/dumpDump debug artifacts
/session info, /session deleteSession metadata or delete
/exit, /quit, /pauseLeave or pause

Model, modes, and behavior

CommandPurpose
/model [id]Select the interactive model only (no role cycle; roles live in settings)
/switchSame interactive model picker
/fast on|off|statusFast mode
/planToggle plan mode
/plan-reviewRe-open plan review
/goal …Goal set/show/pause/resume/drop/budget
/guided-goalGuided goal wizard
/loopLoop mode controls
/prewalkPrewalk edit path
/advisor …Secondary reviewer model
/settings, /setupSettings UI; /setup / /providers opens provider sign-in
/statuslineSettings UI, jumped to Status Line (preset/segments/separator)
/reload-pluginsReload extensions
/forceForce-submit queued work

Tools, context, and jobs

CommandPurpose
/compact [focus]Compact context now (compaction.model + type); optional focus string
/shake [elide|images]Shake tool-result bulk
/handoffCompaction handoff helper
/contextContext usage report
/toolsTools visible to the model
/jobsBackground async jobs
/todo …Todo list CRUD
/browser …Browser tool mode
/memory …Memory backend view/stats/clear/enqueue
/copyCopy last response

Auth and usage

CommandPurpose
/login [provider|url]OAuth / API key login
/logout [provider]Log out
/usage show|resetProvider rate limits
/changelog [full]Release notes

Extensions

CommandPurpose
/mcp …MCP server management
/plugins …Plugin browser
/extensionsExtension dashboard
/agentsSwitch agent threads
/ssh …SSH host setup
/hotkeysActive keybinding chords
/collab …, /join, /leave, /shareCollab sessions

Side agents and misc

CommandPurpose
/btwEphemeral side question
/tan, /omfgBackground agent spawns
/vibeVibe-mode subagents
/retryRetry failed turn
/debugDebug overlays
/queueQueue follow-up message
/dropDrop 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

  1. Model emits a tool call (JSON arguments per schema).
  2. Veyyon validates arguments; handlers run after approval/sandbox checks.
  3. 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)

ToolPurpose
editApply changes — default hashline input string (edit.mode)
writeCreate or overwrite a whole file
apply_patchV4A 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)

ToolPurpose
readFiles, dirs, URLs, archives, SQLite, memory://, skill://, …
grepRipgrep-backed search; hashline headers in hashline display mode
globPath globbing
search_tool_bm25Discover tools by description (when enabled)

Shell and execution (built)

ToolPurpose
bashShell commands with sandbox + approval
sshRemote commands via configured hosts
evalJS/Python/Julia/Ruby eval cells (when enabled)
debugDebugger integration
browserBrowser automation
jobBackground job control

Agent coordination (built)

ToolPurpose
taskSpawn subagents
ircInter-agent messaging
todoStructured task lists
goalGoal card updates (with goal mode)
askUser questions
yieldYield turn for follow-ups

Memory (when backend enabled)

ToolPurpose
memory_recall, memory_retain, memory_reflect, memory_editMnemopi/hindsight surfaces
learnAutolearn (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

BindingAction
ctrl-tOpen transcript scrollback
ctrl-gOpen external editor for the current draft
ctrl-oCopy the last agent response to the clipboard
ctrl-lClear TUI history
alt-rToggle raw scrollback mode
(unbound)Toggle Vim mode
(unbound)Toggle Fast mode

Chat

BindingAction
escInterrupt the running turn
alt-, or shift-downDecrease reasoning effort
alt-. or shift-upIncrease reasoning effort
alt-up or shift-leftEdit the most recently queued message

Composer

BindingAction
enterSubmit the current message
tabQueue the current message while a task is running
alt-enterQueue the current message as a follow-up turn
? or shift-?Toggle the composer shortcut helper
ctrl-rOpen reverse history search or move to the previous match
ctrl-sMove to the next match in reverse history search

Editor

BindingAction
ctrl-j, ctrl-m, enter, shift-enter, alt-enterInsert a new line
left or ctrl-bMove cursor left
right or ctrl-fMove cursor right
up or ctrl-pMove cursor up
down or ctrl-nMove cursor down
alt-b, alt-left, ctrl-leftMove cursor left by one word
alt-f, alt-right, ctrl-rightMove cursor right by one word
home or ctrl-aMove cursor to the start of the line
end or ctrl-eMove cursor to the end of the line
backspace, shift-backspace, ctrl-hDelete the character to the left
delete, shift-delete, ctrl-dDelete the character to the right
alt-backspace, ctrl-backspace, ctrl-shift-backspace, ctrl-w, ctrl-alt-hDelete the word to the left
alt-delete, ctrl-delete, ctrl-shift-delete, alt-dDelete the word to the right
ctrl-uDelete from the cursor to the start of the line
ctrl-kDelete from the cursor to the end of the line
ctrl-yPaste the deleted text buffer

Pager

BindingAction
up or kScroll up one row
down or jScroll down one row
page-up, shift-space, or ctrl-bScroll up one page
page-down, space, or ctrl-fScroll down one page
ctrl-uScroll up half a page
ctrl-dScroll down half a page
homeJump to the beginning
endJump to the end
q or ctrl-cClose the pager
ctrl-tClose the transcript view

List

BindingAction
up, ctrl-p, ctrl-k, kMove selection up
down, ctrl-n, ctrl-j, jMove selection down
left or ctrl-hMove selection left
right or ctrl-lMove selection right
page-up or ctrl-bMove up one page
page-down or ctrl-fMove down one page
homeJump to the first item
endJump to the last item
enterAccept the current selection
escCancel and close the list

Approval

BindingAction
ctrl-a or ctrl-shift-aOpen the fullscreen approval view
oOpen the requesting thread
yApprove the current request
aApprove similar requests for the rest of the session
pApprove similar requests matching the command prefix
dDeny the request
esc or nDecline the request and prompt for feedback
cCancel 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.

ModeBindingAction
NormaliEnter Insert mode
AnyescReturn to Normal mode

Normal-mode basics:

CategoryBindings
Motionsh, j, k, l, w, b, e, 0, $
Operatorsd, y, c
Line operationsdd, yy, cc
Text objectsw, 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 /keymap interactive remapper and nested tui.keymap.<context> tables inside config.yml. The customization surface that actually ships is the flat keybindings.yml file 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:

VariablePurpose
VEYYON_CONFIG_DIROverrides 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_DIRFull override for the agent directory (default ~/<config-dir-name>/agent).
VEYYON_PROFILESelects the active named profile (~/.veyyon/profiles/<name>/agent). Legacy aliases OMP_PROFILE and PI_PROFILE are still accepted.
VEYYON_PACKAGE_DIROverride package directory for bundled assets (Nix/Guix). Legacy OMP_PACKAGE_DIR / PI_PACKAGE_DIR.
VEYYON_NO_PTYSet to 1 to disable PTY-based interactive bash. Legacy OMP_NO_PTY / PI_NO_PTY.
VEYYON_NO_TITLESet 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.

ProviderVariable
OpenAIOPENAI_API_KEY
AnthropicANTHROPIC_API_KEY (or ANTHROPIC_OAUTH_TOKEN, which takes precedence)
DeepSeekDEEPSEEK_API_KEY
MoonshotMOONSHOT_API_KEY
Z.AIZAI_API_KEY
OpenRouterOPENROUTER_API_KEY
Google GeminiGEMINI_API_KEY
xAIXAI_API_KEY (or XAI_OAUTH_TOKEN, which takes precedence for xai-oauth)
GroqGROQ_API_KEY
MistralMISTRAL_API_KEY
CursorCURSOR_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

VariablePurpose
OLLAMA_BASE_URL / OLLAMA_HOSTOllama discovery base URL (defaults to http://127.0.0.1:11434).
LM_STUDIO_BASE_URLLM Studio discovery base URL (defaults to http://127.0.0.1:1234/v1).
LLAMA_CPP_BASE_URLllama.cpp discovery base URL (defaults to http://127.0.0.1:8080).
LITELLM_BASE_URLLiteLLM proxy base URL fallback (defaults to http://localhost:4000/v1).
PI_EDIT_VARIANTForce 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

VariablePurpose
NODE_EXTRA_CA_CERTSExtra 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_KEYmTLS 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

VariablePurpose
VEYYON_INSTALL_DIROverrides 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.

VariablePurpose
VEYYON_MCP_TIMEOUT_MSOverrides 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:

VariablePurpose
VEYYON_AUTH_BROKER_URLBase URL of the remote auth-broker; selects broker mode. Legacy alias OMP_AUTH_BROKER_URL.
VEYYON_AUTH_BROKER_TOKENBearer token sent to the broker. Legacy alias OMP_AUTH_BROKER_TOKEN.
VEYYON_AUTH_BROKER_SNAPSHOT_TTL_MSFreshness window (ms) for the encrypted local snapshot cache; default 3600000.
VEYYON_AUTH_BROKER_SNAPSHOT_CACHEPath to the encrypted local snapshot cache.

Most installs never set these. Details: docs/auth-broker-gateway.md.

Repair

VariablePurpose
VEYYON_REPAIR_DISABLETruthy 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

VariablePurpose
NO_COLORWhen set (to any value), Veyyon renders without color; hierarchy comes through emphasis, spacing, and glyphs instead.
TERM / COLORTERMRead to detect terminal capabilities (truecolor, ANSI-256, ANSI-16) and pick the matching palette mapping.
PI_HARDWARE_CURSORTruthy enables hardware cursor mode.
PI_TUI_WRITE_LOGWhen 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

NameStatus
VEYYON_HOMENever 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_HOMENever existed. No separate SQLite-state override; state lives under the resolved agent directory.
VEYYON_API_KEY / VEYYON_ACCESS_TOKENNever existed as a Veyyon-branded credential; use each provider’s native key variable.
VEYYON_AUTHAPI_BASE_URLNever existed. The ChatGPT AuthAPI host used by personal-access-token whoami is not overridable via env today.
VEYYON_APP_SERVER_LOGIN_ISSUERBelonged to the removed app-server daemon; no equivalent exists in this runtime.
VEYYON_MANAGED_BY_NPM / VEYYON_MANAGED_BY_BUN / VEYYON_MANAGED_PACKAGE_ROOTNever existed.
VEYYON_SANDBOX / VEYYON_SANDBOX_NETWORK_DISABLED / VEYYON_THREAD_IDNever 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.

CodeMeaning
0Success.
1A 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.
2Usage error from argument parsing (an unknown flag or missing value); the standard clap convention.
NWhen Veyyon runs a child process (for example a sandboxed shell command), the child’s own exit code passes through unchanged.
128 + signalOn 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 to 1, 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

PathContents
config.ymlYour configuration (config.yaml also accepted). See Configuration.
auth.jsonThe managed OpenAI/ChatGPT credential (in file credential-store mode).
local.ageThe encrypted managed-secrets store (general secrets, including BYOK provider keys).
veyyon_auth.ageThe 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.jsonlComposer 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:

  • fileauth.json, mode 0600, written atomically.
  • keyring — the OS keyring (with the encrypted secrets backend 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):

PathPurpose
AGENTS.mdProject 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

  1. 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.
  2. 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 under docs/).
  • 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

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

SubsystemResponsibilityChapter
Edit engine (@veyyon/hashline)Default hashline edit pathEdit engine
SessionsSession trees, compactionCompaction & memory
MCPMCP client integrationMCP
ConfigSettings and profilesConfig
Memorymnemopi / local memoryMemory
GoalsGoal cards and budgetsGoal state

Design rules

  1. Prefer one obvious path. Hashline is the default edit surface; alternate edit.mode values exist for compatibility.
  2. 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-B backends.toml catalog 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

AreaResponsibilityHandbook
SessionsJSONL trees, resume, fork, compactSessions
EditHashline patches (default)Edit engine
SandboxApproval + filesystem/network boundsSandbox
ConfigLayered config.yml, profilesConfig
MCPExternal tool serversMCP
ProvidersModel registry + authProviders
Memorymnemopi / local backendsMemory

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

ConceptMeaning
Approval modeWhen to prompt before tool execution (on-request, never, …)
Sandbox policyFilesystem/network posture for command tools
Plan modeRestricts 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 / parentId linkage
  • 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 AgentSession owns 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=value overrides
  • 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)

ModuleRole
packages/coding-agent/src/mcp/Config load, manager, OAuth, tool wiring
packages/coding-agent/src/discovery/mcp-json.tsStandalone 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.

User guide: MCP, MCP setup.

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.

ComponentRole
Provider adaptersPer-provider connection and wire-format adapters
API client registryOpenAI-compatible API client registry
Provider detailsProvider metadata, auth mode, and endpoints
Model catalogModel catalog and per-model capabilities
Model registrySlug 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.md document (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: false schemas refuse leftover unrecognized keys after alias resolution.
  • Bounded: inputs over 1 MiB refuse repair.
  • Disable: VEYYON_REPAIR_DISABLE=1 or per-model harness.profiles with repair: 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:

  1. Parse leniency — trailing commas / relaxed JSON; stringified argument blobs.
  2. Ambiguity guard — refuse when required string fields have multiple plausible donors.
  3. Alias/typo key rename — unknown keys that match a common alias (filepathpath, contentscontent) 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).
  4. 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.
  5. Outcomeclean, repaired (canonical args + hints), or unrepairable (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

  1. Per-tool shape tables — richer per-tool repair rules beyond the generic alias/strict rules above.
  2. Per-model posture — strictness knobs aligned with Per-model harness profiles.
  3. 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.profiles in config.yml or harness-profiles.yml in the agent dir. Keys match provider/model-id or provider/* wildcards.
harness:
  profiles:
    "anthropic/claude-sonnet-4-20250514":
      repair: true
      tools: ["read", "edit", "grep", "bash"]
      promptSectionOrder: ["activeRepo", "system", "project"]
    "google/*":
      repair: false
  • repair: false skips 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 are system (the rendered system-prompt.md/custom prompt), project (the environment/workspace footer), and activeRepo (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 within system-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.toml three-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

  • /stats and veyyon 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: hashline in config.yml), implemented in the @veyyon/hashline package.

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

  1. read or grep records a whole-file snapshot and prints [relative/path#TAG] plus LINE:content rows (TAG is a four-hex snapshot id).
  2. The model sends edit with an input string: one or more [PATH#TAG] sections and hashline ops (SWAP N.=M:, DEL N.=M, INS.POST N:, block ops SWAP.BLK / DEL.BLK / INS.BLK.POST, plus INS.HEAD / INS.TAIL).
  3. @veyyon/hashline parses, verifies the tag against the snapshot store, applies ops, and returns a fresh [path#TAG] header plus a compact diff preview.
  4. write can 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

PropertyBehavior
Stale anchorMismatch errors name the tag; snapshot recovery can suggest the current file hash
Line numbers1-indexed; body rows use +TEXT prefix
OrderNon-overlapping hunks; overlapping regions fail loudly
EncodingApplies to normalized content; BOM and dominant line ending preserved on write

Further reading

Spec — not shipped: a veyyon-edit Rust crate, a V4A-only write path, and make_update_patch envelope 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 edit before validation. Hashline-specific lenient parsing inside @veyyon/hashline remains separate.

When a model emits a malformed edit or apply_patch call:

  1. Schema repair attempts JSON recovery and ambiguity refusal at the agent-loop seam.
  2. If repair succeeds, arguments proceed to hashline / apply_patch validation and dispatch.
  3. If repair cannot disambiguate, the loop returns a loud error with coaching hints (no dispatch).
  4. 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:

  1. Bundled pi-catalog models
  2. ~/.veyyon/agent/models.yml custom providers and models
  3. Runtime discovery (Ollama, LM Studio, discovery-enabled gateways)
  4. 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):

  1. CLI --api-key (ephemeral)
  2. models.yml apiKey on a custom provider
  3. Stored API key / OAuth in the agent auth store (~/.veyyon/agent/agent.db)
  4. Provider environment variables (see docs/providers.md)
  5. 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.toml Tier-B catalog as a separate subsystem. Veyyon uses models.yml plus 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 an apply_patch-forms prompt as the default edit surface. Veyyon uses hashline by default.

Bounded reads & instant search

Status: Built. The read, glob, grep, and write tools ship as TypeScript modules in packages/coding-agent/src/tools/{read,glob,grep,write}.ts. There is no experimental_tools or backends.toml gating — 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}, where path can 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), or src/foo.ts:5-16,960-973 (multiple ranges in one call). :raw reads 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 in session/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, via archive.zip:path/inside), SQLite databases (file.db:table, with pagination and where/order filters), 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?}. path accepts 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 (default true) hides .gitignore matches; set false to find .env*, build output, or anything the repo ignores. hidden (default true) includes dotfiles.
  • Bounded by result count, default and max 200 (DEFAULT_LIMIT / MAX_LIMIT in glob.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 /.
  • .git and node_modules are never descended, and traversal goes through the same filesystem abstraction read/grep use, 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?}. path scopes the search (single path, semicolon-delimited list, or a file:line-range selector on one target); case enables case-sensitivity (default case-insensitive is not assumed — see the tool description for the exact default); skip pages 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 = 20 files per call, with an internal total cap of 2000 matches (grep.ts); skip continues 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/\\n in pattern.
  • The tool description explicitly forbids shelling out to grep/rg/ripgrep/ag/ack/git grep via 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.ts imports 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 edit for surgery. The tool description tells the model to prefer edit for a surgical change to an existing file, keeping write from 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-in Bun.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 \n and \t, drop the rest. The control regex covers C0 (excluding tab/newline), \r, DEL, and the C1 range; \n and \t are 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 goal tool 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.continuationModes allows.
  • Token budget steering when token_budget is 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 alias compaction.threshold) — percent of the context window at which auto-compaction runs. Also on demand with /compact.
  • Type (compaction.strategy; docs alias compaction.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 task tool (packages/coding-agent/src/task/executor.ts) and /agents thread switching.
  • Explicit model slots, not a role→model matrix: the interactive model (/model), plus plain subagent.model and compaction.model fields in settings. default is 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 /advisor slash command — the advisor watchdog (advisor.enabled and related settings, in packages/coding-agent/src/advisor/) is a background continuous-review mechanism, not a mode you invoke. See docs/advisor-watchdog.md.
  • Addressed inter-agent messaging via the irc tool (packages/coding-agent/src/tools/irc.ts, packages/coding-agent/src/irc/bus.ts): send/wait/inbox/list ops over a process-global bus. send is 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 (or send await:true) observes the recipient’s reply as a real turn. Gated by isIrcEnabled: 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 /stats exist; OpenTelemetry metric export is Spec — not shipped.

Shipped

  • Status line token and cost segments during interactive sessions
  • veyyon stats / /stats — usage dashboards (@veyyon/stats when 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

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 veyyon process 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

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

  1. veyyon --version and a normal interactive session start.
  2. veyyon plugin doctor for extension health.
  3. /debug and /memory diagnose inside the TUI.
  4. 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 repository LICENSE.
  • codex, by OpenAI, under the Apache 2.0 license. oh-my-pi and Veyyon carry forward the codex apply_patch patch format and parts of the agent-loop shape as an independent TypeScript reimplementation — see NOTICE for 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; the apply_patch parser 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 Patch envelope, 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, and never. 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 edit and write tools change files on disk. write creates or replaces a file; edit replaces exact text with new text. Both route through the same verified path as apply_patch and 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, or SessionStart. 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, and none.

  • plugin: A directory with a .veyyon-plugin/plugin.json manifest 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.yml under the agent directory) that bundles model, provider, sandbox, approval policy, personality, and other runtime settings. Activate a profile at launch with --profile or 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 id and parent_id; a leaf_move line 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, and external-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.md file. 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

ItemValue
Product nameVeyyon (only name — not “Veyyon Code”)
Primary commandveyyon
Short aliasvey
npm scope@veyyon/* (e.g. @veyyon/pi-coding-agent)
Config home~/.veyyon (VEYYON_CONFIG_DIR / legacy OMP_CONFIG_DIR / PI_CONFIG_DIR)
Profile envVEYYON_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.

ThemeStatusGroundPrimary textBrand accentHighlight
Titanium (default)ShippedPitch black #000000#e5e5e7Silver #B8BDC7Deep blue #4A84C9
Veyyon DarkShippedPitch black #000000#FAFAFASilver #B8BDC7Deep blue #4A84C9
LightShipped (upstream)Terminal / light gray chromeTerminal defaultTeal #5a8080

Spec — not shipped: Veyyon Light with #FAFAFA ground-inverse and silver #B8BDC7 (brand-system target).

The ground is pitch black. On it, two colors do two jobs:

  • Silver #B8BDC7 is the brand accent — wordmark, labels, rules, focus, progress, primary actions.
  • Deep blue #4A84C9 is 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).

TokenValueUse
Silver#B8BDC7Brand accent
Silver bright#E1E4E9Emphasis on dark surfaces
Silver dark#747B86Secondary labels
Deep blue#4A84C9Highlight
Blue bright#6BA3E8Highlight 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:

RoleDark (shipped)Light (shipped, upstream)
Surface#050505Terminal / #e0e0e0 status chrome
Primary text#FAFAFATerminal default
Brand accent (dark only today)#B8BDC7Teal #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:

TokenCellsUse
space-00Flush edges
space-11Inline gap, chip padding
space-22Between stacked rows
space-33Section break inside a pane
space-44Pane padding from terminal edge

Prefer space-1 / space-2 in dense tool UIs. One-off paddings are bugs.

Color and emphasis

RoleRule
Primary textTheme text token (#FAFAFA / #050505)
Secondary / metadim or muted tokens
EmphasisBold on primary; silver accent for focus and selection
Links / pathsTheme accent (silver in first-party themes)
Danger / denyTheme error (red)
Success / approvedTheme success (green)
WarningTheme warning (yellow)
MCP / external toolsDistinct 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

KindBudget
Spinner / shimmerLow effective FPS; no 30 FPS frames for short blinks
Cursor blink~600ms period
Status pulseSlow, 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

  1. Header: glyph + tool name + status word.
  2. Arguments: syntax-aware JSON when applicable; wrap with expand affordance for large bodies.
  3. Output: collapse large bodies; keep a one-line summary visible.
  4. 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 interrupt while a turn runs.
  • Picker gutters use (not >) for the selected row caret.
  • Tree connectors (├─, └─) use theme tree.* 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.

  1. Handoff / compaction promptpackages/agent/src/compaction/compaction.ts (renderHandoffPrompt, generateHandoffFromContext), called from packages/coding-agent/src/session/agent-session.ts. Preserves task continuity across compaction when compaction.type is handoff (the other user-facing type is snap; see Compaction & project memory). Engineering detail: docs/handoff-generation-pipeline.md.
  2. Subagent spawn modelpackages/coding-agent/src/task/executor.ts (spawn, per-model concurrency semaphore, soft output-budget steering notice, background output capture) plus packages/coding-agent/src/task/agents.ts (bundled subagent definitions) and the task tool registered in packages/coding-agent/src/tools/index.ts.
  3. Addressed inter-agent messaging — the irc tool (packages/coding-agent/src/tools/irc.ts, bus in packages/coding-agent/src/irc/bus.ts): send/wait/inbox/list ops, delivery receipts, reply-to threading. This is the shipped equivalent of the old point-to-point InterAgentCommunication/send_message/wait model — send wakes 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 called steeringQueue/followUpQueue). Parent steering-hint prompt text lives in packages/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 (BACKLOG U4-10, beyond the /cockpit MVP) — the messaging primitive itself is built; the dashboard UI around it is not.
  4. Subagent + todo-list interaction model — the todo tool (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).

  1. Role-based model-selection matrix and per-role popups. Superseded by the shipped design: the interactive model via /model, plain subagent.model and compaction.model fields in settings. default is not a model or a role — see packages/coding-agent/src/config/model-roles.ts and packages/coding-agent/src/config/model-resolver.ts.
  2. 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.
  3. Overlapping compaction settings UX. Condensed to three fields: threshold, type (handoff/snap), model — see packages/coding-agent/src/config/compaction-strategy.ts and the compaction.* group in packages/coding-agent/src/config/settings-schema.ts. Not a parallel model-picker maze.
  4. Hosted Cloud/Ultra task-list backend types. Out of scope for this product; do not conflate with the todo tool above.

Coordination vs settings — the confusion line

LayerKEEP or CUTWhere it lives
How agents are spawned and report backKEEPtask/executor.ts, task tool
How agents message each other directly (send/wait/inbox)KEEPtools/irc.ts, irc/bus.ts
How agents track work (todo/checklist discipline)KEEPtools/todo.ts + prompt guidance
How compaction hands off contextKEEPpackages/agent/src/compaction/compaction.ts
Which model runs for which role (matrix + popups)CUTReplaced by plain subagent.model / compaction.model fields
Forced role reassignment on model changeCUT/model only changes the interactive model
Compaction/subagent model popupsCUTPlain settings fields (model tab)
Full IRC-style multi-agent dashboard (channels)Spec — not shippedTracked 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.