Skip to main content

CutCtx

Deterministic transcript compaction for the Local AI Suite. Given a transcript and a token budget, CutCtx decides which turns to keep, mask, summarize or drop, and applies that decision to produce a compacted view plus an auditable account of what was done to it.

Status: Phase 2 complete, 0.1.0 prepared. The transcript model, the invariants, the plan/executor split and all four shipped policies — ObservationMaskingPolicy, SummarizingPolicy, DropOldestPolicy and PolicyChain — are built and gated. Publication is an operator step and has not happened; nothing here is on PyPI yet.

The shape: plan → fulfil → apply

This is the one thing to understand before using the package, and it is friction on purpose.

from cutctx import CompactionBudget, CompactionExecutor, DropOldestPolicy
from cutctx import Role, Transcript, TranscriptTurn

transcript = Transcript(
    (
        TranscriptTurn("s", Role.SYSTEM, "You are a careful assistant.", 12),
        TranscriptTurn("u1", Role.USER, "What changed in the build?", 30),
        TranscriptTurn("a1", Role.ASSISTANT, "Let me look.", 20, tool_call_id="c1"),
        TranscriptTurn("t1", Role.TOOL, "<4 kB of build log>", 900, tool_call_id="c1"),
        TranscriptTurn("a2", Role.ASSISTANT, "The cache key changed.", 25),
    )
)

plan = DropOldestPolicy().decide(
    transcript, CompactionBudget(max_tokens=100, protected_recent_turns=1)
)
plan.tokens_before, plan.tokens_after_estimate, plan.budget_unmet
# (987, 37, False)

view = CompactionExecutor().apply(transcript, plan)
view.transcript.turn_ids()  # ('s', 'a2') — the tool exchange left as a unit
view.report.dropped_turn_ids  # ('u1', 'a1', 't1')
transcript.turn_ids()  # ('s', 'u1', 'a1', 't1', 'a2') — the input is untouched

CutCtx never calls a model. A policy that wants a span summarized does not summarize it; it puts a SummarizationRequest(group_id, turn_ids, target_tokens, prompt_id) in the plan. You fulfil it — through your own governed inference path, on a local tier if the transcript is confidential — and pass the text back:

view = executor.apply(transcript, plan, summaries={"g1": "Earlier: the user asked about …"})

Forget that middle step and you get SummaryMissing naming the group, not a quietly shorter prompt. That is the whole reason the two-phase protocol exists (ADR-0052): a package below the applications must not hold a second, ungoverned path to a model, and separating the decision from the expensive part is what makes the decision auditable before anything is spent.

What it guarantees

A plan is a view, never a deletion apply() returns a new transcript; the input is unchanged and what you retain is your call
The untouchable set is untouchable Every SYSTEM turn, every pinned turn and the protected_recent_turns tail are kept under every policy
Tool exchanges travel together A call and its results are dropped as a unit or summarized into one group — never separated
The budget outcome is trichotomous It fits, or the plan says budget_unmet, or BudgetUnsatisfiable names both numbers. There is no plan that claims to fit while being over, and never a silent truncation
Plans are byte-identical on re-derivation Same transcript, budget and configuration ⇒ the same bytes, on every platform — because a plan appears in an audit record
An estimate is never a count Token figures are estimates; the character-ratio default's ratio rides on the plan that used it
Purity is proven, not claimed No model, no HTTP client, no database, no filesystem, no clock, no logging — asserted by an import-graph test and by import-linter
A masked stub carries a hash, never an excerpt The original may hold a secret, and a "first 200 characters" preview is a leak with an extra step
A summarization is planned, never performed The package emits a SummarizationRequest naming turns, a token target and a prompt id; the caller fulfils it through its own inference path
A chain composes over a projection Applying needs summaries, and the package produces none — so composition works from what a plan would produce, and one plan is built once against the real transcript

The four shipped policies

Policy What it does What it will not do
ObservationMaskingPolicy Replaces old TOOL bodies with a labelled stub carrying the original's digest and estimate Touch a non-TOOL turn, mask within the keep_recent_results floor, or make a turn bigger than it was
SummarizingPolicy Plans one summarization of the oldest contiguous unpinned span Split an exchange, take a span shorter than min_span_turns, or produce prompt text
DropOldestPolicy Drops whole exchanges, oldest first — the deterministic last resort Split an exchange, or truncate silently
PolicyChain / default_chain Runs them in order, stopping when the budget fits Apply an intermediate plan, or build more than one plan

default_chain(prompt_id=…) is masking → summarizing → drop-oldest, and the order is an argument about cost: masking is free, summarizing costs a model call but keeps the substance, dropping keeps nothing at all.

Acceptance

acceptance/plan_and_apply.py is spec §20 criterion 2 as a runnable check: it plans and applies a compaction with a hand-supplied summary, using only cutctx and baseaicore. It exits non-zero when a claim fails, so it is a check rather than a demonstration.

python -m venv /tmp/cutctx-acceptance
/tmp/cutctx-acceptance/bin/pip install .
/tmp/cutctx-acceptance/bin/python acceptance/plan_and_apply.py

The rules live in one module, cutctx._invariants, and there is no way to build a plan that skips it: CompactionPlan's constructor takes the transcript and budget the plan is for and validates against them before the object exists.

Consumers

  • PromptCadence — per-turn transcript compaction, above compaction.threshold of the tier's context budget; emits the CompactionReport as the context.compacted event body.
  • IdeaPressproject_review and stage-context assembly, whose documented reduction order ("research notes → distant unit summaries → adjacent unit summaries", with the unit specification and requirements never dropped) is a compaction policy currently written in English. The mapping from its context rows onto TranscriptTurn is in cutctx.types's module docstring.

Development

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install

The full gate, identical to CI:

ruff format --check . && ruff check . && mypy src tests && lint-imports \
  && pytest -m "not live and not performance"

Property tests use hypothesis (a development dependency; the runtime budget stays at zero non-suite packages). A failing example prints a @reproduce_failure decorator you can paste onto the test to replay it — see CONTRIBUTING.md.

License

Apache-2.0. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cutctx-0.1.0.tar.gz (156.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cutctx-0.1.0-py3-none-any.whl (55.7 kB view details)

Uploaded Python 3

File details

Details for the file cutctx-0.1.0.tar.gz.

File metadata

  • Download URL: cutctx-0.1.0.tar.gz
  • Upload date:
  • Size: 156.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cutctx-0.1.0.tar.gz
Algorithm Hash digest
SHA256 71a1bb5e4023365807991541a877d8f95b18a63fd99f3e7adecf4d0d70506338
MD5 9156ced8ad45e81571826431b9672d5c
BLAKE2b-256 7c3c453262d3c71dcc57566d63a1608e65c0183dc0934baf8b257aaa06b6e1c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for cutctx-0.1.0.tar.gz:

Publisher: release.yml on JPKell/CutCtx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cutctx-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: cutctx-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 55.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cutctx-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 653594a2347baa02d96930352415000119bd6f7ebadb4fc7812cdc99ed91038d
MD5 384b464b64472a8c657a194027007a43
BLAKE2b-256 46528997cd9ea711649591c6d4555fd546482a021277eec62b2495454c4853b9

See more details on using hashes here.

Provenance

The following attestation bundles were made for cutctx-0.1.0-py3-none-any.whl:

Publisher: release.yml on JPKell/CutCtx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

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