AI as an abstraction layer: reviewing outcomes and implementation
On this page4 sections ▾
A conversation with Sam Wagner got me thinking about how much of development is moving from writing an implementation to describing and checking its behaviour. I see AI coding tools as another abstraction layer, although I don't think a working demo is enough to establish that the resulting software is ready to ship.
Sending email is a useful comparison. Most application developers use a library or service rather than implementing SMTP. We still care about whether the message arrives, how failures are handled and what the service costs, but we work at a different level.
Authentication has followed a similar path. Using an identity provider moves much of the implementation into a maintained product; we still have to configure it correctly and check that our application enforces the right access rules.
#From SMTP commands to an email service
#Sending SMTP commands
At the protocol level, sending an email involves a dialogue over a TCP connection using SMTP (Simple Mail Transfer Protocol). In this example, S is the server and C is you, the client:
S: 220 smtp.gordonbeeming.com ESMTP Postfix
C: HELO mycomputer.local
S: 250 Hello mycomputer.local, pleased to meet you
C: MAIL FROM:<me@gordonbeeming.com>
S: 250 2.1.0 Ok
C: RCPT TO:<friend@example.com>
S: 250 2.1.5 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: Subject: Hello from the past!
C:
C: This is a manual email.
C: .
S: 250 2.0.0 Ok: queued as 12345
C: QUIT
S: 221 2.0.0 Bye
A client implementing this protocol has to track the conversation state and handle invalid responses or connection failures.
#Moving the protocol into a library
Internal wrappers moved that protocol handling out of application code. Reusable packages then let us share that work across organisations. I remember the pushback: "We shouldn't just use some random person's code in a black box! If we write it ourselves, we can maintain it."
For a business application that needs to send notifications, maintaining custom SMTP retry logic rarely adds much value. A library such as SmtpClient lets the application supply the message while the library handles the protocol.
And yes, this was full framework days 😅:
using System.Net.Mail;
// ...
using (var client = new SmtpClient("smtp.gordonbeeming.com"))
{
var mail = new MailMessage("me@gordonbeeming.com", "friend@example.com");
mail.Subject = "Hello from .NET";
mail.Body = "The library handles the handshake now!";
client.Send(mail);
}
Open-source libraries also let us inspect the implementation when needed. I spent plenty of time reading raw SMTP logs, but using a library meant I didn't have to work at that level for every change to an application's email feature.
#Using an email service
With a service such as SendGrid, the application sends an HTTP request and the provider handles the mail infrastructure, delivery attempts, bounces and reporting. Deliverability still matters to the application, but much of the work sits behind the service API.
#Where I think AI fits
I expect AI tools to move more bespoke implementation work behind a similar interface. Instead of writing every function, I can describe the behaviour, ask an agent to make a change and assess the result.
Low-code tools have offered parts of this for years, but custom requirements can exceed what a particular platform supports. An AI coding tool can work in the application's language and libraries, which gives it more flexibility. It can also misunderstand requirements, choose an unsuitable approach or produce code that passes a narrow test while failing elsewhere.
For me, the interesting shift is how I spend my attention. I want to spend more time specifying behaviour and checking results, while still inspecting the implementation where security, maintainability or an uncertain test result calls for it. How far that shift goes will depend on the task and the quality of the tools.
#How MCP gives an agent a tool interface
An agent needs a way to act on external systems without implementing every integration during the task. MCP provides an interface for exposing those tools.
Take a basic GitHub MCP for example. Normally, if you wanted an agent to create an issue, it would have to know how to interact with the GitHub API, handle the OAuth, and pass all the right data in the correct format.
With an MCP pre-configured, that entire process is handled outside the LLM's immediate "thought process." The MCP server exposes exactly what parameters are needed for the create_issue tool. The agent just sees the requirement: "I need a title and a body." It doesn't need to know the endpoint; it just needs to know the intent.
#So, what's the difference between MCP and the .NET SDK?
The comparison I have in mind is a defined interface. .NET libraries expose method signatures to application code; an MCP server exposes tools to an agent. They serve different callers, but both reduce how much implementation detail the caller needs.
Today, it might be hard to imagine all that information functioning as tools. We are still limited by context windows and "context rot", where a model's reasoning starts to degrade because it's drowning in too much documentation or irrelevant noise.
Larger context windows and selective tool discovery may help, though the agent still needs the relevant information for its task.
MCP is the standardized interface that allows an agent to see what tools are available and how to call them without needing to keep the entire SDK documentation in its "head" at once. It gives the agent structured tool definitions to work with. The tool can validate its arguments and execute defined operations, but the model still chooses which tool to call and what to pass.
Subagents offer another way to divide the context. Each specialist can work in its own context window. Each sub-agent gets a specific set of tools and only the information it needs for its sub-task.
I find that division useful when specialists have clear tasks and enough context to do them. It still needs coordination and review: separate contexts can also mean that one agent misses a decision made by another.
#What changes in review
My expectation is that developers will spend more time deciding what the software should do and building reliable ways to check it. That includes tests, security checks and examining the parts of the implementation where the consequences of a mistake are high.
The SMTP comparison helps explain the direction I have in mind. It doesn't prove that generated code will become an implementation detail we can always ignore. For now, I use the abstraction where it helps, and keep responsibility for checking the result.