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 app inventory and non-secret app props, and bundles.secrets.yaml for app-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
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 apps 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 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-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_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" # 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 field | Published field | Purpose |
|---|---|---|
frontend.config.auth.authType | auth.authType | Browser auth mode: simple, cognito, or delegated. |
frontend.config.auth.apiBase | auth.apiBase | Auth proxy base path for delegated/custom auth flows. |
auth.turnstile_development_token | auth.turnstileDevelopmentToken | Optional local/test Turnstile bypass token. Not a production site key. |
frontend.config.routesPrefix | routesPrefix | Browser route/API prefix used by the frontend. |
context.tenant, context.project | tenant, project | Deployment scope used by browser API calls. |
frontend.config.debug | debug | Public 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 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
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
| Key | Description |
|---|---|
role_models | Override LLM per role. Maps logical agent roles to concrete provider + model combinations. |
embedding | Embedding provider + model for RAG and vector search in knowledge spaces. |
economics.reservation_amount_dollars | Cost reserved before execution begins. Default 2.0 USD. Committed on completion, released on failure. |
execution.runtime | Per-app code exec routing and isolated-runtime limits. Supports docker or fargate plus file/workspace size limits for that app's exec runs. descriptor_payload_scope: active_bundle filters only bundles.yaml and bundles.secrets.yaml to the caller app for isolated supervisor transport. |
mcp.services | MCP connector config per app. 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 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_amount_dollars: 2.0
execution:
runtime:
mode: "docker" # docker is default; fargate is also supported
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
Named-Service Tool Policy
An app can consume object systems through one named-service tool family instead of many custom tools. The client config declares the allowed namespace operations, and tool traits describe how risky each operation is. Traits can be overridden per namespace when a namespace has different semantics.
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"] }
Search scopes and filter schemas come from provider discovery/about metadata. Namespace styles such as mem: green and task: blue should be shared by chat, canvas, and scene surfaces instead of duplicated in a single widget.
For local prototyping, 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 = get_secret("b:my_service.api_key") # current app
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 app
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 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:.
| Call | Reads 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 app secret document |
get_user_prop("preferences.theme") | Current user + current app 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 app state.
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 the effective deployment-scoped app 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 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.
Descriptor-Driven Local App 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 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:
--upstreamfor the latest upstream repo state instead ofassembly.platform.ref--latestfor 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.yamldeclarespaths.host_bundles_pathfor local path apps on the hostassembly.yamlcan also declarepaths.host_managed_bundles_pathfor platform-managed app checkouts- the runtime mounts those host folders into proc as
/bundlesand/managed-bundles bundles.yamlpoints 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
| Priority | Source | How |
|---|---|---|
| 1 (highest) | Live deploy-scoped app descriptor state | secrets-file: descriptor files; aws-sm: grouped AWS app documents; Redis is cache only |
| 2 (lowest) | entrypoint.configuration | App 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 should be read with get_user_prop(...).
Reserved Property Paths
| Path | Purpose |
|---|---|
role_models | Maps logical agent roles → concrete LLM (provider + model) |
embedding | Embedding provider/model for RAG and vector search |
economics.reservation_amount_dollars | Pre-run cost reservation floor (default 2.0) |
execution.runtime | Per-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. |
knowledge | Knowledge space repo/paths configuration |
ui.main_view | Reserved UI build/serve block for app main-view override assets |
subsystems | App-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 is used by apps 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 app 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