← Zurück zu den Notes

Building an editorial system that scales better with agentic coding

Agentic coding

The impossible connection — Domino

During the complete relaunch of undoom.news, I knew the direction I wanted to take, but did not yet have a finished picture of the editorial system. I wanted to use intermediate versions, see how they worked and learn from them what the next step should be. Agentic coding suited this approach: changes could be implemented immediately and tried out in context. The interface, article review and the assembly of daily editions gradually took shape in this way. I then began systematically consolidating the code that had emerged.

A foundation for further development

I had settled two prerequisites early on: a technical foundation using PHP and Python, and a modular structure in which individual tasks could later be changed and optimised. PHP now handles web access among other things, while Python handles editorial processing; research, review and publication each have their own responsibilities. This division was intended to leave room for further development. I could then work out the details of the editorial workflow in the running application.

Alongside it, I created a small development website under “Devnotes”. For the first time, I recorded such a plan as connected HTML documentation rather than the Markdown files I would normally use. Architecture, ideas, open decisions and development steps were clearly linked. This documentation also evolved iteratively. It helped me keep track of the current state and proceed step by step as the product changed.

Use reveals the bottlenecks

Working with the system eventually exposed a problem: articles were slow to open, scheduling them in the calendar took time, and even approving an article left the interface waiting. With just 229 articles, the delay was already disruptive. I described these observations to the coding agent and asked it to investigate the causes. Once the amount of work behind a simple action became apparent, I questioned the implementation of the data access and asked for a fundamental improvement.

Fetching the editorial state originally triggered 2,931 database queries. The interface also loaded substantial amounts of data from earlier development tools. Individual actions caused large collections to be transferred again even though only one card had changed. Image counting, too, went through every image again for each article. The modular structure had not prevented these inefficiencies, but it provided separate areas whose data access we could now rework selectively.

We separated the read paths according to their purpose. Planning receives the information needed for cards and the calendar; an open article receives its details. Earlier revisions are loaded only when expanded. Shared data is fetched in batches, and image suggestions are grouped once. A persistent backend service avoids starting a process for every request. The draft list loads 48 cards at a time, and subsequent responses transfer only changes. Over the course of the work, the number of queries for the overview fell to 15. In our measurements, the optimised backend request took about 0.2 seconds; a subsequent response with no changes contained just 237 bytes.

Assessing indexes against actual queries

I then asked for a separate review of the database indexes. The agent examined actual query plans and found, among other things, missing statistics for expression indexes that had already been created. We updated the statistics and added a targeted index for image metadata searches. One detail query remained inefficient: several search paths linked by OR were replaced by separate, index-backed queries whose results were combined. In a sample, its execution time fell from around five milliseconds to one. We deferred additional indexes for the calendar and publications because those queries already took little time with the existing dataset. The review therefore also gave us reasons to leave certain changes for later.

Caching with limited operational overhead

Only then did we look at an in-memory cache that persists across requests for the public article feed. Redis was an option, but would have introduced another service with its own configuration and operational overhead. For the existing PHP read path, we chose APCu on each server. OPcache retains compiled PHP code; APCu stores the prepared responses for each language. Editorial writes continue to check the current database state.

For this cache, we had to define when a stored result becomes invalid. Each request checks publications, completed translations, image data and image blocks to determine whether the cached response still matches the data. A withdrawal or an image block is therefore taken into account on the next request. We deliberately retain small database queries; what we avoid is repeatedly loading substantial content and preparing the response. Across 30 local measurement runs, this feed preparation step alone fell from an average of 5.95 to 0.45 milliseconds. That describes one processing step, not the total loading time in the browser.

Verifying the changes

Consolidation also involved a regression: after one change, scheduling reported an error even though the change had already been saved. The response failed during date conversion. My bug report led to a correction and a test of the complete response path. When optimising the detail query, the agent also compared the results for all 229 articles, both with and without revision history. The new query had to return the same data in the same order. After the index work, 163 backend tests passed; for the cache, we additionally checked that withdrawals, translation changes and image blocks, among other events, reliably invalidate stored responses.

My role was to use the product, identify friction and follow through on technical decisions. The agent investigated the code, took measurements on the server, implemented changes and checked their effects. The work moved from a concrete observation through a verifiable cause to a bounded change. I found the sequence particularly useful: the early iterations clarified which system I actually needed; the subsequent consolidation aligned data access with that use. Measurements on our dataset do not yet establish how well the system will cope with very large volumes of data. They show which unnecessary repetitions we eliminated and where additional infrastructure has not so far promised enough benefit.