KDCube
← Engineering
KDCube Engineering · Deep Dive

The KDCube Application Lifecycle: Load, Reload, Build, and Evict

A KDCube app is resolved as a generation, invoked with fresh request context, and cut over cleanly when code, configuration, or UI changes.

18 July 2026Engineering15 minExperienceWorkshop Manual
app lifecyclehot reloadUI buildsproc workersKDCube app reloadBundle Adminapp generation

A KDCube app is not a daemon that owns one permanent Python object. Proc resolves an app generation, loads its entrypoint, binds the current request, applies effective properties, invokes one declared surface, and finalizes the work. Reload changes the generation used by new invocations. It does not rewrite code underneath a coroutine already running.

An AI app lifecycle becomes difficult to operate when four different actions are all called "restart": changing a feature property, selecting a new Git revision, rebuilding a widget, and rebuilding the platform image.

KDCube gives those actions different boundaries. Properties can refresh on live app instances. App descriptors and code move through cache invalidation and generation cutover. UI artifacts rebuild from content signatures. Platform source or image changes use platform refresh. In-flight work continues under the generation it already loaded.

This article follows an app from discovery to invocation and then through each kind of change. It does not repeat the package structure or first integration steps covered by Build an AI App with KDCube.

Current source and commands still use bundle in literal contracts such as @bundle_entrypoint, bundles.yaml, singleton, and kdcube bundle reload. Builder-facing prose calls that deployable unit an app.

00Implementation highlights

ConceptThe takeaway
Proc owns the lifecycleDiscovery, app loading, hooks, route invocation, background-job dispatch, UI build coordination, and eviction happen in proc.
Invocation state is request-localActor, user, authority, communicator, conversation, and turn are rebound for every call, including on a reused singleton.
Singleton is an optimizationIt is worker-local, concurrent, ephemeral reuse of one loaded app spec, not a durable service or serialization guarantee.
Change has several boundariesProperties refresh, descriptor save, app reload, UI rebuild, venv rebuild, and platform refresh are distinct operations.
Reload is a generation cutoverNew invocations resolve the updated app; already-running invocations finish with the generation they loaded.
UI is content-addressedProc builds missing or stale UI under a shared lock, publishes atomically, and leaves no permanent failure marker after a failed build.

01The smallest honest lifecycle

For one API request, widget operation, MCP call, chat turn, Data Bus message, or background job, the conceptual path is:

configured app registry
  -> resolve source + module + app generation
  -> discover entrypoint and surface metadata
  -> instantiate app or reuse worker-local singleton
  -> bind current request, actor, user, authority, and routing context
  -> load and merge effective app properties
  -> invoke the declared async surface
  -> run surface/turn finalization
  -> release request-local context

The app instance is not the request. Even when an instance is cached, the request context is rebuilt and rebound.

The runtime expects every platform-invoked surface and every I/O chain behind it to remain async. A synchronous database, HTTP, filesystem, Redis, subprocess, sleep, or lock call inside async def blocks the shared proc event loop and delays other apps and users.

One KDCube application invocation A seven-station process rail from registry through finalization. Request context attaches only at bind and remains through finalization. Durable state remains outside application memory. WORK ORDER 01 · ONE INVOCATION Resolve a generation. Bind a request. Invoke one surface. The application object is never the request, even when proc reuses a worker-local singleton. 01 Configured registry authority 02 Resolve generation source + spec 03 Discover surfaces decorators 04 Instantiate or reuse worker-local 05 Bind current request context joins 06 Apply properties one snapshot 07 Invoke + finalize async surface DETACHABLE REQUEST CONTEXT actor · user · authority · routing · conversation turn · communicator DURABLE STATE explicit stores, never instance memory PostgreSQL · Redis · app storage · artifacts · conversation stores
Workshop Manual process rail with seven stations: registry, resolve generation, discover, instantiate/reuse, bind request, apply props, invoke/finalize. A detachable strip labeled "request context" attaches only from bind through finalize. A separate "durable state" line runs beneath the rail and never enters the app object's memory.

02Discovery, load, and the one-time hook

Proc starts from the configured app registry. It resolves a Git or local source, imports the declared module, discovers decorators and interface metadata, and constructs the entrypoint.

on_bundle_load() is the one-time initialization hook for one app in one proc process and tenant/project context. It is suitable for deterministic, idempotent preparation:

  • verify or create app-owned tables and indexes;
  • materialize a local search index from a source signature;
  • prepare a repository mirror or shared read-only assets;
  • reconcile an app-owned registry;
  • ensure a hot public-content projection exists.

