Libraries/contextkit
contextkitAssemble

A receipt for
every token.

Treat the context window like a packed suitcase, not a string you concatenate. Declare blocks with priorities, assemble to a token budget deterministically — and get an honest receipt of what was kept, shrunk, or dropped.

$ pip install cendor-contextkitShrink a budget live ↓
support_bot.pydeterministic · budget never exceeded
from cendor.contextkit import Context, Block

ctx = Context(budget_tokens=8000, model="gpt-4o",
              reserve_output=500, order="attention")
ctx.add(Block(SYSTEM_PROMPT, priority=10, pin=True, role="system"))
ctx.add(Block(retrieved_docs, priority=5, evict="compress"))
ctx.add(Block(messages=chat_history, priority=3, evict="drop_oldest"))
ctx.add(Block(user_msg, priority=9, pin=True, role="user"))

messages = ctx.assemble()   # fits the budget, deterministically
print(ctx.report())         # kept / shrunk / dropped + token math
How it packs

How a block loses gracefully.

Every block declares its priority and how it prefers to lose. When the budget is tight, higher priority holds its ground and lower priority gives way — using the eviction strategy the block chose, not an arbitrary string chop.

Strategy
How it loses
drop_oldest
chat-history blocks peel their oldest turns to fit — a sliding window that never mangles a turn
truncate
keep head or tail with a visible "…[truncated]" marker
summarize
your callback (sync, or async via aassemble()) shrinks the block
compress
reversible compression via squeeze (the handle survives in the receipt)
any EvictionStrategy
your own policy, by protocol — no subclassing

Pinned blocks are never evicted — if pins alone overflow, assemble() raises BudgetError instead of silently mangling your prompt. Ordering modes: default · attention (strongest context on the edges — the lost-in-the-middle effect) · cache (stable prefix to maximize prompt-cache hits).

Try it

Shrink the budget. Watch the receipt.

This is the packer, live. Drag the slider — blocks fit in priority order against the usable budget (budget minus reserve_output), and the receipt updates to show exactly what was kept, shrunk, or dropped.

▸ drag the budget — every block refits, in priority order · pins never move

8,000 tok
system prompt · pinned · 800 tok
retrieved docs · evict=compress · 3,500 tok
chat history · evict=drop_oldest · 4,000 tok
user message · pinned · 300 tok
block
action
tokens
system prompt
kept · pinned
800
retrieved docs
kept
3,500
chat history
shrunk · peeled 3 turns
2,600
user message
kept · pinned
300

used 7,200 of 8,000 budget · reserve_output 500 · within budget ✓

Same inputs, same budget → same output, every time. The receipt is emitted on the event bus, so acttrace can audit what the model actually saw.

Beyond assemble()

What-if, adapters, images.

1

Preview before you commit

ctx.whatif(budget=4000)

See what would be kept, shrunk, or dropped at a tighter budget — a dry-run receipt, without committing anything.

2

One context, any provider

for_anthropic() · for_gemini() · for_bedrock()

The same assembled context, reshaped per provider — each adapter returns the messages in that provider's expected format.

3

Images have a budget too

image_tokens=…

Multimodal parts are budgeted per image with your image_tokens figure, so image-bearing blocks pack against the same budget as text.

Honest limits

Where the edges are — by design.

The receipt is honest at the message level.

used == core.tokens.count(assemble()) holds exactly under the offline counting model and approximately under real tokenizers — per-message framing isn't perfectly linear.

Images are budgeted, not counted.

Image parts use your image_tokens figure; a text-only recount can't see them, so don't assert equality on multimodal contexts.

It applies when you assemble the prompt.

If a managed runtime owns context internally, there's nothing for contextkit to shape — the budget and audit tools still work.

Determinism is yours to keep.

Packing is deterministic; a summarize callback that isn't deterministic makes the assembled output vary with it.

Get started

Stop concatenating. Start packing.

$ pip install cendor-contextkit

contextkit docs → · attention-ordering cookbook → · composes with the Cendor stack