Platform

Ingress (chat-ingress)

Handles all inbound traffic. Authenticates users, enforces rate limits, validates the app and environment scope, enqueues the task, and opens an SSE stream back to the client. Your app rarely needs to know about this — the platform wires it up automatically.

Processor (chat-proc)

Dequeues tasks, loads your app singleton, and calls execute_core(). This is also where the ReAct subsystem runs, where apps can launch ISO runtime work, and where the Operations API lives for app widget UI, main UI, and other app-owned frontends.

Task Orchestrator

Runs inside chat-proc. It balances interactive chat tasks from Redis Lists with ready background work from Redis Streams, creates normal app runtime context, and invokes @on_job handlers for durable app-owned work.

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, Event, and Data Planes

Named-service registry

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

Event bus

Carries live UI-visible events, recorded turn events, tool lifecycle steps, and external event envelopes across Ingress, Proc, and connected clients.

Data bus

Carries typed subsystem payloads and surface coordination messages that are not plain chat deltas, for example search results, context pins, and widget state events.

Event Bus and Data Bus Architecture

KDCube uses two related communication planes. The event bus carries time-ordered happenings that clients, recorders, and timelines may observe. The data bus carries typed subsystem payloads and coordination messages such as named-service search results, context pin envelopes, scene commands, and widget state updates.

App / ReAct runtime / integration / widget
  emits event or data payload
      |
      v
Processor
  applies app event firewall, records selected events, resolves provider policies
      |
      v
Redis / runtime channels
  event bus: live + recorded event envelopes
  data bus: typed subsystem payloads and surface coordination
      |
      v
Ingress
  fans out to authenticated SSE / Socket.IO clients
      |
      v
Client surfaces
  chat steps, artifacts, scene, canvas, memory/task widgets, custom app UI
PlanePayloadExamplesConsumer
Event busTimestamped event envelopeschat deltas, tool call/result lifecycle, external event envelope, recorded app events, turn completionChat UI, timeline, event sinks, audit views
Data busTyped subsystem datanamed-service search results, context pins, scene open command, canvas patch notification, widget state payloadScene host, canvas, widgets, custom app surfaces

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 · LangGraph /integrations/.../ops/{op} Admin: app reg · props Admin: secrets · cleanup Communicator (pub/sub) invoke chat·steer·ops app communicator → SSE YOUR BUNDLE @bundle_entrypoint run() · on_bundle_load() 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:80SPA frontendRequired
kdcube-secretsinternalLocal dev secrets helper used by the descriptor-driven runtimeRequired (local)
metricsinternalAutoscaling metric export (CloudWatch) — not needed for single-nodeOptional
proxylogininternalDelegated auth token exchangeOptional
clamavinternalAntivirus scanning for file attachmentsOptional
exec (on-demand)Ephemeral Docker/Fargate container for isolated code executionOptional

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 propsMounted writable bundles.yaml when present; Redis is the proc runtime cache; grouped descriptor docs are fallback only when no mounted file existsExported 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.

Task Queue Model

The processor uses Redis Lists as its task queue. Ingress pushes task payloads with LPUSH; processor workers claim them with BRPOPLPUSH, atomically moving the item from a ready queue to an inflight queue. This gives FIFO ordering within each lane.

Tasks are partitioned into user-type lanesprivileged, paid, registered, and anonymous. Workers rotate fairly across lanes so no single tier starves the others. Each claimed task is protected by a per-task Redis lock (SET NX EX) to prevent duplicate processing.

Redis Key PatternPurpose
{tenant}:{project}:...:queue:{user_type}Ready queue (one per user-type lane)
{tenant}:{project}:...:queue:inflight:{user_type}Inflight queue (claimed but not yet complete)
{LOCK_PREFIX}:{task_id}Per-task dedup lock
{LOCK_PREFIX}:started:{task_id}Started marker — prevents auto-replay once execution begins

Task Orchestrator And Background Job Stream

Interactive chat 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, not the chat Redis List queue.

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. At request time, the registry resolves the app to a concrete path. Module/singleton cache keys are based on the resolved path.
  3. Built-in example apps are merged into the registry and copied to shared storage with a versioned path (/bundles/{bundle_id}__{ref}__{sha}).
  4. On update, loader caches are cleared. New requests use the new path; already-running turns continue on the previously loaded path.

This means app updates are zero-downtime — running work is never affected, and new work picks up the latest version automatically.

Execution Pipeline

Once a task is claimed, execution follows a fixed sequence:

  1. Receive — claim the task from the ready queue via BRPOPLPUSH and acquire the per-task lock.
  2. Validate — materialize ChatTaskPayload, build ServiceCtx and ConversationCtx.
  3. Load app — resolve the target app through the registry, load/reuse the singleton.
  4. Execute — run the app handler under task timeout, accounting binding, lock renewal, and started-marker renewal. On ECS, scale-in protection is enabled for the duration.
  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.

On success the inflight claim is acked, conversation state moves to idle, and conv_status is emitted. On failure the conversation is set to error and the client receives a chat.error event.

State Management & Recovery

The processor distinguishes two recovery cases based on the started marker:

  • Pre-start claims (lock expired, no started marker) — safe to requeue. The inflight reaper moves the item back to the ready lane.
  • Started tasks (lock expired, started marker present) — not replayed. The conversation is set to error and the client is notified with turn_interrupted. This prevents duplicate side effects from partial execution.

The started marker intentionally outlives the claim lock so that a hard worker restart cannot accidentally let both leases expire and trigger an unsafe replay.

Continuation Mailbox

When a user sends a message to a conversation that is already executing, ingress stores it in a per-conversation ordered mailbox in Redis rather than the main ready queue. The active workflow can inspect this mailbox via the ConversationContinuationSource API (peek_next_continuation(), take_next_continuation()). If the app does not consume the item, the processor promotes exactly one pending item back into the ready queue after the current turn completes.

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
App-owned public API auth: when an app declares @api(..., route="public", public_auth="bundle"), proc does not authenticate the public request itself. It forwards the raw HTTP request into the app handler so the app can inspect request: Request and apply its own header, token, signature, or other custom verification logic.

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 is an independent queue consumer with its own Redis pool, Postgres pool, app registry, inflight reaper, and cleanup loop. All workers across all replicas compete for the same Redis task queues. There is no sticky worker-to-conversation affinity — any worker can claim any task, and conversation ownership is per-turn, not per-instance.