KDCube
← Engineering
KDCube Engineering · Experience

The Conversation Is a Lane

A conversation is an ordered event lane. One bus, two consumption models — live ReAct folding and run-to-completion turns — fenced at every seam.

2 July 2026Engineering14 minExperienceBlueprint Deck
event busevent lanereactive turnssupersessionwake vs bodysteer + followuppending lane foldforeign runtime live control

A conversation looks like a request and a reply. Underneath, accepted external events are kept in an ordered event lane. The actual lane key is the full id_card — tenant, project, user, conversation, and agent. A typed prompt, a file dropped on a widget, a webhook from a third-party app, or a mid-flight "stop" can all enter through the same external-event protocol. Once accepted into one lane, their order is the sequence assigned by that lane.

THE REFRAME

Once a conversation is a lane instead of a call, the hard engineering is not delivery — Redis can store and order records. It is coordination: who may start, who owns the lane, and how a live consumer prevents a stale owner from committing.

This article follows the common path from admission to the app's @on_reactive_event door, then separates the two processing contracts KDCube supports today. ReAct is a live consumer: it opens a handler and can fold events while the turn runs. A run-to-completion agent folds everything pending when its turn starts, then watches the lane without folding more content into the loop it owns. Content that arrives later stays pending; steer can still reach the running framework through a runtime-specific control. The same bus serves both without pretending their runtime behavior is the same. The ReAct timeline and a ported LangGraph turn are two consumer outcomes of the bus, not definitions of the bus.

EVENT BUS · ONE ID_CARD, ONE ORDERED LANEclientwidgetwebhookAPIALL AUTHORED AS external_events[]LANE L · id_carde1e2e3e4e5ORDEREDproc lock + scheduled reservationone turn per user conversation@on_reactive_event · invoked onceReAct · live consumeropens a handler · folds new eventswhile the turn runsyour run-to-completion agentfolds the whole pending lane at startwatches control; content stays pendingTWO CONSUMER OUTCOMES OF ONE BUS · NOT TWO BUSES
Fig. 1 — one id_card, one ordered lane; two consumer contracts on the far side of one door.

01 One lane per id_card

Every accepted event belongs to exactly one lane, named by a composite identity — the id_card:

lane.keyIDENTITY
id_card = tenant + project + user_id + conversation_id + agent_id

The submitting transport supplies the target agent when one is configured. For example, Telegram reads the app's surfaces.as_consumer.default_agent and puts that value on the submission and its authored events. Only a submission with no configured or explicit target falls back to default.react.agent. That last coordinate matters: two agents in the same conversation are two lanes. The lane is the unit of event ordering and lane-state coordination.

Processor execution has one deliberately broader fence: its Redis conversation lock is keyed by tenant, project, user, and conversation, without agent_id. Events for two agent lanes remain independently ordered, but processor turns for those lanes do not execute concurrently inside the same user conversation. The full id_card still scopes each lane's handler/consumer state.

Identity on the lane is deliberately layered, so ordering, occurrence, and meaning never get confused:

lane.identityLAYERS
id_card           which ordered lane + consumer scope   tenant/project/user/conv/agent
event_source_id   the semantic source + policy key
event_id          one accepted occurrence               assigned by the lane
sequence          monotonic order in the lane           assigned by Redis

For one id_card, the Redis lane sequence is the event order — full stop. The processor's ready queue is a wake-up channel and must never be treated as the ordering source. This separation is the first load-bearing idea, and the next section is its consequence.

02 The wake is not the event

Ingress does one subtle thing that shapes everything downstream: it splits "there is work" from "the work." When a client submits an external_events[] batch, ingress validates auth/session, resolves the lane identity, normalizes event ids and timestamps, then admits the batch according to its reactivity. For a reactive batch, publication of all prepared lane records and admission of one processor wake are atomic: if wake admission fails, none of that batch is made visible in the lane. A non-reactive batch is published only to the lane and does not schedule a turn.

INGRESS · THE WAKE IS NOT THE EVENTexternal_events[] batchchat-ingressvalidates · resolves id_card · normalizesREACTIVEONE ATOMIC REDIS OPERATION · ALL OR NEITHERLANE L · events + task_payloade1e2e3Q · primary wake · pointer, no bodyExternalEventLaneWakeupNON-REACTIVElane only · no wakeappended; waits for a live or future turnthe work stays in the laneprompt · attachment · steer text · domain payload,recovered from task_payload at consumptionTHE WAKE SAYS “THERE IS WORK” · THE LANE HOLDS THE WORK
Fig. 2 — the wake says "there is work"; the lane holds the work.

