Workflows
Repo-local YAML contracts that bind plans, gates, context, and runs.
A workflow is a YAML file your team commits at .viewport/workflows/NAME.yaml. It declares: under what triggers an agent runs, what context to attach, which steps need a human gate, and where decisions route. Workflows are validated against a JSON Schema, persisted as versioned definitions, and executed as runs.
The contract should travel with the code. There is no UI-only state that drifts from what's checked in.
Schema
Canonical schema: packages/daemon/schemas/workflow-v1.schema.json.
Top level:
schema: viewport.workflow/v1
name: review-auth-change
title: Review an auth middleware change
description: Plan-first auth refactor with reviewer sign-off.
inputs:
branch: { type: string }
paths: { type: json }
context:
- ctx_acme_runbooks # vault attachment
requires:
agents: [claude-code]
tools: []
integrations: []
secrets: []
nodes:
inspect: { type: read_only }
plan: { type: plan, review: { required: true, reviewers: team_platform } }
execute: { type: apply_plan, gate: { destructive: ask } }
capture: { type: capture_outputs }Validate locally:
vpd workflow validate path/to/workflow.yamlinputs[*].type accepts string, number, boolean, json. outputs[*].type adds file and artifact.
The five built-in node types
type | Behavior |
|---|---|
read_only | Inspect the repo. No file writes, no shell. |
plan | Produce a plan artifact. Optionally gated on review. |
apply_plan | Execute an approved plan. Per-action gates apply. |
shell | Run a specific command. Allow-listed and logged. |
capture_outputs | Persist run artifacts. |
Custom node types come from the workflow plugin SDK below.
Runs
Every invocation creates a workflow run with its own page in the app. Steps stream live; gated steps pause and surface a decision. The run carries every plan, gate, and decision back to the workflow that produced it.
Data capture policy
The daemon decides what content the platform stores per run. Three independent flags travel on the run launch:
{
"transcripts": true | false,
"logs": true | false,
"artifacts": true | false
}Off means the platform stores none of that content. On means it persists what the daemon emits. The daemon is the source of truth for what gets sent.
Execution policy
Per run, the daemon can isolate execution:
{ "mode": "current_tree" } // run in the active working tree
{ "mode": "isolated_worktree" } // create a fresh worktree
{ "mode": "named_branch", "branch": "auto/auth-fix" } // run on a specific branchSee vpd worktree for the operator-side primitives.
Approvals
A plan or apply_plan node can declare a managed approval gate. When such a node fires, an inbox item appears for the configured audience. The decider hits Approve or Deny; the daemon reads the result over its workflow-run subscription and continues or aborts.
CLI helpers:
vpd workflow validate PATH
vpd workflow run PATH [--input k=v]...
vpd workflow runs --limit 20
vpd workflow show RUN_ID
vpd workflow rerun RUN_ID
vpd workflow approve RUN_ID NODE_KEY [--message]
vpd workflow cancel RUN_IDThe plugin SDK
@viewportai/workflow-sdk lets you write custom node types.
import { defineNode } from '@viewportai/workflow-sdk';
export const lintNode = defineNode({
type: 'eslint.lint',
contract: 'viewport.workflow-plugin/v1',
async run(ctx, inputs) {
// ctx exposes the run context, secrets, artifacts, logs.
return { ok: inputs.errors === 0 };
},
});Plugins are loaded from ~/.viewport/plugins.json at daemon startup.
Preflight
Dry-run a workflow before persisting it:
vpd workflow validate workflow.yamlOr via the API:
POST /api/resources/{workspace}/workflow-runs/preflightPreflight validates the YAML against the schema, resolves resources, and reports what would run without creating a run record.