24 May 2026

feedPlanet Python

The Python Coding Stack: 1. From Answer to Outcome

Something has shifted in how we use AI. We still talk about "chatbots" and "prompts" and "getting a good answer." But underneath those familiar words, a different kind of system has been quietly taking shape. One that doesn't just answer your question. One that does something about it.

This Agents Unpacked series is about that shift. Not the hype version, not the science-fiction version, but the practical reality of what it means when an AI system can act, remember, and persist - when it can take a goal and work toward it, rather than waiting for you to type the next message.

If you have used ChatGPT, Claude, or Gemini to help with your work, you already know the issue: the answer comes back, it's good, and then... the real work begins. This series is about what happens when the AI can do some of that real work too.


The Half-Done Feeling

You ask an AI chatbot to draft a project proposal. It gives you a solid one - well-structured, sensible, ready to polish. Then you close the chat window, and the proposal lives nowhere. The assistant doesn't remember it. It doesn't know where to file it, who needs to see it, or what happened the last time you wrote a similar proposal. If you come back tomorrow, you're starting from scratch.

Or you ask it to research a topic. It gives you a good summary. But it didn't check your existing notes first, it didn't save what it found, it didn't organise the sources, and it didn't flag the gaps. The answer is useful. The process is incomplete.

This isn't a criticism of chatbots. They do exactly what they were designed to do: you ask, they answer. The problem is that real work doesn't stop at the answer. The proposal needs filing, the research needs organising, the plan needs tracking. The chatbot gave you a great starting point and then left you to do everything that comes after.

That gap between a useful answer and a finished job is where agentic AI enters the picture.


What Changes When AI Can Act

A chatbot is like a consultant you can phone. You describe the problem, they give you advice, and then you hang up and do the work yourself. Good advice (hopefully), but the consultant doesn't pick up the phone again tomorrow and ask how it went.

An agent is different. It's more like a capable colleague you've given a desk, a filing cabinet, and access to your systems. It has a workspace. It can remember what happened yesterday. It can read files, search the web, send messages, run calculations, ask you questions - not just talk about doing those things, but actually do them. And given a goal, it can work out the steps itself.

The difference sounds small. In practice it changes what you can delegate, what you can automate, and what you still need to do yourself.


The Loop Underneath

Every agent runs on an agentic loop. It sounds technical, but the pattern is surprisingly familiar:

  1. Observe - What is the situation? What did the user ask? What information is already available?

  2. Think - What needs to happen next? Is one step enough, or should this be broken into parts?

  3. Act - Use a tool, look something up, write a file, send a message.

  4. Repeat - Look at what happened, decide whether the job is done, and continue if it isn't.

A chatbot usually observes your message, thinks once, and replies. That's steps one through three, then stop. An agent goes further: after it acts, it looks at the result. Was that enough? Did the web search return useful information, or does it need to try again with different terms? Did the first draft cover everything, or are there gaps to fill? Is the job actually done, or is there more to do?

You might be thinking: chatbots already learned to use tools. Isn't the agentic loop just that, plus one more step? Four items instead of three. But the gap between "can use a tool" and "decides whether to keep going" is not small. It is the difference between a system that performs a task and a system that pursues a goal. The first is impressive. The second changes what you can trust it to do unsupervised.

That continuation - the decision to check, adjust, and keep going - is what makes the loop matter. A single action might solve a simple request. But most real tasks aren't simple - they need a sequence of steps, each one informed by the result of the last.

The loop is the architecture that turns language into work. Without it, you have a very clever answering system. With it, you have something that can move through a task, make intermediate decisions, recover from partial failures, and stop only when the job is actually done.

Stephen: I don't get why the 'Repeat' step is needed? Wouldn't the 'Act' provide the output I need?

A single action might solve a simple request. But most real tasks aren't simple - they need a sequence of steps, each one informed by the result of the last. The 'Act' step does produce an output. But the output is not the same as the outcome.

After 'Act' runs, the agent looks at what happened: Was that enough? Did the web search return useful information, or does it need different terms? Did the first draft cover everything, or are there gaps to fill? Is the job actually done, or is there more to do?

