06.4: Post-Turn Effects
This content is not available in your language yet.
The post-turn stage executes side effects after generation finishes. It coordinates empty-response retries, text quota settlement, self-reply tracking, short-term memory caching, thought logging, cross-channel boomerangs, and usage statistics.
Flow and ownership
Section titled “Flow and ownership”runPostTurnEffects() in src/utils/chat/postTurnEffects.ts runs after runGenerationTurn() settles:
ChatTurnContext & GenerationTurnResult │ ├─► 1. recordReunionPresence() ──► commits or releases triggerer presence claim ├─► 2. maybeScheduleEmptyResponseRetry() ──► re-enters tomoriChat(skipLock=true) on empty output ├─► 3. settleTextQuota() ──► keeps the reservation for a reply or releases it ├─► 4. updateSelfReplyBookkeeping() ──► updates last-responded persona and chain counters ├─► 5. writeShortTermMemory() ──► stores conversation turn in STM cache per persona ├─► 6. emitThoughtLog() ──► sends reasoning or BYOK attribution embeds ├─► 7. scheduleBoomerangFollowUp() ──► schedules cross-channel re-entry via setImmediate ├─► 8. rememberLastReplyUsage() ──► caches prompt token usage for /context inspection └─► 9. recordUsageStats() ──► fire-and-forget dispatch of turn & token metricsSide effect sequence
Section titled “Side effect sequence”- Reunion presence:
recordReunionPresence()commits or releases the direct-triggerer presence claim before any retry can rebuild context. - Empty-response retry: if
result.status === "empty_response"andretryCount < MAX_EMPTY_RESPONSE_RETRIES(2):- Waits
EMPTY_RESPONSE_RETRY_DELAY_MS(1000ms). - If caused by a speaker guard trigger or foreign speaker label leak, it prepends a speaker-guard directive
via
buildSpeakerGuardRetryDirective(). - Re-enters
tomoriChat()withskipLock=true, incrementedretryCount, pinned persona, and carried expression delivery receipts so already-posted stickers or reactions are not duplicated. - Upon retry exhaustion, deliberate turns post a warning embed while passive autochat turns stay silent.
- Waits
- Text quota settlement:
settleTextQuota()checks whether text quota was reserved during admission and the response was non-empty. A reply keeps the reserved unit as the charge and marks the trigger key consumed intextQuotaTriggerStates. The last turn of a plan releases an unspent reservation throughreleaseTextQuotaForTrigger(). A lone personal-route turn keeps it, because a queued server persona job from the same trigger may still spend it. - Self-reply bookkeeping: on non-empty responses,
setLastRespondedPersona()records which persona spoke last. For non-stop real user messages, it incrementsselfReplyChainState.triggerCount. - Short-term memory write: for non-stop responses where the user is not full-privacy,
writeShortTermMemory()saves conversation turns to cache viastoreShortTermMemory()for each responding persona. It then increments the cadence counter viaincrementStmTurnCounter(), driving proactive memory refresh nudges in future turns. - Thought log emission: if a thought-log channel is configured in a guild text channel,
emitThoughtLog()sends reasoning and duration details viasendThoughtLogEmbed(). When personal BYOK or the model randomizer was used, the embed names the user’s provider or the model that answered, and a turn without reasoning still gets a standalone attribution embed. Like tool notices and image diagnostics, it copies only whencanMirrorToThoughtLog()confirms the log is in the same guild and everyone there can already read the source channel (or a public thread’s parent) with noViewChanneldeny overwrite. The bot cannot list who reads the log, so a private thread, a private-listed channel, or an undeterminable audience is never mirrored. Each refusal logs its reason, at warn level only for an undeterminable audience. - Boomerang follow-up: if the turn invoked the cross-channel message tool,
scheduleBoomerangFollowUp()consumes the pending boomerang. It schedules a re-entry in the source channel viasetImmediate, retaining the original invoker for human requests. Cross-channel dispatches and thread-creation reports preserve the source turn’sborrowsTriggerMessageflag. Borrowed system turns carry no human invoker, so an anchor author’s blacklist, role whitelist, and personal credentials do not govern generation in either channel.suppressNextSelfReply()suppresses the follow-up’s gateway echo. - Last reply token cache:
rememberLastReplyUsage()caches the prompt token count inlastReplyUsageCacheso the/contextcommand can display prompt token consumption. - Usage statistics:
recordUsageStats()dispatches fire-and-forget database writes recording model usage, token counts, and delivery-gated expression stats (emojis and sprites).
Constraints and rationale
Section titled “Constraints and rationale”- Non-blocking failure tolerance: errors in secondary side effects (such as memory writes, thought logs, or usage stats) are caught and logged, so completed replies are never disrupted by cache or logging errors.
- Single quota settlement: a trigger sequence reserves at most one text quota unit and settles it once, keeping the charge only for a successful, non-empty response.
- Coordinated re-entrancy: recursive calls explicitly declare lock behavior: empty-response retries reuse
the active lock (
skipLock=true), whereas cross-channel boomerangs defer execution until after lock release viasetImmediate.
Response review accounting
Section titled “Response review accounting”With Response Drafting On, GenerationTurnResult.usageEntries carries actual author and reviewer
usage across failed attempts, discarded drafts and cancellation. recordUsageStats drains
that ledger before requiring delivered dialogue and retains a recorder for late usage. Late verdicts
cannot authorize delivery. Missing usage stays unknown; discarded draft length supplies no estimate.
Reviewer counters are a subset of total tokens, so cost attribution does not bill them twice. DMs
retain their exclusion from persistent guild telemetry. Off keeps ordinary usage accounting.
Only Discord-accepted presentation populates personaResponses for memory and reply quotas.
Superseded drafts and held narration consume neither. Expression receipts and tool telemetry still
describe their actual effects independently of prose review.
Source pointers
Section titled “Source pointers”src/utils/chat/postTurnEffects.ts:runPostTurnEffects()and side effect helpers.src/utils/cache/shortTermMemoryCache.ts: short-term memory caching and cadence tracking.src/utils/quota/textQuotaManager.ts: text quota reservation and release.src/utils/chat/textQuotaState.ts: per-trigger reservation state and release helper.src/utils/discord/thoughtLog.ts: thought-log and provider attribution embeds.