KDCube
← Engineering
KDCube Engineering · The Map

Build an AI App with KDCube

What you actually write for a KDCube app: four starting points, the runtime services around each, and the point where the work stays yours.

15 July 2026Engineering16 minExperienceWorkshop Manual
app authoringsurfacesdescriptorchat turnreactive eventswidgets

Keep the agent, backend, UI, tools, and data model that already make your product useful. A KDCube app is the boundary that connects those pieces to a production runtime. Start with one operation or one agent turn. Add chat, UI, storage, integrations, jobs, isolated execution, or cost controls only when the product needs them.

You do not have to build every KDCube surface, and you do not have to move an entire product into one large package. One authenticated API is a valid app. So is one existing LangGraph agent behind ordered conversation delivery. A larger product may use several apps, each with its own code, configuration, interfaces, storage ownership, and release lifecycle.

This article answers the practical first question: what do I actually have to write? It maps four starting points, the runtime services available around them, and the point where product-specific work remains yours.

One terminology note: current source, descriptors, and CLI commands still use bundle in identifiers such as bundle_id, bundles.yaml, @bundle_entrypoint, and kdcube bundle reload. In product prose, that deployable unit is an app.

FOUR EQUAL DOORS · ONE BOUNDARY one API or webhooka thin @api adapteran existing agentone turn adapterthe ready assistantinstructions + inventorya multi-surface productthe surfaces you own your app — the thin boundarydeclares only the surfaces it genuinely owns the production runtimeidentity · ordered delivery · storage · files · economics · reload STOP AT ANY DOOR · ADD A SURFACE ONLY WHEN IT SOLVES A PRODUCT NEED
Fig. 1 — four equal doors into one boundary; stop at any of them.

01 Four valid starting points

You already have or wantWhat you addWhat KDCube can provide around it
An existing backend function or serviceA thin @api(...) adapter and its declared contractAuthentication context, routing, configuration, secrets, reload, health, and optional managed credentials
A LangGraph, CrewAI, Claude Agent SDK, or custom loopOne turn adapter, stream mapping, and identity/state mappingOrdered conversation delivery, multi-user serving, reusable chat, files, conversation records, and optional economics or isolated execution
A configurable assistantAgent instructions plus the allowed tools, skills, models, and servicesKDCube ReAct, ready chat, streaming, web/search and execution building blocks, per-conversation user choices, files, and event handling
A broader AI-native productThe UI and domain surfaces the product actually ownsWidgets, main views, websites, scenes, APIs, named services, MCP, Data Bus, jobs, integrations, storage helpers, and independent app lifecycle

No path is second class. The smallest useful integration is often the best first one.

02 What an app is, and is not

THE BOUNDARY

A KDCube app is a deployable runtime boundary. Use another app when ownership, scaling, authority, or release cadence should be independent.

The platform discovers the app, loads its entrypoint, applies its descriptor configuration, exposes its declared surfaces, and runs those surfaces under the current request context.

The KDCube-facing shell is a Python package because decorators and runtime contracts are implemented in Python. Existing Node or TypeScript backends do not have to be rewritten: they can remain an internal service or use the documented app-sidecar bridge while the Python shell owns KDCube routing, identity, configuration, and surface declarations.

An app is not necessarily:

  • a chat app;
  • an agent;
  • a UI;
  • a database owner;
  • a monolith containing every capability.

Apps can cooperate without becoming one codebase.

03 The two possible directions

An app may expose capabilities, consume capabilities, or do both. Provider-side decorators and registrations define the surfaces it implements; surfaces.as_provider carries provider-facing intent and policy. surfaces.as_consumer declares what the app and its agents may call.

provider side
  decorators and registered providers expose
  API · MCP · widget · main view/site · agent identities · cron schedules

surfaces.as_provider
  declares provider-facing intent and policy

surfaces.as_consumer
  what enters this app and what each agent may use
  reactive-event lane · ready jobs · Data Bus messages
  Python tools · skills · MCP servers · named-service namespaces
THE CONTRACT

The two directions are explicit; neither implies the other. An app that exposes a mail named service may consume a connected Gmail account; an assistant may expose chat while consuming that mail service.

For chat, intent is declared rather than guessed:

bundles.yamlYAML
config:  surfaces:    as_provider:      bundle:        default_chat: true