The wake is an ExternalEventLaneWakeup: service/routing/user context plus the lane coordinates to find the occurrence — but it intentionally contains no request body. The prompt text, the attachment, the steer text, and the domain payload are recovered later from the accepted lane event's stored task_payload.

There is one event protocol, ExternalEventPayload; for lane-backed reactive work the processor queue carries only the wake pointer. A wake can be stale by the time it is dequeued because a running owner may already have consumed past it. Keeping the body in the lane and only a pointer in the queue lets the processor decide from current lane state instead of replaying a frozen request body. Re-wakes preserve the same rule: ReAct can publish a post_save_handoff wake after its turn persists, while the shared run-to-completion door can publish a run_to_completion_handoff wake for later reactive work. That door publishes one such wake however many events are pending, and none when the last thing said was a stop: only intent after the last steer can start a turn. These handoff wakes are duplicate liveness signals; the atomic ingress wake remains primary. Finalization releases the lane consumer before it publishes a handoff wake. Neither wake copies the event body into the queue.

03 One bus, two turn-consumption models

The shared delivery path ends at one app invocation. What happens after that is a consumer contract:

ContractInput for this turnWhile the agent runsTurn-end responsibility
Reactive ReActInitial accepted lane events after the timeline cursorOpens T.handler, marks the consumer active, and drains/folds new events through ContextBrowser at safe boundariesCloses only after the model render has covered accepted events; stops the live reader; persists; releases the consumer; then may publish a liveness wake
Run-to-completionEverything still pending at turn start, in sequence orderOwns execute_core; the shared watcher refreshes its turn-owned reservation and routes steer without folding contentAccounts for the exact start fold, releases scheduled, terminalizes a bare stop, and emits at most one wake for eligible post-steer intent

The ported LangGraph app is the concrete run-to-completion implementation. The processor rehydrates the triggering accepted event from the wake. Because one user submission can store its prompt and attachments as separate lane records, the app's bundle-local fold_turn_external_events(...) adapter performs a read-only fold of the whole pending lane:

pending_lane.foldJOURNEY
wake event
  → read the conversation lane
  → select every occurrence still pending (not consumed/promoted/failed)
  → sort by lane sequence; retain batch, sequence, and arrival stamps
  → pass accepted event bodies to the selected LangGraph agent

That fold happens once, before graph input is built. It does not open a handler or mark lane events consumed. Once the graph starts, the shared watcher keeps its scheduled reservation fresh and observes later arrivals without putting content into graph state. A steer cancels the LangGraph stream; the checkpointer retains the last completed node. If the lane lookup fails, the adapter fails open to the triggering events already dispatched in state.

The shared door's lane finalizer is state-conditional, idempotent, and best-effort. It runs after success or error and is skipped when the processor task is cancelled; cancellation leaves the claimed task on the normal inflight recovery path. It accounts for the exact start fold, releases the consumer, terminalizes a bare stop, and emits at most one duplicate wake for eligible work after the last steer. Earlier events remain pending without starting a turn. ReAct has already accounted for its live lane state, so the same finalizer becomes a no-op without an if ReAct branch. This is one bus with two consumption models, not a ReAct bus plus a separate LangGraph queue.

04 Scheduled reservation vs live ReAct ownership

The shared state table T contains fields used by both delivery models and fields used only when a runtime consumes the lane live. The word consumer in T.consumer_status is coordination state; it does not always mean a running reader:

lane.stateTABLE T
scheduled   proc reserved one app start; a foreign turn may heartbeat it
active      a live ReAct ContextBrowser reader is acknowledging liveness
none        no turn currently holds the lane consumer reservation

handler     the ReAct turn allowed to accept events into its live timeline

A run-to-completion turn remains scheduled while its graph runs and never opens T.handler. Proc records its consumer_turn_id; the read-only watcher refreshes consumer_status_at only while that owner matches, and the shared app door releases the reservation afterward. ReAct advances to active, opens a handler claim — "turn A owns live folding for this lane" — and maintains the additional ownership fences:

ownership.fieldsTABLE T
T.handler_turn_id
T.handler_status                 open | closed
T.handler_status_at

