Configuration

KDCube configuration is descriptor-driven. A deployment is described by a small set of YAML files: assembly.yaml for platform/runtime settings, gateway.yaml for gateway controls, secrets.yaml for platform secrets, bundles.yaml for bundle inventory and non-secret bundle props, and bundles.secrets.yaml for bundle-scoped secrets.

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 Resolution order of platform configuration from assembly to bundle to gateway layers assembly.yaml Platform version frontend config exec defaults gateway.yaml Rate limits Capacity Throttling bundles.yaml Bundle defs git refs per-bundle config Secrets backend secrets.yaml secrets-file · aws-sm selected in assembly.yaml Admin UI Bundle props per tenant/project cached in Redis Effective config: descriptors → staged runtime → live bundle state → code defaults

Environment boundary: one tenant/project is one isolated deployment environment. Use a different tenant/project when you need separate customer data or separate lifecycle stages such as dev, staging, and prod. Keep multiple bundles inside one tenant/project when they belong to the same environment.

Configuration boundary: assembly.yaml, gateway.yaml, and platform/global secrets define the environment. bundles.yaml and bundles.secrets.yaml define the bundles and bundle-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-ai-app.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"
        max_file_bytes: "100m"
        max_workspace_bytes: "250m"
        workspace_monitor_interval_s: 0.5
secrets:
  provider: "secrets-file"   # secrets-service | secrets-file | aws-sm
auth:
  type: "cognito"      # simple | cognito | delegated
  turnstile_development_token: "" # local/dev Turnstile test token only
frontend:
  config:
    auth:
      authType: "cognito" # 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: simple, cognito, or delegated.
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 browser-only deployment differences such as auth mode, route prefix, auth proxy path, and debug flags. Do not put secrets here. The legacy browser value hardcoded is now treated as simple; new descriptors should use simple. oauth is not a deployment auth mode; use cognito for the OSS Cognito/OIDC browser flow.

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

bundles.yaml

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_workspace_bytes: "250m"
            workspace_monitor_interval_s: 0.5
            descriptor_payload_scope: "active_bundle" # optional bundle 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.
economics.reservation_amount_dollarsCost reserved before execution begins. Default 2.0 USD. Committed on completion, released on failure.
execution.runtimePer-bundle code exec routing and isolated-runtime limits. Supports docker or fargate plus file/workspace size limits for that bundle's exec runs. descriptor_payload_scope: active_bundle filters only bundles.yaml and bundles.secrets.yaml to the caller bundle for isolated supervisor transport.
mcp.servicesMCP connector config per bundle. Matched by server_id in MCP_TOOL_SPECS.

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 bundles.

bundles.yaml — Your Bundle Definition

bundles:
  version: "1"
  default_bundle_id: "my-bundle@1-0"
  items:
    - id: "my-bundle@1-0"
      name: "My Bundle"
      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:
        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_amount_dollars: 2.0
        execution:
          runtime:
            mode: "docker"                 # docker is default; fargate is also supported
            max_file_bytes: "100m"          # max single generated file
            max_workspace_bytes: "250m"     # max generated workspace output
            workspace_monitor_interval_s: 0.5
            descriptor_payload_scope: "active_bundle" # optional bundle descriptor narrowing

For local prototyping, a bundle 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"

Bundle 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 bundle-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.

Bundle-Local Config Shape Files

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

Keep those bundle-local files non-sensitive. Use realistic non-secret defaults and placeholders for secret values. If a bundle 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

bundle_api_key = get_secret("b:my_service.api_key")               # current bundle
platform_api_key = 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
prefs = get_user_prop("preferences.theme", "light")         # per user + current bundle
set_user_prop("preferences.theme", "dark")                 # persisted in Postgres

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 bundle". The runtime resolves that bundle id from the bound request or bundle context. Outside a bound bundle 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
get_secret("b:my_service.api_key")Current bundle secret document
get_user_prop("preferences.theme")Current user + current bundle Postgres state

This is the descriptor-backed complement to get_secret(...): use get_secret(...) for provider-managed secrets, use get_plain(...) for mounted descriptor values, and use get_user_prop(...) for per-user non-secret bundle state.

