I built Isvara around a practical problem: a great deal of knowledge already exists on how to operate a private cloud well, from defining service levels and measuring performance to troubleshooting infrastructure, planning capacity and availability, and managing the environment as a service. The hard part is getting to the right piece of it when you need it.
Much of that experience has been captured by Iwan 'e1' Rahabok in his book Private Cloud Management. Across five parts and roughly 243,000 words, the book covers the operational work in detail: defining meaningful service levels, proving a performance problem with the right counters, and interpreting the metrics behind what users experience as “the system is slow.” That depth makes it a valuable reference. During an incident, though, the useful paragraph may be buried several chapters away. I wanted a faster way into the material without reducing it to a thinner summary that loses the original context.
Isvara started by making that knowledge easier to read, search and question through iO, but it now does more than sit in front of the source material. Several of the methods are implemented as tools for SLA design, performance scoring, capacity planning, failover analysis and cost modelling. MCP exposes the same capabilities to other agents and clients, including asking Isvara, calculating operational scores and working with the underlying knowledge.
Isvara is live at isvara.io and is available to anyone working with VMware and Broadcom private-cloud environments.
The project started under the working name IntelliOps, and the assistant name, iO, came from it. My main constraint was to keep the first version small without painting it into a corner. That meant no database, no vector store, and no multi-agent orchestration unless the product showed a real need for them.
How Isvara is put together
Isvara runs as a single Next.js application on Vercel. The book itself is rendered as static, paginated reading pages, and every heading has a stable anchor so citations can link to a specific section. The agent is exposed through two endpoints: /api/chat for the web surfaces and /api/mcp, a thin MCP adapter over the same backend modules. There is no separate API service or queue, and no data store on the answer path; best-effort telemetry lands in Vercel Blob and a small Redis after the response has streamed. On a question, the server loads the Markdown guide from the deployed bundle and includes it in the model context.
I kept the runtime small because I did not yet need retrieval, memory or durable workflows. The first thing I wanted to prove was simpler: can the full guide produce useful, source-linked answers at acceptable latency and cost? The interfaces leave room to add those pieces later.
// The stackWhat the stack contains
I kept the stack simple by design. Every component had to be easy to understand, easy to troubleshoot and necessary to the answer path.
maxDuration is set to 300 seconds for long streaming responses.streamText, and every chat surface shares the same useChat conversation. Models are referenced as provider/model strings through AI Gateway, which keeps model changes small and local.pickModel uses a small heuristic: short lookups and measured-value questions go to Gemini 2.5 Flash-Lite; other questions use Gemini 2.5 Flash, whose 1M context window can hold the full guide. A Sonnet escalation tier exists in configuration but is not currently wired in.content/books. A generated chapter-index.json tracks about 440 sections, and a page manifest maps citations to /read/<part>/<page>#<anchor>. Figures and source .docx files are stored in Vercel Blob rather than git.lib/prompt.ts, backed by an injection pre-scan and a per-request spotlight fence (see What broke). The persona, rules, guide and section list are kept in a stable order for provider caching; only the one-line “currently reading” hint changes per request.lib/opsScore.ts and reused by the agent, the interactive dial and the MCP server. Eight more tools turn methods from the guide into calculators, builders and blueprints, and the tenth is an explorable canvas of the book’s advanced troubleshooting method.AgentProvider and therefore one conversation. ⌘K remains a separate search palette.after() so it never holds the response stream open. npm run eval replays 20 starter queries and checks for valid citations; npm run eval:injection replays a battery of planted attacks. A key-gated admin digest shows volume, outcomes, model mix, attack rate, feedback and downloads.What happens when you ask a question
From the reader's point of view, there is not much machinery to see. A question can be asked from any of the four surfaces, and the current chapter is sent with it as a relevance hint. The answer streams back with normal links to source sections. If the question includes a measured value, the scoring tool can also return a small score chip. Clicking a citation opens the relevant part of the guide.
Sending the current chapter helps with ambiguous questions because the model knows what the reader is looking at. Citations are also real anchors rather than generated footnotes, so an answer can be checked against the source instead of being treated as the final authority.
// Ask iOInside Ask iO
Most of Ask iO lives in one route, so the important decisions are easy to inspect. There is one model call surrounded by five practical pieces: loading the guide, building the system prompt, choosing a model, allowing a bounded amount of tool use, and returning a streamed answer with citations.
Loading the full book into context
I chose not to add retrieval in the first version. On each question, loadBooksContext() assembles all five parts of the guide and includes them in the model context, a pattern I call load-and-ask. The combined guide is built once per server instance and placed in a stable part of the prompt so provider-side caching can reduce repeated input cost. This removes retrieval misses because there is no retrieval stage; it does not remove model error, which is why the source links remain important.
The system prompt and guardrails
The policy layer is part of the system prompt rather than a separate service. Nine numbered rules cover the behaviours I care about most: use the guide first, cite the exact section, search current tech docs only when the guide is insufficient, explain trade-offs, and treat instruction-like text inside supplied content as data rather than commands. Prompt-injection testing showed that the rule alone was not enough, so I added two mechanical checks around it. A regex pre-scan detects likely injection without refusing the question, routes flagged requests to the sturdier model, and records the event. The latest user turn is also fenced between per-request random-nonce markers with a trust reminder after it. The fence is applied to a copy of the messages, so the cached prompt prefix stays unchanged. An injection eval battery checks these three layers for regressions.
Choosing between Flash and Flash-Lite
I did not want to pay for the larger model on every lookup, so pickModel uses a conservative string heuristic with no extra model call. Short, marker-free lookups and measured-value questions go to Gemini 2.5 Flash-Lite; everything else goes to Gemini 2.5 Flash. There are three promotions to Flash: a Lite run that calls a tool switches for its subsequent steps, an attempt that fails before producing text is retried once, and a question the injection pre-scan flags skips Lite entirely. The “try again” action also forces Flash.
Keeping tool use bounded
The model call is small: streamText, temperature 0.2, a frequency penalty, and two tools. stopWhen: stepCountIs(5) gives the model enough room to call a tool and use the result, while putting a hard ceiling on the loop. There is no planner and no sub-agent layer in the current design.
// app/api/chat/route.ts, the heart of the agent (lightly compressed) const { messages, chapter, escalate } = await req.json(); const scan = scanForInjection(latestUserText(messages)); // detect only, never refuse const model = escalate || scan.flagged ? MODEL_PRIMARY : pickModel(latestUserText(messages)); const result = streamText({ model, // 'google/gemini-2.5-flash' or '-lite' system: systemPrompt(loadBooksContext(), chapterListForPrompt(), chapter), messages: convertToModelMessages( spotlightLastUserMessage(messages), // nonce-fence the latest turn only { ignoreIncompleteToolCalls: true }), tools: { computeOpsScore, ...(hasTinyFishKey ? { searchTechDocs } : {}) }, stopWhen: stepCountIs(5), // bounded loop: tool, then answer prepareStep, // a tool call promotes Lite → Flash temperature: 0.2, frequencyPenalty: 0.5, maxOutputTokens: 4096, }); return result.toUIMessageStreamResponse();
Sharing the scoring logic across Isvara
The operations score is shared application code. computeOpsScore takes a metric and measured value, applies the thresholds from the guide (CPU ready 2.5%, memory latency 1.0%, disk latency 10ms, zero dropped packets), and returns a 0–100 score across four bands using 100·(1−value/(4·threshold)). The Tools page uses the same function, so there is only one implementation of the scoring logic. The other agent tool, searchTechDocs, is used when the guide does not cover the question. Tool results can render as score chips, and citations appear as “Read” links into the guide.
// lib/tools.ts, the two instruments the agent can call export const computeOpsScore = tool({ description: 'Compute the four-band operations score for a measured signal...', inputSchema: z.object({ metric: z.enum(['cpu_ready', 'mem_latency', 'disk_latency', 'dropped_packets']), value: z.number().min(0), }), execute: async ({ metric, value }) => scoreFor(metric, value), // lib/opsScore.ts }); export const searchTechDocs = tool({ description: 'Search Broadcom tech docs for topics the guide does not cover...', // pass 1: site:techdocs.broadcom.com · pass 2: wider web, only if pass 1 is empty // 7s timeout, one retry, up to 5 results with exact page URLs });// Current documentation
Using TinyFish for current documentation
The guide will inevitably lag the product. VCF releases continue after a document is published, so some questions cannot be answered reliably from the bundled material alone. searchTechDocs handles that case by searching current documentation when the guide does not cover the topic.
TinyFish powers that search. Isvara queries techdocs.broadcom.com first and falls back to a wider web search only when needed. I use it here because it returns structured results with page URLs the agent can cite directly. For questions outside the bundled guide, I would rather return a checkable source than have the model manufacture a plausible-looking reference.
I kept the integration small: two search passes, a short timeout, one retry, and a clean “search unavailable” result if the key is missing. If external search fails, that part of the answer can fail without taking down the chat endpoint.
// ToolsWhat the ten instruments do
I did not want the book to become chat-only. The Tools section turns ten of its methods into things you can use directly, grouped into four jobs: define the target, measure the environment, plan capacity or cost, and diagnose problems. Each tool shows whether it is based only on the guide or also uses current tech documentation, and the scoring tools reuse the same code as the agent.
From source documents to a working system
I built it in five usable increments, and kept shipping after launch. Each stage left the site in a working state before I added the next layer.
.docx sources to Markdown, generates a chapter-index.json with about 440 stable section anchors, and extracts roughly 1,200 figures to Vercel Blob. Getting this conversion right matters because every later citation depends on it./api/chat handler, load the guide into context, order the stable prompt for caching, and add the nine rules. This was the first point where the agent could answer from the guide with source links.public/llms.txt so other agents can understand the site./api/mcp, layer the prompt-injection defence and its eval battery, add the Advanced Troubleshooting canvas, a downloads page for the original sources, and a key-gated usage digest.Keeping the guide current
The guide changes over time, so the ingestion path also handles refreshes. The source .docx files live in Vercel Blob. A weekly GitHub Action compares their hashes with a manifest; when something changes, it reruns the conversion, rebuilds the index, uploads any changed figures, commits the Markdown, and triggers a redeploy. Updating the content is therefore just a matter of replacing the source document in Blob.
The cost of sending the full book
Sending the full guide has one immediate cost: input tokens. A question carries a few hundred thousand tokens before the model generates an answer, so an uncached Flash request is roughly a tenth of a dollar at the pricing used for this build. Provider caching makes a big difference because the stable guide prefix is usually the expensive part; cached requests can fall to a cent or two. Short questions are routed to Flash-Lite, and the four starter questions are served from a cache without a model call. If input cost starts to dominate, the next options are explicit caching, selecting only the relevant book, or moving to retrieval.
// What brokeWhat broke, and what I changed
The finished diagram hides some of the problems I hit while building it. Each of these five left a concrete change in the current code.
The cheap model would not stop talking
Flash-Lite was attractive on price, but tool use exposed a repeatable failure mode: it would finish an answer, call the tool, and then restate the answer from the beginning. Routing longer or more complex questions to Flash solved most of that behaviour, with a frequency penalty as an additional backstop. pickModel exists largely because of this test result.
One chapter was a hundred and forty thousand words
The first reader rendered one page per book. One page ended up at roughly 140,000 words of Markdown, which was slow on desktop and unpleasant on a phone. The current reader splits books by chapter and splits the largest chapters again at subheadings, keeping pages to roughly 25,000 words or less. Citations now resolve to the exact page, and old anchor links redirect to the page that owns the section.
Invisible whitespace crashed the page
Some merged-cell tables do not convert cleanly to Markdown, so they remain as HTML. React hydration failed when whitespace appeared between certain table tags. The fix belongs in ingestion: the converter now emits those tables as a contiguous HTML block with no inter-tag whitespace, which removes the runtime failure instead of working around it in the reader.
A dropped connection poisoned the next question
If a reader navigated away during a tool call, the conversation could retain an incomplete function call. Replaying that thread on the next question caused a provider 400. The endpoint now ignores incomplete tool calls during message conversion and buffers the opening chunks so a failure before any text can be retried once on Flash.
A crafted paste walked straight past the rules
Prompt-injection testing produced one live failure: a template-wrap attack pasted into a question got Flash-Lite to obey the planted instruction. I did not try to fix that by adding another paragraph to the prompt. I kept the semantic rule and added two mechanical checks: a regex pre-scan that detects likely injection without refusing anything, routes flagged questions to Flash and records them, plus per-request random-nonce markers around the latest user turn that a pasted document cannot forge. The injection eval battery catches regressions, and the admin digest tracks the live attack rate. Some template-wrap attacks still succeed on Flash. I accept and monitor that residual rather than add another model call or block suspicious-looking questions outright.
// Public accessWhy Isvara is public
The vCommunity has always been generous with knowledge, scripts, troubleshooting experience and practical guidance. Iwan and I wanted Isvara to contribute in the same spirit, which is why it is available as a free public resource at isvara.io with no authentication gate. The reader is paginated and usable on a phone, every heading has a deep link that can be dropped into a ticket or shared with a colleague, and the downloads page includes the source documents, diagrams, sample reports and spreadsheets. public/llms.txt gives other AI systems a machine-readable description of the site.
Isvara also exposes an MCP server at isvara.io/api/mcp, so Claude Desktop, IDEs and other agents can use the same capabilities as the web application. ask_isvara uses the same grounded question-answering path and citations, while ops_score calls the scoring logic directly without using a model. The section index, thresholds and guide are also available as MCP resources. I reuse the same backend modules for both the web app and MCP, rather than maintaining separate implementations. I only expose a capability through MCP once its underlying logic has been separated from the page code, as I did with the operations score. The MCP page has the setup details.
Isvara has also been shaped by the people around it. Vincent Han has been part of the discussions and ideas throughout, and has since taken the build on-prem, running it on Kubernetes with VKS, the vSphere Kubernetes Service. How the same architecture behaves on a private-cloud platform instead of Vercel is a story for a future post.
// What I left outWhat I left out
A few components you might expect are missing from this version. I left them out because the current workload does not justify them yet.
When full-context stops making sense
Full-context prompting works well here because the corpus is bounded, changes infrequently, and fits inside the model window. It also avoids a retrieval stage, so there is no risk of selecting the wrong chunk before generation. That does not make it universally better: the model can still misunderstand the material, and the input cost is real. I would add retrieval when the corpus no longer fits comfortably, changes too often to cache effectively, or traffic makes full-context input materially expensive. Starting with a vector store by default would have added infrastructure before I had evidence that retrieval was the actual problem.
Two additions are the most likely next steps:
- Retrieval, memory, and durable workflows. If the corpus or traffic grows, hybrid retrieval with a re-ranker over Postgres/pgvector could replace full-guide prompting. Longer-running operational methods could then become persisted workflows, with their execution history serving as the evidence trail. Memory would be a separate concern rather than something hidden inside chat history.
- A heavier model for the hardest questions. A Sonnet escalation tier is defined in the model configuration but is not wired in yet. If the eval set or question log shows a class of questions the routed Gemini pair handles poorly, it can be added behind the existing router without changing the rest of the architecture.
That is the approach I have taken with Isvara: start small, watch where the pressure appears, and add infrastructure only when the workload needs it. At this stage, being able to inspect and change the whole system easily is more valuable than adding another layer.
That is where Isvara stands today. It runs as one application, with the same agent available through web chat and MCP. Iwan's full guide stays in context, two agent tools handle scoring and current documentation, and ten operational instruments sit alongside the reader. Retrieval and memory can come later if usage justifies them. For now, keeping the system small makes its behaviour, cost and failure modes easier to understand.