Skip to main content

vibe-loop

vibe-loop is a small execution engine for one-slice AI coding loops. It selects one unblocked task from a repository task source, locks it, runs an agent command such as codex exec '$vibe-loop <task_id>', captures logs, validates completion, records local run metadata, and can repeat until no runnable tasks remain.

The CLI is a supervisor, not a branch or worktree manager. It owns task discovery, selection, locks, process execution, logs, completion checks, and run records. The configured worker agent owns branch/worktree setup, implementation, review, and any merge-to-main workflow defined by the repository instructions.

The runtime is built around four bundled skills — see Skills — which also work on their own in Codex or Claude without the CLI.

[!WARNING] vibe-loop is in early development. It is not yet well tested or broadly reviewed, so treat it as experimental automation and run it only where failed commands or incorrect agent behavior cannot damage important work.

Installation

vibe-loop requires Python 3.11 or newer.

Install it as a standalone CLI:

uv tool install vibe-loop
pipx install vibe-loop

Install it into an existing Python environment:

python -m pip install vibe-loop

For unreleased changes, install the current repository state from GitHub:

uv tool install git+https://github.com/ei-grad/vibe-loop

Quick Start

Point vibe-loop at a supported task source. For a small repository, this Markdown table is enough:

| ID | Priority | Status | Dependencies | Scope | Acceptance | Evidence |
| --- | --- | --- | --- | --- | --- | --- |
| TASK-01 | P0 | Next | none | Make one scoped change. | Tests pass. | Not run. |

For an existing planning format, generate a repo-specific profile instead:

vibe-loop tasks configure --repo . --dry-run --json   # review candidate
vibe-loop tasks configure --repo . --json             # activate

Inspect runnable work, then run one selected task with the configured agent:

vibe-loop tasks list --repo .
vibe-loop tasks tree --repo .
vibe-loop next --repo .
vibe-loop run-next --repo .

Add --ask-agent to delegate selection to the configured selection command after the mechanically safe candidate list is built:

vibe-loop run-next --repo . --ask-agent
vibe-loop run-until-done --repo . --ask-agent

[!NOTE] Worker commands and direct skill use work best when routine edits, tests, reviews, and integration steps do not stop on permission prompts. Configure Codex or Claude with a thoroughly scoped allowlist and dontAsk mode.

[!WARNING] With permission prompts disabled, any Codex or Claude session — launched directly or by a worker command — MUST run in isolation (Docker container or VM) with only the required repository, tools, network access, and credentials available.

Skills

The package includes four installable skills — three worker skills and one operator skill:

  • vibe-loop — one coherent bounded slice. The agent inspects the task, edits, verifies, asks for independent review when available, commits, integrates to main when policy permits, cleans up, and stops.
  • infinite-vibe-loop — unattended continuation across finite slices. After each slice it chooses conservative next work, reports blocked paths, and continues until stopped.
  • orchestrated-vibe-loop — multi-agent execution where the main agent keeps orchestration state and delegates exploration, implementation, and independent review without doing the code or review work itself.
  • autopilot — unattended stewardship of the loop. The agent drives vibe-loop run-until-done, reviews what landed each cycle, troubleshoots worker sessions, replenishes the ready queue by invoking orchestrated-vibe-loop to plan and decompose work, recovers the supervisor from evidence, and sleeps between cycles with vibe-loop wait-helper. Unlike the worker skills it drives the CLI by design, and it never edits the main worktree itself.

The worker skills share one slice lifecycle; the operator skill drives a worker pool and delegates to them. They compose rather than transition into each other:

flowchart TB
    subgraph workers["Worker skills — carry a slice through its lifecycle"]
        VL["<b>vibe-loop</b><br/>one bounded slice"]
        IVL["<b>infinite-vibe-loop</b><br/>unattended continuation"]
        OVL["<b>orchestrated-vibe-loop</b><br/>roles split across agents"]
    end
    subgraph operator["Operator skill — stewards a running loop"]
        AP["<b>autopilot</b><br/>drives the CLI, never edits main"]
    end

    IVL -->|each iteration is one| VL
    OVL -.->|same lifecycle,<br/>states assigned to roles| VL
    AP -->|supervises or observes| RUD["vibe-loop run-until-done<br/>(CLI worker pool)"]
    RUD -->|launches workers running| VL
    AP -->|replenishes ready queue via| OVL

See docs/skill-work-modes.md for the slice-lifecycle state machine, the orchestrated swimlane, the autopilot operator cycle, and a mode-selection guide.

Install them into Codex and/or Claude:

vibe-loop install-skills --codex --claude