T.consumer_turn_id
T.consumer_status                active | scheduled | none
T.consumer_status_at

T.last_processed_event_timestamp
T.last_processed_event_id
T.last_processed_reactive_event_timestamp

On that live ReAct path, handler_status_at is not liveness. It only records when handler state was last written. A handler can read open forever after its worker has died. The real liveness signal is a fresh consumer_status_at — a heartbeat the active reader refreshes only while it still matches the current handler. T.last_processed_event_id is paired with the processed timestamp so equal-timestamp events can be distinguished. Liveness lives with the consumer, ownership lives with the handler, and the whole recovery protocol turns on not mistaking one for the other. On the foreign path, a fresh scheduled timestamp proves only that the named turn still holds its reservation; it never grants live-folding authority.

The live ReAct ContextBrowser reader also holds a separate token-fenced Redis event-source owner lease. It acquires the lease when the listener starts, refreshes it while reading, and releases it when the listener stops. That lease fences the actual reader; T.handler_turn_id and the fresh active consumer heartbeat remain the turn-ownership and reclaim protocol. They are related signals, not interchangeable fields.

The lease token fences one listener incarnation, not merely a turn label. A refresh is valid only for the token Redis currently owns. Another token means owner replacement even when both records name the same turn_id; an old listener cannot authorize itself by matching the turn id.

One ReAct turn also has two legitimate local paths that may request the listener: hook registration schedules a start, while the active phase watcher ensures that the listener is running. ContextBrowser serializes those local start/stop requests so they converge on one listener and one lease. Terminal cleanup disables an already-scheduled late start. This prevents one turn from competing with itself without weakening the distributed Redis token fence.

listener.incarnationFENCE
local start requests
  → serialized by one ContextBrowser
  → one lease token

same turn_id + current token
  → refresh allowed

same turn_id + different token
  → owner replaced; old listener stops
liveness.rulesPROTOCOL
fresh active + handler open   →  do NOT steal the handler; a live owner is folding
fresh active + handler closed →  recover; no closed handler can consume the event
stale/missing                 →  reclaim the lane for a new turn
fresh scheduled + matching owner      →  don’t start a duplicate wake; no live fold is implied

05 A conversation lock, then four wake decisions

After claiming a queue item, proc first acquires the broader per-conversation execution lock. If another turn for that user conversation holds it, proc requeues the item; it does not run a second execute_core. Once it owns that lock, proc resolves the wake back to the retained lane occurrence, consults T, and picks one of four conceptual outcomes.

PROC · FOUR WAKE DECISIONSproc holds the conversation lockresolves the wake to the lane occurrencealready consumed, promoted, failed, or cursor-covered?YESIGNOREthe wake is obsolete(a stale pointer)NO → how fresh is the consumer?consumer status + freshness + handler gateACTIVE · FRESH · HANDLER OPENDEFER to the active owneran open live consumer is folding;starting a turn would competeSCHEDULED · FRESHDEFER the duplicate wakea starter was just reserved;no second starter during loadNONE / STALE / TERMINAL ACTIVESCHEDULE a turnT.consumer = scheduled,then load the app → your turn startsIGNORE = OBSOLETE · DEFER = VALID, BUT ALREADY COVERED
Fig. 3 — ignore, defer to active, defer duplicate, or schedule.
THE DISTINCTION

"Ignore" means the wake is obsolete; "defer" means the wake is still valid but someone already has it covered.

The state decision itself uses a short Redis lock. Its value records the holder's operation, process, task, and acquisition time, so a timeout identifies the contended phase without exposing the ownership token. Acquisition, state write, and release complete to a known outcome under cancellation; uncertain ownership is removed by exact token. If proc still times out before app execution, it requeues the claimed wake instead of acknowledging valid lane work as malformed.

06 The shared door, then the ReAct fence chain

Between "proc decided to run a turn" and "the turn committed an answer" lies a chain of runtime fences. The common rows run through the app door; after that, the two contracts diverge.

FenceOwnsGuards
Client submitbrowser / widget / webhook / APICaller's target turn is intent, not authority
Ingress admissionchat-ingressAtomic lane + queue for reactive; lane only otherwise
Wake schedulingproc worker"There is work" → "may I start?" — the body stays in the lane
Conversation executionproc workerOne task per user conversation; requeue on lock contention
Lane ownership decisionproc workerIgnore / defer / schedule
App load / entrypointproc (today)Invoke @on_reactive_event once; T.consumer = scheduled
Reactive ReAct
Handler openReAct runtimeT.handler_turn_id = me, or I'm superseded
Listener lifecycleContextBrowserHook + phase requests converge on one token-fenced lease
Consumer acknowledgementContextBrowser readerRefresh owner lease + active heartbeat
Lane read + block productionlistener / phase watcherapply_live_external_events → owner-fenced accept
Handler closeContextBrowserClose gate stops the live reader immediately
Turn commitfinish_turnOnly a non-superseded handler may persist
Turn finalizationfinish_turnRelease consumer; then optional liveness wake
Run to completion
Pending-lane mappingapp adapterWhole pending lane, sequence ordered and stamped
Live watchshared foreign-runtime seamRefresh owned reservation; route steer; never fold content
Graph / loop executionexecute_coreThe framework owns its loop
Door finalizationBaseEntrypoint.runAccount exact fold; release; at most one post-steer wake
THE FENCE CHAIN · COMMON DOOR, TWO TAILSclient submittarget turn is intent, not authorityingress admissionatomic lane + queue for reactivewake scheduling“there is work” → “may I start?”conversation executionone task per user conversationlane ownership decisionignore / defer / scheduleapp load / entrypoint@on_reactive_event once · T.consumer = scheduledREACTIVE REACTRUN TO COMPLETIONhandler open · T.handler_turn_id = mehook + phase → one listener · token + heartbeatowner-fenced fold · guarded answer commitclose stops reader · persist · release · liveness wakewhole pending lane · sequence orderedexecute_core · read-only live control watchdoor finalization · exact account · release · one wakeEVERY FENCE OWNS ONE QUESTION
Fig. 4 — common fences through one door; the chain forks by consumption model.

The app-load fence is the one people forget. Proc has decided a turn should run, but the lane is not consumed yet — the app instance still has to resolve and its @on_reactive_event entrypoint has to start the runtime. For a non-singleton app this can take observable time. The entrypoint is invoked once for that scheduled proc task. After it starts, ReAct may read later events inside the same turn; a run-to-completion app does not fold them, but its watcher can route control. The state table first records T.consumer = scheduled to stop a duplicate starter during load; the matching watcher then keeps that owned reservation fresh for the life of the foreign turn while explicitly not claiming live-folding authority. (@on_reactive_event is the runtime decorator; the manifest metadata field is still named on_message/OnMessageSpec.)

07 Live ReAct supersession: a stale handler never commits

The live ReAct fences exist for one worst case: a turn goes quiet long enough that its consumer heartbeat goes stale, a second turn reclaims the lane, and then the first turn wakes back up. Without a rule, both would try to answer.

THE RULE

A lane event is folded, and an answer is committed, only for the turn that still owns the lane.

SUPERSESSION · A STALE HANDLER NEVER COMMITSturn A opens the laneT.handler_turn_id = Aconsumer_status_at = freshturn B reclaimsT.handler_turn_id = BA’s heartbeat went stale… A STALLS / WORKER PAUSE …A resumes → reaches an owner fencesees owner = Bsuperseded errorExternalEventLaneTurnSuperseded→ standard turn error cleanupphase watcher pathcancel active phase → close handler→ stop listener → release leaseNO STALE ANSWER COMMITturn B answersthe lane’s true owner folds and commitsOWNERSHIP RECHECKED AT EVERY FENCE WHERE STALE WORK WOULD MATTER
Fig. 5 — turn B reclaims; turn A discovers it at the next fence and never commits.

ContextBrowser rechecks ownership at every moment where stale work would matter — handler open, consumer ack, each event fold via accept_events_for_open_handler(...), and once more at finish_turn before the answer is emitted. Both the background listener and the direct ReAct decision/tool-phase watcher send events through ContextBrowser.apply_live_external_events(), which reaches that same owner-fenced accept operation. There is no raw unfenced fold fallback.

The token fence catches a narrower replacement too: if the stored owner token changes while the diagnostic turn_id stays the same, the old listener still stops. Turn ownership and listener-incarnation ownership are checked together; neither identifier substitutes for the other.

