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

Hands on: How to Bring Your Own Local AI Model to Visual Studio Insiders (Preview)

1 Share
Visual Studio 18.10 Insiders can connect its new Agent directly to locally running AI models, but my test showed that getting a model into the IDE and getting frontier-model performance from it are two different things.
Read the whole story
alvinashcraft
17 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

.NET Conf 2026

1 Share

.NET Conf 2026 banner

Hey .NET Fans! It’s that time of year again: .NET Conf is back November 10-12, 2026.

We’ll spend three days learning from the people who build and use .NET, catching up with the community, and launching .NET 11. The event is free and online, so mark your calendar and join us from wherever you are.

Get ready for the release of .NET 11

We’ve been busy building .NET 11, and there’s already plenty to try. The latest previews include work across the runtime, libraries, SDK, ASP.NET Core, C#, .NET MAUI, Entity Framework Core, and more. We’re especially excited about C# 15 union types and the move to CoreCLR for .NET MAUI apps on Android, iOS, and Mac Catalyst. ASP.NET Core and Blazor also have a lot in store, including richer static SSR and form validation, smaller Blazor WebAssembly apps, a new development server, and smoother Aspire integration. Minimal APIs gain async validation and C# union type support, and OpenAPI 3.2 is now the default. Alongside the .NET 11 work, we’ve also released v2.0 of the official MCP C# SDK, with a stateless-by-default protocol designed to make building and scaling AI tools with ASP.NET Core more straightforward.

Catch up on what we’ve shared so far:

You can also hear directly from the teams in recent .NET Community Standups. There’s more on the way between now and November. We think you’re going to love what the teams have been working on, and we can’t wait to show you the rest at .NET Conf.

Schedule coming soon

We’re hard at work putting the product team schedule together. When the conference site and session list go live, we’ll publish those sessions on our new https://dot.net/conf mini-site and on the community-run https://dotnetconf.net site.

Interested in speaking?

We know you’re eager to submit your community sessions, and we hope to open the call for speakers within the next week. When it opens, we’ll share the news here on the .NET Blog, on both conference sites, and through our social channels. We’ll add selected community sessions to the schedule later.

For now, save November 10-12 and start thinking about what you might want to share. We’re excited to get the .NET community together again.

The post .NET Conf 2026 appeared first on .NET Blog.

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

Start here: Azure SQL Foundations series

1 Share

Most developers I talk to aren’t asking whether Azure SQL Database can handle their next app. They’re asking where to start when it comes to modernization, migration, and AI in the database. If you’re reading this, you’re probably in a similar boat: you’ve got an existing schema or databases, a scaling question you haven’t had to answer yet, and a growing list of AI features you’re expected to have an opinion about.

Azure SQL Foundations Social Card1 image

That’s why we built the path we kept describing in calls, with customers, and in the community. The Azure SQL Database Foundations series are four videos that take you from your first Hyperscale database to AI features running against your own operational data. We also included how to assess and migrate (with AI and skills!) to Hyperscale in the first place, and the common optimizations you should consider. Every episode ships with a repo, so you can follow along in your own environment instead of watching someone else’s terminal.

The series

# Video What you’ll get out of it
1 Get started with Azure SQL Database Hyperscale How Hyperscale handles AI and analytics workloads on the SQL foundation you already know.
2 Migrate and optimize with Azure SQL Database Hyperscale Migration in action. Modernize your SQL environment with AI and without application rewrites.
3 Optimize scale and performance with Azure SQL Database Hyperscale Scale compute and storage independently, and tune for your actual workload.
4 Bring AI into your apps with Azure SQL Database Hyperscale Build agentic AI apps with vector, RAG, and LLMs without moving data between systems.

Watch the series: https://aka.ms/azuresqlfoundationseries

Get the repos: https://aka.ms/azuresqlfoundations

Then go deeper with the Developer Guide

The Azure SQL Foundation Series - Developer Guide

The Azure SQL Database Foundations: Developer Guide picks up where the videos leave off, with GitHub repos for each section. Three things it covers:

  1. Deploy databases using modern DevOps practices. Provision and automate with Azure CLI, PowerShell, Bicep, ARM templates, and Terraform.
  2. Build AI-ready apps on operational data. Vector search and RAG, with Azure OpenAI, Semantic Kernel, LangChain, Azure AI Search, and Microsoft Fabric.
  3. Ship faster with modern data APIs. Data API Builder, SQL MCP Server, JSON support, and recent T-SQL enhancements.

Next steps

 

The post Start here: Azure SQL Foundations series appeared first on Microsoft for Developers.

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

Visual Studio August Update — Work Smarter Across Models and Branches

1 Share

The August update gives you more control over two parts of development that can get complicated quickly: choosing the right level of AI assistance and managing work across Git branches and repositories. You can now tune model thinking effort for the task in front of you, share custom agents across your organization, and see your Copilot usage without breaking your flow. Git workflows get a meaningful upgrade too. Worktrees let you keep multiple branches active at once, while first-class submodule support brings common management tasks directly into Visual Studio. Grab the Visual Studio 2026 Stable Channel update and dig into what is below.

Adjust model thinking effort to match your task Not every question needs the same amount of reasoning. For supported models, you can now set thinking effort to

Low, Medium, or High, balancing response depth, speed, and token usage around the work you are doing. Use Low for straightforward questions and code suggestions, Medium for everyday development, and High when you are working through a tricky algorithm, architecture decision, or hard-to-debug problem. You can adjust the setting from the Model picker or the expanded Language Models view. Thinking effort controls showing the medium setting for supported models

Organization-level custom agents Teams can now share custom agents across repositories instead of configuring the same specialized help one project at a time. GitHub organization and enterprise owners can publish agents for everyone in the organization, helping Copilot follow shared workflows and expectations more consistently. Visual Studio automatically detects organization-level agents when you work in an eligible repository and adds them to the

agent picker. Hover over an agent to see its description and organization source, or select the definition button to open its definition file. This functionality requires a GitHub organization. Copilot agent picker showing an organization agent, its description, and the button that opens its definition

Access your Copilot usage Curious how much of your Copilot plan you have used? Open the context window from the prompt box, then select

View all Copilot usage to jump to your full plan details. Usage notifications are easier to act on too, so you will know when you are close to your limit and what options you have to keep working. It is a small change that puts useful information closer to the moment you need it. Copilot context window with the usage button highlighted and a tooltip for viewing all Copilot usage

Worktrees: work on multiple branches at once Ever stashed half-finished work just to investigate another branch?

Git worktree support gives each branch its own working directory, so your current changes stay in place while you switch to another task. In the Git Repository window, right-click a branch and select New Worktree From. You can create the worktree from a new or existing branch, or start from a commit in the history graph. Open it in the current window or a new Visual Studio instance when you want both branches side by side. When the extra working directory is no longer needed, right-click it and select Delete Worktree. Worktree context menu with commands to create, open, and delete a worktree Your worktrees appear alongside branches in the Git Repository window, branch picker, and repository picker. Git Repository window showing active branches and detached worktrees in the branch tree

Git submodule support One of our

most requested Git improvements is here. Visual Studio now gives submodules a dedicated section in the Git Repository window, better visibility in Git Changes, and a repository picker that clearly shows the parent-child hierarchy. From the Submodules section, you can add, update, and delete submodules. Visual Studio discovers them automatically when you open a solution or folder and keeps them out of the general local repositories list, reducing clutter. Git Repository window with a Submodules section and commands to update or delete a selected submodule Submodules are read-only by default. To make changes inside them, go to Tools > Options > Source Control > Git, find Automatically activate multiple repositories, and select Yes, include submodules. This is the first milestone for the experience, with more improvements planned. Repository picker showing a parent repository and its nested submodule hierarchy * * *From our entire team, thank you for choosing Visual Studio! For the latest updates, resources, and news, check out the

Visual Studio Hub and stay in touch. Happy coding! The Visual Studio team

The post Visual Studio August Update — Work Smarter Across Models and Branches appeared first on Visual Studio Blog.

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

Wolverine 6.30: Optimized at least once delivery with “Native Acks”

1 Share

Wolverine 6.30 is out. It is a big release with one clear headline: a new endpoint modeEndpointMode.NativeAck, plus the supporting cast it needed to be trustworthy — lease renewal, an additional idempotency guard, partitioning, and a five-node chaos harness to address a reported issue. This release came with feature requests from JasperFx Software clients, and just to remind folks, being a JasperFx support client means that like Homer below, you have the secret handshake to get your Critter Stack needs and requests to the top of our priority list:

There is also a crop of transport fixes, two long-standing multi-tenancy gaps closed, and a couple of new HTTP and event sourcing conveniences. And inevitably, we also smuggled in some support for our soon forthcoming “Event Modeling” visualization support across the Critter Stack and more for CritterWatch visualization.

You’ve obviously detected that this post was largely drafted by AI of course, but the other side effect of AI usage is that we actually find many more little issues as we work and this 6.30 release includes quite a few bug fixes that spilled out of AI assisted efforts to improve our continuous integration safety nets and an embarrassing number of combinatorial bugs in the existing code that were discovered during the “Native Acks” effort.

Let’s take the tour.


🚀 The Fourth Endpoint Mode

This mode came about from interactions with a JasperFx client who needed to optimize the throughput of listening endpoints but still needed an “at least once” delivery guarantee. The existing Durable mode gives you the delivery guarantee and parallelization, but the extra database overhead was problematic for a flood of small messages that needed to be handled quickly. The Buffered mode gives you the maximal throughput, but a lesser delivery guarantee and is mostly suitable for “fire and forget” messages. Our Inline mode gives you the “at least once” guarantee without the database overhead of Durable, but limited parallelism and throughput. Our client used a different configuration change in Wolverine that alleviated their message pressure, but this new mode was going to be the next thing we tried for them if they needed it.

Wolverine has had three listener modes for a long time, and if you have configured a busy endpoint you have probably felt the shape of the tradeoff. You want parallelism. You want group-ordered processing. You do not want to lose messages when a pod gets rescheduled. And you would very much like to not stand up a database to get all three.

Until 6.30 you could have any two.

Broker ack timingLoss windowParallelismGroup partitioningDB cost
Inlineafter handler successnoneListenerCount onlynonenone
NativeAckafter handler successnoneMaximumParallelMessages✔none
BufferedInMemoryat receipt, before the handlercrash loses buffered messagesMaximumParallelMessages✔none
Durableafter the inbox insertnoneMaximumParallelMessages✔inbox insert + mark-handled

NativeAck fills the empty cell: Buffered’s throughput and partitioning with Inline’s no-loss guarantee, and no database involvement.

opts.ListenToRabbitQueue("webhooks")
    .ProcessInParallelWithNativeAcks()
    .PartitionProcessingByGroupId(PartitionSlots.Five)
    .MaximumParallelMessages(10);

The mechanism is exactly what the name says. The broker delivery is held unacknowledged while the message flows through an in-memory, optionally group-partitioned execution block, and is settled natively from the completion continuation — acked on handler success, nacked or dead-lettered on terminal failure. Nothing is written to a database. Nothing is acknowledged ahead of its handler.

Three consequences follow, and all three are the point rather than side effects:

  • Back pressure is the broker’s prefetch window, not BufferingLimits. The broker stops delivering once its unacked ceiling is reached, so there is no BackPressureAgent at all.
  • A dying node loses nothing. Anything queued but not yet completed is still unacknowledged, so closing the channel or crashing hands every one of those deliveries back to the broker.
  • Shutdown redelivers, but redelivery is not duplicate execution. More on that below, because it is the part everyone gets wrong — including, until this release, our own documentation.

The guarantee, stated exactly

I want to be precise here rather than reassuring, because this is the sort of claim people build on:

Protection against intra-group concurrency is the hard guarantee. Strict sequential processing in original delivery order is not.

The sequential lane per group slot structurally guarantees that no two messages sharing a group id execute concurrently on the owning node. Original-order processing is not guaranteed under failure, requeue, or broker redelivery — a failed or redelivered message re-enters its lane later, never concurrently. That is the honest contract for native-ack retry semantics on every broker. If you need strict order under failure, keep the durable inbox.


📊 What a Redeployment Actually Costs

This was from the community.

Earlier versions of the docs said a rolling deploy produced “duplicate deliveries bounded by the prefetch depth.” That was honest reasoning. It was also never measured — and when we finally measured it, the figure turned out to be the wrong one to quote. It is the bound on redeliveries, and it overstates handler-visible duplicates by more than an order of magnitude.