default_chat: true tells the platform to serve the SDK chat component under the reserved chat widget alias. It is effective only when the entrypoint has a reactive turn surface. Leave it absent for a backend-only, widget-only, site, or service app.

An agent crosses both directions. The app provides one reactive agent door to callers, while that agent consumes its configured tools, MCP servers, skills, named services, and event sources. Several agents are dispatched behind that one door by stable agent_id; they are not separate surfaces.as_provider.agents entries.

AS PROVIDER · AS CONSUMER — TWO EXPLICIT DIRECTIONS AS PROVIDER AS CONSUMER chatUI / siteAPIMCPnamed servicecron schedulemodelsPython toolsMCP serversnamed-service realmsconnected providers your appsurfaces.as_provider: intent + policysurfaces.as_consumer: what it may call another appconsumes your named service NEITHER DIRECTION IMPLIES THE OTHER
Fig. 2 — the two directions are explicit; neither implies the other.

04 The smallest working app is small

This is a complete runtime surface: one authenticated operation in one app.

entrypoint.pyPYTHON
from typing import Any, Dictfrom kdcube_ai_app.apps.chat.sdk.solutions.chatbot.entrypoint import BaseEntrypointfrom 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, **kwargs: Any) -> Dict[str, Any]:        del kwargs        return {"ok": True, "app": BUNDLE_ID}

Every platform-invoked method and every I/O chain behind it must be async end to end. Apps run inside the shared concurrent proc event loop. A synchronous HTTP, filesystem, database, Redis, subprocess, sleep, or lock call inside async def still blocks other users and apps. Use async clients; isolate a bounded legacy call with await asyncio.to_thread(...); move long CPU or operational work to an explicit job or fenced execution boundary.

BaseEntrypoint supplies the runtime glue: effective app properties, request context, communicator binding, storage helpers, model-role defaults, and UI build hooks. It does not invent domain behavior and it does not turn this app into chat.

The maintained package also carries configuration templates, an interface declaration, storage documentation, tests, release metadata, and a journal. Those files are not extra runtime machinery. They make the app self-describing and keep code, configuration, docs, tests, and deployed interfaces from drifting.

05 Bring an existing agent

Do not translate a working graph into a KDCube-specific graph. Keep the agent in its framework and wrap the seam where it handles one turn.

For a run-to-completion agent, the adapter normally does four things:

pending snapshotread every still-pending lane event in sequence order
bound identitymap the bound KDCube identity to the agent’s user and thread keys
stream outmap the agent’s existing stream onto KDCube progress and answer events
final answerreturn the answer and any declared files
entrypoint.pyPYTHON
async def execute_core(self, *, state, thread_id, params):    question = external_events_text(state.get("external_events") or [])    answer = await run_existing_agent(        question=question,        user_key=bound_user_key(state),        thread_id=thread_id,        emit=stream_to_kdcube,    )    state["final_answer"] = answer    return state

The conversation event lane serializes turns for the same user/conversation/agent while different conversations can run concurrently. A run-to-completion loop receives one processor wake, then folds every event still pending at turn start — prompts, attachment siblings, and messages queued behind an earlier run — into one ordered invocation. Its watcher keeps only that turn's scheduled reservation alive while observing control events; it does not fold later content into a loop the framework owns. At completion the shared door accounts for the exact event ids it supplied and may wake one later turn. KDCube ReAct can instead keep a live lane handler and fold eligible events into the active turn. A ported LangGraph steer cancels the graph stream, while hosted Claude Code can receive follow-up text or a stop before its next tool call — the consumption models are contrasted in Reactive Turn Delivery, with the ReAct V3 flow in Event Ingress To React Turn and the run-to-completion counterpart worked in ported-langgraph-agents@2026-07-13.

THE RULE

At scale, rebuild the agent or graph for each turn from shared durable state. Reuse connections — a database pool, a checkpointer connection — and discard the per-turn graph when the turn ends.

The worked ported-langgraph-agents@2026-07-13 app demonstrates this boundary with two agents, one app entrypoint, per-agent dispatch, conversation streaming, shared-schema persistence, capability selection, and the reusable isolated execution workspace.