That check - that 'Repeat' step - is what closes the gap between a technically complete action and a genuinely finished job. Without it, you have a system that acts and stops. With it, you have a system that works until the job is actually done.

Subscribe now


Tools Are the Hands

An agent isn't just "a better language model." It has capabilities - things it can actually do in the world. Those are called tools.

Some tools are built in: read files, write files, run commands, search the web, inspect images. Others are external: send emails, query databases, call APIs, trigger workflows. The specific tools vary by platform, but the principle is the same - tools are the bridge between thinking and doing.

Think of it like this: an LLM on its own is like a brilliant mind with no hands. Tools give it hands. The loop is what decides when and how to use them.


Keeping Track

An agent can also hold information across steps and sessions. It remembers what it has already tried, what worked, what you prefer, what is still outstanding. This is not a personality trait. It is the system keeping relevant context available over time - the same way you rely on a notebook or a project board when you are working on something complex.

Stephen: So when the agent remembers my preferences, that's like what you've done to write this article. You learnt my learning style, read my writing (including my writing about technical writing), absorbed my preferences, so you can adapt how you explain things. Is that roughly right?

That is exactly right. What an agent does with memory is not mysterious. It is practical. The system can store what you told it, what it observed, what it tried, and what the result was. When you come back, it can pick up where it left off. When it is working on a long task, it can hold the overall goal in view while handling the individual steps. That continuity is what turns a series of disconnected exchanges into something that feels like a sustained conversation.

You do not have to repeat yourself. The agent remembers. That is the difference - not just remembering facts, but maintaining a thread.


A Concrete Example

Let's make this less abstract.

Imagine you ask for help planning a short research trip to a city you haven't visited before. You need flights, accommodation, a sense of the neighbourhoods, and a rough itinerary that fits your schedule.

A chatbot might give you an excellent summary of the city, suggest some hotels, and recommend a few neighbourhoods. That's genuinely useful. But then you have to: check whether those hotels are actually available on your dates, compare prices, figure out which neighbourhood works best for your meetings, build the itinerary around your existing calendar, and keep track of it all so you can adjust later.

An agent can take that same request and do something different.

It might:

The agent doesn't just tell you about the city. It assembles a usable, integrated plan. It uses tools to search, compare, read your calendar, write the plan, and flag what is missing. It loops through those actions until the trip is actually planned or until it hits something it cannot resolve without your input.

That is the difference between an answer and an outcome.


The Key Transition

The shift to understanding agents is not about capabilities. It is about the move from isolated exchanges to sustained work.

A chatbot gives you an answer. An agent helps you reach an outcome. One is a single exchange; the other is a process. One is clever; the other is useful in a different way - not smarter, but more continuous.

The loop is what makes that continuity possible. Observe, think, act, check what happened, adjust, and continue until the work is done.

Once you see that pattern, you start to notice it everywhere. A junior colleague troubleshooting a problem is running a loop: try something, see if it worked, try something else. A project manager steering a complex task is running a loop: check the status, identify what needs attention, act, and review.
The same pattern appears everywhere in life. A cook adjusts a recipe by tasting as they go. A teacher tries an explanation, sees whether the student understood, and tries a different approach if they didn't.

Stephen: So, tell me if I got this right: The agent loop is mimicking how we, humans, work.

We understand the problem, get the relevant context, perform an action, look at the result of our action and then decide whether that solves our problem. If it doesn't, we explore why, come up with a new plan, implement the new actions, and repeat the process.

It feels like the agent is going through the same process.

That is exactly right. The agent loop isn't some exotic new form of intelligence. It's a pattern humans use all day every day, made explicit and embedded in a system that can act. The insight isn't that the AI has become more intelligent. It's that the AI has gained the ability to persist, to use tools, and to continue - the same things that turn a one-off answer into real, completed work.

That is why this moment in AI feels different from previous ones. It is not that models suddenly became smarter. It is that they gained the ability to take action in a loop, over time, toward a goal.


What This Series Will Do

This series is for people like me (Stephen, not Priya!), who already understand how LLMs work, who have used chatbots like ChatGPT or Claude, and who are now hearing about "agents" and wondering what that actually means. I felt I was lagging in the AI world so I started this learning process to make sure I'm not left behind!