It is not a place for request-local actor, user, conversation, turn, or communicator state. Several proc workers can load the same app, so mutations of shared PostgreSQL, Redis, object storage, or EFS still need a lock at that shared scope.

on_bundle_load() means once per relevant process lifecycle, not once for the entire fleet and not once forever. The operation must remain safe after worker restart, scale-out, cache eviction, or a new app generation.

03Non-singleton and singleton apps

The safest default mental model is stateless per invocation.

Without singleton reuse, the runtime constructs an app instance for the invocation. Durable data remains in properties, secrets, PostgreSQL, Redis, app storage, or another explicit owner.

With singleton: true, one proc worker may reuse an instance associated with a loaded app spec. That can avoid repeated construction for crawler-heavy public content, expensive client setup, or other justified cases. It does not change the durability model.

Singleton factOperational consequence
Cache is in one proc workeranother worker has another instance
Key is the loaded app spec, not merely app idsource/module changes create a different generation
The same object can serve several users and surfacesrequest state cannot live in instance fields
Calls can overlapsingleton does not serialize app methods
Worker restart drops the objectmemory is not authority
Request context is reboundself.comm, actor, user, conversation, and turn must resolve from the current invocation

Long-lived shared clients can live on a singleton only when they are safe for concurrent use and do not capture a previous request's identity. A per-user client, mutable graph state, selected conversation, or old communicator reference cannot.

04What happens on every invocation

Before the app surface runs, proc reconstructs the current execution context. That includes the effective tenant/project, app, actor, user, authority, routing, and surface metadata required by the call.

The runtime then applies effective app properties:

code defaults
  deep-merged with
descriptor / Bundle Admin properties
  plus
request-scoped bundle_call_context where a supported feature consumes it

If the effective app properties changed on a cached instance, on_props_changed(previous_props, current_props, ...) can reconcile long-lived side effects. It is not a second constructor and should not perform slow one-time installation that belongs in on_bundle_load().

Examples:

  • update a reusable client when its non-secret endpoint changed;
  • replace a timer or local policy snapshot;
  • rebuild a model service when platform-interpreted model properties changed;
  • close a resource no longer enabled by the new properties.

The current invocation sees one coherent effective snapshot. Application code should read through self.bundle_prop(...), not poll raw descriptor files.

05Six kinds of change, six application moments

The word "hot reload" hides important differences. The runtime applies changes at the boundary appropriate to the changed thing.

What changedAuthority/change pathWhen new behavior appears
Non-secret app propertysupported helper or Bundle Admin persists authority, updates Redis cache, publishes props updaterefreshed cached instance or next invocation; on_props_changed runs only when effective props differ
App secretconfigured secrets providera fresh helper read sees the provider value; clients/config objects that captured an old secret must be reconstructed
App registry/source fields (repo, ref, subdir, path, module, singleton)Bundle Admin Save or descriptor-authority replayworkers evict the old generation; new invocations resolve the saved generation
Python code or decorator metadataapp reload/cache evictionnext invocation imports/discovers the updated generation
UI sourcesource signature checked on main-view/widget HTML entrypointcurrent artifact is served when signature matches; stale/missing output builds before serving
@venv requirementsrequirements.txt content hashnext decorated call lazily rebuilds that app venv

Secret rotation has no universal props-style notification. Code that calls the async secret helper for each operation sees the current provider value. A singleton client constructed with a secret keeps what it captured until app code explicitly refreshes it or the app generation is reconstructed. On the chat path, provider keys used to construct a turn's model configuration are resolved for the next turn.

Surface behavior can therefore change without code reload when it is intentionally property-driven: feature gates, visibility policy, model routing, cron configuration, MCP connections, UI config, or another declared runtime knob. Changing an API implementation, decorator, import, or module still changes code and needs app eviction/reload.