THE SEAM · FOUR PINS WIDE RUNTIME SERVICES · ORDERED LANE · CHAT · CONVERSATION RECORD · FILES · ECONOMICS your existing agentgraph · prompts · tools · state modelvendored unchanged — same package,same imports, same nodes the adapter ◦ pending snapshot ◦ bound identity ◦ stream out ◦ final answer/files the platformeverything aroundthe seam at scale: worker A: build → run → discard · worker B: build → run → discard shared durable state underneath — reuse only CONNECTIONS THE AGENT STAYS WHOLE · THE ADAPTER IS FOUR PINS WIDE
Fig. 3 — the agent stays whole; the adapter is four pins wide.

Read: Settle Your Solution In A KDCube App · Your LangGraph Agent in KDCube · Reactive Turn Delivery.

06 Start with the ready assistant

When you do not need to preserve another agent loop, KDCube ReAct is the shorter path. It is a reactive agentic harness with its own channel and event protocol; it is not dependent on a provider's tool-calling protocol. The runtime builds the agent for each turn from app code, app configuration, and the user's saved conversation selection.

The administrator grants an inventory. The user may narrow it, never widen it:

bundles.yamlYAML
config:  react:    main:      max_iterations: 12      supported_models:        - provider: anthropic          model: claude-sonnet-4-6          label: Sonnet  surfaces:    as_provider:      bundle:        default_chat: true    as_consumer:      default_agent: main      agents:        main:          tools:            - name: web              kind: python              module: kdcube_ai_app.apps.chat.sdk.tools.web_tools              alias: web_tools              allowed: [web_search, web_fetch]            - name: exec              kind: python              module: kdcube_ai_app.apps.chat.sdk.tools.exec_tools              alias: exec_tools              allowed: [execute_code_python]          skills:            custom_root: skills            consumers: {}

From there, add only what the assistant needs:

  • product instructions and skills;
  • Python tools, MCP tools, or named-service namespaces;
  • supported models and role defaults;
  • web search, memories, files, or isolated code execution;
  • connected-account claims for tools that use Gmail, Slack, or another provider;
  • conversation-specific user choices for model and capabilities.

For a generic named service these directions meet at two live gates. The hosted connection grants the agent the named_services:use door; the current catalog defines the requested action; and the selected account's account_scope supplies the provider claim. The bridge rebinds that agent and account authority at the actual tool invocation. Static connection scopes are not a substitute for the provider's current action contract.

The shared chat component already understands streaming progress, answers, files, context, followups, events, reconnection, and conversation reload. You can serve it as the app's default chat, mount it in a scene, or embed it in another product.

THE READY ASSISTANT · ASSEMBLED PER TURN app instructionsprompts · skills admin-granted inventorythe ceiling user’s saved selectionnarrows, never widens ReAct — this turnassembled fresh from the three inputsper-turn build binds the result the shared chat componentstream · files · events · reload web · opt in skills · opt in named services · opt in isolated exec · opt in memories · opt in EVERY CAPABILITY IS OPT-IN · THE USER MAY NARROW, NEVER WIDEN
Fig. 4 — assembled per turn from three inputs; every capability is opt-in.

Read: How To Construct A ReAct Agent · Chat With A ReAct Agent · Chat Component.

07 Add product surfaces only when needed

The decorators are not a checklist. They are independent ways to enter the app — the full declarative contract behind this table is Bundle Platform Integration.

Product needBuilder addsRuntime handles
Streamed conversation / agent turnsexecute_core(state, thread_id, params) on the app base — the base’s shared @on_reactive_event door (run()) calls it; add default_chat: true when the SDK chat should serve it (Reactive Turn Delivery)Ordered event lane, per-conversation lock serialization across workers, the lane-consumer reservation and its finalize invariant, streaming transports, conversation record, files, reload
Authenticated request/response API@api(route="operations"), request/response contract, declared visibilitySession resolution, route dispatch, configured role/user checks, runtime context
Webhook or external callback@api(route="public") plus the correct proof, signature, or managed guardPublic routing and request delivery; the app or configured guard still owns trust verification
KDCube-served widget@ui_widget(...) plus ui.widgets.<alias> build configurationBuild, storage, serving, auth/runtime config handshake
App main view or websiteA normal ui.main_view; optionally ui.main_view.site in bundles.yamlBuild and static serving; site catalog, alias/host routing, SPA fallback, cache policy
Several cooperating browser surfacesA scene only when composition is usefulWidget mounting, surface commands, context drag/drop, configured event relay
Domain objects for other apps or agentsA named-service provider with nouns, refs, search, actions, guards, and presentationDiscovery and the generic named-service grammar across local/API/MCP/Data Bus transports
MCP provider endpoint@mcp(...), tool schemas, and the chosen auth contractMCP routing; managed delegated credentials when configured
Durable app-domain mutation@data_bus_handler(...) and idempotent domain handlingStream delivery, worker claiming, retry/redelivery mechanics
Scheduled or background work@cron(...) to find due work and @on_job for ready executionDistributed scheduling, queueing, worker dispatch, and configured exclusivity
Open-ended generated codeThe platform execution tool or reusable agent adapterSparse workspace, isolated executor, trusted supervisor tools, output hosting

