Skip to content
KONIGI

AI Assistants / Memory and context / Context meter

1 of 5

Context meter

A long conversation quietly stops remembering its own beginning.

Updated September 12, 2026

Problem

Forty turns into a working session the assistant forgets a constraint set on turn three. Nothing announced the change. The conversation looks the same, reads the same, and is now running against a version of the history with the beginning cut off.

Solution

The window is finite and shared. Every turn of history, every attached document, the standing instructions and the reply being generated all draw on the same budget, and it’s fixed per model. When the conversation exceeds it something has to give, and the three available behaviours produce very different experiences.

Truncation drops the oldest turns. Cheap, and it is the silent-forgetting failure exactly. Summarisation compresses earlier turns into a synopsis, which keeps the gist and loses the specifics, so the model remembers that a format was agreed and forgets what it was. Refusal stops and asks the viewer to start a new conversation, which is the most honest and the most disruptive.

Whichever is chosen, the viewer needs two things: a sense of where they’re against the limit while there’s still room to act, and an unambiguous event when the boundary is crossed. A meter alone is insufficient, because nobody watches a meter. A marker in the transcript at the point where earlier material stopped being visible to the model is the part that actually helps, since it converts an invisible state change into something a reader can see when they scroll back.

Timing follows from that. A meter that appears at ninety per cent is informing someone whose options have already narrowed to starting over. Surfacing it around two-thirds leaves room to act, and the actions worth offering are concrete: start a fresh conversation carrying a summary, remove a large attachment, or switch to a model with a larger window.

Attachments are where this becomes urgent rather than academic. A three-hundred-page document can consume most of a window on its own, and the viewer experiences that as the assistant being strangely forgetful for the rest of the session. Showing the attachment’s share of the budget, in the tray where it was added, explains a symptom that would otherwise look like the product being bad.

Use when

Sessions run long, documents are attached, or the product offers models whose windows differ enough to matter.

Don’t use when

Exchanges are short and self-contained. A meter that never moves off empty is decoration, and it teaches people to ignore an indicator that would matter later.

Trade-offs

Exposing the budget invites people to manage it, which is real work the product has offloaded onto them. Tokens are also the wrong unit for a human: a percentage is comprehensible and imprecise—a token count is precise and meaningless to almost everyone. Automatic summarisation keeps conversations alive and introduces a silent lossy step that’s harder to reason about than plain truncation. Displaying a limit also makes the product feel constrained at exactly the moment someone is deep in productive work.

Checklist

  • Does the viewer learn the window is filling before it is full?
  • Is there a visible marker where earlier turns stopped being included?
  • What’s the overflow behaviour, and is it stated anywhere a viewer would look?
  • Does a large attachment show its share of the budget?
  • Are the units something a person can act on?
  • Does switching models change the limit visibly?
  • Is there a one-click way to continue in a fresh conversation with a summary?
  • Do standing instructions count against the budget, and is that visible?
  • What happens to a branch abandoned by an edit, and does it still occupy space?
  • Does the assistant say when it can no longer see something it was told?

Compare

Claude publishes its window sizes per model and ties the limit to the model choice, so the constraint is a documented property rather than folklore. ChatGPT mostly hides the meter and manages overflow behind the scenes. The interface stays calm and the forgetting gets harder to attribute. Gemini competes largely on window size, which turns the limit into a marketing surface and reduces the need for an in-product meter. Cursor takes the opposite approach for a technical audience and shows what is in context as an explicit, editable list. The budget becomes something to curate rather than something the product hides.

Conversation history is where a session that outgrew its window should continue. Attachment tray is the largest single consumer of the budget. Usage meter is the other limit and measures money rather than memory. Scoped context determines how much material is in play. Custom instructions spend part of the window on every turn.

Context meter anatomy A budget bar split between a document, the standing instructions and the conversation, a threshold at which the meter appears, and a marker in the transcript at the point where earlier turns stopped being visible to the model. The conversation stops remembering, and says nothing lease.pdf rules turns meter appears here the model can no longer see anything above this line truncate · summarise · refuse. Three very different experiences. 1 2 3 4 1 ONE SHARED BUDGET A document, the standing rules and the history all draw on the same window. 2 MARK THE LINE A meter alone is never watched. A marker in the transcript is seen on scrolling back. 3 APPEAR WITH ROOM LEFT At ninety per cent the only option left is starting over. Two-thirds leaves choices. 4 SAY WHICH OVERFLOW Truncation forgets, summarisation blurs, refusal interrupts. Pick one and say it. Tokens are precise and meaningless. A percentage is comprehensible and imprecise. Pick the second.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

A budget split between the document, the standing instructions and the conversation, with a threshold at which the meter appears at all. The line in the transcript marks where earlier turns stopped being visible to the model.

shadcn
npx shadcn@latest add button
Tokens
--card--muted--muted-foreground--border--chart-1--scale-seq-1--scale-seq-2--scale-seq-3
lease.pdfrulesturns
82% of the window used

you: Summarise the break clause in lease.pdf.

assistant: The tenant can end the lease at the end of year two on six months' notice, if rent is paid up and the premises are handed back empty.

you: What about the rent review?

assistant: Upward only, at the third anniversary, to the higher of open market rent and the passing rent.

ContextMeter.tsxThe stacked meter, hidden until two-thirds is spent, and the boundary line whose wording is decided by which overflow the product chose.

import { Button } from "@/components/ui/button";

/**
 * What the product does when the window is full. The set is closed because
 * each one is a different experience for the viewer, and the boundary marker
 * has to say which happened.
 */
export type Overflow = "truncate" | "summarise" | "refuse";

/** Everything that draws on the window. Order is display order. */
export type Segment = {
  key: string;
  /** What the viewer sees under the bar: a file name, "rules", "turns". */
  label: string;
  tokens: number;
};

const SEGMENT_COLOR = ["bg-scale-seq-3", "bg-scale-seq-2", "bg-scale-seq-1", "bg-scale-seq-4"];

/**
 * A stacked budget bar. It stays out of the way until `showAt` of the window is
 * spent, because a meter that never moves off empty teaches people to ignore
 * it, and once visible it offers the one action that still helps.
 */
export function ContextMeter({
  budget,
  segments,
  showAt = 2 / 3,
  onStartFresh,
}: {
  /** The window, in tokens, for the model in use. */
  budget: number;
  segments: Segment[];
  /** Fraction of the window spent before the meter appears at all. */
  showAt?: number;
  /** Continue in a new conversation carrying a summary. */
  onStartFresh: () => void;
}) {
  const used = segments.reduce((sum, s) => sum + s.tokens, 0);
  const pct = (n: number) => `${(100 * n) / budget}%`;
  if (used / budget < showAt) return null;

  // Percent, never tokens: a token count is precise and meaningless to almost everyone.
  const usedPct = Math.round((100 * used) / budget);
  return (
    <div>
      <div className="relative">
        <div className="flex h-[18px] w-full overflow-hidden rounded-[2px] bg-muted" role="meter" aria-valuenow={usedPct} aria-valuemin={0} aria-valuemax={100} aria-label={`${usedPct}% of the context window used`}>
          {segments.map((s, i) => (
            <span key={s.key} className={SEGMENT_COLOR[i % SEGMENT_COLOR.length]} style={{ width: pct(s.tokens) }} title={`${s.label}: ${Math.round((100 * s.tokens) / budget)}%`} />
          ))}
        </div>
        <span className="absolute -top-1.5 -bottom-1.5 w-0.5 bg-chart-1" style={{ left: `${100 * showAt}%` }} aria-hidden />
      </div>
      <div className="mt-2 flex text-[11px] text-muted-foreground">
        {segments.map((s, i) => (
          <span key={s.key} className={SEGMENT_COLOR[i % SEGMENT_COLOR.length].replace("bg-", "border-l-2 border-")} style={{ width: pct(s.tokens) }}>
            <span className="pl-1">{s.label}</span>
          </span>
        ))}
      </div>
      <div className="mt-3 flex items-center justify-between gap-3 text-xs">
        <span className="text-muted-foreground">{usedPct}% of the window used</span>
        <Button variant="outline" size="sm" className="h-7 text-xs" onClick={onStartFresh}>Continue in a new conversation</Button>
      </div>
    </div>
  );
}

const BOUNDARY: Record<Overflow, string> = {
  truncate: "the model can no longer see anything above this line",
  summarise: "everything above this line has been summarised for the model",
  refuse: "this conversation has reached its limit",
};

/**
 * The marker that goes in the transcript at the point where earlier turns
 * stopped being included. Nobody watches a meter; everybody sees a line when
 * they scroll back.
 */
export function ContextBoundary({ overflow }: { overflow: Overflow }) {
  return (
    <div role="separator" aria-label={BOUNDARY[overflow]} className="my-3 border-t border-dashed pt-2">
      <p className="text-[11px] text-chart-1">{BOUNDARY[overflow]}</p>
    </div>
  );
}

demo.tsxHow it is called: the budget, its segments, and a transcript with the line where the model stopped seeing.

import { ContextBoundary, ContextMeter } from "./ContextMeter";

/** A 200k window with a lease attached. Two early turns have already fallen out. */
const BUDGET = 200_000;
const SEGMENTS = [
  { key: "lease", label: "lease.pdf", tokens: 98_600 },
  { key: "rules", label: "rules", tokens: 18_400 },
  { key: "turns", label: "turns", tokens: 46_000 },
];

const TURNS = [
  { who: "you", text: "Summarise the break clause in lease.pdf." },
  { who: "assistant", text: "The tenant can end the lease at the end of year two on six months' notice, if rent is paid up and the premises are handed back empty." },
  { who: "you", text: "What about the rent review?" },
  { who: "assistant", text: "Upward only, at the third anniversary, to the higher of open market rent and the passing rent." },
];
/** Index of the first turn the model can still see. */
const VISIBLE_FROM = 2;

export default function Demo() {
  return (
    <div className="rounded-lg border bg-card p-4">
      <ContextMeter budget={BUDGET} segments={SEGMENTS} onStartFresh={() => console.log("start fresh with a summary")} />
      <div className="mt-5 border-t pt-4 text-xs">
        {TURNS.map((t, i) => (
          <div key={i}>
            {i === VISIBLE_FROM && <ContextBoundary overflow="truncate" />}
            <p className={i < VISIBLE_FROM ? "text-muted-foreground/60" : "text-card-foreground"}>
              <span className="font-medium">{t.who}: </span>{t.text}
            </p>
          </div>
        ))}
      </div>
    </div>
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

No captures reference this pattern yet. Captures arrive product by product; see Products for what's in the gallery so far.