Six application change boundaries Six operation cards distinguish property refresh, secret resolution, registry save and eviction, Python reload, UI signature build, and virtual environment rebuild. WORK ORDER 02 · CHANGE CONTROL Six changes. Six application moments. Use the boundary owned by the thing that changed; “restart” is not one operation. OP 10 · PROPERTIES Refresh persist authority → cache → publish current/next invocation sees one snapshot on_props_changed only when values differ OP 20 · SECRETS Resolve again provider read → reconstruct client no universal props-style notification fresh helper read sees provider value OP 30 · REGISTRY Save + evict persist spec → update registry → notify new invocations resolve saved generation Save does not run npm OP 40 · PYTHON Reload + import evict caches → discover decorators next invocation imports new generation in-flight work stays on old generation OP 50 · UI Signature + build source signature → shared lock atomic artifact publish failure leaves no permanent marker OP 60 · VENV Hash + lazy rebuild requirements hash → isolated environment next decorated call checks content hash app-specific dependency boundary THE RULE No single restart verb covers these six contracts.
Six Workshop Manual operation cards. OP 10 Props -> refresh; OP 20 Secrets -> next resolution/reconstruction; OP 30 Registry -> save + evict; OP 40 Python -> reload + import; OP 50 UI -> signature + build; OP 60 Venv -> requirements hash + lazy rebuild. A footer says "No single restart verb covers these six contracts."

06Save, reload, build, and refresh are not synonyms

Bundle Admin Save

When an administrator changes app registry fields or configuration and presses Save, the platform:

  1. persists the configured app authority;
  2. updates the active Redis registry;
  3. publishes the app update;
  4. makes proc workers evict the changed app's imported code, singleton, manifest, and static-entrypoint state as required;
  5. returns without running npm, Vite, or another UI build command.

For a changed Git ref, Save is already the registry mutation and invalidation operation. Pressing a separate Reload button immediately after it is not the normal second half of Save.

Reload app

Reload rereads/reapplies the existing authority and evicts the selected app from proc caches. Use it when code or descriptors changed outside Bundle Admin, or when explicit import/manifest/static-entrypoint invalidation is needed.

Reload does not synchronously build every UI and does not rerun the completed startup preload pass.

UI build

The next startup preload or main-view/widget HTML request resolves the active generation, computes the UI signature, and builds only when the shared artifact is missing or stale.

Platform refresh

Platform refresh is for platform source, images, dependencies, and runtime services. It is not the normal inner loop for editing one app.

Operator intentCorrect boundary
Change a live app propertyBundle Admin or supported property helper
Select another Git revisionSave the app registry change
Load edited local Python/decoratorskdcube bundle reload <app-id> ...
Rebuild edited UI sourcerequest/reload the HTML entrypoint; signature decides
Rebuild an app-specific venvchange requirements; next decorated call rebuilds
Change KDCube platform code/imageplatform refresh/build/restart flow

07A reload does not rewrite an in-flight request

Suppose request A began under generation G1 while an administrator saves G2.

request A: resolve G1 -> execute G1 ----------------------> finish G1

admin:                         Save G2 -> publish update
                                           |
workers:                                   evict G1 caches
                                           |
request B:                                 resolve G2 -> execute G2

Request A continues with the code and effective context it already loaded. Request B sees G2 after invalidation and resolution. This is generation cutover, not live mutation of Python frames.

That property keeps the change understandable. The runtime does not promise that every concurrent request flips at one global nanosecond. It promises that new invocations use the updated generation after the relevant invalidation boundary, while old invocations are not half-rewritten.

Application generation cutover An in-flight request continues on generation G1 while an administrator saves G2. After workers evict G1 caches, a new request resolves and runs G2. WORK ORDER 03 · GENERATION CUTOVER New work moves. In-flight work is not rewritten. SAVE G2 REQUEST A resolve G1 execute G1 finish G1 REQUEST B resolve G2 execute G2 publish update → evict G1 caches Generation cutover, not frame mutation G1 remains coherent for request A; G2 becomes the generation resolved by request B.
Two horizontal timelines. Request A starts on G1 and crosses the Save marker to finish on G1. Request B begins after worker eviction on G2. The Save marker is a brass gate between generations, not a knife cutting Request A.

08UI build is a coordinated artifact lifecycle

Widgets and main views are built by proc. They do not build in the generated code executor or the isolated runtime.

The source/build declaration belongs to effective app properties. The build signature includes the component kind, component source tree and path, build command, bundle delivery id, and signatures of declared shared UI sources. Proc copies/assembles build input in a worker-local work area, while the completed output, signature, and lock live under the configured shared app storage.