A main view does not have to be a scene. A scene is one optional composition layer for products with several cooperating surfaces. Likewise, a website is a normal built main view with a site declaration; it is not a separate web server inside the app.

Five delivery paths that must not be collapsed

PathWhat it means
Conversation event busOrdered agent-visible context in one tenant/project/user/conversation/agent lane. The proc queue carries a wake; the lane carries the events.
Data BusDurable app-domain messages and mutations, routed by subject with retry, idempotency, and optional object partitioning.
Background jobsRetryable delivery of work already made durable by the app. The stream is transport, not the business record.
CommunicatorTransient peer/session/project progress and UI events, subject to the app's outbound firewall.
Telemetry/recordingApproved observations about work that already happened; not a trigger for domain work.

For a public webhook that starts a conversation turn, verify the caller, submit through the chat-ingress submitter, and reply early. Do not hold the webhook open for an agent and do not route the chat turn through the background-job stream.

SURFACES ARE DETACHABLE CARDS · NOT A CHECKLIST reactive turn / chatADD ONLY IF OWNEDoperationADD ONLY IF OWNEDwebhookADD ONLY IF OWNEDwidgetADD ONLY IF OWNEDmain view / siteADD ONLY IF OWNEDsceneADD ONLY IF OWNEDnamed serviceADD ONLY IF OWNEDMCPADD ONLY IF OWNEDData BusADD ONLY IF OWNEDcron / jobADD ONLY IF OWNEDisolated execADD ONLY IF OWNED your small app boundarystarts with ONE surface ELEVEN CARDS · ZERO MANDATORY · A MAIN VIEW IS NOT A SCENE
Fig. 5 — eleven detachable cards, none mandatory: add only if owned.

08 Identity and credentials stay outside product guesses

One running KDCube deployment is bound to one tenant/project. Many users and conversations can share the same processors, connection pools, Redis, and filesystem infrastructure. The runtime binds the actor, user, authority, routing, app, conversation, and turn to each request and preserves the relevant facts across supported runtime boundaries.

THE BOUNDARY

Application and tool code uses the carried context and scoped SDK/store contracts. Model output does not choose a user id and does not escape into another user's storage namespace.

Crossing a runtime fence preserves actor and delegation provenance; it does not pre-authorize the next operation. The trusted tool, MCP, named-service, or provider boundary checks the authority and grants it requires again. A caller supplied user id or object ref remains an untrusted locator.

For integrations there are two different directions:

  • KDCube uses a user's external account. The user connects Gmail, Slack, Telegram identity, or a normal custom OAuth/OIDC service. Tool code declares the provider claims it needs; credentials resolve server-side and are not exposed to the agent.
  • External automation uses KDCube. A user or admin issues a bounded delegated credential for selected KDCube resources and operations. Managed guards resolve server-side grant records and project the correct authority.

Telegram is an example of a public transport with an identity link. The webhook starts without a KDCube browser session, verifies Telegram proof, resolves the stored delegation edge to the linked platform principal, and only then submits conversation events under delegated authority. An unlinked user receives the connection flow rather than an invented platform identity.

Public routes are therefore not synonymous with unauthenticated behavior. They mean the normal browser-login gate is absent; the route must use the proof model appropriate to the caller.

09 Put each kind of state in its real home

KDCube does not pretend that one store fits every kind of application state.

