Platform

KDCube combines an app framework with the runtime that serves and enforces it. One running deployment is bound to one effective tenant/project and can serve many apps and concurrent users. Backing Postgres, Redis, object storage, or filesystem infrastructure may be dedicated or shared with other deployments through tenant/project schemas, namespaces, and prefixes.

Ingress (chat-ingress)

Authenticates conversation clients, validates routing and admission policy, atomically publishes accepted conversation events, and opens SSE or Socket.IO delivery back to the client. Synchronous app APIs have their own processor-side surface guards.

Processor (chat-proc)

Claims scheduled work, resolves the target app, constructs runtime context, and invokes its declared surface. ReAct is one conversation consumer; existing LangGraph or custom agents can use the same serving path. App operations, widgets, MCP surfaces, named-service providers, and site delivery also live here.

Task Orchestrator

Runs inside chat-proc. It fairly schedules conversation lane wakes and ready background work, creates the normal app runtime context, and invokes reactive-event or @on_job handlers.

i
Current platform layer: apps can also participate in the object ecosystem by registering named-service providers, emitting external events, publishing block policies, and mounting scene surfaces. Internal routes and descriptors still use bundle_id and /bundles/... as compatibility identifiers.

Object, Conversation, and App Planes

Named-service registry

Maps object namespaces to app-provided operations such as search, schema, action, materialization, and block policy hooks.

Conversation Event Bus

Stores ordered prompts, attachments, follow-ups, steer requests, consent completions, and other context for a current or future app turn.

App Data Bus

Delivers app-owned domain mutations to @data_bus_handler(...), independently of any conversation. Payload size does not decide which bus to use.

Conversation Event Bus, Data Bus, and Client Streaming

These mechanisms solve different problems. The Conversation Event Bus is conversation-scoped ingress. The Data Bus is app-scoped work. ChatCommunicator is the outgoing stream from a running turn to connected clients. Sharing Redis transport does not merge their semantics.

conversation input                 app-owned mutation
        |                                |
        v                                v
ordered event lane L              Data Bus stream
        |                                |
bodyless wake Q                  @data_bus_handler(...)
        |                                |
        v                                +-- optional explicit external_events[]
scheduled app turn                         to a conversation lane
        |
        v
ChatCommunicator -- live/recorded output --> SSE / Socket.IO client
MechanismAuthorityExamplesConsumer
Conversation Event BusOrdered Redis lane per tenant/project/user/conversation/agentUser prompt, attachment, follow-up, steer, delegated-helper result, connected-account grant eventThe scheduled app turn and its live event browser
Data BusApp-owned durable work streamDomain mutation or integration work that exists independently of chatDeclared @data_bus_handler
CommunicatorRunning app turnStreaming text, blocks, tool lifecycle, files, errors, completionAuthenticated browser clients, recorders, event sinks

Classical Runtime and Agent Harness

KDCube has two runtime layers with different ownership. The shared Agent Harness is not ReAct renamed, and it does not replace the classical runtime facilities used by apps and tools.

LayerCanonical responsibilityConsumers
Classical runtime
sdk/runtime
Process facilities, tool execution, external execution, and profile-selected generated-code isolation.Apps, tools, ReAct, hosted frameworks, and platform services.
Agent Harness eventsResolve canonical object and event refs and authorize byte or action access. It is not the Event Bus.ReAct, hosted frameworks, conversation services, and integrations.
Agent Harness workspacePer-turn paths, conv:fi refs, pull/materialization, change detection, files, and framework-neutral artifacts.ReAct and ported-agent adapters, chat, canvas, and file surfaces.
Agent Harness timelineEvent identity, conv.timeline.v1, TurnLog, turn views, and ownership-fenced provider projection.ReAct and ported-agent adapters, conversation persistence/search, and clients.

ContextRAGClient belongs to sdk/solutions/conversation, not ReAct. ReAct and a hosted LangGraph adapter are sibling consumers of shared harness contracts; importing a harness helper does not give another framework ReAct's live mid-turn folding or protocol guarantees.

SSE Streaming Flow