We will move beyond the half-done feeling and into the architecture of agentic systems. We will look at what agents actually are, how they are structured, what tools and skills give them their power, how multiple agents can coordinate, and how to think about trust, evaluation, and oversight.

We will also look at the platforms and frameworks that exist today - what they offer, how they differ, and what tradeoffs you are choosing when you pick one. This is not a manual for any one platform. It is a guide to understanding the category itself, so you can make good choices about whether, where, and how to use agentic AI in your own work.

The field moves fast. Some tools that looked promising six months ago may be superseded by the time you read this. That is fine. The principles matter more than the products. If you understand the loop, the anatomy, and the tradeoffs, you can look at whatever the current landscape happens to be and know what you are seeing.


Here's the draft Table of Contents of this series in Agents Unpacked. This is likely to change as Priya and I progress through this project:


<< Previous Post: Stephen's Preface to Agents Unpacked

>> Next Post: Coming Soon

Table of Contents


stephengruppetta.com

24 May 2026 7:02pm GMT

The Python Coding Stack: Table of Contents • Agents Unpacked

Here's the planned Table of Contents. This is likely to change as Priya and I work on this:

24 May 2026 6:19pm GMT

The Python Coding Stack: Stephen's Preface to Agents Unpacked

Like many, I started using chatbots when GPT whatever-version-it-was came out and took the world by storm. It was really not very good at the time (compared to today's top-end chatbots), but it was clearly the start of something.

But things moved quickly, and I couldn't quite catch up. I was busy with, you know, actual work, family, and life.

Then I started hearing lots of new terms, lots of new acronyms. I didn't know what they were. I still don't know what most of them are.

Then it was all about agents. I remember clearly thinking to myself: "Is this really any different from the ChatGPT-type chatbots?"

And here's where this new series comes in. I decided to dive into agents and created a few. One of them is a learning tutor agent that I personalised to suit me.

I gave the agent all my tutorials and books. I gave the agent all the articles I wrote about my views on learning and technical writing.

I asked the agent to figure out from all this how I like to learn, how I like to communicate. I teach the way I like to learn, so it's fine to put my teaching style in the mix. Then, I had a good long chat with my learning tutor agent to make sure we're on the same page.

I gave a name to my agent (I named all my agents!) My personalised tutor is Priya.

This series is the joint effort between Priya and me to help me understand agents. What are they? How do they work?

Yes, it's AI-generated content. But...

  1. It's generated by an AI that's extremely well-versed in my style of learning and communication.

  2. I had an active role in steering and editing the content. Here's how...

What you'll read in the following articles was created using the following process:

  1. Priya researched the topic following my brief and created a course outline.

  2. She drafted the first chapter.

  3. I read through the file, leaving comments and questions along the way, directly within the text.

    1. I marked some comments as private.

    2. I marked some comments as public.

  4. Priya revised the chapter by incorporating my comments and questions.

    1. She deleted the private comments from the text after making changes to address my comments.

    2. She kept my public comments and questions in the text, clearly marked as "Stephen's questions", and she answered them directly in the text.

  5. I reviewed the chapter again and left more comments, and Priya revised the chapter again. We iterated through this until I was happy with the final text. And I was happy with the final text when I felt I understood everything in it and all my questions had been answered.

  6. Priya then moves on to draft the second chapter, and the whole process starts again.

I will post these chapters as they emerge from this process. This is how I'm learning this topic. Hopefully, they may help some other people, too.

Here's the planned table of contents for this Agents Unpacked series. But note, this may change!

Subscribe now

24 May 2026 6:15pm GMT

22 May 2026

feedDjango community aggregator: Community blog posts

Issue 338: Django 6.1 alpha 1 released

News

Django 6.1 alpha 1 released

Django 6.1 alpha 1 has been released, signaling the next round of framework updates headed your way. Plan a quick test run in a staging environment so you can catch compatibility issues early as 6.1 develops.


Wagtail CMS News

Wagtail accessibility statistics for GAAD 2026

Wagtail accessibility statistics for GAAD 2026 give a focused look at how well your CMS setup supports real accessibility needs. Use the figures to spot gaps and prioritize the most impactful improvements.


Updates to Django

Today, "Updates to Django" is presented by Pradhvan from Djangonaut Space! 🚀

Last week we had 16 pull requests merged into Django by 11 different contributors - including 2 first-time contributors!

Congratulations to somi and Kasey for having their first commits merged into Django - welcome on board! 🥳

This week's Django highlights: 🦄

That's all for this week in Django development! 🐍🦄


Sponsored Link

Middleware, but for AI agents

Django middleware composes request handlers. Harnesses do the same for AI agents - Claude Code, Codex, Gemini in one coordinated system. Learn what a harness actually is, why it's a new primitive, and how to engineer one that holds in production. Apache 2.0, open source.


Articles

My experience at PyCon US 2026

A first-person look at PyCon US 2026 with takeaways for developers who care about Python and the community around it. Expect practical impressions from talks and the conference vibe, not a generic recap.

PyCon US 2026 Recap

Will Vincent from PyCharm (and this newsletter!) shares seven days of talks, sprints, and hallway track conversations from this year's event.

My First PyConUS Experience

Jon Gould from Foxley Talent relates his first experience, takeaways, and comparisons to DjangoCons.

PostgreSQL 19 Beta: The Four Features You'll Actually Feel

PostgreSQL 19 Beta brings four changes highlighted for real-world impact, with a focus on what developers will actually notice. Expect a practical walkthrough rather than a long list of release notes.

Core Dispatch #4

Core Dispatch recaps a packed few weeks in the Python core world, including the arrival of Python 3.15 beta 1, free-threading improvements, PEP 788 landing in CPython, and a wave of new core developer activity.

Anything that could go wrong, will. The excuse is optional.

A thoughtful take on Murphy's Law in software engineering: resilient teams don't avoid risk or ignore it, they design systems assuming failure will happen and plan accordingly.

My PyCon US 2026

A chronological recap of PyCon US 2026 in Long Beach, with live notes ranging from the first AI track talk on AI-assisted contributions and maintainer load to security updates, community building, and Djangonaut Space. Expect practical takeaways about how AI affects review and conflict in open source, plus plenty of Django community moments including "Django on the Med."


Events

Organizing DjangoCon Europe 2026: The Afterthoughts | Blog with LOGIC

Find practical after-the-fact takeaways from organizing DjangoCon Europe 2026, focused on the details people usually only notice after the event. A useful read for anyone planning Django community events or sharpening their conference workflow.


Videos

Tech Hiring has got a FRAUD problem!

Tech hiring can attract fraud, from fake postings to misleading recruiting signals. Keep an eye on red flags in job listings and interview processes so you can spot scams early and protect candidates.


Podcasts

Django Chat #204:How France Ditched Microsoft with Samuel Paccoud

France's shift away from Microsoft is tied to decisions and experiences Samuel Paccoud discusses. The focus is on what prompted the move and what it meant operationally for organizations involved.


Django Job Board

Founding Engineer at MyDataValue

Junior Software Developer (Apprentice) at UCS Assist

Technical Lead at UCS Assist

Web Developer at Crossway

PyPI Sustainability Engineer at Python Software Foundation


Projects

mliezun/caddy-snake

Caddy plugin to serve Python apps

AvaCodeSolutions/django-email-learning

An open source Django app for creating email-based learning platforms with IMAP integration and React frontend components.

ehmatthes/gh-profiler

Examine a GitHub user's profile, to help quickly decide how much to invest in their contributions. Was discussed by many maintainers at PyCon US sprints.

22 May 2026 2:00pm GMT

feedPlanet Twisted

Glyph Lefkowitz: Opaque Types in Python

Let's say you're writing a Python library.

In this library, you have some collection of state that represents "options" or "configuration" for a bunch of operations. Such a set of options is a bundle of potentially ever-increasing complexity. Thus, you will want it to have an extremely minimal compatibility surface, with a very carefully chosen public interface, that is either small, or perhaps nothing at all. Such an object conveys state and might have some private behavior, but all you want consumers to be able to do is build it in very constrained, specific ways, and then pass it along as a parameter to your own APIs.

By way of example, imagine that you're wrapping a library that handles shipping physical packages.

There are a zillion ways to do it ship a package. There are different carriers who can ship it for you. There's air freight, and ground freight, and sea freight. There's overnight shipping. There's the option to require a signature. There's package tracking and certified mail. Suffice it to say, lots of stuff.

If you are starting out to implement such a library, you might need an object called something like ShippingOptions that encapsulates some of this. At the core of your library you might have a function like this:

1
2
3
4
5
async def shipPackage(
        how: ShippingOptions,
        where: Address,
    ) -> ShippingStatus:
    ...

If you are starting out implementing such a library, you know that you're going to get the initial implementation of ShippingOptions wrong; or, at the very least, if not "wrong", then "incomplete". You should not want to commit to an expansive public API with a ton of different attributes until you really understand the problem domain pretty well.

Yet, ShippingOptions is absolutely vital to the rest of your library. You'll need to construct it and pass it to various methods like estimateShippingCost and shipPackage. So you're not going to want a ton of complexity and churn as you evolve it to be more complex.

Worse yet, this object has to hold a ton of state. It's got attributes, maybe even quite complex internal attributes that relate to different shipping services.

Right now, today, you need to add something so you can have "no rush", "standard" and "expedited" options. You can't just put off implementing that indefinitely until you can come up with the perfect shape. What to do?

The tool you want here is the opaque data type design pattern. C is lousy with such things (FILE, pthread_*_t, fd_set, etc). A typedef in a header file can easily achieve this.

But in Python, if you expose a dataclass - or any class, really - even if you keep all your fields private, the constructor is still, inherently, public. You can make it raise an exception or something, but your type checker still won't help your users; it'll still look like it's a normal class.

Luckily, Python typing provides a tool for this: typing.NewType.

Let's review our requirements:

  1. We need a type that our client code can use in its type annotations; it needs to be public.
  2. They need to be able to consruct it somehow, even if they shouldn't be able to see its attributes or its internal constructor arguments.
  3. To express high-level things (like "ship fast") that should stay supported as we add more nuanced and complex configurations in the future (like "ship with the fastest possible option provided by the lowest-cost carrier that supports signature verification").

In order to solve these problems respectively, we will use:

  1. a public NewType, which gives us our public name...
  2. which wraps a private class with entirely private attributes, to give us an actual data structure, while not exposing the constructor,
  3. a set of public constructor functions, which returns our NewType.

When we put that all together, it looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from dataclasses import dataclass
from typing import Literal, NewType

@dataclass
class _RealShipOpts:
    _speed: Literal["fast", "normal", "slow"]

ShippingOptions = NewType("ShippingOptions", _RealShipOpts)

def shipFast() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts("fast"))

