Skip to content
VulnetixPublic

Repository files navigation

Vulnetix | Belai

A safer LLM coding harness.

Every delimiter carries a cryptographic nonce and integrity hash, untrusted content is sanitised, and a role manager classifies untrusted inputs while segmenting benign tasks from risky agentic actions.

It pairs a safety-first architecture with multiple interaction modes (agent, plan, goal, and code for batching tool calls in a script) let you choose the right level of autonomy for the task. Inspiration for agents is taken from harnesses like Hermes, while the agentic loop and context management is designed to minimise token usage while maximising model alignment over longer sessions. This is not a model provider coding harness that incentivises token maxing, or a tool used as a gimmick "look! it has agency!". No, Belai holds models to sensible constraints and the result is a tool that leaves an impression of confidence and assurance.

Installation

macOS & Linux (Homebrew)

brew install vulnetix/tap/belai

Homebrew also knows an unrelated cask called belai, so upgrade by the tapped name to avoid the ambiguity:

brew upgrade --formula vulnetix/tap/belai

Windows (Scoop)

scoop bucket add vulnetix https://github.com/Vulnetix/scoop-bucket
scoop install belai

Shell installer

curl -fsSL https://raw.githubusercontent.com/Vulnetix/belai/main/install.sh | sh

The script detects your platform, verifies the download against the release checksums.txt, and refuses to install on a mismatch. It installs to /usr/local/bin, falling back to ~/.local/bin when that is not writable.

# choose the directory or the version
curl -fsSL https://raw.githubusercontent.com/Vulnetix/belai/main/install.sh | sh -s -- --install-dir ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/Vulnetix/belai/main/install.sh | sh -s -- --version v0.1.1

GitHub Releases

Pre-built binaries for Linux, macOS, and Windows are available on the Releases page.

Release assets ship in four families. The default install (install.sh, Homebrew, Scoop) is belai-bert-guardrails, which embeds the phase-1 prompt-saturation model so guardrails work with no provider or API key configured. The others:

Asset family What is embedded Approx size
belai nothing (LLM sentinel only, exactly the pre-embed behaviour; voice input downloads its speech model on /voice download) ~24 MB
belai-bert-guardrails (default) phase-1 prompt-saturation model, and the speech model for voice input ~77 MB
belai-bert-guardrails-jailbreak phase-1 + phase-2 jailbreak model, and the speech model ~517 MB
belai-no-classifier the speech model only ~56 MB

Sizes are for Linux amd64; other platforms differ by a few megabytes. The speech model is a 32 MB file that voice input runs in pure Go: every family except the plain belai build carries it, so voice works with no download. Plain belai stays small and fetches it only when you run /voice download.

install.sh accepts --variant (or BELAI_VARIANT) to pick one of vanilla (the plain belai family), no-classifier, bert-guardrails, or bert-guardrails-jailbreak. The on-disk command name is belai regardless of variant, and a variant binary updates to its own family.

Update checks

At startup Belai compares its own version against the newest GitHub release. When a newer one exists the banner says so and the belai panel prints the upgrade command for how this binary was installed — the Homebrew tap, the Scoop bucket, go install, or the installer script plus the release asset for your OS and architecture. Nothing is downloaded or installed; you run the command yourself.

The answer is cached for six hours, an unstamped source build never checks, and the check is off with update_check: false in settings or BELAI_NO_UPDATE_CHECK=1 for a single run.

Build from source

Belai is pure Go with no cgo, so a clean build needs only a Go toolchain.

git clone https://github.com/vulnetix/belai.git
cd belai
go build -o belai ./cmd/belai
./belai -version

To install it onto your PATH instead:

go install github.com/vulnetix/belai/cmd/belai@latest

A binary built this way reports its version as dev. To stamp the real version — and to get the same flags the release builds use — install just and run just build or just install from a clone. See docs/development.md.

Usage

Run belai in a project directory to start the terminal UI:

cd ~/code/my-project
belai

The first launch opens Getting started. A welcome page gives three ideas with the commands and keys for each, then it offers to install the Vulnetix CLI with Homebrew or Scoop (only if you pick Install). It then offers to create a Vulnetix account or log in, and once the CLI has credentials it turns on the Vulnetix AI Firewall and the Vulnetix MCP server. esc starts Belai from any page except the sign-up and login forms, where it goes back; /vulnetix setup or /welcome reopens it. Details are in docs/vulnetix.md.

