For the complete documentation index, see llms.txt. This page is also available as Markdown.

Sync behavior and timing

Understand how dynamic context flushes after debounce, how Wait Until Conversation Is Idle holds updates, and how offline queueing works.

Dynamic context updates do not send to Convai on every Blueprint call. By default, the plugin waits briefly so rapid changes can merge into one send, and each mutation function also carries a separate Delivery option that can hold the update for a natural pause in the conversation. This page explains when flushes happen, what each flush contains, when a held update is actually delivered, and when to bypass either wait.

When updates send

Situation
What happens

Default debounced call while connected, Delivery = Send Normally

Updates stage locally; one Replace context-update sends after ContextDebounceWindow (default 0.5 s) with no new updates in the window

Rapid burst of updates

All changes in the burst coalesce into one flush; timer resets on each call but cannot exceed ContextMaxDebounceWindow (default 3.0 s) from the first update

Delivery = Wait Until Conversation Is Idle, aggregate ShouldRespond is Auto/Always

The update is held — not staged into the debounce batch — until the conversation has stayed continuously quiet for Quiet Time Before Delivery (s) (default 2.0 s), then it flushes. See When a held update is actually delivered

Delivery = Wait Until Conversation Is Idle, aggregate ShouldRespond is Never

Holding does not apply — a silent update has nothing to interrupt with, so it sends normally through the regular debounce path

bFlushImmediately = true after connect, Delivery = Send Normally

FlushDynamicContext() runs in the current frame, bypassing debounce

bFlushImmediately = true after connect, Delivery = Wait Until Conversation Is Idle

Still waits for a quiet moment, but sends at the first quiet instant instead of waiting out the full Quiet Time Before Delivery (s) window

Updates before session connects

Changes accumulate in PendingContextBatch; first flush after connect and debounce deadline

Reset Dynamic Context

Drains staged content first, then sends a Reset context-update, then clears the local tracker

When a held update is actually delivered

Set Context State, Set Context States, and Add Context Event each expose a Delivery parameter (EConvaiContextDelivery). Debounce and delivery are two separate waits, and they apply in sequence:

  1. Debounce (ContextDebounceWindow, ContextMaxDebounceWindow) decides when a batch of staged updates is ready to leave the client.

  2. Delivery decides whether a ready update sends immediately or waits for the conversation to go quiet first.

An update with Delivery set to Wait Until Conversation Is Idle is held rather than staged into the normal debounce batch. It is released, and flushed as a Replace context-update, only after the conversation has stayed continuously quiet for Quiet Time Before Delivery (s) (the ConversationIdleSettleSeconds property, default 2.0 s). Any conversation activity — the character speaking, the user speaking — restarts that quiet window. There is deliberately no upper bound on the total hold, so a waiting update can never interrupt an ongoing exchange; it can wait for as long as the conversation lasts. A held Add Context Event lands at the end of the event history when it finally sends, so it still reads as the most recent thing that happened.

Holding only applies when the batch's aggregate ShouldRespond is Auto or Always. A silent (Never) update has nothing to interrupt with, so Delivery has no effect on it and it always sends through the regular debounce path.

bFlushImmediately = true changes what the hold waits for, not whether it holds: with Delivery set to Wait Until Conversation Is Idle, an immediate call still waits for a quiet moment, but sends as soon as that first quiet instant is detected instead of waiting out the full Quiet Time Before Delivery (s) window.

Transport message shape

UConvaiSubsystem::UpdateContext sends a context-update message with a JSON data payload:

JSON field
Values (wire format)
When present

mode

append, replace, reset

Present on every message

run_llm

"auto", "true", "false"

Present on every message — maps from EC_RunLLMOption (Auto, Always, Never)

text

Context string

Omitted on Reset when text is empty

current_attention_object

Object name string

When attention is folded into the same message

Blueprint enum display names (Append, Replace, Reset, Auto, Always, Never) differ from the wire strings above.

Flush payload structure

Each flush that includes staged states or events builds one text payload:

The first block is canonical context — states in insertion order ("{Key} is {Value}" per line) followed by events in chronological order. It is built by FConvaiDynamicContextTracker::BuildCanonicalContext().

When the batch's aggregate ShouldRespond is not Never and state delta lines exist, the plugin appends them on the next line after canonical context (one \n separator, not a blank line). Delta lines surface state changes at the tail of the prompt.

For state updates where the new value has more than three whitespace-separated words, the delta line uses "{Key} changed from {OldValue}" without repeating the long new value.

Scenarios during an active session

New state — aggregate ShouldRespond is Never

Triggering call: Set Context State or Set Context States with a key that has not been set before, and every staged item in the batch keeps aggregate ShouldRespond at Never.

The new key enters canonical immediately. No delta lines are added.

New state — aggregate ShouldRespond is Auto or Always

Triggering call: Set Context State or Set Context States with a new key, and at least one staged item raises aggregate ShouldRespond to Auto or Always.

On this flush, the new key is excluded from canonical. A delta line such as Health is 50 is appended after the canonical block.

On the next flush, the key enters canonical normally.

Update to existing state — aggregate ShouldRespond is Never

Triggering call: Set Context State with an existing key, and the batch keeps aggregate ShouldRespond at Never.

The updated value enters canonical. No delta lines are added.

Update to existing state — aggregate ShouldRespond is Auto or Always

Triggering call: Set Context State with an existing key, and at least one staged item raises aggregate ShouldRespond to Auto or Always.

The updated value enters canonical. A delta line describing the transition is appended.

Multiple states at once

Triggering call: Set Context States with a map of key-value pairs, potentially mixing new and existing keys.

All values are written to the tracker. First-appearance deferral applies per new key when aggregate is not Never. All changes share one flush — one Replace context-update.

Add context event

Triggering call: Add Context Event.

The event text is committed to the tracker's event list and appears in canonical. Regular context events are not duplicated into a separate delta line.

Identical event strings staged multiple times in the same debounce window are deduplicated — only the first occurrence is kept for that flush.

Remove context state

Triggering call: Remove Context State.

The key is removed from the tracker. bForceReplace is set so the flush sends canonical context without the removed key. No delta lines are added for removals.

Dynamic context reset

Triggering call: Reset Dynamic Context.

The plugin drains any staged context batch first, then sends a Reset context-update (mode: reset, run_llm: "false") with no text field for the empty Reset payload. The local tracker is cleared after the Reset message.

Queued triggers that already exist when Reset Dynamic Context is called are cleared immediately. Triggers queued after a pending offline Reset may still drain before the Reset during FlushDynamicContext().

When connected, Reset Dynamic Context calls the flush path immediately. When offline, the reset flag waits for the first post-connect flush.

Pre-session queueing

Default debounced mutation calls — Set Context State, Set Context States, Add Context Event, Remove Context State, and Reset Dynamic Context — accumulate in PendingContextBatch while IsChatbotConnected() is false.

TickDynamicContext() keeps accumulating until the session connects. When connected and the debounce deadline is reached, FlushDynamicContext() delivers the pending batch.

Offline updates collapse into the flush behavior described above — not replayed as a sequence of individual messages. After connection, on the first tick where the debounce deadline has elapsed, a batch whose aggregate ShouldRespond remains Never sends the current canonical snapshot from the tracker; batches with aggregate Auto or Always follow the canonical-plus-delta behavior described above. Any pending Reset clears context last.

If Reset Dynamic Context and subsequent updates are all pending offline, the flush sends staged content first, then Reset last.

bFlushImmediately

Each mutation function — Set Context State, Set Context States, Add Context Event, Remove Context State — exposes an Advanced parameter bFlushImmediately. When true, FlushDynamicContext() runs in the current frame.

Use bFlushImmediately for time-critical updates where the default debounce delay would cause perceptible lag. Each immediate call runs FlushDynamicContext() in the current frame and sends a context-update when the chatbot is connected with deliverable staged work. Avoid calling it every tick on continuously changing values.

Reset Dynamic Context calls the flush path immediately when connected. It has no bFlushImmediately parameter.

bFlushImmediately does not override Delivery. On an update with Delivery set to Wait Until Conversation Is Idle, bFlushImmediately still waits for the conversation to go quiet — see When a held update is actually delivered — it only skips the full Quiet Time Before Delivery (s) window once quiet is detected.

Next steps

Troubleshoot dynamic contextDynamic context Blueprint reference

Last updated

Was this helpful?