The worker skills do not require the CLI; you can invoke them directly for manual bounded or unattended work. The autopilot operator skill does drive the CLI. The CLI exists when a repository already has a task source and you want repeatable orchestration: candidate discovery, locks, configured worker commands, run logs, completion checks, and run metadata.

Commands

Tasks

vibe-loop tasks list --repo .
vibe-loop tasks tree --repo .
vibe-loop tasks inspect QUERY-09 --repo .
vibe-loop tasks runnable --repo .
vibe-loop tasks locks --repo .
vibe-loop tasks configure --repo . --dry-run --json
vibe-loop tasks configure --repo . --force-refresh --json
vibe-loop tasks configure --repo . --promotion-toml
vibe-loop next --repo .

vibe-loop tasks without a subcommand is a compatibility alias for vibe-loop tasks runnable.

Run

vibe-loop run-next --repo . --ask-agent
vibe-loop run-until-done --repo . --ask-agent --jobs 2

--ask-agent hands the agent the mechanically safe candidate list plus recent .vibe-loop/runs.jsonl entries and log tails. The CLI validates returned IDs against current unlocked candidates, rejects duplicates and unknown or locked tasks, and falls back to deterministic ready order before spawning. When task sources declare resources or paths, the scheduler also rejects overlapping conflict domains; undeclared tasks are not paired once conflict-domain scheduling is active.

run-next always runs a single worker. run-until-done is serial by default; --jobs N keeps up to N workers active, each with its own task lock, run id, and log path. Two independent stop limits bound a run-until-done session:

Flag Caps Counts Default
--max-slices N total dispatched slices every attempt, any outcome 0 (unlimited)
--max-tasks N completed slices only completed results 0 (unlimited)

Whichever limit is reached first ends the loop. Under --jobs, the scheduler never dispatches more in-flight work than the remaining --max-tasks budget allows, so completed runs do not overshoot N.

run-next and run-until-done keep their result JSON on stdout; progress and mirrored agent stdout go to stderr, and full streams are captured in .vibe-loop/runs/<run-id>.log. Each result includes a run_id.

Agent session transcripts

For Claude workers the supervisor injects a known --session-id <uuid> into the worker command, so the run records the agent's real session id (session_id_source = observed) and a transcript_path pointing at the agent's own transcript ($CLAUDE_HOME/projects/<encoded-cwd>/<uuid>.jsonl, default ~/.claude). The result record's path is resolved by globbing for the unique session id after the run, so it is correct regardless of how Claude encodes the working directory; the earlier run_started record carries a predicted path before the transcript exists. An operator can go straight from a runs.jsonl record (or vibe-loop runs) to the transcript file to see what the agent actually did. The path is best-effort: if the agent persists no transcript (for example --no-session-persistence), the recorded path may not exist. Injection is skipped when the command already pins --session-id.

Codex exec has no equivalent flag to force or print a session id without switching stdout to --json (which would replace the streamed human-readable output the wrapper log and the selection/analysis text parsing rely on), so Codex worker runs keep session_id_source = fallback:run_id and no transcript_path. If a worker instead emits a session id: ... line on its output, that native id is captured with session_id_source = native:stdout/native:stderr.

Unknown-run recovery

A worker that does real work, commits to its claimed branch, then exits without filing a clear terminal report — for example a one-shot claude -p that parks on a billable external/authorization gate and quits — leaves the run unknown: never merged, never reported. Rather than stop or re-attempt the task from scratch (which orphans the committed-but-unmerged work), run-until-done deterministically launches a bounded continuation worker for unknown runs.

Recovery uses the normal read-write worker command path (not the read-only analysis agent of --ask-agent). The continuation worker receives a recovery prompt carrying the task id, the prior run_id, the claimed branch and worktree (from the workspace_claim record), the prior agent transcript path and wrapper log, and the instruction to investigate, finish the work and/or emit a proper status, and report blocked with a precise reason instead of parking again. It builds on the existing claimed branch/worktree and never deletes, resets, steals, or merges another worker's committed work.

Recovery is bounded by the same per-task budget as transient restarts (supervision.max_restarts): each attempt is counted through the task_restart record, and once the budget is exhausted the run is left with a terminal failed record (classification_source = recovery_budget_exhausted) so an unknown → recover → unknown cycle cannot loop. Every recovery launch and its outcome are journaled append-only as task_recovery records in .vibe-loop/runs.jsonl. Set supervision.recover_unknown_runs = false to disable recovery and have the supervisor stop on unknown as before.

Worker-side commands

Workers can report status, claim a workspace, and serialize final integration while the supervisor run is active.

# Report final status (authoritative for classifying that run).
vibe-loop report --repo "$VIBE_LOOP_REPO" --run-id "$VIBE_LOOP_RUN_ID" \
  --task-id "$VIBE_LOOP_TASK_ID" --status blocked --commit HEAD \
  --message "waiting on reviewer" --metadata-json '{"reason":"review"}'

