How FreeLLMAPI Manages Context Handoff When Switching Models Mid-Conversation
FreeLLMAPI maintains a lightweight per-session store that records recent conversation turns and the previous model identifier, automatically injecting a summary system message when FREELLMAPI_CONTEXT_HANDOFF=on_model_switch detects a provider change.
FreeLLMAPI is an open-source LLM gateway that enables dynamic routing between multiple AI providers without losing conversational continuity. When users switch models mid-conversation—transitioning from GPT-4 to Claude or any other supported backend—the system must ensure the new model comprehends the prior context without requiring client-side state management. The repository solves this through a server-side context handoff mechanism implemented in server/src/services/context-handoff.ts.
The Context Handoff Architecture
The implementation relies on a stateless API surface with bounded server-side storage. Rather than forcing clients to resubmit entire conversation histories or maintain complex state, FreeLLMAPI tracks three critical pieces of data per session: the last 12 conversation turns, the timestamp of the last activity, and the identifier of the model that processed the previous request. This design ensures that context handoff when switching models mid-conversation happens transparently, with the injected system message providing the new model with a concise summary of the discussion so far.
Step-by-Step Implementation
The context handoff logic proceeds through three distinct phases, each handled by specific functions in the handoff service.
1. Recording Incoming Messages
When a request arrives at the proxy handler (server/src/routes/proxy.ts), the system immediately calls recordIncomingMessages(sessionKey, messages) to update the session store. This function performs aggressive trimming to control memory usage:
- Retains only the last 12 turns of the conversation
- Limits each message to 500 characters
- Clears the
lastModelKeyfield if the payload contains no assistant turns (indicating a fresh conversation)
This ensures the store never grows unbounded while preserving the most relevant recent context.
2. Tracking the Active Model
After the router (server/src/services/router.ts) successfully receives a response from the selected provider, it invokes recordSuccessfulModel({sessionKey, modelKey}). This writes the provider identifier (e.g., "gpt-4" or "claude-2") into the session entry, enabling the next request to detect whether a different model has been selected.
3. Injecting the Handoff Message
Before routing a request, the system calls maybeInjectContextHandoff({mode, sessionKey, messages, selectedModelKey}). If the environment variable FREELLMAPI_CONTEXT_HANDOFF is set to "on_model_switch" and the stored lastModelKey differs from the newly selected model, the function:
- Generates a summary via
buildSummary(), capped at 6,000 characters - Constructs a system message with the template:
FreeLLMAPI context handoff: You are taking over an ongoing conversation from another model (<prev> → <new>)... - Inserts the message immediately after any existing system messages, preserving provider-specific prompt ordering
- Returns the estimated token count (
injectedTokens) alongside the modified message array
The function also checks for duplicate handoff messages (alreadyPresent) to prevent redundant injection if the user manually included a similar instruction.
Configuration and Environment Variables
The entire handoff mechanism is gated by the getContextHandoffMode() function, which reads the environment at runtime:
export function getContextHandoffMode(): ContextHandoffMode {
const raw = process.env.FREELLMAPI_CONTEXT_HANDOFF?.trim().toLowerCase();
return raw === 'on_model_switch' ? 'on_model_switch' : 'off';
}
When the mode is "off" (the default), the system skips injection entirely and passes requests through unchanged. To enable automatic context handoff when switching models mid-conversation, set:
FREELLMAPI_CONTEXT_HANDOFF=on_model_switch
Integration Points in the Request Lifecycle
The handoff service integrates at three critical points in the request pipeline:
- Entry point: The proxy handler (
server/src/routes/proxy.ts) callsrecordIncomingMessagesimmediately upon receiving the user payload, ensuring the store stays current regardless of routing decisions - Pre-routing: The router (
server/src/services/router.ts) invokesmaybeInjectContextHandoffbefore forwarding requests to the selected provider, allowing the injected summary to reach the new model - Post-response: The router calls
recordSuccessfulModelafter receiving a successful provider response, updating the session with the model that just completed the turn
Safety Mechanisms and Token Budgeting
FreeLLMAPI implements several safeguards to prevent the handoff mechanism from overwhelming models or exhausting server resources:
- Token budgeting: The exported constant
HANDOFF_MAX_TOKENS(approximately 1,600 tokens) is added to the router's token estimate, ensuring the injected summary never pushes the request beyond the model's context window - Memory bounds: The session store caps at 500 active sessions and evicts entries after a 3-hour TTL, preventing unbounded growth
- Deduplication: The injection logic detects existing handoff messages to avoid duplicate context summaries
Code Examples
Enabling Context Handoff
Set the environment variable before starting the server:
# .env
FREELLMAPI_CONTEXT_HANDOFF=on_model_switch
Recording User Turns
Call this when handling incoming chat requests:
import { recordIncomingMessages } from '@freellmapi/server/src/services/context-handoff';
// sessionKey derived from cookie or Authorization header
recordIncomingMessages(sessionKey, incomingChatMessages);
Checking and Injecting Context
Use this in your request router before forwarding to the provider:
import { maybeInjectContextHandoff, getContextHandoffMode } from '@freellmapi/server/src/services/context-handoff';
const mode = getContextHandoffMode();
const { messages, injected, injectedTokens } = maybeInjectContextHandoff({
mode,
sessionKey,
messages: originalMessages,
selectedModelKey: providerModelKey, // e.g., "gpt-4", "claude-2"
});
Updating the Model Tracker
Call after receiving a successful response:
import { recordSuccessfulModel } from '@freellmapi/server/src/services/context-handoff';
recordSuccessfulModel({ sessionKey, modelKey: providerModelKey });
Reserving Token Budget
Account for handoff overhead in your token calculations:
import { HANDOFF_MAX_TOKENS } from '@freellmapi/server/src/services/context-handoff';
const tokenBudget = modelContextWindow - requestTokens - HANDOFF_MAX_TOKENS;
Summary
- FreeLLMAPI manages context handoff when switching models mid-conversation through a server-side session store that tracks the last 12 turns and the previous model identifier
- The
recordIncomingMessagesfunction inserver/src/services/context-handoff.tsmaintains the conversation buffer with strict limits (12 turns, 500 chars per message) maybeInjectContextHandoffautomatically inserts a summary system message when it detects a model change and theFREELLMAPI_CONTEXT_HANDOFFenvironment variable is set to"on_model_switch"- The
HANDOFF_MAX_TOKENSconstant (~1,600 tokens) ensures the injected summary never exceeds the model's context window - The store implements TTL-based eviction (3 hours) and caps at 500 sessions to prevent memory leaks
Frequently Asked Questions
What happens if context handoff is disabled?
When FREELLMAPI_CONTEXT_HANDOFF is unset or set to "off", the getContextHandoffMode() function returns "off" and maybeInjectContextHandoff returns the original messages unchanged. The system passes requests directly to the new model without injecting summary context, meaning the model receives only the conversation history explicitly sent by the client.
How does FreeLLMAPI prevent the handoff message from exceeding token limits?
The system uses the exported constant HANDOFF_MAX_TOKENS (approximately 1,600 tokens) as a safety margin. The router subtracts this value from the available context window before checking if the request fits within the model's limits. Additionally, the buildSummary function caps the generated summary at 6,000 characters, and individual stored messages are truncated to 500 characters each.
Can the context handoff message template be customized?
Currently, the handoff message template is hardcoded within the maybeInjectContextHandoff function in server/src/services/context-handoff.ts. The template includes the previous and new model names, specific instructions for the incoming model, and the recent session summary. Users cannot modify this template via configuration files; customization would require editing the source code and rebuilding the service.
Where is the conversation history stored during a session?
The conversation history resides in a server-side in-memory store maintained by the context handoff service. This store caps at 500 concurrent sessions and automatically evicts entries after 3 hours of inactivity. The client never needs to maintain this state, enabling truly stateless API interactions while preserving continuity across model switches.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →