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.
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.
01 Four valid starting points
| You already have or want | What you add | What KDCube can provide around it |
|---|---|---|
| An existing backend function or service | A thin @api(...) adapter and its declared contract | Authentication context, routing, configuration, secrets, reload, health, and optional managed credentials |
| A LangGraph, CrewAI, Claude Agent SDK, or custom loop | One turn adapter, stream mapping, and identity/state mapping | Ordered conversation delivery, multi-user serving, reusable chat, files, conversation records, and optional economics or isolated execution |
| A configurable assistant | Agent instructions plus the allowed tools, skills, models, and services | KDCube ReAct, ready chat, streaming, web/search and execution building blocks, per-conversation user choices, files, and event handling |
| A broader AI-native product | The UI and domain surfaces the product actually owns | Widgets, 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
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 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:
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.
04 The smallest working app is small
This is a complete runtime surface: one authenticated operation in one app.
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 orderbound identitymap the bound KDCube identity to the agent’s user and thread keysstream outmap the agent’s existing stream onto KDCube progress and answer eventsfinal answerreturn the answer and any declared filesasync 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.
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.
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:
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.
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 need | Builder adds | Runtime handles |
|---|---|---|
| Streamed conversation / agent turns | execute_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 visibility | Session resolution, route dispatch, configured role/user checks, runtime context |
| Webhook or external callback | @api(route="public") plus the correct proof, signature, or managed guard | Public routing and request delivery; the app or configured guard still owns trust verification |
| KDCube-served widget | @ui_widget(...) plus ui.widgets.<alias> build configuration | Build, storage, serving, auth/runtime config handshake |
| App main view or website | A normal ui.main_view; optionally ui.main_view.site in bundles.yaml | Build and static serving; site catalog, alias/host routing, SPA fallback, cache policy |
| Several cooperating browser surfaces | A scene only when composition is useful | Widget mounting, surface commands, context drag/drop, configured event relay |
| Domain objects for other apps or agents | A named-service provider with nouns, refs, search, actions, guards, and presentation | Discovery and the generic named-service grammar across local/API/MCP/Data Bus transports |
| MCP provider endpoint | @mcp(...), tool schemas, and the chosen auth contract | MCP routing; managed delegated credentials when configured |
| Durable app-domain mutation | @data_bus_handler(...) and idempotent domain handling | Stream delivery, worker claiming, retry/redelivery mechanics |
| Scheduled or background work | @cron(...) to find due work and @on_job for ready execution | Distributed scheduling, queueing, worker dispatch, and configured exclusivity |
| Open-ended generated code | The platform execution tool or reusable agent adapter | Sparse 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
| Path | What it means |
|---|---|
| Conversation event bus | Ordered agent-visible context in one tenant/project/user/conversation/agent lane. The proc queue carries a wake; the lane carries the events. |
| Data Bus | Durable app-domain messages and mutations, routed by subject with retry, idempotency, and optional object partitioning. |
| Background jobs | Retryable delivery of work already made durable by the app. The stream is transport, not the business record. |
| Communicator | Transient peer/session/project progress and UI events, subject to the app's outbound firewall. |
| Telemetry/recording | Approved 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.
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.
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.
| State | Correct home |
|---|---|
| Deployment-scoped non-secret configuration | bundles.yaml, merged over safe app code defaults |
| Deployment-scoped app secrets | bundles.secrets.yaml or the configured secrets provider; placeholders only in templates |
| Durable user choices | Typed User Settings stores over user_bundle_props |
| Connected-provider tokens | User-scoped secrets resolved through the connections SDK |
| Conversation messages, events, summaries, and hosted files | Platform conversation/event/file stores |
| App-owned relational state | App-prefixed tables in the tenant/project PostgreSQL schema, scoped by the columns the data requires |
| App filesystem state | bundle_storage_root() on local or mounted/shared filesystem such as EFS; not S3 |
| Persisted app artifacts | BundleArtifactStorage, which may use object storage or localfs |
| Provider-owned mail or Slack data | Read 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.
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.
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.
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
- Choose the smallest useful boundary: API, existing agent, ready assistant, UI, service, or job.
- State what the app provides and what it consumes.
- Keep existing business logic reusable; make
entrypoint.pya thin composition root. - Choose the entrypoint base and optional runtime services deliberately.
- Decide identity, authority, state, files, and economics before adding transport adapters.
- Write the interface and configuration contract with the implementation.
- Test the real transport and its failure path, not only the Python method.
- 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
workspace@2026-03-31-13-36for ReAct, chat, scene, tools, files, and integrations;ported-langgraph-agents@2026-07-13for existing-agent integration at scale;kdcube-services@1-0for backend services, APIs, MCP, named services, storage contracts, and jobs;website@2026-07-12for an app-hosted main-view website.