Skip to content
xavidopPublic

About

Pipeline engine for Go: define typed, testable CI/CD and automation workflows as code, then run them locally, in containers, on Kubernetes or over SSH, with live attach, caching, retries and monorepo support.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Latest commit

 

History

76 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

senro: a horizontal transit line with four stations along it, and a spur branching off partway to a fifth station

線路  senro

A pipeline engine, defined in Go

Define a pipeline as ordinary Go code, run it locally, in containers, on Kubernetes or over SSH, and attach to it live from a second terminal or a browser while it runs.

Go Reference CI Go Report Card License: MIT


senro (線路, Japanese for railway track) is a pipeline engine you program instead of configure. Your Go code builds an immutable graph of workflows and steps, senro resolves it into a plan, executes it, and exposes one append-only event stream that a terminal UI, a browser page, or a script can attach to, live, to watch and steer the run.

CI/CD is the most familiar thing to build with it, but not the boundary: a data pipeline, a batch job, an infrastructure rollout or a release script is built the same way, out of steps with dependencies, retries, failure handlers, and one event stream.

Why

CI systems ask you to describe real programming (dependency graphs, retries, branching, recovery) in YAML, which has no functions, no types, no tests, and no debugger. So the logic ends up in shell scripts the YAML merely invokes.

senro starts from the other end: the pipeline is the Go program. For the thing that decides whether your build ships, you get a real language's tooling: the compiler, types, go test, and a debugger.

Features

  • Pipelines as code. Workflow, Step, Needs, Retry, Timeout, OnFailure, Always, and When conditions that prune the graph per branch or run parameter: typed, composable, testable. See Pipelines and Conditions.
  • Definition, plan, execution. Build() resolves your code into an immutable, validated Plan; the engine executes the plan, never your code directly. That is what makes a run inspectable, replayable and attachable. See Concepts.
  • Live attach. A terminal UI (senro attach), a browser UI (senro ui, a Go client compiled to WebAssembly), or plain streaming lines, all folding the same event stream, live over a socket or replayed from disk with the same client. See Attach.
  • Control, not just watch. Cancel, pause and resume a run; retry, skip and re-run from a step; set breakpoints; open an interactive shell inside a live step's sandbox. See Control operations and The shell.
  • Four executors. Local processes, containers on a local Docker daemon, one pod per step on Kubernetes, and processes on a remote host over your own SSH configuration. Chosen per workflow with senro.On(...). See Kubernetes and SSH.
  • Two step kinds. exec.Command runs a command on any executor; senro.Func runs a registered Go function, on the coordinator, on an SSH host, or in a container, by staging the pipeline binary there and re-entering it. See Func steps off the coordinator.
  • Fan-out for monorepos. Expand builds one step per unit over eight unit graphs (glob, gowork, cargo, jswork, maven, gradle, pyproject, bazel), with per-unit edges (NeedsEach), duration-balanced sharding (Partition), and change-scoped runs (Affected). See Fan-out and Monorepos.
  • Graphs decided at runtime. When the list only exists once something has run, Generates returns a Fragment the engine splices into the plan, so each item becomes a real step with its own log, state, cache entry and retry, instead of a shell loop senro cannot see into. See Generate a subgraph.
  • Caching that tells the truth. Named workspaces snapshotted between steps (and, with bounds, between runs), an opt-in Pure() action cache keyed off declared inputs, a best-effort scratch cache, and a shared remote tier over an S3-compatible bucket or an OCI registry so a fleet starts warm. senro verify --recheck-pure audits purity claims empirically. See Shared cache.
  • Runs that outlive the runner. With the shared cache on, each step's output and the sealed event ledger are archived as the run goes; senro logs fetch <id> brings a destroyed CI runner's failed build back to read locally. See Archiving a run.
  • Secrets that stay out of logs. Values are delivered as files, never argv or environment values; output is redacted, unsafe channels are refused outright before anything runs. Built on mamori. See Secrets.
  • Triggers. The pipeline binary is its own matcher: hand it a GitHub, GitLab, Bitbucket or Gitea webhook event (or your own provider) and it decides whether to run, with exit code 78 for "not my business". Or be the endpoint yourself: trigger.FromRequest verifies and parses a delivery straight off the wire, per source, with no event file in between. See Triggers and Run it as a server.
  • Notifications and export. Slack, GitHub Checks, webhooks, or your own destination through a small Renderer/Requester seam; traces exported from the event stream through a Sink. See Notifications.
  • Failure analysis, gated. Hand a failed step to your own analyzer (your SDK, your model, no provider dependency in senro); its proposal applies only when a human or an explicit policy accepts it. contrib/genkitanalyzer is one you can install, in its own module so senro's graph stays free of it. See Writing a failure analyzer and An AI analyzer.
  • Honest failure states. A step ends in one of ten states, never a boolean; recovered (flaky but passed on retry) is not succeeded, and retry.OnInfra() retries broken infrastructure but never a failing test. See Failure handling.
  • Embeddable. senro is a library first: no global state, no os.Exit, no reading your argv; attach is one opt-in call. The wire contract lives in github.com/xavidop/senro/api with no dependency beyond the standard library. See Embedding.

