Configuration

KDCube configuration is descriptor-driven. A deployment has six descriptor families: assembly.yaml for topology, identity, storage, and runtime settings; gateway.yaml for admission controls; economics.yaml for reservation, pricing, plans, quota, and budget policy; secrets.yaml for platform secrets; bundles.yaml for app inventory and non-secret app config; and bundles.secrets.yaml for app-scoped secret references and values.

App behavior belongs with the app entry in bundles.yaml. Agent inventories, consumed MCP services, provided surfaces, default chat intent, and application-hosted site declarations are not assembly or proxy configuration.

The browser frontend also receives a generated public config from the server at /api/cp-frontend-config. This config is built from assembly.yaml, so frontend auth mode, route prefix, tenant/project, and debug flags stay aligned with the same descriptor set used by the backend.

Config Hierarchy

Configuration hierarchy diagram
Configuration Hierarchy Six KDCube descriptor families and the Admin UI write surface assembly.yaml Platform version frontend config exec defaults gateway.yaml Rate limits Capacity Throttling bundles.yaml App defs git refs per-app config economics.yaml Plans · quotas Budgets · prices Reservations secrets.yaml bundles.secrets.yaml platform + app secrets provider selected in assembly Admin write surfaces App props · agents Economics policy writes through authority Configured descriptor authority → runtime projections/caches → app code defaults

Deployment scope: each running deployment is bound to one effective tenant/project, which may serve many users and operator-approved apps. Backing services can enforce logical tenant/project namespaces, but the identifier alone is not dedicated infrastructure isolation. Use separate deployments or dedicated infrastructure when stronger customer or lifecycle isolation is required.

Configuration boundary: assembly.yaml, gateway.yaml, economics.yaml, and platform/global secrets define the environment. bundles.yaml and bundles.secrets.yaml define the apps and app-scoped configuration inside that environment.

assembly.yaml

context:
  tenant: "demo-tenant"
  project: "demo-project"
release_name: "prod-2026-04-29"
platform:
  repo: "https://github.com/kdcube/kdcube.git"
  ref: "2026.4.29.1545"
  services:
    proc:
      exec:
        py_code_exec_image: "py-code-exec:latest"
        py_code_exec_timeout: 600
        py_code_exec_network_mode: "host" # trusted supervisor network
        py_code_exec_container_strategy: "split" # separate networkless executor
        max_file_bytes: "100m"
        max_exec_workspace_delta_bytes: "250m"
        max_workspace_bytes: ""
        workspace_monitor_interval_s: 0.5
secrets:
  provider: "secrets-file"   # secrets-service | secrets-file | aws-sm
auth:
  type: "cognito"      # bundle | simple | cognito | delegated
  turnstile_development_token: "" # local/dev Turnstile test token only
frontend:
  config:
    auth:
      authType: "cognito" # bundle | simple | cognito | delegated
    routesPrefix: "/platform"
    debug:
      injectDebugCommands: false
      animateStreaming: true
storage:
  workspace:
    type: "git"              # custom | git
    repo: "https://github.com/org/react-workspace.git"
  claude_code_session:
    type: "git"              # local | git
    repo: "https://github.com/org/claude-session-store.git"
paths:
  host_bundles_path: "/Users/you/dev/bundles"          # mounted into /bundles
  host_managed_bundles_path: "/Users/you/.kdcube/managed-bundles"
  host_bundle_storage_path: "/Users/you/.kdcube/bundle-storage"
  host_exec_workspace_path: "/Users/you/.kdcube/exec-workspace"

Server-Published Frontend Config

The frontend should request GET /api/cp-frontend-config at startup. The response is public browser configuration generated from assembly.yaml. It replaces hand-maintained static config files in managed deployments, while local/static fallback files can still exist for development.

{
  "auth": {
    "authType": "cognito",
    "apiBase": "/auth/",
    "turnstileDevelopmentToken": "1x00000000000000000000AA"
  },
  "routesPrefix": "/platform",
  "tenant": "demo-tenant",
  "project": "demo-project",
  "debug": {
    "injectDebugCommands": false,
    "animateStreaming": true
  }
}
Descriptor fieldPublished fieldPurpose
frontend.config.auth.authTypeauth.authTypeBrowser auth mode: app-hosted bundle, simple, cognito, or delegated.
frontend.config.auth.tokenauth.tokenExplicit public browser credential for simple development mode only. Other auth modes clear this field.
frontend.config.auth.apiBaseauth.apiBaseAuth proxy base path for delegated/custom auth flows.
auth.turnstile_development_tokenauth.turnstileDevelopmentTokenOptional local/test Turnstile bypass token. Not a production site key.
frontend.config.routesPrefixroutesPrefixBrowser route/API prefix used by the frontend.
context.tenant, context.projecttenant, projectDeployment scope used by browser API calls.
frontend.config.debugdebugPublic development flags such as debug command injection and stream animation.