# Make branch/worktree ownership visible (advisory metadata only).
vibe-loop worker claim-workspace --repo "$VIBE_LOOP_REPO" \
  --run-id "$VIBE_LOOP_RUN_ID" --task-id "$VIBE_LOOP_TASK_ID" \
  --branch "$BRANCH" --worktree "$WORKTREE"

# Serialize the refresh/verify/fast-forward-merge critical section.
vibe-loop main-integration acquire --repo "$VIBE_LOOP_REPO" \
  --run-id "$VIBE_LOOP_RUN_ID" --task-id "$VIBE_LOOP_TASK_ID" --wait --timeout 300
vibe-loop main-integration release --repo "$VIBE_LOOP_REPO" \
  --run-id "$VIBE_LOOP_RUN_ID" --task-id "$VIBE_LOOP_TASK_ID"
vibe-loop main-integration status --repo .

Report statuses are completed, blocked, failed, and unknown. Reports classify a run but are not task-source mutations and do not mark a task Done; before reporting completed, the worker should update the active task source. Without a report, the supervisor falls back to exit status, completion checks, task probing, and main-branch change heuristics.

claim-workspace requires a matching active task lock and verifies the worktree is on the requested branch; it records branch, worktree, base commit, HEAD, and dirty state, and never creates, deletes, resets, merges, or cleans up branches/worktrees. main-integration acquire --wait --timeout N waits for a live or unknown holder; stale locks are reported, never stolen. If the active task lock has a workspace claim, acquisition is blocked when the claim's diagnostics make integration unsafe. Worktree and branch handling stay outside the CLI runtime — put that policy in repository instructions or the agent command.

Status and diagnostics

vibe-loop workers --repo . --json
vibe-loop runs list --repo .
vibe-loop runs inspect <run-id> --repo .
vibe-loop doctor --repo . --json
vibe-loop specs check --repo . --json
vibe-loop --version

runs list groups records by run id and shows the latest status plus log path; runs inspect <run-id> prints the detailed record history. vibe-loop --version prints the package version; editable source-tree and non-tag Git installs append (git <short-sha>).

Evals

vibe-loop eval local-demo --repo . --trials 3 --agent-command '*=codex exec {prompt}'
vibe-loop eval release-gate --repo . --overwrite --record-output .vibe-loop/release-readiness.json
vibe-loop eval benchmark --adapter manifest --manifest path/to/benchmark.json

eval local-demo materializes fresh bundled fixture repositories, runs the same prompt across selected skill conditions and agent commands, and emits aggregate.json/aggregate.md with a skill_quality section that separates task failures from workflow-contract failures and compares against any previous aggregate.

eval release-gate is the bundled skill release-readiness check. Without --aggregate/--dry-run it runs a 16-trial release matrix and writes a skill_release_readiness record. It requires required trials to pass and blocks unresolved workflow_contract_regression findings; a regression is accepted only when parked with a task id (e.g. --parked-regression condition_comparison:vibe_loop=EVAL-99). eval benchmark runs an explicit external smoke manifest through the same paired-condition harness without claiming public leaderboard comparability. See docs/skill-evaluation-strategy.md and docs/external-benchmark-fit.md.

Autopilot

Autopilot supervises one repository (or a registry of several) and drives run-until-done cycles. It never deletes worktrees, resets branches, steals locks, or mutates tracked project files on its own. See docs/prd/autopilot.md for the full contract.

vibe-loop autopilot status --repo . --json
vibe-loop autopilot run --repo . --once
vibe-loop autopilot run --repo . --interval 60 --max-cycles 10 --jobs 2
vibe-loop autopilot projects register --repo . --name my-project
vibe-loop autopilot projects status --json
vibe-loop wait-helper --pid 12345 --json

status collects a read-only snapshot — queue counts, runnable tasks, active workers, stale locks, workspace diagnostics, git refs/dirty state, the main-integration lock, supervisor state, blockers, and the last cycle. It never starts a worker or mutates state. The --json ProjectStatus payload is the machine-readable boundary consumed by external status surfaces such as the loopyard web UI (board, agents, and timeline screens).