Install

The CLI:

brew install xavidop/tap/senro
# or
go install github.com/xavidop/senro/cmd/senro@latest

The library, which is all a pipeline needs:

go get github.com/xavidop/senro

Released binaries for linux and darwin on amd64 and arm64, with checksums, SBOMs and SLSA provenance, are on the releases page. See Install for verifying a download.

Requires Go 1.26+ on Linux or macOS. Windows is deliberately unsupported; see Platform support.

Quick start

A pipeline is a Go package with a main that builds the pipeline and calls senro.Run. This one runs go test and, once that passes, go build, retrying only infrastructure failures, never a failing test:

// Command ci is a two-step pipeline: test, then build.
package main

import (
	"context"
	"log"
	"os"

	"github.com/xavidop/senro"
	"github.com/xavidop/senro/attach"
	"github.com/xavidop/senro/exec"
	"github.com/xavidop/senro/retry"
)

func main() {
	ctx := context.Background()

	p := senro.New("ci")

	verify := p.Workflow("verify")
	verify.Step("test", exec.Command("go", "test", "./..."))
	verify.Step("build", exec.Command("go", "build", "./...")).
		Needs("test").
		Retry(3, retry.OnInfra()) // retries a dropped connection, never a failing test

	// Opens a unix socket a second terminal can attach to while this runs.
	att, err := attach.Listen(ctx, attach.Options{Bind: attach.AutoUnixSocket})
	if err != nil {
		log.Fatal(err)
	}
	defer att.Close()

	log.Printf("attach with: senro attach --pid %d", os.Getpid())

	if err := senro.Run(ctx, p, senro.WithAttach(att)); err != nil {
		log.Print(err)
		os.Exit(1)
	}
}

Run it with the CLI, which builds the package, execs it, and attaches automatically (a TUI on a TTY, plain lines otherwise):

senro run ./ci

Or run the binary yourself and attach from a second terminal, or open the browser view:

go run ./ci &
senro attach     # terminal UI
senro ui         # browser UI, prints a one-time link

Every run writes runs/<id>/ with events.jsonl and per-step logs. senro attach --run <id> reopens a finished run from disk over the exact same protocol; there is no separate offline mode. From here, the getting started guide continues.

Documentation

The full documentation lives under site/, an Astro site (make site-dev serves it locally with Node 22 via nvm). It is the primary reference for what is implemented:

Runnable examples live in examples/, each a small main package with a doc comment saying what it demonstrates. An agent skill ships in-repo for AI coding tools.

Platform support

senro targets Linux and macOS. Windows is not supported, deliberately: attach's security boundary is a kernel peer-credential check with no Windows equivalent implemented, and senro fails closed rather than advertising a feature it cannot secure. See Attach security for the full reasoning.

Contributing

Contributions are welcome. make all runs formatting, go vet, the linters and the test suite; run it before sending a change. See CONTRIBUTING.md for the repository layout, what the container, Kubernetes and SSH executors' tests need, and how releases are cut.

License

MIT © Xavier Portilla Edo

About

senro is built by Xavier Portilla Edo, and mirrors the structure, tooling and release process of mamori (typed, watchable configuration and secrets for Go), which senro's secrets support is built on.

About

Pipeline engine for Go: define typed, testable CI/CD and automation workflows as code, then run them locally, in containers, on Kubernetes or over SSH, with live attach, caching, retries and monorepo support.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages