Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
159358 stories
·
33 followers

Consumption-Based Billing Is Reshaping the AI Industry

1 Share

AI vendors are moving toward consumption-based billing as rising token costs make traditional flat-fee and user-based licensing increasingly difficult to sustain.

The post Consumption-Based Billing Is Reshaping the AI Industry appeared first on Cloud Wars.

Read the whole story
alvinashcraft
21 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

The Intent Debt

1 Share

The following article originally appeared on Addy Osmani’s blog site and is being republished here with the author’s permission.

Technical debt lives in your code. Cognitive debt lives in your head. Intent debt lives in the artifacts you may never have written: the goals, constraints, and rationale for why the system is the way it is. If you’re lucky, some of this exists scattered in team documents or discussions, but it’s likely incomplete. It’s the one kind of debt your agents can’t pay down for you, and agentic engineering makes it the most expensive.

____________________________

Three places debt can live

Margaret-Anne Storey’s Triple Debt Model is a clean way to think about software health. The three models of debt are technical, cognitive, and intent.

Technical debt lives in the code. It’s the accumulation of implementation choices that make the system harder to change later: the tangled module, the shortcut you took under deadline, the abstraction that leaked. We’ve understood this one for decades. You feel it coming through slow builds, fragile tests, and the dread of touching one particular file.

Cognitive debt lives in people. It’s the erosion of shared understanding, the gap between how much code exists and how much any human understands. I’ve been calling this comprehension debt. It builds up when the system grows faster than the team’s mental model of it. Your code can be pristine and you can still carry crippling cognitive debt, because nobody understands the pristine code either.

Intent debt lives in artifacts. It’s the absence or erosion of the externalized rationale, goals, and constraints that explain why the system is the way it is. The key word is externalized. The rationale has to be written down where a teammate, a future you, or an agent can read it, not held in your head. When intent debt runs high, the system drifts from what you meant it to do, and nobody can say when it diverged or why.

These three are independent, which took me a while to internalize.

You can have low technical debt and high intent debt. You can understand a system completely yourself (no cognitive debt for you) while its intent exists nowhere outside your skull (enormous intent debt for everyone else).

From the inside they feel alike, but each one bills you separately.

Why intent debt is the one agents can’t help with

AI generates code faster than ever, which makes technical debt cheaper to take on and cheaper to pay down. Point an agent at a tangled module and it’ll refactor it.

Cognitive debt recovers too, more easily than most engineers expect. When you don’t understand a chunk of the system, you ask the agent to explain it. You rebuild part of the lost mental model on demand, because the code still exists and the model can read it back to you.

Intent is different. An agent can’t generate intent, because intent is the one input that has to come from you. A model can infer a plausible rationale from the code, the same way you can guess why a previous engineer did something. A guess about intent isn’t the intent. The model doesn’t know whether that 300ms debounce was a deliberate UX decision, a benchmark result, or a number someone typed once and never revisited. It will invent a confident-sounding reason, which is worse than admitting it doesn’t know.

Of the three debts, intent debt is the only one where the agent can’t bail you out. It can write the code and restore your comprehension. The why is the one thing it can only fabricate.

Agents make the unwritten cost compound much faster

Teams got away with high intent debt for years because we carried it in our head and old docs.

When a new human joined a team, you didn’t write everything down, because they picked up intent over time: hallway conversations, code review comments, “Oh, we don’t do it that way because of an incident in 2023.” Knowledge moved person to person and built up. The engineer who’d been there four years was the intent documentation, expensive and lossy, but it worked.

Agents break that model. Bringing agents onto a team doubles its size overnight with junior people who have no long-term memory. An agent starts most sessions cold. It carries none of the tacit intent humans built up over years. Whatever you haven’t externalized into an artifact it can read, it doesn’t have.

That changes the economics of not writing things down. Unexternalized intent used to cost you once in a while, at onboarding or after someone left. Now you pay it every session, multiplied by every agent you run.

Picture the 20 agents you’re so excited to parallelize. Each one is a teammate who has never met you, can’t read your mind, and will fill any gap in your intent with a plausible guess. The orchestration tax I wrote about is partly an intent-debt tax. Much of what makes managing many agents exhausting is resupplying the intent you never wrote down.

The other half of the comprehension debt argument

When I wrote about comprehension debt, I made a point I want to revisit, because intent debt sharpens it.

I argued that detailed specs aren’t a complete answer. Translating a spec into working code involves a huge number of implicit decisions no spec ever captures, and a spec detailed enough to be the program is the program in a slower language. I still believe that.

Intent debt is the complementary truth.

Being unable to capture all intent is no license to capture none of it. The implicit decisions an agent now makes on your behalf, the ones a spec will never enumerate, are the decisions whose rationale evaporates if you don’t record at least the load-bearing ones. You can’t write down everything.

You do have to write down the why behind the choices that would be expensive to get wrong, because nobody will reconstruct those later.

Comprehension debt warns you not to trust that code is correct because it exists.

Intent debt warns you not to trust that the reason survives because the code does. Code is the answer; the intent was the question it was meant to solve. AI is brilliant at producing answers to questions you forgot to write down.

What high intent debt looks like

Intent debt rarely shows up as friction. It shows up as a particular kind of helplessness.

  • An agent “fixes” a bug by deleting a guard clause, and nobody can say whether that guard was load-bearing or leftover, because no doc or commit message ever recorded why it was there.
  • A refactor changes a behavior users depend on. The review passed because the diff looked clean and the tests were green, but the tests only encoded the previous behavior, never the intent.
  • You ask why two services talk over a queue instead of a direct call, and the honest answer is “An agent suggested it and it seemed fine.” That answer is intent debt, already accruing interest.

If you’ve felt the cognitive surrender version of this, defending a design choice you can’t reconstruct, intent debt is the team-scale, written-down version of the same hole.

Surrender is about your own posture in the moment. Intent debt is what a hundred of those moments leave in the repo for the next person and the next agent to inherit.

Paying it down: externalize intent as a first-class artifact

Almost everything I’ve been writing about for the last few months turns out to be intent-debt management. I didn’t have the word for it. The move is the same each time: Take the intent out of your head and put it somewhere an agent can read.

Write the spec for the intent, not the implementation. A good spec captures the goals, the constraints, the nonnegotiables, and an explicit definition of done (fast, accessible, secure, delightful, beyond “functionally correct”). The spec carries the intent the code can’t carry on its own.

Treat AGENTS.md as your intent ledger, not your config. It’s why I keep saying stop using /init. An auto-generated file describes what the code is. An intent file describes what the team means: the conventions, the “we don’t do it this way because,” the constraints invisible in any single file. Agents can’t infer that, and they need it most.

Capture decisions where they happen. Lightweight decision logs (ADRs) are pure intent-debt paydown. Recording why at the moment you decide costs almost nothing. Reconstructing it eight months later, after the person who knew why has moved teams, costs a fortune. Agents have made logging cheaper than ever, so the old excuse is gone.

Make the learning loop write intent back down. I’ve argued for self-improving agents that update a learnings file at the end of a session. The same loop is an intent-debt pump running in reverse: every mistake whose root cause you’ve recorded, every “We tried X and it didn’t work because Y” is intent that would otherwise have lived only in your memory of a bad afternoon.

None of these are new tools. They’re the discipline of refusing to let the why exist only in your head, in an era where your head is no longer where most of the work happens.

Where the value moved

For a long time, the scarce, valuable thing in software was the ability to produce a correct implementation. Code was expensive, so we optimized for writing it.

AI made code cheap, and comprehension is recoverable. Intent, the goals and constraints and reasons, is the one input that still has to originate with a human. It’s also the one we’re worst at externalizing, because for decades we got away with carrying it in our heads.

That worked when the team was a handful of people who could absorb intent over years of shared context. It does not work when half the team is agents that start every session as strangers.

Technical debt makes your system hard to change. Cognitive debt makes it hard to understand. Intent debt makes it hard to know whether the system still does what you wanted, and it’s the only one of the three your agents can’t pay back for you. That part stays with you. Write down the why, because it’s becoming the most valuable thing you can leave in the repo.



Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

The Packager’s Role in Enterprise Software Deployment

1 Share
Our colleague Radu Popescu dives deep into analyzing his career path as a software packager in the enterprise software deployment environment.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Smartify your doorbell

1 Share

I have a regular old fashion “ding-dong” doorbell, but what is the fun of that?

Not smart doorbell

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Consuming MCP Servers from .NET: When Your App Becomes the Client

1 Share

Consuming MCP Servers from .NET: When Your App Becomes the Client

Last time we built an MCP server in C# and plugged it into Claude Desktop and Claude Code. Both of those are somebody else's app. You wrote the tools, handed them over, and a client you didn't write got all the benefit.

So here's the other half of the story: what happens when your .NET app is the thing that needs those tools? Not Claude Desktop — your internal support console, your worker service, your CLI. This post writes the client side, connects it to the same Aurora Coffee Co. server from last time, and ends somewhere satisfying: the hand-rolled agent loop from the tool-use post — the one we wrote by hand in about sixty lines — collapses into three.

