Skip to main content

ctxopt

A lightweight CLI for taming LLM context windows.

ctxopt helps you keep conversations with LLMs — and the agents built on them — lean. It does three things, each independently useful:

  • 🧹 Sandbox noisy tool output (logs, stack traces, diffs, JSON blobs) down to a compact form that fits an actual token budget, instead of dumping thousands of lines into your context.
  • 📝 Summarize long conversations or transcripts into a shorter digest, locally and deterministically — no API calls required.
  • 🧭 Route a prompt to the right model tier based on an estimate of how complex it actually is, so you're not paying frontier-model prices for "what's the capital of France?"

No heavy dependencies, no required API keys, no telemetry. It's a small tool that does a small number of things well.

Install

pip install ctxopt

Or from source:

git clone https://github.com/feralxr/ctxopt.git
cd ctxopt
pip install -e .

Requires Python 3.9+.

Usage

Sandbox noisy output

This is the core command: it collapses long, repetitive, or oversized tool output while preserving the signal that actually matters.

ctxopt sandbox build.log
# or pipe it in:
cat build.log | ctxopt sandbox

Token-budget fitting (recommended — tokens, not lines, are what fill a context window):

ctxopt sandbox giant_stacktrace.log --max-tokens 300
  File "app.py", line 14, in recurse
    return recurse(n-1)
RecursionError: maximum recursion depth exceeded
profile=stacktrace  5346 -> 118 tokens (5228 saved, 981 lines elided, fit to budget)

Near-duplicate collapsing. Lines that are identical after stripping volatile bits (timestamps, hex addresses, UUIDs, standalone counters) get folded together — this is what actually tames recursive stack traces and progress spam, where only a counter or timestamp changes line to line. Digits embedded inside an identifier (like line1 or v2) are left alone, so distinct content never silently merges:

ctxopt sandbox big_diff.patch
diff --git a/big_module.py b/big_module.py
--- a/big_module.py
+++ b/big_module.py
@@ -1,300 +1,300 @@
+added line 0
... [299 near-identical line(s) collapsed] ...
profile=diff  1216 -> 28 tokens (1188 saved, 0 lines elided)

Content profiles. ctxopt sandbox auto-detects the shape of the input (--profile auto by default) and adjusts behavior accordingly:

Profile Detected when Behavior
generic default fallback Head/tail elision + near-dup collapsing
json input parses as JSON Structural truncation: long strings and large arrays are trimmed at the value level, so the output stays valid JSON
stacktrace 3+ frame-like lines (File "...", line N / at foo(...)) Tighter head window, Traceback/Caused by lines always preserved
diff 2+ diff markers (+++, ---, @@, diff --git) File headers and hunk markers always preserved even when the body is elided

JSON example — a 300-user payload with 800-character bios collapses to a fraction of its size while staying parseable:

ctxopt sandbox users.json
{
  "users": [
    { "id": 0, "name": "user_0", "bio": "xxxx...xx... [300 chars truncated]" },
    ...
  ]
}
profile=json  2401 -> 306 tokens (2095 saved, 0 lines elided)

Signal preservation. Lines matching error, exception, fail(ed/ure), traceback, fatal, or panic are never elided, even inside the collapsed middle — up to a cap (max_kept_matches as a library param) so a pathological log full of "error" lines can't defeat the budget. Add your own patterns with --keep:

ctxopt sandbox app.log --keep "payment_id=\w+" --max-tokens 500

Flags:

Flag Default Description
--max-tokens — Shrink output to fit this token budget (shrinks head/tail/keep windows until it fits)
--max-lines 200 Collapse once output exceeds this many lines (used when --max-tokens isn't set)
--head / --tail profile-specific Lines to keep from the start/end
--profile auto auto, generic, json, stacktrace, or diff
--keep — Extra regex to always preserve (repeatable)
--out stdout Write result to a file instead

Summarize a conversation

Condense a long transcript or log into a shorter digest using a fast, transparent extractive heuristic (paragraph length, keyword density, and position all factor into what gets kept):

ctxopt summarize transcript.txt --ratio 0.2
638 -> 126 tokens

This is intentionally local and deterministic rather than LLM-backed, so it costs nothing and behaves predictably. If you want an abstractive summary, pipe the condensed output into your own LLM call — you'll be sending it far fewer tokens than the original.

Route a prompt to a model tier

Score a prompt's complexity and get a model recommendation back:

ctxopt route "What is the capital of France?"
Score    0.00
Tier     simple
Model    claude-haiku-5-5
Reasons  2 simple-query keyword(s) matched
ctxopt route "Design a fault-tolerant distributed architecture for this payment system and debug the race condition below: ..."
Score    0.74
Tier     complex
Model    claude-opus-5-5
Reasons  contains code or code-like syntax; 4 complexity keyword(s) matched

The scoring heuristic weighs input length, presence of code, and complexity/simplicity keyword signals. Tiers and model names are fully configurable — see below.

Using it as a library

Every command is backed by a plain Python function with no CLI coupling:

from ctxopt import sandbox_text, summarize_text, route, RouterConfig

# Collapse noisy output to fit an actual token budget
result = sandbox_text(log_text, max_tokens=500)
print(result.profile, result.text, result.original_tokens, result.final_tokens)

# Always preserve your own markers (e.g. a request/trace id), on top of the
# built-in error/exception/fail defaults
result = sandbox_text(log_text, max_tokens=500, keep_patterns=[r"trace_id=\w+"])

# Summarize a transcript
digest = summarize_text(transcript_text, target_ratio=0.25)

# Route to a model tier, with your own model names
config = RouterConfig(tiers={
    "simple": "gpt-4o-mini",
    "standard": "gpt-4o",
    "complex": "o1",
})
decision = route(user_prompt, config)
print(decision.tier, decision.model, decision.reasons)

Why this exists

Agent and chat pipelines accumulate context fast: verbose tool output, sprawling multi-turn history, and routing every request — simple or complex — through the same expensive model. ctxopt is a small set of composable primitives for the three most common sources of that bloat. It's not a framework and doesn't ask you to adopt an architecture — pipe things in, get leaner things out.

Development

git clone https://github.com/feralxr/ctxopt.git
cd ctxopt
pip install -e ".[dev]"
pytest

Every push/PR to main runs the test suite across Python 3.9–3.12 via .github/workflows/tests.yml.

Publishing

Releases are published to PyPI via Trusted Publishing (OIDC) — no PyPI API token is ever stored as a secret. One-time setup on PyPI's side, under Your project → Publishing → Add a new pending publisher:

Field Value
PyPI Project Name ctxopt
Owner feralxr
Repository name ctxopt
Workflow name workflow.yml
Environment name pypi

After that's linked, cutting a release is just:

# bump the version in pyproject.toml and src/ctxopt/__init__.py first
git tag v0.2.0
git push origin v0.2.0

.github/workflows/workflow.yml builds the sdist/wheel, checks the tag matches pyproject.toml's version, and publishes.

License

MIT — see LICENSE.

Metadata

Release files for ctxopt 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ctxopt 0.2.0
File Size Uploaded Provenance
ctxopt-0.2.0.tar.gz 16.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ctxopt 0.2.0
File Interpreter ABI Platform
ctxopt-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 31.6 kB

Release files / ctxopt-0.2.0.tar.gz

Download URL ctxopt-0.2.0.tar.gz
Size 16.3 kB
Tags Source
SHA-256 checksum
How to use checksums
e83c257327a7bfe6d6c1683a164347caa54c04242833bdfe053be9e3238e6e26
BLAKE2b-256 checksum
How to use checksums
a8e46397496de7c3295a67bb9acc4277feb7b1a08d61330268de080a269d2a17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / ctxopt-0.2.0-py3-none-any.whl

Download URL ctxopt-0.2.0-py3-none-any.whl
Size 15.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
408c642cfceccc70673b7f372656d082decfb08760943603bf3ae9c4e7e5e63b
BLAKE2b-256 checksum
How to use checksums
28eee2ec429dc00354cd6e80206612cad61b6deaa137cfa1c4b3c6c120567625
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for ctxopt 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ctxopt-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page