SSE streaming flow diagram
SSE Streaming Flow Real-time event delivery from app to client via Server-Sent Events Client open SSE stream send message Ingress auth · rate limit enqueue task fan-out SSE Redis Queue task buffer Proc dequeue · execute app.run() communicator App emit events via comm. SSE stream → client in real time (Redis pub/sub relay)

Modules

Platform Architecture — Detail

Platform architecture detail diagram
Platform Architecture Detail Detailed view of KDCube service modules including ingress, processor, storage, and Redis layers Client Browser HTTPS :443 web-proxy OpenResty TLS termination token unmasking routing chat-ingress :8010 Auth · JWT · Rate Limit /api/chat/* — SSE gateway /api/conversations/* /api/resources/* · opex/* /api/economics/* · ctrl/* Redis Queue per user_type chat-proc :8020 App Loader · harness adapters /integrations/.../ops/{op} Admin: app reg · props Admin: secrets · cleanup Communicator (pub/sub) invoke chat·steer·ops app communicator → SSE YOUR APP @bundle_entrypoint surfaces · lifecycle tools · skills ReAct Agent loop Communicator Firewall Settings PostgreSQL RDS conversations Redis ElastiCache cache · pub/sub · queue EFS / S3 app storage Shared persistence — managed by platform

Services

ServicePortRoleRequired?
web-proxy:443 / :80TLS termination, token unmasking, routingRequired
chat-ingress:8010Auth, SSE/Socket.IO gateway, task enqueueingRequired
chat-proc:8020App execution, integrations REST API, task orchestrator, background job stream consumerRequired
web-ui:80Reference SPA frontendReference deployment
kdcube-secretsinternalLocal secrets helper for deployments configured with the secrets-service providerConditional
metricsinternalAutoscaling metric export (CloudWatch) — not needed for single-nodeOptional
proxylogininternalDelegated auth token exchangeOptional
clamavinternalAntivirus scanning for file attachmentsOptional
exec (on-demand)Profile-selected generated-code execution: local subprocess, Docker combined/split, or an external remote backend; Fargate is the shipped external backendOptional

Routing

Path PatternRoutes To
/sse/*, /api/chat/*, /admin/*chat-ingress
/api/integrations/*chat-proc
/auth/*proxylogin (delegated auth only)
/*web-ui

Processor Architecture

The chat-proc service is the execution side of the platform. After ingress admits and enqueues a request, the processor claims it, loads the target app, executes the workflow, and streams results back through the relay communicator.

App State: What Lives Where

Data classLive authority todayOperational note
Deployment-scoped app propsThe configured descriptor provider: mounted writable bundles.yaml in file mode or grouped descriptor documents in provider-backed mode. Redis is a runtime cache, never the write authority.Exported with app descriptors
Deployment-scoped app secretsConfigured secrets provider; in local secrets-file mode this is bundles.secrets.yamlExported with app secrets only
User-scoped app propsPostgreSQL <SCHEMA>.user_bundle_propsOperational user data, not descriptor state
User-scoped app secretsConfigured secrets provider; in local secrets-file mode this is secrets.yamlOperational user data, not descriptor state

This split is intentional: only deployment-scoped app state belongs to descriptor authority and app export. User-scoped state stays outside bundles.yaml and bundles.secrets.yaml.

When isolated execution runs in Docker or Fargate, chat-proc ships the descriptor authority to the supervisor as KDCUBE_RUNTIME_*_YAML_B64 payloads. The supervisor materializes those descriptors and uses the same settings/secrets APIs as proc; generated code receives only the filtered executor environment. App runtimes can set descriptor_payload_scope: active_bundle to filter bundles.yaml and bundles.secrets.yaml to the active caller app before packaging.

Conversation Lane and Wake Model

Conversation event order comes from a Redis lane, not from the processor queue. One lane identity is tenant + project + user_id + conversation_id + agent_id. Redis lane sequence orders accepted events for that identity.

The effective materialization turn for an active agent execution is always runtime_ctx.turn_id. Earlier ingress or event-lane values remain provenance such as origin_turn_id or target_turn_id; they do not replace the current execution's turn.

Reactive ingress atomically appends the complete prepared event batch to lane L and admits one ExternalEventLaneWakeup to scheduling queue Q. Either both happen or neither happens. Non-reactive events are appended only to L and do not wake the app. The wake contains routing and lane coordinates, not the event body; proc resolves the accepted event from the lane and reconstructs the payload from its retained task_payload.

StatePurpose
Lane LAccepted, sequence-ordered conversation events and retained task payloads.
Wake queue QFair work scheduling. It never defines event order and carries no request body.
State table TLogical turn owner, handler/consumer status, processed cursor, and handoff state.
Owner leaseToken-fenced physical reader lease held by the live ContextBrowser listener.

Task Orchestrator And Background Job Stream

Conversation turns are only one kind of work. The processor also runs a sibling background job stream for ready work produced by cron scans, widget operations, admin actions, or app-specific schedulers. These jobs use Redis Streams and remain separate from the conversation event lane.

The stream does not decide when work is due. A producer first creates any durable app-owned domain record, then enqueues a ready-work envelope. The processor fairly polls chat work and background work, claims stream messages through a consumer group, builds a normal app runtime context, and invokes the app's async @on_job handler.

LayerResponsibility
ProducerDetect due work, create the durable domain record, choose work_kind, job_id, dedupe_key, metadata, and payload.
Redis StreamPersist ready work, dedupe submissions, expose consumer-group claiming, and recover idle pending jobs with XAUTOCLAIM.
ProcessorRoute the envelope to the target tenant/project/bundle_id app slot, bind runtime context, invoke @on_job, and acknowledge only after success.
App @on_jobInterpret work_kind, load app-owned records from payload, execute the job, and update execution/result state.

The platform treats metadata and payload as routing/runtime context plus app-owned data. It does not understand app-specific job semantics. That boundary lets apps implement scheduled reports, task executions, mailbox processing, or other domain work without adding a new platform service for each job type. See the source design note: jobs-stream-README.md.

App Loader & Lifecycle

Processor workers load apps through a registry + singleton cache model:

  1. On startup (and on app-update broadcasts), the worker rebuilds its in-memory app registry.
  2. Startup preload is collaborative: workers claim app generations in Redis, heartbeat active claims, and write generation-specific done markers after success.
  3. At request time, the registry resolves the app to a concrete path. Module/singleton cache keys are based on the resolved path.
  4. Built-in example apps are merged into the registry and copied to shared storage with a versioned path (/bundles/{bundle_id}__{ref}__{sha}).
  5. On update, loader caches are cleared. New requests use the new path; already-running turns continue on the previously loaded path.

Redis distributes preload work; shared-filesystem locks remain the final guard around UI artifact writes. UI builds are supervised process-local tasks independent of the initiating HTTP request: source and node_modules stay worker-local, while shared storage holds locks, signatures, temporary output, and atomically published artifacts. A heartbeat and lock TTL permit retry after a worker crash, and npm/Vite process groups are terminated and reaped on cancellation or timeout. Bundle Admin property reads are non-lifecycle operations and cannot start, cancel, or restart a build.

If Redis is unavailable, workers fall back to local traversal with the shared-storage lock. A cached app singleton is a code/lifecycle object, not permission to retain a user's stateful agent graph: scalable framework integrations rebuild their graph per turn and reuse only durable connections.

Application Sites

An app can expose its built main view as a complete website. Validated declarations from bundles.yaml compile into a versioned ApplicationSiteCatalog. Redis atomically distributes generation snapshots, while each proc routes host and alias requests from an immutable in-memory catalog. Request-time routing does not parse YAML, scan app properties, or query Redis.

The catalog carries both site metadata and the resolved app target, keeping one generation internally coherent. Site files use the standard app UI storage lifecycle and support direct files, directory indexes, and SPA fallback. A CDN may preserve the viewer host and rewrite clean paths to /api/integrations/site-root/{path}; it does not own the site registry.

Execution Pipeline

Once scheduled work is claimed, the processor follows the surface-specific path:

  1. Receive — claim a lane wake or background-work envelope under the configured queue ownership rules.
  2. Resolve — load the accepted lane event or job payload and construct service, request, conversation, and accounting context.
  3. Load app — resolve the target app through the registry, load/reuse the singleton.
  4. Execute — invoke the declared reactive, operation, job, MCP, or named-service surface under its timeout, ownership, policy, and accounting bindings.
  5. Stream results — the app emits events through the ChatCommunicator; the relay forwards them via Redis pub/sub to ingress, which delivers them to the client over SSE.

Conversation completion is additionally fenced by lane ownership and the processed-event close gate. PostgreSQL conversation state is the admission/UI projection, not the live turn lease and not authority to start a competing turn.

Turn Ownership, Recovery, and Close Safety

The logical turn owner is recorded in lane state. A fresh active-consumer heartbeat acknowledges liveness; a short scheduled reservation prevents duplicate starts during app loading but does not prove an old handler is alive. The live ContextBrowser separately holds the token-fenced event-source lease that protects the physical reader.

Both the background listener and direct decision/tool-phase watcher accept live events through the same owner-fenced path. Accepted events first run source-owned block production. A source may produce zero or more timeline blocks; a zero-block event still advances the processed cursor explicitly. The close gate succeeds only when the timestamp-plus-event-ID cursor covers the latest accepted event, including after compaction.

If another turn supersedes the owner, the stale turn may resume briefly but cannot fold new lane events, commit an answer, or become conversation head. Cancellation closes the handler, stops the listener, and releases the owner lease. Retained reactive work that remains unconsumed after save receives a new wake through post_save_handoff.

Operations API Surface

The processor exposes REST endpoints that do not require SSE:

  • /integrations/bundles/{tenant}/{project}/{bundle_id}/operations/{op} — app-defined operations called directly by UI widgets.
  • Admin endpoints for app registration, property management, secrets injection, and cleanup.

These endpoints run inside the processor because they need access to the loaded app singleton and its runtime context. This is what makes app-owned UI surfaces practical: widget UI or main UI can call directly into the live app rather than going through a second app server. Frontends may embed those surfaces, but embedding is a client display choice; KDCube serves the app UI and its APIs.

i
Public API auth: an app may own verification for a declared public route, or use a Connection Hub managed REST/MCP guard. Internal peer operations carry platform-controlled invocation provenance but still apply target visibility, delegated-authority, and enabled-surface checks.

Runtime Availability Enforcement

The processor treats the canonical enabled.* app props as live runtime policy, not build-time metadata. On every inbound call it resolves the app-level and resource-level switches from effective app props before dispatching the app method.

  • App disabled: operations, widgets, and MCP endpoints return 404; scheduled jobs for that app are not reconciled.
  • Resource disabled: only that operation, widget, MCP endpoint, or cron job is suppressed.
  • No switch or missing prop: the surface stays enabled.

HTTP enforcement lives in the processor integration layer; cron enforcement lives in the app scheduler. This is why an app-props update can change surface availability immediately for new requests without rebuilding or redeploying the app.

Communicator Integration

The processor uses the relay communicator pattern: app events are published to Redis pub/sub channels, ingress subscribes and fans them out over SSE to connected clients. This decouples execution from delivery — the processor never holds SSE connections directly, and horizontal scaling of proc replicas does not affect client connectivity.

Recorded Events and Isolated Tool Handoff

The communicator can also record selected post-firewall envelopes into scoped, bounded buffers. Recording is runtime-local while execution is active, but portable recording scopes are included in COMM_SPEC when the processor launches a platform child runtime.

The normal isolated-tool pattern is: child runtime records, child writes comm_recorded_events.json, host merges the side file into the host communicator, and the host sends the merged batch through the configured sink. This covers TOOL_RUNTIME[tool_id] = "local" as well as Docker/Fargate tool execution when the runtime output directory is returned. Sink callbacks remain host-side unless the child explicitly configures its own sink.

Process Topology

Each Uvicorn worker in chat-proc has its own Redis and Postgres clients, app registry, consumers, and cleanup loops. Workers across replicas compete for scheduled work. There is no sticky worker-to-conversation affinity: lane ownership, active-consumer liveness, and the event-source lease protect each turn independently of the process that happens to run it.