KDCube
← Recipes
KDCube Recipes · The Job Card

App Ingredients: Build the Boundary Before the Features

One small, production-shaped KDCube app — surface, contracts, identity, state, tests, release. Twelve stamped operations, from app card to shipped.

16 July 2026RecipesHands-onThe Job Card
app authoringasync runtimeidentitystateapp ingredientsas_provideras_consumerevent delivery

WHAT LEAVES THE SHOP

One small, production-shaped KDCube app with a useful surface, explicit provider and consumer contracts, non-blocking execution, correct identity and state boundaries, focused tests, and a releasable package.

You do not need to start with chat, an agent, a UI, MCP, storage, and jobs at once. Start with the smallest capability the product owns. Add another ingredient only when the same app should own its behavior, authority, state, and release lifecycle.

Current code and descriptors still say bundle in names such as bundle_id, bundles.yaml, and @bundle_entrypoint. In this recipe, app = bundle: one deployable KDCube runtime unit.

YOU WILL NEED
  • A running KDCube deployment (local is fine)
  • One capability the product truly owns
  • The app descriptor: bundles.yaml + its templates
  • The reference doc, open in a tab (linked below)

The ingredient map

the.ingredient.mapMAP
APP BOUNDARY
|
+-- provides        API · MCP · widget · main view · agent · cron
+-- consumes        event lane · jobs · Data Bus · tools · MCP · named services
+-- executes        async in shared proc · explicit fence only when needed
+-- identifies      actor · storage user · platform authority · economics subject
+-- stores          props/secrets · Redis · Postgres · artifacts · shared files
+-- communicates    conversation events · Data Bus · comm · recording · telemetry
+-- governs         roles/grants · delegation · firewall · economics
`-- maintains       interface · config · docs · journal · tests · release notes

The detailed reference behind this recipe is What I Should Know Before Writing a KDCube App.

THE JOB CARD · APP INGREDIENTSprovidesAPI · MCP · widget · main viewagent door · cron scheduleOP 10consumesevent lane · jobs · Data Bustools · MCP · named servicesOP 40executesasync inside the shared procexplicit fence only when requiredOP 30identifiesactor · storage userplatform + economics subjectsOP 50storesprops · secrets · Redis · Postgresartifacts · shared filesOP 60communicatesconversation events · Data Buscomm · recording · telemetryOP 70governsroles · grants · delegationfirewall · economicsOP 100maintainsinterface · config · docs · journaltests · release notesOP 120your KDCube appone deployable runtime boundaryAPP = BUNDLE IN CURRENT IDENTIFIERSADD ONLY WHAT THIS APP OWNS · ONE INGREDIENT AT A TIME
The app boundary and its eight responsibilities — add ingredients one at a time.

OP 10OF 120 Write the app card

Before code, write two lists.

app.cardLISTS
THIS APP PROVIDES
  one authenticated status API
  one main agent called my-app.main

THIS APP CONSUMES
  web_search and web_fetch
  the mem named-service namespace (read/search only)

THIS APP OWNS
  deployment config: feature.enabled, request_timeout_s
  no app database tables yet
  no public callback yet

This prevents a common design failure: implementing a capability without declaring who owns it or who may consume it.

Provider-side examples are @api, @mcp, @ui_widget, @ui_main, one reactive agent door, and @cron. Consumer-side examples are @on_reactive_event, @on_job, Data Bus handlers, tools, MCP servers, and named-service namespaces.

An agent crosses both directions. The app provides the agent to callers; that agent consumes its allowed capabilities. Several agents share one reactive door and are dispatched by stable agent_id.

OP 20OF 120 Create a maintainable package

Keep the root small. Put implementation in named modules.

my-app@1-0/LAYOUT
my-app@1-0/
  entrypoint.py                 thin composition root
  README.md                     app purpose, surfaces, verification
  AGENTS.md                     implementation map and invariants
  release.yaml                  release metadata

  agents/                       agent construction, prompts
  services/                     domain logic
  surfaces/                     API/MCP/UI adapters when useful
  events/                       event normalization and dispatch
  tools/                        app-local tools
  ui/                           widgets/main view
  skills/                       app-local skills

  config/
    bundles.template.yaml
    bundles.secrets.template.yaml

  interface/
    README.md
    my-app.openapi.yaml         when the app has HTTP

  docs/
    storage/README.md
    journal/

  tests/

Only create folders this app needs. entrypoint.py declares and composes; it does not become the whole application.

the.invariantCONTRACT
decorators
  == interface declaration
  == descriptor keys and gates
  == README surface inventory
  == focused tests
  == journal entry
  == release note when behavior changes
CHECK · THE INSPECTOR’S STAMP

Keep this invariant in every functional change: decorators, interface declaration, descriptor keys, README inventory, tests, journal, and release notes agree.

The journal is the quick onboarding history for the next developer or coding agent. Record architecture, interface, state, and behavior decisions, not every mechanical edit.

OP 30OF 120 Make the first surface async

This is a complete app surface:

entrypoint.pyPYTHON
from typing import Any

from kdcube_ai_app.apps.chat.sdk.solutions.chatbot.entrypoint import BaseEntrypoint
from kdcube_ai_app.infra.plugin.bundle_loader import api, bundle_entrypoint, bundle_id


BUNDLE_ID = "my-app@1-0"


@bundle_entrypoint(name="my-app", version="1.0.0", priority=10)
@bundle_id(id=BUNDLE_ID)
class MyApp(BaseEntrypoint):
    @api(
        method="GET",
        alias="status",
        route="operations",
        user_types=("registered",),
    )
    async def status(self, **_: Any) -> dict[str, Any]:
        return {{"ok": True, "app": BUNDLE_ID}}

Every platform callback and every I/O call behind it is async. KDCube apps run inside the shared concurrent proc event loop. A synchronous HTTP, filesystem, database, Redis, subprocess, sleep, or lock operation blocks unrelated users and apps even when it is hidden inside async def.

Use async clients. For a bounded dependency with no async API, use await asyncio.to_thread(...). Put long CPU work, generated code, or operational work behind an explicit job, venv, or fenced execution boundary.

OP 40OF 120 Declare policy and consumer inventory

Decorators declare that a surface exists. Descriptor policy controls exposure, and consumer configuration limits what the app or agent may call.

bundles.yamlYAML
items:
  - id: my-app@1-0
    config:
      feature:
        enabled: true

      surfaces:
        as_provider:
          bundle:
            visibility:
              allowed_roles:
                - kdcube:role:registered

        as_consumer:
          default_agent: main
          agents:
            main:
              tools:
                - id: web
                  kind: python
                  module: kdcube_ai_app.apps.chat.sdk.tools.web_tools
                  alias: web_tools
                  allowed: [web_search, web_fetch]

Use user_types for ordered platform levels. Use raw/custom roles such as kdcube:role:finance-team for role membership. Use authority and grant checks for delegated or managed boundaries. These are separate dimensions; do not turn an authority grant into a guessed role.

OP 50OF 120 Bind identity; never accept it from the model

The runtime binds tenant, project, actor, user, session, app, conversation, turn, and authority before app code runs. Request/model fields may locate an object; they cannot choose the effective identity.

For external channels or automation, verify the incoming actor and resolve an explicit delegation edge when the protected boundary requires another authority. Keep these subjects distinct when needed:

identity.subjectsIDENTITY
actor             who called
storage subject   whose user-scoped data is addressed
platform subject  whose roles/grants are checked
economics subject whose quota/funding pays

Cross-runtime context preserves provenance. It does not grant every downstream operation. The trusted tool, MCP, named-service, or provider boundary checks the authority and grants it requires again.

OP 60OF 120 Put state in its real home

StateCorrect home
App non-secret configbundles.yaml; read with self.bundle_prop(...)
App secretbundles.secrets.yaml / secret provider; await get_secret("b:...")
User non-secret app stateawait get_user_prop(...), await get_user_props(), await set_user_prop(...), await delete_user_prop(...)
User secret/connected tokenuser-secret and connections helpers
Small distributed cache/dedupe/leaseruntime Redis/KV, correctly namespaced
Relational domain stateapp-prefixed tables in the shared tenant/project Postgres schema
Durable backend-neutral artifactsBundleArtifactStorage
Shared filesystem tree/index/checkoutbelow self.bundle_storage_root()
Conversation transcript/filesKDCube conversation/file contracts

Do not use environment variables or module globals as app configuration or state. Use bundle_call_context only for small JSON-safe invocation metadata, not secrets, clients, files, or durable data.

Treat singleton: true as worker-local reuse, not persistence or serialization. Singleton invocations may overlap and another worker can serve the next request.

Provision Postgres tables idempotently under a tenant/project/app-scoped advisory critical section. KDCube does not yet provide a complete app-delete deprovision hook, so document table ownership, retention, and operator cleanup in docs/storage/README.md.

OP 70OF 120 Choose one delivery contract

NeedContract
Direct request/responseAPI/operation
Ordered context for an agentconversation event bus
Durable app-owned mutation/messageData Bus
Ready work after durable state existsbackground job stream
Peer/session/project progresscommunicator event
Observation after work happenedrecording/telemetry

The conversation queue carries a wake; accepted events live in the ordered conversation/agent lane. ReAct can fold eligible events into a live turn. A run-to-completion agent receives a fixed start batch and later events become a later turn.

Data Bus writes are retryable and require idempotency/revision handling. Background jobs are also retryable; the durable domain record is the source of truth, not the queued envelope.

For a webhook that starts a conversation, verify proof, submit through ChatIngressSubmitter, and reply early. Do not hold the webhook open until the agent completes.

OP 80OF 120 Reuse the runtime before adding code

  • Use built-in web, exec, rendering, file-hosting, and context tools directly. Their native paths already carry isolation, accounting, artifacts, citations, and provenance.
  • Connect MCP once and allow only selected tools per agent. The trusted runtime resolves credentials; the restricted executor does not receive them.
  • Expose ordinary MCP without creating a named service. Add a named service only when the domain also needs provider-owned refs, schemas, search, actions, materialization, and generic UI/agent behavior.
  • Use the namespace provider's contract instead of teaching chat, canvas, or a scene to guess foreign ref semantics.
  • Use @venv only for app-specific dependencies absent from proc requirements.

OP 90OF 120 Wrap agents in the KDCube conversation

Keep two stores correct:

two.storesSTATE
agent working memory/checkpointer
  what the framework restores for the next model turn

KDCube conversation record
  ordered turns · chat list/reload/search · files/artifacts · cost/time

Key the agent thread by KDCube conversation_id, rebuild per-turn graphs from durable state, map progress to the communicator, and use the framework-neutral conversation recorder. Do not keep a mutable graph as singleton state.

For Claude Code, use the SDK integration instead of local subprocess glue. It binds user/conversation/agent continuity, streams through the communicator, accounts usage, and can preserve the native session/transcript in Git across workers and restarts.

OP 100OF 120 Guard spend and outbound data

For every paid API, tool, MCP, search, agent, cron, or job surface:

  1. resolve the economics subject from the bound authority context;
  2. choose a stable accountable scope_id;
  3. enter the economics guard with an estimate;
  4. run tracked services inside it;
  5. settle actual usage and handle denial/degradation.

A paid call is not accountable merely because it runs in KDCube. It must emit tracked usage under the right scope.

Use the outbound event firewall to decide which communicator events may leave the app. Recording captures approved post-firewall events; keep it bounded and use an async sink. Telemetry records facts that happened. It does not replace accounting, Data Bus, jobs, or the conversation event lane.

OP 110OF 120 Verify the app as deployed

Test more than the Python method:

  • interface discovery and descriptor aliases match;
  • API/widget role, user-type, authority, and grant denial paths work;
  • no sync I/O runs in platform callbacks;
  • duplicate Data Bus/job delivery is harmless;
  • shared initialization survives two concurrent workers;
  • config reload changes effective props without restart-only state;
  • restart/scale-out preserves durable app and agent state;
  • webhook returns early and the submitted turn is processed;
  • fenced execution cannot access undeclared credentials or storage;
  • paid work is admitted, attributed, recorded, and settled;
  • outbound events are filtered and recorded as intended.

OP 120OF 120 Maintain and release the whole contract

Use the canonical sequence:

  1. Assemble With SDK Building Blocks
  2. Write a KDCube App
  3. Configure and Run the App
  4. Avoid Common Integration Failures
  5. Release App Content

Before release, update code, interface declarations, descriptor templates, README, storage docs, journal, tests, and release notes together. A version tag on drifting contracts is not a release.

FINAL INSPECTION · DONE MEANS
  • The app provides one useful, declared surface.
  • Every runtime path is async and non-blocking.
  • Provider policy and consumer inventory are explicit.
  • Identity and delegation are host-bound and fail closed.
  • Configuration, secrets, invocation context, and durable state use their correct homes.
  • Singleton assumptions and shared mutations are concurrency-safe.
  • The chosen bus/job/event contract matches the work semantics.
  • Paid work and outbound data are governed.
  • Conversation and framework memory survive restart when the app has an agent.
  • Interfaces, config, docs, journal, tests, and release notes agree.

Build one boundary cleanly. Then add the next ingredient.

Read more

From the blog

KDCube Recipe
№ 2026-07-16 · kdcube.tech