Use frontend.config for public browser deployment differences such as auth mode, route prefix, auth proxy path, and debug flags. Do not put production secrets here. The legacy browser value hardcoded is treated as simple; new descriptors should use simple. oauth is not a deployment auth mode; use bundle for application-hosted login or cognito for Cognito/OIDC.

For auth.type: simple, the authority registry declares a simple_idp verifier provider and every service reads the pinned /config/idp_users.json store. A per-service idp_db_path does not configure this mode. SimpleIDP and its seeded browser administrator token are development/demo facilities, not a production identity posture.

auth.turnstile_development_token is only for local or test Turnstile bypass flows. Real Turnstile site keys are app/public feature config, not platform secrets. Turnstile secret keys belong in app secrets or platform secrets, depending on which component validates the token.

bundles.yaml

i
Public term vs descriptor name: KDCube now calls these units apps. The descriptor remains bundles.yaml and fields such as default_bundle_id remain unchanged for compatibility.
bundles:
  version: "1"
  default_bundle_id: "marketing-chat@2-0"
  items:
    - id: "marketing-chat@2-0"
      repo: "git@github.com:org/repo.git"
      ref: "2026.4.29.1545"
      subdir: "src/app/service/bundle"
      module: "marketing-chat@2-0.entrypoint"
      config:
        model_id: "gpt-4o-mini"
        role_models:
          solver.react.v2.decision.v2.strong:
            provider: "anthropic"
            model: "claude-sonnet-4-6"
        execution:
          runtime:
            mode: "docker"
            max_file_bytes: "100m"
            max_exec_workspace_delta_bytes: "250m"
            max_workspace_bytes: ""
            workspace_monitor_interval_s: 0.5
            descriptor_payload_scope: "active_bundle" # optional app descriptor narrowing

Platform-Reserved Config Keys

KeyDescription
role_modelsOverride LLM per role. Maps logical agent roles to concrete provider + model combinations.
embeddingEmbedding provider + model for RAG and vector search in knowledge spaces.
services.llm.customShared custom/local model gateway endpoint, fallback num_ctx, and exact-model model_overrides.<model-tag>.num_ctx. Model identity comes from role/composer selection; the API key is services.llm.custom.api_key in app secrets.
memory, ui.widgets.memoriesUser-memory runtime, reconciliation, tools, and optional built-in Memories widget for memory-enabled apps.
economics.reservation.<surface>Per-app cost reservation override, for example economics.reservation.chat. The legacy reservation_amount_dollars spelling remains accepted.
execution.runtimePer-app generated-code routing and limits. Modes are none, local, docker, fargate, or external; Docker additionally selects combined or reference split. descriptor_payload_scope: active_bundle filters only bundles.yaml and bundles.secrets.yaml to the caller app for trusted supervisor transport.
surfaces.as_consumer.mcp.servicesCanonical MCP services consumed by the app. Agent MCP tool entries under surfaces.as_consumer.agents.<agent_id>.tools reference these services by server_id.

Configuration & Secrets

bundles.yaml does not define a whole environment by itself. It defines the application modules that live inside the current tenant/project environment. One environment can host many apps.

bundles.yaml — Your App Definition

bundles:
  version: "1"
  default_bundle_id: "my-bundle@1-0"
  items:
    - id: "my-bundle@1-0"
      name: "My App"
      repo: "git@github.com:org/my-bundle-repo.git"   # optional git source
      ref: "2026.4.29.1545"
      subdir: "src/my_product/bundles"
      module: "my-bundle@1-0.entrypoint"
      config:
        named_services:
          namespace_styles:
            mem: { color: "green", label: "Memory" }
            task: { color: "blue", label: "Tasks" }
        embedding:
          provider: "openai"
          model: "text-embedding-3-small"
        role_models:
          solver.react.v2.decision.v2.strong:
            provider: "anthropic"
            model: "claude-sonnet-4-6"
        economics:
          reservation:
            chat: 2.0
        execution:
          runtime:
            mode: "docker"                 # none | local | docker | fargate | external
            container_strategy: "split"   # reference Docker profile; combined is legacy
            max_file_bytes: "100m"          # max single generated file
            max_exec_workspace_delta_bytes: "250m" # max net-new workspace output per exec call
            max_workspace_bytes: "50m"      # max total active workspace size
            workspace_monitor_interval_s: 0.5
            descriptor_payload_scope: "active_bundle" # optional app descriptor narrowing