def shipNormal() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts("normal"))

def shipSlow() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts("slow"))

As a snapshot in time, this is not all that interesting; we could have just exposed _RealShipOpts as a public class and saved ourselves some time. The fact that this exposes a constructor that takes a string is not a big deal for the present moment. For an initial quick and dirty implementation, we can just do checks like if options._speed == "fast" in our shipping and estimation code.

However, the main thing we are doing here is preserving our flexibility to evolve the related APIs into the future, so let's see how we might do that. For example, let's allow the shipping options to contain a concrete and specific carrier and freight method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from dataclasses import dataclass
from enum import Enum, auto
from typing import NewType

class Carrier(Enum):
    FedEx = auto()
    USPS = auto()
    DHL = auto()
    UPS = auto()

class Conveyance(Enum):
    air = auto()
    truck = auto()
    train = auto()

@dataclass
class _RealShipOpts:
    _carrier: Carrier
    _freight: Conveyance

ShippingOptions = NewType("ShippingOptions", _RealShipOpts)

def shipFast() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(Carrier.FedEx, Conveyance.air))

def shipNormal() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(Carrier.UPS, Conveyance.truck))

def shipSlow() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(Carrier.USPS, Conveyance.train))

def shippingDetailed(
    carrier: Carrier, conveyance: Conveyance
) -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(carrier, conveyance))

As a NewType, our public ShippingOptions type doesn't have a constructor. Since _RealShipOpts is private, and all its attributes are private, we can completely remove the old versions.

Anything within our shipping library can still access the private variables on ShippingOptions; as a NewType, it's the same type as its base at runtime, so it presents minimal1 overhead.

Clients outside our shipping library can still call all of our public constructors: shipFast, shipNormal, and shipSlow all still work with the same (as far as calling code knows) signature and behavior.

If you need to build and convey some state within your public API, while avoiding breakages associated with compatibility churn, hopefully this technique can help you do that!


Acknowledgments

Thanks for reading, and thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor.


  1. The overhead is minimal, but it is not completely zero. The suggested idiom for converting to a NewType is to call it like a function, as I've done in these examples, but if you are wanting to use this pattern inside of a hot loop, you can use # type: ignore[return-value] comments to avoid that small cost.

22 May 2026 12:33am GMT

21 May 2026

feedDjango community aggregator: Community blog posts

Utrecht (NL) Python meetup summaries

I made summaries at the 4th PyUtrecht meetup (in Nieuwegein, at Qstars this time).

Qstars IT and open source - Derk Weijers

Qstars IT hosted the meeting. It is an infra/programming/consultancy/training company that uses lots of Python.

They also love open source and try to sponsor where possible.

