Context files & prompts
Vis reads plain markdown files to learn your rules: context files
(AGENTS.md / CLAUDE.md) for project conventions, system prompt files
(SYSTEM.md / APPEND_SYSTEM.md) to change the core prompt itself, and
prompt templates (.vis/prompts/*.md) for reusable slash-invokable
prompts. All of them auto-refresh: Vis stat-checks the files each turn and
re-reads only when something changed — no restart, no manual reload.
Context files: AGENTS.md / CLAUDE.md
Vis stacks guidance from three layers into every turn's
PROJECT-INSTRUCTIONS system block, outermost first:
- User-global —
~/.vis/AGENTS.md(or~/.vis/CLAUDE.md): your personal house rules, applied in every project. - Ancestor directories —
AGENTS.md/CLAUDE.mdin every parent directory of the workspace root, walking down from the filesystem root. In a monorepo this means the repo-rootAGENTS.mdand the subproject'sAGENTS.mdboth apply. - Workspace root —
AGENTS.md/CLAUDE.mdin the directory you opened.
Per directory the precedence is strict: AGENTS.md wins, CLAUDE.md is only
read when AGENTS.md is absent there. Across layers nothing is dropped —
nearer files render later, so on conflict the more specific rules override
the outer ones (and the CORE engine contract always wins over both).
Files are inlined verbatim, untruncated; each one is labeled with its origin and path so the model knows which layer a rule came from. Provider prompt caching amortizes the cost across the session.
System prompt files: SYSTEM.md / APPEND_SYSTEM.md
To change the core system prompt itself (not project rules), drop markdown
files under a .vis/ directory:
| File | Effect |
|---|---|
<project>/.vis/SYSTEM.md |
Replaces the core system prompt (project) |
~/.vis/SYSTEM.md |
Replaces the core system prompt (global) |
~/.vis/APPEND_SYSTEM.md |
Appends to the system prompt (global) |
<project>/.vis/APPEND_SYSTEM.md |
Appends to the system prompt (project, rendered last) |
Replacement precedence: project SYSTEM.md > global SYSTEM.md > the
config system_prompt: {is_replace: true} form > the built-in core prompt.
Append files always apply, global first, project last — the nearest text sits
closest to the conversation. The system_prompt string form (see
Configuration) still works and is appended before the
APPEND_SYSTEM.md files.
Replacing the core prompt is a sharp tool — Vis's prompt teaches the whole
tool surface (file tools, python_execution, structural editors). Prefer
APPEND_SYSTEM.md or AGENTS.md unless you know you want a full rewrite.
Prompt templates: .vis/prompts/*.md
A prompt template is a markdown file that expands into a user message when you
type /<name> [arguments] in any channel (TUI, CLI):
<project>/.vis/prompts/*.md— project templates (win name collisions)~/.vis/prompts/*.md— user-global templates
The template name is the filename stem, overridable with frontmatter; a
description documents it:
---
description: Review a PR branch against main
---
Review the current branch against main. Focus on: $ARGUMENTS
- Correctness first, style second.
- End with a verdict: approve / request changes.
Typing /review error handling expands the file and runs it as a normal LLM
turn. Argument handling:
- Every
$ARGUMENTSoccurrence is substituted with the raw argument string (empty when none given). - A body without
$ARGUMENTSgets non-blank arguments appended as a trailing paragraph.
Registered slash commands (from extensions) always win over templates — a
template only fires for a /name no extension claimed.
Skill invocations: /<name>
Every discovered skill is also exposed as a dynamic template
named <name>, so you can load a skill explicitly instead of waiting
for the model to pick it:
/setup-pre-commit # load the skill, follow its instructions
/setup-pre-commit for husky # load it with a task appended
The expansion explicitly injects the full SKILL.md plus its bundled resource
paths into that user message. The model-facing skill(name) verb is
cache-aware: it returns a full body once while live, then compact
already-active receipts until the body leaves the provider tape or changes.
Shell shortcuts: ! and !&
A line that starts with ! is a shell escape — the command runs directly,
without an LLM round-trip, so it's instant and costs no tokens. It's the
shell analogue of /slash, and works the same way in the TUI and web
channels:
!git status # run synchronously, foreground (shell)
!&npm run dev # run in the background (shell op "background"), returns immediately
!<cmd>invokes the run operation, equivalent to Pythonawait shell(cmd), and blocks until the command exits. Use it for short bounded commands.!&<cmd>invokesawait shell(cmd, {"op": "background", "id": …})under an auto-generated resource id (background-<hex>) and returns right away. Prefer it for commands that may take a while: builds, test suites, servers, watchers, and interactive processes. Read output withawait shell({"op": "logs", "id": id})and stop it withawait shell({"op": "stop", "id": id}).- A bare
!(or!&) with no command is ordinary prose and runs as a normal LLM turn.
While you're typing a shell shortcut, the composer flags it visually so a
shell command never looks like an ordinary message — with no layout shift.
The TUI tints just the leading !/!& marker in the shell tool color; the web
composer tints its frame and shows a small shell / shell & pill in the
corner. The cue only lights up once a real command follows the marker (a bare
!/!& stays neutral), mirroring the run/no-run rule above.
The command output renders as the turn's answer bubble and is persisted in the
transcript. Crucially, the result lands in context exactly like a
model-issued shell call — so a later turn can reason over
what the command printed, just as if the model had run it itself.
Both shortcuts require the shell layer to be enabled — the user-owned
shell toggle (settings dialog → Shell commands). When it's off, the
shortcut refuses cleanly with a note on how to
turn it on, and nothing runs.