Using Bundle Props to Enable or Disable Inbound Surfaces

enabled_config is not a separate descriptor. It is a dot path into deployment-scoped bundle props under the bundle's config: block in bundles.yaml.

bundles:
  items:
    - id: "my-bundle@1-0"
      config:
        features:
          bundle_enabled: true
          operations:
            report: true
          widgets:
            admin: false
          mcp:
            automation: true
          jobs:
            news_sync: false

Bundle code then points to those props:

@agentic_workflow(enabled_config="features.bundle_enabled")
@api(alias="report", enabled_config="features.operations.report")
@ui_widget(alias="admin", enabled_config="features.widgets.admin")
@mcp(alias="automation", enabled_config="features.mcp.automation")
@cron(alias="news-sync", enabled_config="features.jobs.news_sync")

Resolution uses the effective deployment-scoped bundle props, not hard-coded defaults. In descriptor-backed local mode that authority is the mounted writable bundles.yaml. In live admin flows, the updated props become the new runtime authority and proc applies them immediately to HTTP surfaces and on the next scheduler reconcile.

Missing path or absent enabled_config means enabled. Use an explicit falsy value such as false, 0, off, disable, or disabled to turn a surface off. A bundle-level disable suppresses all of its APIs, widgets, MCP endpoints, and cron jobs.

Descriptor-Driven Local Bundle Development

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

kdcube init reads assembly.yaml -> context.tenant/project to choose the runtime scope. If those values are absent, it uses default/default. It then uses assembly.yaml -> platform.repo/ref unless a source selector is passed.

export WORKDIR="$HOME/.kdcube/kdcube-runtime"
export DESCRIPTORS="/path/to/descriptors"
export DEPLOYMENT_WORKDIR="$WORKDIR/demo-tenant__demo-project"

# prepare runtime from assembly.platform.ref
kdcube init \
  --workdir "$WORKDIR" \
  --descriptors-location "$DESCRIPTORS"

# start the prepared runtime
kdcube start \
  --workdir "$WORKDIR"

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. A normal start should start the already prepared runtime; use start --build only when you intentionally want startup to rebuild images too.

# latest upstream source, with local images built during init
kdcube init \
  --workdir "$WORKDIR" \
  --descriptors-location "$DESCRIPTORS" \
  --upstream \
  --build

kdcube start \
  --workdir "$WORKDIR"

With that setup:

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

Then the edit/test loop is:

# edit the local bundle under paths.host_bundles_path
kdcube reload my-bundle@1-0 \
  --workdir "$DEPLOYMENT_WORKDIR"

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

Canonical descriptor references:

When secrets.provider: aws-sm is active, you can export the current effective live bundle descriptor state into portable files with:

kdcube --export-live-bundles \
  --tenant demo-tenant \
  --project demo-project \
  --aws-region eu-west-1 \
  --out-dir /tmp/kdcube-export

This reads the live grouped AWS documents for bundle descriptors and bundle secrets and writes fresh bundles.yaml and bundles.secrets.yaml snapshots.

Configuration Resolution Order

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

User-scoped bundle props are separate from that precedence chain. They are not part of bundles.yaml; they are stored per user and per bundle in Postgres and should be read with get_user_prop(...).

Reserved Property Paths

PathPurpose
role_modelsMaps logical agent roles → concrete LLM (provider + model)
embeddingEmbedding provider/model for RAG and vector search
economics.reservation_amount_dollarsPre-run cost reservation floor (default 2.0)
execution.runtimePer-bundle code exec mode and isolated-runtime limits such as max generated file size and max workspace output size. descriptor_payload_scope: active_bundle narrows bundle descriptor payloads for Docker/Fargate supervisors.
knowledgeKnowledge space repo/paths configuration
ui.main_viewReserved UI build/serve block for bundle main-view override assets
subsystemsBundle-declared UI/helper subsystems and dashboard resources

frontend.config is not a bundle 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 is used by bundles such as versatile to describe how a standalone main-view frontend should be built or served. Decorator-discovered surfaces such as widgets, APIs, ui_main, and on_message are scanned from the bundle class rather than stored in props.

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

See full config docs: bundle-runtime-configuration-and-secrets-README.md