The client side of the protocol

A server post talks about exposing capabilities. A client post is the mirror image, and it's just three verbs:

  1. Connect — over a transport. Either you launch the server as a child process (stdio) or you point at a URL (HTTP).
  2. Discover — ask the server what it has. Tools, and their input schemas, come back as data. Nothing is compiled in.
  3. Call — invoke a tool by name with a bag of arguments, get content back.

That third step is worth pausing on, because it's the same security boundary from the tool-use post viewed from the opposite side. Back then, your code owned execution and Claude only got to ask. Now you're the one asking, and the server owns execution. You send a name and arguments; what actually runs is entirely the server's business.

The discovery step is what makes this different from just calling an API client. You don't have a generated proxy class with GetOrderStatus(string) on it. You have a list of tools that arrived at runtime, and code that has to be okay with that.

Connecting to the Aurora server

Start a console app and add the SDK. The client lives in ModelContextProtocol.Core if you want minimal dependencies, or the full ModelContextProtocol package if this same app also hosts a server:

dotnet new console -o AuroraCoffee.Support
cd AuroraCoffee.Support
dotnet add package ModelContextProtocol.Core

For a local server, the stdio transport launches it as a child process — exactly what Claude Desktop was doing for you via that JSON config last time, except now you're the one spawning it:

using ModelContextProtocol.Client;

var transport = new StdioClientTransport(new StdioClientTransportOptions
{
    Name = "aurora-coffee",
    Command = "dotnet",
    Arguments = ["run", "--project", "../AuroraCoffee.Mcp"],
});

await using var client = await McpClient.CreateAsync(transport);

That's the whole connection. McpClient.CreateAsync starts the process, performs the protocol handshake, and hands you a live client. Note the await using — the client owns a child process, and letting it go undisposed leaves an orphaned dotnet running. Ask me how many stray processes it took to internalize that.

Discover before you call

Now the part people skip and then debug for an hour. Print the tool list before you write a single CallToolAsync:

foreach (var tool in await client.ListToolsAsync())
{
    Console.WriteLine($"{tool.Name}{tool.Description}");
}

The reason isn't ceremony. Tool names are derived from your server's method names by the SDK, so the exact string the protocol exposes may not be the C# identifier you typed. Discovery is the contract; the C# source on the server side is an implementation detail. Print the list, copy the real names, then write your calls against those. Guessing the name gets you a runtime error with no compiler to save you.

Calling a tool

With a real name in hand, invoking it is a name plus a dictionary of arguments:

using ModelContextProtocol.Protocol;

var result = await client.CallToolAsync(
    "get_order_status",
    new Dictionary<string, object?> { ["orderId"] = "A-1001" },
    cancellationToken: CancellationToken.None);

Console.WriteLine(result.Content.OfType<TextContentBlock>().First().Text);
Order A-1001: shipped, ETA 2026-08-03.

Content is a list of blocks, not a string, because a tool can return text, images, or several blocks at once. Filtering with OfType<TextContentBlock>() is the honest way to read it — and if you .First() on a tool that returned no text, that's an exception, so use FirstOrDefault() when you don't control the server.

Notice what you didn't write: no JSON Schema, no HTTP plumbing, no message framing. You also didn't reference the server's project. Order and OrdersStore don't exist in this app — the only contract is the wire.

The payoff: hand the tools to Claude

Everything above is a client driving tools by hand. The interesting version is letting a model decide which to call — which is exactly what we hand-wrote a loop for in the tool-use post.

McpClientTool derives from AIFunction in Microsoft.Extensions.AI. That single inheritance is the whole trick: tools discovered from an MCP server drop straight into any IChatClient as callable functions, no adapter code.

dotnet add package Anthropic
dotnet add package Microsoft.Extensions.AI
using Anthropic;
using Microsoft.Extensions.AI;

IChatClient chatClient = new AnthropicClient()   // reads ANTHROPIC_API_KEY
    .AsIChatClient("claude-opus-4-8")
    .AsBuilder()
    .UseFunctionInvocation()
    .Build();

var tools = await client.ListToolsAsync();

var response = await chatClient.GetResponseAsync(
    "Is order A-1001 shipped, and do you still have ETH-250 in stock?",
    new ChatOptions { Tools = [.. tools] });

Console.WriteLine(response.Text);
Order A-1001 has shipped and is on track to arrive 2026-08-03. And yes — the
Ethiopia beans (ETH-250) are in stock, with 42 units available.

