Skip to content

Rituals

Rituals are VeloGit’s built-in pipeline system. They run automatically on push events, verifying your code and feeding results back into the agent ecosystem.

Rituals are defined in a .velo/rituals.yaml file at the root of your repository. The file has two top-level sections: environment and rituals.

environment:
language: go
version: "1.25"
packages: [ postgresql, redis ]
runner:
size: medium # small | medium | large | xlarge
rituals:
- name: build
on: [ push ]
command: "go build -v ./..."
critical: true
- name: test
on: [ push ]
command: "go test -v ./..."
critical: true
- name: lint
on: [ push ]
command: "go fmt ./..."
FieldDescription
environment.languageRuntime language (e.g., go, node, python)
environment.versionLanguage version
environment.packagesSystem-level dependencies to provision
rituals[].nameUnique name for this ritual step
rituals[].onEvents that trigger this ritual (e.g., push)
rituals[].branchesBranch glob patterns that trigger this step (e.g., main, release/*). Omit to run on all branches.
rituals[].commandShell command to execute (omit for gate steps)
rituals[].typecommand (default) or gate. A gate step has no command; execution pauses until approved or rejected
rituals[].afterList of step names that must complete (pass or be approved) before this step runs
rituals[].criticalIf true, a failure or rejection stops all subsequent steps
rituals[].timeoutMax wall-clock time for this command step (Go duration: 30s, 5m, 1h30m). Default: 30m. Ignored on gate steps.
rituals[].gate_timeoutMax time a gate can be pending before it expires (Go duration: 30m, 8h). Default: 48h. Ignored on command steps.
rituals[].artifactsFiles produced by this step and uploaded after a successful run. See Artifacts.
rituals[].needs_artifactsNames of artifacts to fetch from earlier steps in the same run before this step runs.
runner.sizeMachine size for this repo’s runners: small (default), medium, large, or xlarge
pipeline.timeoutMax wall-clock time for the full pipeline run (Go duration). Default: 2h. Gate-paused time in earlier segments is not counted — only execution time in the current runner machine.

By default, each ritual run on a small machine. For resource-intensive workloads — large test suites, Docker builds, compilation-heavy projects — you can request a larger machine via the runner block:

runner:
size: large
SizeCPUMemoryBilling multiplier
small1 vCPU256 MB
medium2 vCPU512 MB
large4 vCPU2 GB
xlarge8 vCPU8 GB

The runner.size setting applies to all ritual steps in the repository. You cannot set different sizes per step.

VeloGit enforces wall-clock limits at three levels:

pipeline:
timeout: 45m # whole-pipeline budget (default 2h)
rituals:
- name: build
on: [ push ]
command: "go build ./..."
timeout: 10m # per-step cap (default 30m)
- name: integration-tests
on: [ push ]
command: "./scripts/run-integration.sh"
timeout: 20m
- name: approve-deploy
on: [ push ]
type: gate
gate_timeout: 8h # gate expiry (default 48h)

All timeout fields accept Go duration strings: 45s, 10m, 1h30m, etc.

Step timeout — each command step is killed when it exceeds its timeout. A timed-out step is reported as failed with output prefixed by Command timed out after <duration>. If the step is critical: true, the rest of the pipeline is skipped.

Gate timeout — each gate step has a gate_timeout (default 48h). If a gate is not approved or rejected within this time, it expires and is treated as a rejection, skipping all subsequent steps.

Pipeline timeout — checked between steps. If the cumulative wall-clock time of the current runner machine exceeds pipeline.timeout, all remaining steps are marked skipped and the run ends. Time spent paused at a gate (with the runner machine destroyed) is not counted.

Timeouts apply equally to remote runs and to velo run executed locally.

Steps can produce artifacts — files saved after a step completes successfully and made available to later steps in the same pipeline run, including steps that execute on a fresh machine after a gate approval.

rituals:
- name: build
on: [ push ]
command: "go build -o dist/myapp ./..."
critical: true
artifacts:
- name: binary
paths: [ "dist/" ]
- name: integration-test
on: [ push ]
command: "./dist/myapp --self-test"
needs_artifacts: [ binary ] # extracted into the workspace before the command runs
critical: true
- name: approve-deploy
on: [ push ]
type: gate
- name: deploy
on: [ push ]
command: "./scripts/deploy.sh dist/myapp"
needs_artifacts: [ binary ] # fetched onto the fresh post-gate machine

After a step with artifacts: finishes successfully, the runner walks each paths: entry (literal paths or globs, workspace-relative), tar+gzips the matched files, and uploads the bundle to VeloGit storage tagged with the run ID. When a later step declares needs_artifacts:, the runner downloads each bundle and extracts it into the workspace before the command starts.

This is the canonical solution to the cross-gate workspace problem: ritual machines are destroyed when a gate is reached, so without artifacts a post-gate step would need to rebuild from scratch. With needs_artifacts, the build output flows through the gate transparently.

Per-artifact size1 GB max (rejected at upload)
SymlinksSkipped at upload; rejected on download to prevent workspace escape
Path traversalTar entries with absolute paths or .. are rejected
VisibilityOrg-member only — even on public repos, artifacts are not publicly downloadable
RetentionArtifacts cascade-delete with their pipeline run
Failed stepsArtifacts are not uploaded when the step itself fails

velo run ignores artifacts: and needs_artifacts: declarations because the bind-mounted workspace already persists between steps inside the single local container. The fields parse cleanly; they’re just no-ops locally. Remote runs honour them.

Ritual steps run in a minimal, sanitised environment. Infrastructure secrets from the runner process (Zitadel keys, internal API keys, NATS credentials, data-plane URLs) are deliberately stripped — they would otherwise leak to any pipeline step via printenv, and a malicious commit in any repo could exfiltrate them.

What your step does receive:

SourceVariables
System defaultsPATH, HOME, USER, LOGNAME, SHELL, TERM, LANG, LC_*, TZ, PWD, TMPDIR
Per-run helpersVELO_COMMIT_SHA, VELO_BRANCH, VELO_REF, VELO_REPO, VELO_RUN_ID
Your org/repo secretsEverything you’ve configured in Settings → Secrets

What your step does not receive: anything else from the runner process. If your pipeline needs a credential to call an external service, store it as a repo or org secret rather than relying on an env var being inherited.

By default a ritual step runs on every push regardless of branch. Add a branches field to restrict it to specific branches using glob patterns:

rituals:
- name: test
on: [ push ]
command: "go test ./..."
# no 'branches' — runs on every push to every branch
- name: deploy-staging
on: [ push ]
branches: [ develop ]
command: "./scripts/deploy.sh staging"
critical: true
- name: deploy-production
on: [ push ]
branches: [ main, release/* ]
command: "./scripts/deploy.sh production"
critical: true

Glob patterns follow standard shell rules: * matches any characters within a single path segment (e.g., release/* matches release/1.0 but not release/1.0/hotfix).

A ritual step can be designated as a gate — a manual checkpoint that pauses the pipeline until a team member explicitly approves or rejects it. Gates are useful for requiring human sign-off before deploying to production.

rituals:
- name: build
on: [ push ]
command: "go build ./..."
critical: true
- name: approve-deploy
on: [ push ]
type: gate # pauses here until approved
- name: deploy
on: [ push ]
command: "./scripts/deploy.sh production"
after: [ approve-deploy ]

When a pipeline reaches a gate step, the runner machine reports waiting_gate to the data-plane and exits cleanly. No machine is running or billing while the gate is pending.

When a team member approves the gate in the UI, the dispatcher boots a new ephemeral machine that resumes execution from immediately after the approved gate — using the same runner image and commit SHA. If the gate is rejected, all remaining steps are marked skipped and the pipeline closes.

Blocked gates appear in the Pipelines tab of any repository, where members can approve or reject them directly from the UI. The page polls automatically every few seconds, so approve/reject buttons appear without requiring a page refresh.

When a single step fails — a flaky test, a transient registry timeout, an out-of-quota deploy — you can re-run that step in place rather than restarting the whole pipeline. Each command step on a terminal pipeline run (passed, failed, rejected, canceled) has a Retry from here button next to it.

The named step and every step transitively downstream of it via After: are reset to pending. Anything upstream keeps its prior result.

Given this pipeline:

rituals:
- { name: lint, on: [push], command: "go vet ./..." }
- { name: test, on: [push], command: "go test ./...", after: [lint] }
- { name: build, on: [push], command: "go build ./bin/app", after: [test] }
- { name: publish, on: [push], command: "./scripts/publish.sh", after: [build] }

If test failed and you click Retry from here on it:

  • lint stays passed (not reset)
  • test, build, publish all reset to pending
  • A fresh runner boots; test runs first because its dependency lint is already satisfied; build and publish follow naturally

If instead you click Retry from here on build, only build and publish reset — test’s previous passed result is preserved.

  1. The data-plane reads .velo/rituals.yaml at the run’s commit SHA — not HEAD. The After: graph in effect at the time of the original run is what’s used to compute the reset set.
  2. Affected PipelineStep rows have their status set to pending, and logs / started_at / finished_at / approved_by cleared.
  3. The PipelineRun row flips back to running.
  4. A velo.pipeline.rerun NATS event is published. The dispatcher boots a fresh runner machine with the same runner image, with VELO_PIPELINE_RUN_ID and VELO_RERUN_FROM_STEP set.
  5. The runner enters the same resume code path used for gate approvals. fetchCompletedSteps returns only the rows whose status is in {passed, approved, rejected, failed, skipped} — the rows we reset are pending so they’re skipped. readySteps picks them up in dependency order and runs them.
  • The After: graph is read at the run’s commit. If you rewrote rituals.yaml after the original run but want to retry that older run, you’re retrying with the original dependency graph. To pick up a new graph, push the new yaml and let the next push trigger a fresh run.
  • Gates can’t be individually retried via this button — they have their own approve/reject affordances. To re-run a gate decision, click Rerun at the run level (whole-pipeline restart from a fresh commit).
  • Artifact output from prior runs of a re-run step is overwritten. If a step uploaded an artifact, the new execution replaces it. Steps downstream that needs_artifacts: get the new version.

Rituals and agents work together. When a ritual completes, it emits a Verification Fragment that is displayed in the Ghost Workspace and on Pull Request timelines alongside any agent code suggestions. This gives reviewers confidence that a proposed change has already been tested before they meld it.

The Rituals tab in any repository shows a live-updating history of all pipeline runs, their step-by-step logs, and current status. Active runs poll automatically every few seconds.