UI build is a coordinated artifact lifecycle A flow of work-order cards. The browser requests a widget or main index.html; proc computes the current source signature from component kind, source tree, build command, and shared UI sources; a shared artifact check follows. On a match, the warm card: serve it, no build runs. On no match, flow drops into the shared build lock card: re-check the signature under the lock, run the npm or Vite subprocess in a work area, publish the completed directory atomically, write the signature and release the lock — then serve. A failure-rules card states: timeout or cancellation reaps the subprocess tree, writes no signature, and releases the lock; a dead worker's lock expires so another can recover. A closing card notes main views and widgets have separate outputs and signatures, and an app-hosted website adds routing behavior, not a second compiler. WORK ORDER 04 · UI BUILD Build under one lock. Publish atomically. Serve. Proc builds UI in a worker-local area; output, signature, and lock live in shared app storage. REQUEST widget / main index.html browser or scene asks SIGNATURE compute source signature kind · tree · command · shares MATCH? shared artifact check YES serve it no build runs NO SHARED BUILD LOCK one owner builds; waiters re-check and reuse re-check the signature under the lock run the npm / Vite subprocess in a work area publish the completed directory atomically write the signature · release the lock FAILURE RULES no permanent marker timeout / cancel reaps the subprocess tree, writes no signature, releases the lock; a dead worker’s lock expires SEPARATE OUTPUTS main views and widgets have separate outputs and signatures an app-hosted website adds routing/catalog behavior — not a second UI compiler A FAILED BUILD LEAVES NO SUCCESSFUL SIGNATURE · A LATER REQUEST SIMPLY RETRIES
Build under one lock, publish atomically, serve — a failed build leaves no permanent marker.

Several workers may discover stale output concurrently. Only the lock owner builds; waiters re-check the signature and use the completed artifact.

The build task is shielded from a canceled browser request. A timeout or administrative cancellation terminates and reaps the subprocess tree, writes no successful signature, and releases the lock. A later qualifying request can retry. If the worker dies, its lock heartbeat stops and expiry permits another worker to recover.

Main views and widgets have separate outputs and signatures. Declaring a main view as an app-hosted website adds routing/catalog behavior; it does not create a second UI compiler.

09Startup preload warms generations, it does not watch forever

When bundles_preload_on_start is enabled, each proc worker joins one collaborative preload generation during startup. Redis claims distribute apps across workers, while shared signatures and locks prevent duplicate UI builds.

The preload generation includes the source/module/singleton facts and resolved Git commit. A deterministic new revision therefore becomes a new generation.

Startup preload is not a continuously running reconciler:

  • saving an app while proc is already running invalidates that app;
  • opening its main view/widget can warm the changed generation;
  • restarting proc starts another full collaborative preload pass;
  • Reload app invalidates the app but does not replay startup preload itself.

Health output exposes preload readiness and per-app status so an operator can distinguish "registered," "loaded," "UI warm," and "failed to preload."

10Git is the deployable source; local paths are the development seam

Operator-supplied apps in non-local deployments normally resolve from Git:

descriptorYAML
repo: https://github.com/example/acme-apps.gitref: <immutable release ref or commit>subdir: apps/reportingmodule: entrypoint

Built-in platform apps use their reserved platform delivery path. For a Git-delivered app, an immutable ref makes the generation reproducible. Changing the selected ref through Bundle Admin or the descriptor authority creates the normal Save and eviction journey described above.

Local path plus module is for local development only. It points proc at host-edited source mounted into the local runtime:

descriptorYAML
path: /bundles/acme-apps/apps/reportingmodule: entrypoint

After local Python or decorator changes, reload that app. UI-only source edits are detected by the UI signature on the next HTML entrypoint request; reload remains the escape hatch when Python defaults, decorators, manifests, imports, or other process-local state also changed.

Do not mix path with repo, ref, or subdir on one app item. A source is either the local development seam or the Git deployment definition.

11The lifecycle review

Before adding state or operational behavior to an app, verify:

  1. Is this data durable outside the Python instance?
  2. Is initialization deterministic and idempotent in every proc worker?
  3. Does shared mutation use a lock at the actual shared store?
  4. Can the same singleton object receive two users concurrently without leaking request context?
  5. Is the change a property refresh, secret reconstruction, registry Save, app reload, UI signature rebuild, venv rebuild, or platform refresh?
  6. What happens to an invocation already running under the old generation?
  7. Is UI output published atomically only after a complete build?
  8. Can a failed build or dead worker be retried without a permanent poison marker?

The result is a lifecycle that can scale across workers without turning every change into a fleet restart.

Read the contracts

Platform documentation:

Related public articles:

The Markdown file beside this page is the approved indexing source. Literal platform contracts retain names such as bundle_id, bundles.yaml, and SDK bundle decorators.

KDCube Engineering
18 · 07 · 2026