Read that output, then go back and look at the loop we wrote by hand. Same question, same two tool calls, same answer — except the while (true), the message-list bookkeeping, the block reconstruction, and the "echo the assistant turn back verbatim" gotcha are all gone. UseFunctionInvocation() is the middleware that runs that loop for you: Claude asks for a tool, it invokes the matching AIFunction, feeds the result back, and repeats until there's an answer.

The tools themselves live in a completely separate process, written by someone who never heard of this app. That's the part worth sitting with.

Connecting to a remote server

stdio works when the server is a local child process. For a shared server — the ASP.NET Core HTTP version from the end of the last post — swap the transport and change nothing else:

var transport = new HttpClientTransport(new HttpClientTransportOptions
{
    Endpoint = new Uri("https://tools.auroracoffee.example/mcp"),
    TransportMode = HttpTransportMode.StreamableHttp,
});

await using var client = await McpClient.CreateAsync(transport);

ListToolsAsync, CallToolAsync, and the IChatClient wiring are all identical from here — the transport is the only thing that knows the difference. TransportMode defaults to AutoDetect, which tries Streamable HTTP and falls back to legacy SSE, so you can leave it off if you don't know what the far end supports. Set it explicitly when you do; auto-detection costs a round trip.

One thing that changed since the last post

Worth flagging if you're building on the server post: the C# SDK shipped v2.0 on July 28, 2026, implementing the 2026-07-28 spec revision — the largest protocol change since MCP launched. The server-side attributes ([McpServerToolType], [McpServerTool]) and the client APIs above are unchanged, but two things moved:

  • HTTP transport is stateless by default. HttpServerTransportOptions.Stateless now defaults to true, the initialize handshake is gone, and the Mcp-Session-Id header went away. Great for scaling a shared server behind a load balancer; a behavior change if you assumed sessions.
  • Server-initiated requests (sampling, roots) are deprecated under diagnostic MCP9005 and throw in stateless mode. Interactive flows move to Multi Round-Trip Requests instead.

If your server is doing plain request/response tools — which is most servers, including Aurora — you won't notice. If it was leaning on sessions, read the migration notes before you upgrade.

When to write a client — and when not to

Write an MCP client when your app needs capabilities that live outside it: a server your platform team maintains, a third-party server you don't control, or your own server that several apps share. The payoff is that the tool list can change on the server without a redeploy on your side — new tool appears, ListToolsAsync returns it, and if a model is driving, it starts using it without you shipping a line of code.

Skip it when the tools are just... your own methods, in your own app, called by your own model loop. Going through a protocol and a process boundary to call a function you could have called directly is architecture cosplay. The tool-use approach is simpler and faster there, and it always was.

Rule of thumb: MCP client when the tools cross a boundary you don't own; plain in-process tool use when they don't.

Key Takeaways

  • A client is three verbs — connect over a transport, discover what's there, call by name. Everything else is the SDK's problem.
  • Discovery is the contract, not your server's C# source — print ListToolsAsync() and use the names it returns. The compiler can't catch a guessed tool name.
  • McpClientTool is an AIFunction — that one inheritance means MCP tools drop into any IChatClient, and UseFunctionInvocation() replaces the entire hand-written agent loop.
  • Transport is the only thing that changes for remote — stdio for a local child process, HttpClientTransport for a shared server; the discover-and-call code is identical.
  • v2.0 went stateless by default — HTTP servers no longer do the initialize handshake and sampling/roots are deprecated. Plain tool servers are unaffected; session-dependent ones need a look.
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

AI Engineer: Fixes 3 Way Drift

1 Share
From: Postman
Duration: 3:41
Views: 2

Are your API specs, Postman collections, and actual running servers out of sync?

In this quick walkthrough, see how Postman’s AI Engineer leverages the Context Graph to perform a "3-way drift report" by comparing your OpenAPI specs, Postman collection requests, and live running server responses automatically.

Instead of just checking basic syntax differences, the AI Engineer traverses your entire ecosystem to pinpoint deep architectural drift, missing authentication checks, field validation mismatches, and hardcoded stubs.

Key Highlights:
• Querying the Context Graph to analyze your full API infrastructure
• Generating a 3-Way Drift Report (Spec vs. Collection vs. Running Server)
• Identifying endpoint coverage gaps, authentication inconsistencies, and schema discrepancies
• Automated PR creation for instant fixes across your codebase

Learn More & Try Postman:
• Official Postman Site: https://postman.com

#Postman #APIDrift #OpenAPI #SoftwareEngineering #APITesting #DeveloperTools #PostmanAI #DevOps

Read the whole story
alvinashcraft
4 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories