Self-serve first run
Sign up, connect GitHub and Slack, pair a self-hosted worker, commit .viewport policy, and prove the first governed agent run.
This is the launchable early-partner path. It assumes hosted Viewport, one self-hosted polling worker, GitHub, Slack, and one sandbox repository.
After the run works once, use the Launch checklist as the team acceptance bar before inviting more users.
The goal is one visible governed run:
Slack or GitHub event
-> .viewport route
-> git-backed policy
-> self-hosted vpd worker
-> plan-only agent
-> human approval
-> implementation agent
-> bounded branch + PR
-> Slack completion
-> receipts and usage1. Create The Workspace
Open the hosted app and create your organization:
https://app.getviewport.comCreate one team for the first proof. Assign at least one reviewer tag that your
policy will use, for example tech-lead. Viewport rejects policy sync when a
human gate points at a reviewer tag that has no matching team member, so fix tag
membership before triggering the first run.
2. Connect GitHub
Install the Viewport GitHub App on a sandbox repository.
The proven first-run path needs:
- contents read/write for checkout and branch publish;
- pull request read/write for the PR side effect;
- webhook delivery to Viewport;
- JSON webhook body.
Use one safe repo first. Do not start with production repos or broad organization-wide installation scope.
3. Connect Slack
Connect Slack and choose the channel for completion receipts.
Slack is a notification surface. Viewport run detail remains the source of truth for approvals, usage, branch/PR receipts, and audit packets.
If the first route starts from Slack, use one controlled launch channel and verify that completion replies to the source thread. A seeded or fixture run may post directly to the configured channel because it has no source Slack timestamp.
4. Install And Pair vpd
Install vpd on the machine that should execute the work:
npm install -g @viewportai/daemon
vpd --versionPair it as a self-hosted worker. Hosted Viewport is the default server:
vpd pair PAIRING_CODE --worker --transport=polling --workdir "$HOME/.viewport/worktrees"For a customer-internal or self-hosted compatible control plane, make the server explicit:
vpd pair PAIRING_CODE --worker --server https://viewport.customer.internal --transport=polling --workdir "$HOME/.viewport/worktrees"Then run doctor:
vpd worker doctor --jsonDoctor should show vpdProfile, the paired server, workspace root, worker
identity, transport polling, workspaceId, and detected capabilities.
workspaceId should match the workspace where you generated the pairing code.
If you use --profile, vpdProfile.home should be the profile directory you
expect before you start the worker.
The workdir is recorded during pairing. If you change it later, reset and pair
again so vpd worker start uses the intended workspace root.
5. Commit .viewport/
Declarative Viewport config is committed to Git. It is not local daemon state.
Minimum layout:
.viewport/
policy.yaml
routes/
github-pr.yamlRun this before pushing:
vpd check .vpd check validates policy and route syntax. It also warns if .viewport/ is
gitignored, which would prevent the control plane from reconciling your policy.
Minimal policy.yaml:
version: 1
repos:
- repo: acme/sandbox
access: read-write
credential: brokered
branches:
push_allowed: ["agent/**"]
restricted: ["main"]
invoke:
agent: claude-code
notify:
provider: slack
credential_ref: slack/oauth
channel: C1234567890
gates:
- name: plan-review
type: plan
reviewers:
tags: ["tech-lead"]
timeout: 4h
on_timeout: escalateMinimal routes/github-pr.yaml:
route:
name: github-pr-review
team: platform
trigger:
integration: github
events: ["pull_request.*"]
conditions:
repo: acme/sandbox
policy:
source: git
repo: acme/sandbox
ref: main
path: .viewport/policy.yaml
priority: 20Minimal Slack support route:
route:
name: slack-support
team: platform
trigger:
integration: slack
events: ["app_mention"]
conditions:
channel: C1234567890
mentionsAny:
- viewport
policy:
source: git
repo: acme/sandbox
ref: main
path: .viewport/policy.yaml
priority: 306. Register The Sync Source
In the app, register the repository/path that contains .viewport/.
Healthy sync shows:
- sync status
synced; - latest short SHA;
- route count;
- no repeated HMAC failures;
- a composed policy workflow version.
If sync is failing, fix this before triggering a run. A worker cannot execute a policy the server has not reconciled.
7. Start The Worker
Start the worker after the policy is synced:
vpd worker start --mode persistent --transport pollingUse the command shown in the app as the source of truth. New self-hosted
runners use vpd pair --worker ... followed by vpd worker start ....
8. Trigger And Approve
Open or update a pull request in the sandbox repo, or mention the app in the configured Slack launch channel if your first route is Slack-backed.
Expected run shape:
checkout -> plan -> gate_plan-review -> agent -> publish -> open_pr -> notify_completionThe run should pause at the plan review gate. Approve it in Viewport. After
approval, the worker resumes, implements, publishes an agent/** branch, opens
a GitHub PR or provider proposal, and posts the Slack completion. For
Slack-originated runs with a source timestamp, the completion should reply in
the source thread.
9. Verify The Run
A good first run shows:
- matched route and git-backed policy;
- composed workflow version and policy hash;
- brokered GitHub checkout;
- plan-only node with no write/shell tools;
- human approval with approver and timestamp;
- implementation node usage and cost when reported by the adapter;
- bounded branch and GitHub PR URL;
- Slack completion permalink;
- Slack source thread for Slack-originated runs;
- execution receipts with provider URLs;
- redacted action bodies and no runtime secrets in stored metadata.
Troubleshooting
Start here:
- Read run detail
- Route does not start a run
- Runner won't claim work
- Approval or plan revision is stuck
- Pairing fails or loops
- Daemon won't connect
- Debug recipes
- Support packet
Common first-run failures:
| Symptom | Likely cause | Check |
|---|---|---|
.viewport/ never syncs | Directory is gitignored or not pushed | vpd check . and git status --ignored .viewport |
| Policy sync errors on reviewer tags | Reviewer tag has no matching member | Team settings reviewer tags, then sync again |
| Worker never claims | Worker not paired, wrong runner pool, missing capability | vpd worker doctor --json |
| Worker exits with "already running" | Another persistent worker is active for this server/workspace/executor | Stop the old worker with vpd worker stop, then restart; for proof-only execution use vpd worker run-once --lease <lease-token> --transport polling |
| Checkout fails | GitHub App not installed or credential grant missing | Integration settings and run node error |
| Slack notification skipped | Slack integration is disconnected, the policy channel is wrong, or the provider grant failed closed | Integration settings, invoke.notify.channel, and the Slack receipt/error on run detail |
| Slack posts to the channel instead of the source thread | Source timestamp missing or renderer/threading regression | Run detail source event and Slack receipt permalink |
Capture a sanitized support packet before asking for help:
vpd worker doctor --json > /tmp/vpd-worker-doctor.json
vpd status --json > /tmp/vpd-status.json
vpd check . --json > /tmp/vpd-check.jsonThen copy the workspace id, sync source id, workflow version id, run id, route name, runner name, and any denial code shown in the app.
Do not include raw tokens, pairing codes, claim tokens, lease tokens, worker private keys, or provider credentials. See Support packet for the full handoff checklist.