Pipelines in code
not YAML

Local or cloud | Burst capacity wherever | Built for agents

The accelerant your agents need to test and ship with confidence.

Sparkwing shifts your pipelines left, to your local machine. Then, with intelligent admission control, Sparkwing squeezes every machine you've got. It distributes load across your laptop, gaming PC, team's laptops, and free GitHub Actions minutes. Then, burst to Sparkwing Cloud to keep your agents going. Pipelines are Go or TypeScript, so one shared library fixes every service. This is the new era of CI/CD. Cheaper, faster, clearer.

See a green pipeline locally in 60s
curl -fsSL https://sparkwing.dev/install.sh | shsparkwing pipeline new --name release --template build-test-deploysparkwing run releasesparkwing dashboard start# then view at localhost:4343
Let your agent set it up

Run `sparkwing info || curl -fsSL https://sparkwing.dev/install.sh | sh` and follow its guidance. Create a lint pipeline with `sparkwing pipeline new --name lint --on pre_commit`, make it run this repository’s linters and formatters, and get it passing with `sparkwing run lint`. Then install it as a git hook with `sparkwing pipeline hooks install`.

Go pipelines need Go 1.26+. TypeScript pipelines need Node.js 22.18+. macOS, Linux, and Windows with Git Bash.

CI/CD Reimagined - Because it needed to be

Some of my favorite work in my career has been within CI/CD, but the tooling itself was always full of frustrations. I’m not sure why we stepped away from using real code to author pipelines, but I wanted to change that. In fact, there were a lot of things I wanted to change.

My first motivation for making Sparkwing was having sprawling bash scripts for 14 different services that were hard to verify, trace, and change. I thought - wouldn’t it be nice if I could just write Go?

My second motivation was writing GitHub Actions pipelines, and feeling a huge amount of pain with the way their conditionals worked. The strict YAML rules simply couldn’t handle the types of pipelines I was trying to write. I could either write 32 efficient pipelines, or 1 inefficient one. I chose inefficient - because who has the time. Code was also the answer here.

Once I finally had enough frustrations, I decided to make my own platform. And, what an exciting journey it’s been. I’ve been able to load Sparkwing with absolutely unique and amazing features. It’s such a joy to set up, to use, and to develop. It’s easy for people to troubleshoot, but easy for agents too. My agents have gotten way more productive working with my pipelines, and so have I.

I hope you’ll try it. I’m confident you’ll love it. And if you don’t, let me know. I am committed to making this the best system you’ve ever used.

Korey GambillCreator of Sparkwing

A new way to do CI/CD

Run a pipeline on cloud runners, your own machines, or both. Give each job runner requirements to control where it executes.

Example release pipeline
Memoized = result reused; job does not runCache = files restored; job still runs
A release pipeline with memoized tests, a build per architecture, staging checks, an approval and a production deployLint and unit tests reuse memoized results without running. Integration tests run on your desktop or your team pool. The image then builds natively for amd64 in the cloud, after restoring its Docker layer cache from S3, and for arm64 on your Mac or a team Mac mini. The release deploys to staging, a separate synthetics pipeline checks it, a person approves, and it deploys to production. Select a job to see its required labels.LintCloud · LinuxMemo lookup pendingUnit testsCloud · LinuxMemo lookup pendingIntegration testsYour desktop · LinuxWaitingBuild amd64Cloud · amd64WaitingBuild arm64Your Mac · arm64WaitingDeploy to stagingCloud · LinuxWaitingSmoke checksCloud · LinuxWaitingApprove releaseNeeds a personWaitingDeploy to prodCloud · LinuxWaiting

Runner requirements

Build arm64

Builds the arm64 image natively on your Apple silicon Mac, where the layers are already warm.

.Requires("arch=arm64", "name=my-mac")

name=my-mac selects that authenticated runner. Every requirement must match.

Your computers, connected

Connect your own runners on Linux, macOS, or Windows

Connect a computer to your team’s controller to run selected jobs alongside cloud runners. Choose which repositories it accepts and how much CPU, memory, and concurrency it contributes.

Runner setup documentation ↗
RunningCompletedMemoized · not re-runSelect a job to see its labels

Where the time goes

The fastest job is the one that never runs.

Sparkwing cuts a run down in four passes. The last one is your laptop, where the cache is already warm.

One release run
Typical hosted CI run

Bar widths are illustrative. 14 seconds observed in a cached local-to-Kubernetes workflow; timing varies with the changes, cache state and cluster.

Execution / access control

Security guardrails

Control which code a machine accepts, where controller-dispatched steps run, and what must be approved before deployment.

✓ DeployedJob → runner → deployment

