Extending Vis
An extension adds Python tools, user commands or integrations to Vis. Start with one file and one tool; add a package only when you need dependencies, reusable code or distribution. You can write it yourself or ask Vis to build it.
Choose what you need
| I want to… | Use | Read next |
|---|---|---|
| Let the agent call Python code or an external service | A tool, declared with vis.Symbol |
Your first extension |
Add a command a person types, such as /hello |
vis.SlashCommand |
Slash commands |
| Explain when to use an extension's tools | The extension's short prompt |
Prompts and discovery |
| Describe a reusable, multi-step procedure | A skill (SKILL.md); no Python extension required |
Skills |
| Check tool calls before they run | vis.OpHook |
Op hooks |
| Ask for input or display progress | vis.ask or vis.live inside a tool |
Forms · Live views |
| Add an LLM service with custom authentication | vis.Provider |
Provider extensions |
Tools do work; prompts and skills explain when or how to use them. Instructions alone do not register a callable, run a procedure or authorize its side effects.
Your first extension
Before you start: install Vis and open a project you trust. This example uses only Python's standard library and the SDK supplied by Vis: no pip install, uv, manifest or separate virtual environment is needed.
Trust boundary: extension code runs with your user's permissions, outside the model's jail. Review code and dependencies before loading them, including files already present in an unfamiliar checkout.
1. Create the entry file
In your project, create .vis/extensions/ if needed, then save this complete file
as .vis/extensions/greeting_tools.py:
# .vis/extensions/greeting_tools.py
from __future__ import annotations
import blockether.vis.extension as vis
def hello(name: str, *, uppercase: bool = False) -> str:
"""Greet one person without sending a message.
name must be nonblank; surrounding whitespace is removed.
uppercase defaults to False, preserving the recipient's capitalization.
Raises ValueError for a blank name. Does not modify stored state.
"""
if not name.strip():
raise ValueError("name must not be blank")
text = f"Hello, {name.strip()}!"
return text.upper() if uppercase else text
vis.register(
vis.Extension(
name="greeting",
description="Generate greetings without sending messages.",
alias="greeting",
symbols=[vis.Symbol(hello)],
)
)
The filename identifies the entry file; name identifies the extension;
Symbol(hello) exposes the callable as hello. alias does not add a prefix.
Keep entry filenames different from the packages they import.
2. Load it
Start Vis in that project, or type /reload in an existing session. Reloaded tools
become available at the next turn boundary. If loading fails, run
vis-agent doctor in your terminal and follow troubleshooting.
3. Discover and call it
Ask Vis: “Use the hello tool to greet Ada, first normally and then in uppercase.”
The agent can inspect and call it in python_execution:
print(apropos(r"^hello$"))
print(doc("hello"))
print(await hello("Ada"))
print(await hello("Ada", uppercase=True))
The two calls print Hello, Ada! and HELLO, ADA!. apropos() filters public
names, not descriptions. doc() gives the complete tool documentation.
hello.contract exposes the same description as structured data when needed.
A displayed default of ... means an optional argument's value was withheld,
not that you should pass Ellipsis. Omit the argument to use its real default.
The docstring explicitly explains the public False default in this example.
See documenting defaults.
4. Check an edit
Change the greeting text, run /reload, and ask for another greeting on the next
turn. Check both the result and doc("hello"). If a reload fails, Vis retains the
last working version and marks it stale; fix the error before testing the edit.
Registration or a successful reload alone does not prove that a tool call works.
Ask Vis to build an extension
Describe the task, inputs, expected result and permitted side effects. For example:
Build a project-local Vis extension that lists this project's open GitHub issues.
Read doc("extending") first. Reuse the existing authenticated gh CLI.
Return typed issue summaries; do not create or modify issues.
Choose the smallest suitable layout. Test the implementation and a registered
call, explain omitted arguments, and tell me which reload steps remain.
Do not publish or install it globally.
When implementing a requested extension:
- Check whether an existing tool or skill already covers the task.
- Choose one file for a small integration, an existing Python package for reusable logic, or a distributable package when sharing it is part of the request.
- Register once; annotate tools and document inputs, defaults, results and failures.
- Test ordinary Python behavior, then discovery and a real tool call in Vis.
- Report any untested boundary or required reload. Do not substitute registration success for execution or add publishing steps the user did not request.
Continue by task
| Task | Guide available on the site and through doc(name) |
|---|---|
| Design useful tools, typed results and tests | Extension design · doc("extension-design") |
| Install, reload, remove or share a package | Installing and sharing extensions · doc("extension-packages") |
| Connect an existing editable uv project | Using an existing Python project · doc("extension-development") |
| Look up declarations, defaults, callbacks and host operations | Extension API · doc("extension-api") |
| Diagnose a missing tool, stale result or import error | Extension troubleshooting · doc("extension-troubleshooting") |
See also
- Extension design — move beyond the one-file example.
- Installing and sharing extensions — locations, dependencies and distribution.
