Long-running agents have a boring failure mode: they accumulate conversation until they hit the model's context window and fall over. xAI ships a fix for Grok 4.5 — a context-compaction endpoint — but you have to call it yourself, on the right cadence, and feed the result back in a very specific way.
grok-loop-kit does that for you.
npm install grok-loop-kit # Node (CJS + ESM + types)
pip install grok-loop-kit # Python
The loop
import { GrokLoopClient } from 'grok-loop-kit';
const client = new GrokLoopClient(process.env.XAI_API_KEY!, {
compactEvery: 8, // compact every 8th turn...
compactAtTokens: 8000, // ...or when the rendered context crosses this
});
for (const turn of userTurns) {
const res = await client.sendMessage(turn, tools);
// res._grokLoopKit -> { turnsSinceCompact, estimatedTokensSaved, totalCompactions }
}
Every sendMessage appends the user turn, calls POST /v1/responses, and — when the turn count hits a boundary or the rendered context crosses your token budget — calls POST /v1/responses/compact and rebuilds the transcript from the result.
The gotcha that actually matters
xAI's compaction endpoint returns an item you must feed back into the next request's input verbatim:
{ "type": "compaction", "id": "cmp_…", "encrypted_content": "…" }
Not as a summarized user message — the exact item, unmodified. Get this wrong and you either lose context or corrupt the record. grok-loop-kit handles it (and still degrades gracefully against gateways/mocks that return a plain string).
Does compaction actually preserve information, or does it quietly forget? I tested it adversarially: plant five unguessable vault codes early, run enough turns to force four real compactions (dropping ~10 messages each), then demand exact recall.
✅ 5/5 exact codes recalled after 4 compactions
Since the codes are random, the model can only answer if the compacted encrypted_content genuinely carried them forward. It did.
Native LangGraph
GrokLoopChatModel is a real BaseChatModel, so it drops straight into a LangGraph agent:
import { GrokLoopChatModel } from 'grok-loop-kit/langgraph';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
const model = new GrokLoopChatModel({
apiKey: process.env.XAI_API_KEY,
grokLoop: { compactEvery: 8, compactAtTokens: 120_000 },
});
const agent = createReactAgent({ llm: model, tools: [getWeather] });
const out = await agent.invoke({ messages: [new HumanMessage('weather in Tokyo?')] });
Because LangChain invokes a model with the full history each turn, the adapter compacts that history (a pure function of the incoming messages) rather than keeping a parallel transcript. Tool calls are parsed onto AIMessage.tool_calls. It's verified end-to-end against a live createReactAgent tool loop.
(One 1.0 lesson: extend BaseChatModel, not ChatOpenAI — on LangChain 1.x, ChatOpenAI.withConfig/bindTools clone into a plain ChatOpenAI and silently drop a subclass's _generate.)
Also in 1.0
- Streaming (SSE) with a token callback
- Retries with exponential backoff, honoring
Retry-After - Per-call
AbortSignal+ request timeout -
getState()/loadState()for durable, resumable agents - An
AsyncGrokLoopClientfor asyncio - 25 Node + 16 Python tests; MIT licensed
When does compaction help?
Compaction replaces the transcript with one dense record. That's a win once the raw transcript outweighs the record — i.e. long conversations. With tiny messages the record can cost more than it saves, so keep compactAtTokens realistic (thousands, not hundreds). It's a large-context optimization, and it keeps your agent inside the window indefinitely.
Repo: https://github.com/Booyaka101/grok-loop-kit · npm i grok-loop-kit
Built with the xAI context-compaction docs.
Top comments (0)