So #3713 built the harness: a five node cluster, group-partitioned across five RabbitMQ slots, under a sustained flood deep enough that the broker was holding a full unacked window at every disruption, reading the broker’s own messages_unacknowledged at that instant. Ranges are across four consecutive runs.

ScenarioUnacked at disruptionDuplicate executionsRate
Steady state, no disruption000%
Rolling deploy, all 5 nodes drained and replaced18000%
One node killed outright mid-flood1803–4~0.1%
Two hard kills plus two rolling replacements1807–9~0.05%

Two findings matter more than the headline percentage:

A graceful rolling deploy costs zero duplicate executions. Draining settles the handlers that were already running, so nothing runs twice. The prefetch window is redelivered — those messages simply had not executed yet, so they run for the first time, and your handler cannot tell them from any other first delivery.

A hard kill costs about one duplicate per busy lane, not one per unacked message. Only handlers that were mid-flight when the connection died can run twice, and that population is the partition slot count, not the prefetch depth. Three or four per killed node against an unacked window of 180 — a factor of roughly 45, and it barely moved between runs.

Handlers still need to be idempotent; at-least-once is the contract and 0.1% of a flood is not a small number of messages. But size that work against in-flight lanes rather than against prefetch. The full writeup is in What a redeployment actually costs.


🔌 Transport Support: Opt-In and Default-Closed

Wolverine actually supports more messaging technology options than any other messaging tool in .NET, and man, that’s not always a blessing.

A transport must settle each delivery individually and tolerate settling out of order, because the execution block completes messages in handler-completion order rather than delivery order. Not every broker can express that, so support is opt-in and default-closed — calling ProcessInParallelWithNativeAcks() on a transport that has not claimed the mode throws at configuration time rather than degrading silently.

Seven transports qualified and shipped in this release:

TransportDocsIssue
RabbitMQNative Ack Endpoints#3708
Amazon SQSNative Ack Processing#4050
Azure Service BusNative ack endpoints#4051
NATS JetStreamNative Acks with Parallel Processing#4053
Redis StreamsNative Acks with Parallel Processing#4046
PulsarNative Ack Processing#4047
GCP Pub/SubConcurrency and flow control#4052

Kafka cannot and is out of scope. A cumulative offset commit has no way to express a gap. This is not an oversight to be fixed later — it is what the storage model means.

Two transports also refuse the mode for particular endpoints whose own settings contradict it, again at bootstrap rather than at runtime. Pulsar’s AcknowledgeCumulative() reintroduces exactly the gap-less commit problem that disqualifies Kafka. And an SQS FIFO queue exists to guarantee ordering within a message group, which native-ack lanes deliberately do not preserve — and which partitioning by group id does not rescue, because SQS blocks a message group behind its own in-flight head. Both combinations are rejected by name.

Brokers that put a clock on an unsettled delivery

This is about protecting you from problems that arise from long executing messages when using any kind of native broker acknowledgement. This isn’t an issue at all in our Durable mode, but becomes an issue using any other endpoint mode. We had to get more serious about this with the advent or our new NativeAck mode in 6.30.

On SQS, Azure Service Bus, JetStream and Pub/Sub, an unsettled delivery is on a timer — visibility timeout, lock duration, AckWait. Wolverine renews that clock for every delivery still sitting in a lane, for as long as it sits there (#4048).

This is unconditional under this mode and not something you opt into. Lane queue time is unbounded by design, so an un-renewed native-ack endpoint would be a duplicate-delivery generator by construction rather than merely at risk under a slow handler. A transport that declares such a clock but does not implement renewal is refused at startup.

SQS is a good illustration of how far the per-transport tuning goes — MaxNumberOfMessages defaults down under this mode:

opts.ListenToSqsQueue("webhooks")
.ProcessInParallelWithNativeAcks()
.PartitionProcessingByGroupId(PartitionSlots.Five)
.MaximumParallelMessages(10);

Instead of the usual 10, a native-ack endpoint receives twice the number of lanes that can be busy at once, clamped to the SQS maximum. Under every other mode the surplus messages in a batch are deleted before their handlers run, so a full batch is free and saves API calls. Here each one sits in a lane holding an unsettled delivery to renew and to redeliver on a crash. Setting the property explicitly always wins.


🛡 The In-Memory Idempotency Guard

The durable inbox deduplicates on the primary key of its incoming table. NativeAck has no such table and is at-least-once by design, so 6.30 adds the non-durable analogue (#3710): an opt-in, bounded, in-memory set of the message ids this process has already handled on this endpoint.

opts.ListenToRabbitQueue("webhooks")
    .ProcessInParallelWithNativeAcks()
    .PartitionProcessingByGroupId(PartitionSlots.Five)

    // Opt in. Both arguments are optional; these are the defaults.
    .WithInMemoryIdempotency(window: 5.Minutes(), maxTracked: 100_000);

Or everywhere at once:

opts.Policies.AllListeners(x => x.WithInMemoryIdempotency());

Read the limits before you rely on it. The guard is per process and in memory, and three consequences follow — none of which is a bug:

  • A restart forgets everything. The very deploy that produces the redelivery burst also empties the guard on the node that starts up. It protects a running process against a redelivery it saw itself.
  • A second node never knew. With competing consumers, a redelivery can land on a different node than the original.
  • Eviction is generational, not exact. An id is remembered for at least half the window and at most the whole window — less if a flood of unique ids hits maxTracked first.

The promise is at-least-once delivery with best-effort deduplication, not exactly-once. If you need hard deduplication across restarts and nodes, that is what the durable inbox is for. Memory is bounded by construction — two rotating hash sets, no per-entry timestamps, no LRU bookkeeping, roughly single-digit megabytes at the 100,000 default. Details in In-Memory Idempotency Guard.


⚠ The Fix You Might Actually Be Affected By

Buried in the supporting work is #3712, and it is worth pulling out because it may already apply to you.

Wolverine now validates listener configuration coherence at bootstrap instead of silently ignoring settings that the chosen mode cannot honor. In writing those checks we discovered that RabbitMQ queues default to Inline — and Inline supports neither parallelism nor group partitioning.

Which means: if you built a sharded topology with PublishToShardedRabbitQueues() and did not explicitly add ConfigureListening(x => x.BufferedInMemory())your partitioning was silently doing nothing. The configuration was accepted and ignored. Now it is rejected with an error that says so.

A companion fix (#4022) catches the related case where a local queue configured with ProcessInline() was accepted at configuration time and then threw a message-less NotSupportedException from deep inside agent startup.

The partitioning guide covers the topology options, including the NativeAck variant added in #3709 — partitioned clustering without the durable inbox, across all ten transports that support the sharded topology.


🏢 Multi-Tenancy: Conjoined EF Core Under a Marten-Owned Store

#4044 is a small feature with a genuinely sneaky root cause.

If Marten owns your message storage through IntegrateWithWolverine(), Wolverine’s message store is built from Marten’s NpgsqlDataSource and never sees a connection string. And NpgsqlDataSource.ConnectionString deliberately omits the password — so there is no string the conjoined DbContext could be configured with that the database would actually accept. The obvious fix (pull the connection string off the data source) produces an authentication failure at first use.

So there is a new DbDataSource overload that carries the credentials through intact:

var builder = Host.CreateApplicationBuilder();

var configuration = builder.Configuration;

builder.UseWolverine(opts =>
{
    // Marten owns the message storage here, so Wolverine's message store is built
    // from Marten's NpgsqlDataSource rather than from a connection string
    opts.Services.AddMarten(m =>
    {
        m.Connection(configuration.GetConnectionString("main")!);
    }).IntegrateWithWolverine();

    // ...which means the conjoined DbContext has to be configured from that same
    // DbDataSource. NpgsqlDataSource.ConnectionString deliberately omits the password,
    // so the connection string overload cannot authenticate in this setup
    opts.Services.AddDbContextWithWolverineManagedConjoinedTenancy<ConjoinedItemsDbContext>(
        (builder, dataSource) =>
        {
            builder.UseNpgsql((NpgsqlDataSource)dataSource);
        }, AutoCreate.CreateOrUpdate);
});

Registering the connection string overload in this setup now fails fast at startup with an error naming this overload, rather than surfacing later as an authentication failure.

A second defect on the same path is fixed too: IntegrateWithWolverine() never registered the tenant partitioning provider, so PartitionPerTenant() failed outright. Both are covered in With Marten Owning the Message Store.


🌐 HTTP and Event Sourcing

Marten concurrency conflicts as 409

This was a JasperFx client request.

An endpoint using [WriteAggregate] can lose an optimistic concurrency race — two clients posting to the same aggregate at once. Without a handler that escapes as an unhandled 500, even though nothing went wrong: optimistic concurrency did its job. 409 Conflict is the honest status.

6.30 ships a documented, tested recipe (#3764). The OnException middleware convention is all you need — but there are two exception types, and the second is easy to miss:

public static class MartenConcurrencyExceptionMiddleware
{
    // Marten's optimistic concurrency failures -- EventStreamUnexpectedMaxEventIdException from
    // the event store, and document level concurrency violations -- all derive from
    // JasperFx.ConcurrencyException, so one handler covers them
    public static ProblemDetails OnException(ConcurrencyException ex)
    {
        return new ProblemDetails
        {
            Status = 409,
            Title = "Conflict",
            Detail = ex.Message
        };
    }

    // StreamLockedException does NOT derive from ConcurrencyException -- it is a MartenException --
    // so the FetchForExclusiveWriting path needs its own handler. Catching only ConcurrencyException
    // silently misses it
    public static ProblemDetails OnException(StreamLockedException ex)
    {
        return new ProblemDetails
        {
            Status = 409,
            Title = "Conflict",
            Detail = ex.Message
        };
    }
}

That second handler is the whole reason this is a documented recipe rather than a one-liner in a FAQ. Marten.Exceptions.StreamLockedException — what FetchForExclusiveWriting throws on a contended stream — derives from MartenExceptionnot ConcurrencyException. A recipe that catches only the latter silently leaves the exclusive locking path returning 500s. See Recipe: Marten Concurrency Conflicts as 409.

[StreamState] and [StreamEvents]

New parameter attributes for handlers whose read is the raw stream rather than the folded aggregate — timeline views, audit endpoints, anything [ReadModel] cannot express (#3627).

They are store-agnostic across Marten, Polecat and Fisher, and on Marten both fetches are batched into a single round trip.

Event Model slices per route

HttpChainDescriptor and GrpcRpcDescriptor now carry the slice the route is, so a consumer walking endpoint by endpoint sees it next to the route rather than only through the assembled model (#4000).


🐛 Transport Fixes

The AI tools are sometimes good about finding combinatorial or lurking bugs while doing other work. It’s annoying, but I always ask Claude to file issues for any unrelated problems it finds while doing any work — then immediately turn around and try to address them.

A good crop this time, and several of them share a theme worth naming: silent failure. Each of these was doing the wrong thing without reporting anything.

Pulsar

  • Requeue, scheduled retry and dead-letter routing were simply unimplemented (#3797).
  • A global native-resiliency failure rule was swallowing every user-configured error policy in the entire application (#4079, also reported as #4075). A plain local queue in a UsePulsar() host got exactly one attempt. If you use Pulsar and have ever wondered why an OnException policy seemed inert, this is why.
  • Hot-tail listeners silently dropped deferred messages in every mode (#4060).

GCP Pub/Sub

Redis

  • DeleteStreamEntryOnAck silently never acked on Redis < 8.2, where XACKDEL is unsupported (#4058).

Ack reliability

  • A shared ack-attempt budget across stacked retry blocks, plus terminal-failure classification for Azure Service Bus and SQS so a permanent settle failure stops rather than burning the whole budget (#4012 — partially delivered; the remaining items are tracked there for 6.31).

⬆ Upgrading

This release is additive. EndpointMode.NativeAck is opt-in per endpoint and default-closed per transport, and MaximumBrokerRedeliveries defaults to off.

Requires JasperFx 2.55.0.

The one thing to look at before upgrading is #3712, described above — the new listener coherence validation will reject at startup a configuration it previously accepted and ignored. That is a change in behavior, but the configuration it rejects was never doing what it looked like it was doing.


What’s Next

NativeAck is probably overdue and another option for Wolverine usage. We’ll be releasing a new version of our curated AI Skills this week that builds in decision making about endpoint usage in Wolverine applications.

Find us in the Critter Stack Discord or open an issue on GitHub.



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

Understanding the Modern AI Stack

1 Share

Grasp AI terminology with these helpful parallels from the full-stack development world.

AI engineering comes with a vocabulary hurdle. LLMs, embeddings, vector databases, RAG, agents, memory, orchestration, evaluation, observability: the terms tend to arrive all at once, and most explanations treat them as a glossary of disconnected definitions. That makes the field feel larger and stranger than it is.

In the first article of this AI Engineering Basics series, we made the case that the full-stack role is expanding rather than being replaced, and that the developers who adapt will treat AI as a new layer of the stack to master. This article builds the mental model for that layer. Every term above has a job; the jobs relate to each other the same way the pieces of a web application do, and once we see the structure, the terms fall into place.

We’ll organize the stack into five layers, and each one maps onto an instinct we already have:

LayerComponentsClosest full-stack analogy
ModelLLMsA runtime we call over an API
KnowledgeEmbeddings, vector databases, RAGThe database and query layer
ActionAgents, tools, memoryBusiness logic and session state
CoordinationOrchestrationBackend workflows and job queues
TrustEvaluation, observabilityTesting and monitoring

To keep things concrete, we’ll follow a single example throughout: a customer support assistant for TaskFlow, a fictional project management product. By the end, we’ll trace one user request through all five layers.

The Model Layer: LLMs

At the base of the stack sits the large language model (LLM), the component that understands and generates language. Models from providers like OpenAI, Anthropic and Google are available behind ordinary APIs, and calling one looks like calling any other web service. A minimal request for our TaskFlow assistant:

const response = await openai.responses.create({
  model: "gpt-5",
  instructions: "You are a support assistant for TaskFlow.",
  input: "How do I export my project data?",
});

console.log(response.output_text);

We send the user’s input along with instructions that set the assistant’s role, and we get generated text back. There’s no infrastructure to manage and no machine learning background required.

Two properties of this call shape everything else in the stack.

First, the model itself is stateless. It retains nothing between requests, so anything it should know (the conversation so far, the user’s plan, the relevant documentation) must be assembled into each request. Provider APIs can do some of that bookkeeping for us; the OpenAI Responses API, for example, can store conversation state server-side and chain turns with a previous_response_id parameter. The convenience leaves the underlying principle intact, since the full history still gets fed to the model (and billed as input tokens) on each call.

Second, the request has a size limit called the context window, measured in tokens, which caps how much we can include. A model’s knowledge is otherwise frozen at training time, which means our TaskFlow assistant, out of the box, knows nothing about TaskFlow.

The model, in other words, is a powerful runtime with no memory and no knowledge of our product, and the rest of the stack exists to work around those two gaps.

The Knowledge Layer: Embeddings, Vector Databases and RAG

Our assistant needs to answer questions about TaskFlow’s actual documentation, and stuffing every doc into each request would blow through the context window (and the budget). The knowledge layer solves this by finding the few passages that matter for a given question and supplying only those.

The mechanism underneath is the embedding, a representation of text as a vector of numbers. Think of it as a coordinate system for meaning: just as GPS coordinates position a location on a map, an embedding positions a piece of text in a high-dimensional space where distance reflects similarity of meaning. The embeddings for “How do I export my project data?” and “Downloading your projects as CSV” land close together in that space despite sharing almost no words, while a passage about billing lands far away.

Those vectors need a home, which is what a vector database provides. It stores the embeddings for every chunk of our documentation and can answer the question “which stored chunks are closest to this query?” across millions of entries in milliseconds.

Retrieval-Augmented Generation (RAG) is the pattern that ties these pieces to the model. When a user asks a question, the system retrieves the most relevant chunks from the vector database, augments the prompt with them and asks the model to generate an answer grounded in that material. The model still does the writing, but it works from our documentation rather than its training data, which keeps answers current and lets them cite sources. Platforms like Progress Agentic RAG package this whole layer (chunking, embeddings, indexing, retrieval) as a service, and we’ll dig into RAG in a dedicated article later in this series.

The Action Layer: Agents, Tools and Memory

Answering questions only gets our assistant so far. A user who asks “Can you move all my overdue tasks to next sprint?” wants something done, and doing things is the action layer’s job.

We covered the assistant-versus-agent distinction in the first article, so a short recap suffices here. An agent receives a goal rather than a question, plans the steps to reach it, acts, checks the results and adjusts until the task is done.

Tool use is what makes that possible. A tool is an operation we expose to the model with a name, a description, and typed parameters, like move_task(task_id, sprint_id) or get_overdue_tasks(project_id). The model doesn’t execute anything itself; it outputs which tool it wants to call and with what arguments, our code runs the call, and the result goes back into the conversation for the model’s next decision. The Model Context Protocol (MCP) is the emerging standard for wiring these connections consistently.

Memory rounds out the layer, and it splits into two kinds. Short-term memory is the running conversation, resent with each request so the model can follow references like “move those ones too.” Long-term memory persists across sessions in ordinary storage we control: when our assistant remembers that a user prefers two-week sprints, our application saved that fact and retrieved it into the context at the right moment. Memory patterns get their own article later in the series.

The Coordination Layer: Orchestration

A real support interaction rarely fits in one model call. Consider a TaskFlow user reporting a duplicate charge. A production-quality handling of that request might start by classifying the message as a billing issue, retrieving the refund policy and looking up the account’s payment history through a tool. From there, it drafts a response and routes the case to a human when the refund amount exceeds a threshold. Each step might involve a different model call, a different tool or no model at all.

Orchestration is the layer that coordinates these multi-step flows. It carries state from step to step and decides which branch to take based on intermediate results. It also handles the operational details: retrying transient failures, enforcing timeouts, and managing handoffs between models, tools and humans.

If the model layer is a runtime, orchestration is the backend workflow engine wrapped around it, and the discipline it demands is the same one we apply to any distributed system: clear steps, explicit state and predictable failure handling. Frameworks exist to help (LangGraph is a prominent example), though plenty of production systems orchestrate with plain application code.

The Trust Layer: Evaluation and Observability

Everything up to this point gets a system working. The trust layer is what lets us ship it and keep it running, and it’s the part of the stack that teams most often discover too late.

Evaluation answers the question “is this working?” for systems whose outputs aren’t deterministic. A traditional assertion like expect(output).toBe(expected) falls apart when the same input can produce different, equally valid responses. Instead, we build evaluation sets: collections of representative inputs paired with criteria for judging the outputs, whether that judgment comes from string checks, from rules or from another model acting as a grader. Evaluations run whenever we change a prompt, swap a model or adjust retrieval, serving the same role a regression suite serves in conventional software.

Observability answers “what is it doing in production?” For our TaskFlow assistant, that means tracing each request through every layer it touched: the documents retrieval returned, the tools the agent called and what each model call consumed and cost.

Token usage deserves particular attention because it’s both the cost model and an early warning signal. A prompt change that doubles token consumption shows up in the bill before it shows up anywhere else.

Traces also turn debugging from guesswork into inspection, since we can see when retrieval surfaced the wrong document or when a tool returned an error that the model papered over.

This layer is also where governance lives: knowing what our AI systems are doing, proving it and enforcing limits on agent behavior. The AI operations articles later in this series spend most of their time here.

One Request Through the Stack

To pull the layers together, we can follow a single message end to end. A TaskFlow user writes: “I was charged twice for my Pro subscription this month. Can I get a refund?”

  1. Orchestration receives the message and classifies it as a billing issue with a refund request.
  2. The knowledge layer retrieves TaskFlow’s refund policy and the duplicate-charge troubleshooting guide from the vector database.
  3. The action layer calls the get_payment_history tool, confirms the duplicate charge and checks memory for prior billing issues on the account.
  4. The model receives the policy, the payment data and the conversation, and drafts a response with a proposed refund.
  5. The refund amount falls under the auto-approval threshold, so orchestration executes the create_refund tool call; had it been larger, the flow would have paused for human approval.
  6. The trust layer records the full trace: documents retrieved, tools called, tokens spent and the outcome, ready for review and for the next evaluation run.

One Request Through the Stack diagram: Orchestration, Knowledge, Action, Model, Trust

Notice that no single layer did anything magical. The model wrote text, retrieval found documents and tools touched real systems. Orchestration kept the steps in order while the trust layer recorded it all. The modern AI stack amounts to familiar engineering arranged around one unfamiliar component.

Wrap-up

The vocabulary of AI engineering describes a stack with real structure. Models generate, the knowledge layer grounds them in our data, the action layer lets them do things, orchestration coordinates the steps, and evaluation and observability make the whole thing trustworthy enough to operate.

Each layer maps onto skills full-stack developers already practice, and each one gets a deeper treatment as this series continues, starting with how these pieces are architected into real applications.

For more on the platforms and standards mentioned in this article, check out the following resources:

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