vibey-domain-model
Skill3 starsChanged 25 days agoMITView on GitHub
Phases, jobs, ledger, handoff, rotation, and the no-loss gate — the core domain types and invariants in vibey.
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 0 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 9
Read docs/plans/domain-model.md before touching domain/.
- Very specific: line 73
Bounded ladders park, they do not loop: When build.implement or build.verify exhausts its effort ladder (or a spend cap), the job parks …
- Very specific: line 81
Idempotency: every job is idempotent under replay via idempotency_key — a deterministic hash of (project_id, cycle, kind, subject).
- Very specific: line 115
Digest: every event carries a digest (SHA-256 of canonical JSON payload).
- Very specific: line 123
When an engine hits CreditsExhausted, vibey:
- Very specific: line 127
If the gate passes, writes the full ledger to <worktree>/.vibey/handoff/ledger.jsonl and seeds the next engine.
- Very specific: line 129
If the gate fails, regenerates (up to 3 attempts), then escalates to full_transcript mode, then raises a human gate.
- Very specific: line 165
No starvation: over sum(effective_weight) consecutive selections, every candidate with effective_weight > 0 is selected at least once.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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 → DESIGNwhen blocked on ambiguity (spec insufficient).REVIEW → DESIGN(default) orREVIEW → BUILD(fast path when every finding isunambiguous). See ADR-0010.DEPLOY_REVIEWcan 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 → BUILDedge directly. - Deployment: after ③ has no open product findings, vibey asks whether to
work on deployment. Declining records
completion_mode = "local"and transitions toDONEwithout 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(aliasbuild.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(aliasdeploy.accept) - DEPLOY_EXECUTE:
deploy.execute(aliasdeploy.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:
- Produces a
HandoffBrief(the outgoing engine writes it, or the incoming engine synthesizes it if the outgoing is dead). - Verifies it against the no-loss gate (
domain/noloss.py::verify()). - If the gate passes, writes the full ledger to
<worktree>/.vibey/handoff/ledger.jsonland seeds the next engine. - If the gate fails, regenerates (up to 3 attempts), then escalates to
full_transcriptmode, 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 theledger.jsonlfile 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 witheffective_weight > 0is 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 > 0has 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 toSTANDARD→HIGHafter 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:
- Type-level:
domain/capacity.py's dataclass has no such attribute. - Property-tested:
schedule_probe(CreditsExhausted(...))can only return aBackoffProbe, never aDeadlineProbe. - Database-level: the
engine_healthtable has a CHECK constraintcredits_never_have_a_deadlinethat rejects anyINSERT/UPDATEwithcapacity_state = 'CreditsExhausted'and non-nullresets_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).
In this repository23 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .cursor/rules/typesafe-ai.mdcCursor rule
- .cursor/rules/vibey-architecture.mdcCursor rule
- .cursor/rules/vibey-domain-model.mdcCursor rule
- .cursor/rules/vibey-engine-adapters.mdcCursor rule
- .cursor/rules/vibey-quality-gates.mdcCursor rule
- .cursor/rules/vibey-releasing.mdcCursor rule
- .cursor/rules/vibey-testing.mdcCursor rule
- docs/llms.txtllms.txt
- typesafe-aiSkill
- vibey-architectureSkill
- vibey-domain-modelSkill
- vibey-engine-adaptersSkill
- vibey-quality-gatesSkill
- vibey-releasingSkill
- vibey-testingSkill
- typesafe-aiSkill
- vibey-architectureSkill
- vibey-engine-adaptersSkill
- vibey-quality-gatesSkill
- vibey-releasingSkill
- vibey-testingSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