StateCorrect home
Deployment-scoped non-secret configurationbundles.yaml, merged over safe app code defaults
Deployment-scoped app secretsbundles.secrets.yaml or the configured secrets provider; placeholders only in templates
Durable user choicesTyped User Settings stores over user_bundle_props
Connected-provider tokensUser-scoped secrets resolved through the connections SDK
Conversation messages, events, summaries, and hosted filesPlatform conversation/event/file stores
App-owned relational stateApp-prefixed tables in the tenant/project PostgreSQL schema, scoped by the columns the data requires
App filesystem statebundle_storage_root() on local or mounted/shared filesystem such as EFS; not S3
Persisted app artifactsBundleArtifactStorage, which may use object storage or localfs
Provider-owned mail or Slack dataRead through the provider; do not copy it into app storage without a product reason

One consequence worth stating plainly: the descriptor is the app's whole configuration surface. Every runtime knob an operator may need — a model's output-token budget, a timeout, a feature switch — exists as a declared app property the code reads through the property API. Process environment variables are not an app configuration channel; when you host an existing solution whose own config reads env vars, that stays its standalone idiom, and the wrap overlays the descriptor property onto it (the property wins, the vendored default applies offline).

For PostgreSQL, use the shared schema returned for the tenant/project and app-prefixed table names. Do not create one schema per app, agent, or version. Provision tables idempotently and keep user/agent/conversation columns where the domain requires them.

Treat the app as stateless per invocation even when singleton: true is enabled. Singleton is worker-local reuse, can receive concurrent requests, and does not survive restart or scale-out. Shared schema setup, indexes, generated registries, filesystem trees, and mutations need idempotency plus a lock at the real shared scope: Postgres advisory lock, Redis/distributed lock, or the async observed-file lock for mounted storage. An asyncio.Lock covers only one process.

KDCube does not yet expose a complete app-deprovision hook that automatically drops app-owned Postgres tables. Document table ownership, retention, and the current operator cleanup procedure rather than implying that app deletion removes its data.

Search is another optional building block. You can retain your existing index, use PostgreSQL/pgvector, or use the SDK hybrid-index solution. Route paid embedding/search calls through the economics-aware service when they should be budgeted and attributed.

EVERY KIND OF STATE HAS A REAL HOME configbundles.yaml over code defaultsapp secretssecrets provider / bundles.secrets.yamluser choicestyped User Settingsprovider tokensuser-scoped secrets · connections SDKconversation + filesplatform conversation/event/file storesapp rowstenant/project Postgres · app-prefixed tablesapp filesbundle_storage_root() · local/EFSartifactsBundleArtifactStoragemail / Slack dataread through the provider DO NOT MERGE THESE STORES INTO ONE BOX
Fig. 6 — every kind of state has a real home; none of them is "one box".

10 Isolation and economics are integrated, not magical

The platform provides reusable enforcement points, but the app must select the right ones.

For generated code, KDCube ReAct and the LangGraph reference adapter can use the same isolated-workspace model. The agent proposes a logical locator; a trusted, user-bound resolver decides whether it is visible and materializes only the resolved bytes into the current workspace. The executor sees that workspace, not platform storage, provider credentials, deployment descriptors, or another user's workspace. Trusted tools run through the supervisor boundary.

A conversation file ref such as conv:fi:... is therefore durable addressing, not a local filename and not bearer authority. Tenant, project, actor, user, conversation, selected provider account, and required claim are checked by trusted runtime code before bytes are materialized for execution or for a named-service action. A ref copied from another user's conversation cannot change those bound subjects.

For paid work, derive the economics-aware entrypoint for guarded chat turns and use the economics guards/services on search, tools, jobs, or APIs that spend money. A call is not accounted merely because it exists inside an app. The paid surface must use the platform accounting contract so admission, reservation, usage, settlement, and denial are visible and enforceable.

Use @venv only for app-specific Python dependencies absent from the processor runtime, and pass serializable values across it. For untrusted generated code, use the isolated executor/trusted supervisor instead of passing credentials or live runtime objects into a subprocess. For Claude Code agents, use the SDK integration: it preserves conversation binding, streams through the communicator, accounts usage, and can keep native session/transcript continuity in the Git-backed store across workers and restarts. A missing provider consent should be recorded as an unavailable connection and still let that native runtime see the user's request with its remaining tools; otherwise an approval followed by "try again" reaches a session that never saw the first turn. Independently, the platform conversation record persists accepted user input before either a normal assistant completion or a terminal error, so reload does not lose the failed turn's prompt.

11 The package is a synchronized contract

Even a small app should be maintainable by someone who did not write it. The canonical package keeps runtime declarations and human/machine contracts together:

