Extension Center
Find extensions by tag or choose Official releases. Review the source and install an approved version.
Explore extensions
8 extensions
spel
Officialblockether · v0.1.21
Spel browser automation with verified installation, reserved sessions, CDP, JavaScript, snapshots and screenshots.
vis-decisions
Officialblockether · v0.6.1
Train, resume, validate and publish Laya, GLiNER, Decision 2.0 and D3 decision models in Vis.
vis-graphify
blockether · v0.15.3
Code knowledge graphs for Vis with graphify: search, change impact, exact references, safe renames and moves, domain flows, and a knowledge base server with ontology, documents and undoable edits.
vis-lang-clojure
Officialblockether · v1.17.3
Clojure tools for Vis: zprint and cljfmt formatting, clj-kondo lint, reader syntax checks, test runs and an nREPL.
vis-lang-interface
Officialblockether · v2.11.3
The shared contract for Vis language extensions.
vis-lang-python
Officialblockether · v1.14.0
Python tools for Vis: ruff formatting and lint, pytest runs and a managed project REPL.
vis-optmem
Officialblockether · v0.6.0
Permanent memory for Vis agents, based on OptMem by Victor Taelin.
vis-podcaster
blockether · v0.1.0
Two-host podcasts with Vis speech engines, ASR-verified takes and quality gates.
blockether/vis-lang-interface
OfficialThe shared contract for Vis language extensions.
Install
Only approved releases are listed. Each version keeps its reviewed commit. You are viewing a different version from the catalog default.
Approved releases (22)
- 2.11.3Oct 22, 20269c9f9c5856d0Release notes
- 2.11.2Oct 16, 2026ca053435b261Release notes
- 2.11.1Oct 14, 2026bb9e54b59e6cRelease notes
- 2.11.0Oct 8, 2026dcd5bb70e561Release notes
- 2.9.1Oct 5, 20264569671e308fRelease notes
- 2.9.0Oct 5, 2026ada0a11dc839Release notes
- 2.8.0Oct 5, 2026675e25ddb645Release notes
- 2.7.0Oct 4, 202645d872fa5e50Release notes
- 2.6.2Oct 2, 20261b00fc79a655Release notes
- 2.6.1Oct 2, 2026af10c5fac538Release notes
- 2.6.0Oct 2, 20265f9597db284cRelease notes
- 2.5.0Oct 2, 2026ad463e6603ccRelease notes
- 2.4.0Sep 28, 20267d9ca812afdbRelease notes
- 2.3.0Sep 28, 2026bc0ba930a0fbRelease notes
- 2.2.0Sep 27, 202614a55f773c7eRelease notes
- 2.1.2Sep 23, 2026e36d837ccfa5Release notes
- 2.1.1Sep 21, 202665c5590b2d24Release notes
- 2.1.0Sep 21, 2026abe830d942b4Release notes
- 2.0.2Sep 21, 20263aafafc419d9Release notes
- 2.0.0Sep 20, 202642b00cd0628cRelease notes
- 1.0.1Sep 20, 20267e84e94a4f31Release notes
- 1.0.0Sep 20, 20263075962b8e4fRelease notes
This command selects version 2.6.1 at its approved commit. It never follows a moving tag or branch.
Project: run from your project directory to install in .vis/extensions. This command does not add an extensions entry to vis.yml. Add --save to also record the installation. Save an installation in configuration to share the setup.
Review the source first. --trust allows extension code and build backends to run with your permissions.
vis-agent extension install \
'blockether/vis-lang-interface' \
--version '2.6.1' \
--project \
--trustThen start Vis or use /reload. Dependencies are prepared automatically.
Already installed? Check, update or roll back
vis-agent extension versions 'blockether/vis-lang-interface'
vis-agent extension update 'blockether/vis-lang-interface' --trust
vis-agent extension rollback 'blockether/vis-lang-interface' --trustUse the identifier shown above, not the Python package name. Add --project for a project installation. Update selects the latest approved stable version; rollback restores the previous pinned source. Choose a specific version and read the lifecycle contract.
Project details
- Version
- 2.6.1
- Extension
- blockether/vis-lang-interface
- Project folder
- Repository root
- License
- Apache-2.0
- GitHub stars
- 0
- Downloads
- 1201
- Updated
- Oct 2, 2026
- GitHub owner
- Blockether
- Reviewed commit
- af10c5fac538
- Metadata checked
- Oct 2, 2026
Dependencies
Python >=3.11
- vis-agent>=0.2.26
README
vis-lang-interface
The shared contract for Vis language extensions, written in Python.
Vis knows nothing about programming languages. A language extension runs that language's own
toolchain and hands the model back a result; this package is what those results look like, so
clj.lint_code and py.lint_code read the same way and one presentation renders both.
Building a language extension on it
Add the package as a dependency of your extension and return its result types:
dependencies = [
"vis-agent>=0.2.26",
"vis-lang-interface @ git+https://github.com/Blockether/vis-lang-interface@v2.6.0",
]
from vis_lang_interface import LintResult, process
def lint(paths):
done = process.run(["ruff", "check", "--output-format=json", *paths])
return LintResult.of("python", findings_of(done.out), files=len(paths))
| Module | What it gives you |
|---|---|
vis_lang_interface.results |
Diagnostic, FormatResult, LintResult, SyntaxResult, TestResult, TestFailure, BuildResult, BuildArtifact, ReplResult, ReplSession |
vis_lang_interface.changes |
line_changes — the lines an edit added and removed, as git diff --minimal --numstat counts them |
vis_lang_interface.process |
run, spawn, tool_path, ToolRun, ToolMissing, ToolTimeout |
vis_lang_interface.runtime |
start, Runtime, Rendezvous, RuntimeGone — a runtime that stays alive between calls |
vis_lang_interface.project |
project_root, source_files |
vis_lang_interface.presentation |
Activity rendering every language binding shares: a pass or fail verdict for lint, syntax and test runs, changed-line counts for formatting, and an evaluation's code, output, error and value |
vis_lang_interface.syntax |
SyntaxGuard — refuses a patch that makes a file unparseable and reports Python writes that did |
vis_lang_interface.prompt |
routing — the block that tells the model your verbs exist |
Count what formatting changed
FormatResult.lines_added and lines_removed say how much formatting changed. Count them with
line_changes, so every extension reports the numbers git diff --minimal --numstat gives:
from vis_lang_interface import FormatResult, line_changes
added, removed = line_changes(before, formatted)
result = FormatResult(
"python", (path,), (), is_written=True, lines_added=added, lines_removed=removed
)
A line counts with its newline: a changed line counts once as removed and once as added, and a last line that only gains or loses its newline counts as changed. A source file of a few thousand lines takes about a millisecond. A changed region of tens of thousands of lines that exact counting cannot settle in about half a second is matched through the lines that repeat least; its counts can then exceed the fewest, never fall below them.
Tell the model your verbs exist
Vis prints one section per active extension into the system prompt. An extension that contributes
none still works, but the model has to discover it first, so it reaches for a shell line it already
knows and never asks whether the REPL this session started is still running — nothing else reports
that. prompt.routing writes that section for you:
from vis_lang_interface import prompt
PROMPT = prompt.routing(
"Python",
"py",
("format_code", "lint_code", "run_tests", "repl_start", "repl_eval", "repl_stop"),
notes=("`py.repl_eval` needs the REPL `py.repl_start` already started.",),
)
vis.register_extension(vis.Extension(..., alias="py", prompt=PROMPT))
Keep notes to routing and policy — when a verb is the right approach and what it refuses. Each
method's own docstring already carries its arguments and its result.
Keep source files parseable
SyntaxGuard asks the language's own parser whether changed files still parse. Syntax checks run
only through edit hooks. They are not tools available to python_execution or public routing verbs.
The interface has no parser. Supply the file suffixes and a private _check_syntax callback.
The callback takes {path: text} and the workspace root. It returns a SyntaxResult whose
diagnostics name the supplied paths. Keep the callback outside the extension's exported symbols.
from vis_lang_interface import Diagnostic, SyntaxResult
from vis_lang_interface.syntax import SyntaxGuard
def _check_syntax(sources, root):
rows = [Diagnostic(path, line, column, "error", message) for ... in parse(sources)]
return SyntaxResult.of("clojure", rows, files=len(sources))
guard = SyntaxGuard("clojure", (".clj", ".cljs", ".cljc", ".edn"), _check_syntax)
vis.register_extension(vis.Extension(..., op_hooks=guard.op_hooks(), ctx=guard.ctx))
- Before a
patchwrites, Vis gives the hook the file as the patch would leave it. A patch that would make a parseable file unparseable is refused, and nothing is written. A file that did not parse before is not guarded, so a repair can take several steps. - After a
patch, and after everypython_executionblock, the guard parses the changed files again. This coversPath.write_text(),open(..., "w")and the programs a block ran. Files that still do not parse reach the model's next request assession["clojure_syntax_errors"]. patchis atomic for one file, and the guard judges each call on its own. A block that patches several files keeps the patches that passed when a later one is refused.- The guard watches the files under the workspace root that git lists, tracked or untracked but not ignored. Where git cannot list the tree, it walks it and skips hidden and build directories. Between listings, file and directory stat calls find what changed, so a block pays no process.
- When the parser cannot answer because its toolchain is missing, slow or failing, the guard allows the operation, logs why and does not ask again for two minutes.
A Vis host that predates the patch preview gives the before hook nothing to judge. There, the guard only reports a broken file after the write.
Everything a language tool starts runs in the jail
process.run and runtime.start spawn through vis.jailed_shell, so whatever confinement the
person running Vis turned on covers the toolchain as much as the model's own shell. A jailed
toolchain still reaches the package indexes it needs: Maven, Clojars and PyPI answer through the
egress proxy.
A shell child runs under a pty, which merges stdout with stderr and normalizes what passes through.
Tool output is data, so process.run gives each run a private directory, writes the two streams
into files there and reads them back — ToolRun.out stays parseable JSON. A runtime that stays
alive between calls gets a rendezvous instead: two FIFOs in a private directory, handed to that one
child as its stdin and stdout, so it keeps speaking one JSON request per line in and one answer per
line out without ever touching the pty.
from vis_lang_interface import runtime
live = runtime.start(["python3", driver_path], cwd=project)
answer = live.request({"op": "eval", "code": "1 + 1"}, timeout_s=30)
live.stop()
The extensions that use it
- vis-lang-clojure — cljfmt, zprint, clj-kondo,
Lazytest and an nREPL, through the
clojureCLI. - vis-lang-python — ruff, pytest and a managed project REPL.
Development
vis-agent python -m pytest tests -q
Community feedback
Votes and display names are not verified identities. One vote per network address; shared networks share a vote. Select your current choice again to clear it.
Loading feedback…