Provider and Consumer Surfaces

surfaces.as_provider is what the app exposes. surfaces.as_consumer is what the app and each agent may call. An app can use either direction or both, and it does not need chat, UI, or an agent merely because those capabilities exist in the SDK.

config:
  surfaces:
    as_provider:
      bundle:
        default_chat: true
    as_consumer:
      mcp:
        services:
          mcpServers:
            knowledge:
              transport: "streamable-http"
              url: "https://knowledge.example/mcp"
      agents:
        main:
          tools:
            - kind: "mcp"
              server_id: "knowledge"
              alias: "knowledge"
              allowed: ["*"]

Top-level mcp.services, mcp.mcpServers, mcp_services, and MCP_SERVICES remain resolver fallbacks for older descriptors. New configuration should use the consumer surface above. The referenced server_id must exist in that app's consumed service map.

Named-Service Tool Policy

An app can consume domain realms through one named-service tool family instead of adding a near-duplicate tool set per provider. Consumer config declares the allowed namespace operations. Tool strategy traits describe ordered causality; execution traits describe completed-call scheduling and replay. Neither trait family authorizes a call.

tools:
  - kind: "named_service"
    alias: "named_services"
    namespaces:
      mem:
        allowed:
          - "provider.about"
          - "object.search"
          - "object.schema"
          - "object.upsert"
        tool_traits:
          upsert_object:
            strategy: ["neutral"]
      task:
        allowed: ["provider.about", "object.search", "object.schema", "object.upsert", "object.delete"]
    tool_traits:
      provider_about: { strategy: ["exploration"] }
      search_objects: { strategy: ["exploration"] }
      upsert_object: { strategy: ["exploitation"] }

Trait keys use ReAct-facing callable names such as search_objects, not provider operation names such as object.search. Search filters and action payloads come from provider self-description; before an action, the agent reads object.schema. Namespace styles such as mem: green and task: blue should be shared by chat, canvas, and scene surfaces instead of duplicated in one widget.

Per-Agent Configuration and User Selection

config.react.<agent_id> configures instructions, additional instructions, supported models, role models, and iteration limits; it can fall back to default_agent. The matching surfaces.as_consumer.agents.<agent_id> block defines the administrator-granted tools, skills, MCP servers, and named-service inventory.

Application config is the ceiling, not the user's stored choice. Agent model and capability selections are durable per conversation under conversation:<conversation_id>:agent_selection:<agent_id> in user_bundle_props. A separate agent_selection:<agent_id> row is an optional baseline for future conversations. New conversations copy that baseline once, or fall back to app configuration when no baseline exists. Chat changes remain local until the user selects Save changes.

Capability denials can narrow tool groups, tools, MCP servers/tools, named-service realms/operations/actions, skills, and subagents. A model pick is bounded by supported_models; denying subagents prevents spawner installation, react.delegate, and delegation teaching.

react:
  main:
    instruction_profiles:
      default: "full"
      options:
        - { id: "full", label: "Full", body: "..." }
        - { id: "extra-lite", label: "Extra Lite", blocks: ["xlite:workspace_exec"] }

The picker and wire contract carry instruction profile IDs only; instruction bodies and blocks stay server-side. A model or instruction-profile switch is a fully cold change for the applicable turn because provider caches are model-specific and the stable system prefix changes.

Default Chat and Application-Hosted Websites

surfaces.as_provider.bundle.default_chat: true declares that the app intends to serve the reserved SDK chat widget alias chat. Inheriting a reactive handler is capability, not intent. The effective value also requires a reactive entrypoint and can be gated by enabled.widget.chat.

config:
  ui:
    main_view:
      site:
        enabled: true
        alias: "workspace"
        default: true
        hosts:
          - "workspace.example.com"

Site config belongs to the app entry in bundles.yaml, not assembly.yaml, the CLI, CDN, or OpenResty. Valid declarations compile into the distributed ApplicationSiteCatalog.

For local development, an app entry can point at a mounted local path instead of a git repo. In that case the path in bundles.yaml must be the container-visible path under /bundles, not the raw host path.

bundles:
  items:
    - id: "my-bundle@1-0"
      path: "/bundles/my-bundle@1-0"
      module: "entrypoint"