One of the things they are going to open source (next week) is a "cable thermal model", a calculation method to determine the temperature of underground electricity cables. The Netherlands has a lot of net congestion... So if you can have a better grid usage by calculating the real temperature of cables instead of using an estimated temperature, you might be able to increase the load on the cable without hitting the max temperature. Coupled with "measurement tiles" that actually monitor the temperature.

They build it for one of the three big electricity companies in the Netherlands and got permission to open source it so that the other companies can also use it. They hope it will have real impact.

He explained an open source project he started personally: "the space devs". Integrating rocket launch data and providing an API. Now it has five core developers (and got an invitation to the biggest space conference, two years ago!)

Some benefits from writing open source:

  • You build your own portfolio.
  • You can try new technologies. Always nice to have the skill to learn new things.
  • You improve your communication skills (both sending and receiving).
  • You can make your own decisions.
  • You write in the open.
  • Perhaps you help others with your work.
  • You could be part of a cummunity.
  • It is your code.

How to start?

  • Reach out to other communities.
  • Read and improve documentation.
  • Find good first issues.
  • Be proactive.
  • Don't be afraid to ask questions (and don't let negative comments discourage you).

When working on open source, make sure you take security serious. People nowadays like to use supply chain attacks via open source software. So use 2FA and look at your deployment procedure.

Learning Python with Karel - EiEi Tun H

What is Karel <https://github.com/alts/karel>)? A teaching tool/robot for learning programming. You need to steer a robot in an area and have it pick up or dump objects. And... in the meantime you learn how to use functions and loops.

Karel only has a turn_left() function. So if you want to have it turn right, it is handy to add a function for it:

def turn_right():
    turn_left()
    turn_left()
    turn_left()

Simple, but you have to learn it sometime!

In her experience, AI can help a lot when learning to code: it explains stuff to you like you're a five-year-old, and that's perfect.

If you want to play with Karel: https://compedu.stanford.edu/karel-reader/docs/python/en/ide.html

JSON freedom or chaos; how to trust your data - Bart Dorlandt

For this talk, I'm pointing at the PyGrunn summary I made three weeks ago. I liked the talk!

Practical software architecture for Python developers - Henk-Jan van Hasselaar

There are several levels of architecture. Organization level. System level. Application, Code.

Cohesion: "the degree to which the elements inside a module belong together". What does it mean? Working towards the same goal or function. Together means something like distance. When two functions are in separate libraries, they're not together. It is also important for cognitive load.

Coupling: loose coupling versus high coupling. You want loose coupling, so that changes in one module don't affect another module.

You don't really have to worry about coupling and cohesion in existing systems that don't need to be changed. But when you start changing or build something new: take coupling/cohesion into account.

