This document provides a comprehensive overview of Visor's internal architecture, explaining how the system works at a high level.
- System Overview
- Entry Points
- Core Components
- Provider Architecture
- State Machine
- Event Sources
- Data Flow
- Extension Points
- Telemetry and Observability
- Error Handling
- Security Considerations
- Memory and State Management
- Performance Optimization
- Related Documentation
- Appendix A: File Structure
- Appendix B: Configuration Schema Reference
- Appendix C: Event Types
- Appendix D: Glossary
Visor is an AI-powered workflow orchestration tool that can run as a GitHub Action, CLI tool, Slack bot, or A2A agent server. The system uses a state machine-based execution engine to orchestrate checks (steps) with sophisticated dependency resolution, routing, and error handling.
+------------------+
| Entry Points |
+------------------+
| |
+------+------+-----+----+--------+---+----------+
| | | | |
v v v v v
+------+------+ +---+----+ +---+---+ +--+-------+ +----+------+
| GitHub | | CLI | | Slack | | HTTP | | A2A |
| Action | | | | Socket| | Webhook | | Agent |
| (index.ts) | | | | Mode | | Server | | Server |
+------+------+ +---+----+ +---+---+ +--+-------+ +----+------+
| | | | |
+------+------+----------+--------+--------------+
|
v
+----------+-----------+
| Configuration |
| Manager |
| (config.ts) |
+----------+-----------+
|
v
+----------+-----------+
| State Machine |
| Execution Engine |
+----------+-----------+
|
+--------------------+--------------------+
| | |
v v v
+------+------+ +------+------+ +------+------+
| Dependency | | Journal | | Provider |
| Resolver | | (Snapshot | | Registry |
| | | Store) | | |
+-------------+ +-------------+ +------+------+
|
+----------------+------------------------+
| | | |
v v v v
+------+------+ +------+------+ +-----+----+ +--+--+
| AI Provider | | Command | | HTTP | | ... |
| | | Provider | | Provider | | |
+-------------+ +-------------+ +----------+ +-----+
| Component | Description |
|---|---|
| Entry Points | GitHub Action, CLI, Slack Socket Mode, HTTP webhooks, A2A Agent Protocol |
| Configuration Manager | Loads and validates YAML configuration |
| State Machine Engine | Orchestrates check execution with wave-based scheduling |
| Provider Registry | Registry of pluggable check providers |
| Journal | Execution state and output history |
| Dependency Resolver | Builds execution graph from check dependencies |
Visor supports multiple entry points for different integration scenarios.
The primary entry point for GitHub Actions:
// Simplified flow
async function run(): Promise<void> {
const { octokit, authType } = await createAuthenticatedOctokit();
const configManager = new ConfigManager();
const config = await configManager.loadConfig(configPath);
await handleEvent(octokit, inputs, eventName, context, config);
}Responsibilities:
- Authenticate with GitHub (token or App)
- Load GitHub event context from
GITHUB_EVENT_PATH - Map GitHub events to Visor event triggers
- Execute checks via the state machine engine
- Post results as PR comments
Supported GitHub Events:
pull_request(opened, synchronize, edited)issue_comment(command-driven reviews)issues(issue assistant workflows)push(associated PR detection)
The command-line interface for local development and CI:
visor --config .visor.yaml --checks security,performance
visor review # Built-in code review workflow
visor test tests/ # Run YAML test suites
visor validate # Validate configurationCLI Features:
- Configuration validation and loading
- Check filtering by name or tags
- Multiple output formats (table, json, markdown, sarif)
- Debug visualizer integration
- TUI (Terminal User Interface) mode
- Telemetry/tracing support
For Slack bot integration:
visor --slack --config .visor.yamlUses Slack's Socket Mode for real-time event handling without requiring a public webhook endpoint.
For receiving external webhooks:
http_server:
enabled: true
port: 8080
endpoints:
- path: /webhook
transform: "{{ request.body | json }}"The A2A (Agent-to-Agent) entry point exposes Visor workflows as standards-compliant A2A agents:
visor --a2a --config .visor.yamlResponsibilities:
- Serve Agent Card at
/.well-known/agent-card.jsonfor discovery - Accept tasks via
POST /message:send - Route incoming A2A skills to internal workflows via
skill_routing - Manage task lifecycle (submitted → working → completed/failed)
- Persist tasks in SQLite via
TaskStore - Execute workflows through the state machine engine
Supported A2A Endpoints:
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/.well-known/agent-card.json |
No | Agent Card for discovery |
POST |
/message:send |
Yes | Submit a task |
GET |
/tasks/{id} |
Yes | Get task status |
GET |
/tasks |
Yes | List tasks |
POST |
/tasks/{id}:cancel |
Yes | Cancel a task |
See A2A Provider for complete documentation.
The ConfigManager handles all configuration operations:
+------------------+
| ConfigManager |
+------------------+
| - loadConfig() | <-- Load from file path
| - findAndLoad() | <-- Auto-discover .visor.yaml
| - validateConfig | <-- Schema validation (Ajv)
| - mergeDefaults | <-- Apply default values
+------------------+
Configuration Resolution Order:
- CLI
--configparameter visor.yamlor.visor.yamlin project rootvisor.yamlor.visor.yamlin git repository root- Bundled default configuration
Key Features:
- YAML parsing with
js-yaml - Schema validation with Ajv
- Configuration inheritance via
extends/include - Environment variable interpolation
- Remote configuration loading (HTTP/HTTPS)
The main orchestration layer that coordinates check execution:
class StateMachineExecutionEngine {
async executeGroupedChecks(
prInfo: PRInfo,
checks: string[],
timeout?: number,
config?: VisorConfig,
// ... additional options
): Promise<ExecutionResult>
}Responsibilities:
- Build engine context from configuration
- Initialize workspace isolation (if enabled)
- Create and run the state machine runner
- Manage frontends (GitHub, Slack integration)
- Aggregate results into
ExecutionResult
Providers are pluggable components that implement specific check types:
+-------------------+
| CheckProvider | <-- Abstract base class
+-------------------+
| + getName() |
| + execute() |
| + validateConfig()|
| + isAvailable() |
+-------------------+
^
|
+----+----+----+----+----+
| | | | | |
AI Cmd Script HTTP MCP ...
See Provider Architecture for details.
Checks can define routing rules for success, failure, and completion:
checks:
my-check:
type: ai
prompt: "..."
on_fail:
retry:
max: 3
backoff:
mode: exponential
delay_ms: 1000
run: [remediation-step]
goto: previous-step
on_success:
run: [post-process]
on_finish: # For forEach checks
run: [aggregation-step]The A2A feature introduces a task-based execution layer that sits between external clients and the workflow engine. This is architecturally separate from the step-level state machine — it governs the lifecycle of inbound agent tasks.
External A2A Client
│
▼
┌─────────────────────┐
│ A2A Frontend │ HTTP server, auth, Agent Card
│ (a2a-frontend.ts) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐ ┌─────────────────────┐
│ Task Store │◄───►│ Task Queue │
│ (SQLite) │ │ (async execution) │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────┐
│ State Machine Execution Engine │
│ (same engine used by CLI, GitHub Action, etc.) │
└──────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Provider Registry → [ai, a2a, command, ...] │
└──────────────────────────────────────────────────┘
SQLite-backed persistence for A2A tasks:
CREATE TABLE agent_tasks (
id TEXT PRIMARY KEY, -- UUID
context_id TEXT NOT NULL, -- Session grouping
state TEXT NOT NULL, -- Task state
created_at TEXT NOT NULL, -- ISO 8601
updated_at TEXT NOT NULL,
request_message TEXT NOT NULL, -- JSON: original message
artifacts TEXT DEFAULT '[]', -- JSON: AgentArtifact[]
history TEXT DEFAULT '[]', -- JSON: AgentMessage[]
workflow_id TEXT, -- Target workflow
run_id TEXT, -- Engine run correlation
claimed_by TEXT, -- Worker ID (queue)
claimed_at TEXT, -- Claim timestamp
ttl TEXT -- Expiration
);The store provides CRUD operations with state transition enforcement — invalid transitions (e.g., completed → working) throw InvalidStateTransitionError.
Manages async task execution with configurable concurrency:
- Polls the database at
poll_intervalfor unclaimedsubmittedtasks - Claims tasks using a worker ID with
stale_claim_timeoutto recover from crashed workers - Feeds tasks to the state machine engine, limited by
max_concurrent - Updates task state and artifacts on completion
Separate from the workflow engine state machine — this governs task lifecycle:
submitted ──→ working ──→ completed (terminal)
│ ├──→ failed (terminal)
│ ├──→ canceled (terminal)
│ ├──→ input_required ──→ working (resumed)
│ └──→ auth_required ──→ working (resumed)
├──→ canceled (terminal)
└──→ rejected (terminal)
Terminal states: completed, failed, canceled, rejected.
The Agent Card is a JSON document describing the agent's capabilities:
interface AgentCard {
name: string;
description?: string;
version?: string;
provider?: { organization: string; url?: string };
skills?: AgentSkill[]; // Advertised capabilities
supported_interfaces?: Array<{ url: string; protocol_binding?: string }>;
capabilities?: { streaming?: boolean; push_notifications?: boolean };
}Skills are mapped to internal workflows via skill_routing configuration. When a client sends a message with metadata.skill_id, the frontend resolves the target workflow and dispatches execution.
All providers implement the CheckProvider abstract class:
abstract class CheckProvider {
abstract getName(): string;
abstract getDescription(): string;
abstract validateConfig(config: unknown): Promise<boolean>;
abstract execute(
prInfo: PRInfo,
config: CheckProviderConfig,
dependencyResults?: Map<string, ReviewSummary>,
context?: ExecutionContext
): Promise<ReviewSummary>;
abstract getSupportedConfigKeys(): string[];
abstract isAvailable(): Promise<boolean>;
abstract getRequirements(): string[];
}The singleton CheckProviderRegistry manages provider registration:
class CheckProviderRegistry {
static getInstance(): CheckProviderRegistry;
register(provider: CheckProvider): void;
getProvider(name: string): CheckProvider | undefined;
getAvailableProviders(): string[];
}Registration Flow:
- Registry instantiates on first access
- Default providers registered automatically
- Custom providers can be registered programmatically
| Provider | Type | Description |
|---|---|---|
a2a |
A2A Agent | Calls external A2A-compatible agents |
ai |
AI-powered | Uses Gemini, Claude, OpenAI, or Bedrock for analysis |
command |
Command | Executes shell commands |
script |
Script | Executes JavaScript in a sandbox |
http |
HTTP Output | Sends results to webhooks |
http_input |
HTTP Input | Receives webhook data |
http_client |
HTTP Client | Makes HTTP requests |
mcp |
MCP | Connects to MCP tool servers |
utcp |
UTCP | Calls UTCP tools via native protocols |
claude-code |
Claude Code | Uses Claude Code SDK with MCP tools |
memory |
Memory | Persistent key-value storage |
log |
Logger | Debug logging output |
noop |
No-op | Placeholder for orchestration |
human-input |
Human Input | Collects user input interactively |
workflow |
Workflow | Invokes nested workflows |
git-checkout |
Git Checkout | Checks out git references |
github |
GitHub Ops | GitHub API operations (labels, comments) |
+-------------+ +-------------+ +-------------+
| Instantiate | --> | Validate | --> | Execute |
| Provider | | Config | | Check |
+-------------+ +-------------+ +-------------+
| |
v v
Config Error? +-----+-----+
| | Result |
v | (Summary) |
Skip Check +-----------+
Visor uses a state machine-based execution engine for orchestrating checks. This provides deterministic execution, observability, and support for complex workflows.
+-------+ +-----------+ +-------------+
| Init | --> | PlanReady | --> | WavePlanning|
+-------+ +-----------+ +-------------+
|
v
+-------------+ +---------------+ |
| Completed | <-- | LevelDispatch | <+
+-------------+ +---------------+
^ |
| v
+-------+ +-------------+
| Error | | CheckRunning|
+-------+ +-------------+
|
v
+-------------+
| Routing |
+-------------+
| State | Description |
|---|---|
| Init | Initialize context, load configuration, build dependency graph |
| PlanReady | Execution plan is ready, dependency graph built |
| WavePlanning | Plan the next execution wave based on dependency levels |
| LevelDispatch | Dispatch checks at the current dependency level |
| CheckRunning | Execute dispatched checks (parallel execution) |
| Routing | Evaluate routing rules (on_success, on_fail, on_finish) |
| Completed | All checks completed, aggregate results |
| Error | Fatal error occurred, cleanup and exit |
Checks are executed in waves based on their dependency levels:
Wave 0: [check-a, check-b] # No dependencies
|
v
Wave 1: [check-c] # depends_on: [check-a]
|
v
Wave 2: [check-d, check-e] # depends_on: [check-c]
Wave Planning Rules:
- Checks with no unmet dependencies are scheduled
- Checks run in parallel within a wave (respecting
max_parallelism) - A new wave starts when all checks in the current wave complete
- Routing can trigger additional waves (on_fail.goto, on_success.run)
The DependencyResolver builds an execution graph:
interface DependencyGraph {
levels: ExecutionGroup[]; // Checks grouped by dependency level
checkDeps: Map<string, string[]>; // Check -> its dependencies
checkDependents: Map<string, string[]>; // Check -> checks that depend on it
}Dependency Features:
- Linear dependencies:
depends_on: [step-a] - OR dependencies:
depends_on: "step-a | step-b"(either can satisfy) - Cycle detection and error reporting
- Skip propagation (failed dependencies skip dependents)
Runtime state is tracked in RunState:
interface RunState {
currentState: EngineState;
wave: number;
levelQueue: ExecutionGroup[];
eventQueue: EngineEvent[];
activeDispatches: Map<string, DispatchRecord>;
completedChecks: Set<string>;
stats: Map<string, CheckExecutionStats>;
historyLog: EngineEvent[];
// ... additional tracking fields
}The ExecutionJournal stores all check outputs for cross-check access:
// In Liquid templates
{{ outputs["previous-check"].result }}
{{ outputs["data-fetch"] | json }}
// In JavaScript expressions (fail_if, transform_js)
outputs["check-name"].issues.length > 0GitHub events are processed by the action entry point:
GitHub Event --> GITHUB_EVENT_PATH --> Event Parser --> Event Trigger
Event Mapping:
| GitHub Event | Action | Visor Trigger |
|---|---|---|
pull_request |
opened |
pr_opened |
pull_request |
synchronize |
pr_updated |
issue_comment |
created |
issue_comment |
issues |
opened |
issue_opened |
Real-time Slack integration without public webhooks:
class SlackSocketRunner {
async start(): Promise<void> {
// Connect to Slack Socket Mode
// Handle app_mention and message events
// Route to engine execution
}
}Event Flow:
- User mentions bot or sends message
- Slack Socket Mode delivers event
- Visor extracts thread context
- Engine executes configured checks
- Results posted back to thread
For custom webhook sources:
checks:
webhook-handler:
type: http_input
endpoint: /api/webhook
transform: |
{% assign data = request.body | json %}
{{ data.message }}External agents and orchestrators send tasks via the A2A protocol:
- Client fetches Agent Card from
GET /.well-known/agent-card.json - Client sends
POST /message:sendwithskill_idin metadata - A2A Frontend authenticates the request (bearer/api_key)
- Task created in TaskStore (state:
submitted) - Skill routing maps
skill_id→ internal workflow name - Engine executes the workflow (same engine as CLI/GitHub Action)
- Task updated with artifacts (state:
completed) - Client receives response (blocking) or polls
GET /tasks/{id}(async)
1. GitHub PR Event
|
v
2. Load Configuration
|
v
3. Build Dependency Graph
|
v
4. Wave Planning (Level 0)
|
v
5. Dispatch Checks -----> [AI Provider]
| |
| v
| 6. AI Analysis
| |
v v
7. Collect Results <----- [ReviewSummary]
|
v
8. Route (on_success/on_fail)
|
v
9. Next Wave or Complete
|
v
10. Aggregate Results
|
v
11. Post PR Comment
checks:
fetch-data:
type: command
exec: "curl -s https://api.example.com/data"
analyze:
type: ai
depends_on: [fetch-data]
prompt: |
Analyze this data:
{{ outputs["fetch-data"] | json }}Output Access:
- Liquid templates:
{{ outputs["check-name"] }} - JavaScript:
outputs["check-name"] - Transform:
transform_js: "return outputs['fetch-data'].items"
Visor uses Liquid templates extensively:
checks:
example:
type: ai
prompt: |
Review this PR:
Title: {{ pr.title }}
Author: {{ pr.author }}
Files changed: {{ pr.files | size }}
{% for file in pr.files %}
- {{ file.filename }} (+{{ file.additions }}/-{{ file.deletions }})
{% endfor %}Available Context:
| Variable | Description |
|---|---|
pr |
PR information (title, body, author, files) |
outputs |
Previous check outputs |
env |
Environment variables |
event |
GitHub event context |
memory |
Memory store accessor |
Create a new provider by extending CheckProvider:
import { CheckProvider, CheckProviderConfig } from './check-provider.interface';
export class CustomProvider extends CheckProvider {
getName(): string { return 'custom'; }
getDescription(): string { return 'My custom provider'; }
async execute(
prInfo: PRInfo,
config: CheckProviderConfig,
dependencyResults?: Map<string, ReviewSummary>,
context?: ExecutionContext
): Promise<ReviewSummary> {
// Implementation
}
// ... other required methods
}
// Register
registry.register(new CustomProvider());Define tools in configuration for use with MCP/AI checks:
tools:
search-docs:
description: "Search documentation"
inputSchema:
type: object
properties:
query: { type: string }
required: [query]
exec: "grep -r '{{ query }}' docs/"
transform_js: |
return { results: output.split('\n').filter(Boolean) }Tools are exposed to AI providers via an ephemeral MCP server.
Connect to external MCP servers:
checks:
code-analysis:
type: ai
ai:
mcpServers:
probe:
command: "npx"
args: ["-y", "@anthropic/probe-mcp"]
prompt: "Analyze the codebase"Or use the MCP provider directly:
checks:
direct-mcp:
type: mcp
transport: stdio
exec: "npx @my-org/tool"
method: analyze
methodArgs:
path: "{{ pr.files[0].filename }}"Create reusable workflows:
# workflows/security-scan.yaml
id: security-scan
inputs:
- name: severity_threshold
default: warning
steps:
scan:
type: ai
prompt: "Scan for security issues..."
outputs:
- name: issues
value: "{{ outputs['scan'].issues }}"Use in main configuration:
checks:
security:
type: workflow
workflow: workflows/security-scan.yaml
args:
severity_threshold: errorCreate event-driven integrations:
class CustomFrontend implements Frontend {
async start(ctx: FrontendContext): Promise<void> {
ctx.eventBus.on('CheckCompleted', async (event) => {
// Handle check completion
});
}
}Frontends receive events via the event bus:
CheckScheduledCheckCompletedCheckErroredHumanInputRequestedStateTransition
Visor includes comprehensive telemetry support for debugging, monitoring, and performance analysis.
Visor supports OpenTelemetry (OTEL) for distributed tracing:
# visor.yaml
telemetry:
enabled: true
sink: otlp # or 'file', 'console'
file:
dir: ./output/traces
ndjson: true
tracing:
auto_instrumentations: true
trace_report:
enabled: trueEnvironment Variables:
| Variable | Description |
|---|---|
VISOR_TELEMETRY_ENABLED |
Enable telemetry (true/false) |
VISOR_TELEMETRY_SINK |
Sink type: otlp, file, console |
VISOR_TRACE_DIR |
Directory for trace files |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
OTLP endpoint (e.g., http://localhost:4318/v1/traces) |
visor.run (root span)
|
+-- engine.state.init
| +-- dependency_resolver.build_graph
|
+-- engine.state.wave_planning
| +-- wave: 0, wave_kind: initial
|
+-- engine.state.level_dispatch
| +-- level_size: 3, level_checks_preview: [check-a, check-b, check-c]
|
+-- visor.check.check-a
| +-- visor.check.id: check-a
| +-- visor.check.type: ai
|
+-- visor.routing (events)
+-- trigger: on_success, action: run, target: next-check
Start the debug visualizer for interactive debugging:
visor --debug-server --debug-port 3456 --config .visor.yamlThe visualizer provides:
- Real-time span streaming
- State machine visualization
- Execution timeline
- Pause/resume/step controls
Visor implements comprehensive error handling at multiple levels.
Each provider handles errors and returns them in the ReviewSummary:
try {
const result = await provider.execute(prInfo, config, deps, context);
return result;
} catch (error) {
return {
issues: [{
severity: 'error',
ruleId: `${checkName}/execution-error`,
message: error.message,
file: '',
line: 0
}]
};
}Checks can define error recovery strategies:
checks:
risky-operation:
type: command
exec: "./risky-script.sh"
on_fail:
retry:
max: 3
backoff:
mode: exponential
delay_ms: 1000
max_delay_ms: 30000
run: [cleanup-step]
goto: safe-fallback
cleanup-step:
type: command
exec: "./cleanup.sh"
safe-fallback:
type: noop
message: "Using fallback due to failure"Enable fail-fast to stop execution on first critical error:
# visor.yaml
fail_fast: true
# Or via CLI
visor --fail-fast --config .visor.yamlErrors propagate through the dependency graph:
check-a (fails)
|
v
check-b (depends_on: [check-a]) --> SKIPPED
|
v
check-c (depends_on: [check-b]) --> SKIPPED
Skipped checks are marked with severity: info and ruleId: checkName/__skipped.
For untrusted code execution, enable workspace isolation:
# visor.yaml
workspace:
enabled: true
base_path: /tmp/visor-workspaces
cleanup: true # Remove after executionThis creates isolated directories for each execution, preventing cross-contamination.
Visor supports three sandbox engines for isolating command execution:
| Engine | Platform | Isolation Model |
|---|---|---|
| Docker | Linux, macOS, Windows | Full container isolation |
| Bubblewrap | Linux only | Linux kernel namespaces (PID, mount, network) |
| Seatbelt | macOS only | macOS sandbox-exec with SBPL profiles |
All three implement the SandboxInstance interface and are routed through SandboxManager. Configure via the sandboxes: block and sandbox: default in .visor.yaml.
See Sandbox Engines for complete documentation.
Sensitive values can be passed via environment:
checks:
api-call:
type: http_client
url: "https://api.example.com"
headers:
Authorization: "Bearer {{ env.API_TOKEN }}"Security practices:
- Never commit secrets to configuration files
- Use GitHub Secrets or environment variables
- Visor does not log environment variable values
The command provider executes shell commands. Mitigate risks by:
- Using explicit paths:
exec: "/usr/bin/grep" - Avoiding shell expansion: Use
argsarray instead of string interpolation - Setting working directory:
workingDirectory: /safe/path - Limiting execution time:
timeout: 30000
When using AI providers:
- API keys are never logged or included in outputs
- Prompts containing sensitive data should use redaction
- Review AI outputs before automated actions (labels, comments)
For webhook endpoints, configure authentication:
http_server:
enabled: true
port: 8080
auth:
type: hmac
secret_env: WEBHOOK_SECRET
header: X-Signature-256Visor provides a persistent key-value memory store:
checks:
store-data:
type: memory
operation: set
key: "session:{{ pr.number }}"
value: "{{ outputs['fetch-data'] | json }}"
retrieve-data:
type: memory
operation: get
key: "session:{{ pr.number }}"
depends_on: [store-data]Memory persists across checks within a single execution but not across separate runs (unless using external persistence).
The ExecutionJournal (snapshot store) maintains:
- Output history per check
- Scoped visibility for nested workflows
- Snapshot points for rollback scenarios
// Read outputs visible to a check
const outputs = journal.readVisible(sessionId, snapshot, scope);
// Write check result
journal.write(sessionId, checkId, scope, result);AI sessions are managed via SessionRegistry:
checks:
first-analysis:
type: ai
prompt: "Initial analysis..."
reuse_ai_session: self # Create named session
follow-up:
type: ai
prompt: "Follow-up question..."
depends_on: [first-analysis]
reuse_ai_session: first-analysis # Reuse session
session_mode: append # or 'clone'Control parallelism via configuration:
max_parallelism: 5 # Execute up to 5 checks concurrentlyOr via CLI:
visor --max-parallelism 10 --config .visor.yamlThe wave-based scheduler automatically optimizes execution:
- Identifies independent checks (no dependencies)
- Schedules them in parallel
- Waits for wave completion before next level
- Respects
max_parallelismlimits
Currently, Visor does not cache AI responses between runs. For expensive operations:
- Use
memoryprovider to store intermediate results - Implement
ifconditions to skip unnecessary checks - Consider external caching for HTTP responses
- Configuration - Configuration file reference
- Sandbox Engines - Docker, Bubblewrap, and Seatbelt isolation
- Security - Security overview and best practices
- Providers - Provider-specific documentation
- Custom Tools - Creating custom tools
- A2A Provider - Agent-to-Agent protocol integration
- MCP Provider - MCP integration details
- Command Provider - Shell command execution
- HTTP Integration - HTTP server and client features
- Debugging - Debugging techniques
src/
index.ts # GitHub Action entry point
cli-main.ts # CLI entry point
config.ts # Configuration loading
state-machine-execution-engine.ts # Main orchestration
types/
config.ts # Configuration types
engine.ts # Engine state types
execution.ts # Execution result types
cli.ts # CLI option types
state-machine/
runner.ts # State machine runner
states/
init.ts # Init state handler
plan-ready.ts # PlanReady state handler
wave-planning.ts # WavePlanning state handler
level-dispatch.ts # LevelDispatch state handler
check-running.ts # CheckRunning state handler
routing.ts # Routing logic
completed.ts # Completed state handler
error.ts # Error state handler
providers/
check-provider.interface.ts # Provider base class
check-provider-registry.ts # Provider registry
a2a-check-provider.ts # A2A agent client provider
ai-check-provider.ts # AI provider (Gemini, Claude, OpenAI)
claude-code-check-provider.ts # Claude Code SDK provider
command-check-provider.ts # Shell command provider
script-check-provider.ts # JavaScript sandbox provider
http-check-provider.ts # HTTP output provider
http-input-provider.ts # HTTP webhook input provider
http-client-provider.ts # HTTP client provider
mcp-check-provider.ts # MCP tool provider
utcp-check-provider.ts # UTCP tool provider
mcp-tools.ts # MCP server management
memory-check-provider.ts # Key-value memory provider
log-check-provider.ts # Debug logging provider
noop-check-provider.ts # No-op orchestration provider
human-input-check-provider.ts # Interactive input provider
workflow-check-provider.ts # Nested workflow provider
git-checkout-provider.ts # Git reference checkout provider
github-ops-provider.ts # GitHub API operations provider
custom-tool-executor.ts # Custom tool execution
event-bus/
event-bus.ts # Event bus for frontends
types.ts # Event envelope types
agent-protocol/
a2a-frontend.ts # A2A HTTP server and endpoints
types.ts # Protocol-agnostic types and error classes
state-transitions.ts # Task state machine validation
task-store.ts # SQLite-backed task persistence
task-queue.ts # Async task execution queue
task-stream-manager.ts # SSE streaming support
push-notification-manager.ts # Push notification delivery
tasks-cli-handler.ts # CLI: visor tasks
index.ts # Re-exports
frontends/
host.ts # Frontend manager
github/ # GitHub integration frontend
slack/ # Slack integration frontend
sandbox/
types.ts # SandboxInstance interface, SandboxConfig
sandbox-manager.ts # Lifecycle management, engine routing
docker-image-sandbox.ts # Docker image-based sandbox
docker-compose-sandbox.ts # Docker Compose sandbox
bubblewrap-sandbox.ts # Linux namespace isolation (bwrap)
seatbelt-sandbox.ts # macOS sandbox-exec isolation
check-runner.ts # Check execution in sandboxes
env-filter.ts # Environment variable filtering
cache-volume-manager.ts # Docker cache volumes
sandbox-telemetry.ts # Telemetry for sandbox operations
utils/
config-loader.ts # Remote config loading
config-merger.ts # Configuration merging
workspace-manager.ts # Workspace isolation
sandbox.ts # JavaScript sandbox
liquid-helpers.ts # Liquid template helpers
telemetry/
opentelemetry.ts # OTEL initialization
trace-helpers.ts # Span creation helpers
fallback-ndjson.ts # File-based trace export
debug-visualizer/
ws-server.ts # WebSocket server for debug UI
The complete configuration schema is defined in src/types/config.ts. Key types:
interface VisorConfig {
version: string;
checks: Record<string, CheckConfig>;
steps?: Record<string, CheckConfig>; // Alias for checks
// Global settings
max_parallelism?: number;
fail_fast?: boolean;
fail_if?: string;
// AI configuration
ai_provider?: string;
ai_model?: string;
ai_mcp_servers?: Record<string, McpServerConfig>;
// Output configuration
output?: OutputConfig;
// HTTP server
http_server?: HttpServerConfig;
// Agent Protocol (A2A)
agent_protocol?: AgentProtocolConfig;
// Workflow imports
imports?: string[];
// Frontends (GitHub, Slack)
frontends?: FrontendConfig[];
// Telemetry
telemetry?: TelemetryConfig;
}
interface CheckConfig {
type: ConfigCheckType;
prompt?: string;
exec?: string;
url?: string;
// Dependencies and routing
depends_on?: string[];
on_success?: RoutingConfig;
on_fail?: RoutingConfig;
on_finish?: RoutingConfig;
// Execution control
if?: string;
forEach?: string | boolean;
timeout?: number;
// Output handling
schema?: string | object;
transform_js?: string;
fail_if?: string;
// Metadata
tags?: string[];
group?: string;
on?: EventTrigger[];
}Events that flow through the state machine:
| Event | Description | Fields |
|---|---|---|
PlanBuilt |
Dependency graph constructed | graph |
WaveRequested |
New wave requested | wave |
LevelReady |
Execution level ready | level, wave |
LevelDepleted |
All checks in level complete | level, wave |
CheckScheduled |
Check dispatched for execution | checkId, scope |
CheckCompleted |
Check finished successfully | checkId, scope, result |
CheckErrored |
Check failed with error | checkId, scope, error |
ForwardRunRequested |
Routing triggered new check | target, gotoEvent, scope, origin |
WaveRetry |
Wave needs re-execution | reason |
StateTransition |
State machine transitioned | from, to |
Shutdown |
Engine shutting down | error? |
| Term | Definition |
|---|---|
| Check | A single unit of work (AI analysis, command, HTTP call, etc.) |
| Step | Alias for check (used interchangeably) |
| Wave | A batch of checks executed in parallel |
| Level | Dependency level in the execution graph |
| Provider | Implementation of a check type |
| Journal | Execution history and output storage |
| Scope | Hierarchical path for nested workflow execution |
| Frontend | Integration point (GitHub, Slack) |
| Routing | Control flow based on check results |
| A2A | Agent-to-Agent protocol — Google's open standard for agent interoperability |
| Agent Card | JSON metadata describing an agent's capabilities, skills, and endpoints |
| Task | A unit of work in the A2A protocol with lifecycle state (submitted → working → completed) |
| Skill | A named capability advertised in an Agent Card, mapped to a Visor workflow |
| Skill Routing | Configuration mapping A2A skill IDs to internal workflow names |
| Task Store | SQLite-backed persistence layer for A2A tasks |
| Task Queue | Async execution queue for processing A2A tasks with concurrency control |
| MCP | Model Context Protocol - standard for AI tool integration |
| UTCP | Universal Tool Calling Protocol - client-side protocol for calling tools directly via native protocols |