Writing · Chapter 5

Typed, checked AI calls for .NET

Chapter 5 of Managing AI that does the work

For .NET architects and senior developers who build and maintain business applications · · 8 min read

Where this chapter sits

The first four chapters looked at the organization: where AI work is defined, who may act, what it costs and where the time goes. This chapter goes inside a single program. It is written for the architects and senior developers who build the .NET applications those organizations run on, and it asks the series’ four questions at the smallest scale there is: what a model call means, what an acceptable answer requires, who may change the rules, and what happened when the call ran.

The problem, as you would put it

Every .NET team that has put a model into a business application remembers the first afternoon. You pick a provider, send a prompt with some text, and get back something that looks like what you asked for. The demo works. Someone asks for it in production.

Then the ordinary failures arrive. The model returns a category that is not in your enum. It writes a date your parser rejects. It wraps the JSON in a friendly sentence. It sets a priority of “urgent-ish”. On Tuesday the same input gives a different answer from Monday’s, and nobody can say which one reached the database.

None of this is exotic. It is what happens when a component with a probabilistic output sits inside a pipeline that expects exact types. The call itself was never the hard part. Knowing whether the answer can be trusted, and what to do when it cannot, is the hard part.

There is a second, slower problem. The wording of the prompt, the list of allowed categories and the rules about what counts as a valid answer end up as string constants and if statements in compiled code. When the business changes a rule, the change waits for a release. When an auditor asks which rule was in force last month, the answer is somewhere in the commit history, if anyone can find it.

Why the usual fixes do not hold

The first fix most teams reach for is a better prompt. It helps, until the next unusual input. A prompt states what you want. It does not check what you got.

The second fix is a hand-written loop: parse the answer, validate it, and if it fails, ask again. This works, and almost every team writes one. The trouble is that each team writes its own, often several times inside one application. Each copy has its own idea of how many attempts to allow, what to tell the model on the retry, and what to log. Most of them retry with the same prompt, so the model has no idea what it got wrong. Most of them keep only the final answer, so the failed attempts that would explain a bad result are gone.

The third fix is to move the rules into configuration. That is the right instinct, but a loose settings file has no shape, no version and no check on load. A typo in a list of allowed values becomes a production incident rather than a refused edit.

The fourth fix is to keep the model away from anything that matters: it drafts, and a person retypes the result into the real system. That is cautious, and it gives back most of what the model was meant to save.

Each of these addresses one symptom. None of them gives the call a contract: a stated goal, a stated shape, stated rules, a bounded way to recover, and a record of what happened.

The idea: keep the meaning outside the compiled code

Our thesis starts from a plain observation. Much of business software exists to reconcile differences in representation, and developers keep rewriting code because the business changed its vocabulary, not its need. Its proposal is a separation: a stable execution core carries out exact operations, and external definitions describe the contracts, interpretation rules and domain decisions that change.

That separation applies directly to a model call. The goal of the call, the shape of the answer and the rules an acceptable answer must meet are definitions. They belong in files that can be read, versioned and changed on purpose. The code that sends the call, checks the answer, retries and records the attempts is machinery, and it should be the same for every call in the application.

The checked call: intent and input, the answer, the check, and a retry that carries the findings A left-to-right flow. An intent and an input go to the model the team configured. The model's answer is checked against the .NET type and the rules. If every check passes, the code receives a typed .NET value. If a rule fails, the rule's message goes back to the model on the next attempt, within the limits the team set for attempts, timeouts and model calls. Below, a ledger shows that every attempt is kept: the first refused because a priority was not an allowed value, the second passing every check. Intentgoal · examples · rules Inputtext · record · JSON Modelthe one you configure Answeras returned Checkagainst your .NET typeand your rules Passa typed .NET value Failthe rule's message retry with the findings, within your limits: attempts, timeouts, model calls Every attempt kept Attempt 1answer as returnedrefused: the priority is not an allowed value Attempt 2answer with the finding in handpassed every check: typed value returned
Figure 1. The checked call. The retry tells the model what it got wrong, both attempts stay on record, and a pass means the checks passed, not that the answer is right.

Put together, a call becomes a checked call (Figure 1). The application states the intent and passes the input. The model’s answer is checked against the .NET type the team owns and against the rules beside the intent. When a rule fails, its message goes back to the model on the next attempt, so the retry carries the finding instead of repeating the question. A transient error at the provider can move the call to the next provider in a configured chain. All of it happens inside limits the team set: attempts, timeouts and model-call ceilings. Every attempt is kept, the failed ones included.

Two properties of that picture matter most. The record is complete: a developer can see what the model returned each time and which rule refused it. And a pass means the checks passed. It does not mean the answer is right.

The thesis draws the boundary that keeps this honest. Arithmetic, transaction rules and permission checks keep exact implementations, and the model is reserved for the part of a case that is genuinely uncertain: “When a case genuinely requires interpretation, a bounded model call can address that uncertainty.” (C2Consultants, Intelligent Software and Autonomous Organizations.) A checked call is that sentence written as machinery: bounded by a type, rules and limits, and used only where interpretation is the job.

What it looks like in a firm’s application

Take a mid-sized firm with an internal application that receives customer requests by email and turns each one into a ticket. Today a person reads every message, picks a category, sets a priority and copies the account details into the ticket form. The team wants a model to do the first pass.

