Week 10: Context trimming, and the first draft of /paper

by Armaan Gupta

week
gsoc
gsoc2026
McpServerAndAgent
week#10
Phase-2
Phase-3

This week finished the last piece of the chat interface’s second milestone, then started the thing the interface was built to carry: /paper, an agent that researches a topic in the catalogue and writes it up.

Keeping a conversation inside the window #26

A long conversation eventually stops fitting in the model’s context window. The backend now counts tokens before each turn and drops the oldest messages until it fits, except it never drops the most recent one, even if that message alone is over budget. Better to send one oversized request and let the provider complain than send an empty one. The browser caps what it sends too, but the backend’s trim is what decides, since it can’t trust what a client sends it.

This came with a correction worth recording. My own plan doc described browser-stored history: full transcripts in IndexedDB, a thread list, conversations surviving a reload. When I sat down to build it, my mentor clarified that this was never the intent, only in-session context management was. The plan had drifted from the real design and I was about to build from the drifted version. I rewrote that section and about ten other references, and fixed the domain-gate sections too, which still described the keyword design I’d thrown away the week before. Lesson taken: when what I build differs from the plan, the plan gets fixed the same day, not “later”.

Testing it taught me something as well. My first idea was to send a 250,000-token conversation and check it didn’t fail, but it didn’t fail, the provider took it happily, so that proved nothing. Planting a codeword early and asking for it later failed the same way with and without trimming, because the model can’t recall through that much filler either way. What actually showed it working was boring: a temporary log line printing messages in and out, and the same request taking 1.4 seconds instead of 16.6.

A small fix to the rate limiter #27

Left over from the identity work: I was keying the anonymous rate limit on the exact IP. Fine for IPv4, but an IPv6 user usually has a huge block of addresses, so they could cycle through them and walk past the limit. The library ships a helper that keys by subnet, which is the right unit, and it had been warning me about this at every startup. One line, its own MR.

Starting /paper

The rest of the week was Phase 3. /paper <topic> should go away for a few minutes and come back with a cited note built from real CDLI records. The first decisions were about shape.

It runs as its own Python service over HTTP and SSE, not a subprocess and not another MCP server. A run passes through several stages and I want the browser to see each one as it happens, which MCP tool calls don’t stream well. The chat backend proxies those events into the stream it already has. The Python service talks to /mcp with its own client, since that endpoint is public anyway and proxying through the backend would buy nothing. The other decision was cost: a run is fifteen to thirty model calls against one or two for a chat turn, so two runs would eat the funded tier’s whole day, and a per-minute limit means little for a job that takes minutes. /paper is bring-your-own-key only until I can measure what a run actually costs.

Then I built it, as a state machine with seven stages: find candidates, narrow them, read and summarise each one, group them into themes, judge whether the evidence holds up, write the draft, check every citation. The evaluation stage can send it back to re-narrow if the evidence is thin, capped at two tries so a bad topic can’t loop forever. Raw transliteration is dropped after the reading stage, so only summaries reach the writing stage. By the end of the week it ran end to end from the command line against the live server and produced a real, if shallow, draft.

What’s next

Daily Work Update

# Day Date A short description of the work done
1 Monday 2026/07/27 Built the token-budget trim: counts the conversation, drops the oldest turns until it fits, never drops the newest message #26
2 Tuesday 2026/07/28 Corrected the plan doc, which described persistent browser history that was never the intent, and fixed the stale domain-gate sections while I was there
3 Wednesday 2026/07/29 Verified the trim properly after two tests that proved nothing, merged #26, then keyed the anonymous rate limit by IPv6 subnet #27
4 Thursday 2026/07/30 Settled the /paper design: its own Python service over HTTP and SSE, its own MCP client, bring-your-own-key only for now #28
5 Friday 2026/07/31 Scaffolded the pipeline as a seven-stage state machine with typed state and a capped loop back to re-narrowing #28
6 Saturday 2026/08/01 Wrote the Python MCP client and wired the search, narrowing, and reading stages to the live server #28
7 Sunday 2026/08/02 Filled in the grouping, evaluation, writing, and citation stages, and ran the whole pipeline end to end from the CLI #28