On mismatch, handler-open, fold, and finish boundaries raise ExternalEventLaneTurnSuperseded into standard turn error cleanup. If the direct phase watcher detects the mismatch, it cancels the active phase; the outer ReAct run then closes the event handler, stops the listener, and releases its owner lease before propagating cancellation. Processor shutdown and watchdog cancellation use the same close-before-propagate discipline. The cleanup route is therefore not one universal delete_turn(...) call, but every route enforces the same invariant: the stale turn cannot commit an answer or become the conversation head.

This is specifically the ReAct live-handler guarantee. A run-to-completion graph never opens that handler and therefore does not claim these ReAct supersession fences. Its turns are serialized by the processor conversation lock; its turn-owned scheduled heartbeat prevents a second starter; runtime-specific control can stop the work; and task/inflight recovery remains the crash path.

08 The bus event is not the timeline block

Here the design makes its second sharp cut. Arriving on the bus and appearing on the ReAct timeline are separate facts. In the current ContextBrowser ingest path, block production runs first. Its result controls both timeline contribution and the later optional workflow/ReAct hook.

THE BUS EVENT IS NOT THE TIMELINE BLOCKaccepted lane eventblock-production policyper event sourceZERO BLOCKS / no_timelineconsumed, invisiblyadvance cursor · mark consumedno timeline block · no generic hookONE OR MORE BLOCKSthe model’s viewblocks join the timeline · hooks fire where enabled;projection · announce · compaction see thembusiness ops live in the producing serviceshare refs (conv:fi:) — react.read / react.pull on demandARRIVING ON THE BUS AND APPEARING ON THE TIMELINE ARE SEPARATE FACTS
Fig. 6 — block production is the gate between the bus and the model's view.

A source can bind react.block_production.no_timeline; the occurrence then advances the processed cursor and is marked consumed without creating a durable ReAct block. This is a visibility choice, not a promise that a generic callback persisted product state. Any business operation that must always happen belongs in the producing service or in explicit source-owned processing, not in an assumed unconditional ReAct callback. Conversely, when a policy does share file material, it can share only refs such as conv:fi: paths or owner-namespace refs and let ReAct use the supported react.read / react.pull path on demand. The file body does not have to be inlined into the event.

A run-to-completion agent does not automatically pass through this ReAct block policy. It maps the pending lane at turn start into its own framework input. The ported LangGraph app, for example, frames prompt text and attachment refs for the graph; afterward the platform records a framework-neutral minimal turn log. Neither step creates a ReAct timeline.

09 Kind is transport, type is meaning

One more separation, and it is the one that most recently bit us in production. Every lane event carries two names: an operational lane kind and a semantic event type. The kind is a scheduling label. The type is the meaning.

kind.vs.typeNAMES
lane kind     message · followup · steer · external_event      operational / scheduling
event type    event.user.prompt · event.user.followup ·        semantic (what it MEANS)
              event.user.steer · event.external · event.canvas · event.snapshot

The catch: in-flight events submitted through the plural external_events[] batch all arrive with lane kind = external_event. The real type (event.user.steer, event.user.followup) lives only nested inside payload.event.type. A live consumer that branches on the kind would see one undifferentiated external_event for everything — and a live "stop" would be silently dropped.

So live consumers must recover the semantic type before deciding anything. The ReAct runtime does exactly this — on_external_event calls live_events.recover_semantic_event_type(...) to dig the real type out of the nested payload before it decides steer-interrupt vs iteration-credit. Key off the transport label and you drop the user's intent; key off the recovered type and the bus stays honest.

The run-to-completion start fold contains the accepted event bodies, including their semantic type; its adapter likewise reasons from that type, not from the lane's operational kind.

10 Followup and steer depend on the consumption model

Because control rides the same lane as content, the two live control events are worth their own picture. The behavior below belongs to an agent that declares and implements live followup/steer consumption — today, the ReAct path:

followup.vs.steerCONTROL
A FOLLOWUP continues the turn          A STEER stops the current work
  event.user.followup, continuation      event.user.steer, continuation
  → may add bounded iteration credit     → requests cancellation of the active phase
  → the turn keeps working, fresh        → the turn enters a bounded finalize

A followup is new work. When live reactive iteration credit is enabled, an eligible current-turn followup can add credit once per event, bounded by the configured per-event value and total cap. A steer is control: an empty steer with explicit is a pure stop; a steer carrying text is a redirect. The runtime requests cancellation of the active decision/tool phase and enters a bounded finalize. Isolated tool execution performs its own cleanup when cancellation reaches it; the protocol does not promise that every possible tool process is instantaneously killed.

Ingress treats event.user.steer as active-turn control before it reaches either consumer model. It accepts the control only while the conversation is in_progress, stamps the server-observed active turn id, and acknowledges an idle or stale-target steer as a no-op. The event remains reactive so it can wake the live lane, but it is not promotable: proc cannot start a turn from it. A bare steer is terminalized once its target turn ends. A steer carrying text stays pending for a future start fold but cannot start that turn by itself. A later owner rejects control fenced to an older turn. Telegram /stop uses this exact event and fence; it has no transport-specific stop queue.

The subtlety the bus forces on us: a steer only stops the current work — a followup is a valid way to continue the turn after a steer. So a queued followup must supersede the steer's bounded finalize, regardless of whether it arrived before or after the steer, and let its generation run at full budget — while a bare steer with nothing queued still gets its short wrap-up. Runtime target checks ignore steer/followup control stamped for another active/owner turn. If reactive work remains unconsumed when a turn closes, the post-save handoff may enqueue a duplicate liveness wake after the consumer is released; the atomic ingress wake remains primary. The current production path does not use a separate generic "promotion" stage on the ReAct path. Run-to-completion uses the shared door finalizer described above.

The ported LangGraph agents declare accepts_followup: false and accepts_steer: true. They do not absorb content into a graph already running; the watcher instead cancels the graph stream on steer. The checkpointer retains the last completed node, and the adapter repairs a dangling tool-call boundary before later model input. Pending content is folded together when an eligible event starts one later serialized turn. There is still no ReAct-style iteration credit.

11 Why a bus, and not a queue

Every separation in this design pulls apart two things a naive message queue would fuse. Laid side by side:

The bus keeps apart…so thatA plain queue would fuse them into
Wake vs event bodythe processor decides on current lane state, not a frozen requestreplaying a stale request body
Agent lane vs conversation lockeach agent has independent event order while execution in one user conversation stays serializedevent identity and execution exclusion becoming one accidental key
Scheduled reservation vs active ReAct readerapp load is protected without pretending a live reader existsa start timestamp being mistaken for runtime liveness
ReAct handler vs active heartbeata dead live consumer's open handler can be safely reclaimedownership that never recovers
sequence (order) vs queue (schedule)order is authoritative even as wakes racethe queue accidentally defining order
Pending-lane start fold vs runtime consumptionReAct may fold live while a ported graph freezes one ordered view at start, then watches only for controlevery framework being forced into fake live reactivity
Bus event vs ReAct timeline blocka widget save can be bus-only; a prompt can become model-visibleevery event polluting model context
Lane kind vs event typea live "stop" is recovered, never droppedcontrol lost behind a transport label
ReAct fold vs ReAct commita superseded live handler is discarded before it can answertwo live handlers racing to commit

Read down the middle column: each row is a failure the naive design ships and the bus refuses. That is the through-line — the event bus is not a delivery mechanism with features bolted on; it is a set of deliberate separations, each earning its keep against a specific way conversations break when turns overlap.

12 Where this is going

The current implementation runs both ReAct and the ported LangGraph app inside chat-proc. That is an implementation detail, not a semantic requirement. A remote app runner would still need the common contract: accepted-event storage, wake scheduling, conversation serialization, start reservation, and turn-end release/handoff. A runtime that opts into live consumption additionally needs the handler, heartbeat, owner lease, ordered live read, and superseded-turn rollback fences. The contract follows the consumption model; it is not inferred from where the code runs.

Two gaps remain explicit. Non-reactive idle events are retained today only in the Redis lane, with a bounded window (CHAT_EXTERNAL_EVENTS_STREAM_MAX_ENTRIES, default 1024; CHAT_EXTERNAL_EVENTS_STREAM_RETENTION_SECONDS, default 7 days) — operational state, not durable business history. Durable idle event-history materialization is pending, and the goal is to stop pretending Redis retention is permanent conversation history. And the conversation-native scheduler — folding out-of-turn non-reactive events into a durable timeline without waiting for the next turn — is still design-only.

The reframe holds through all of it: one conversation/agent id_card is one ordered lane; proc starts turns through a broader per-conversation execution fence; and each app declares whether its turn is a live lane consumer or a run-to-completion consumer that folds the pending lane once and watches without folding afterward. Redis is the current implementation. The boundaries are the design.

· Read more

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