Checkpoint illustration: approved jobs pass all three gates. Repositories outside the allowlist stop at gate two. Risk-labeled steps wait at gate three for explicit approval.

01Pool

Choose approved runners

Target your own machine for preview work and an approved runner for sensitive deployment jobs. Team admins reserve runners for a pool and choose which repositories and branches may use it.

.Requires("pool=prod")
02Repository

Limit what a machine accepts

Repository allowlists restrict the source a runner may build. Set concurrency, CPU, and memory limits on each machine, and revoke its runner token when access should end.

--repos 'your-team/*'
03Risk approval

Gate sensitive operations

Pipeline guards check where a run executes, its branch, and its arguments before dispatch. Risk-labeled steps require explicit authorization before they execute.

step.Risk("prod", ...)

Runner selection controls placement; guards do not prohibit every local invocation. Pipeline code runs as the runner’s OS user. Use an isolated environment for shared runners and restrict production credentials to approved machines.

Inside Sparkwing

Your pipeline.
Every point of view.

One CLI and dashboard, built for humans and agents alike.

Recorded on the production console. Credentials and other sensitive values are hidden.

sparkwing / console01 / 02

From a run to its job logs

Open a run, inspect its DAG, and read the logs from a job.

Same jobs. A different language.

Skipping a job shouldn’t take a job.

In Actions, deciding to skip costs a runner, and every job after it needs an if: always() to keep going. In Sparkwing, a memoized job never starts one.

GitHub Actions28 lines to skip one job
jobs:
check-cache:Boots a runner just to decide
runs-on: ubuntu-latest
outputs:
hit: ${{ steps.lookup.outputs.cache-hit }}
steps:
- uses: actions/checkout@v4
- id: key
run: echo "key=it-${{ hashFiles('src/**', 'go.sum', 'migrations/**') }}" >> "$GITHUB_OUTPUT"
- id: lookup
uses: actions/cache/restore@v4
with:
path: .it-passed
key: ${{ steps.key.outputs.key }}
lookup-only: true
integration-tests:
needs: check-cache
if: needs.check-cache.outputs.hit != 'true'Skips the tests
steps:
# ...run the tests, then leave a marker for next time
- run: touch .it-passedRemembers that they passed
- uses: actions/cache/save@v4
build-image:
needs: [check-cache, integration-tests]
if: ${{ always() && !failure() && !cancelled() }}Or the skip cancels the build too
deploy:
needs: build-image
if: ${{ always() && needs.build-image.result == 'success' }}And again for every later job
Sparkwing2 lines
unchanged := inputs.Files("src/**", "go.sum", "migrations/**")What the tests read
tests := sw.Job(plan, "integration-tests", runTests).Memo(unchanged)Skip when it hasn’t changed
build := sw.Job(plan, "build-image", buildImage).Needs(tests)
sw.Job(plan, "deploy", deploy).Needs(build)

Downstream jobs read the memoized result and run normally. No guards, no marker files, no runner.

LOCAL CODE. MORE HORSEPOWER.

Your laptop runs what fits.
Your other machines take the rest.

Sparkwing Cloud catches the overflow. It all starts from one local run of your uncommitted code.

OFFLOAD / LOCAL RUNsparkwing run <pipeline>

Your laptop

Uncommitted code

  • linthere
  • unit testshere
  • buildqueued
  • e2e testsqueued
CPUTHROTTLING
01 / YOUR MACHINES FIRST

Gaming desktop

Ready for work

Spare server

Ready for work

02 / LAST RESORT

Sparkwing Cloud

Standing by

Warm Linux runners
Sparkwing Cloud · managed controller + storageYour working tree stays the starting point

Agents are working. Lint, unit tests, the build and e2e tests all queue on your laptop, and the CPU throttles.

Cloud agents get the same local feel. Run pipelines on demand, instead of pushing a GitHub Actions run and polling for it.

Three ways to host.

Choose an option to light up what it provides.

Ready to ditch the YAML?

See a green pipeline locally in 60s
curl -fsSL https://sparkwing.dev/install.sh | shsparkwing pipeline new --name release --template build-test-deploysparkwing run releasesparkwing dashboard start# then view at localhost:4343
Let your agent set it up

Run `sparkwing info || curl -fsSL https://sparkwing.dev/install.sh | sh` and follow its guidance. Create a lint pipeline with `sparkwing pipeline new --name lint --on pre_commit`, make it run this repository’s linters and formatters, and get it passing with `sparkwing run lint`. Then install it as a git hook with `sparkwing pipeline hooks install`.

Go pipelines need Go 1.26+. TypeScript pipelines need Node.js 22.18+. macOS, Linux, and Windows with Git Bash.

Read the getting started guide →