Sparkwing

CI/CD in code
Runs Local | compile-checked | agent-first

Pipelines in Go,
not YAML

Sparkwing replaces brittle YAML with a typed Go DSL that humans and AI agents can actually understand. Reuse steps as standard Go modules. Run locally or on a cluster, with an agent-first CLI and offline docs built in.

Install and run in 2 easy steps

Requires Go 1.26+ - install Go.

Install

curl -fsSL https://sparkwing.dev/install.sh | sh

Works on macOS, Linux, & Windows (bash or WSL) - arm64 & amd64. Verifies SHA-256 before installing.

Run and view the dashboard

sparkwing pipeline new --name ci --template build-test-deploy

sparkwing run ci

sparkwing dashboard start

Watch an agent build a pipeline

Pipelines run locally with fast iteration loops and structured, clear log and error output - the exact debug surface AI agents need. Embedded offline docs, clear examples, and a composable module ecosystem help them build highly efficient pipelines with zero guesswork.

Demo video coming soon

An AI agent installs sparkwing and builds a multi-stage pipeline end-to-end.

One CLI, everything you or an agent needs

Run pipelines, start the dashboard, scaffold new templates, read embedded docs, manage secrets, view old runs, surface logs or errors, install pre-commit or pre-push sparkwing hooks, and more. No web access required.

CLI screenshots coming soon

Real terminal output: structured -o json, embedded docs, tab completion, every verb in one binary.

The Dashboard you’ve dreamed of

Information-dense and easily parsed. Sparkwing visualizes your runs the way you’ve always wanted, featuring a triage view, built-in metrics for failure rates, common errors, and insane grep-ability. Seamlessly explore the DAG, jump straight to errors, and filter by log adjacency. Packed with useful features like quick keyboard navigation, full pipeline log search, and step and job summary views. If there’s a feature you’re missing, just let us know. We’re building this for you!

Dashboard screenshot coming soon

Run timelines, step output, replay - one UI for local and cluster runs.

Stop debugging YAML indentation

Sparkwing replaces fragile, sprawling YAML with a strongly-typed Go DSL. It brings robust library support, native autocomplete, and advanced build caching directly into your workflow definitions. Compile and catch errors locally before you ever push to the cloud, easily import legacy bash scripts, and build declarative dependency graphs that do exactly what you expect.

Before

release.yml

name: Release
on:
  push:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

permissions:
  id-token: write
  contents: read
  packages: write

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.26'
      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: ${{ runner.os }}-go-
      - name: Download deps
        run: go mod download
      - name: Run tests
        run: go test -race -coverprofile=coverage.out ./...
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: coverage.out
          token: ${{ secrets.CODECOV_TOKEN }}

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.CI_ROLE }}
          aws-region: us-west-2
      - uses: aws-actions/amazon-ecr-login@v2
      - uses: docker/setup-buildx-action@v3
      - name: Cache buildx layers
        uses: actions/cache@v4
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: ${{ runner.os }}-buildx-
      - name: Build + push
        run: |
          TAG=$(git rev-parse --short HEAD)
          docker buildx build \
            --platform linux/arm64,linux/amd64 \
            --cache-from type=local,src=/tmp/.buildx-cache \
            --cache-to type=local,dest=/tmp/.buildx-cache-new \
            --tag $ECR/myapp:$TAG --push .
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Bump gitops manifest
        env:
          GH_TOKEN: ${{ secrets.GITOPS_TOKEN }}
        run: |
          git clone https://x-access-token:$GH_TOKEN@github.com/org/gitops.git
          cd gitops
          sed -i "s|myapp:.*|myapp:$TAG|" envs/prod/values.yaml
          git -c user.email=ci@x -c user.name=ci \
              commit -am "bump myapp $TAG" && git push
      - name: Sync ArgoCD
        env:
          ARGO_TOKEN: ${{ secrets.ARGOCD_TOKEN }}
        run: |
          argocd login argocd.example.com --grpc-web --auth-token $ARGO_TOKEN
          argocd app sync myapp --grpc-web
          argocd app wait myapp --health --grpc-web

  notify:
    needs: deploy
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Notify Slack
        uses: slackapi/slack-github-action@v1
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
        with:
          payload: |
            {"text":"Release ${{ needs.deploy.result }}: ${{ github.sha }}"}
After - Sparkwing

release.go

import (
    sw "github.com/sparkwing-dev/sparkwing/sparkwing"
    "github.com/sparkwing-dev/sparks-core/cache"
    "github.com/sparkwing-dev/sparks-core/deploy"
    "github.com/sparkwing-dev/sparks-core/docker"
    "github.com/sparkwing-dev/sparks-core/gotest"
    "github.com/sparkwing-dev/sparks-core/notify"
)

type Release struct{ sw.Base }

func (Release) Plan(ctx context.Context, plan *sw.Plan, _ sw.NoInputs, rc sw.RunContext) error {
    ecr := "123.dkr.ecr.us-west-2.amazonaws.com"

    test := sw.Job(plan, "test", &gotest.Run{
        Race:  true,
        Cover: true,
    }).Cache(cache.GoModules())

    build := sw.Job(plan, "build", &docker.BuildAndPush{
        Image:      "myapp",
        Dockerfile: "Dockerfile",
        Registries: docker.DetectRegistries("prod", ecr),
        Platform:   "linux/arm64,linux/amd64",
    }).Cache(cache.BuildxLayers()).Needs(test)

    deployed := sw.Job(plan, "deploy", &deploy.GitopsArgoCD{
        GitopsRepo: "git@github.com:org/gitops.git",
        GitopsPath: "envs/prod",
        ECR:        ecr,
        Images:     []string{"myapp"},
        AppName:    "myapp",
    }).Needs(build)

    sw.Job(plan, "notify", &notify.Slack{
        Channel: "#deploys",
        Message: "Release {{.Status}}: {{.GitSHA}}",
    }).Needs(deployed).AlwaysRun()

    return nil
}

Four ways to host

Lightest to heaviest. Pipeline code stays the same as you scale - and all three shared modes give the team a centralized dashboard.

Local

Lightest

SQLite + filesystem. Zero infrastructure.

For: Trying sparkwing out, solo work, fast dev loops.

Shared object storage

Light

N runners write to the same bucket (S3, GCS, Azure Blob). One shared dashboard, shared caches, no database to operate. Buffers writes offline and syncs when reachable.

For: Teams keeping GitHub Actions runners but moving pipeline logic to typed, composable Go. Or any team that wants a unified dashboard and shared cache without operating Postgres.

Postgres + object storage

Heavier

Add Postgres for state and real-time coordination. Exactly-one execution for cached steps; triggers, approvals, and debug pauses work across runners.

For: Teams that have outgrown shared-bucket - expensive cached work where coalescing matters, or workflows that need cross-runner triggers and approvals.

Cloud or self-hosted controller

Heaviest

A central controller owns the DB, dispatches jobs to worker pools, and serves the dashboard. Auth (tokens, sessions), trusted-team workflows, optional laptop-as-worker registration to save costs or increase efficiency.

For: Organizations with dedicated infrastructure and team-scale workflows.

Ready to ditch the YAML?

Get up and running in under 60 seconds with Go pre-installed. No account required, no cloud dependency.