Reference: Configuration Schemas

Beach's configuration files are described by JSON Schemas published alongside the package. The schemas are the contract; TypeScript types are a projection. Other languages — when Beach grows bindings beyond TypeScript — validate against the same schema files.

The schemas ship under @cool-ai/beach-config/schemas/ and are addressed by canonical $id URLs of the form https://cool-ai.org/protocol/v1/config/<file>.schema.json.

File shapes

Eight schemas, one per file shape:

Schema Describes $id
umbrella.schema.json beach.yaml https://cool-ai.org/protocol/v1/config/umbrella.schema.json
functional.schema.json beach.functional.yaml https://cool-ai.org/protocol/v1/config/functional.schema.json
environmental.schema.json beach.environment.yaml https://cool-ai.org/protocol/v1/config/environmental.schema.json
deployment.schema.json beach.environment.<env>.yaml overlays https://cool-ai.org/protocol/v1/config/deployment.schema.json
actor.schema.json actors/<id>.yaml (polymorphic on kind) https://cool-ai.org/protocol/v1/config/actor.schema.json
channel.schema.json channels/<id>.yaml https://cool-ai.org/protocol/v1/config/channel.schema.json
tool.schema.json tools/<id>.yaml https://cool-ai.org/protocol/v1/config/tool.schema.json
model.schema.json models/<id>.yaml https://cool-ai.org/protocol/v1/config/model.schema.json
secrets-env.schema.json beach.secrets.env https://cool-ai.org/protocol/v1/config/secrets-env.schema.json
common.schema.json shared $defs referenced by the others https://cool-ai.org/protocol/v1/config/common.schema.json

The common.schema.json defines seven reusable types referenced by the others: configVersion (the version: 1 field every file declares), appExtension (the consumer-defined app: namespace), appCustomCollection (the shape Beach recommends for a single consumer-defined collection block under app.collections.<name>), appExtensionWithCollections (the extended app: block on the functional file, accepting both arbitrary consumer fields and the reserved collections namespace), varReference (the ${VAR} reference pattern), stringOrVarReference, and includeBlock (the my.cnf-style include vocabulary).

Application-side extensibility

Beach's schemas validate Beach's own fields. Consumer-defined fields live under app: namespaces and are validated separately, against a JSON Schema the consumer ships with the application:

  • Top-level app: on the functional file — application-wide consumer settings.
  • Per-instance app: on actor / channel / tool files — settings scoped to one thing.
  • app.collections.<name> on the functional file — consumer- defined collections using the same include vocabulary as Beach's own collections. The loader walks the directories and surfaces the assembled instances at config.functional.app.collections.<name>.instances.

Recommendation: ship a JSON Schema with every Beach application that has any consumer-defined app: content, and run the validator with --with-app-schema <path>. Why: it brings consumer fields under the same per-file checking Beach gives its own fields, instead of every adopter writing a parallel validator. The schema may target either the functional file's beach.app block, per-thing app: blocks, or both — Beach walks every app: block it finds and validates each against the schema.

YAML or JSON

Every per-thing schema accepts files written in either YAML or JSON. Recommendation: hand-edited config files use YAML (comments, friendly whitespace); machine-edited config files (typically files an admin UI in the application reads, modifies, and writes back) use JSON (canonical serialiser, no comments to preserve). Why both: forcing a single format penalises one audience for the convenience of the other. The validator checks contents, not extension.

The actor schema is polymorphic

actor.schema.json is a oneOf over three shapes, discriminated by kind:

  • kind: llm — the LLM-actor case. Requires model; permits systemPromptInline | systemPromptFile, tools, domainDataSchema, maxTokens, temperature, inject.
  • kind: handler — the deterministic-handler case. Requires handlerName. Runtime support arrives with CR-02 (non-LLM participants); v1 accepts the declaration so that no schema migration is required when CR-02 lands.
  • kind: long-running — the durable-workflow case. Requires runtime and workflowId. Same forward-compatibility posture.

A file with kind: llm that omits model is rejected. A file with kind: handler that adds model is rejected. The validator's error points at the specific field.

Tool scope is required and prominent

tool.schema.json makes scope a required field with no default. Router-scope tools form the public capability surface that other peers can see; specialist-scope tools are internal substrate. A tool file that omits scope is rejected at validation time, not silently defaulted.

beach-config tree groups tool files by scope so the public surface is legible at a glance.

The deployment overlay is a strict allowlist

deployment.schema.json is not a permissive subset of the environmental schema — it is a strict allowlist of the fields that legitimately differ between environments: infrastructure.redis.url, infrastructure.manifestRegistry.backend, infrastructure.missiveStore.backend, per-channel imap / smtp / pollIntervalSeconds / manifestTimeoutSeconds, and observability.logLevel / observability.otlpEndpoint.

Anything not on the allowlist — including any functional field — is rejected. The principle is non-negotiable: an environmental overlay must not reach into interior behaviour. A change to the orchestrator's prompt or to a tool's input schema is a code change, not a deployment change.

Cross-axis enforcement (validator-only)

Two enforcement rules are declared in the schemas' descriptions but applied at validation time, because JSON Schema cannot express them directly:

  • ${VAR} references in functional files. Permitted in environmental and deployment files; rejected anywhere on the functional axis (the functional file itself, every actor file, every tool file, every model file). The validator reports the path and points at beach.environment.yaml as the correct location.
  • Secrets-shape rules. A value in beach.secrets.env that looks like a plain URL, hostname, port number, model name, or boolean is rejected with a hint pointing at beach.environment.yaml. A connection string with embedded credentials is permitted but flagged as a warning.

Both rules are part of the v1 contract; future versions can refine the detection rules but cannot remove the enforcement.

Versioning

Every Beach config file declares version: 1 at the top of its beach: block. The schemas share this version; a future version: 2 introduces a migration path through beach-config migrate (out of v1 scope).

The $id URLs are stable. Schema changes that are non-breaking (added optional fields, added enum values) ship as patch updates of the package; schema changes that break existing files would bump the version field and ship a new $id family at https://cool-ai.org/protocol/v2/....

Related