Context Engineering: Token Budgets, Compaction and Running Notes for Long Chats
Hands-on lab · IDE in your browser

Context Engineering: Token Budgets, Compaction and Running Notes for Long Chats

Keep a long conversation inside a token budget without losing what matters: count tokens, keep a recent window, trim bulky tool output, compact old turns into running notes that track changed decisions, and assemble each request by priority.

Time
50 min
Checked steps
4
Level
Intermediate
Setup
None
Read step 1

Hands-on labs require Pro · $29.99/mo · cancel anytime

Lab cockpit50 min · 4 stepsSession running
3 / 4 steps passingAssemble within the total budget · step 4 of 4
context.py▶ Run✓ Check
# ---------- Step 4: assemble within the total budget ----------def build_messages(notes, kept, question, total=TOTAL_BUDGET):    """System message (SYSTEM, a blank line, "Notes on the earlier conversation:", the notes), then as many of the    most recent kept pairs as fit within `total` tokens for the whole request, then the question as the last user    message. The system message and the question always go in; pairs are added newest first and never split."""    system = {"role": "system", "content": f"{SYSTEM}\n\nNotes on the earlier conversation:\n{notes}"}    q = {"role": "user", "content": question}     
TerminalOutput

The job

A travel assistant has spent a long chat planning Sam's family trip to Japan: a peanut allergy, two hotel bookings with reference codes, a budget that went up halfway through, a day trip that was dropped. The app can afford about 900 tokens a request. You make the assistant remember what matters within that budget, and prove it on a second chat it has never seen.

4 steps, each checked when you finish it

A check runs your work at the end of every step. Hints and the full solution are there if you get stuck.

  1. 1

    Count tokens and keep a window

    session.json is a long chat in which Sam plans a two-week family trip to Japan with an assistant: who is travelling, a peanut allergy, two hotel bookings with reference codes, a budget that changes halfway, a day trip that gets dropped.

    You writecount_tokens()recent_window()
  2. 2

    Trim bulky tool output

    Half of this chat is search output: hotel lists and train timetables the user read once and moved on from.

    You writetrim_bulky()
  3. 3

    Compact old turns into notes

    Turns that leave the window should not simply vanish.

    You writecompact()
  4. 4

    Assemble within the total budget

    The request has parts of different value.

Step 1 as it appears in the lab

The lab’s own text. The hint and the solution stay inside the lab.

Step 1: Count tokens and keep a window

session.json is a long chat in which Sam plans a two-week family trip to Japan with an assistant: who is travelling, a peanut allergy, two hotel bookings with reference codes, a budget that changes halfway, a day trip that gets dropped. At the end come ten probe questions whose answers were settled earlier in the chat.

Sending the whole history works, but every request pays for all of it, and a longer chat pays more every turn. The app allows 1,000 tokens of history per request (BUDGET). The simplest way to respect that is a window: keep the most recent turns that fit.

Do this

1. Write count_tokens(messages): harness.tokens(content) + 4 for each message (the 4 covers the role and separators).

2. Write recent_window(turns, budget): walk the user+assistant pairs from the end and keep each one while the total stays within the budget; stop at the first pair that does not fit. Return the kept turns oldest first. Never split a pair.

3. Run. It answers the probes with the full history and with your window. Read what the model says when the facts have fallen out of the window.

context.py, the file you edit77 lines
"""Context engineering for a long chat: fit the conversation into a token budget without losing what matters."""
import re

import harness

SYSTEM = ("You are the assistant in this conversation. Answer the user's latest question briefly. "
          "Facts from earlier in the conversation are in your notes; trust them.")
BUDGET = 1000        # tokens of conversation history kept between calls
TOTAL_BUDGET = 900   # tokens of the whole request: system prompt, notes, kept turns and the question


# ---------- Step 1: count and window ----------
def count_tokens(messages):
    """Tokens a list of messages costs: harness.tokens(content) + 4 per message for the role and separators."""
    # TODO (Step 1): the sum over messages of harness.tokens(m["content"]) + 4
    raise NotImplementedError("Step 1: write count_tokens()")