my-app@1-0/
  entrypoint.py
  README.md
  AGENTS.md
  release.yaml

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

  interface/
    README.md
    my-app.openapi.yaml

  docs/
    README.md
    storage/README.md
    journal/

  tests/

  services/ · agents/ · surfaces/ · events/ · tools/ · skills/ · ui/
    add only the implementation folders this app genuinely owns

OpenAPI describes actual HTTP paths. x-kdcube-surfaces carries the machine-readable declarations for non-REST surfaces such as widgets, MCP, named services, Data Bus handlers, jobs, and chat. AGENTS.md is the local implementation contract for coding agents, not another product README.

THE CONTRACT

Keep this invariant in one change: decorators == interface declaration == descriptor keys and gates == README surface list == focused tests == journal entry.

Conditional folders and docs appear only when the app provides that capability. The package contract makes the app explicit; it does not require chat, UI, ReAct, MCP, a database, or a scheduled job.

The journal is the quick onboarding trail for the next developer or coding agent. Record meaningful architecture, interface, state, and behavior changes as they happen. Keep storage ownership and cleanup in docs/storage, describe every provider/consumer surface in interface/, document every config field and secret placeholder, and maintain release notes alongside release.yaml.

The canonical builder sequence is: assemble from existing SDK building blocks, write the modular app contract, configure and run it, check the common failure catalog, then follow the app-content release procedure.

12 Deploy from Git and update the app independently

bundles.yaml is the environment's app registry. An entry can resolve source from a local path or from Git using repo/ref/subdir, then names the module, singleton behavior, non-secret configuration, and surface policy. App secrets follow the separate secret lifecycle.

Git repo/ref/subdir or local path
        |
        v
bundles.yaml app declaration
        |
        v
platform resolves package + discovers decorators
        |
        +--> builds declared UI
        +--> exposes declared surfaces
        +--> loads effective props and secret refs
        v
running app

For local development, kdcube init creates the runtime and kdcube start starts it. After app code or descriptor changes, apply the app descriptors and reload affected apps; a platform rebuild is not the normal app iteration loop. Different apps in the same tenant/project can be updated independently.

release.yaml records app release metadata. It is not runtime configuration.

FROM GIT TO A RUNNING APP Git / local sourcerepo · ref · subdirapp descriptorbundles.yaml entryresolve + discoverpackage · decoratorsbuild + loaddeclared UI · props · secrets your running appsurfaces served · independently updated EDIT APP → BUNDLE RELOAD PLATFORM REFRESH — A SEPARATE, RARE LANE FOR PLATFORM CHANGES ONLY APP ITERATION IS RELOAD, NOT REBUILD
Fig. 7 — the app iterates by reload; the platform lane is separate and rare.

13 The honest amount of work

KDCube removes repeated runtime engineering. It does not remove the work that makes your product specific.

You still own:

  • domain behavior and product policy;
  • the quality of prompts, tools, skills, and agent logic;
  • custom UI and interaction design when the shared components are not enough;
  • app-owned schemas, migrations, retention, and recovery;
  • unusual provider adapters when standard OAuth/OIDC mechanics do not fit;
  • tests for the authority and failure modes your app introduces.

What becomes reusable is the surrounding path: app loading and reload, request context, ordered conversations, streaming transports, ready chat, file hosting, configuration and secret resolution, user settings, isolated execution, connected-account consent, delegated operators, economics, background delivery, and control-plane visibility.

That is the practical value. You keep focusing on the product while adopting only the runtime boundaries that would otherwise be rebuilt around it.

14 Recommended build order

  1. Choose the smallest useful boundary: API, existing agent, ready assistant, UI, service, or job.
  2. State what the app provides and what it consumes.
  3. Keep existing business logic reusable; make entrypoint.py a thin composition root.
  4. Choose the entrypoint base and optional runtime services deliberately.
  5. Decide identity, authority, state, files, and economics before adding transport adapters.
  6. Write the interface and configuration contract with the implementation.
  7. Test the real transport and its failure path, not only the Python method.
  8. Register the app from local source or Git, reload it, and verify the served surface.

Start with one app and one useful surface. Add another surface when the same app should own it. Add another app when it deserves a separate boundary.

· Read more

Start here

Choose your path

Reference apps

KDCube Engineering
№ 2026-07-15 · kdcube.tech