Skip to content

ISS-13446: Run vibe and fleet in any repository - #426

Open
shafty023 wants to merge 9 commits into
mainfrom
feat/iss-13446-generic-vibe
Open

shafty023 wants to merge 9 commits into
mainfrom
feat/iss-13446-generic-vibe

Conversation

@shafty023

@shafty023 shafty023 commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • ISS-13446: vibe and fleet-orchestrator run in any git repository. Everything ClosedLoop and symphony-alpha specific moves into a new optional plugin, vibe-closedloop-provider, that ships text and data only (provider.json plus reference blocks). Plan: the approved v2 plan, with Daniel's answers to its open questions (OQ1 to OQ12) recorded on the ticket.
  • Both prerequisites have landed: symphony-alpha #9779 (merged as e3f9f4d8e7, commits .closedloop-ai/vibe-recipes.json, the opt-in this PR reads) and the fleet orchestrator's migration of its orchestration directory into the fleet home. origin/main (7c34119: ISS-14039: Accept a Fable review for a Claude cloud import #427 at vibe 1.1.82 / vibe-core 1.0.5, then ISS-14028, ISS-14024: Bind fleet chats to their programs, safe migrate #428 at 1.1.84 / 1.0.7) is merged in, so this change takes the next versions.
  • Binding: one probe at vibe or fleet start records provider: {name, state, capabilities, ...} on the startup record and the session; scripts, workers and hooks read it and never probe. States are none, bound and bound-disconnected (a failed precondition is surfaced, never a silent downgrade). Records from before bindings existed wait for a chat that binds instead of being written generic.
  • Repository opt-in (OQ5): recipes bind only when the repository commits .closedloop-ai/vibe-recipes.json naming the provider; it is judged in the session's repository, falling back to its main checkout for sessions branched before the file landed. No repository names are hard-coded anywhere.
  • Generic path (provider absent or disabled): description intake with no tracker, operator identity from gh, local #n work ids for fleet units, plans and decision tables under <git common dir>/vibe/<slug>/, bootstrap from AGENTS.md or the lockfile, an unknown layout takes the full review path, UI files known by extension, repo-owned gates run only when the repo ships them, main sync with nothing to run reports NEEDS_REVIEW, an undeclared lane source must be the AGENTS.md test command or the repo's only workflow.
  • Hooks: every vibe-core guard is gated on activation (a vibe-plugin skill in the chat, a launcher-started worker, or a cwd in a vibe worktree); elsewhere a guard prints, writes and calls nothing. ClosedLoop guard rules (unit status, e2e slot) come from the bound provider's data.
  • Fleet: writers inherit the lead's binding through the brief's Provider: line; remote writers get it with the launch (--lead-provider). Status sweeps, ticket units and placement by ticket are provider duties.
  • Install (OQ1): a start with no provider ever installed and a ClosedLoop connection answering offers to install it once; a disabled or unreadable provider is never reinstalled over, and an uninstall is respected.
  • Gates: vibe_generic_scan.py check fails on provider-specific text in runtime files (exact-line allowlist with reasons); the inventory diff test proves no gating line of the base was dropped; generic-walk.test.mjs runs the walk's deterministic steps in a personal repository and requires an empty blocker ledger; the Codex eval runner has provider and generic arms.
  • Two independent adversarial reviews (A and B) ran on the full diff; every finding was verified against the code. A1 to A7 and B2 to B10 are fixed with tests: remote fleet writers could not start under a bound lead; the opt-in was read only from the session branch; generic UI diffs counted as server-only; legacy records could be written generic; old hook scope records stopped activating; the Prior removal check widened; clerkOrgId was no longer written for an older vibe; main sync with no check reported DONE; an undeclared workflow lane source could be any workflow; generic plans lived in the per-worktree git dir; symphony E2E text sat in generic handoff text; an unreadable provider was offered for reinstall.
  • Reported, not changed:
    • A8: vibe-core 1.0.5 installed beside an older vibe. Both ship in this PR, and the start check flags an outdated pair.
    • B11: in a chat that never activated vibe, activation reads the transcript on each hook event. The proposed fix is a state write, which the activation ruling forbids.
  • Known limit, tracked as ISS-14022 (review finding B1) and not changed here: vibe still assumes the default branch is main unless vibe.baseRef is set (session creation, handoff gate, several merge-base diffs, main sync).
  • Reading provider text (Daniel's ruling of 2026-11-11, adopting the weight review): stubs and inline SKILL.md stay; provider.mjs assemble <file> writes a vibe file merged for the session's binding, and assemble --blocks <file> every bound block of a loaded SKILL.md in one call (35 per-stub calls in vibe SKILL.md become 1). Each generic region and its provider block now share their identical lines once (5207 leading/trailing and 1818 interior words), proved byte-identical for 41 files across 12 bindings. Measured bound read path (skills and references, SKILL.md raw plus one blocks file, references assembled, the provider references a bound reader opens): 61045 words on main for the changed files, 67321 bound, +6276 (about 8.5% of the 73778-word base, down from +10.4k); almost all of the rest is the generic regions and stubs inline in the six stubbed SKILL.md files (vibe +2563, handoff +1776). Moving the heaviest stubbed sections into references is later work, measured first.
  • Broken provider (orchestrator rulings): only "no provider with is bound" (exit 3) means generic text. A bound provider that is gone, a missing block or reference, or a session or startup record that exists but cannot be read stops with provider-broken: <reason>. Fix: ... (exit 4) in provider.mjs, the Claude launcher (PROVIDER_BROKEN), and every vibe script that reads the provider. Hooks never throw: they show the reason and fix and keep their generic default, and a guard whose default would relax the bound rule refuses the action it guards (unit-status-guard on an MCP status write, shell-safety-guard on an executed e2e script, pr-create-guard when the override decides). Session records are written atomically.
  • Also fixed: prototype-session.mjs shadowed deploymentHost with a const of the same name (a throw on every call) and its bundle was stale; test helpers ran scripts from the real checkout, which the new cwd repository leg could pick up.
  • Versions: vibe 1.1.85, vibe-core 1.0.8 (VIBE_CORE_MIN 1.0.8), vibe-closedloop-provider 1.0.0; dist bundles rebuilt. Word count against 1.1.84 at this head (instruction-words.mjs --changelog passes): vibe about 20747 words shorter, the provider adds about 42146, all plugin Markdown about 21780 longer. The merges brought ticket slugs into runtime files (four (ISS-14039) from ISS-14039: Accept a Fable review for a Claude cloud import #427, five ISS-14028/ISS-14024 comments from ISS-14028, ISS-14024: Bind fleet chats to their programs, safe migrate #428); they are removed here (OQ10). The sixth from ISS-14028, ISS-14024: Bind fleet chats to their programs, safe migrate #428, fleet-home.mjs LAUNCHER_MARK, is text written into installed launchers and matched before an overwrite, so it stays as an allowlisted compatibility line.
  • Merging ISS-14028, ISS-14024: Bind fleet chats to their programs, safe migrate #428: unit-status-guard keeps the bound provider's rule and gains ISS-14028, ISS-14024: Bind fleet chats to their programs, safe migrate #428's per-chat program board (both tests kept; ISS-14028, ISS-14024: Bind fleet chats to their programs, safe migrate #428's chat test now passes the provider rule). provider.json (1310 words) is data only scripts read; per-file provider headers and block markers are never printed to an agent.

Breaking changes

  • With the provider absent or disabled, vibe and fleet run generically: no ClosedLoop ticket, plan, graph or memory steps. symphony-alpha keeps today's behavior once the provider is bound (its opt-in file is on main).
  • board.mjs refuses writes to an orchestration directory that is not a program in a fleet home (fleet-home.mjs migrate moves one). Session records gain an optional provider field; environmentOrgId is written beside the earlier clerkOrgId, which is still read and written.

Test plan

  • Full lanes, because the diff is too broad to target: provider.mjs (new, so not in the test-selection index) is imported by nearly every vibe and vibe-core test. code_tests_for_changes on the last round's 61 files selected 102 unit test files (hooks, fleet, handoff and vibe scripts, claude-worker); the full lanes cover them all.
  • Local, the way ci.yml runs them (harness env stripped, fixture HOME): ruff, pyright and pytest (2806) pass. Node Tests: 201 files, 2074 tests, 0 failed. Plugin Manifest Versions, Vibe Eval T0 (vibe-bench typecheck, validate:plugins, tests, Codex test_run.py) and vibe-workers typecheck, vitest and dist freshness all pass.
  • e2e-lock.test.mjs hung once under a loaded full run (load average about 40 from other sessions). Its signal cases then passed 3 of 3 in isolation, and its one change (the top-level catch calls failScript) is not on the signal paths. It passed in the final full run.
  • vibe-workers main-sync.test.ts and main-sync-input-boundary.test.ts timed out (16 to 44 s against 15 s) at load average 37 to 45. They passed 122/122 on one sequential rerun.
  • The 26 cl-sweep tests that failed locally failed only because Claude Code's env vars (CLAUDECODE, CLAUDE_CODE_SESSION_ID) leaked into the lane. Run the CI way, 69/69 pass.
  • New tests: assemble output equals assembleProviderText (none, each capability, all); --blocks order and labels; exit 2, 3 and 4; an unreadable record is broken; a script exits 4 (local-plans); a guard refuses while generic checks still run, and grep/cat of e2e paths pass (pre-tool-use); a hook warns and still runs (program-change); PROVIDER_BROKEN mapping and CLI exit 4 (claude-worker).
  • Reviews: two adversarial reviews of the full diff, plus a Fable 5.2 weight review and a Fable 5.2 adversarial review of the final round. Every finding was fixed or is listed above.
  • Cloud lanes not run (targeted-tests ruling; CI on this PR is the Linux gate). Plugin Version Bump checked locally.
  • After merging main (ISS-14039: Accept a Fable review for a Claude cloud import #427): ruff, pyright, vibe python pytest, the node tests of the files ISS-14039: Accept a Fable review for a Claude cloud import #427 touched plus the provider and version tests (135/135), claude-worker vitest (67/67) with dist rebuilt, manifests and vibe-bench all pass.
  • After merging main 7c34119 (ISS-14028, ISS-14024: Bind fleet chats to their programs, safe migrate #428): ruff, pyright and pytest pass; Node Tests the CI way 201 files, 2123 tests, 0 failed; manifests and vibe-bench (54 passed) pass through the local heavy slot. vibe-workers through the heavy slot: 843 passed and 18 timed out (16 Test timed out at 5 s, 2 spawnSync node ETIMEDOUT), with no assertion failure: claude-worker.test.ts 8, main-sync.test.ts 7, main-sync-ci, main-sync-input-references and main-sync-report-admission 1 each, at load average 50 to 65 on 10 cores from other sessions. After the ISS-14039: Accept a Fable review for a Claude cloud import #427 merge the same claude-worker file passed 67/67 and main-sync 122/122 in a sequential run; they are left to CI on clean runners rather than rerun here.

Move every ClosedLoop and symphony-alpha specific line out of vibe and
vibe-core into the new optional vibe-closedloop-provider plugin, which ships
text and data only. With the provider bound, sessions keep today's behavior;
without it, vibe and fleet work from the repository itself: the person's
description, plans in the private git directory, the AGENTS.md test command,
the lockfile install, and a requested PR or named recipient at the end.

- provider binding, provider text assembly and data access in vibe-core
- guards read their rules from the bound provider and stay silent outside
  an activated chat
- main sync gates its provider-only machinery on the provider's recipe
- the recipes opt-in file is validated and withholds recipes with one reason
- the provider is offered for install once, never re-enabled or reinstalled
- generic scan gate, generic walk fixture, eval arms, and generic twins for
  the provider-bound tests
- vibe 1.1.81, vibe-core 1.0.5, vibe-closedloop-provider 1.0.0
- writer ledgers parse capabilities by shape; grants are checked against the
  session binding at register and launch
- the Claude launcher resolves provider text for its roles
- writers inherit the lead's binding through a Provider line, enforced by the
  Codex launcher, the Claude launcher and the Agent guard
- the recipes opt-in file is validated and judged in the session repository
- adopting a session keeps its binding; moved provider releases re-resolve
- every installed provider's plan folder stays private
- generic fleet recovery ids and duties, the worktree exclude rule, the
  guardrails formatter from the verify recipe
- dangling reference paths fixed, provider text free of ticket ids and home
  paths, the inventory snapshot and mapping checked in pytest
- the eval provider arm installs provider-resolved text
- bundles build byte-identically from any working directory
Bind the tracker in the tracker-duty tests, run spawned scripts outside the
real checkout so the cwd repository leg cannot pick it, derive the provider
line in fleet tests from the fixture binding, fix the shadowed prototype
deploymentHost check and rebuild dist, list provider.mjs as a vibe-core
wrapper, and record the changelog word count.
Pass the lead's binding to remote fleet writers, read the recipes opt-in
from the main checkout for older sessions and for a named checkout, know
UI files by extension without an areas recipe, never write a legacy
provider record generic, keep old hook scope records active, restore the
exact ticket pattern for Prior removal, keep clerkOrgId for older vibe,
report a main sync with no check as needing review, require the sole
workflow for an undeclared lane source, keep generic plans in the common
git dir, move the repository E2E text into provider blocks, and stop
offering an install over an unreadable provider.
…c-vibe-v2

# Conflicts:
#	CHANGELOG.md
#	plugins/vibe/.claude-plugin/plugin.json
#	plugins/vibe/.codex-plugin/plugin.json
…ider

- provider.mjs assemble writes a vibe file merged for the session's
  binding, and assemble --blocks gives every bound block of a loaded
  SKILL.md in one call; the reading rules use them
- each generic region and its provider block share their identical lines
  once, so assembled text is unchanged and the provider is smaller
- only an unbound capability falls back to the generic text: a bound
  provider that is gone, a missing block or an unreadable record stops
  with its reason and fix (exit 4) in the CLI, the launcher, every script
  and the hooks, and a guard refuses the action it guards
- session records are written whole and renamed into place
- the 1.1.82 word count is measured at this tree
ISS-14039 (#427) took vibe 1.1.82 and vibe-core 1.0.5. Keep both sides of
the claude-worker launcher (the broken-provider mapping and the review
model), rebuild the bundles, drop the ticket slugs the merge brought into
runtime files, and remeasure the word count against the new main.
shafty023 added a commit that referenced this pull request Nov 12, 2026
Merge origin/main (ISS-14039, #427) and take vibe 1.1.84 and vibe-core 1.0.7, past the numbers #427 used and #426 has reserved. VIBE_CORE_MIN, the README version line, the release-pin tests and the CHANGELOG entries follow, and the vibe-workers bundles are rebuilt.
ISS-14028 and ISS-14024 (#428) took vibe 1.1.84 and vibe-core 1.0.7. The
unit-status guard keeps the bound provider's rule and gains the chat's
own program board; its tests keep both cases. The fleet-resume header
covers both changes, the bundles are rebuilt, the ticket slugs the merge
brought into runtime comments are dropped, the launcher mark written into
installed launchers stays as an allowlisted compatibility line, and the
word count is remeasured against the new main.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant