contexel GitHub

The context contract

Spec-driven development made intent an explicit, versioned artifact. The context an agent reasons over deserves the same promotion — because today it is the loose variable that quietly undermines reproducibility.

The leak

An agent's behavior is a function of its instructions and the context it assembled from the repo. The instructions are written, reviewed, versioned. The context is improvised at runtime and thrown away: the model writes throwaway reshaping code, run by run, that re-derives field selection, truncation, and trimming slightly differently each time. Two runs against identical instructions can see materially different contexts — and therefore produce materially different results.

Where the drift compounds

Iteration → iteration

The fix loop

Implement, run tests, read failures, fix, repeat. If the shaping of the test output wobbles, the agent's view of "what is still failing" wobbles with it — the loop thrashes on a moving target.

Run → run

Re-running a story

Same task, same repo — you expect the same behavior. Ad hoc reshaping breaks that even when nothing else changed.

Story → story

One repo, many tasks

A backlog hits one codebase. You want the agent's treatment of that codebase to be uniform, so behavior is a function of the task — not of how the model compressed things that day.

The contract

A contexel pipeline is a deterministic function from raw tool output to distilled context. Write it once, version it in the repo, apply it at every tool boundary:

# project_context.py — one context policy for this repo, versioned with it
def code_search(task_query: str):
    """The repo's one lens on code search; the query varies, the policy never does."""
    return pipeline([
        stage(select, fields=["path", "line", "symbol", "snippet", "score"]),
        stage(dedupe, key=["path", "line"]),
        stage(rescore, query=task_query, fields=("snippet", "symbol", "path")),
        stage(truncate_field, field="snippet", max_tokens=60),
        stage(rank, by="score", desc=True),
        stage(trim_to_budget, max_tokens=2000),
    ])

Two guarantees make this a contract rather than a convenience: deterministic shaping — identical input yields byte-identical output, so reshaping stops being a variable — and uniform policy — one lens on the repo across every iteration, run, and story. And it is inspectable: trace() records what each stage did, so a shaping decision is a reviewable artifact instead of a silent one.

Two kinds of removed tokens — never conflated

select and dedupe remove genuine redundancy — losslessly; nothing of value is lost. truncate_field and trim_to_budget discard detail and low-ranked records to hit limits you set — a deliberate completeness-for-budget trade. A good contract makes both deterministic and makes the second visible through the trace: what you chose to drop is an explicit, reviewable decision.

What it does not do

It pins shaping, not inputs. If retrieval is nondeterministic or the repo changed between runs, contexel faithfully shapes what it is handed; it cannot make varying inputs identical. It removes the largest controllable drift source — necessary, not sufficient.

It is stateless, not memory. It holds nothing between calls. Carrying decisions forward across runs is a memory layer — a different primitive.

It is lexical, not semantic. Synonym- and paraphrase-aware relevance belongs to model rerankers, which trade exactly the reproducibility this contract exists to provide. The benchmarks measure where that line sits.

The payoff

Because a backlog reworks the same repository repeatedly, a context contract is amortized: write the lens once and every story inherits it. And the failure mode changes character — when an implementation misses something, the trace lets you ask a precise question: did the relevant context even reach the model? Debugging the contract is a tractable engineering activity; debugging a black-box miss is not.

The full thesis lives in the repo: the-context-contract.md.