A single native binary that provisions bare-metal servers and manages isolated dev environments for AI-assisted engineering. One binary, zero dependencies, fully declarative.
Built with Java 25 + picocli + GraalVM native-image. <1ms startup.
You have a bare-metal server. You work on multiple projects simultaneously, each needing its own JDK, Postgres, Meilisearch, Redpanda, and AI coding agents — fully isolated so a runaway agent in one project can't affect another.
sail provisions the server, creates project environments as Incus system containers, manages their lifecycle, and orchestrates AI agents inside them with spec-driven workflows, guardrails, and rollback safety. Each project gets a complete Ubuntu 24.04 userspace with its own filesystem, network stack, and rootless Podman runtime.
# Install sail (single binary, no dependencies)
curl -fsSL https://raw.githubusercontent.com/singlr-ai/sing/main/install.sh | bash
# Initialize server (one-time, needs sudo)
sudo sail host inithost init installs Incus, creates a storage pool (dir by default, ZFS with --storage zfs --disk /dev/sdX), configures networking, and caches the base Ubuntu 24.04 image.
# Pull project descriptor from shared repo (team projects)
export GITHUB_TOKEN=ghp_...
sail project pull acme-health
# Or generate a new sail.yaml interactively
sail project init
# Create the environment
sail project create acme-healthproject pull stores the project bundle under ~/.sail/projects/<name>/ by default.
project create provisions an Incus container with everything declared in sail.yaml — installs runtimes, starts Podman services, clones repos, configures git identity, generates agent context files, and sets up the harness.
# Print SSH config for your editor
sail project connect acme-healthAdd the output to ~/.ssh/config, then connect in Zed: Cmd+Shift+P → "Connect to SSH Host" → acme-health.
Engineers work in two modes. sail supports both from the same project.
Open Zed, connect via SSH remote dev, start the agent from Zed's Agent Panel. You're in the loop — brainstorming, exploring code, writing specs, reviewing output. sail is invisible here; the generated context files (CLAUDE.md, SECURITY.md, .context/) guide the agent.
sail project restart acme-health # start container, show connection infoDeveloper processes (java -jar, npm run dev) run interactively in Zed terminal tabs. Infrastructure services (Postgres, Meilisearch, etc.) are managed by Podman with --restart=always and survive container reboots automatically.
Write specs during the day. Walk away. The agent works through them overnight.
sail spec dispatch acme-health # pick next ready spec, launch agentdispatch scans specs/*/spec.yaml from the container, finds the next pending spec (respecting dependencies and assignee), reads the detailed spec.md, and launches the agent with full context. Guardrails enforce time limits. Auto-snapshot provides rollback safety.
Specs are the unit of work. Each spec lives in its own directory inside specs/, checked into a shared repo so the team can see and assign work.
specs/
├── oauth-flow/
│ ├── spec.yaml # Metadata: id, title, status, assignee, depends_on
│ ├── spec.md # What to build and why (brainstormed with agent in Zed)
│ └── plan.md # How to build it (optional, for complex specs)
├── payment-integration/
│ ├── spec.yaml
│ ├── spec.md
│ └── plan.md
└── fix-footer-typo/
├── spec.yaml
└── spec.md # Simple spec, no plan needed
id: payment-integration
title: "Stripe payment webhook"
status: pending
assignee: bob
depends_on:
- oauth-flow
repo: webapp
agent: codex
model: gpt-5.5
reasoning_effort: high
branch: feat/payment-integrationrepo/repos routes work to the right repository in multi-repo projects. agent routes a spec
to a specific installed agent (claude-code, codex, or gemini); if omitted, dispatch uses
agent.type from sail.yaml. model and reasoning_effort tune agents that support those
controls; unsupported combinations fail fast.
pending → in_progress → review → done → archive
pending: Ready for an agent to pick up. in_progress: Agent is working. review: Work complete, security review and code review hooks run automatically. done: All reviews pass. archive: Cleaned up (sail task archive).
Morning (Zed, interactive):
Engineer + agent brainstorm → write specs/oauth-flow/spec.md
Optionally plan → write specs/oauth-flow/plan.md
Push specs to shared repo
Evening:
sail spec dispatch acme-health
Overnight:
Agent reads spec.md, works, commits, pushes branch
Reviews run automatically at spec completion
Guardrails enforce time limits
Next morning:
sail agent status # all projects at a glance
sail agent report acme-health # detailed: commits, spec progress, review results
Review PRs, merge or address findings
Specs live in a shared private repo (e.g., your-org/projects/acme-health/specs/). Multiple engineers, each with their own container:
- Alice specs out OAuth, assigns to herself
- Bob specs out payments, depends on Alice's OAuth work
sail spec dispatchrespectsassignee— each engineer's agent only picks up their specs (or unassigned ones)- Dependencies prevent premature work — payments won't start until OAuth is done
No project board needed. The spec directory is the board. Git history is the audit trail.
After upgrading sail, bring existing project containers up to the current spec layout and agent
instructions with one command:
sail project migrate --all --pull-specsFor a single project:
sail project migrate acme-health --pull-specsThe migration starts stopped projects, regenerates agent context files, converts legacy
specs/index.yaml entries into specs/<id>/spec.yaml, removes the legacy index after a safe
conversion, and reports spec Git sync state. Use --json for automation or --keep-index if you
want to retain the old index file during manual review.
sail agent run (or sail agent context regen) generates a complete agent environment from sail.yaml. Context files are agent-agnostic — Claude Code gets CLAUDE.md, Codex gets AGENTS.md, Gemini gets GEMINI.md. Same content, different format.
| Generated | Purpose |
|---|---|
CLAUDE.md / AGENTS.md / GEMINI.md |
Tech stack, conventions, runtimes, services, autonomous work protocol |
SECURITY.md |
Zero-trust principles, OWASP Top 10, input validation, secrets management |
.context/ repository |
Persistent knowledge across sessions (system overview, patterns, failure log) |
| Methodology skills | /spec (write spec first), /verify (run tests and block on failures) |
| Post-task hooks | Auto-trigger security audit and code review at spec completion |
The autonomous work protocol in the context file tells the agent how to read specs, update status, and hand off — this works identically across Claude Code, Codex, and Gemini.
.context/
system/ # always loaded — project overview, key decisions
patterns/ # discovered architectural patterns and conventions
failures/ # what went wrong and how it was fixed
Agents read system/README.md at session start and update these files as they learn. Committed alongside code so knowledge persists across sessions and engineers.
agent:
methodology:
approach: spec-driven # spec-driven | tdd | free-form
verify: "mvn clean test"
lint: "mvn spotless:check"spec-driven: Agent writes a spec before coding. Generated as a/specskill.tdd: Agent writes failing tests first, then implements until green.free-form: No methodology constraints.verify/lint: Commands injected as skills the agent runs after implementation.
agent:
guardrails:
max_duration: 4h
action: snapshot-and-stopWhen the time limit triggers, the agent is stopped and rolled back to the pre-launch snapshot. The watcher starts automatically with sail spec dispatch and sail agent run --background.
Actions: snapshot-and-stop (rollback), stop (keep changes), notify (webhook, agent continues).
# Dispatch
sail spec dispatch acme-health # next ready spec, background launch
sail spec dispatch acme-health --spec auth # specific spec by ID
# Run (context regen + launch)
sail agent run acme-health # interactive mode (Zed)
sail agent run acme-health --task "..." # headless with explicit task
sail agent run acme-health --background # background, picks next spec
# Monitor
sail agent status # all projects at a glance
sail agent status acme-health # single project detail
sail agent log acme-health -f # stream output live
sail agent report acme-health # morning-after summary
# Control
sail agent stop acme-health # SIGTERM → SIGKILL after 3s
sail agent watch acme-health # enforce guardrails (auto-started by dispatch)
# Quality
sail agent audit acme-health # cross-agent security audit
sail agent review acme-health # cross-agent code review
sail agent sweep acme-health # codebase cleanup passsail is agent-agnostic. Configure the primary agent and optionally install others for cross-agent review:
agent:
type: claude-code
install:
- claude-code
- codex
- gemini
security_audit:
enabled: true
# auditor: gemini # defaults to a different agent than primary
code_review:
enabled: trueClaude, Codex, or Gemini can execute a spec. Set agent: codex in spec.yaml to override the
project default for that unit of work. For Codex, model and reasoning_effort map to
codex exec --model ... --config model_reasoning_effort=.... The hooks fire at spec completion,
not session stop — so reviews always see complete, coherent work.
name: acme-health
description: "Acme Health Platform"
resources:
cpu: 4
memory: 12GB
disk: 150GB
image: ubuntu/24.04
runtimes:
jdk: 25
node: 22
maven: "3.9.9"
git:
name: "Acme Engineering"
email: "eng@acme.com"
auth: token
repos:
- url: "https://github.com/acme/backend.git"
path: "backend"
branch: "main"
- url: "https://github.com/acme/webapp.git"
path: "webapp"
services:
postgres:
image: postgres:16
ports: [5432]
environment:
POSTGRES_DB: acme
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
volumes:
- pgdata:/var/lib/postgresql/data
meilisearch:
image: getmeili/meilisearch:latest
ports: [7700]
environment:
MEILI_ENV: development
agent:
type: claude-code
auto_branch: true
auto_snapshot: true
specs_dir: specs
methodology:
approach: spec-driven
verify: "mvn clean test"
lint: "mvn spotless:check"
guardrails:
max_duration: 4h
action: snapshot-and-stop
ssh:
user: dev
authorized_keys:
- "ssh-ed25519 AAAA... you@laptop"sail project create acme-health # provision container from sail.yaml
sail project start acme-health # start stopped container
sail project stop acme-health # stop container (preserves state)
sail project restart acme-health # start + show connection info
sail project containers # list all projects with status
sail project snapshot create acme-health # create snapshot
sail project snapshot restore acme-health snap-01 # rollback to snapshot
# Modify running projects
sail project add service acme-health
sail project add repo acme-health
sudo sail project resources set acme-health --memory 16GB
sail project destroy acme-health # delete container and stateRequires JDK 25+ and Maven 3.9+.
mvn clean test # run tests (588 tests)
mvn clean package # build JAR
# Native image (requires GraalVM JDK 25)
JAVA_HOME=/usr/lib/jvm/graalvm-jdk-25 mvn clean package -Pnative -DskipTestsEvery command supports --help. State-modifying commands support --dry-run. All commands support --json for machine-parseable output.