Skip to content

Quickstart

1. Install

pip install "convopack[tiktoken]"

The base package is zero-dependency. Tokenizer extras are optional.

2. Build a packer

from convopack import Packer, Recency

packer = Packer(
    budget=4000,
    tokenizer="tiktoken:gpt-4o",
    strategy=Recency(),
    pin=("system", "first_user"),
)

The four knobs:

Argument Purpose
budget Maximum tokens you want the packed conversation to occupy.
tokenizer A string spec or a Tokenizer instance. See tokenizers guide.
strategy How to decide which messages survive. See strategies guide.
pin Messages that must always be kept ("system", "first_user", "last_user", "tool_results", or an integer index).

3. Pack

Convert your provider's messages to convopack's internal form, pack, and convert back:

history = [
    {"role": "system", "content": "You are concise."},
    {"role": "user", "content": "Norway?"},
    {"role": "assistant", "content": "A Nordic country..."},
    # ... 50 more turns ...
    {"role": "user", "content": "What did we discuss?"},
]

packed = packer.pack_openai(history)
# `packed` is a list of OpenAI Chat Completion dicts, fits the budget.

response = openai.chat.completions.create(model="gpt-4o", messages=packed)
history = [
    {"role": "user", "content": "Norway?"},
    {"role": "assistant", "content": "A Nordic country..."},
    # ...
]

payload = packer.pack_anthropic(history, system="You are concise.")
# `payload.system` is a string; `payload.messages` is the list.

response = anthropic.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    system=payload.system,
    messages=payload.messages,
)
contents = [
    {"role": "user", "parts": [{"text": "Norway?"}]},
    {"role": "model", "parts": [{"text": "A Nordic country..."}]},
    # ...
]

payload = packer.pack_gemini(contents, system_instruction="Be concise.")

response = model.generate_content(
    payload.contents,
    system_instruction=payload.system_instruction,
)

4. Inspect what happened

pack() returns a PackResult:

result = packer.pack_messages(history)  # internal form
print(f"kept {len(result.kept)} of {len(history)}")
print(f"tokens used: {result.token_count} / {result.budget}")
print(f"fits: {result.fits}")

Need a stream of events for a progress bar or audit log? Use pack_stream:

for event in packer.pack_stream(history):
    print(event.kind, event.index, event.token_cost)
# kept 0 12
# kept 1 8
# dropped 2 24
# done -1 1873

5. Tool calls are safe by default

If your assistant emits tool_use and the next message is a tool_result, convopack keeps or drops them together — never splits a pair. This is enforced for every strategy:

from convopack._pairs import validate_pairs

assert validate_pairs(result.kept) == []  # never has dangling tool_use IDs

That guarantee is what lets you point your existing agent loop at Packer.pack_openai() and stop worrying about 400s from half-evicted tool exchanges.

Next

  • Strategies — pick the right one for your app.
  • Pinning — keep critical messages anchored.
  • Recipes — full-loop examples.