run is a foreground supervisor that launches run-until-done as a child and append-records one autopilot_cycle per iteration. Before each launch decision it cleans only stale worker locks whose recorded worker process is missing — the same validated, audited path as vibe-loop workers clean --force (it emits lock_expired records and never deletes worktrees, resets branches, steals live locks, or removes a lock that has not yet observed a worker PID). Each cycle then runs a native worktree-disposition step: it gathers per-worktree evidence mechanically, asks the read-only analysis agent for a keep-or-reap decision per orphaned worktree, and reaps (git worktree remove plus git branch -d) only merged, clean, non-live-claimed leftovers within the evidence-gated guardrails — never the primary worktree and never dirty or unmerged work-in-progress. It appends a reaped_worktrees:N action tag and an autopilot_worktree_reap journal record every cycle. A cycle is still blocked (never force-recovered) when preflight diagnostics are unsafe: dirty repo, remaining stale locks, unsafe workspace diagnostics, missing task source, or an unavailable agent command. --once runs one cycle. Without --interval, it drains runnable work and exits when a cycle is idle or blocked; with --interval N it stays resident, sleeping N seconds between cycles until --max-cycles or an interrupt. --jobs, --ask-agent, --continue-on-failure, --max-slices, and --max-tasks are forwarded to each child; --min-ready sets the minimum runnable depth required before launching. If the queue is below that depth and no explicit [autopilot] planning_command is configured, the cycle records planning_unconfigured with the low/no-runnable observation instead of authoring new tasks itself. A single supervisor lock prevents duplicates; Ctrl-C terminates the in-flight child and releases the lock.

projects manages an optional multi-project registry (register, list, remove, status). It records only repo paths and display names in a small JSON file (default ~/.vibe-loop/projects.json, --registry to override); each project keeps its own state directory. projects status [--json] returns one aggregate entry per repo, and a repo that cannot be read becomes an isolated error entry so one broken project never hides the others.

Interactive status dashboards (board, agents, and timeline/Gantt screens) live in the loopyard web UI, which consumes the read-only autopilot status --json / projects status --json boundary. The former in-tree autopilot tui (Textual) and autopilot webui surfaces were removed in favor of loopyard.

The top-level vibe-loop wait-helper blocks until a watched process exits or a wall-clock deadline arrives, so an unattended steward can sleep between cycles. --pid (repeatable) wakes on process exit; --cycle-schedule [SECONDS] wakes at the next UTC */SECONDS boundary; omitting both --deadline and --cycle-schedule uses the default 1800s boundary. --deadline takes an explicit ISO-8601 UTC time; --mode all waits for every PID. It prints wake_reason (pid, all_complete, or deadline) with a summary. It watches OS processes and the clock only — agent-specific wake signals stay in the agent environment.

Configuration

All configuration is optional. A typical .vibe-loop.toml:

main_branch = "main"
state_dir = ".vibe-loop"

[agent]
# Optional when kind = "auto" and Codex or Claude is available on PATH.
kind = "auto"
command = "codex exec {prompt}"
selection_command = "codex exec {prompt}"
# analysis_command runs a read-only agent for autopilot decisions; the default is
# read-only by construction (Codex read-only sandbox; Claude with Edit/Write/
# NotebookEdit disallowed). Override only with another read-only invocation.
# analysis_command = "codex exec --sandbox read-only {prompt}"
forward_stderr = false   # agent stderr is log-only by default; set true to mirror it

[task_source]
type = "markdown-plan"
# Set source keys only when you want to pin Markdown discovery.
plan_path = "PLAN.md"
plan_paths = ["PLAN.md", "docs/PLAN.md", "ROADMAP.md", "TODO.md"]
runnable_statuses = ["Active", "Next", "Planned"]

[completion]
commands = [
  "uv run python scripts/record_worklog.py --validate",
  "uv run python scripts/generate_gantt.py --coverage-check",
]

[supervision]
max_restarts = 3
cooldown_seconds = 30.0
recover_unknown_runs = true   # set false to stop on `unknown` instead of launching a continuation worker
worker_timeout_seconds = 10800.0  # wall-clock cap per worker; its process group is killed and the task returns to runnable. 0 = unbounded

[locks]
type = "directory"
# lease_seconds = 300   # locks go stale after this many seconds without a heartbeat

[autopilot]
# Defaults for `autopilot run`; explicit CLI flags override these.
# jobs = 2
# interval_seconds = 60.0
# min_ready = 1
require_clean_repo = true   # set false to let a dirty tree run a cycle
# Optional user-authored maintenance hooks, redacted in status/doctor JSON.
# A failing health command blocks the launch; planning runs when the runnable
# queue is below min_ready; if planning is not configured, the cycle records
# planning_unconfigured. Summary runs after a launch; troubleshoot runs after a
# failed child. Generated profiles can never introduce these hooks.
# health_command = "scripts/health.sh"
# summary_command = "scripts/summary.sh"
# troubleshoot_command = "scripts/troubleshoot.sh"
# planning_command = "scripts/plan.sh"