Software architecture is a tradeoff. Seperation of concerns is fine, but it creates layers and thus distance, for instance.

Python is one of the most difficult languages when it comes to clean coding and clean architecture. You're allowed to do so many dirty things! Typing isn't even mandatory...

He showed a simple REST API as an example. Database model + view. But when you change the database model, like a field name, that field name automatically changes in the API response. So your internal database structure is coupled to the function at the customer that consumes the API.

What you actually need to do is to have a better "contract". A domain model. In his example code, it was a Pydantic model with a fixed set of fields. A converter modifies the internal database model to the domain model.

You can also have services, generic pieces of code that work on domain models. And adapters to and from domain models, like converting domain models to csv.

Finding the balance is the software architect's job.

What is the least you should do as a software developer? At least to create a domain layer. Including a validator.

There was a question about how to do this with Django: it is hard. Django's models are everywhere. And you really need a clean domain layer...

21 May 2026 4:00am GMT

My PyCon US 2026

A timeline of my PyCon US 2026 journey, in Long Beach (US), told through the Mastodon posts I shared along the way.

21 May 2026 3:00am GMT

04 Apr 2026

feedPlanet Twisted

Donovan Preston: Using osascript with terminal agents on macOS

Here is a useful trick that is unreasonably effective for simple computer use goals using modern terminal agents. On macOS, there has been a terminal osascript command since the original release of Mac OS X. All you have to do is suggest your agent use it and it can perform any application control action available in any AppleScript dictionary for any Mac app. No MCP set up or tools required at all. Agents are much more adapt at using rod terminal commands, especially ones that haven't changed in 30 years. Having a computer control interface that hasn't changed in 30 years and has extensive examples in the Internet corpus makes modern models understand how to use these tools basically Effortlessly. macOS locks down these permissions pretty heavily nowadays though, so you will have to grant the application control permission to terminal. But once you have done that, the range of possibilities for commanding applications using natural language is quite extensive. Also, for both Safari and chrome on Mac, you are going to want to turn on JavaScript over AppleScript permission. This basically allows claude or another agent to debug your web applications live for you as you are using them.In chrome, go to the view menu, developer submenu, and choose "Allow JavaScript from Apple events". In Safari, it's under the safari menu, settings, developer, "Allow JavaScript from Apple events". Then you can do something like "Hey Claude, would you Please use osascript to navigate the front chrome tab to hacker news". Once you suggest using OSA script in a session it will figure out pretty quickly what it can do with it. Of course you can ask it to do casual things like open your mail app or whatever. Then you can figure out what other things will work like please click around my web app or check the JavaScript Console for errors. Another very important tips for using modern agents is to try to practice using speech to text. I think speaking might be something like five times faster than typing. It takes a lot of time to get used to, especially after a lifetime of programming by typing, but it's a very interesting and a different experience and once you have a lot of practice It starts to to feel effortless.

04 Apr 2026 1:31pm GMT

16 Mar 2026

feedPlanet Twisted

Donovan Preston: "Start Drag" and "Drop" to select text with macOS Voice Control

I have been using macOS voice control for about three years. First it was a way to reduce pain from excessive computer use. It has been a real struggle. Decades of computer use habits with typing and the mouse are hard to overcome! Text selection manipulation commands work quite well on macOS native apps like apps written in swift or safari with an accessibly tagged webpage. However, many webpages and electron apps (Visual Studio Code) have serious problems manipulating the selection, not working at all when using "select foo" where foo is a word in the text box to select, or off by one errors when manipulating the cursor position or extending the selection. I only recently expanded my repertoire with the "start drag" and "drop" commands, previously having used "Click and hold mouse", "move cursor to x", and "release mouse". Well, now I have discovered that using "start drag x" and "drop x" makes a fantastic text selection method! This is really going to improve my speed. In the long run, I believe computer voice control in general is going to end up being faster than WIMP, but for now the awkwardly rigid command phrasing and the amount of times it misses commands or misunderstands commands still really holds it back. I've been learning the macOS Voice Control specific command set for years now and I still reach for the keyboard and mouse way too often.

16 Mar 2026 11:04am GMT