Skip to content
Back to skills

Vibey Domain Model

ASecurity

Phases, jobs, ledger, handoff, rotation, and the no-loss gate — the core domain types and invariants in vibey.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsgosqlazuregitdatabase

Works with

  • cli

Security analysis

A100/100

Scanned October 4, 2026

npx -y skills add the-vibey-project/vibey --skill vibey-domain-model --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Vibey Domain Model?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Vibey Domain Model
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/the-vibey-project-vibey-domain-model/badge)](https://www.skillsdirectory.com/skills/the-vibey-project-vibey-domain-model)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: vibey-domain-model
description: Phases, jobs, ledger, handoff, rotation, and the no-loss gate — the core domain types and invariants in vibey.
allowed-tools: Read
---

# vibey domain model

Read `docs/plans/domain-model.md` before touching `domain/`. This skill is a
compressed orientation; the full spec lives in that doc.

## Phases

Six numbered phases plus optional visual interstitial:

```
INTAKE → ① DESIGN → [optional VISUAL_DESIGN] → ② BUILD ⇄ ③ REVIEW
         ▲──────────────────────────┘               │
                                                     │
③ ── user declines deployment ──────────────────────→ DONE (local)
  │ user opts into deployment
  ▼
④ DEPLOY_DESIGN → ⑤ DEPLOY_EXECUTE → ⑥ DEPLOY_REVIEW → DONE (deployed)
```

Interactive phases (talk to the user): ①, ③, ④, ⑥, VISUAL_DESIGN.
Autonomous phases (unattended): ②, ⑤.

**Loop-backs:**
- `BUILD → DESIGN` when blocked on ambiguity (spec insufficient).
- `REVIEW → DESIGN` (default) or `REVIEW → BUILD` (fast path when every
  finding is `unambiguous`). See ADR-0010.
- `DEPLOY_REVIEW` can route to ④, ⑤, or back to ①/②/③ depending on the
  failure classification.

**Opt-in gates:**
- Visual design: after ① accepts a buildable spec, vibey asks whether to
  enter the visual-design interstitial. Declining takes the `DESIGN → BUILD`
  edge directly.
- Deployment: after ③ has no open product findings, vibey asks whether to
  work on deployment. Declining records `completion_mode = "local"` and
  transitions to `DONE` without enqueueing any Azure job.

See ADR-0014 (optional visual-design and deployment opt-in).

## Jobs

Job kinds are strings registered against handlers in `bootstrap.py` (25 kinds
as of 2026-09-15):
- DESIGN: `design.interview`, `design.research`, `design.synthesize`, `design.spec`
- VISUAL_DESIGN: `visual.inventory`, `visual.plan`
- BUILD: `build.decompose` (alias `build.plan`), `build.implement`, `build.verify`,
  `build.integrate`
- REVIEW: `review.demo`, `review.collect`, `review.triage`, `review.deployment_choice`
- DEPLOY_DESIGN: `deploy.design`, `deploy.interview`, `deploy.synthesize`,
  `deploy.spec` (alias `deploy.accept`)
- DEPLOY_EXECUTE: `deploy.execute` (alias `deploy.graph`)
- DEPLOY_REVIEW: `deploy.demo`, `deploy.triage`, `deploy.route`

`docs/plans/phase-protocols.md` also names kinds that are not registered
(`media.generate.*`, `visual.review`, `deploy.discover`/`plan`/`validate`/`apply`/
`release`/`verify`/`recover`/`collect`); treat those as planned. Handoff is not a
job kind: it runs inside the engine-driven job through `application/wind_down.py`
and `application/handoff_orchestration.py`.

**Job lifecycle (`domain/job.py::JobState`):**
`ready → leased → succeeded | failed | awaiting_human | awaiting_capacity | cancelled`.

**Failure classes (`domain/job.py::FailureClass`):** `capacity` (opens the
circuit), `engine` (opens after 3), `work` (the code is wrong; circuit untouched),
`vibey` (our bug; circuit untouched).

**Bounded ladders park, they do not loop.** When `build.implement` or
`build.verify` exhausts its effort ladder (or a spend cap), the job parks behind a
human gate. The answer can grant a bound (`max_attempts` for implement attempts,
`max_rounds` for verify repair rounds, `max_dollars`/`max_turns` for spend);
further attempts run at the ladder's top effort until that grant is used up, then
park again (ADR-0024; `granted_limit`/`granted_amount` in
`application/build_verify_handler.py`).

**Idempotency:** every job is idempotent under replay via `idempotency_key` —
a deterministic hash of `(project_id, cycle, kind, subject)`. Re-enqueueing
the same logical work is a no-op. Handlers also guard their own side effects.

**Parallel vs serial:** some jobs run in parallel (e.g., `build.implement` —
each work item in its own git worktree). Others are serial (e.g.,
`build.integrate` — one integration branch, serialised per project and cycle by a
Postgres advisory lock in `infrastructure/db/advisory_lock.py`, ADR-0029).

See `domain/job.py` (`JobState`, `FailureClass`, `backoff()`,
`idempotency_key()`) and the handlers in `application/*_handler.py`.

## Ledger

The **append-only event log** in Postgres, written in a vendor-neutral schema.
Vendor transcripts (`~/.claude/projects/**/*.jsonl`, codex rollouts) are
copied in as *attachments* referenced by events — they are
evidence, not state.

**Event kinds (26 total, `domain/ledger.py::EventKind`):** `SessionSeeded`,
`TurnRequested`, `TurnCompleted`, `ToolInvoked`, `TranscriptRecorded`, `FileEdited`,
`VerdictRendered`,
`CapacityRejected`, `QuestionAsked`, `AnswerGiven`, `DecisionRecorded`,
`AssumptionStated`, `FindingRaised`, `FindingResolved`, `ArtifactProduced`,
`SavePointCreated`, `HandoffInitiated`, `HandoffAccepted`, `PhaseTransitioned`,
`BudgetSpent`, and the ADR-0014 opt-in gate events `VisualDesignOptedIn`,
`VisualDesignDeclined`, `VisualDesignAccepted`, `VisualDesignWaived`,
`DeploymentOptedIn`, `DeploymentDeclined`.

**Readers are forward compatible, writers strict (vibey#275):** a stored kind this vibey does not know is read as `UnrecognizedEventKind` (so `LedgerEvent.kind` is `EventKind | UnrecognizedEventKind`), kept in every range, full ledger and digest, skipped by every projection, and never written. Match kinds with `is EventKind.X` and narrow with `isinstance(kind, EventKind)` before using a member-only attribute.

**Closable events:** `QuestionAsked`, `DecisionRecorded`, `AssumptionStated`,
`FindingRaised` — these are the events the no-loss gate checks.

**Digest:** every event carries a `digest` (SHA-256 of canonical JSON payload).
`digest_range()` computes an order-sensitive Merkle-ish fold of a sequence of
events — this is what R6 (range integrity) checks.

See `domain/ledger.py` and `docs/plans/handoff-protocol.md`.

## Handoff and the no-loss gate

When an engine hits `CreditsExhausted`, vibey:
1. Produces a `HandoffBrief` (the outgoing engine writes it, or the incoming
   engine synthesizes it if the outgoing is dead).
2. Verifies it against the **no-loss gate** (`domain/noloss.py::verify()`).
3. If the gate passes, writes the full ledger to
   `<worktree>/.vibey/handoff/ledger.jsonl` and seeds the next engine.
4. If the gate fails, regenerates (up to 3 attempts), then escalates to
   `full_transcript` mode, then raises a human gate. In that mode the gate waives
   R1–R5, R7, R9; the design inlines the whole range, but as built the range is only
   the `ledger.jsonl` file named in the seed prompt — do not describe it as inlined.

**The 10 rules (R1–R10):**
- R1: remaining-work closure
- R2: open-question closure
- R3: decision closure
- R4: assumption closure
- R5: finding closure
- R6: range integrity (digest match)
- R7: artifact closure
- R8: budget carry
- R9: constraint closure
- R10: containment (nothing in the brief contradicts the spec)

The gate is **pure and deterministic** — no model call. This is the reason
rotation is safe.

See ADR-0004 (no-loss gate on handoff) and `domain/noloss.py`.

## Rotation

**Smooth weighted round robin** (nginx-style SWRR), not naive modulo. Engines
are selected from the eligible set via `domain/rotation.py::select()`.

**Eligibility:** installed, authenticated (`doctor` passed within TTL),
circuit not `open`, capability requirements met, and per-phase allow-list
permits it.

**When rotation happens:** at boundaries only — never mid-turn. Triggers:
new work item starts, capacity rejection (forced, exclude the rejecting
engine), effort escalation, phase transition, operator-requested handoff.

**Properties (property-tested):**
- No starvation: over `sum(effective_weight)` consecutive selections, every
  candidate with `effective_weight > 0` is selected at least once.
- Weight fidelity: selection counts converge to weight ratios within ±1 over
  any full period.
- Smoothness: no candidate is selected twice consecutively while another with
  `effective_weight > 0` has gone unselected longer.
- Determinism: identical candidate state produces an identical selection.

**Wiring:** `application/engine_selector.py::EngineSelector` calls `select()`
for engine-driven BUILD jobs, through `SelectingEngineProvider` in `bootstrap.py`.
Candidates come from `engine_health` rows, so an engine without a recorded
`vibey doctor --conformance --record` pass is never eligible. A local engine joins
only while its switch is on — gptossloop unless switched off, qwenloop and
claudeloop-local only when switched on — and is preferred first (ADR-0038,
ADR-0064).

See ADR-0005 (smooth weighted round robin), ADR-0007 (rotate at boundaries),
and `domain/rotation.py`.

## Effort ladder

Vibey speaks a normalized 5-level ladder: `TRIVIAL, LOW, STANDARD, HIGH, MAX`.
Each engine descriptor provides a **projection** onto native flags, which may
saturate.

**Phase base effort:**
- ① DESIGN: `HIGH`
- VISUAL_DESIGN: `HIGH`
- ② BUILD: `LOW` (auto-escalating to `STANDARD` → `HIGH` after failures)
- ③ REVIEW: `HIGH`
- ④ DEPLOY_DESIGN: `HIGH`
- ⑤ DEPLOY_EXECUTE: `LOW` (auto-escalating)
- ⑥ DEPLOY_REVIEW: `HIGH`

`Phase` also carries `DEPLOY` (a legacy single-phase bridge, base `LOW`) and
`ABANDONED`; the phase diagram omits them deliberately.

See ADR-0006 (normalized effort ladder) and `domain/effort.py`.

## Capacity states

`Available | WindowExhausted | CreditsExhausted | AuthenticationFailed`

**The one rule that matters:**
`CreditsExhausted` has **no `resets_at` field** and can never acquire one.
This is enforced at three independent layers:
1. Type-level: `domain/capacity.py`'s dataclass has no such attribute.
2. Property-tested: `schedule_probe(CreditsExhausted(...))` can only return a
   `BackoffProbe`, never a `DeadlineProbe`.
3. Database-level: the `engine_health` table has a CHECK constraint
   `credits_never_have_a_deadline` that rejects any `INSERT`/`UPDATE` with
   `capacity_state = 'CreditsExhausted'` and non-null `resets_at`.

See `domain/capacity.py`, `domain/circuit.py`, the constraint in
`migrations/0007_engine_health_rotation.sql`, and the CLAUDE.md non-negotiable "A
capacity rejection always outranks a completion claim" (it has no ADR of its
own).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…