Inside the UI, / opens slash-command autocomplete — /providers (the provider list) to manage providers, credentials and local models, /providers report to probe local model servers, /model to pick provider/model/effort for the agent and classifier roles, /settings to edit settings, /budgets to set token budgets per provider and model (also f1 then b), /caps to show the run caps (spend, tokens and wall time a session may use) and extend them (see docs/run-caps.md), /intel for session intelligence: plan limits, pace, trend, runway and usage over time (also f12 or f1 then i), /context to break down where the last request's tokens went against the model's window (see docs/token-budgets.md), /cache to show the prompt cache and set its lifetime (ttl 5m|1h) and the opt-in keep-alive pings (keepalive N|off; see docs/prompt-cache.md), /tts to read replies aloud (ctrl+b reads one, see docs/tts.md), /paste-image to attach the image on the clipboard (also ctrl+v, and @file.png attaches an image file), /permissions to edit tool rules, /prompts to manage the prompt library, /locate --dry-run to see which files explore locate may look at and where it would send its questions (see docs/jev-jobs.md), /kanban to view and manage the global kanban board (also f1 then t; see docs/kanban.md), /knowledge to browse the documents Belai has indexed, with their types, labels and topics, for this project, every project or the agents, and to search them as a model's Grep, Glob and Read would (also f1 then n; see docs/knowledge.md), /diff to browse the working tree's staged, unstaged and untracked changes read-only (see docs/diff.md), /processes (or /process) to manage supervised long-lived processes, /mode to set the operating mode (agent, plan, goal or auto), /handoff <plan file> to execute a plan file directly with no intent detection, /finish-review to show or switch the harness check of the working tree before a goal is accepted as complete (status, on, off; see docs/finish-review.md), /worktree to file a card a board agent works in its own git worktree and to list, update and drop the worktrees the harness made (new TITLE, list, update ID, drop ID; see docs/worktrees.md), /lint-loop to show or switch the per-edit lint loop, which runs the detected linter on each edited file (status, on, off; see docs/lint.md), /symbols to see the symbol index the Outline and Symbol tools serve: its size, where a name is declared, a file's declarations (refresh re-reads it; shown to you only; see docs/symbol-map.md), /init to have an agent draft a root AGENTS.md from the repository's detected facts when there is none (you approve the write; see docs/instruction-files.md), /patch-tolerance to show or set how loosely apply_patch matches hunks for this session (see docs/patch-tool.md), /todos, /profile, /agent to create, edit, and run background agents, /agents for the running agents, profiles, audit trail and fleet, /fleet to start (/fleet start NAME, /fleet crew NAME), stop and watch kanban worker agents (see docs/fleet.md), /crew for the crew control room, where a crew run's workers, cards, providers, scope of directories, faults and audit are watched and changed live (also f1 then o; see docs/fleet.md), /inbox for the cards waiting on you, where you approve or send back a flow's checkpoint (also f1 then w; see docs/kanban.md), /resume to resume a session by id or browse saved sessions, /tree to see the session as branches, continue from an earlier reply or prompt, or fork a branch into a new session (the file only grows; files on disk are not rolled back), /undo to put back the files your last prompt changed and /rewind to restore the code, the conversation or both to just before an earlier prompt, each after you confirm (see docs/checkpoints.md), /commit [message] to commit the files this session changed from a clean state, never the ones you had already edited and never a push (needs commit_mode ask or auto; see docs/commit-workflow.md), /lsp to manage language-server diagnostics, /execute and /refine to run or refine an extracted plan, /add-dir to add another directory to the workspace, /vulnetix (with review, configure, list, status, firewall, mcp and setup subcommands), /firewall to configure the AI Firewall — Vulnetix, the beta Fastly, Kong and AI Security Gateway adapters, or any custom proxy — and pick the active one (also f1 then f; see docs/firewall.md), /compact to summarise a long session into a new one (/compact <focus> steers the summary, /compact status shows the thresholds and the circuit breaker; see docs/compaction.md), /relay [direction] to draft a handoff note, review and edit it, then continue in a fresh session that begins from it (see docs/handoff.md), /review [pr N|branch --base REF] to review a change with a read-only model turn and a fixed rubric, and /review post to post the last pull request review after you confirm (see docs/review.md), /btw <question> to ask the model a side question that never joins the conversation or the session record (see docs/side-question.md), /clear (or /new, /reset) to start a fresh session, /yolo to turn guardrails and the ask gate off together (/yolo off restores the settings-file values; the hazard rails still ask), /hazards to list the commands and files that always ask, whatever a rule or ask off says (see docs/hazard-rails.md), the session controls /guardrails, /ask, /caveman, /effort, /autocommit, /reasoning, /tools, /decisions, /tests and /jev (plus /mode, /model <provider> <model> and /lsp on|off), which change this session only and take the same commands from a web session (see docs/session-controls.md), /export to write the current session (or another by id) as Markdown under .vulnetix/exports, /rename to name the session, /sync to show or change session sync to the Vulnetix website (on, off, backfill; see docs/session-sync.md), /gitsync to switch the rebase of the session's branch onto origin's default branch before turns, which is on by default (on, off, global on, global off; see docs/git-sync.md), /rc to set up and run remote control, which lets the Vulnetix website start and drive sessions on this machine (start, stop, status; see docs/remote-control.md), /trusted to list the directories you trust, which remote control offers, and revoke stale or temporary ones or trust a new one (also f1 then d), /voice to dictate into the composer with a hold-to-talk key (status, debug, on, off, download, push, listen, insert, submit, cleanup; see docs/voice.md), /skills to list installed skills, /commands to list your custom slash commands (a Markdown prompt template in ~/.vulnetix/belai/commands/<name>.md, or the project's .vulnetix/belai/commands/, runs as /<name> args and takes $ARGUMENTS and $1; a built-in command always wins; see docs/library-items.md), /sandbox to see what commands may touch, /egress to see which hosts the sandbox's egress proxy lets a command reach and which it refused, and to allow one for this session (allow HOST, revoke HOST; see docs/sandbox.md), /mcp to list MCP servers and their tools, and /plugin to list, enable, disable or remove plugins (install them with belai plugin install; see docs/plugins.md). /help lists every command and keyboard shortcut. The popup matches fuzzily (/pmt finds /prompts) and also offers saved prompts as /prompt:<name> (loads it into the composer), agent profiles as /agent:<name> (switches to agent mode with that profile) and saved processes as /process:<name> (starts it unless it is already running, then shows its status).

Operator safety controls live in the footer: guardrails: on|off (posture gates) and ask: on|off (the permission-ask gate). f3 toggles guardrails, f4 toggles ask, and when both are off the two chips collapse into a single gold YOLO. These are explicit opt-ins: turning them off is announced in the transcript and traced under BELAI_TRACE. Guardrails off sets every posture gate to ignore across every surface — the agent loop, inline !cmd, @file attachments, background agents and the CLI — and the classifier is then not called at all rather than called and ignored, so a turn costs no extra requests. Sanitising is not part of the switch: delimiter markup is stripped either way. Next to them the footer always states caveman: on|off, so the voice rewrite (f2) can never be on without saying so.

In the composer, ctrl+left and ctrl+right move the cursor by word, crossing into the neighbouring line at a line boundary, and home/end (fn+left/fn+right) jump to the ends of the line. A word stops at punctuation, so foo.bar is three hops and foo_bar is one.

Shortcuts use ctrl, shift and the function-key row — never alt. alt chords are unreliable across terminals, and under the kitty keyboard protocol a ctrl+alt+<key> press is indistinguishable from ctrl+<key> by the time it reaches the UI, so it could never have worked. The session toggles are f2 caveman, f3 guardrails, f4 ask, f5 cycle mode, and f6 cycle reasoning effort — all five from any screen — f7 saves the prompt to the library from the composer, and ctrl+s saves the hovered panel, overwrites/deletes a loaded library prompt, or saves the prompt. If your terminal eats a function key, /caveman, /guardrails, /ask, /mode and /effort do the same jobs.

In agent mode a strip above the prompt lists the agents that can carry your turns — your own profiles, the background-agent definitions (↻), and the built-ins (◈). tab moves the highlight, enter engages, ctrl+g starts a ↻ definition in the background instead, and typing @name filters the strip. The engaged agent shows in the footer chip and carries every turn until you pick another or (none). An agent with a display name and palette (the built-in ones have both) is shown by that name, and the interface takes on its colours; ctrl+p cycles the agents and re-themes live. It applies to agent mode only: plan and goal mode run Belai's own logic and cannot be steered by an agent.

shift+tab cycles the mode: agent, plan, goal, then auto, where no mode is held and the classifier picks the mode and intent for each prompt. /handoff <plan file> runs a plan file as a handoff without that detection step; prompt admission, attachment classification and every permission gate still apply. Goal mode is the one that keeps going: instead of stopping when the tool budget runs out, an evaluator checks whether the work advanced and grants another pass while it does, tracking a todo list in a panel above the prompt. It is stopped by a stall, not a counter — press esc (or ctrl+c outside the UI) to stop it and keep the partial result. Set resilience.max_passes if you want a hard ceiling. The rules are in docs/role-manager.md.

For a single answer without the UI:

belai -prompt "what does internal/run do?"
belai -provider anthropic -model claude-sonnet-4-5 -prompt "review this diff"
Flag Meaning
-prompt send one turn, print the reply, exit; -prompt -, or a piped standard input with no -prompt, reads the prompt from standard input (see docs/headless.md)
-output-format with -prompt, how the run reports: text (the reply, default), json (one result object) or stream-json (one event per line, then the result); exit codes are 0 ok, 1 error, 2 usage, 3 run cap, 4 denied or could not ask, 5 schema failure (headless.output_format sets the default)
-json-schema with -prompt, require the final answer to be JSON that satisfies the schema in this file (a subset: type, properties, required, enum, items, additionalProperties); one repair turn, then exit 5
-provider openai, anthropic, cloudflare-workers-ai, cloudflare-ai-gateway, openrouter, google-gemini, ollama, llama-server, github-copilot, huggingface, kiro, groq, deepseek, fireworks, mistral, together, xai, moonshot, minimax, alibaba, a custom name from settings.json, or a configured display label
-model model id; each provider has a default
-effort thinking-effort level: low, medium, or high
-mode operating mode for -prompt: agent, plan, goal or code (default: classified from the prompt)
-plan start in plan mode (read-only)
-detect-mode report which operating mode the prompt selects
-agent start a background agent by name in foreground mode
-agent-create create an agent profile from a description and save to disk
-export export a session by id or unique id prefix as Markdown and exit
-caveman enable caveman voice rewrite for this run
-guardrails posture guardrails (default on); -guardrails=false turns every gate off for this run
-ask-permission the permission-ask gate (default on); -ask-permission=false resolves asks to allow
-firewall route LLM traffic through the active AI Firewall (see /firewall)
-tools enable tool execution (default on); -tools=false disables
-defer-tools advertise core tools in full and load the rest on demand with ToolSearch (default on); -defer-tools=false sends every tool definition on every request
-dangerously-yolo-everything ignore every posture gate
-allow-unsafe-tool-result ignore unsafe tool results
-allow-malformed-tool-result ignore malformed tool results
-allow-unsafe-prompt ignore unsafe prompt classification
-allow-malformed-prompt ignore malformed prompt classification
-tool-call-mismatch abort|strip|ignore tool-call mismatches
-allow-unpermitted-tools allow tool calls matching no permission rule (default)
-allow-ask-without-tty ignore ask-without-tty blocks
-allow-invalid-skills ignore invalid skill validation
-allow-invalid-hooks ignore invalid hook validation
-classifier-provider security-classifier provider (default: the main provider)
-classifier-model security-classifier model (default: the main model)
-classifier-effort security-classifier thinking effort (default: none)
-classifier-kind security-classifier stack: llm, models, openrouter-decisions or systemone (jev is read as systemone); default: models when the binary embeds a model, else llm
-classifier-phase1-model phase-1 prompt-saturation model id
-classifier-phase1-source phase-1 source: embedded or huggingface
-classifier-phase1-threshold phase-1 attack threshold (default 0.75)
-classifier-phase2-model phase-2 jailbreak model id
-classifier-phase2-source phase-2 source: embedded, huggingface, or disabled
-classifier-phase2-threshold phase-2 attack threshold (default 0.75)
-session-retention-days idle session retention in days (default 28)
-no-prune never prune idle sessions
-max-cost stop a run at this many US dollars of model spend (run_caps.max_cost_usd); the model is told to finalise at 90%, then the run ends unless you extend it; 0 is off
-max-tokens stop a run at this many tokens (run_caps.max_tokens)
-max-wall stop a run after this many minutes spent running turns (run_caps.max_wall_minutes); a -prompt run stopped by a cap exits with code 3
-no-transcript with -prompt, do not keep a session transcript of the run
-no-finish-review do not run the finish review of the working tree before a goal is accepted as complete (finish_review.enabled in settings; BELAI_FINISH_REVIEW=0; /finish-review in the TUI; see docs/finish-review.md)
-no-checkpoints keep no copies of files before an edit changes them, so /undo and /rewind have nothing to restore (checkpoints.enabled in settings; see docs/checkpoints.md)
-lint-loop run the repository's detected linter on each file after an edit and return its findings on the tool result (lint_loop.enabled in settings; BELAI_LINT_LOOP=1; /lint-loop in the TUI; see docs/lint.md)
-no-git-sync do not rebase the branch onto origin's default branch before a turn (git.sync in settings; /gitsync in the TUI)
-resume, -r resume a session by id or unique id prefix in the interactive TUI; with -prompt, replay it and run the prompt as its next turn
-continue, -c continue the most recent session for the current project
-teleport continue a session of your account from another host, sandbox or the web: it opens here under a new id and the original keeps running (see docs/teleport.md)
-teleport-push with -teleport, agree that the origin host may push the session's uncommitted and unpushed changes to the forge as one belai/teleport/<id> branch, which this host then fetches; without it they are sent as a patch and replayed here
-teleport-ref with -teleport, check out this ref or commit instead of the one the session was at
-verbose print mode and security decisions to stderr
-usage-json with -prompt, write a JSON summary of the run's token usage (per role, per model, cache reads and writes, request composition) to a file on exit — see docs/benchmarks.md
-version print the version and exit
-trust-dir trust the current directory without prompting (grants the directory only, not its proposed workspace dirs)

Belai starts the UI only when both stdin and stdout are a terminal, so it is safe in pipelines and CI.

Run belai -help for every command, a set of examples and the flags grouped by purpose. belai help <command> (or belai <command> -h) prints one command's own usage. Subcommands sit beside the flags: belai acp serves the Agent Client Protocol so an editor such as Zed can use Belai as its agent (docs/acp.md), belai agent lists, runs and manages background agents and fleet workers (docs/fleet.md), belai kanban reads and edits the global board (docs/kanban.md), belai review reviews the working tree, a branch or a pull request against a fixed rubric and prints findings the harness checked against the diff (docs/review.md), belai worktree files, lists, updates and removes the git worktrees the harness made for board agents (docs/worktrees.md), belai library scan searches this host for commands, skills, prompts, agents and instruction documents kept by Claude Code, Cursor, Codex and the other harnesses in the registry, and says what importing each would do (docs/agent-import.md), belai login kiro signs in to Kiro (docs/kiro.md), belai plugin installs and manages plugins (docs/plugins.md), and belai rc runs remote control so the Vulnetix website can start sessions here (docs/remote-control.md).

Configuration

No provider configured? Belai defaults to OpenRouter's free router (openrouter / openrouter/free). Sign up at https://openrouter.ai/, where a one-time signup credit unlocks the free model, then store the key with /providers (or export OPENROUTER_API_KEY). If exactly one other provider's credentials resolve, Belai adopts that provider instead.

Set the API key for your provider and Belai picks it up:

Provider Environment
openai OPENAI_API_KEY
anthropic ANTHROPIC_API_KEY
cloudflare-workers-ai CLOUDFLARE_API_KEY, CLOUDFLARE_ACCOUNT_ID
cloudflare-ai-gateway CF_AIG_TOKEN, CF_ACCOUNT_ID (or CLOUDFLARE_ACCOUNT_ID). Optional: CF_AIG_URL
openrouter OPENROUTER_API_KEY
google-gemini GEMINI_API_KEY or GOOGLE_API_KEY
ollama none (local; honours OLLAMA_HOST)
llama-server none (local; honours BELAI_LLAMA_HOST, default http://localhost:8080/v1)
github-copilot GITHUB_COPILOT_TOKEN or GH_TOKEN (OAuth, exchanged for a session token)
huggingface HF_TOKEN or HUGGINGFACE_TOKEN
kiro none: run belai login kiro (AWS Builder ID or IAM Identity Center sign-in; see Kiro). KIRO_LOGIN holds a stored login
groq GROQ_API_KEY
deepseek DEEPSEEK_API_KEY
fireworks FIREWORKS_API_KEY
mistral MISTRAL_API_KEY
together TOGETHER_API_KEY
xai XAI_API_KEY
moonshot MOONSHOT_API_KEY; optional BELAI_MOONSHOT_BASE_URL for .cn
minimax MINIMAX_API_KEY; optional BELAI_MINIMAX_BASE_URL for .cn
alibaba DASHSCOPE_API_KEY or ALIBABA_API_KEY

A custom provider defined in settings.json resolves its key from its api_key_env variable or BELAI_<NAME>_API_KEY.

/providers in the UI opens the provider list where you can store them instead, in your host keychain or in ~/.vulnetix/belai/credentials.json. Belai resolves credentials from the environment first, then a project-local .vulnetix/belai/credentials.json, then the user file, then ~/.netrc, then the keychain — and tells you which one each value came from. A credential may be stored as the name of an environment variable rather than a value, which is how project credential files stay committable.

Pick a default provider without passing -provider every time by setting BELAI_PROVIDER.

For a custom state directory, set BELAI_HOME. To disable the TUI and keep the old one-shot behaviour, set BELAI_NO_TUI=1 or run in CI (CI is honoured). To stop Belai opening a web browser (it already skips this over SSH, without a graphical session, and in belai rc), set BELAI_NO_BROWSER=1.

Belai creates ~/.vulnetix/belai/ at mode 0700 and credential files at mode 0600. If you already have a ~/.belai/ directory, it is migrated automatically on the next run. We recommend adding .vulnetix/belai/ to your repository .gitignore — even though the project file only holds references, defence in depth is cheap.

Settings files

Declared intent lives in settings.json; last-used runtime values live in state.json. The global file is ~/.vulnetix/belai/settings.json (or $BELAI_HOME/settings.json); the project file is <workdir>/.vulnetix/settings.json. Precedence, lowest to highest: defaults < state.json < global settings.json < project settings.json < environment < CLI flags. The /settings browser shows the effective value and its provenance for every key. From /model the agent provider/model/effort can be written to session scope (state.json), global scope, or project scope, following the same precedence rules.

Key Meaning
provider default provider name
model default model id
effort default thinking effort: low, medium, high
caveman toggle the caveman voice rewrite
permissions structured allow / ask / deny tool rule arrays
session_retention_days idle session retention (default 28)
ui.banner / ui.status_bar TUI presentation toggles
show_session_names show session names in the status bar (default on)
update_check check GitHub for a newer Belai release at startup (default on)
tests the post-end test pass: post_end (off default, goal, goal_plan, session), command (an argv that replaces the detected suites), scope (affected default or full), on_fail (fix default, diagnose or off), max_fix_passes (default 3), timeout_seconds (default 300), report (default on) and each_turn (default off; also runs the suites after a plain TUI agent turn that edited files). A completed goal, plan or session runs the suites, a passing run gets a fast-model report and a failing run a bounded diagnose-and-fix loop. A project may only turn it off (each_turn included) and lower the budgets, never on — see docs/testing.md
defer_tools advertise the core tools in full and load the rest (native catalogue, cloud CLIs, repo and agent-store tools, MCP tools) on demand with ToolSearch, keeping every request small (default on; -defer-tools=false for one run). Deferred tools stay callable by name. ToolSearch also finds installed skills, and with a decision backend it ranks its matches and the request's likely tools and skills are ready before the model asks (see docs/jev-jobs.md)
offload keep oversized tool output out of the context: a head-and-tail preview stays inline and ReadResult reads the rest; enabled (default on), threshold_tokens (default 4000), preview_tokens (default 1500) — see docs/context-offload.md
web_fetch what a session keeps of the pages it fetched: cache (serve a repeat WebFetch from memory, still sanitised and classified; default on), cache_ttl_seconds (default 900), index (an in-memory index so SearchFetched can search earlier pages without fetching them again; default on) and index_tokens (default 60000) — see docs/web-fetch.md
jev relevance jobs that use a decision backend: jobs.<job> switches (bash swap, compaction prune, tool selection and search, LSP triage, option order, explore locate, voice command, request scale and goal judge; default on, and only while a decision backend is the classifier, when /settings shows them) locate_previews and thresholds (the score cut-offs of the security gate and each job, defaults 0.10 allow and 0.90 deny); a project may only turn a job off, and cannot set thresholds — see docs/jev-jobs.md
voice speech input to the composer: enabled (on by default when the speech model is built into the binary, as in every release family except the plain belai build; otherwise off), mode (push_to_talk default or listen), delivery (insert default or submit), cleanup (fast-model tidy-up streamed over the words as you speak, default on), log (voice notices and cleanup rows in the thread, default on), wake_word (act only on speech that starts with "Hey, Belay", needs listen, default off), commands (spoken keywords such as stop, option 2, submit, skip, approve and deny, plus Jev voice command, default on), key (f11 default; ctrl+space if your terminal keeps it) and device. /voice debug checks the microphone, shows a live level meter, the raw and tidied transcripts and a key event log. The speech model is built into release binaries and run as pure Go (a build without it downloads 32 MB after /voice download); a capture helper (parecord, arecord, ffmpeg or sox) supplies the audio. Per-user only: a project file cannot turn it on — see docs/voice.md
tts read replies aloud with a player card in the thread: enabled and consented (off by default; /tts on is the agreement to send the text to Microsoft's read-aloud service), read_reports (read each turn's final reply, default off), voice (default en-US-AndrewMultilingualNeural), speed (0.5 to 3, default 1) and cache_mb (the local audio cache for replay, default 256, 0 off); ctrl+b reads the reply under the pointer or the last one, and saying "stop" stops it; the project layer cannot set it - see docs/tts.md
knowledge sizes of the retrieval store behind agent profile documents and the project's .vulnetix output, which Grep, Glob and Read search by meaning: max_index_tokens (per profile, default 200000), max_project_tokens (the project, default 200000) and max_result_tokens (one search, default 3000); the project layer cannot set it - see docs/knowledge.md
compaction when the context compacts and how much a prune keeps: threshold_pct (default 70), keep_recent_turns (6), keep_result_chars (300), keep_tool_results (2), and a circuit breaker for a failing summary, breaker_failures (3) and breaker_cooldown_passes (4). Your layers set any key in range; a project may only compact sooner or keep more. /compact <focus> steers one summary and /compact status shows the numbers and the breaker — see docs/compaction.md
headless the contract of a -prompt run with its caller: output_format (text, json or stream-json, default text), max_stdin_bytes (a prompt read from standard input, default 1048576) and event_text_cap (bytes of text in one event, default 4096); the project layer cannot set it - see docs/headless.md
finish_review the check of the working tree before a goal is accepted as complete: enabled (on), max_rounds (2, 0 to 5, how many times a completion is sent back) and scratch_patterns (extra scratch file names). Your layers and flags set any key; a project may only tighten it. /finish-review shows and switches it; see docs/finish-review.md
prompt_cache the provider cache: ttl (5m default, or 1h), keepalive_max (pings per idle stretch, 0 to 100, default 0 = off) and keepalive_every_s (60 to 3500, default 270 with 5m, 3300 with 1h). Your layers only; the project layer cannot set it. /cache shows and changes it; see docs/prompt-cache.md
commit_mode, commit_draft, commit_exclude the commit workflow: commit_mode (off default, ask or auto) commits the files a completed goal changed from a clean state, commit_draft (off) lets a model write the words of the message from file names and line counts, and commit_exclude lists path globs never committed. Your layers only for the first two; a project may add commit_exclude patterns, never remove one. auto_commit_per_task: true still means auto. /commit commits on request; see docs/commit-workflow.md
checkpoints file checkpoints: enabled (on), max_per_session (50, 1 to 500), max_bytes (200 MiB, 1 MiB to 10 GiB) and retain_days (7, 1 to 365). Your layers and flags set any key; a project may only turn them off. /undo and /rewind restore; see docs/checkpoints.md
symbol_index the symbol index behind the Outline and Symbol tools: enabled (on), max_files (2000, 100 to 10000), max_results (50, 1 to 200) and refresh_secs (60, 5 to 3600). Your layers set any key; a project may only turn it off. /symbols shows it; see docs/symbol-map.md
lint_loop the per-edit lint loop: enabled (off), timeout_ms (10000, 1000 to 60000), max_attempts (3, 1 to 6) and max_rows (10, 1 to 20). Your layers and flags turn it on and set any key; a project may only turn it off or lower a number. /lint-loop shows and switches it; see docs/lint.md
run_caps ceilings on one session: max_cost_usd, max_tokens and max_wall_minutes (all off by default), finalize_at (0.9, the share at which the model is told to wrap up) and max_extends (3, how many times you may extend a reached cap). Your layers and flags set any key; a project may only add a cap or lower one. /caps shows and extends them; see docs/run-caps.md
instructions how AGENTS.md and CLAUDE.md reach agent turns: enabled (default on), nested (a file in a subdirectory arrives when a tool touches a path beneath it, default on), max_files (12), file_bytes (49152) and total_bytes (131072). Your layers set any key in range; a project may only turn a switch off or lower a number — see docs/instruction-files.md
review /review and belai review, a code review turn: enabled (default on), max_diff_kb (400, clamped to 16 through 2048), post (ask default, deny or allow: whether a review may be posted to a pull request, and whether the command line may do it without a question) and rubric_file (your own extra criteria). Your layers set any key; a project may only turn the review off, lower max_diff_kb or set post to deny; see docs/review.md
worktrees worktree sessions: setup and teardown (lists of argv arrays, none by default), timeout_seconds (120, 1 to 600), update_mode (off default, merge or rebase), keep_on_exit (default on) and network (deny default or allow). Every key is yours: a project file can only set update_mode to off. See docs/worktrees.md
side /btw, a side question answered off the main history: enabled (default on), history_turns (6, clamped to 0 through 20) and max_reply_bytes (4096, clamped to 512 through 8192). Your layers set any key; a project may only turn /btw off or lower a limit; see docs/side-question.md
handoff /relay, the reviewed handoff into a fresh session: enabled (default on), max_bytes (16384, clamped to 2048 through 65536) and include_goal (default on, gives the drafter the goal and todo list). Your layers set any key; a project may only turn /relay off, turn include_goal off or lower max_bytes; see docs/handoff.md
edit tolerant (default on): Edit applies a whitespace-only relaxed match (line endings, trailing spaces and tabs, one consistent indentation shift) when exactly one place matches, and names the closest lines when none does. false matches byte for byte. A project may only turn it off — see docs/edit.md
patch_tool, patch_tolerance apply_patch changes several files in one atomic call. patch_tool (default on) offers it; patch_tolerance is how loosely a hunk may match: strict, whitespace (default) or unicode (also folds curly quotes, dashes and wide spaces). A project may only turn the tool off or move toward strict. /patch-tolerance sets the level for one session — see docs/patch-tool.md
kanban the global kanban board: tools, wrap-up, composer pane, /kanban and sync (default on; a project may only turn it off) — see docs/kanban.md
agents fleet workers: enabled (default on), max_workers (default 4), factory_reserved_workers (slots only Software Factory dispatches may fill, default 0, global only), max_candidates (default 4, the ceiling on a profile's best-of-N kanban.candidates), publish (default on); a project may only turn them off or lower a cap
token_budgets global only: token allowances per provider, model and scope (session, day, month) — see Token budgets
ui.budget_cycle_seconds seconds the footer shows each budget of the selected model before cycling (default 10, minimum 2)
ui.budget_warn print a warning line on each call to the selected model while one of its budgets is amber or red (default off)
ui.intel session intelligence: the footer slot, the intel tab (f12) and /intel (default on) — see Token budgets
ui.mac_key_hints add the Mac form after an F key in hints and /help, such as f9 (Fn+⏭) (unset follows the platform: on for macOS, off elsewhere; display only)
ui.clipboard_images let ctrl+v and /paste-image attach an image from the clipboard (default on; a project file can turn it off, never on) — see Images
ui.reply_cost print tokens in and out, the cache share and an estimated cost after each agent-role model call (default off; a project file can only turn it off) — see Context and reply cost
intel.plan_limits global only: read provider rate-limit response headers into plan-limit readings (default on)
context_windows per-model context-window overrides, in tokens
providers custom provider profiles (see below)
provider_labels display labels keyed by provider name (see below)
allow_project_providers opt in to project-layer providers (default off)
resilience.max_agents fan-out ceiling for explore subagents + background agents (default 15)
resilience.plan_explore survey the repository before the first planning pass (default off)
resilience.goal_explore survey a goal with references before its first pass (default off)

Custom providers. A providers block defines a provider by name, with base_url, api (openai-chat, openai-responses, or anthropic-messages), optional auth (bearer, x-api-key, or cf-aig), optional api_key_env, and a models catalogue. Secrets never live here; a profile references the key via api_key_env or the credential backends. Project-layer providers blocks are ignored unless the global settings set allow_project_providers: true, because a hostile repo defining a provider is an API-key exfiltration primitive.

A profile may also carry a kind (ollama, llama-server, or openai-compatible) that templates it from a built-in descriptor, plus protocol / host / port for the editor and the default display label. base_url stays the authoritative wire value. A custom provider's API key is always optional: with no key stored, Belai offers the provider when its /models endpoint answers (the same liveness probe local servers get), so a keyless self-hosted OpenAI-compatible endpoint works with no credential at all. Add as many named instances as you need — a second Ollama, a second llama-server — and give each a friendly name in provider_labels, which the TUI shows in /providers, /model and the footer and accepts as a -provider selector:

{
  "providers": {
    "ollama-gpu": {
      "base_url": "http://localhost:11435/v1",
      "api": "openai-chat",
      "auth": "bearer",
      "kind": "ollama",
      "protocol": "http",
      "host": "localhost",
      "port": "11435"
    }
  },
  "provider_labels": {
    "ollama-gpu": "GPU Ollama"
  }
}

Permissions merge is a union, never a replacement. A project file can add rules but can never remove a rule you set globally, and a deny from either scope wins.

Bash permissions. Because Bash is a registered tool, permission rules use the same Tool(spec) shape as other tools. Common rules include Bash(git status *), Bash(git diff *), Bash(ls *), and Bash(echo *). Under the read-only master switch Bash executes without a shell, so pipes, redirections, and command substitution are rejected structurally. The legacy flat-map form ("bash": "ask") is still accepted on read but files self-upgrade to the structured form on first write.

Bash is unavailable in plan mode. Plan mode neither advertises nor executes it, read-only or otherwise — investigation there goes through Read, Grep, Glob, Cd, and the native read-only tools, whose argument shapes are fixed.

What goes to the security classifier. Every tool result is sanitized. Results whose content is arbitrary are classified on top of that: Bash (an arbitrary command), WebFetch and WebSearch (text written off your machine), and Read (a file's bytes). The tools whose output shape the harness already knows — Grep, Glob, Write, Edit, and the native read-only tools — are sanitized and promoted directly. See docs/architecture.md.

Outbound requests identify themselves. Provider calls, WebFetch and WebSearch send User-Agent: belai/<version> plus X-Belai-Session-Id, X-Belai-Tool, X-Belai-Tool-Call-Id, X-Belai-Client-Version, X-Belai-Client-Build and a W3C traceparent. Tool subprocesses get the same identity as BELAI_* and TRACEPARENT environment variables. The session id is sent as-is, so any site you fetch can see it. See docs/architecture.md.

Path rules are relative to the working directory. Tools share one working directory that starts at the session root and can move within it with Cd. A path beginning with / means the session root; anything else is relative to the current working directory. Nothing reaches outside the root either way — a move changes how a path is spelled, never what it can reach — and the TUI footer shows where the session currently is.

Session storage

The TUI writes an append-only JSONL session per workdir under ~/.vulnetix/belai/sessions/. /compact never mutates the old file — it writes a new session whose root entry links meta.parent_session to the old id, and carries the old name forward. /clear starts a new session and leaves the previous one on disk untouched.

Quitting (ctrl+d twice, /exit, or its alias /quit) prints a branded exit card below the restored shell prompt: the session's display name, turn/duration/token facts, its on-disk path, and the exact belai --resume <id> command that returns to it. belai --continue (-c) reopens the most recent session for the current project without remembering an id. belai -teleport <id> continues a session that ran on another host, in a Pix Sandbox or on the web, in the checkout you are in, under a new id (docs/teleport.md); every session card on the Vulnetix website copies that command.

Documentation

Highlights — full reference in docs/README.md:

Releases

Packages

Used by

Contributors

Languages