When --repo points at a Git linked worktree without its own .vibe-loop.toml, vibe-loop falls back to the main worktree's config (warning on stderr). Runtime state, locks, logs, and caches still live under the invoked --repo worktree.

Agent command and prompt dialect

The executable command and the worker prompt dialect resolve independently. agent.command and agent.selection_command are shell templates; agent.kind, agent.prompt_dialect, and agent.skill_ref_prefix control how the prompt references the bundled skill. Explicit .vibe-loop.toml command values stay authoritative — no generated profile can introduce executable commands.

kind Behavior
auto (default) Omitted commands use deterministic supported-agent detection.
codex Codex-style worker prompts with $vibe-loop.
claude Claude-style worker prompts with /vibe-loop.
custom Explicit templates; requires prompt_dialect or skill_ref_prefix.

Under kind = "auto", omitted commands follow a Codex-first policy: Codex only → codex exec {prompt}; Claude only → claude -p {prompt}; both installed → Codex. When neither CLI is available, agent-using commands fail with a diagnostic.

Set Claude (or a custom launcher) explicitly when that is the worker you want regardless of what else is installed — the dialect comes from kind, not from parsing the command string:

[agent]
kind = "claude"
command = "CLAUDE_HOME=.claude claude -p {prompt}"
selection_command = "CLAUDE_HOME=.claude claude -p {prompt}"

[agent]
kind = "custom"
command = "my-worker --prompt {prompt}"
selection_command = "my-selector --prompt {prompt}"
prompt_dialect = "claude"   # maps to /vibe-loop; "codex" maps to $vibe-loop
# skill_ref_prefix = "/"     # equivalent low-level form ($ or /)

kind = "custom" without prompt_dialect or skill_ref_prefix is a configuration error, not an implicit Codex default. Legacy configs that set agent.command without agent.kind still run; set one of the dialect fields to clear the migration diagnostic.

agent.command receives {task_id}, {run_id}, and a shell-quoted {prompt} (skill reference, normalized task context, CLI addendum). Workers also get VIBE_LOOP_RUN_ID, VIBE_LOOP_TASK_ID, VIBE_LOOP_REPO, and VIBE_LOOP_LOG in their environment; selection_command receives a {prompt} with the candidate list and recent run context. Single-task selection prints JSON with task_id; batch selection prints task_ids. If a task has traceability metadata, agent.command must include {prompt} — task-id-only templates fail fast.

agent.analysis_command is a third template used by autopilot for read-only analysis and decision steps, distinct from the read-write worker. Its auto default is read-only by construction — Codex runs in a read-only sandbox (codex exec --sandbox read-only {prompt}) and Claude disallows Edit/Write/NotebookEdit while keeping Read/Grep/Glob (claude -p --disallowedTools Edit Write NotebookEdit {prompt}) so it can inspect work-in-progress without mutating the repository. It resolves through the same Codex-first detection as the other commands, must include {prompt}, returns a strict JSON decision parsed like selection_command output, and is never invoked by routine read-only status commands. Generated profiles can never introduce an analysis_command.

Per-task agent routing

The default [agent] runs every task. When different tasks are better served by different agents, define named agent profiles and routing rules so the worker agent is chosen per task at dispatch. The motivating case: Codex refuses security-heavy tasks under its cyber-content policy ("flagged for possible cybersecurity risk"), while claude -p --model opus handles them without refusals. Routing keeps Codex as the fast default and sends only the security-hazard tasks to Claude.

[agent]
kind = "codex"            # default profile: fast, separate quota

[agent.profiles.claude-opus]
kind = "claude"
command = "claude -p {prompt} --model opus"

[[agent.routing]]
profile = "claude-opus"
match_hazards_any = ["abi", "dma", "irq"]   # security-sensitive kernel hazards
match_paths_glob = ["kernel/**"]            # optional extra constraint

Each [agent.profiles.<name>] table takes the same fields as [agent] (kind, command, selection_command, analysis_command, dialect fields) and resolves through the same machinery — a profile with only kind = "claude" gets the built-in Claude command default. The top-level [agent] remains the default profile. With no profiles and no routing, behavior is identical to a single [agent].

[[agent.routing]] is an ordered list. Each rule names a profile and one or more match predicates evaluated against the task; a rule matches when all of its predicates match (AND within a rule), and the first matching rule wins (OR across rules by order). Predicates (all optional, absent-safe):

Predicate Matches when
match_hazards_any the task's hazards share any token with this list
match_paths_glob any task path matches any glob (fnmatch)
match_task_id_regex the regex searches the task id
match_title_regex the regex searches the task title
match_priority the task priority equals this value (case-insensitive)