def recent_window(turns, budget=BUDGET):
    """The most recent whole user+assistant pairs that fit in the budget, oldest first. Never split a pair."""
    # TODO (Step 1): walk the pairs from the end (i = len(turns) - 2, then i - 2, ...); put each pair turns[i:i + 2]
    # in front of what you have while count_tokens() of the result stays within budget; stop at the first that does not.
    raise NotImplementedError("Step 1: write recent_window()")


# ---------- Step 2: trim bulky tool output ----------
BULKY = re.compile(r"^(Search results:|Train options|Quotes for)")


def trim_bulky(turns, keep_last=2):
    """Copies of the turns where each bulky assistant message (search results, options, quotes) older than the last
    keep_last pairs is replaced by a one-line stub: "[<its first line, without a trailing colon> (<N> options)
    shown to the user; omitted]", N being its number of lines after the first."""
    raise NotImplementedError("trim_bulky() arrives in Step 2")


# ---------- Step 3: compact into running notes ----------
NOTES_PROMPT = """You keep the running notes for a long conversation between a user and an assistant. Rewrite the notes to include what the new turns add.

Write two sections:
FACTS: people, dates and times, bookings with their reference codes, budgets, health and dietary needs, preferences. Copy names, numbers, times and codes exactly.
DECISIONS: what the user has decided, with the latest decision replacing any earlier one it contradicts (write what changed, e.g. "Hiroshima dropped; Nara instead").

Leave out search results, options the user did not choose, and suggestions. Use short bullet points. At most 180 words.

Notes so far:
{notes}

New turns:
{turns}

Updated notes:"""


def compact(turns, budget=BUDGET):
    """Replay the conversation pair by pair. Whenever the kept turns exceed the budget, fold the oldest pairs into the
    notes (one NOTES_MODEL call) until the kept turns are at most budget // 2. Returns (notes, kept)."""
    raise NotImplementedError("compact() arrives in Step 3")


# ---------- Step 4: assemble within the total budget ----------
def build_messages(notes, kept, question, total=TOTAL_BUDGET):
    """System message (SYSTEM, a blank line, "Notes on the earlier conversation:", the notes), then as many of the
    most recent kept pairs as fit within `total` tokens for the whole request, then the question as the last user
    message. The system message and the question always go in; pairs are added newest first and never split."""
    system = {"role": "system", "content": f"{SYSTEM}\n\nNotes on the earlier conversation:\n{notes}"}
    q = {"role": "user", "content": question}
    return [system] + list(kept) + [q]  # Step 4 fits this into the total budget


def prepare(turns):
    """What the app keeps between calls: trim, then compact."""
    return compact(trim_bulky(turns))


def answer(notes, kept, question):
    return harness.chat(build_messages(notes, kept, question), harness.ANSWER_MODEL, 300)
Provided for you:evaluate.pyharness.pysession.json

Frequently asked questions

What is context engineering?

Deciding what goes into each model request: which turns, which tool results, which summaries or notes, within a token budget. It matters most in long conversations and agent loops, where history grows every turn.

What happens if you just keep the last few messages?

The model loses facts from early in the conversation and often invents replacements. In the lab, a recent window answers 2 of 10 probe questions and makes up a budget, an airport and a booking code.

What is conversation compaction?

Replacing old turns with notes the model writes, rewritten as the conversation continues so that later decisions replace earlier ones. In the lab, notes plus a short window answer the probes as well as the full history at about a seventh of the tokens.

Should tool results stay in the conversation history?

Only while they are in use. Once the user has chosen, a one-line stub is enough: the user's choice is in their own message, and the full search results only cost tokens.

Managing context in long LLM conversations, measured

Long chats either get expensive, because every request carries the whole history, or forgetful, because old turns are cut. Context engineering is choosing what goes into each request: a window of recent turns, stubs in place of bulky tool output, and running notes that keep facts and decisions from earlier in the conversation. In this lab you build each piece and measure it with probe questions whose answers were settled early in the chat: the full history, a recent window, notes plus a window, and a final pipeline that fits every request into a fixed token budget.