Prologue — a Chatbot Is the Easy Part
Anyone can bolt a chat box onto an application. Paste an API key, forward the user's text, print the reply. It demos beautifully and collapses the moment it meets a real enterprise: it knows nothing about the user, nothing about the screen they are staring at, nothing about the data that would actually answer their question—and it will happily leak, hallucinate, or run up a bill doing it.
The hard part is not calling a model. The hard part is governing one: deciding, precisely and repeatedly, what it may know, what it may ask for, what it may say, and what it costs—without scattering that discipline across every screen in the product.
That is the problem ITSAI was built to solve, and it solves it the same way the framework solves everything else: by refusing to special-case it. The AI is not an exception to the architecture. It is another projection of it.
1. The Idea in One Sentence
ITSAI is a small, self-contained layer that connects the framework to a large language model without letting the model know anything the framework does not deliberately hand it. Every fact the model sees—who the user is, what screen they are on, what the database says—is injected through well-defined seams, exactly the way the rest of the platform injects behavior through configuration.
In one line:
The AI does not read the application. The application decides, turn by turn, what the AI is allowed to know.
That inversion is the whole design. It keeps the model useful, keeps it grounded in real data, and keeps it inside the same security and configuration boundary as every other capability in the platform. For a manager, that sentence is a risk posture. For an engineer, it is an architecture.
2. Two Pieces, One Boundary
The project is deliberately just two moving parts, plus a thin window to host them.
ITSAI(the engine). Asealedclass—a stateful client that talks to the model's HTTP endpoint, assembles each request, tracks the conversation, counts tokens, estimates load, and enforces safety limits. It has no UI and no knowledge of WinForms. You could unit-test it from a console.CntrlAIAgent(the surface). A reusableUserControlchat panel that can stand alone in a window or be embedded in a tab. It owns the text box, the transcript, the Share Screen and Share Files toggles, the internal commands, and the RTF rendering of replies. It feeds the engine through the same provider seams.FrmAIAgent(the host). A trivialFormwhose only job is to show the control and pass the providers through. It is almost content-free on purpose—proof that the control, not the window, is the real unit of reuse.
The clean split matters: the engine is testable in isolation, and the control is the only thing that touches the screen. Everything interesting flows across the seam between them—and that seam is where the governance lives.
3. The Seam: Providers Instead of Hard-Wiring
The engine never reaches into the framework. Instead, it is constructed with a set of providers—small functions, implemented as Func<> delegates, that the host supplies to answer questions on demand:
| Provider | Question it answers | Where the answer comes from |
|---|---|---|
literal_provider(key) | “What is this configuration value?” | The framework's configuration surface—the database |
system_prompt_provider() | “Who are you and how do you behave?” | Standing instructions |
developer_prompt_provider() | “What operational rules apply?” | Layered rules on top of the system prompt |
u_context(prompt) | “Who is asking, and what may they do?” | User identity, role, and entitlements |
runtime_context_provider() | “What is happening right now?” | Active screen, current record, and last input |
db_context_provider(cmm, params) | “May I look something up?” | A whitelisted, parameterized database lookup |
Because every one of these is a function, not a hard reference, the engine stays ignorant of the framework's internals. The host decides what each function returns—and therefore controls exactly what the model sees. This is the same configuration, not code leverage the framework uses everywhere, aimed at AI.
There is a quiet elegance here worth naming: the model is not “integrated” with the platform in the usual sense. It is handed a set of sealed envelopes, opened only when the framework chooses to open them. Nothing reaches the model by accident, because nothing reaches the model except through a function the framework wrote.
4. A Stateful Conversation, Assembled Every Turn
The engine keeps a private ConversationState: the running message list, turn counters, token totals, and a snapshot of the last system, developer, user, runtime, and database context it sent. On each call it rebuilds the request payload in a fixed, layered order:
- System prompt—plus a hard-coded identity rule appended every single time, so the model never reveals which vendor is behind it.
- Developer prompt—operational instructions.
- User context—who is asking.
- Runtime context—the live state of the app.
- Conversation history—the accumulated back-and-forth.
- The new user message—text, and optionally images.
The order is not decorative. It is a hierarchy of authority: identity and rules first, live context next, history after that, and only then the user's words—so no later, lower-trust text can quietly override an earlier, higher-trust instruction.
One small decision pays off enormously at scale: only the text of a user message is stored in history. Images are attached to the turn that needs them and never re-sent. A conversation can involve a dozen screenshots and still not drag their weight through every subsequent turn—which is the difference between a feature that stays affordable and one that quietly triples the bill by turn ten.
5. Multimodal Input: the Model Can See
The chat control exposes two toggles—Share Screen and Share Files—that are visible only when the host actually provides a screen-capture function or a file-picker function. Capability follows configuration here too: no provider, no button.
When enabled:
- A screen capture is turned into a PNG data URL and attached as a high-detail image part. The user's own screen becomes something the model can reason about: “What's wrong with this record?” or “Read this scanned form for me.”
- A file list is resolved first: directories are expanded into their supported image files (
.png,.jpg,.jpeg,.gif,.bmp, and.webp), sorted for determinism, and capped at a fixed maximum so a stray folder of a thousand images cannot detonate a single request. Each file is base64-encoded with the correct MIME type.
The result is a single multimodal message: the user's text plus one or more images. The framework decides what can be shared; the user decides when. Neither decides carelessly.
6. The Recursive Loop: Letting the Model Ask for Data—through a Door You Hold
This is the most interesting mechanism in the whole layer, and the one that most clearly carries the framework's DNA.
The model does not get the database dumped into its context. That would be slow, expensive, and a security catastrophe. Instead, it can request more information in a structured way, and the engine answers it in a controlled follow-up turn.
The flow:
- The engine sends the turn and reads the reply.
- It tries to parse the reply as a small control object with three fields:
mode,cmm(a command), andparams. - If
mode == "request_info", the engine callsdb_context_provider(cmm, params). The host runs a whitelisted, parameterized lookup and returns the answer as text. - That answer is appended as a new system message, and the engine re-queries with the enriched context—automatically, without troubling the user.
To the operator it looks like the assistant simply knew the answer. Under the hood, the assistant asked a question, the framework decided whether and how to answer it, and the conversation continued. The model never touches SQL, never sees a connection string, and never chooses a table. It names an intention; the framework fulfills it—or does not.
Two guardrails keep this powerful mechanism safe and terminating:
- A recursion cap (
AIMaxRecursiveTurns) stops runaway loops; exceeding it returns a blunt “loop protection triggered.” - A duplicate-context check: if the database returns the same context it just returned, the loop stops instead of spinning—no progress, no point.
So the model can pull exactly the data it needs to answer well, but only through a door the framework holds, one question at a time. This is the AI-era restatement of the platform's founding rule: the database is the contract.
7. Guardrails, Cost Awareness, and Honesty
The engine does a handful of unglamorous things that separate a demo from something you can put in front of real users all day:
- Identity protection. A fixed rule is appended to every system prompt so the assistant never names the underlying vendor. The product has one voice—its own.
- Repetition detection. Send the same prompt three times in a row and the engine returns a light, randomized quip instead of paying for another identical call. It is a joke with a budget attached.
- Token accounting. After each response it reads the reported input and output token usage and accumulates the totals across the whole session.
- Load estimation. It walks the outgoing payload, estimates characters, charges a flat and deliberately conservative cost per attached image, converts the result to approximate tokens, and derives a load percentage against the model's context window plus a coarse complexity score. That collapses into one honest human-readable line—Light · Medium · Heavy · Critical—shown directly in the UI.
- Graceful fatigue. Past a configured conversation length, the assistant begins, on alternating turns, to admit it is losing the thread and suggest that the user reset with
CLS. A fresh context is cheaper and sharper than a bloated one; the assistant is candid about both. - A hard timeout on every HTTP call, so a stalled request can never freeze the UI behind it.
None of this is glamorous. Together it is the entire difference between a party trick and a tool.
8. Where It Plugs into the Platform
Everything about ITSAI mirrors the framework's existing seams, right down to how it ships:
- Configuration lives in the database.
AIKey,AIModel,AITkn,AIApiUrl,AIConversationLength,AIMaxRecursiveTurns, the prompt literals, and the failure and repetition messages are all resolved throughliteral_providerfrom the same configuration surface as the rest of the platform. Swapping models, retuning limits, or rewriting the assistant's personality is a data edit, not a redeploy. - Context comes from the running app. User identity, active screen, and current record come from the same runtime the modules already expose. Nothing new had to be invented for the AI to be situationally aware.
- Data access is the controlled path. Every lookup goes through
db_context_provider, so the model inherits the platform's security instead of quietly routing around it. - The UI is a shared control. Consistent with the framework's compose from shared controls approach,
CntrlAIAgentcan be placed in a form or tab and behave identically everywhere. - Deployment follows the module model. The build copies
ITSAI.dllinto the shell's output and registers it in the module manifest—the same dynamic-loading path every other module uses. The AI is not a bolt-on; it is a citizen with papers.
The message is perfectly consistent with the whole architecture: a new capability is engineered once, at the right seam, and then configured—never re-implemented per screen.
9. A Day in the Life of an AI-Assisted Change
To make it concrete, follow one request through the layer.
An adjuster is looking at a denied claim and types: “Why was this denied, and what's the member's remaining deductible?”
- The control captures the text and, because Share Screen is on, a screenshot of the claim.
- The engine assembles the turn: identity rule, developer rules, this user's entitlements, the runtime context (module = Claims, current record = this claim), the short history, then the question and image.
- The model replies with
mode: "request_info", asking in structured form for the member's deductible balance. db_context_providerruns a whitelisted lookup scoped to what this user is allowed to see and returns the figure as text.- The engine folds that in and re-queries. The model now answers in plain language: the denial reason it read from the screen and the deductible it was handed.
- The operator sees a single, fluent answer—and a status line reading Medium · 41% · In 3,900 / Out 260.
No connection strings crossed the wire to the model. No data the adjuster could not already see was exposed. No screen-specific AI code was written. The capability was built once; this interaction was just configuration meeting intent.
Agent Demonstrations
The captures below show the reusable agent surface applied to three operational tasks. Select any image to inspect the complete demonstration.
10. What It Deliberately Does Not Do
Half the discipline is in the omissions:
- It does not give the model free database access. Only whitelisted, parameterized lookups, one request at a time.
- It does not trust the model's identity to prompt luck. The identity rule is re-asserted on every turn, not just the first.
- It does not let conversations grow without bound. Fatigue warnings and the
CLSreset are built in, and cost is measured, not hoped about. - It does not resend images forever. History holds text; images ride only the turn that needs them.
- It does not hard-wire itself to the framework. Every dependency is a provider, so the engine could be re-hosted against a completely different application by supplying six functions.
Each omission is a deliberate fit to the environment—enterprise data, real users, and a small team that cannot afford a runaway bill or a leak. In a different context some choices would be wrong, and saying so is part of the honesty of the design.
11. The Leverage, Restated One Level Higher
The framework's original thesis was a single sentence: convert senior engineering into reusable configuration. The AI layer does not break that thesis—it extends it by one turn of the screw.
| The framework | The AI layer |
|---|---|
| Senior engineering → configuration | User intent → configuration |
| A screen is a projection of the pipe | An answer is a projection of the context |
| The database is the contract | The database is still the contract |
| Built once, configured hundreds of times | Governed once, invoked endlessly |
Where the framework let one engineer wield the reach of a team, the AI layer lets any user express intent in plain language and have the platform—safely, cheaply, and within its existing security model—turn that intent into grounded action.
That is the whole idea: not a chatbot bolted to the side of the product, but a disciplined bridge that lets a language model participate in the platform strictly on the platform's own terms. Same leverage curve. One level higher.