Resolution precedence at dispatch: an explicit per-task agent field (a profile name carried on the task itself) wins over everything; otherwise the first matching routing rule; otherwise the default [agent]. A routing rule or task agent field that names an undefined profile is a hard error — routing fails closed rather than silently falling back, so a mistyped profile can never send a security task to a refusing agent. The resolved profile name is recorded in the run metadata alongside the agent kind and model for provenance.

The per-task agent and hazards fields come from command-backed task sources that emit them in their task JSON ("agent": "claude-opus", "hazards": ["abi"]); sources that omit them are unaffected.

Locks

Locks default to directory locks under <state_dir>/locks. Repos that coordinate through an external service can opt into command-backed locks:

[locks]
type = "command"
acquire_command = "my-lock-tool acquire --json"
release_command = "my-lock-tool release --json"
status_command = "my-lock-tool status --json"
list_command = "my-lock-tool list --json"

Lock commands run from the repository root and receive VIBE_LOOP_LOCK_OPERATION, VIBE_LOOP_LOCK_TASK_ID, VIBE_LOOP_LOCK_RUN_ID, VIBE_LOOP_LOCK_ROOT, and VIBE_LOOP_LOCK_METADATA_JSON. acquire_command handles both acquire and update, returning {"acquired": true|false, "metadata": {...}}. Release returns {"released": true|false}; status returns {"locked": true|false, ...}; list returns a JSON array or {"locks": [...]}. Once type = "command" is set, lock failures fail closed instead of falling back to directory locks. When locks.lease_seconds is set, acquired locks carry lease_seconds, heartbeat_at, and a fencing token; workers refresh with vibe-loop worker heartbeat, and stale holders are rejected on a generation mismatch.

Task sources

The runner is task-system agnostic. Without explicit source configuration, read-only commands inspect <state_dir>/generated-task-source.json: a fresh profile cache becomes the active source; a degraded cache (planning_only, needs_input, unavailable, rejected) is diagnostic only and may continue to Markdown fallback; a stale or invalid cache blocks fallback and points to tasks configure. With no cache, the Markdown fallback scores .md files outside ignored directories and picks the best unambiguous table. Set task_source.plan_path to pin a specific Markdown source.

Runnable statuses default to Active, Next, and Planned. A task is runnable when all dependencies are Done and no local lock exists. The active task source is the source of truth for the dependency graph; .vibe-loop/runs.jsonl records attempts but does not advance task status. A completed worker must update the task source itself (or its command-backed adapter must report the task as non-runnable) before the next pass — vibe-loop deliberately keeps no private task-state channel, so agents and humans working without the CLI advance the same backlog.

Setting any explicit source key — type, plan_path, plan_paths, profile, list, next, probe, reset — disables generated cache as the active source. Non-source settings such as runnable_statuses still override matching generated fields without disabling the generated parser.

Command-backed sources read tasks from an issue tracker or custom tool:

[task_source]
type = "command"
list = "my-task-tool list --json"
probe = "my-task-tool show {task_id} --json"
reset = "my-task-tool reset {task_id}"

list returns a JSON array or {"tasks":[...]}. Each task should include id, title, status, priority, dependencies, scope, acceptance, and evidence. Optional resources and paths arrays declare conflict domains for parallel scheduling: resource names match exactly, path locks use repo-relative paths and conflict when one is an ancestor of another. Omitted/null arrays are undeclared; empty arrays explicitly declare no domains. Tasks may also carry optional traceability fields — requirement_ids, spec_paths, design_refs, approval_state, source_fingerprints — emitted in task JSON, analytics, promotion, and worker prompts when present.

reset is an optional hook, templated with {task_id}, that asks the backend to return a claimed task to its runnable state. A worker claims its task (runnable → active) in the backend itself; if it then dies on a provider limit wall before any terminal transition, the task is left claimed with no live vibe-loop lock and would never be re-dispatched. When the supervisor classifies a run as a limit wall it invokes this hook for the affected task so the next cycle can pick it up again. Absent hook, backend status is left untouched (vibe-loop never mutates project-owned status on its own); a hook that fails is logged and non-fatal.

Spec gates (read-only diagnostics by default). doctor and specs check report unapproved tasks, stale fingerprints, missing requirement IDs, and completed traceable tasks without evidence. Repos that require current approved specs can opt into execution gates:

[specs]
require_approved = true
require_current_fingerprints = true
require_requirement_coverage = true
require_completion_evidence = true
approved_states = ["approved"]
override_commands = ["make specs-override"]

The require_* settings gate execution commands (run-next, run-until-done); read-only inspection stays available. Override commands are reported as repository-owned recovery guidance and are never run automatically.

Ralphex-style Markdown plans:

[task_source]
type = "ralphex-markdown"
plan_path = "docs/plans/checkout.md"
# plan_paths = ["docs/plans/checkout.md", "docs/plans/refund.md"]