The ticket stays an ordinary .NET type the team owns, with its category, priority and account fields. The intent lives in a file: a sentence saying what the call is for, a few examples, the output requirements, and a description written for people rather than for the model. The rules sit beside it: the priority must be one of the allowed values, the account number must match a pattern, a numeric field must fall within a range.

Six months later the business adds a category. Someone edits the intent file and saves it, and the next call uses the new wording with no release. If the edit does not load, the last good version stays in force while the application runs, and the problem is reported by file and line. Every file carries a version, so the team can activate a version and revert to an earlier one.

What changes with a save, and what still needs a release Two columns. Changes with a save: the intent's wording, examples and output requirements; the rules such as allowed values, patterns and ranges; the description written for people; which version is active, including reverting to an earlier one. Needs a release: a new .NET result type, a new named code check, and code that calls a new intent by name. Below, a band explains that a bad save does not load, the last good version stays in force while the application runs, and the problem is reported by file and line. Changes with a save Needs a release The intent's wording, examples, output requirements The rules: allowed values, patterns, ranges The description written for people Which version is active; revert to an earlier one A new .NET result type A new named code check Code that calls a new intent by name These are code, so they ship with code. A bad save does not load. The last good version stays in force while the application runs, and the problem is reported by file and line.
Figure 2. What a save changes and what still needs a release. The wording and the rules move at the speed of the business; types and code still move at the speed of a release.

The thesis is careful about what this does not mean. “Moving a rule outside a compiled method does not remove the responsibility to define it correctly.” (C2Consultants, Intelligent Software and Autonomous Organizations.) A rule in a file still has to be right. What the file adds is a place where the rule can be inspected, revised and tied to the period in which it was in force.

The approach has edges, and they matter. A new .NET result type, a new named code check, or code that calls a new intent by name still needs a release (Figure 2). And the rules catch the failures the team thought to write down. A person still owns the ones nobody anticipated, which is why the failed attempts are kept where a person can read them.

What to do first, before you buy anything

1. Find every place your applications call a model. For each one, write down the goal, the expected shape of the answer and the rules an answer must meet. If you cannot write the rules, you have found a call that is not being checked at all.

2. Count your retry loops. Note how many attempts each allows, what it sends back to the model on a retry, and whether it keeps the failed attempts. Differences between them are differences in behaviour your users already see. Figure 3 is the inventory you are taking: everything on its left side is code your team wrote and now maintains.

The same model call, before and after Left, before: around one model call, code written by hand at each call site: prompt text as string constants, parsing the reply, validating each field, a retry loop that resends the same prompt, business rules as if statements, and logging that keeps only the final answer so failed attempts are lost. Right, after: the team's .NET type and the code that states the intent; an intent file holding the wording, examples, rules and version; the library supplying the call, parsing, the check against type and rules, retry with the findings, fallback to the next provider, every attempt kept and the limits enforced; the code receives a typed value. Before: written by hand around each call Prompt text as string constants Model call Parse the reply; strip the sentence around the JSON Validate each field by hand Retry loop: the same prompt, its own attempt count Business rules as if statements Log the final answer; failed attempts lost Repeated at every call site, each copy a little different. After: supplied by the library, rules in a file Your .NET type, and the code that states the intent Intent file: wording, examples, rules, version Supplied by the library send the call · parse the reply check against your type and your rules retry with the findings · fall back to the next provider keep every attempt · enforce your limits Your code receives a typed value Written once, the same for every call in the application.
Figure 3. The same model call, before and after. The model call does not change; what changes is who writes and maintains the machinery around it, and where the rules live.

3. Trace the rule that changes most often. Pick it and follow what it takes to change it today: who edits it, how long until it reaches production, and how you would prove later which version was in force. That trace is the case for moving it into a versioned file, and it tells you how much a release-free change is worth to your team.

These steps cost nothing but time. They show whether your model calls are checked or merely hoped for, and they give you a concrete starting point whatever you decide next.

How C2Consultants does this

IntentSharp is our .NET library for exactly this, and the program scale of Cognitive Infrastructure, our method. Developers express application behaviour as intent, apply it to ordinary .NET values and methods (text, records, lists, a DataTable or JSON, or an [AIIntent] method that replaces one interpretation step inside a method you already have), and move the changeable wording and rules into JSON or YAML intent files. It checks each answer against your .NET type and your rules, retries with the findings within the limits you set, keeps every attempt, and keeps the last good version when a saved edit is bad. It runs on .NET 8, 9 and 10 with OpenAI, Anthropic or any IChatClient, including a local model on Ollama; data goes only to the model providers you configure, and the licence key is checked offline. Our estimate for a typical business application is 20 to 40% less C# code; that is an estimate, not a measurement. One honest limit: when the running application proposes an edit to a definition, the library applies it by default and ships no review layer, so a team that wants changes reviewed before they run wraps the writer and holds the edits it flags as widening. C2Empowered, our operations platform, is built on IntentSharp and applies the same separation at the scale of an organization. IntentSharp 1.0 is on NuGet for .NET 8, 9 and 10, with documentation at intentsharp.com. It is commercial software, not open source, licensed from $349 per developer per year with unlimited deployments, and you can evaluate it free for 30 days on development and test systems at https://intentsharp.com/pricing.

Next chapter: A Tuesday, before and after. What changes for the people who do the work when the routine part of it runs through a governed platform.

Next

Start with one process.