VT-AIAgent
System-automation agent for your machine, exposed as an MCP server for
opencode / Claude Code / pi — and drivable directly via the vtai CLI.
Tools
| Tool | What it does |
|---|---|
system_info |
Read-only machine snapshot (CPU, RAM, disks, load, GPU) plus the config and guardrails in effect |
run_shell |
Allowlisted shell commands, sandboxed (see below) |
organize_files |
Content-aware directory cleanup inside writable roots |
backup_sync |
rsync incremental backup with verification; --delete is opt-in |
transcode_media |
VAAPI-accelerated batch video transcode; never touches originals |
disk_audit |
Read-only scan for reclaimable disk space |
delegate_coding |
Hand tasks to opencode/claude/pi; git snapshot + verify + rollback; require human approval by default |
auto_mode |
Autonomous loop: hand a goal to a coding CLI, review the reply, iterate until it matches |
inspect_application |
REA-powered static analysis of an app under guardrails: deny-glob check → quarantine copy → minimal-env run → evidence digest in the audit trail → optional judge pass (off until enabled in config) |
Safety model
- Dry-run by default (
guardrails.dry_run_default) — previews never touch disk. - Path containment: writes only inside
paths.writable_roots, resolved paths defeat../symlink escapes;guardrails.deny_globsprotect secrets. - Fail-closed shell sandbox: the binary must be a bare allowlisted name
with an explicit read/write policy, resolve inside
trusted_bin_dirs, carry no exec-enabling flags, and every path argument passes the same checks. - Kill switch: drop a
KILLfile indata_dirto abort executing tools. - Echo-loop breaker: N identical denials in a window escalate the refusal
into a
CIRCUIT BREAKERinstruction the looping model can't miss. - Budget cap:
budget_usd_per_runaborts an orchestrated run when accrued token cost exceeds it; spend is recorded per run. - Audit: every allow and deny is logged to SQLite in
data_dir, each invocation (orchestrated, MCP, or CLI) attributed to a run row.
Quick start
Prerequisites
| Requirement | Notes | |
|---|---|---|
| OS | Linux, macOS, or Windows | all three run in CI (blocking since 0.6.0) |
| Python | 3.14+ | pinned by .python-version; uv fetches it for you |
| uv | >= 0.13.1 | curl -LsSf https://astral.sh/uv/install.sh | sh |
| Planner LLM | one of: Ollama, any OpenAI-compatible endpoint, or a cloud key | see Choosing a model |
| rsync | optional | required by backup_sync; without it the tool fails closed |
| ffmpeg | optional | required by transcode_media and the voice helpers |
| Lemonade Server | optional | speech in/out for agent-listen / agent-speak |
1. Install
git clone https://github.com/velsrocky/vtai-agent.git
cd vtai-agent
uv sync --dev
uv sync needs uv >= 0.13.1 (the lockfile is revision = 5; the floor is
declared as [tool.uv] required-version, so an older uv says so plainly
instead of failing on a lockfile parse).
2. Configure
The agent reads one TOML file. Which one is resolved in this order:
$VT_CONFIG— explicit overrideconfig/settings.tomlnext to the package (i.e. the clone you just made)~/.vtaiagent/settings.toml— the per-user file
A fresh clone contains the maintainer's config (their provider, their writable roots), so point the agent at your own file instead of editing the tracked one:
export VT_CONFIG="$HOME/.vtaiagent/settings.toml"
uv run vtai init # writes a starter config there + creates the dirs
Put that export in your shell profile so every later command (and the MCP
server) picks it up.
vtai init creates ~/.vtaiagent (audit DB + data dir), ~/Downloads, and
~/Documents/vt-data, and writes a starter config with safe defaults:
dry_run_default = true, deny_globs for ~/.ssh, ~/.gnupg, ~/.aws and
**/.env, and writable roots limited to ~/Downloads, ~/Documents/vt-data
and ~/.vtaiagent/tmp. It is safe to re-run.
Check which config you ended up with — vtai info names the file and its
origin:
uv run vtai info
# config file : /Users/you/.vtaiagent/settings.toml (user-level)
Alternative: keep the checkout's config and override with env vars
A .env file (gitignored; copy .env.example) in the working directory
overrides any setting, using the VT_ prefix with __ for nesting:
VT_LOCAL__BASE_URL=http://localhost:13305/api/v1
VT_LOCAL__MODEL=<a pulled chat model>
VT_ACTIVE_PROVIDER=local
This is how a machine with no Ollama points the agent at a Lemonade server
instead. The .env is read from the current working directory, so run
the CLI from the checkout, or export the variables in your shell.
Choosing a model
The planner/judge model comes from active_provider:
local— any OpenAI-compatible endpoint: Ollama (http://localhost:11434/v1), llama.cpp, vLLM, LM Studio, Lemonade. No API key.anthropic/openai— cloud models; putANTHROPIC_API_KEYorOPENAI_API_KEYin.env.
Set cost_per_1k_input / cost_per_1k_output in the provider block to make
budget_usd_per_run meaningful; without them the agent falls back to the
model's native price table.
3. Verify
Start with the preflight — it checks uv, your config, the writable roots, what is protected, the optional tools, the model endpoint, the MCP registration and any delegate waiting for review:
uv run vtai doctor
VT-AIAgent doctor — v0.6.2
✓ agent version 0.6.2
✓ uv 0.13.1
✓ config file /Users/you/.vtaiagent/settings.toml (user-level)
✓ config valid no issues
✓ writable roots 3 present
✓ protected paths 4 deny globs
✓ shell allowlist 22 binaries
✓ model endpoint http://localhost:11434/v1
! optional tools not installed: ffmpeg (for transcode_media, voice helpers)
✓ mcp registration opencode (global)
✓ pending approvals none
It exits non-zero if something needs fixing. Then, to try the pieces:
uv run vtai info # config, provider, guardrails
uv run vtai tools # the registered tools
uv run vtai run system_info # a read-only call, no LLM involved
uv run vtai goal --dry-run "list my Downloads" # full loop with the model
4. Use it from your agent CLI
scripts/register-mcp.sh # opencode (global), claude (user), pi (CLI)
opencode's registration lands in ~/.config/opencode/opencode.jsonc and
merges with your existing providers. The script registers an absolute
uv path on purpose: a bare uv fails with
NotFound: ChildProcess.spawn (uv ...) when the client spawns the server
from a daemon with a minimal PATH.
Use
uv sync # install
uv run vtai init # create/validate config, dirs, writable roots
uv run vtai info # show config
uv run vtai tools # list tools
uv run vtai run system_info # invoke a tool
uv run vtai run run_shell --params '{"command":"ls ~/Downloads","dry_run":true}'
scripts/register-mcp.sh # register with opencode / claude
To get the vtai CLI outside the checkout, install it as an editable tool so
it tracks your working copy:
uv tool install --editable /path/to/vtai-agent # -> ~/.local/bin/vtai
Install into another project as a dependency with
uv add git+https://github.com/velsrocky/vtai-agent@v0.6.8, or pull the
image with docker build -t vtai-agent ..
Registry & distribution
PyPI: uv tool install vtai-agent (or pipx install vtai-agent) — publishes
on v* tags via Trusted Publishing with build attestations (see
.github/workflows/publish.yml). The <!-- mcp-name: ... --> comment above
is the PyPI-package proof the official MCP registry expects; server.json
carries the registry manifest (io.github.velsrocky/vtai-agent, stdio)
alongside a metadata-consistency gate in the publish workflow. Only install
from these sources; a similar repository name does not establish provenance.
Register with the official MCP registry after a PyPI release:
mcp-publisher auth --oauth2-github
mcp-publisher publish server.json
Certification (vtai-cert)
Safety claims are probes, not prose: vtai certify runs the executable
guardrail-compliance suite (deny-glob containment, dotdot/symlink escapes,
fail-closed shell, injected-argv resistance, dry-run-first, kill switch,
audit exactness, echo-loop breaker, and the delegate's verify-then-trust
loop with scripted-fake rollback + approval-gate probes) and emits a
digest-stamped certificate;
exit 0 gates the release in CI. See docs/certification.md.
HTTP transport
vtai serve --transport http --port 8765 --token $VT_HTTP_TOKEN exposes the
same guarded tools over MCP StreamableHTTP at /mcp. The bearer token is
required when binding anything other than localhost; the server refuses to
bind externally without one.
Development
uv sync --dev
uv run pytest tests/ -v
uv run ruff check src/ tests/
uv run python scripts/generate_tool_catalog.py --check # tool-catalog doc drift
The config file is resolved as described in Configure: $VT_CONFIG
→ the checkout's config/settings.toml → ~/.vtaiagent/settings.toml. Env
overrides use the VT_ prefix with __ nesting (e.g.
VT_GUARDRAILS__DRY_RUN_DEFAULT=false). Config is validated at startup
(validate_settings) — a bad deny_globs pattern or a missing writable root
fails fast, not mid-run.
Windows: the shell sandbox needs the allowlisted binaries to resolve, and a
fresh Windows runner does not put Git for Windows' coreutils on PATH. Install
Git for Windows and prepend its usr\bin (this is what CI does):
Add-Content $env:GITHUB_PATH "C:\Program Files\Git\usr\bin" # CI
# locally: add "C:\Program Files\Git\usr\bin" to your PATH
Everything else (install, config, MCP registration) is the same on all three platforms.
Orchestrator
vtai goal runs a bounded ReAct-style agent loop. Each step the model
either emits one fenced-JSON tool call (parsed, guarded, executed, result fed
back) or a final natural-language answer. Bounds enforced every step:
max_steps, kill switch, and budget_usd_per_run. A lonely dry_run: true
text blob that fails to parse or misses its tool key is retried with a
corrective note instead of ending the run as "success"; the run mode is
authoritative — the model cannot override it — and every tool call is
persisted as a Step row and audit entry. If a paid provider's spend can't
be determined (no cost_per_1k_* config and no native cost), the run fails
closed rather than reporting free. The system prompt describes the machine's
actual OS.
We use a text protocol rather than native function-calling on purpose: the local Ollama models on this box emit tool calls as raw JSON text even when native tool schemas are provided (verified 2026-10-03 with qwen2.5-coder:7b), so the text protocol behaves identically across local and cloud providers.
Auto mode
auto_mode runs the loop you describe: it dispatches the goal to a coding
CLI (opencode by default), reviews the reply against the goal with the
configured model, and if the reply is insufficient, sends a follow-up prompt
quoting the critique — up to max_iterations. The judge only accepts a bare
OK as satisfied (hedged replies fail). Spend is metered against
guardrails.budget_usd_per_run and attributed in the run's spend detail:
the judge's own LLM calls always; the delegate CLI when it can report it —
claude -p --output-format json exposes total_cost_usd, and an unreadable
report from a cost-capable CLI fails closed. opencode/pi report no
headless spend (documented vendor behavior, not guess-parsed event streams),
so their runs carry "cli_usd": "unmeasured" and stay bounded only by
max_iterations/timeout. From inside opencode or
Claude Code, start the prompt with @vt-ai_automode_on: the MCP server's
instructions route that to the tool.
uv run vtai run auto_mode --params '{"goal":"add a unit test for parser.py","working_dir":".","max_iterations":3}'
Delegate coding
delegate_coding snapshots the target repo, runs the chosen CLI headlessly,
captures the diff, and on verify_command failure (or timeout / non-zero
exit) rolls back. Three guards worth knowing:
- Human approval gate:
require_approvaldefaults totrue. Interactively it showsgit diff --statand asks before keeping changes. Non-interactive runs (MCP, scripted, orchestrated) keep the changes in the worktree as a pending approval — the run reports failure so automated loops stop — andvtai approve <id>(keep) orvtai approve <id> --rollbackresolves it. While a pending approval exists for a repo, delegate_coding refuses new work there, even withrequire_approval: false. Passrequire_approval: falseonly for tasks a human has already reviewed. pineeds credentials: with no authenticated provider,pi -pexits 0 having emitted a tool-call JSON block and applied nothing. The tool now fails closed with a clear message. Runpi auth check— or preferopencode/claude— before delegating to pi.
Claude delegations run with --output-format json, so the reply text comes
from result and the invocation's total_cost_usd is captured into the
tool detail as cli_cost_usd (and audited). Delegations to opencode/pi
report "cli_cost_usd": "unmeasured".
uv run vtai approve # list pending delegate approvals
uv run vtai approve 7 # keep the pending diff
uv run vtai approve 7 --rollback
vtai info shows how many approvals are waiting, so a delegate that was kept
for review does not get lost.
Inspect applications (REA capability)
inspect_application integrates REA so the
agent can statically explain how a local app works without source code.
Because REA's inspection is powerful, it runs entirely inside vtai-agent's
guards and is off by default:
[capabilities.rea]
enabled = true # enable only after install vetting
binary = "rea"
command_prefix = ["analyze-javascript-application"]
timeout = 1800.0
keep_copy = false
Safety contract, in order: the target must pass deny_globs; it is then
copied into a quarantine dir under data_dir (denied entries and
out-of-policy symlinks are skipped on the way in, so REA never reads a path
you couldn't); REA runs on the copy with a minimal child environment (no
inherited API keys) and is killed after timeout; output JSON is parsed and
a sha256 evidence digest plus REA's evidence id are written to the audit
trail; the optional judge pass (same model and fail-closed policy as
auto_mode) verifies the evidence answers the goal before the result reports
success. The original target is never touched, and the quarantine copy is
deleted unless keep_copy is true. Live-validated 2026-10-21 against the
real rea 6.7.0 CLI on a real JS app — spawn, --json envelope, and
evidence digits all confirmed; see
docs/showcases/rea-live-validation.md.
uv run vtai run inspect_application \
--params '{"goal":"explain how Notes builds its CSV export header",
"target":"~/quarantine/Notes-app","dry_run":true}'
Install REA only from official sources (github.com/morluto/rea or the
rea-agents package on npm — verify provenance), not mirror sites.
Audit log
Every allow/deny is stored in ~/.vtaiagent/vtai.db. Query it without a SQL
client:
uv run vtai audit --limit 20
uv run vtai audit --denied --grep ssh
uv run vtai audit --action delegate --run-id 193
To see one run end to end — the goal, every tool call, what was allowed or refused, and the spend:
uv run vtai runs # list runs
uv run vtai runs --summary # aggregate: spend percentiles, tool counts, denial codes (JSON with --json)
uv run vtai show 193 # one run in full
Voice control (agent-listen / agent-speak)
Both helpers talk to a local Lemonade Server (TTS kokoro-v1, STT
Whisper-Tiny on Linux/macOS — Windows defaults to Moonshine-Tiny-Streaming
because the whisper.cpp recipe misbehaves on some AMD GPUs) at
http://localhost:13305. LEMONADE_STT_MODEL overrides the STT model on any
platform. Use --seconds N to change the
record window, --vad for silence-based early stop, LEMONADE_BASE_URL to
point elsewhere.
The scripts are versioned in scripts/; install them with:
uv run vtai voice-sync
voice-sync copies both helpers to ~/.local/bin (override with --dest),
marks them executable, and reports installed / updated / unchanged per
file. vtai voice-sync --check only reports drift (exit 1 when out of sync)
— re-run without --check after pulling script updates. Set
VTAI_SCRIPTS_DIR to sync from a different checkout.
Cross-platform notes (verified on macOS 27/ARM, 2026-10-12):
Supported: Linux + macOS + Windows — all three run in CI (Linux and macOS push-blocked; Windows reinstated in 0.6.0 after the suite became portable, with Git Bash coreutils prepended to PATH so the allowlisted sandbox resolves real binaries). rsync-dependent tests skip on runners without rsync and are complemented by a fail-closed counter-test.
arecordis used for recording on Linux; elsewhere the helpers fall back toffmpeg(-f pulse/avfoundation), so macOS and Windows work too.--vadcurrently requires Linuxarecord; on other platforms use--seconds N.- macOS mic selection: avfoundation device indexes vary per machine — list
them with
ffmpeg -f avfoundation -list_devices true -i ""and override viaAGENT_LISTEN_FF_INPUT(default audio-only-f avfoundation -i ":0"). Machines with no built-in microphone (many mini PCs) need an external/USB device attached, otherwise recording yields silence (exit 6, no speech). - Playback tries
ffplay→afplay(macOS) →mpg123→aplayin order. - The ALSA mic-level fix and
vtai-audio-fixsystemd unit below are Linux-specific; Windows and macOS handle this in System Settings. guardrails.trusted_bin_dirsdefaults per OS (C:\Windows\System32,/usr/bin, /bin, /usr/local/bin, /opt/homebrew/bin, and the Linux set); entries missing on the current machine are ignored (validation only fails when none exist), so one config can list both Linux and macOS paths.system_infois portable: memory viasysctl/vm_statand disks via/,$HOME,/Volumes/*on macOS (Metal GPU note; norocm-smithere).transcode_mediadefaults tolibx264off Linux (VAAPI is Linux-only); an explicit"codec"still overrides.disk_auditalso measures~/Library/Caches,~/Library/Logs,/Library/Caches, and~/.Trashon macOS; fresh mac installs getvm_stat(instead of Linux-onlyfree/nproc) in the starter allowlist.
Troubleshooting
Setup and MCP
opencode mcp listshows✗ vtai-agent failed: NotFound: ChildProcess.spawn (uv ...): the client spawned the server with a minimal PATH (daemons givePATH=/usr/bin:/bin:...), so a bareuvwas unresolvable. Re-runscripts/register-mcp.sh— it registers the absoluteuvpath — or edit themcp.servers.vtai-agent.commandarray in your opencode config to the full path ofuv(command -v uv).opencode mcp listsaysNo MCP servers configured: the registration is per-project. Run it from the checkout, or use the global registrationscripts/register-mcp.shwrites to~/.config/opencode/opencode.jsonc.- "config exists: …/config/settings.toml" and your settings are ignored:
the checkout's config wins over
~/.vtaiagent/settings.toml. SetVT_CONFIG="$HOME/.vtaiagent/settings.toml"and re-runvtai init;vtai infoprints the file in use and where it came from. uv syncfails withRequired uv version ... does not match: install uv >= 0.13.1 (the lockfile is revision 5).
Voice (Linux / PipeWire / ALSA)
-
Mic produces silence or clipped garbage transcripts: the internal mic boost is pinned too high and clips the input. Fix (also applied at every login via
~/.config/systemd/user/vtai-audio-fix.service):amixer -c 2 sset 'Internal Mic Boost' 0 amixer -c 2 sset 'Capture' 75%
-
Headphone jack not detected: the kernel reports no jack on the combo port, so the headphone amp stays off. Check
pw-cli enum-params 53 EnumRoute— theanalog-output-headphonesroute showsavailable: nountil a headset is firmly plugged in. Route manually withwpctl set-route 38 1if detection is flaky. -
Default source is the wrong mic: list routes with
pw-cli enum-params 53 Routeand select withwpctl set-route 47 <index>(0= internal mic,1= headset mic). -
STT returns
[BLANK_AUDIO]or noise transcripts: verify levels witharecord -d 3 -f cd /tmp/t.wavand inspect the peak; near 0 means the selected route carries no signal, full-scale means clipping (lower the boost).
Delegate coding
- Files changed via
delegate_codingare still sitting in the repo: that is a pending approval — the changes are kept for your review until you runvtai approve <id>to keep them or--rollbackto discard them. Passrequire_approval: falseonly for tasks a human has already reviewed.
Contributing
See CONTRIBUTING.md — setup, the guardrail rules any change has to respect, and the test/lint commands.
Security
Guardrails, what counts as a vulnerability, and how to report one privately: SECURITY.md.
License
MIT — see LICENSE. Use it, fork it, run it on your own machine.
Metadata
Release files for vtai-agent 0.6.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vtai_agent-0.6.8.tar.gz | 87.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vtai_agent-0.6.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 179.9 kB
Release files / vtai_agent-0.6.8.tar.gz
| Download URL | vtai_agent-0.6.8.tar.gz |
|---|---|
| Size | 87.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
982f792858cadd7aef7025adbfbea7400968535148fff63b32d9a93c798b7e0f
|
|
BLAKE2b-256 checksum How to use checksums |
5713f391334efe9afe7ccb19e4676a3a026b7db18412c74eb2688dc337896df7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / vtai_agent-0.6.8-py3-none-any.whl
| Download URL | vtai_agent-0.6.8-py3-none-any.whl |
|---|---|
| Size | 92.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
120f44b71ab22bbf601fa7150bf63c4af2fe5116910db7b1569acca196378df5
|
|
BLAKE2b-256 checksum How to use checksums |
cd4ff70a83b0a6d4452f5fb6414e599316170a61b540b52a5913cf9cc1ab2130
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for vtai-agent 0.6.8
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for vtai_agent-0.6.8.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.