The parser reads ### Task N: and ### Iteration N: headings, derives Done only when every checkbox in a block is checked (Planned otherwise), and uses stable repo-relative IDs such as docs.plans.checkout:task-1. A ## Validation Commands section is copied into each task's evidence. Conflict domains can be declared per task, in a plan-level ## Conflict Surface section, or inline:

### Task 1: Add checkout API
- [ ] Add checkout handler
- Resources: api, checkout
- Paths: src/checkout.py, tests/test_checkout.py
- Conflict Surface: resources: api, checkout; paths: src/checkout.py

In a plan-level ## Conflict Surface section, unlabeled bullets that look like repo-relative paths (including root files like Makefile) are treated as path domains. Use Resources: none / Paths: none to declare an empty domain; blank or absent labels leave it unknown.

Spec-driven presets for common task artifacts, instead of a command adapter:

[task_source]
type = "spec-kit"   # specs/*/tasks.md, .specify/specs/*/tasks.md
# type = "kiro"     # .kiro/specs/*/tasks.md
# type = "openspec" # openspec/changes/*/tasks.md

spec-kit reads checkbox lists with T001-style IDs, optional [P]/story markers, inline (depends on T001), and nested Depends/Acceptance/Evidence labels. kiro and openspec read numbered checkbox lists (1., 1.2) with the same patterns. Checked → Done, unchecked → Planned, [-]/[~]Active. All three also read nested Conflict Resources/Conflict Paths labels. When several files are exposed, IDs are prefixed with the spec/change directory (e.g. 001-login:T001, checkout-mutation:1.2); missing files, missing or duplicate IDs, and invalid dependency syntax fail visibly. Markdown profiles can map the same traceability and conflict-domain fields:

[task_source.profile.fields.resources]
column = "Resources"
none_values = ["none"]

[task_source.profile.fields.paths]
column = "Paths"
none_values = ["none"]

Generated discovery asks the resolved agent.selection_command for a strict JSON parser profile, validates it, and caches it under state_dir:

vibe-loop tasks configure --repo . --dry-run --json       # review candidate, no write
vibe-loop tasks configure --repo . --json                 # create/repair active cache
vibe-loop tasks configure --repo . --force-refresh --json # regenerate a fresh cache
vibe-loop tasks configure --repo . --promotion-toml       # print committable [task_source] TOML

Generated caches are versioned JSON with fingerprints, redacted provenance, confidence, and a degradation status; they record agent identity and command source, never raw command strings, and can never contain executable adapters or lock backends. Read-only commands never launch an agent to create or repair the cache. --promotion-toml prints a non-executable type = "markdown-profile" snippet so a repo can make discovery explicit. See docs/generated-task-discovery.md for the schema, precedence, stale-cache behavior, and degradation states.

Planning Analytics

Planning timeline and Gantt analytics were removed from vibe-loop; those reporting surfaces now live in the loopyard web UI (board, agents, and timeline screens) over the read-only autopilot status --json boundary. See docs/planning-analytics.md for the superseded in-tree contract.

Local State

Runner state is intentionally untracked:

.vibe-loop/
  locks/
  runs/
  runs.jsonl

Task locks store the worker pid, task_id, run_id, log path, start time, base main revision, host, resolved command, and optional lease metadata. vibe-loop workers reconstructs the active view from lock files plus runs.jsonl and marks same-host locks with missing processes/PIDs, expired leases, or incomplete metadata as stale — without reading raw logs. When a worker claims its workspace, the lock also stores a workspace object (branch, worktree, base commit, HEAD, current branch, dirty state); workers --json adds read-only workspace_git_state and workspace_diagnostics that flag missing or duplicate worktrees, already-merged branches, dirty worktrees, and stale mismatches with manual recovery hints. doctor --json summarizes the same diagnostics. Neither command deletes locks, branches, or worktrees.

main-integration.lock is a separate advisory lock for worker-owned final integration (owner task, run id, host, pid, start time), visible through vibe-loop main-integration status. Stale status is diagnostic only.

runs.jsonl is an append-only stream of versioned run records: result records carry the run_id, started_at, resolved session_id and source, the agent transcript_path when one is resolved, the agent command/selection sources, prompt dialect and skill reference sources, and the default agent policy source. Lifecycle records (run_started, agent_context_observed, run_state_transition) expose the same anchor plus bounded trailer-ready context — task IDs for Plan-Item/Run-Id/Session-Id, agent kind, prompt dialect, and model provider/ID/reasoning effort when the agent emits them. vibe-loop does not own commit hooks; repository tooling decides whether to persist this context into project history. Project worklogs should remain final evidence ledgers — attempt logs and failed runs belong in .vibe-loop/, not in completion records.

