∞
infiniteDeploy.com
LLM ArchitectureAgentic AIContext ManagementLLMOps

How LLMs Work Under the Hood: The Autocomplete Trap

infiniteDeploy Architecture Team·2026-10-14· 7 min read
Technical architectural diagram showing LLM next-token prediction and stateless memory rebuild

The Illusion of Intelligence

When a chatbot answers a question fluently, it feels like talking to something that understands you. Architecturally, it is far simpler and far stranger: a large language model (LLM) is a very sophisticated autocomplete engine. It reads a sequence of text and predicts what comes next, one small piece at a time.

Understanding this single fact separates engineers who build fragile AI demos from engineers who ship systems that scale.

What an LLM Actually Returns

Tokens, Not Words

An LLM never sees raw sentences. Text is split into tokens, small chunks of roughly 3-4 characters. The model maps each token to a vector and runs it through layers of self-attention.

A Probability Distribution

The output of a single inference step is not an answer. It is a probability distribution over the entire vocabulary:

P(t_{n+1} \mid t_1, t_2, \ldots, t_n) = \mathrm{softmax}(W \cdot h_n)

Given the prompt "2 + 2 = ", the token "4" simply carries an overwhelming probability. The model is not calculating; it is recalling a statistical pattern.

Autoregressive Generation

The chosen token is appended to the input, and the whole process repeats. This loop is called autoregressive generation, and it explains why asking a model to think step-by-step improves accuracy: each reasoning token becomes new context that steers the next prediction toward logical consistency.

The Token Autoregressive LoopDiagram 1: The Token Autoregressive Loopw₁prompt tokenLLMpredict next tokenw₂new tokenw₃next token…each output token is appended to the input, then the loop repeats

Why AI Has Zero Memory

The Stateless API

An LLM API call is a pure function: text goes in, text comes out, and nothing is retained on the server between calls. The "memory" you experience in a chat product is an application feature.

The Compounding Context Problem

To fake memory, applications resend the entire conversation with every new message. Total tokens processed across a session grow quadratically:

T_{total} = \sum_{i=1}^{n} (S + i \cdot m) \approx n \cdot S + \frac{m \cdot n^2}{2}

Here S is the system prompt size, m is the average tokens per turn, and n is the number of turns. At turn 3 nobody notices. By turn 10 latency has tripled and the monthly bill shows runaway growth.

The Chassis vs. The Engine

It helps to separate two layers:

  • The Foundational LLM Engine: trained weights, the embedding layer and the self-attention matrices. It only predicts tokens.
  • The AI Application Chassis: the session database, authentication, tool calling, guardrails, observability and budget controls.

Almost every production problem (cost, latency, safety, reliability) lives in the chassis, not the engine.

Building a Scalable Conversation Manager

Strategy

Three techniques keep context bounded:

  1. Sliding window: keep only the most recent turns.
  2. Summarization: compress older turns into a short running summary.
  3. Context caching: reuse the static prefix (system prompt, policies) across calls.
The Stateless Memory Rebuilding PipelineDiagram 2: The Stateless Memory Rebuilding PipelineRedis Cachesummary + recent turnsPayload Assemblysystem + summary + windowModel Callstateless: starts from zerosave the reply back to Redis, so the next turn can rebuild memory

Reference Implementation

python
from dataclasses import dataclass, field
from typing import Callable, List, Dict


def rough_token_count(text: str) -> int:
    # ~4 characters per token is a safe planning heuristic.
    return max(1, len(text) // 4)


@dataclass
class ProductionConversationManager:
    system_prompt: str
    summarizer: Callable[[List[Dict[str, str]]], str]
    max_context_tokens: int = 3000
    keep_recent_turns: int = 6
    summary: str = ""
    history: List[Dict[str, str]] = field(default_factory=list)

    def add_message(self, role: str, content: str) -> None:
        self.history.append({"role": role, "content": content})
        self._enforce_budget()

    def _current_tokens(self) -> int:
        total = rough_token_count(self.system_prompt) + rough_token_count(self.summary)
        total += sum(rough_token_count(m["content"]) for m in self.history)
        return total

    def _enforce_budget(self) -> None:
        # Compress the oldest turns once we exceed the token budget.
        while (
            self._current_tokens() > self.max_context_tokens
            and len(self.history) > self.keep_recent_turns
        ):
            overflow = self.history[: -self.keep_recent_turns]
            self.summary = self.summarizer(
                [{"role": "system", "content": self.summary}] + overflow
            )
            self.history = self.history[-self.keep_recent_turns:]

    def build_payload(self) -> List[Dict[str, str]]:
        messages = [{"role": "system", "content": self.system_prompt}]
        if self.summary:
            messages.append(
                {"role": "system", "content": "Conversation so far: " + self.summary}
            )
        return messages + self.history

With this manager, the payload size stays roughly constant instead of growing every turn, so cost per turn becomes predictable.

Agents Need Circuit Breakers

The same stateless, loop-driven design is dangerous for autonomous agents. An agent that retries a failing test forever can burn hundreds of dollars in minutes. Production agents need:

  • A hard budget ceiling per task.
  • A maximum iteration count.
  • A circuit breaker that halts on repeated identical failures.
  • Human-in-the-loop escalation.

Key Takeaways

  • An LLM predicts the next token; it does not remember, calculate or search.
  • Memory is an application-layer feature, and naive memory compounds cost.
  • Bound your context with windows, summaries and caching.
  • Wrap every autonomous loop in budgets and circuit breakers.