Running VibeSys
A repository-native VibeSys run operates on one candidate repository and one
named task. The repository root is the agent and evaluator working directory.
Human-authored task inputs live below .vibesys/tasks; generated state lives
below .vibesys/state.
project/
├── .git/
├── .vibesys/
│ ├── tasks/
│ │ └── <task>/
│ │ ├── OBJECTIVE.md
│ │ ├── vibesys.input.toml
│ │ ├── Dockerfile # optional task execution environment
│ │ ├── accuracy_checker/ # optional task-owned program
│ │ ├── benchmark/ # optional task-owned program
│ │ └── reference/ # optional held-out inputs
│ └── state/
│ ├── project.json
│ ├── runs/<run-id>/ # committed run metadata and loop state
│ └── local/runs/<run-id>/worktrees/ # temporary evolve worktrees only
└── candidate source
~/.vibesys/projects/<project-key>/
├── current-run
└── runs/<run-id>/ # logs and machine-local state
~/.vibesys/cache/<architecture>/
└── evaluator-tools/<tool>/<spec-digest>/ # evaluator tools, shared by all projects
VibeSys commits candidate evolution and portable .vibesys/state/runs/
metadata on a vibesys-runs/<run-id> branch, cut from the branch you had checked
out when the run started. A run that adopts a winner commits it on that branch,
naming the candidate and what it measured; your own branch is never changed, so
merge the run branch to keep the winner. The run's branch stays checked out after
it ends; the next run checks your branch out again first, so each run starts from your own
work. Machine-local metadata defaults to
~/.vibesys; set VIBESYS_STATE_HOME to an absolute directory to override it.
Existing .vibesys/state/local/ metadata moves there on first open without
changing its file formats. Temporary evolve worktrees remain repository-local.
The entire .vibesys/ directory is read-only to coding agents, and its
state/runs/ and state/local/ are hidden from them: the run's record is the
framework's, and state/agent-visible/ holds the few documents an agent reads
by path.
Start a Task
Launch from the candidate repository. Select the task explicitly when the repository defines more than one:
cd /path/to/project
vibesys validate --task latency
vibesys --task latency --max-rounds 4
--project selects the repository explicitly. --input remains a compatibility
alias during migration:
vibesys --project /path/to/project --task latency \
--config /path/to/project/agent.toml
The task name may be omitted when .vibesys/tasks contains exactly one task.
The repository must be its Git root, or outside any Git repository so VibeSys
can initialize one. An existing repository needs a baseline commit and a clean
worktree. Keep agent.toml and root .env* files out of Git history.
When the selected repository-native task contains a conventional
.vibesys/tasks/<task>/Dockerfile, VibeSys builds it with that task directory
as the complete build context, on top of the backend's default image (passed
as the build argument BASE_IMAGE, so the Dockerfile may start with
ARG BASE_IMAGE and FROM ${BASE_IMAGE}), and layers the agent on top. Every
run environment whose agent runs in a local container does this the same way:
Docker (the default), and the agent container of Modal, SkyPilot and Slurm.
The run records the task image as a local tag, vibesys-task-build:<owner>-<digest>.
<owner> identifies the task directory and is used only to clean up that
directory's older tags; a build never removes another task's tags. <digest>
covers the base image and every file the build sends to Docker: the
Dockerfile, .dockerignore, and the build context minus what .dockerignore
excludes. Only <digest> decides whether the inputs changed, so a project
copied elsewhere (as --runs-dir does) resumes. The run itself uses the
image's immutable ID, read once at launch, so a later launch that removes the
tag cannot affect a running run's agent containers. On resume:
- if the recorded image is still in the local Docker daemon, it is reused and nothing is built. If the inputs changed since the run started (an edited benchmark, say), VibeSys prints a notice and keeps the recorded image;
- otherwise, if the inputs are unchanged, VibeSys rebuilds the image and continues, with a notice (the image ID may differ when Docker's build cache has been pruned);
- if the image is gone and the inputs changed, or the run was recorded without
a task image the task now declares, resume refuses. A run recorded with the
removed
localenvironment and continued in Docker falls in the second case.
Runs recorded before tags held a bare image ID: they are reused while that ID
is local, and otherwise rebuilt as they were then (without BASE_IMAGE) and
refused unless the rebuild reproduces the recorded ID. Docker's native layer
cache makes later launches incremental. The candidate repository
is mounted at runtime and is never part of the image build context.
Task Dockerfiles are intentionally convention-based: they require no manifest
field or image-build command. A task Dockerfile cannot be combined with
--docker-image, and the host environment (Metal on Apple Silicon) refuses it,
because it has no container to build it into. A root Dockerfile never opts
in, because that file commonly belongs to the candidate application.
On Modal, SkyPilot and Slurm the task image is the agent's container only;
evaluation runs where those environments run it. With Slurm and
[vibesys.agent_gpu], vibesys-gpu forwards the agent container's
environment variables into each GPU job on the compute node
(libs/vs-sandbox/src/vs_sandbox/host_command_client.py sends them,
job_environment in host_command_broker.py keeps all but the container's
identity variables such as PATH and HOME). An ENV line in the task
Dockerfile therefore leaks into GPU jobs, where it may name paths that exist
only in the image. Put settings the job needs somewhere it reads them on both
sides instead, such as a .pth file in the project's virtual environment, and
keep image ENV to values that are correct on the compute node too.
All outer loops use this model. The agent always runs in a local Docker
container. Modal, SkyPilot, and Slurm change where evaluation runs, not the task
layout. The Slurm environments reach the cluster through a host-owned broker
that the container connects to over a mounted socket. Task commands always start in the repository root. .vibesys is mounted read-only
for coding agents. Machine-local state is outside their workspace.
Modal tasks may set a project-relative deployment file. Omit this block to use
the legacy main.py default:
[environment.modal]
entrypoint = "examples/deployment/service.py"
A task declares how a candidate is measured in one of two forms. The short
form names the stages build (optional), check and measure, and the
headline metric:
version = 1
metric = "total_ops_per_sec"
[agent]
domain = "generic"
[evaluator]
name = "vibesys-evaluator-queue"
version = "0.1.0"
[build]
command = ["make", "queue-candidate.so"]
timeout_seconds = 600
[check]
entrypoint = "vibesys-queue"
args = ["check", "--workspace", "${PROJECT_ROOT}", "--scenario", "spsc"]
[measure]
entrypoint = "vibesys-queue"
args = ["benchmark", "--workspace", "${PROJECT_ROOT}", "--scenario", "spsc"]
The existing form names the same check and measure stages [accuracy] and
[benchmark], and may declare [build] too. Both forms compile to the same
stages; declaring one stage in both forms is refused. A short-form measure
speaks evaluator result protocol 2 unless it declares a result table.
Check, measure and diagnostic profile commands may be task-owned argv arrays,
or stable entry points supplied by an exact evaluator package; they are trusted
evaluator code. The build is candidate code: an argv run from the root of a
fresh checkout of the measured commit, confined like the agent (in the Docker
run environment, the candidate's container). After it passes, VibeSys verifies
every file the evaluator package requires (candidate_outputs in its
vibesys.evaluator.toml, for example queue-candidate.so); a missing one
fails the build stage naming it. The verified files are kept outside every
workspace and placed at their declared paths in the checkouts where the check
and the measure run, which receive their workspace-relative paths, :-joined,
in VIBESYS_CANDIDATE_OUTPUTS. A commit is built once: a later stage or
measurement of the same commit reuses its build. A task whose evaluator requires
outputs but declares no build fails vibesys preflight and the desktop app's
New run check. The build needs agent confinement, so the host run environment
refuses it, and the Slurm evaluation executor does not run build stages yet (#2968).
For trusted serving captures, also declare a bundle-owned diagnostic workload:
[profile]
command = ["python", "benchmark/profile.py", "--base-url", "http://127.0.0.1:VIBESYS_DYNAMIC_PORT/v1"]
timeout_seconds = 120
The workload is short and fixed, exercises requests without benchmark preflight
or warmup gates, and lives in trusted evaluator inputs. It follows the same
command-or-entrypoint contract as the other task commands. A configured serving
capture requires this declaration and names profile.command when it is absent.
The timeout bounds the load after readiness and cannot exceed the remaining
capture deadline. Profiling provides diagnostic evidence; accuracy and benchmark
acceptance still use their own unchanged gates.
Start from an Example
The examples under examples/<category>/ in a VibeSys source checkout are
project roots in this layout. Runs never use them in place: copy one into a
new repository first.
vibesys example list # categories, examples, and their tasks
vibesys example new data-structures/stack ~/work/stack
cd ~/work/stack
vibesys --task spsc
example new copies the files the checkout tracks under the example, runs
git init, makes one initial commit, and prints the vibesys --task ... line
to run. The destination must be absent or an empty directory, outside the
checkout. An example registered with external_repo = true is cloned from its
repository at the commit the checkout pins instead. A unique last path
component (stack) also selects an example. vibesys example list --json
prints one ExampleList document (entrypoints.example_catalog), which the
desktop app reads.
Container Topologies
A microservices candidate is not a process the agent starts on its own: it is
a Docker Compose topology the agent must build, start, and trace. Such a task
needs Docker Engine reachable without sudo and, when its benchmark shells out
to Go helpers, a Go toolchain on PATH.
A microservices task therefore opts in to a Docker daemon of its own with
docker_in_docker = true:
[environment]
docker_in_docker = true
The key selects the Docker run environment automatically. Modal, SkyPilot, or Slurm with the key is an error that names it. Without the key a task gets no container runtime at all: VibeSys never mounts or imports the host's Docker socket.
Docker-in-Docker
The agent container runs under the Sysbox runtime (docker run --runtime sysbox-runc, never --privileged) and VibeSys starts dockerd inside it
before the first turn, waiting until docker info answers. The agent can
docker compose up --build or create kind clusters inside the sandbox. The
daemon's images, containers, and volumes live in the sandbox container and
disappear with it, and trusted evaluator commands run in the same container, so
they use the same daemon. The sandbox is CPU-only: Sysbox cannot forward
accelerators.
The workspace is mounted at the same path inside the container as on the host
(not at /workspace), so a compose file's relative or absolute bind sources
mean the same directory to the agent and to the daemon; /tmp is the
sandbox's own. The agent image gains the Docker engine, the compose plugin,
kind, and kubectl only for these tasks (the container-runtime toolchain;
versions are pinned in vs_agent.provider_policy), so ordinary tasks' images
do not grow.
If the host's Docker daemon does not register the sysbox-runc runtime, the
run fails before building any image, naming the missing runtime and the key.
There is no fallback to the host socket or to a plain container.
A benchmark command that names both --telemetry-output and
--trace-graph-json declares that the task provisions instrumentation and a
collector. --profiler auto selects the OpenTelemetry profiler for such a task,
which gives the loop service, span, datastore, critical-path, and trace
breakdown evidence. Tasks without those flags stay on none, since OTel
profiling has nothing to read.
Compute Backend
A task declares the compute backends it targets, as a top-level key of its
vibesys.input.toml:
version = 1
backends = ["cpu"] # or ["cuda"], ["rocm"], ["trainium"], ["metal"]
A run takes its backend from the task, never from the CLI's default:
- One backend declared: the run uses it.
- Several declared, in preference order: the run uses the first this host
provides (
cudaneeds an NVIDIA GPU,rocma/dev/kfd,trainiumNeuron devices,metalApple Silicon;cpualways). When the host provides none, the first is kept andvibesys preflightfails naming the task and that backend. --backend NAMEoverrides the choice and must be one the task declares, else the run is refused before it starts.- A task that declares none (written before backends were declared) falls back,
explicitly, to
--backend, then[backend].nameinagent.toml, whose default iscuda. That default fails a CPU task on a CPU-only host, so declarebackends.[resources].accelerator_backend, when a task declares resources for a cluster, counts as its declaration and must be one ofbackendsif both are given. Every bundled example declares its backends (tests/examples/test_example_registry.py).
vibesys preflight (and the desktop app's Ready to start checks) fail when the
host cannot provide the backend the run resolved to, reporting a typed fact (the
task, the backend and the declared set) that the CLI words with --backend and
the app words for its own controls.
Task Layout Only
Task inputs live only in .vibesys/tasks/<task>/. A project with a root
vibesys.input.toml fails validation with an error that names the file and
says to move it, with OBJECTIVE.md and the files its commands read, to
.vibesys/tasks/<task>/vibesys.input.toml. A directory without
.vibesys/tasks/ is not a VibeSys project.
A repository-shaped example nested below another Git root uses --runs-dir to
materialize an isolated project:
vibesys \
--runs-dir /work/vibesys-runs \
--local \
--project /path/to/example --task default
VibeSys provisions a self-contained project below the runs directory, with
[[workspace.sources]] checked out. The source's .git/, .vibesys/state/, agent.toml, and
.env* files are not copied. Omit --local to publish the copied project using
the authenticated GitHub CLI. vibesys example new is the simpler way to start
from an example.
Resume
Inside a project, resume the machine-local current run from the configured VibeSys state home, then the newest run if no current pointer exists. A run ID selects a specific run:
cd /path/to/project
vibesys --resume
vibesys --resume <run-id>
With --runs-dir, the resume argument selects a copied project in the
collection. It may be a directory name, a local path, a GitHub OWNER/NAME, or
a cloneable URL. VibeSys then resumes that project's current or newest run.
vibesys --runs-dir /work/vibesys-runs --resume <project-directory-name>
vibesys --runs-dir /work/vibesys-runs --resume /path/to/project
vibesys --runs-dir /work/vibesys-runs --resume my-org/my-experiment
vibesys --runs-dir /work/vibesys-runs --resume https://github.com/my-org/my-experiment.git
Omitted configuration flags and the selected task are restored from
.vibesys/state/runs/<run-id>/run.json. The total round or generation limit
may increase. Other recorded settings cannot change during a resume, including
the runtime environment: a run launched with --modal resumes on Modal without
repeating the flag.
For a SkyPilot run, the portable environment choice is recorded, while the
operator profile and local profiles-file path must be supplied on the resuming
host. Nonterminal evaluator identity and replayable results live in the run's
machine-local skypilot state namespace. See
Remote Slurm execution.
Supply an Input with CLI Flags
Automation can supply the task contract without first writing a project.
This form requires --runs-dir. VibeSys synthesizes a one-task project (task
default) under <runs-dir>/_inputs/<exp-name>/, then provisions it like any
other project.
vibesys \
--runs-dir /work/vibesys-runs \
--local \
--input-objective-file ./OBJECTIVE.md \
--input-domain generic \
--input-accuracy-command "python check.py" \
--input-benchmark-command "python benchmark.py" \
--input-benchmark-metric throughput \
--input-benchmark-result-arg=--output-json \
--input-evaluator-dir ./evaluation
Use this form for CI or programmatic integrations. Checked-in task files
under .vibesys/tasks/ are easier to review for projects maintained by people.
Presentation and Validation
vibesyslaunches the TUI when attached to a terminal.vibesys --headlessdisables the TUI.- Non-interactive execution runs headless automatically.
vibesys validate [PROJECT] --task NAMEchecks the static task contract without starting an agent or executing the checker and benchmark.vibesys tasks [PROJECT] [--json]lists the tasks a project defines.vibesys example list [--json]andvibesys example new EXAMPLE DESTlist the example catalog and copy an example into a new project.
See the CLI reference for every flag and
examples/ for complete objectives and manifests.