Skip to content
KONIGI Patterns

AI Assistants / Memory and context / Context meter

1 of 5 ›

Context meter

People need to know when a conversation is approaching the amount of information the assistant can use.

Updated September 12, 2026

Problem

Long conversations can exceed the amount of information an AI model can use at once. Earlier details may be dropped or summarized without people realizing it.

Solution

Show how much of the model’s available context is in use. Explain that messages, attachments, and instructions share this space. Use a readable measure, such as a percentage, with technical details available separately.

Warn people while they still have room to act. Offer relevant options, such as removing a large attachment or starting a new conversation with a summary.

When earlier messages are summarized or removed from context, mark that change in the conversation. Explain what the assistant can still use. Show when an attachment takes up a large share of the available space.

Use when

Conversations run long, include large documents, or use models with different context limits.

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

A meter helps explain forgotten details but asks people to manage a technical limit. Summaries keep conversations going while losing some detail. A percentage is easier to understand than a token count, but it cannot show exactly how much more work will fit.

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 preserves earlier sessions even when their full text no longer fits in context. Attachment tray shows files using that space.

Usage meter tracks a separate usage allowance. Scoped context selects the material available to the assistant. Custom instructions also use context space.

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 screenshots yet for this pattern. Browse Products to see other examples.