App Secrets — Provider Backed

Secret resolution is provider-based. With secrets.provider: secrets-service, secrets are provisioned via the Admin UI or injected into the local kdcube-secrets sidecar and resolved in-memory at runtime. With secrets.provider: secrets-file, runtime reads and writes secrets.yaml and bundles.secrets.yaml through the configured storage backend, so those files are the source of truth. With aws-sm, deployment-scoped secrets live in grouped AWS Secrets Manager documents such as .../bundles/<bundle_id>/secrets; Redis is only cache, not authority.

bundles:
  version: "1"
  items:
    - id: "my-bundle@1-0"
      secrets:
        openai:
          api_key: null          # null = resolve from env OPENAI_API_KEY
        my_service:
          api_key: "<MY_SERVICE_API_KEY>"
          webhook_url: "env:MY_WEBHOOK_URL"  # or env: reference

Use bundles.secrets.yaml for app-scoped deployment secrets. Use secrets.yaml for platform/service secrets. In secrets-file mode, those files remain durable runtime inputs. In aws-sm mode they are the portable export/import shape, while the live authority is the grouped secret documents in AWS Secrets Manager.

App-Local Config Shape Files

A releasable app can self-document the descriptor shape it expects by carrying config/bundles.yaml and config/bundles.secrets.yaml inside the app directory. These files are documentation and release inputs for humans and agents; the running deployment still reads the environment's descriptor set.

Keep those app-local files non-sensitive. Use realistic non-secret defaults and placeholders for secret values. If an app has no secrets, keep the secrets shape empty so the absence of required secrets is explicit.

my-bundle@1-0/
  README.md
  release.yaml
  config/
    bundles.yaml          # non-secret config shape
    bundles.secrets.yaml  # secret shape with placeholders only

Reading Config & Secrets in Code

value = self.bundle_prop("some.nested.key")   # dot-path navigation
all_props = self.bundle_props                    # full merged dict

from kdcube_ai_app.apps.chat.sdk.config import get_settings, get_secret, get_plain

settings = get_settings()
max_exec_file_size = settings.PLATFORM.EXEC.PY.EXEC_MAX_FILE_BYTES

app_api_key = await get_secret("b:my_service.api_key")            # current app
platform_api_key = await get_secret("services.openai.api_key")    # same as a:services.openai.api_key

from kdcube_ai_app.apps.chat.sdk.config import get_user_prop, set_user_prop, set_user_secret
prefs = await get_user_prop("preferences.theme", default="light") # user + app
await set_user_prop("preferences.theme", "dark")           # Postgres
await set_user_secret("provider.refresh_token", token)              # secrets provider
token = await get_secret("u:provider.refresh_token")

host_bundles = get_plain("paths.host_bundles_path")                  # assembly.yaml
managed_bundles = get_plain("paths.host_managed_bundles_path")     # assembly.yaml
default_bundle = get_plain("b:default_bundle_id")                  # bundles.yaml

get_plain(...) / read_plain(...) reads non-secret descriptor data by dot path. Use no prefix or a: for assembly.yaml, and use b: for bundles.yaml.

get_secret("b:...") means "the current app". The runtime resolves that internal app id from the bound request context. Outside a bound app invocation, use the fully qualified secret path instead of b:.

CallReads from
get_plain("storage.workspace.type")assembly.yaml
get_plain("a:notifications.email.host")assembly.yaml
get_plain("paths.host_managed_bundles_path")assembly.yaml
get_plain("b:default_bundle_id")bundles.yaml
await get_secret("b:my_service.api_key")Current app secret document
await get_user_prop("preferences.theme")Current user + current app Postgres state
await get_secret("u:provider.refresh_token")Current user + current app secret provider scope

