Skip to content

Declaring what to watch

What the adaptive placement agent may watch, and which paths it may write.

.berth/classes.yaml, in your repository. pilot reads it and writes nothing outside the paths it names.

version: 1
repo:
  allowed_paths:
    - deploy/voice.yaml
    - deploy/embed.yaml
classes:
  - name: voice-agent-prod
    model_id: NousResearch/Meta-Llama-3-8B    # what to watch in the registry
    model: llama3-8b                          # the berth registry key
    running_on: h100-pcie                     # what it runs on today
    config_path: deploy/voice.yaml
    slo:
      metric: p99_ttft_ms
      bound_ms: 800
    workload:
      concurrency: 8
      prompt_tokens: 512
      output_tokens: 128
      mtok_per_hour: 12.0                     # optional, enables the ledger

Declared axes and chosen axes

Three things are declared by you and define the problem:

model drives the physics: bytes per token, cache geometry, precision
workload token lengths, concurrency, arrival shape
slo the bound, applied as a feasibility gate

Three are chosen by the engine and are the search space: chips, providers, and price basis.

Declaring one of the chosen axes is refused:

classes[0] carries 'provider' as a declared axis. Chips, providers and price
bases are chosen by the engine, not declared. If you need to narrow the
search, put it in a `constraints:` block on this class and say why.

The reason is that the engine cannot otherwise tell a preference from a physical property. running_on is not a pin: it says what runs today, so a recommendation has something to beat.

A constraint is legitimate and it goes somewhere else:

    constraints:
      providers: [aws]
      price_bases: [on-demand, reserved]
      interruption_tolerant: false
      exclude_silicon: [mi300x]
      reason: data residency, EU only, and AMD quota is not approved

A reason is required. A constraint costs money, and the reason is what lets anyone tell later whether it is still worth paying. The status page shows what the answer would have been without it.

interruption_tolerant: false is the default and it is not a narrowing. It means spot has no price for this workload rather than a low one: an evicted request does not deliver a late token, it delivers nothing.

Every field is refused rather than defaulted

Missing fields raise, named in the order you read them down the file. Duplicate class names raise, because the name is the key state is tracked against. An empty allowed_paths means none, deliberately: an agent that can write anywhere is one nobody grants access to.

mtok_per_hour is the exception, and it is optional. Without it the class appears on the status page and not in the ledger, because a percentage improvement is not money until somebody says how much work there is, and inferring it would let the reported saving be adjusted by changing an assumption.

Parsing

from berth.declaration import load_yaml

decl = load_yaml(open(".berth/classes.yaml").read(), repo="acme/infra")
repo = decl.repo_target("acme", "infra")   # the same paths bound the client

PyYAML is used when present and a minimal reader handles this format when it is not. The core has no dependencies and you should not install one to be read.