Visor — AI workflow engine for code review, assistants & automation
Orchestrate checks, MCP tools, and AI providers with YAML-driven pipelines. Runs as GitHub Action, CLI, Slack bot, Telegram bot, or HTTP API.
Visor is an open-source workflow engine that lets you define multi-step AI pipelines in YAML. Wire up shell commands, AI providers, MCP tools, HTTP calls, and custom scripts into dependency-aware DAGs — then run them from your terminal, CI, Slack, Telegram, Email, WhatsApp, Teams, or an HTTP endpoint.
What you get out of the box:
- YAML-driven pipelines — define checks, transforms, routing, and AI prompts in a single config file.
- 8 runtime modes — CLI, GitHub Action, Slack bot, Telegram bot, Email, WhatsApp, Teams, HTTP server — same config, any surface.
- 17 provider types —
ai,command,script,mcp,utcp,http,claude-code,a2a,github,memory,workflow, and more. - AI orchestration — multi-provider (Gemini, Claude, OpenAI, Bedrock), session reuse, MCP/UTCP tool calling, retry & fallback.
- Execution engine — dependency DAGs, parallel waves, forEach fan-out, conditional routing, failure auto-remediation.
- Built-in testing — YAML-native integration tests with fixtures, mocks, and assertions.
What do you want to build?
| Goal | Start here | Example |
|---|---|---|
| Code review on PRs | Guide: Code Review Pipeline | quick-start-tags.yaml |
| AI agent with tools | Guide: AI Agent | ai-custom-tools-simple.yaml |
| Multi-step automation | Workflow Creation Guide | enhanced-config.yaml |
| Chat assistant / Bot | Bot Integrations | teams-assistant.yaml |
| Run shell commands + AI | Command Provider | ai-with-bash.yaml |
| Connect MCP tools | MCP Provider | mcp-provider-example.yaml |
| Call tools via UTCP | UTCP Provider | utcp-provider-example.yaml |
| Add API integrations (TDD) | Guide: TDD Assistant Workflows | workable.tests.yaml |
First time? Run
npx visor initto scaffold a working config, thennpx visorto run it.
Table of Contents
- Quick Start
- AI Assistant Framework
- Runtime Modes
- PR Comment Commands
- Core Concepts
- Provider Types
- Orchestration
- AI & MCP
- GitHub Provider
- Templating & Transforms
- Suppressing Warnings
- Testing Framework
- SDK
- Configuration
- Observability
- Security
- Enterprise Policy Engine
- Further Reading
- Contributing
- License
Requirements: Node.js 18+ (CI runs Node 20).
🚀 Quick Start
Install & Run
# Install
npm i -D @probelabs/visor
# Scaffold a starter config (pick a template)
npx visor init # interactive picker
npx visor init code-review # PR review pipeline
npx visor init agent # AI agent with tools
npx visor init automation # multi-step pipeline
npx visor init assistant # chat assistant / Slack bot
# Run
npx visor # run all steps
npx visor --tags fast # run steps tagged "fast"
npx visor validate # check config for errors
Or one-off without installing: npx -y @probelabs/visor@latest --check all --output table
Minimal Config (.visor.yaml)
version: "1.0"
steps:
security:
type: ai
prompt: "Identify security issues in changed files"
tags: ["fast", "security"]
run-tests:
type: command
exec: npm test
depends_on: [security]
notify:
type: http
method: POST
url: https://hooks.slack.com/...
body: '{ "text": "Tests {{ outputs[''run-tests''].status }}" }'
depends_on: [run-tests]
As a GitHub Action
# .github/workflows/visor.yml
name: Visor
on:
pull_request: { types: [opened, synchronize] }
issues: { types: [opened] }
issue_comment: { types: [created] }
permissions:
contents: read
pull-requests: write
issues: write
checks: write
jobs:
visor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: probelabs/visor@v1
env:
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
Tip: Pin releases for stability with
@v1. For bleeding-edge, use@nightly.
🤖 AI Assistant Framework
Visor ships with a built-in assistant framework — three composable workflows for building AI-powered assistants with skills, tools, and multi-repo code exploration. Import them with a single line:
version: "1.0"
imports:
- visor://assistant.yaml
checks:
chat:
type: workflow
workflow: assistant
assume: ["true"]
args:
question: "{{ conversation.current.text }}"
system_prompt: "You are a helpful engineering assistant."
intents:
- id: chat
description: general Q&A or small talk
- id: code_help
description: questions about code or architecture
default_skills: [code-explorer]
skills:
- id: code-explorer
description: needs codebase exploration or code search
tools:
code-talk:
workflow: code-talk
inputs:
projects:
- id: backend
repo: my-org/backend
description: Backend API service
allowed_commands: ['git:log:*', 'git:diff:*']
on_success:
goto: chat
| Workflow | What it does |
|---|---|
| assistant | Full AI assistant — intent routing, dynamic skill activation, tool orchestration, knowledge injection, bash command control |
| code-talk | Multi-repo code exploration — routes questions to repos, checks out code, explores with tools, returns answers with file references and confidence scoring |
| intent-router | Lightweight intent classification — picks intent, rewrites question, selects skills/tags |
The visor:// protocol resolves to bundled workflows shipped with the package — no network fetch needed.
Learn more: docs/assistant-workflows.md | Examples: code-talk-workflow · code-talk-as-tool · intent-router
🖥️ Runtime Modes
Visor runs the same YAML config across multiple surfaces:
| Mode | How to run | Best for |
|---|---|---|
| CLI | visor --check all --output table |
Local dev, CI pipelines |
| GitHub Action | uses: probelabs/visor@v1 |
PR reviews, issue triage, annotations |
| Slack bot | visor --slack --config .visor.yaml |
Team assistants, ChatOps |
| Telegram bot | visor --telegram --config .visor.yaml |
Personal assistants, group bots |
| Email bot | visor --email --config .visor.yaml |
Email assistants, threaded conversations |
| WhatsApp bot | visor --whatsapp --config .visor.yaml |
WhatsApp assistants, customer support |
| Teams bot | visor --teams --config .visor.yaml |
Enterprise assistants, team ChatOps |
| HTTP server | http_server: { enabled: true, port: 8080 } |
Webhooks, API integrations |
See Bot Integrations for a comparison of all bot transports.
Additional modes:
- TUI — interactive chat-style terminal UI:
visor --tui - SDK — programmatic Node.js API:
import { runChecks } from '@probelabs/visor/sdk' - Scheduler — cron-based execution with database-backed persistence
# CLI examples
visor --check all --output table
visor --tags fast,local --max-parallelism 5
visor --uncommitted # Review only uncommitted (incl. untracked) changes
visor --analyze-branch-diff # PR-style diff vs base branch, even with uncommitted edits
visor --event pr_updated # Simulate GitHub events
visor --tui --config ./workflow.yaml # Interactive TUI
visor --debug-server --debug-port 3456 # Live web debugger
visor config snapshots # Config version history
visor validate # Validate config
visor test --progress compact # Run integration tests
What a local run reviews: uncommitted changes when there are tracked edits (untracked files ride along); on a feature branch without tracked edits (and with a code-review schema) the diff vs the base branch. --analyze-branch-diff forces the branch diff, --uncommitted forces the working-tree changes; the two are mutually exclusive. See docs/ci-cli-mode.md.
Run modes: Default is CLI mode everywhere. For GitHub-specific behavior (comments, checks, annotations), run with --mode github-actions or set mode: github-actions in the Action. Force CLI mode inside Actions with VISOR_MODE=cli.
See docs/commands.md for the full CLI reference.
💬 PR Comment Commands
Trigger reviews and assistant actions via comments on PRs or issues:
/review # Re-run all checks
/review --check security # Re-run specific check
/visor how does caching work? # Ask the built-in assistant
Learn more: docs/commands.md
🧩 Core Concepts
| Concept | What it is |
|---|---|
| Step (or Check) | Unit of work — a shell command, AI call, HTTP request, script, etc. |
| Provider | How a step runs: ai, command, script, mcp, utcp, http, claude-code, github, memory, workflow, … |
| depends_on | Execution order — independents run in parallel, dependents wait. |
| forEach | Fan-out — transform output into an array, run dependents per item. |
| Routing | on_fail, on_success, goto, retry — conditional flow with loop safety. |
| Transform | Reshape output with Liquid templates or JavaScript before passing downstream. |
| Schema | JSON Schema that validates step output (e.g., code-review). |
| Template | Renders validated output into Markdown/table for PR comments. |
| Group | Which PR comment a step posts into. |
| Tags | Label steps and filter with --tags fast,local. |
| Events | Trigger steps on PRs, issues, comments, webhooks, or cron schedules. |
🔌 Provider Types
| Provider | Description | Example use |
|---|---|---|
ai |
Multi-provider AI (Gemini, Claude, OpenAI, Bedrock) | Code review, analysis, generation |
command |
Shell commands with Liquid templating | Run tests, build, lint |
script |
JavaScript in a secure sandbox | Transform data, custom logic |
mcp |
MCP tool execution (stdio/SSE/HTTP) | External tool integration |
utcp |
UTCP tool execution (HTTP/CLI/SSE) | Direct tool calling via manuals |
claude-code |
Claude Code SDK with MCP tools | Deep code analysis, refactoring |
http |
HTTP output/webhook sender | Notify Slack, trigger CI |
http_input |
Webhook receiver | Accept external events |
http_client |
HTTP API client | Call external APIs |
github |
GitHub operations (labels, comments, checks) | Label PRs, post reviews |
memory |
Key-value store (get/set/append/increment) | State across steps |
workflow |
Reusable sub-workflows from files/URLs | Compose pipelines |
human-input |
Interactive prompts (TUI/Slack) | Approvals, user input |
log / logger |
Structured logging | Debug, audit trail |
noop |
No-op placeholder | Orchestration nodes |
git-checkout |
Git operations (clone, checkout, worktree) | Multi-repo workflows |
See docs/pluggable.md for building custom providers.
⚙️ Orchestration
Dependencies & Parallel Execution
Steps without dependencies run in parallel waves. depends_on enforces ordering:
steps:
fetch-data:
type: command
exec: curl -s https://api.example.com/data
analyze:
type: ai
prompt: "Analyze: {{ outputs['fetch-data'] }}"
depends_on: [fetch-data]
report:
type: command
exec: 'echo "Done: {{ outputs[''analyze''] | truncate: 100 }}"'
depends_on: [analyze]
forEach Fan-Out
Transform output into an array, run dependents once per item:
steps:
list-services:
type: command
exec: 'echo ''["auth","payments","notifications"]'''
forEach: true
check-service:
type: command
exec: 'curl -s https://{{ outputs["list-services"] }}/health'
depends_on: [list-services]
Use outputs_raw in downstream steps to access the aggregated array of all forEach results:
summarize:
type: script
depends_on: [list-services]
content: |
const arr = outputs_raw['list-services'] || [];
return { total: arr.length };
Learn more: docs/foreach-dependency-propagation.md
Failure Routing & Auto-Remediation
Steps can retry, run remediation, or jump to other steps on failure:
version: "2.0"
routing:
max_loops: 5
steps:
build:
type: command
exec: make build
on_fail:
retry: { max: 2, backoff: { mode: exponential, delay_ms: 500 } }
goto: setup # Jump back on exhausted retries
deploy:
type: command
exec: make deploy
depends_on: [build]
on_success:
run: [notify] # Run extra steps on success
on_fail:
goto_js: |
return attempt <= 2 ? 'build' : null; # Dynamic routing
Learn more: docs/failure-routing.md
Conditional Execution & Author Permissions
steps:
security-scan:
type: command
exec: npm audit
if: "!hasMinPermission('MEMBER')" # Only for external contributors
auto-approve:
type: github
op: labels.add
values: ["approved"]
if: "hasMinPermission('COLLABORATOR') && totalIssues === 0"
protect-secrets:
type: command
exec: echo "Checking permissions..."
fail_if: "!isMember() && files.some(f => f.filename.startsWith('secrets/'))"
Available permission functions: hasMinPermission(level), isOwner(), isMember(), isCollaborator(), isContributor(), isFirstTimer().
Learn more: docs/author-permissions.md
🤖 AI & MCP
Multi-Provider AI
steps:
review:
type: ai
prompt: "Review this code for security issues"
ai:
provider: anthropic # or: google, openai, bedrock
model: claude-sonnet-4-20250514
fallback:
strategy: any # Try other providers on failure
Supported providers: Google Gemini, Anthropic Claude, OpenAI GPT, AWS Bedrock.
Set one key via environment: GOOGLE_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, or AWS credentials.
MCP Tool Integration
Give AI steps access to MCP tools, or call MCP tools directly:
# AI step with MCP tools
steps:
analyze:
type: ai
prompt: "Use the search tool to find security patterns"
ai:
mcp_servers:
- name: code-search
command: npx
args: ["-y", "@probe/search"]
# Direct MCP tool execution
search:
type: mcp
transport: stdio
command: npx
args: ["-y", "@probe/search"]
method: search
arguments:
query: "{{ outputs['setup'].pattern }}"
AI Session Reuse
Chain AI conversations across steps:
steps:
security:
type: ai
prompt: "Find security issues"
remediation:
type: ai
prompt: "Suggest fixes for the issues you found"
depends_on: [security]
reuse_ai_session: true # Carries conversation history
session_mode: append # Or: clone (default)
Claude Code Provider
Full Claude Code SDK integration with MCP tools and subagents:
steps:
deep-review:
type: claude-code
prompt: "Analyze code complexity and suggest refactoring"
max_turns: 10
mcp_servers:
- name: filesystem
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
Learn more: docs/claude-code.md · docs/mcp-provider.md · docs/advanced-ai.md
🧰 GitHub Provider
Native GitHub operations (labels, comments, checks) without shelling out to gh:
steps:
apply-labels:
type: github
op: labels.add
values:
- "{{ outputs.overview.tags.label | default: '' | safe_label }}"
value_js: |
return values.filter(v => typeof v === 'string' && v.trim().length > 0);
Learn more: docs/github-ops.md
🧬 Templating & Transforms
Liquid Templates
Steps can use Liquid templates in prompts, exec commands, HTTP bodies, and more:
steps:
greet:
type: command
exec: 'echo "Files changed: {{ files | size }}, branch: {{ branch }}"'
post-results:
type: http
url: https://api.example.com/results
body: |
{ "issues": {{ outputs["review"] | json }},
"pr": {{ pr.number }} }
Available context: outputs, outputs_raw, inputs, pr, files, env, memory, branch, event, conversation.
JavaScript Transforms
Transform step output before passing to dependents:
steps:
fetch:
type: command
exec: 'node -e "console.log(JSON.stringify({items:[1,2,3]}))"'
transform_js: |
return output.items.filter(i => i > 1);
Dynamic Routing with JavaScript
steps:
check:
type: command
exec: npm test
on_fail:
goto_js: |
if (attempt >