CI in your language. No YAML. No DSL. Just code.
A CI orchestrator that lets you define pipelines in Go, Rust, or Elixir. Your pipeline is a real program that outputs a task graph, which Sykli executes in parallel.
This is a learning project - exploring how to build CI tools with BEAM/OTP.
Current Status: Core working - parallel execution, caching, cycle detection, matrix builds.
sykli.go ──run──▶ JSON task graph ──▶ parallel execution
SDK stdout Elixir engine
- Sykli detects your SDK file (
sykli.go,sykli.rs, orsykli.exs) - Runs it with
--emitto get a JSON task graph - Executes tasks in parallel by dependency level
- Caches results based on input file hashes
# Install
curl -fsSL https://raw.githubusercontent.com/yairfalse/sykli/main/install.sh | bash
# Create sykli.go
cat > sykli.go << 'EOF'
package main
import sykli "github.com/yairfalse/sykli/sdk/go"
func main() {
s := sykli.New()
s.Task("test").Run("go test ./...")
s.Task("build").Run("go build -o app").After("test")
s.Emit()
}
EOF
# Run
sykliOutput:
── Level with 1 task(s) ──
▶ test go test ./...
✓ test 42ms
── Level with 1 task(s) ──
▶ build go build -o app
✓ build 1.2s
─────────────────────────────────────────
✓ 2 passed in 1.3s
s := sykli.New()
s.Task("test").Run("go test ./...")
s.Task("lint").Run("go vet ./...")
s.Task("build").Run("go build -o app").After("test", "lint")
s.Emit()test and lint run in parallel. build waits for both.
s.Task("test").
Run("go test ./...").
Inputs("**/*.go", "go.mod")If input files haven't changed, task is skipped:
⊙ test CACHED
s.Task("test").
Run("go test ./...").
Matrix("go_version", "1.21", "1.22", "1.23")Expands to test[go_version=1.21], test[go_version=1.22], test[go_version=1.23].
s := sykli.New()
src := s.Dir(".")
cache := s.Cache("go-mod")
s.Task("test").
Container("golang:1.21").
Mount(src, "/src").
MountCache(cache, "/go/pkg/mod").
Workdir("/src").
Run("go test ./...")
s.Emit()s.Task("integration").
Run("./integration-tests.sh").
Retry(3).
Timeout(300)s.Task("deploy").
Run("./deploy.sh").
When("branch == 'main'").
Secret("DEPLOY_TOKEN")package main
import sykli "github.com/yairfalse/sykli/sdk/go"
func main() {
s := sykli.New()
s.Task("test").Run("go test ./...")
s.Task("build").Run("go build -o app").After("test")
s.Emit()
}use sykli::Pipeline;
fn main() {
let mut p = Pipeline::new();
p.task("test").run("cargo test");
p.task("build").run("cargo build --release").after(&["test"]);
p.emit();
}Mix.install([{:sykli, path: "sdk/elixir"}])
defmodule Pipeline do
use Sykli
pipeline do
task "test" do
run "mix test"
inputs ["**/*.ex", "mix.exs"]
end
task "build" do
run "mix compile"
after_ ["test"]
end
end
end| Feature | Status |
|---|---|
| Go SDK | ✅ |
| Rust SDK | ✅ |
| Elixir SDK | ✅ |
| Parallel execution | ✅ |
| Content-addressed caching | ✅ |
| Cycle detection | ✅ |
| Retry & timeout | ✅ |
| Conditional execution | ✅ |
| Matrix builds | ✅ |
| Container tasks | ✅ (SDK support) |
| GitHub status API | ✅ |
| Remote execution | Planned |
┌─────────────┐ ┌──────────────┐ ┌────────────┐
│ sykli.go │────▶│ JSON Graph │────▶│ Engine │
│ (SDK) │ │ (stdout) │ │ (Elixir) │
└─────────────┘ └──────────────┘ └────────────┘
│
┌──────────────────────────┼──────────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ lint │ │ test │ │ build │
│ (level 0)│ │ (level 0)│ │ (level 1)│
└──────────┘ └──────────┘ └──────────┘
Why Elixir? The same OTP code that runs locally can distribute across a cluster. Local and remote execution are the same system at different scales.
sykli/
├── core/ # Elixir engine
│ └── lib/sykli/
│ ├── detector.ex # Finds SDK file, runs --emit
│ ├── graph.ex # Parses JSON, topological sort
│ ├── executor.ex # Parallel execution
│ ├── cache.ex # Content-addressed caching
│ └── cli.ex # CLI interface
├── sdk/
│ ├── go/ # Go SDK (~1000 lines)
│ ├── rust/ # Rust SDK (~1500 lines)
│ └── elixir/ # Elixir SDK
└── examples/ # Working examples
# Build escript binary
cd core && mix escript.build
# Run tests
mix test
# Run from source
mix run -e 'Sykli.run(".")'Sykli (Finnish: "cycle") - Part of a Finnish tool naming theme:
- SYKLI (cycle) - CI orchestrator
- NOPEA (fast) - GitOps controller
- KULTA (gold) - Progressive delivery
- RAUTA (iron) - Gateway API controller
MIT
Learning Elixir. Learning CI. Building tools.