This is the descriptor-backed complement to secrets and user state: get_plain(...) is synchronous descriptor access; get_secret(...), user-prop reads/writes, and user-secret reads/writes are awaitable provider calls. User prop values may be native dictionaries or lists. Compatibility readers only decode a second JSON string layer when it begins with { or [, preserving literal strings such as false or de-DE.

Applying App-Specific Props

The platform applies reserved paths first on every props refresh and every surface. App-specific runtime configuration belongs in the public async hook below; do not override the internal _apply_bundle_props_overrides.

async def on_apply_props(self, props: dict) -> bool | None:
    feature = props.get("my_feature") or {}
    self.my_client.configure(feature)
    return True  # only when the models service must be rebuilt

Using App Props to Enable or Disable Inbound Surfaces

Inbound surface switches use the canonical enabled.* section under the app's config: block in bundles.yaml. Resource-level enabled_config decorator arguments are not part of the current contract.

bundles:
  items:
    - id: "my-bundle@1-0"
      config:
        enabled:
          bundle: true
          api:
            operations.report.POST: true
          widget:
            admin: false
          mcp:
            automation: true
          cron:
            news-sync: false
        jobs:
          news_sync:
            cron: "0 8 * * *"
            timezone: "Europe/Berlin"

App code declares stable aliases. The platform reads the canonical enabled.* section from effective app props:

@bundle_entrypoint(name="my-bundle", version="1.0.0")
@api(alias="report", method="POST")
@ui_widget(alias="admin", icon="settings")
@mcp(alias="automation")
@cron(alias="news-sync", expr_config="jobs.news_sync.cron", tz_config="jobs.news_sync.timezone")

Resolution uses effective deployment-scoped app props, not hard-coded defaults. The configured descriptor provider is authority: file mode writes the mounted bundles.yaml; provider-backed mode writes grouped descriptor documents. Bundle Admin and CLI changes go through that write path, refresh Redis/runtime projections, apply immediately to HTTP surfaces, and reach cron on the next scheduler reconcile. Direct Redis edits are never configuration writes.

Missing enabled.* values mean enabled. Use an explicit falsy value such as false, 0, off, disable, or disabled to turn a surface off. An app-level enabled.bundle: false suppresses all of its APIs, widgets, MCP endpoints, and cron jobs.

Per-Operation CSRF

CSRF is opt-in policy for selected POST APIs on the operations route, not a rule inferred from every POST:

@api(method="POST", alias="account_disconnect", route="operations", csrf=True)
async def account_disconnect(self, account_id: str, **kwargs):
    ...

The browser requests GET .../operations/account_disconnect/csrf and sends the returned value as X-KDCube-CSRF-Token on POST. Tokens are subject-, tenant-, project-, app-, operation-, and method-bound, expire after ten minutes, and are atomically consumed once in Redis. The protected operation returns 503 if one-use enforcement is unavailable.

Descriptor-Driven Local App Development

The fastest local development path is descriptor-driven. Keep assembly.yaml, gateway.yaml, economics.yaml, secrets.yaml, bundles.yaml, and bundles.secrets.yaml in one descriptor folder. For the fully non-interactive install path, use secrets.provider: secrets-file.

kdcube init is first-time setup only; it refuses if the target workdir is already initialized. To rebuild images / restart later, use kdcube refresh --tenant T --project P --build. Bare refresh --build rebuilds the platform source already staged or recorded in the runtime; add --path /path/to/kdcube-ai-app when the current local checkout should be copied into the runtime first. Add exactly one of --latest, --upstream, or --release <ref> when the existing runtime should move to another platform source while preserving staged descriptors. init reads assembly.yaml -> context.tenant/project to validate the runtime scope and uses assembly.yaml -> platform.repo/ref unless a source selector is passed.

init once:
  descriptors + platform source
      -> kdcube init --descriptors-location ... --build
      -> workdir/config/*.yaml + workdir/repo + compose/env files
      -> kdcube start

refresh platform runtime:
  existing workdir/config/*.yaml is preserved
      -> kdcube refresh --build
      -> kdcube refresh --path /path/to/kdcube-ai-app --build
      -> kdcube refresh --release <ref> --build

app-only config/source loop:
  bundles.yaml + bundles.secrets.yaml
      -> kdcube bundle config apply --descriptors-location ... --dry-run
      -> kdcube bundle config apply --descriptors-location ... --reload
      -> kdcube bundle reload <bundle_id>
export TENANT="demo-tenant"
export PROJECT="demo-project"
export DESCRIPTORS="/path/to/descriptors"

# prepare runtime from assembly.platform.ref (composes path under default base ~/.kdcube/kdcube-runtime)
kdcube init  --tenant "$TENANT" --project "$PROJECT" --descriptors-location "$DESCRIPTORS"

# start the prepared runtime
kdcube start --tenant "$TENANT" --project "$PROJECT"

Choose at most one source selector during init:

  • --upstream for the latest upstream repo state instead of assembly.platform.ref
  • --latest for the latest released platform ref
  • --release <ref> for a specific released ref
  • otherwise assembly.yaml -> platform.ref

Add --build to init when the prepared runtime should build local images during initialization. After the runtime exists, use kdcube refresh --tenant T --project P --build to rebuild images and restart without touching staged descriptors. Use --path only when refresh should copy a local platform checkout into the runtime first. The same source selectors are valid on refresh for already-initialized runtimes: --latest, --upstream, or --release <ref>.

# latest upstream source, with local images built during init
kdcube init --tenant "$TENANT" --project "$PROJECT" \
  --descriptors-location "$DESCRIPTORS" \
  --upstream \
  --build

kdcube start --tenant "$TENANT" --project "$PROJECT"

With that setup:

  • assembly.yaml declares paths.host_bundles_path for local path apps on the host
  • assembly.yaml can also declare paths.host_managed_bundles_path for platform-managed app checkouts
  • the runtime mounts those host folders into proc as /bundles and /managed-bundles
  • bundles.yaml points local app entries at /bundles/<app-dir>
  • git-backed app entries are cloned under the managed app root when a repo/ref source is configured

Then the edit/test loop is:

# edit the local app under paths.host_bundles_path
kdcube bundle reload my-bundle@1-0 --tenant "$TENANT" --project "$PROJECT"

This reload command replays the active descriptor and clears the app cache so the next request picks up the updated code and descriptor-backed config.

If you edited the seed descriptor files instead of the staged runtime copy, reapply only app descriptors with kdcube bundle config apply. This does not rebuild images or restart Docker.

kdcube bundle config apply \
  --tenant "$TENANT" \
  --project "$PROJECT" \
  --descriptors-location "$DESCRIPTORS" \
  --dry-run

kdcube bundle config apply \
  --tenant "$TENANT" \
  --project "$PROJECT" \
  --descriptors-location "$DESCRIPTORS" \
  --reload

Canonical descriptor references:

Before replacing live app descriptors with older seed files, export the current live app descriptor state into portable files:

kdcube export \
  --tenant demo-tenant \
  --project demo-project \
  --out-dir /tmp/kdcube-export

This writes fresh bundles.yaml and bundles.secrets.yaml snapshots from the active app authority. In local descriptor-backed mode, runtime /bundles/... paths are normalized back to host paths for non-git apps; git-backed entries keep repo/ref/subdir and do not keep incidental materialized runtime paths.

Configuration Resolution Order

PrioritySourceHow
1 (highest)Live deploy-scoped app descriptor statesecrets-file: descriptor files; aws-sm: grouped AWS app documents; Redis is cache only
2 (lowest)entrypoint.configurationApp code defaults

User-scoped app props are separate from that precedence chain. They are not part of bundles.yaml; they are stored per user and per app in Postgres and read asynchronously with await get_user_prop(...). User-scoped secrets use the configured secrets provider and are not app descriptor state.

Reserved Property Paths

PathPurpose
role_modelsMaps logical agent roles → concrete LLM (provider + model)
embeddingEmbedding provider/model for RAG and vector search
services.llm.customCustom/local model gateway endpoint and serving-context defaults; model identity is selected separately
memory, ui.widgets.memoriesUser-memory runtime and optional memories widget configuration
economics.reservation.<surface>Per-app pre-run cost reservation override; legacy scalar remains accepted
execution.runtimePer-app code exec mode and isolated-runtime limits such as max generated file size and max workspace output size. descriptor_payload_scope: active_bundle narrows app descriptor payloads for Docker/Fargate supervisors.
knowledgeKnowledge space repo/paths configuration
surfaces.as_providerSurfaces the app exposes, including the default_chat intent declaration
surfaces.as_consumerServices, tools, skills, MCP servers, and named-service operations the app and its agents may call
react.<agent_id>Per-agent instructions, supported models, role bindings, and iteration configuration
ui.main_viewMain-view build/serve configuration and optional application-hosted website declaration
subsystemsApp-declared UI/helper subsystems and dashboard resources

frontend.config is not an app property path. It belongs in assembly.yaml and is published to the browser through /api/cp-frontend-config.

ui.main_view is the current reserved UI config surface. It describes how a standalone main view should be built or served and can opt that file tree into application-site routing. Decorator-discovered APIs, widgets, MCP endpoints, providers, and handlers remain code declarations; config may enable or restrict them but must not advertise a surface the app does not serve.

config:
  ui:
    main_view:
      src_folder: "ui-src"
      build_command: "npm install && OUTDIR=<VI_BUILD_DEST_ABSOLUTE_PATH> npm run build"

release.yaml is release metadata, not runtime configuration. App-local config templates may contain safe defaults; secret templates contain placeholders only. See full config docs: bundle-runtime-configuration-and-secrets-README.md.