Manifest schema

.cabinet — manifest schema

Every cabinet folder has a .cabinet file at its root. It's the only file Cabinet requires. Without it, a folder is just a folder; with it, the folder becomes a cabinet that the app can open, the registry can install, and other cabinets can nest.

schemaVersion: 1
id: my-startup
name: My Startup
description: One-person SaaS company with an AI team.
kind: root
version: 0.1.0
entry: index.md

Every field

FieldTypeRequiredDefaultWhat it does
schemaVersionintegeryesAlways 1 right now. Lets Cabinet evolve the schema without breaking old cabinets.
idstringyesSlug used as the cabinet's stable identifier. [a-z0-9-]+.
namestringyesDisplay name. Shown in the sidebar header and in the registry.
descriptionstringno""One-line summary. ≤80 chars — that's the registry card limit.
kind"root" | "child"yesRoot cabinets stand alone; child cabinets nest under another cabinet's folder.
versionsemverno"0.1.0"Useful for templates published to cabinets.sh.
entrypathno"index.md"The page Cabinet opens when you click into the cabinet.
iconstringnoinheritsPath or URL to a square icon used in the sidebar and registry.
tagsstring[]no[]Free-form tags used for registry filtering.
licenseSPDX idnoinheritsMIT, Apache-2.0, etc. Shown on the registry page.
authorobjectno{name, url, email}. Shown on the registry page.
providersobjectnoProvider routing config. See BYOAI for the shape.
dispatchobjectnoCross-department dispatch policy. See Org chart.
approvalsobjectnoAuto-approval rules for low-impact proposals.

Root vs. child

A root cabinet is the top of a tree. It has its own .cabinet, lives directly in $CABINET_DATA_DIR, and shows up in cabinetai list.

A child cabinet is a folder inside another cabinet that gets its own scope, agents, and settings. The child has its own .cabinet, and the parent treats it as a sub-cabinet (it shows up nested in the sidebar).

~/cabinets/
├── my-startup/                ← root
│   ├── .cabinet               ← kind: root
│   └── clients/
│       └── acme/
│           ├── .cabinet       ← kind: child
│           └── index.md
└── job-hunt-hq/               ← root
    └── .cabinet               ← kind: root

Provider routing example

providers:
  defaults:
    lead: claude-opus-4-7
    specialist: claude-sonnet-4-6
  fallbacks:
    claude-opus-4-7:
      - claude-sonnet-4-6
      - gpt-4.1
  budgets:
    daily:
      maxCostUsd: 25
    perTask:
      maxCostUsd: 5
  localOnly: false
SubkeyPurpose
defaults.lead / defaults.specialistDefault model per agent type.
fallbacksPer-model fallback chain when the primary returns 429 or hits a budget cap.
budgets.daily.maxCostUsdHard cap per cabinet per day.
budgets.perTask.maxCostUsdHard cap per individual task.
localOnlyIf true, refuse any non-local provider.

Dispatch policy example

dispatch:
  allow:
    - from: marketing/gtm-lead
      to: research/*
      kinds: [LAUNCH_TASK]
    - from: operations/ops-coordinator
      to: marketing/copywriter
      kinds: [LAUNCH_TASK, SCHEDULE_JOB]
  acceptFromParent:
    - from: ops-coordinator
      kinds: [LAUNCH_TASK]

See Org chart & departments for the full story.

Approvals example

approvals:
  auto:
    - kind: LAUNCH_TASK
      maxCostUsd: 0.10
      fromAgent: linkedin-operator
      toAgent: copywriter
    - kind: SCHEDULE_TASK
      maxCostUsd: 0.50
      fromAgent: ceo

Anything outside policy queues for manual approval. There's no global "trust everything" toggle.

Validation

Cabinet validates the manifest at boot. cabinetai doctor re-runs validation. A bad manifest exits with code 6 and a precise field-level error.

Read on