More from Alex Strick van Linschoten
It’s important to instrument your AI applications! I hope this can more or less be taken as given just as you’d expect a non-AI-infused app to capture logs. When you’re evaluating your LLM-powered system, you need to have capture the inputs and outputs both at an end-to-end level in terms of the way the user experiences things as well as with more fine-grained granularity for all the internal workings. My goal with this blog is to first demonstrate how Phoenix and litellm can work together, and then to make sure that we are able to group all spans together under a single trace. I’ll write the blog as I work so at this point I’m not sure exactly how this will turn out. Basic logging with litellm + phoenix As a reminder, here’s how we make an LLM call with litellm: import litellm completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": "What's the capital of China? Just give me the name.", "role": "user", } ], ) print(completion_response.choices[0].message.content) # prints 'Beijing' The Phoenix docs explain how to set up basic logging for litellm: install the following pip packages: arize-phoenix-otel openinference-instrumentation-litellm (litellm, obviously) set up the necessary environment variables with API key etc to ensure that traces get sent to the right account and endpoint Let’s assume we’re using the hosted Phoenix Cloud version for now. Then we can rerun our example, with some slight tweaks: import litellm from phoenix.otel import register # configure the Phoenix tracer tracer_provider = register( project_name="hinbox", # Default is 'default' auto_instrument=True, # Auto-instrument your app based on installed OI dependencies ) completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": "What's the capital of China? Just give me the name.", "role": "user", } ], ) print(completion_response.choices[0].message.content) So we first register the Phoenix tracer, specify the project (already set up in Phoenix Cloud) and then run our litellm completion as previously. In the terminal we see he following logs: 🔭 OpenTelemetry Tracing Details 🔭 | Phoenix Project: hinbox | Span Processor: SimpleSpanProcessor | Collector Endpoint: https://app.phoenix.arize.com/v1/traces | Transport: HTTP + protobuf | Transport Headers: {'api_key': '****', 'authorization': '****'} | | Using a default SpanProcessor. `add_span_processor` will overwrite this default. | | ⚠️ WARNING: It is strongly advised to use a BatchSpanProcessor in production environments. | | `register` has set this TracerProvider as the global OpenTelemetry default. | To disable this behavior, call `register` with `set_global_tracer_provider=False`. Beijing So immediately there are a lot of things to consider. It seems that we’ll want to use the BatchSpanProcessor that it suggests, and also it seems like I might not want to set this as the global tracing provider, too. In Phoenix Cloud, I see this: Basic Phoenix tracing interface As you can see, we’ve captured the input and output messages for the completion, it’s tracked the latency of the call (1.16s, which seems pretty slow actually!). There is also some sort of an annotation interface though I’ll explore that down the line maybe. I immediately notice that I’m missing things like the system attributes for where the call was made, also metadata like the temperature and other settings. I’d also like to see things like token counts (which you can get in Phoenix but they’re sort of buried) as well as the estimated cost of the call(s) and so on. We can see about adding some of that down the line. BatchSpanProcessor for production usage Let’s next move on to adding BatchSpanProcessor as the message suggested, which is as simple as adding batch=True to the tracer provider registration code. What this does is make sure that spans are processed in batches before they’re exported to Arize. This takes away some of the network costs that you incur when sending the spans one by one. I’ve also made sure to turn off the registration of this tracing provider as the global one: import litellm from phoenix.otel import register # configure the Phoenix tracer tracer_provider = register( project_name="hinbox", # Default is 'default' auto_instrument=True, # Auto-instrument your app based on installed OI dependencies set_global_tracer_provider=False, batch=True, ) completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": "What's the capital of China? Just give me the name.", "role": "user", } ], ) print(completion_response.choices[0].message.content) And I get this in the terminal: 🔭 OpenTelemetry Tracing Details 🔭 | Phoenix Project: hinbox | Span Processor: BatchSpanProcessor | Collector Endpoint: https://app.phoenix.arize.com/v1/traces | Transport: HTTP + protobuf | Transport Headers: {'api_key': '****', 'authorization': '****'} | | Using a default SpanProcessor. `add_span_processor` will overwrite this default. Beijing It’s actually somehow a bit annoying to still see a message about the fact that I’m using a default SpanProcessor. It’s unclear to me why I need to care that this is a default one. The message is taking up real estate in the logs and it seems important (otherwise why would they have included it?) but it’s also unclear to me what the alternative is and why I’d want to overwrite the default. I think for now I’ll leave it. Using the litellm callbacks as an alternative If we stray away from the official supported way to handle tracing with Phoenix, there’s also the community-supported in-built litellm option: import litellm litellm.callbacks = ["arize_phoenix"] completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": "What's the capital of China? Just give me the name.", "role": "user", } ], metadata={"PROJECT_NAME": "hinbox"}, ) print(completion_response.choices[0].message.content) This achieves a similar result, though I was unable to get the trace to land anywhere other than the default project. Arize’s docs mention a PHOENIX_PROJECT_NAME environment variable but it seems this isn’t respected or used by the litellm implementation. Indeed when I look at the implementation, I don’t see this being used anywhere, so it seems that the community-driven implementation isn’t really the way forward. I just wanted to mention it, however, since some of the ‘callback’ integrations for tracing in litellm are really nicely implemented (like the one for Langfuse, e.g.) so I wanted to try it out at least. One trace, multiple spans For anything beyond a simple LLM call, which means most real-world LLM applications, we’ll want to be capturing multiple spans as part of a single trace. LLM Tracing Tools’ Naming Conventions (June 2025) Side-note: I dug into how some of the major LLM tracing providers name their primitives. I was reassured that we seem to have coalesced around ‘trace -> span’ and that the OpenTelemetry way seems to have been adopted by most. Tracing nomenclature (June 2025) Grouping spans under a single trace I updated the code such that we now have a function that makes two separate LLM calls. I’d want them to both be registered as spans under the same trace: import litellm from phoenix.otel import register tracer_provider = register( project_name="hinbox", # Default is 'default' auto_instrument=True, # Auto-instrument your app based on installed OI dependencies set_global_tracer_provider=False, batch=True, ) def query_llm(prompt: str): completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": prompt, "role": "user", } ], ) return completion_response.choices[0].message.content def my_llm_application(): query1 = query_llm("What's the capital of China? Just give me the name.") query2 = query_llm("What's the capital of Japan? Just give me the name.") return (query1, query2) if __name__ == "__main__": print(my_llm_application()) But these just get registered as two separate traces/calls. The key bit of the documentation is the ‘Using Phoenix Decorator’ section, it seems. If I add a decorator on top of my function and get the specific tracer, it seems I am able to start to group things together: import litellm from phoenix.otel import register tracer_provider = register( project_name="hinbox", # Default is 'default' auto_instrument=True, # Auto-instrument your app based on installed OI dependencies set_global_tracer_provider=False, batch=True, ) tracer = tracer_provider.get_tracer(__name__) def query_llm(prompt: str): completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": prompt, "role": "user", } ], ) return completion_response.choices[0].message.content @tracer.llm def my_llm_application(): query1 = query_llm("What's the capital of China? Just give me the name.") query2 = query_llm("What's the capital of Japan? Just give me the name.") return (query1, query2) if __name__ == "__main__": print(my_llm_application()) This works and I see this in the Phoenix Cloud dashboard: Grouped traces under a single span See how it’s taken the function name as the name of the span. And it’s grouped those two LLM calls that happen within the function as we wanted. We can also update the decorator to denote different kinds of spans that we want to capture: The kinds of spans you can choose from I’m immediately a bit confused by the interface again, because when you click on the ‘Traces’ tab in Phoenix Cloud you actually still just see ‘spans’: Spans in the Traces tab In the documentation it isn’t clear to me how to create a trace that includes an llm span and an embedding span, for example. What’s even more frustrating is that the tracer decorator object doesn’t implement all the span types, just agent, chain and llm it seems. I tried something like this but it just ended up producing 3 separate traces in Phoenix Cloud. I looked at the documentation for using base OTEL instead of the Phoenix decorators, but there was also nothing in there on how to denote the trace instead of just the span. I was wondering if their ‘Sessions’ primitive was the way forward here, but they’re pretty clear in stating that a Session is a “sequence of traces”. So I’m at a bit of a dead end with Phoenix for now. I might return to Braintrust or Langfuse since these seem to have better support for what I’m trying to do (i.e. group spans together underneath a trace). I’m really reluctant to try to instrument hinbox with Phoenix when I’m unable even to get this basic grouping working properly with some dummy code. Update: solution from the Arize team I posted this blog on the Arize slack and they got back to me with a solution: import litellm from phoenix.otel import register tracer_provider = register( project_name="hinbox", # Default is 'default' auto_instrument=True, # Auto-instrument your app based on installed OI dependencies set_global_tracer_provider=False, ) tracer = tracer_provider.get_tracer(__name__) @tracer.llm def query_llm(prompt: str): completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": prompt, "role": "user", } ], ) return completion_response.choices[0].message.content @tracer.agent def query_agent(prompt: str): return "I am an agent." @tracer.chain def my_llm_application(): query1 = query_llm("What's the capital of China? Just give me the name.") query2 = query_llm("What's the capital of Japan? Just give me the name.") agent1 = query_agent("Who are you?") return (query1, query2, agent1) if __name__ == "__main__": print(my_llm_application()) And you can see how this looks in the Phoenix Cloud dashboard: Grouped spans Judging from the code it seems like the way the span is constructed simply depends on how you assemble the hierarchy of spans. For instance, if I wanted to consider the top-level entity for this ‘trace’ (i.e. a grouping of spans) then I could use this code: import litellm from phoenix.otel import register tracer_provider = register( project_name="hinbox", # Default is 'default' auto_instrument=True, # Auto-instrument your app based on installed OI dependencies set_global_tracer_provider=False, # batch=True, ) tracer = tracer_provider.get_tracer(__name__) @tracer.llm def query_llm(prompt: str): completion_response = litellm.completion( model="openrouter/google/gemma-3n-e4b-it:free", messages=[ { "content": prompt, "role": "user", } ], ) return completion_response.choices[0].message.content @tracer.agent def query_agent(prompt: str): return "I am an agent." @tracer.tool(name="query_embedding", description="Query embedding") def query_embedding(prompt: str): return [0.1, 0.2, 0.3] @tracer.agent def my_llm_application(): query1 = query_llm("What's the capital of China? Just give me the name.") query2 = query_llm("What's the capital of Japan? Just give me the name.") agent1 = query_agent("Who are you?") embedding1 = query_embedding("What's the capital of China? Just give me the name.") return (query1, query2, agent1, embedding1) if __name__ == "__main__": print(my_llm_application()) And now instead of this trace being of kind ‘chain’, it’s now of kind ‘agent’, which some internal spans also being of kind ‘agent’. In a conversation in the Arize Slack I got the following clarification: “Traces as the concept under”signals” is basically a unique identifier of spans (think “span” of time). See https://opentelemetry.io/docs/concepts/signals/traces/ In most cases if you filter spans by “roots” (e.g. spans that don’t have parents) and or look at the collective set of “traces” they will roughly look the same. Most of the time this is the view you want when looking at telemetry. Spans are too noisy to be looking at in isolation. While the two tabs feel largely overlapping, it’s a bit intentional as there’s actually no real object called a trace - it’s just a series of spans. You will see these abstractions in most observability platform.” The line that: “there’s actually no real object called a trace - it’s just a series of spans” Was extremely clarifying, actually. It explains the fuzziness between the spans and traces tab in the Phoenix dashboard. I also got some clarification around the missing @tracer.embbeding and @tracer.reranker decorators: “We emit spans for embedding text to vectors (like”adda”), guardrailing via thinks like guardrals or content moderation, and reranking things via things like cohere. However it’s sorta rare for people to manually write these. We will have decorators for them but right now they are typically emitted from autoinstrumentors like langgraph where there are common patterns for these things. We will have decorators for them very soon - but things like reranking are much more complex than things like tool calling so we are codifying these primitives now.” So there you have it! Some clarity. I’ll have to play around to see whether I go with the Langfuse route or the Phoenix route and which feels most ergonomic in the hinbox codebase. Appreciate the quick feedback from the Phoenix team, though!
Google released an updated iteration of their Deep Research tool that uses the new 2.5 Pro model. This was taken from a post originally made on Twitter, so please excuse the terseness. First impressions: a bit too eager to jump into a deep research task even when I just ask a clarifying question quite verbose, just like the OpenAI version. Not sure why both play this up a lot. It looks impressive but in practice I think we need more entry points into this. The ‘Executive Summary’ and other concluding headers are nice touches but I feel maybe there should be some more adherence to user requests for short reports. (I get that as UI it’s maybe weird to think for 10 mins and then spit out a very concise version, but it might actually be more useful.) I continue to be annoyed about how these Gemini DR reports handle footnotes (i.e. as endnotes whacked on at the end of the report). Almost a deal-breaker IMO. It’s almost like GDR tries to show how scholarly and serious it is by giving you these walls of prose (vs OpenAI DR which throws in a lot more bullet points). Not sure one is better than the other but would appreciate a bit more flexibility! The portability of these reports has always been not great. Yes you can export them to Google Docs but markdown (+ other options) would have been much better. In practice, this means that whenever I use GDR the report stays stuck there and I’m far less likely to share it with anyone, whereas the OpenAI DR reports I drop parts/all into a Github Gist etc. These reports have been getting better and better, all things considered. I’ve been following along and using GDR from the early days (even pre-OpenAI DR) and this latest version is the best version of it so far (as you’d hope!) (It’s also a little bit annoying that GDR has removed any way to use the older versions of GDR with Gemini Pro 2.0 and 1.5 etc. Makes it harder to actually compare these things.) Please let’s get an ipad version of the Gemini iPad app soon, too? Feels a bit regressive to have to use GDR on the web interface always. For serious research (as opposed to simply generating a nice report on some area where you don’t know much about already), all these tools remain hamstrung by the quality of the sources. In areas where I am (or very recently used to be) a leading scholar / researcher, the difference between what I’d expect (in terms of taste / discernment for picking out these sources) is especially egregious. Make the models better, yes, but have better filters + retrieval. So yeah, these tools are getting good! Kudos to the teams who are implementing this stuff. Hard to make it perform reproducibly well on so many open-ended uses. But more work to be done! IMO the really great implementations of this ‘deep research’ pattern will all be in-house where you can have control over: source selection (i.e. high-quality inputs only, not just some random things on the internet) how long it spends thinking about a particular area / loop of the research (or decides to backtrack and dig deeper etc) output types / templates / length different modalities of Q&A (sometimes you want reports, other times you want a quick question answered, other times you want visual guides etc etc.) different models for different kinds of tasks possibly you have little sub-research agents / processes which will go off and work on some hypothesis, possibly involving actual datasets / analysis of tabular data etc, something clearly missing from the current versions we have A few other things: GDR’s ‘clarification step’ (which I’ve heard them discuss on podcasts etc) is not as good or useful as the OpenAI DR clarification questions. In practice, because it’s buried under a concealment button that you have to click etc, and where the entire UI seems to be screaming at you to ‘Start Research’, you basically never update or amend the research plan. And when you do, it’s really not clear what’s changed because you don’t get some feedback or diff that your comments were understood; you just get an entire new research plan (again buried under the concealment button) Going forward we’re probably going to want / need ways of navigating the layers to this research. A global overview report will have subsections that (should you wish) can be expanded into their own more detailed or granular reports. This is how research works, after all. Not just endless new reports all trailed one after another pointed in the same direction. The other thing that I think we’re really going to need to work on is research taste. Like the LLMs that power them, GDR and OpenAI DR offer a level of research taste developed to the mean. (I know people are thinking about this since it came up on Dwarkesh’s podcast with the AI 2027 guys, but they were focused on scientific research.) I think there’s not a single answer for this which is, again, why I see the end result as people bringing these things in-house where they get to develop and refine what makes their particular flavour of research unique. (In the human-generated research world this is very much the case, where certain institutions (or even particular authors) are known for how deep they go, or what kinds of sources they prefer, or how they choose to feature or highlight the primary sources they access, and so on.) There are many possible variations of how this manifest, and I hope that we’re headed into a world where all the AI ‘deep researchers’ will be unique and quirky in all the best senses of that word.
I took the past week off to work on a little side project. More on that at some point, but at its heart it’s an extension of what I worked on with my translation package tinbox. (The new project uses translated sources to bootstrap a knowledge database.) Building in an environment which has less pressure / deadlines gives you space to experiment, so I both tried out a bunch of new tools and also experimented with different ways of using my tried-and-tested development tools/processes. Along the way, there were a bunch of small insights which occurred to me so I thought I’d write them down. As usual with this blog, I’m mainly writing for my future self but I think there might be parts that are useful for others! Apologies for the somewhat rushed nature of these observations; better I get the blog finished and published than not at all! 🤖 Local Models During this project, I experimented with several local models, which continue to impress me with their evolving capabilities. The recent launch of gemma3 was particularly timely - I found myself regularly using the 27B version, which performed admirably across various tasks. There are three or four models I keep returning to. mistral-small stands out as an exceptional model that’s been relatively recently updated and seems a bit underrated / underappreciated. The original mistral model continues to hold up remarkably well, particularly for structured extraction tasks and general writing needs like summarization. One important realization when working with real-world use cases: benchmarks can be deceptive. While helpful as general indicators, each model has its own strengths and quirks. Many newer models are heavily optimized for structured data extraction, but their performance ultimately depends on whether their training documents align with your specific use case. It’s crucial to test models against your actual requirements rather than relying solely on published benchmarks. For robust results with local models, I’ve found that implementing a “reflection, iterate and improve” pattern significantly enhances performance. When you need a model to summarize or analyze content in a particular format, having a secondary model (or even the same model!) review the output against the original prompt requirements is incredibly valuable. This reviewer model can suggest improvements to better fulfill the original request. Running this loop for 2-5 iterations (depending on complexity) can yield results approaching those of proprietary models like Claude or GPT-4, which might achieve similar quality in a single pass. For local deployments, this iterative improvement pattern is essentially non-negotiable. I also explored vision models, particularly llava and llama-3.2-vision. These were my primary tools for extracting context from images, generating captions, and analyzing visual content. Their effectiveness varies based on content type and language, but they represent impressive capabilities that can run entirely on local systems. A significant portion of my work involved non-English languages, including some relatively rare ones. This is another area where benchmark claims about supporting “hundreds of languages” often don’t align with real-world performance. Models might list impressive language coverage in their specifications, but actual proficiency varies dramatically. It reinforces my earlier point - always verify benchmark claims against your specific use case before committing to a particular model. 💬 Prompting & Instruction Following Working extensively with various models during this project reinforced some fundamental insights about prompting that might seem basic, but prove critical in practical applications. These observations are particularly relevant when working with local models, though they apply to cloud-based systems as well. Context matters significantly more than we might assume. While we’ve grown accustomed to proprietary models like Claude or GPT-4o performing admirably with minimal guidance, local models require more deliberate direction. The more relevant context you can provide (within reasonable token limits), the better your results will be. If you would naturally provide certain background information to a human performing the task, make sure to include it in your prompt to the model as well. Another key insight: every model has its unique characteristics. Techniques that work brilliantly with one model might fall flat with another, especially in the local model ecosystem. They each require slightly different prompting approaches, specific phrasing patterns, and tailored guidance. This necessitates running small experiments to understand how different models respond to various prompting styles. It’s still more art than science, but this experimentation phase is crucial when implementing local models effectively. Perhaps the most valuable lesson I rediscovered is that breaking complex tasks into smaller components yields superior results compared to using a single comprehensive prompt. This is particularly true with local models. When performing extensive data extraction or when dealing with structured data where the extraction targets differ significantly from each other, don’t expect the model to handle everything in one pass – even a human might struggle with such an approach. Instead, break down the task into logical components, create targeted mini-prompts for each aspect, and then recombine the results once all the separate LLM calls are completed. Yes, this approach adds processing time and complexity, but the quality improvement is well worth the trade-off. When accuracy matters more than speed, this decomposition strategy consistently delivers better outcomes. 🧰 Process & Tools My development environment during this project provided plenty of opportunities to evaluate various tools and workflows. As context, I primarily work on a Mac while maintaining access to a separate (local) machine with GPU capabilities for more intensive tasks. This setup allows me to flexibly experiment with both local and cloud-based models. For managing local models, Ollama continues to be my go-to solution for downloading, running, and interfacing with these models. A recent discovery that significantly improved my workflow is Bolt AI, an excellent Mac interface that provides seamless switching between local Ollama models and cloud-based alternatives. If you’re working in a hybrid model environment, Bolt AI is definitely worth exploring. I’ve also recently integrated OpenRouter into my toolkit, which solves the problem of managing countless API keys across different inference providers. OpenRouter not only offers native connections to many cloud providers but also allows you to incorporate your own API keys, streamlining access to a diverse model ecosystem through a unified interface. It also helps with setting spend limits on various models or projects. In terms of development insights, I was impressed by how rapidly front-end development can progress with the assistance of models like Claude 3.7 and OpenAI’s O1-Pro. These models perform exceptionally well when supplemented with documentation (such as an llms.txt file) alongside your prompts. While I can’t speak to their effectiveness with extremely complex applications or massive frontend codebases, they demonstrate remarkable proficiency with small to medium-sized projects. A significant portion of my experimentation involved RepoPrompt, a tool that recently transitioned from free beta to a paid license model. RepoPrompt addresses the challenge of getting your codebase into an LLM-friendly format. Unlike standard CLI tools that simply export code to clipboard or text files, RepoPrompt generates a structured XML representation that, when modified by an LLM and pasted back, creates a reviewable diff of the proposed changes. At least, that’s one of the things it allows you to do! It’s actually a bit more powerful / flexible than that and here’s a video so you can see it in action: RepoPrompt Demo Video While tools like Cursor and Windsurf offer similar functionality, they tend to become less reliable as project complexity increases. RepoPrompt shines when paired with an OpenAI Pro subscription, enabling effective integration of models like O1 Pro and o3-mini-high into your development lifecycle. In my testing, the RepoPrompt + O1 Pro/O3 Mini High combination consistently delivered superior results compared to using Cursor with Claude 3.7 (even with ‘Thinking Mode’ enabled). Despite the occasional pauses while these models process complex problems, the quality improvement justifies the wait. Additionally, I continued working with Claude Code and CodeBuff, both CLI-driven tools focused on code improvement. Of the two, CodeBuff has become my preferred option. Both tools require careful supervision—I typically keep Cursor open to monitor changes in real-time, occasionally needing to revert modifications or redirect the approach. These tools excel when you clearly articulate your objectives and maintain oversight of the implementation process. CodeBuff particularly impresses with larger codebases and demonstrates superior stability overall. An interesting pattern emerged during development: whenever files approached 800-900 lines, it signaled the need to refactor into smaller submodules to maintain LLM comprehension, especially when using agent mode in Cursor. The modular approach significantly improved model performance. I was genuinely surprised by the effectiveness of the RepoPrompt and O1 Pro combination. For smaller, targeted modifications, CodeBuff continues to demonstrate remarkable capability. While I didn’t evaluate these tools in conjunction with local models, I suspect such combinations would require more iterative refinement to achieve comparable results. 🧑🔬 Software Engineering Patterns Throughout this experimental project, several software engineering principles proved particularly valuable when working with LLM-assisted development. These patterns aren’t revolutionary, but their importance amplifies in the context of AI-augmented workflows. The principle of simplicity served as a cornerstone approach. Breaking development into the smallest logical next task repeatedly demonstrated its value, especially during the exploratory phases when project architecture was still taking shape. While some engineers might possess the cognitive bandwidth to fully conceptualize complex systems with perfect abstractions from the outset, I’ve found incremental development leads to more robust outcomes. This approach aligns naturally with how most developers actually think through problems and provides clear checkpoints for evaluating progress. Data visibility emerged as another critical factor. When leveraging LLM-assisted coding, comprehensive logging becomes even more essential than in traditional development. Strategically placed log outputs create a diagnostic trail that proves invaluable when troubleshooting unexpected behaviors. This practice creates a feedback loop that strengthens both your understanding of the system and the LLM’s ability to assist effectively. A particularly underappreciated practice I haven’t seen widely discussed is the importance of dead code detection. When working with LLM-assisted development, code cruft tends to accumulate more rapidly than in conventional programming. Tools like deadcode and vulture provide static analysis of Python projects to identify unused functions and variables. Running these tools periodically helps maintain codebase clarity by flagging remnants that might otherwise cause confusion during review. I’m not certain whether newer tools like ruff from Astral include this functionality (particularly for function calls), but the capability is invaluable for maintaining a clean, navigable codebase. Taking time to think offline—away from the keyboard—often yields surprising clarity. This deliberate pause creates space to articulate precisely what you need for the next development increment. When you can express your requirements with precision, the LLM’s output improves proportionally. Ambiguous instructions inevitably produce suboptimal results, whereas clarity fosters efficiency. A final observation worth emphasizing: having experience as an engineer in the pre-LLM era remains tremendously advantageous. When confronting complex workflows involving chained LLM calls with interdependencies and reflection patterns, traditional debugging skills become indispensable. Knowing when to step away from AI assistance and dive into manual debugging with tools like pdb, stepping through code execution and inspecting variables directly, represents a crucial judgment call. LLMs and coding agents often demonstrate a bias toward generating new code rather than methodically analyzing existing problems. Recognizing the moment when direct human intervention becomes more efficient than continually prompting an AI is a skill that comes with experience. Once you’ve manually identified the underlying issue, you can return to the LLM with precisely targeted prompts that yield superior results. 🌐 Appendix 1: FastHTML As a practical addition to my experimentation, I implemented FastHTML for the first time to build a frontend for my knowledge base extraction assistant. The experience was remarkably frictionless, particularly when leveraging their llms.txt file—a markdown-formatted documentation set that integrates seamlessly with your frontend codebase when provided alongside prompts. This approach works exceptionally well with models like O1 Pro or O3 Mini High, creating a development workflow that feels intuitive and responsive. Despite having substantial JavaScript experience from previous roles, I found FastHTML significantly more manageable than complex JavaScript frameworks that dominate the ecosystem today. The reduced cognitive overhead and natural integration with Python-based workflows makes FastHTML a compelling choice for ML practitioners who prefer to minimize context-switching between languages and paradigms. The framework strikes an excellent balance between capability and simplicity that aligns perfectly with rapid prototyping and iterative development cycles common in ML projects. For those building interfaces to ML systems, it’s definitely worth considering as your frontend solution. 📃 Appendix 2: OCR + Translation Another interesting challenge I tackled involved OCR and translation of handwritten documents in non-English languages—a task that proved impossible to accomplish in a single pass with local models, particularly for less common languages. The solution emerged through methodical problem decomposition: Breaking down PDFs into individual page images Segmenting each page into overlapping image chunks (critical for handwriting where text may slant across traditional line boundaries) Applying OCR to extract text in the original source language from each image segment Using translation models to convert the extracted text to English This multi-stage pipeline allowed me to overcome the limitations of local models when confronted with the combined complexity of handwriting recognition and translation. Both gemma3 and llama-3.3 performed admirably within this decomposed workflow, demonstrating that even resource-constrained local deployments can achieve impressive results when problems are thoughtfully restructured. This case exemplifies a core principle of effective ML implementation: when dealing with complex, multi-faceted challenges, breaking them into targeted sub-problems often yields better outcomes than attempting end-to-end solutions—especially when working with constrained computational resources. While this approach may increase processing time, the quality improvement justifies the trade-off for many practical applications.
Finally back on track and reading the next chapter of Chip Huyen’s book, ‘AI Engineering’. Here are my notes on the chapter. Overview and Core Philosophy “Data will be mostly just toil, tears and sweat.” This is how we start the chapter :) This candid assessment frames dataset engineering as a discipline that requires both technical sophistication and pragmatic persistence. While the chapter’s placement might have been suitable earlier in the book, its position allows it to build effectively on previously established concepts. Data Curation: The Foundation Data curation addresses various use cases including fine-tuning, pre-training, and training from scratch, with specific considerations for chain of thought reasoning and tool use. The process addresses three fundamental aspects: Data Quality: The equivalent of ingredient quality in cooking Data Coverage: Analogous to having the right mix of ingredients Data Quantity: Determining the optimal volume of ingredients Quality Criteria Data quality encompasses multiple dimensions: Relevance to task requirements Consistency in format and structure Sufficient uniqueness Regulatory compliance (especially critical in regulated industries) Coverage Considerations Coverage involves strategic decisions about data proportions: Large language models often utilize significant code data (up to 50%) in training, which appears to enhance logical reasoning capabilities beyond just coding Language distribution can be surprisingly efficient (even 1% representation of a language can enable meaningful capabilities) Training proportions may vary across different stages of the training process Quantity and Optimization A key phenomenon discussed is ossification, where extensive pre-training can effectively freeze model weights, potentially hampering fine-tuning adaptability. This effect is particularly pronounced in smaller models. Key quantity considerations include: Task complexity correlation with data requirements Base model performance implications Model size considerations (OpenAI notes that with ~100 examples, more advanced models show superior fine-tuning performance) Potential for using lower quality or less relevant data for initial fine-tuning to reduce high-quality data requirements Recognition of performance plateaus where additional data yields diminishing returns Data Acquisition Process The chapter provides a detailed example workflow for creating an instruction-response dataset: Initial dataset identification (~10,000 examples) Low-quality instruction removal (reducing to ~9,000) Low-quality response filtering (removing 3,000) Manual response writing for remaining high-quality instructions Topic gap identification and template creation (100 templates) AI synthesis of 2,000 new instructions Manual annotation of synthetic instructions Final result: 11,000 high-quality examples Data Augmentation and Synthesis Synthesis Objectives Increasing data quantity Expanding coverage Enhancing quality Addressing privacy concerns Enabling model distillation Notable Research: An Anthropic paper (2022) found that language model-generated datasets can match or exceed human-written ones in quality for certain tasks. Note that some teams actually prefer AI-generated preference data due to human fatigue and inconsistency factors. Synthesis Applications The chapter distinguishes between pre-training and post-training synthesis: Synthetic data appears more frequently in post-training Pre-training limitation: AI can reshape existing knowledge but struggles to synthesize new knowledge LLaMA 3 Synthesis Pipeline A comprehensive workflow example: AI generation of problem descriptions Solution generation in multiple programming languages Unit test generation Error correction Cross-language translation with test verification Conversation and documentation generation with back-translation verification This pipeline generated 2.7 million synthetic coding examples for LLaMA 3.1’s supervised fine-tuning. Model Collapse Considerations The chapter addresses the risk of model collapse in synthetic data usage: Potential loss of training signal through repeated synthetic data use Current research suggests proper implementation can avoid collapse Importance of quality control in synthetic data generation Model Distillation Notable example: BuzzFeed’s fine-tuning of Flan T5 using LoRa and OpenAI’s text-davinci-003 generated examples, achieving 80% inference cost reduction. Data Processing Best Practices Expert Tip: “Manual inspection of data has probably the highest value to prestige ratio of any activity in machine learning.” - Greg Brockman, OpenAI co-founder Processing Guidelines The chapter emphasizes efficiency optimization: Order optimization (e.g., deduplication before cleaning if computationally advantageous) Trial run validation before full dataset processing Data preservation (avoid in-place modifications) Original data retention for: Alternative processing needs Team requirements Error recovery Technical Processing Approaches Deduplication strategies include: Pairwise comparison Hashing methods Dimensionality reduction techniques Multiple libraries are referenced (page 400) for implementation. Data Cleaning and Formatting HTML tag removal for signal enhancement Careful prompt template formatting, crucial for: Fine-tuning operations Instruction tuning Model performance optimization Data Inspection The chapter emphasizes the importance of manual data inspection: Utilize various data exploration tools Dedicate time to direct data examination (recommended: 15 minutes of direct observation) Consider this step non-optional in the process
More in AI
Really excited to share some work we did with Xint.io. We don’t always get to publicly share what we do. We have permission to share this one, and it is some public good. Xint.io provides a code vulnerability scan service that helps flag Common Vulnerabilities and Exposures (CVEs) in a […]
The Generative AI bubble may finally have popped.
If you are helping build something that might kill everyone, maybe stop doing that.
Today's links Autodoxing: The road to hell is paved with badly thought-through tech policy. Hey look at this: Delights to delectate. Object permanence: Shakespeare programming language; DeCSS is "pure speech"; Blue Coat sold surveillance tools to Syrian secret police; "Extraordinary Catalog of Peculiar Inventions"; Cable prices rose at double the rate of inflation for 20 years; Supercuts; Antivax x Trump. Upcoming appearances: Kilkenny, Courtenay, Vancouver, Folkestone, Oxford, Sheffield, Virtual, Oxford, Montreal. Recent appearances: Where I've been. Latest books: You keep readin' em, I'll keep writin' 'em. Upcoming books: Like I said, I'll keep writin' 'em. Colophon: All the rest. Autodoxing (permalink) After 25 years of fighting for human rights amid the wreckage of bad tech policy, I'm here to tell you that the road to hell is paved with badly thought-through tech policy. The most galling thing about this wreckage is that it was both foreseeable and foreseen. Back when Sun Microsystems CEO Scott McNealy was telling the world "Privacy is dead, get over it," digital rights advocates were saying, "Not only are digital tools pretty dang good at helping you protect your privacy, but a society-wide abolition of privacy sets us up for infinite harms long into the future": https://pluralistic.net/2025/10/31/losing-the-crypto-wars/#surveillance-monopolism It wasn't just business leaders who hated privacy, it was also our political classes. Bill Clinton tried to put a backdoor into every microchip (no, really): https://en.wikipedia.org/wiki/Clipper_chip The NSA managed to enact a total prohibition on civilian access to working encryption, on the grounds that we're all safer if our spooks can spy on "bad guys" even if that means that every creep, petty crook, corporate or state-sponsored spy, and mafia can spy on us: https://pluralistic.net/2026/04/09/bernstein-2/#win-some-lose-some Even after that ban was overturned, governments encouraged the private sector to spy on us without limit, precaution or permission. Those private-sector troves of intimate, compromising data about every person alive constitute a warrantless, off-the-books surveillance system that cops, spies and government agencies can tap at will: https://pluralistic.net/2026/10/13/petards-r-us/#nobus To protect these surveillance capabilities, private firms and governments colluded to pass "anticircumvention" laws that make it illegal to reverse engineer our devices and apps to add privacy blockers to them, with the effect that every device we own and every app we use spies on us constantly: https://pluralistic.net/2024/03/12/market-failure/#car-wars At the same time, states either failed to pass new privacy laws (as in the USA, where federal consumer privacy law was last updated in 1988), or passed privacy laws that went unenforced (as in the EU, where "pretending to be Irish" allows US tech giants to flout EU privacy law): https://pluralistic.net/2023/05/15/finnegans-snooze/#dirty-old-town Our surveillance free-for-all world is objectively very bad. Over and over, companies and agencies who have been permitted to nonconsensually harvest and retain our data demonstrate that they are not capable of keeping that data safe, and no matter how badly they fuck up, nothing changes. It's been nearly a decade since Equifax doxed 148 million Americans and not only is the company still in business, they've been permitted to buy up and absorb more commercial surveillance firms. US regulators let Equifax buy Teletrack in 2021. Canadian regulators let them buy the Food Industry Credit Bureau in 2023. The same year, Brazilian regulators let Equifax buy Boa Vista Serviços. This all despite the fact that Equifax blamed its breach on shitty security practices after a string of mergers that led to it haphazardly combining databases from newly acquired surveillance companies. Data isn't the new oil. Data is the new oily rag: profitable only if you can warehouse it in vast quantities under unsafe conditions and have society pick up the bill for every conflagration: https://locusmag.com/feature/cory-doctorow-zucks-empire-of-oily-rags/ Equifax is a serial reoffender, but rather than getting the corporate death-penalty, they've been made to pay coins-down-the-back-of-the-sofa fines that amount to a fraction of a fraction of a fraction of the costs they imposed on society by amassing these vast, flaming storehouses of oily rags. Why wouldn't Equifax reoffend? Our policymakers keep demanding less privacy, not more, and their indifference to the consequences is (almost) comical. Take Malcolm Turnbull, the former Australian Prime Minister who uttered what remains, without a doubt, the single stupidest thing anyone anywhere has ever said about tech policy. Like many of his peers before, during, and since his reign, Turnbull had been trying to outlaw working encryption, insisting that every device have a Bill Clinton-esque back-door for the purpose of spying on "bad guys." When told that the laws of mathematics dictated that any measure that let Australia's cops spy on "bad guys" would also let bad guys spy on Australians, Turnbull replied, "The laws of mathematics are very commendable, but the only law that applies in Australia is the law of Australia": https://www.newscientist.com/article/2140747-laws-of-mathematics-dont-apply-here-says-australian-pm/ We could laugh Turnbull off…if he was an outlier. But while Turnbull had the world's stupidest rejoinder to the scientifically irrefutable fact that banning encryption will let bad guys hack everything, he was hardly alone in wanting to ban encryption. Banning encryption is current policy in Canada: https://blog.mozilla.org/netpolicy/2026/10/08/canadas-bill-c-22-threatens-encryption-users-privacy-and-the-security-of-digital-economies/ It's also current policy in the UK: https://pluralistic.net/2023/03/05/theyre-still-trying-to-ban-cryptography/ And in the EU: https://fightchatcontrol.eu/ The difference between Malcolm Turnbull and, say, Canadian PM Mark Carney is that Turnbull was stupid enough to try to defend his ban on encryption, while Carney simply shakes his head at his critics' naivete and continues to press forward with his catastrophic proposal to welcome every cybercriminal in the world to hack every morsel of Canadian data. When it comes to bad privacy policy, our political classes aren't content to rest on their laurels: hardly a week goes by without news of a new way to encourage (or even require) the private sector to recklessly collect and retain our data, despite the dead certainty that this data will eventually end up breaching and getting plastered all over the internet. Take "age verification" laws. There's no such thing as "age verification" – there's just spying on everyone, all the time, creating (even more) records of all of our internet activities: https://pluralistic.net/2026/05/19/shes-dead-of-course/#consensus-hallucination Every promise of "privacy preserving" age verification is bullshit: https://pluralistic.net/2025/08/14/bellovin/#wont-someone-think-of-the-cryptographers If you think kids are experiencing internet harms today, just wait until we expose them to the harms of having even more of their data harvested, stored, breached, and weaponized against them: https://pluralistic.net/2026/06/23/destroy-the-village/#to-save-it If you want to protect kids from algorithmic manipulation and targeting by bad actors, what about banning spying on kids so no one can target them? https://made-not-found-by-danah-boyd.ghost.io/no-one-paid-me-to-disagree-with-jonathan-haidt-it-was-teenagers-who-taught-me-that-he-was-wrong/ Now, at this point, you might be thinking, "How can we know who the kids are so we know who not to spy on?" You can't. In order to not spy on kids, you'd just have to not spy on anyone: https://pluralistic.net/2023/04/09/how-to-make-a-child-safe-tiktok/ "Don't spy on anyone" is the kind of thing that has a reasonable chance of enhancing online privacy. But in the time-honored tradition of "we've tried nothing and we're all out of ideas," governments keep finding ways to make digital privacy worse, for example, by procuring Flock cameras for every lamp-post: https://www.eff.org/deeplinks/2026/09/high-crime-lmao-how-cops-are-treating-mass-surveillance-joke And then there's UK Labour: after the success of a pilot program for an incredibly effective and safe privacy-preserving way of analyzing the country's health records for research processes, they commissioned the Goldacre Report to evaluate the possibility of making this permanent: https://pluralistic.net/2024/03/08/the-fire-of-orodruin/#are-we-the-baddies After that report was completed and had a chance to be metabolized by policymakers, Labour acted upon it…by throwing it out and handing all of the country's health data to Palantir, a serial mishandler of data and violator of privacy: https://www.theguardian.com/technology/2026/sep/30/legal-objections-palantir-nhs-platform-handling-data Here's the scariest thing about bad privacy policy: it has a long fuse. The harms of commercial and private-sector digital surveillance can occur years or even decades after the decision to permit it. There's a straight line from the post-9/11 decision to encourage public agencies to gather, merge and warehouse data on every person in America, and the ability of ICE ethnic cleansing squads to break down people's doors and drag them off to concentration camps; just as there's a straight line from the decision to do nothing about the breach of every Social Security Number and the later theft of all Social Security data by adolescent DOGE criminals: https://www.pcmag.com/news/is-your-ssn-in-the-national-public-data-breach-heres-how-to-find-out But despite how obvious these connections are, the fact that they're separated by a lot of time and space provides our policymakers with cover for making terrible decisions with foreseeably terrible outcomes. As a society, we lack object permanence, the ability to connect acts in the past with present-day phenomena. When the press reports on a "Black October" of catastrophic, AI-fueled data breaches and hack attacks, they don't mention a generation of policymakers' indifference and hostility to information security: https://pluralistic.net/2026/09/12/god-in-the-box/#llms-are-fake Policymakers benefit from politics' eternal now, in which disasters simply befall us, without any history or precedent or cause. Remember the One Big Beautiful Bill? Its architects designed it to wreak its worst harms after the midterms, with Medicaid work rules and food aid restrictions starting in December, and cuts to affordable housing and rural hospitals phasing in through the year: https://www.tumblr.com/mostlysignssomeportents/832146932089700352 Black October is going to give way to Black November, Black December, Black 2027, and the Black 2030s – a string of cybersecurity catastrophes that will befall us until policymakers decide that keeping us safe is more important than making it easy for them to attack their chosen "bad guys." Here's a preview of Black November: there's a new strain of the Volta infostealer malware that uses AI tools to dox you even as it hacks you: https://www.wired.com/story/kernel-panic-volta-autodoxer-ai-infostealer-malware/ How that works: your computer gets hacked and your data gets stolen, then it gets merged with breach data from Ticketmaster, Disney, AT&T and other companies that have recklessly compiled vast troves of data on all of us, stored it insecurely, and leaked it onto the internet where it will live forever. Volta's makers prompt their LLM "to act like an intelligence analyst and produce structured citation-backed assessment of the victim." This lets the criminals prioritize their victims based on the expected return from exploiting them. It augments this dossier by combing through your Gmail as well as your Doordash and Uber Eats profiles. This merged data makes it much easier for criminals to call you up or email you (or have a chatbot call you up or email you) and trick you into giving up even more data: https://pluralistic.net/2024/02/05/cyber-dunning-kruger/#swiss-cheese-security Volta seems especially interested in younger internet users, targeting "Discord, Telegram, Steam, and Roblox sessions." Of course, once our "age verification" services breach vast, eternally persistent, detailed troves of data on young internet users, this kind of AI-supercharged malware will be able to target kids with much greater fidelity and efficacy. AI capabilities have made infostealers much worse, but the reason for that new potency lies in how AI capabilities are supercharged by breach data that is the foreseeable and foreseen consequence of generations of hostility to privacy on the part of policymakers and corporations who responded to the incentives policymakers created. We are living in the age of oily rags. For decades we fucked around with letting the private sector warehouse our data and weaken our security so that public sector agencies could plunder our data and our computers at will. Now, we keep finding out that everything is on fire. It's going to take a long time to fix this, but we can do it. We can build more secure systems. We can make it much more of a legal ball-ache for companies to gather our data in the first place, and we can legalize and promote the creation of privacy blockers for browsers, phones, computers, cars, thermostats and smart speakers. We can enact corporate death-penalties for breaches to reflect the true societal cost of warehousing our data. We can ban Flock cameras and tear down the post-9/11 "fusion centers" that neatly package up our data for use by DOGE fascists and ICE thugs. We can ban our spy agencies from hoarding defects in widely used software, and instead require them to divulge these bugs so vendors can fix them: https://en.wikipedia.org/wiki/NOBUS And before we do any of that, we can just stop making it worse by ending the campaigns to require companies to spy on us and deliberately introduce security defects into their products. Hey look at this (permalink) ICE wants its mask back, and hired a company to ask https://blog.flokinet.is/2026/10/29/ice-wants-its-mask-back-and-hired-a-company-to-ask/ Is There Life After AI? Cory Doctorow Thinks So. https://jacobin.com/2026/10/ai-bubble-labor-copyright-competition Online censorship is crushing indie webcomics https://xtramagazine.com/culture/online-censorship-indie-webcomics-284463 Fascists Thrive On Bad Economics https://economicpopulist.substack.com/p/fascists-thrive-on-bad-economics PrivacySafe Foundation Releases Free Security Tools to Combat Global Cyber Threats https://www.einpresswire.com/article/952907125/privacysafe-foundation-releases-free-security-tools-to-combat-global-cyber-threats Object permanence (permalink) #25yrsago NY Post blames pagans from Indianapolis for spam malware https://web.archive.org/web/20011103073038/http://www.nypost.com/news/regionalnews/33077.htm #25yrsago The Shakespeare Programming Language https://web.archive.org/web/20011207071324/http://shakespearelang.sourceforge.net/report/shakespeare/ #25yrsago Appeals court: DeCSS is "pure speech" https://web.archive.org/web/20011102003502/https://www.wired.com/news/business/0,1367,48075,00.html #20yrsago Apple drops Trusted Computing https://web.archive.org/web/20070106150844/https://www.osxbook.com/book/bonus/chapter10/tpm/ #20yrsago Microsoft threatens China pull-out over jailed bloggers http://news.bbc.co.uk/1/hi/technology/6102180.stm #15yrsago Righthaven copyright troll’s victims send in US marshals to seize its assets https://arstechnica.com/tech-policy/2011/11/us-marshals-turned-loose-to-collect-6372080-from-righthaven/ #15yrsago Supercuts: rapid-cut videos that collect repetition https://web.archive.org/web/20111103190737/http://www.wired.com/epicenter/2011/11/supercut/ #15yrsago Study: why parents help their underage kids pretend to be 13 in order to use Internet sites https://firstmonday.org/ojs/index.php/fm/article/view/3850/3075 #15yrsago Blue Coat “deep packet inspection” tools used by Syrian secret police and other repressive regimes https://www.eff.org/deeplinks/2011/10/blue-coat-acknowledges-syrian-government-use-its-products #15yrsago Extraordinary Catalog of Peculiar Inventions: awesomely dangerous pranks from the age of fraternal lodges https://memex.craphound.com/2011/11/01/extraordinary-catalog-of-peculiar-inventions-awesomely-dangerous-pranks-from-the-age-of-fraternal-lodges/ #10yrsago Cable prices have risen at more than double the rate of inflation for 20 years https://cordcutting.com/news/cable-prices-have-risen-faster-than-inflation-for-each-of-the-past-20-years/ #10yrsago Typical English village mystified by its cult status among Chinese tourists https://www.bbc.com/news/uk-england-oxfordshire-37820663 #10yrsago Nebraska’s billionaire governor couldn’t preserve the death penalty through the law, so now he’s buying a ballot initiative https://www.motherjones.com/politics/2016/11/ricketts-nebraska-death-penalty/ #10yrsago Anti-vaxx conspiracy leaders back Donald Trump, claim it’s mutual https://scienceblogs.com/insolence/2016/11/02/donald-trump-and-disgraced-antivaccine-scientist-andrew-wakefield-best-buds-forever #10yrsago New, fast-spreading IoT botnet hybridizes two less-effective strains to achieve quick dominance https://arstechnica.com/information-technology/2016/11/new-iot-botnet-that-borrows-from-notorious-mirai-infects-3500-devices/ #1yrago There's one thing EVERY government can do to shrink Big Tech https://pluralistic.net/2025/11/01/redistribution-vs-predistribution/#elbows-up-eurostack Upcoming appearances (permalink) Kilkenny (Kilkenomics), Nov 6-8 https://kilkenomics.com/ Courtenay: Enshittification (Sid Williams Theatre Society), Nov 10 https://www.sidwilliamstheatre.com/events/cory-doctorow-talks-enshittification/ Vancouver: BC Policy Solutions Gala, Nov 12 https://bcpolicy.ca/gala/ Folkestone Writers Festival, Nov 16 https://www.creativefolkestone.org.uk/whats-on/cory-doctorow-the-internet-is-broken-now-what/ Oxford: Ministry of the Future, Nov 23 https://ministryforthefuture.co.uk/events.html Sheffield: Reverse Centaur at Juno Books, Nov 25 https://www.outsavvy.com/event/40274/cory-doctorow-in-conversation Virtual: Enshittification, AI, and the Future of Technology (UBC Master of Educational Technology), Nov 28 https://met.ubc.ca/2026/09/met-speaker-series-cory-doctorow-enshittification-ai-and-the-future-of-technology/ Oxford: Bennett Oxford Symposium on Open Code in Science, Dec 9 https://www.bennett.ox.ac.uk/events/2026-bennett-institute-symposium/ Montreal: World Science Fiction Convention, Sep 2-6 https://montreal2027.ca/en Recent appearances (permalink) Clint Eastwood (No Gods No Mayors) https://www.patreon.com/NoGodsNoMayors/posts/clint-eastwood-173008088 Life After AI (Brain Bar) https://www.youtube.com/watch?v=IPPa43-n0vI Terms of Service with Clare Duffy (CNN) https://www.cnn.com/audio/podcasts/terms-of-service-with-clare-duffy/episodes/458ce968-af5d-11f0-b539-13ed2afe25f8 AI, Work, and Power (Software Engineering Daily) AI, Work, and Power https://softwareengineeringdaily.com/podcasts/cory-doctorow-on-ai-work-and-power/ AI, Corporate Power, and the Fight for Worker Control (Plutopia) https://plutopia.io/cory-doctorow-ai-corporate-power-and-the-fight-for-worker-control/ Latest books (permalink) "The Reverse-Centaur's Guide to AI," a short book about being a better AI critic, Farrar, Straus and Giroux, June 2026 https://us.macmillan.com/books/9780374621568/thereversecentaursguidetolifeafterai/ "Canny Valley": A limited edition collection of the collages I create for Pluralistic, self-published, September 2025 https://pluralistic.net/2025/09/04/illustrious/#chairman-bruce "Enshittification: Why Everything Suddenly Got Worse and What to Do About It," Farrar, Straus, Giroux, October 7 2025 https://us.macmillan.com/books/9780374619329/enshittification/ "Picks and Shovels": a sequel to "Red Team Blues," about the heroic era of the PC, Tor Books (US), Head of Zeus (UK), February 2025 (https://us.macmillan.com/books/9781250865908/picksandshovels). "The Bezzle": a sequel to "Red Team Blues," about prison-tech and other grifts, Tor Books (US), Head of Zeus (UK), February 2024 (thebezzle.org). "The Lost Cause:" a solarpunk novel of hope in the climate emergency, Tor Books (US), Head of Zeus (UK), November 2023 (http://lost-cause.org). "The Internet Con": A nonfiction book about interoperability and Big Tech (Verso) September 2023 (http://seizethemeansofcomputation.org). Signed copies at Book Soup (https://www.booksoup.com/book/9781804291245). "Red Team Blues": "A grabby, compulsive thriller that will leave you knowing more about how the world works than you did before." Tor Books http://redteamblues.com. "Chokepoint Capitalism: How to Beat Big Tech, Tame Big Content, and Get Artists Paid, with Rebecca Giblin", on how to unrig the markets for creative labor, Beacon Press/Scribe 2022 https://chokepointcapitalism.com Upcoming books (permalink) "The Post-American Internet," a geopolitical sequel of sorts to Enshittification, Farrar, Straus and Giroux, 2027 "Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027 "Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027 "The Memex Method," Farrar, Straus, Giroux, 2027 Colophon (permalink) Today's top sources: Currently writing: “Once Is Enemy Action,” a science fiction novel about the origins of modern technofascism. Today's words: 574 (7730 total). "The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor. A Little Brother short story about DIY insulin PLANNING This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net. https://creativecommons.org/licenses/by/4.0/ Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution. How to get Pluralistic: Blog (no ads, tracking, or data-collection): Pluralistic.net Newsletter (no ads, tracking, or data-collection): https://pluralistic.net/plura-list Mastodon (no ads, tracking, or data-collection): https://mamot.fr/@pluralistic Bluesky (no ads, possible tracking and data-collection): https://bsky.app/profile/doctorow.pluralistic.net Medium (no ads, paywalled): https://doctorow.medium.com/ Tumblr (mass-scale, unrestricted, third-party surveillance and advertising): https://mostlysignssomeportents.tumblr.com/tagged/pluralistic "When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer. ISSN: 3066-764X
A recent scroll through social media got me thinking about trust. About half my feed was filled with AI slop that astute users would call out as being machine-generated, often to the embarrassment of the rube who unknowingly spread it and sometimes to the delight of the poster who shared it deliberately because they found it amusing or useful for sowing confusion. Remember the good old days of GPT-3 when we could ask the chatbot anything and it would almost instantly and very confidently spit back a plausible but false answer? That’s when the word “hallucination” as applied to AI answers entered the lexicon. Six years and several versions of a few different chatbots later, I still can’t trust the answers I get, even though Claude or Gemini often do useful work for me. That’s the thing about current AI: Trust but verify. And verify you must. Apply that maxim at scale to maintain the integrity of the scientific record and you get an idea of the challenge facing publishers of peer-reviewed scholarship. In “I Used AI to Fake a Scientific Paper,” freelance writer and doctor of applied mathematics Benjamin Skuse tells the story of the human gatekeepers trying to beat back the rising tide of AI slop by attempting to get his own AI-generated papers past IEEE’s defenses. “He basically hit ‘run experiment’ and ‘write paper.’ And then he didn’t do a damn thing to it.” —Eliza Strickland As features and AI beat editor Eliza Strickland told me, Skuse was surprised at just how effortless it was to generate a paper that was more or less plausible and more or less science. For one of his experiments, “he basically hit ‘run experiment’ and ‘write paper,’” she said. “And then he didn’t do a damn thing to it. He just launched it into the maw of the publishing engine.” Skuse discovered that the combination of IEEE’s human-review processes and research-integrity checker programs are currently keeping pace in what amounts to a cat-and-mouse game against people using free tools. But the more advanced LLMs are no longer making the same dumb mistakes the free versions do. That leaves publishers locked in an arms race between increasingly plausible slop and the systems aimed at detecting it. Scholarly publishers aren’t the only ones trying to hold the line against slop. So is that global depository of human-generated knowledge, Wikipedia. For this month’s Five Questions, Strickland interviewed Ellia Lebleu, a founding member of WikiProject AI Cleanup, a volunteer effort to detect and delete AI-generated material from the platform. Lebleu’s team is confronting the same problem as commercial publishers, but with fewer people and fewer tools. “Wikipedia is known as a repository of human-generated information, and they want to hold on to that,” says Strickland. “Who knows how long they’ll be able to do that.” If we can’t collectively assume that the scientific record is trustworthy or that basic facts about the world are accurate, then our understanding of what is real unspools into uncertainty. Perhaps that delights quantum scientists and the grifters who thrive in an information environment where there is no shared reality. But that uncertainty ultimately undermines trust in the institutions that exist to curate the corpus of human knowledge and to promote the advancement of science and technology for the benefit of humanity. Or maybe in a world where AI is doing the science and writing the papers, it won’t matter whether or not humans trust the outputs. As Strickland put it, “Doesn’t it just feel crazy that we’ll have AI writing for other AIs at some point? We’ll just get left out of the loop.”