Build and ship software with one language-agnostic workflow.
- Declarative: describe steps once, use them anywhere.
- Cross-language: Rust and Go SDKs today; more to come.
- Reproducible: hermetic steps and pinned toolchains.
- Scalable: the same artifacts power your end-to-end flow.
Vorpal is distributed and composed of horizontally scalable components:
- CLI (orchestrator): runs builds and talks to services over gRPC.
- Agent service (localhost): performs filesystem/sandbox tasks close to the workload.
- Registry service (storage): persists artifacts and metadata (e.g., S3-backed in CI).
- Worker service (executor): executes steps in isolated environments; scale by adding workers.
Run services locally during development with make vorpal-start (or cargo run --bin vorpal -- system services start).
flowchart LR
Agent -- "Read & Write" --> Sandbox
Agent -- "Pull & Push" --> Registry
Registry -- "Read & Write" --> Store
Worker -- "Read & Write" --> Sandbox
Worker -- "Pull & Push" --> Registry
CLI -- "GetArtifacts" --> SDK
SDK -- "FetchArtifact" --> Registry
CLI -- "PrepareArtifact" --> Agent
CLI -- "BuildArtifact" --> Worker
Store --> ObjectStorage(Object Storage)
curl -fsSL https://raw.githubusercontent.com/ALT-F4-LLC/vorpal/refs/heads/main/script/install.sh -o install.sh && sh install.sh
- macOS only (once):
xcode-select --install - All platforms:
./script/dev.sh make build(preferred; installs and uses a consistent toolchain) - Common tasks:
make check,make test,make format,make lint,make dist
The examples below build a simple Rust binary artifact for multiple systems and run the context.
use anyhow::Result;
use vorpal_sdk::{
api::artifact::ArtifactSystem::{Aarch64Darwin, Aarch64Linux, X8664Darwin, X8664Linux},
artifact::language::rust::Rust,
context::get_context,
};
#[tokio::main]
async fn main() -> Result<()> {
let ctx = &mut get_context().await?;
let systems = vec![Aarch64Darwin, Aarch64Linux, X8664Darwin, X8664Linux];
Rust::new("example", systems).build(ctx).await?;
ctx.run().await
}package main
import (
api "github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/api/artifact"
"github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/artifact/language"
"github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/config"
)
var systems = []api.ArtifactSystem{
api.ArtifactSystem_AARCH64_DARWIN,
api.ArtifactSystem_AARCH64_LINUX,
api.ArtifactSystem_X8664_DARWIN,
api.ArtifactSystem_X8664_LINUX,
}
func main() {
ctx := config.GetContext()
language.NewGo("example", systems).Build(ctx)
ctx.Run()
}These steps assume you installed Vorpal via the installer and have vorpal on your PATH.
- One-time keys
vorpal system keys generate# installer runs this; safe to re-run
- Start services (agent, registry, worker)
- If you used the installer, services are already running.
- Otherwise:
vorpal system services start# defaults to https://localhost:23151
- Create a new project (pick Go or Rust)
mkdir hello-vorpal && cd hello-vorpalvorpal artifact init# scaffolds Vorpal.toml and a sample
- Build your artifact
vorpal build "vorpal"# builds using the local services- To get the output path:
vorpal build --path "vorpal"
- Run the sample
vorpal run example# runs the built artifact directly from the store- Or manually:
ARTIFACT_PATH=$(vorpal build --path "vorpal")then$ARTIFACT_PATH/bin/example
Build this repository
- From the repo root:
vorpal build "vorpal" - Optional Go parity (if present):
vorpal build --config "Vorpal.go.toml" "vorpal"
Use vorpal run to execute a built artifact directly from the store without manually locating the output path. The command resolves the artifact locally first, then falls back to pulling from the registry.
# Basic usage — run an artifact by name
vorpal run rsync -- --help
# Alias format: [<namespace>/]<name>[:<tag>]
vorpal run rsync # defaults: namespace=library, tag=latest
vorpal run rsync:3.4.1 -- -avz src/ dest/ # pin a specific version tag
vorpal run team/my-tool:v2.0 # custom namespace and tag
# Override which binary inside the artifact to execute
vorpal run my-tool --bin my-tool-helper -- --verbose
# Point to a remote registry (defaults to localhost)
vorpal run rsync --registry https://registry.example.com:23151Resolution flow: local alias → registry alias lookup → pull artifact archive → execute binary.
Errors and troubleshooting:
- "artifact alias not found" — the artifact hasn't been built yet. Run
vorpal build <name>first. - "binary not found in artifact output" — the artifact exists but doesn't contain a binary matching the name. The error lists available binaries; use
--bin <name>to select one. - "artifact output not found for digest" — the alias exists but the output was cleaned up. Rebuild with
vorpal build <name>.
Manage development and user-wide environments using the builders:
- Development environment (devenv): creates a portable shell activation (
bin/activate) that prepends tool artifacts to PATH and sets env vars. - User environment (userenv): installs activation helpers and safe symlinking under
$HOME/.vorpal/bin.
Rust
use anyhow::Result;
use vorpal_sdk::{
api::artifact::ArtifactSystem::{Aarch64Darwin, Aarch64Linux, X8664Darwin, X8664Linux},
artifact::{ProjectEnvironment, UserEnvironment},
context::get_context,
};
#[tokio::main]
async fn main() -> Result<()> {
let ctx = &mut get_context().await?;
let systems = vec![Aarch64Darwin, Aarch64Linux, X8664Darwin, X8664Linux];
ProjectEnvironment::new("my-project", systems.clone())
.with_environments(vec!["FOO=bar".into()])
.build(ctx).await?;
UserEnvironment::new("my-home", systems)
.with_symlinks(vec![("/path/to/local/bin/app", "$HOME/.vorpal/bin/app")])
.build(ctx).await?;
ctx.run().await
}Go
package main
import (
api "github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/api/artifact"
"github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/artifact"
"github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/config"
)
var systems = []api.ArtifactSystem{
api.ArtifactSystem_AARCH64_DARWIN,
api.ArtifactSystem_AARCH64_LINUX,
api.ArtifactSystem_X8664_DARWIN,
api.ArtifactSystem_X8664_LINUX,
}
func main() {
ctx := config.GetContext()
artifact.NewProjectEnvironment("my-project", systems).
WithEnvironments([]string{"FOO=bar"}).
Build(ctx)
artifact.NewUserEnvironment("my-home", systems).
WithSymlinks(map[string]string{"/path/to/local/bin/app": "$HOME/.vorpal/bin/app"}).
Build(ctx)
ctx.Run()
}- Development environments: source generated
bin/activateinside the artifact output when used within a step or your own wrapper script. - User environments: run
$HOME/.vorpal/bin/vorpal-activate, thensource $HOME/.vorpal/bin/vorpal-activate-shell.
Vorpal does not lock you to a single executor. Each step sets its executor via artifact.step[].entrypoint and artifact.step[].arguments.
- Default: Bash. SDK “shell” helpers run in Bash (on Linux these run inside Bubblewrap).
- Custom: Point
entrypointto any binary (e.g.,bwrap,docker,podman) and pass flags viaarguments.
Rust (custom entrypoint/arguments)
use anyhow::Result;
use vorpal_sdk::{
api::artifact::ArtifactSystem::{Aarch64Darwin, Aarch64Linux, X8664Darwin, X8664Linux},
artifact::{Artifact, ArtifactStep},
context::get_context,
};
#[tokio::main]
async fn main() -> Result<()> {
let ctx = &mut get_context().await?;
let systems = vec![Aarch64Darwin, Aarch64Linux, X8664Darwin, X8664Linux];
let step = ArtifactStep::new("docker")
.with_arguments(vec![
"run", "--rm", "-v", "$VORPAL_OUTPUT:/out",
"alpine", "sh", "-lc", "echo hi > /out/hi.txt",
])
.build();
Artifact::new("example-docker", vec![step], systems).build(ctx).await?;
ctx.run().await
}Go (custom entrypoint/arguments)
package main
import (
api "github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/api/artifact"
"github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/artifact"
"github.com/ALT-F4-LLC/vorpal/sdk/go/pkg/config"
)
var systems = []api.ArtifactSystem{
api.ArtifactSystem_AARCH64_DARWIN,
api.ArtifactSystem_AARCH64_LINUX,
api.ArtifactSystem_X8664_DARWIN,
api.ArtifactSystem_X8664_LINUX,
}
func main() {
ctx := config.GetContext()
step, _ := artifact.NewArtifactStep().
WithEntrypoint("docker", systems).
WithArguments([]string{"run", "--rm", "-v", "$VORPAL_OUTPUT:/out", "alpine", "sh", "-lc", "echo hi > /out/hi.txt"}, systems).
Build(ctx)
artifact.NewArtifact("example-docker", []*api.ArtifactStep{step}, systems).Build(ctx)
ctx.Run()
}- Read the contributor guide:
AGENTS.md(structure, commands, style, and PR workflow). - Before opening a PR:
make format && make lint && make test. - Prefer small, focused changes with clear descriptions and linked issues.
- For local development, use
./script/dev.shordirenv allowto get a consistent environment.