Spec-Driven Workflow Execution

vibe-loop can sit underneath spec-driven development tools as the task execution layer. Tools such as Spec Kit, Kiro, and OpenSpec own intent — requirements, design docs, proposals, task lists, approvals. vibe-loop owns repeatable execution: it consumes the task layer, schedules runnable slices, launches finite workers, captures logs, enforces locks, and records completion. A spec or PRD is not treated as proof of implementation unless a task row, worker report, commit reference, test, review, or other explicit evidence links the contract to completed work.

This repository uses a three-level planning model: PROMPT.md (philosophy, architecture boundaries, PRD-writing rules) → docs/prd/ (stable PRD-* contracts) → PLAN.md (runnable slices with permanent task IDs).

Relationship to ralphex

vibe-loop is inspired by umputun/ralphex: a repeatable autonomous loop that gives coding agents bounded tasks, validates results, and records progress instead of relying on one long interactive chat. The main differences:

  • ralphex is plan-file centered; vibe-loop is task-source agnostic (generated profiles, Markdown tables, explicit plan paths, or command adapters) and fits existing project planning instead of requiring a dedicated plan directory.
  • ralphex runs a dedicated plan through task and review phases; vibe-loop runs one repository backlog slice at a time and merges reviewed slices back to main frequently.
  • vibe-loop treats agent execution as configuration (template commands, not a hard dependency on one CLI) and keeps workers finite, leaving branch/worktree management to the agent.

Future Plans

The current implementation supports generated task-source profiles, command-backed sources, dependencies, conflict domains, finite workers, run logs, structured reports, and skill evals. Planned spec-driven additions stay below the authoring layer:

  • parser presets for Spec Kit, Kiro, OpenSpec, and similar artifacts;
  • optional traceability fields on normalized tasks;
  • read-only spec coverage and drift checks;
  • opt-in execution gates requiring approved, current spec artifacts;
  • spec-aware worker prompt context;
  • completion evidence mapping requirements to plan rows, reports, trailers, tests, and reviews.

Development

Install the repository tools with uv, then run the standard checks:

uv sync
uv run python -m unittest discover
uv build
uv run --with twine --no-project -m twine check dist/*

The Makefile wraps the common release steps:

make install-hooks
make bump-patch
make bump-minor
make check
make tag

make tag uses the current uv version --short value by default; pass VERSION=... to check or tag an explicit version. The installed pre-commit hook runs ruff check and ruff format --check; the installed pre-push hook rejects pushed v* tags when pyproject.toml or the vibe-loop entry in uv.lock does not match the tag.

Releases are built by .github/workflows/release.yml via PyPI trusted publishing with the TestPyPI and PyPI GitHub environments. Run the workflow manually with target TestPyPI for staging; to publish, push a v<version> tag matching project.version, or dispatch from that tag with target PyPI. Before publishing bundled skill changes, run the release-readiness gate and put the record path in the release notes:

uv run vibe-loop eval release-gate --repo . --overwrite \
  --record-output .vibe-loop/release-readiness.json

See docs/release-checklist.md for the checklist and dry-run record format.

License

vibe-loop is licensed under the MIT License. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vibe_loop-0.2.0.tar.gz (267.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vibe_loop-0.2.0-py3-none-any.whl (373.5 kB view details)

Uploaded Python 3

File details

Details for the file vibe_loop-0.2.0.tar.gz.

File metadata

  • Download URL: vibe_loop-0.2.0.tar.gz
  • Upload date:
  • Size: 267.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vibe_loop-0.2.0.tar.gz
Algorithm Hash digest
SHA256 acd87552947b9589dd0a3922eb6b8ccb7e69075499e93651fe90cec6e052ce7f
MD5 c0f99ed6e4ceb1500dc2b7e49d7b8e62
BLAKE2b-256 34d19573f8e25fa043e097d7692e5bfa431324265e65bcce49f04e99611c2555

See more details on using hashes here.

Provenance

The following attestation bundles were made for vibe_loop-0.2.0.tar.gz:

Publisher: release.yml on ei-grad/vibe-loop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vibe_loop-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: vibe_loop-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 373.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vibe_loop-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 807639c2a55b59240dfca0a376a4491822bcad5bf21bfea28185108065de0741
MD5 1a465f69cd4f897d466470a0c4946510
BLAKE2b-256 9c35b6dc14bdef29016e6f36350223d9b952399f95811c5e38668185e68d5f11

See more details on using hashes here.

Provenance

The following attestation bundles were made for vibe_loop-0.2.0-py3-none-any.whl:

Publisher: release.yml on ei-grad/vibe-loop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page