<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rinat kozin</title>
    <description>The latest articles on DEV Community by rinat kozin (@rinat_kozin).</description>
    <link>https://dev.to/rinat_kozin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3929984%2F9dcd2f94-0b1b-4161-9693-3e8c3f6ce385.jpg</url>
      <title>DEV Community: rinat kozin</title>
      <link>https://dev.to/rinat_kozin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rinat_kozin"/>
    <language>en</language>
    <item>
      <title>redb.Route.Llm 3.1.1 — per-message audit fields for LLM compliance / replay</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Thu, 11 Jun 2026 22:48:57 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/redbroutellm-311-per-message-audit-fields-for-llm-compliance-replay-4mi1</link>
      <guid>https://dev.to/rinat_kozin/redbroutellm-311-per-message-audit-fields-for-llm-compliance-replay-4mi1</guid>
      <description>&lt;p&gt;When an auditor asks "reproduce this exact answer" six months later, you need more than (messages, model). You need the sampling parameters that were actually applied, the prompt template version, the tool set the model saw, and ideally the provider's backend fingerprint — because closed-source providers silently re-release weights.&lt;/p&gt;

&lt;p&gt;Just shipped this in redb.Route.Llm 3.1.1: 7 nullable audit fields on every persisted message.&lt;/p&gt;

&lt;p&gt;Set on assistant rows:&lt;/p&gt;

&lt;p&gt;Temperature, MaxTokens, TopP — effective values (request override → factory default)&lt;br&gt;
ToolSetHash — SHA-256 of canonical {name, description, InputSchema} set, sorted by name. Tool added/renamed/schema-changed → hash changes&lt;br&gt;
ProviderSystemFingerprint — system_fingerprint from the response (OpenAI / xAI / Together echo it; Anthropic / Gemini-compat / Ollama leave it null)&lt;br&gt;
Set on every row of the run:&lt;/p&gt;

&lt;p&gt;PromptTemplateName, PromptTemplateVersion — when the caller used a managed template&lt;br&gt;
Honest limitation: bit-exact replay is only possible with self-hosted (Ollama / vLLM / llama.cpp). Anthropic doesn't expose a fingerprint, OpenAI rotates them silently. For closed providers we record (model_id, params, tool_set_hash, prompt_template) and label it best-effort.&lt;/p&gt;

&lt;p&gt;Zero migration. REDB stores props schemalessly — added 7 fields to the C# class, redeployed, done. No ALTER TABLE, no version bump on the storage layer.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; details: CHANGELOG.md → 3.1.1 — section per-message audit fields on MessageProps.&lt;/p&gt;

&lt;p&gt;Triggered by feedback from a compliance auditor on Habr — turns out "we log conversations" doesn't cut it when the courtroom asks which exact prompt drove this.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Enterprise-grade AI integration: embedding LLMs into the business processes of large companies — redb.Route.Llm 3.1.1</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Wed, 10 Jun 2026 21:05:20 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/enterprise-grade-ai-integration-embedding-llms-into-the-business-processes-of-large-companies--3a36</link>
      <guid>https://dev.to/rinat_kozin/enterprise-grade-ai-integration-embedding-llms-into-the-business-processes-of-large-companies--3a36</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ssuz2slbrels1zh17p2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ssuz2slbrels1zh17p2.png" alt="redb route llm AI" width="659" height="797"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; redb ecosystem (LLM, part 2)&lt;/p&gt;

&lt;p&gt;A few days ago I announced &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/"&gt;&lt;code&gt;redb.Route.Llm&lt;/code&gt;&lt;/a&gt; — the 24th transport in &lt;code&gt;redb.Route&lt;/code&gt;, where calling an LLM is &lt;code&gt;.To("llm://claude")&lt;/code&gt; and an agent tool is just a route with &lt;code&gt;.AsLlmTool("shell")&lt;/code&gt; on it. The whole pitch was: &lt;strong&gt;stop bolting an "AI framework" onto an integration framework that already has retry, throttle, breaker, audit, observability&lt;/strong&gt;. Plug the LLM in as one more endpoint, get those primitives for free.&lt;/p&gt;

&lt;p&gt;I closed that announcement with a deliberately uncomfortable section called the &lt;strong&gt;"honest skip-list"&lt;/strong&gt; — features I hadn't shipped yet: streaming end-to-end, tool cache, RAG knowledge store, async batch + callback consumer, eval-run store, sliding-window memory, sandboxed tool execution. I'd rather under-promise in writing than over-promise in screenshots.&lt;/p&gt;

&lt;p&gt;Most of that list is shipped. But that's not what this post is about. This post is about the thing I didn't quite see at announcement time: &lt;strong&gt;shipping the skip-list turned the whole thing into something different from a chat library&lt;/strong&gt;. It turns out that once you put an LLM into an ESB and start adding the boring enterprise plumbing, the result isn't "a chat framework with extras". It's a &lt;strong&gt;runway from a 6-line demo to a multi-tenant, audited, budgeted, human-in-the-loop agent platform — with no rewrite in the middle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the story I want to tell. Three angles: deep dive on the technically-tricky bits (streaming, persistence, RAG), eight enterprise patterns that collapse into one DSL line each, and a project-to-platform timeline I've watched live more than once.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Coming in cold?&lt;/strong&gt; Earlier in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/"&gt;redb.Route 3.1.0 — LLM as just another connector&lt;/a&gt; (announcement)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0"&gt;redb.Route — Apache Camel for .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/enterprise-integration-patterns-in-net-the-deep-dive-series-part-1-the-four-in-memory-channels-3e24"&gt;Enterprise Integration Patterns in .NET, part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A quick word on EIP.&lt;/strong&gt; Throughout this post I lean on Enterprise Integration Patterns — &lt;code&gt;Multicast&lt;/code&gt;, &lt;code&gt;Aggregator&lt;/code&gt;, &lt;code&gt;Scatter-Gather&lt;/code&gt;, &lt;code&gt;Wire-Tap&lt;/code&gt;, &lt;code&gt;Choice&lt;/code&gt;, &lt;code&gt;Aggregate-by-window&lt;/code&gt; — without unpacking each of them in detail. That's deliberate: the "EIP in .NET via redb.Route" series with per-pattern deep-dives (diagrams, code, side-by-sides) hasn't shipped yet, I'm writing those in parallel. So I'm getting a little ahead of myself here.&lt;/p&gt;

&lt;p&gt;If you've worked with &lt;strong&gt;Apache Camel&lt;/strong&gt; or &lt;strong&gt;WSO2 Micro Integrator&lt;/strong&gt;, you'll spot the patterns on the fly — the names and semantics are identical. I've personally shipped and maintained integration projects on WSO2 MI for years (and on its ESB ancestor), but architecturally &lt;code&gt;redb.Route&lt;/code&gt; sits closer to Camel — a compiled DSL over a typed Exchange, not an XML config. Side-by-side comparisons "Camel ↔ redb.Route" and "WSO2 MI ↔ redb.Route" are their own posts, also in flight. If anything EIP-flavoured in the code below trips you up, drop a question in the comments — I'll answer on the spot, and the answer goes straight into those upcoming pieces.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why this isn't "yet another agent framework"
&lt;/h2&gt;

&lt;p&gt;The agent-framework shelf is crowded. LangChain, LangChain4j, Semantic Kernel, AutoGen, LlamaIndex — each one solves the same single problem: &lt;em&gt;how does my model call functions and remember a conversation&lt;/em&gt;. Each one leaves you to solve the &lt;strong&gt;other eighteen problems&lt;/strong&gt; yourself: retry, idempotency, audit, multi-tenant, budget, observability, governance, approval, batch, scheduling, replay, dead-letter, throttling, circuit-breaking, correlation, timeouts, cost attribution, metric exporters.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;redb.Route.Llm&lt;/code&gt;'s entire reason for existing is to make those eighteen problems be already solved at the framework layer. Not "we have an integration library, plus an LLM library, glue them yourself" — same DSL, same runtime, same governance hooks. If you already have a &lt;code&gt;redb.Route&lt;/code&gt; policy interceptor that writes every message to Kafka, it automatically writes every tool call from your agent to Kafka too — because they're the same Exchange.&lt;/p&gt;

&lt;p&gt;This isn't theoretical elegance. It cuts through the single biggest production headache with agents: &lt;strong&gt;what happens the day your model decides to drop a production table?&lt;/strong&gt; In the LangChain-plus-hooks shape, the answer is "wire up our approval callbacks and don't forget". In the LLM-as-transport shape, the answer is "intercept the Exchange before &lt;code&gt;.To("exec://...")&lt;/code&gt; with the same &lt;code&gt;.Process(...)&lt;/code&gt; you use for any other potentially-destructive step in any other route". Not a new pattern for AI. The same pattern as for FTP, Kafka, JMS.&lt;/p&gt;

&lt;p&gt;Same people, same tools, same review gates. That's the actual win.&lt;/p&gt;




&lt;h2&gt;
  
  
  What got shipped from the skip-list
&lt;/h2&gt;

&lt;p&gt;I said I would, and I'm saying I did.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skip-list item&lt;/th&gt;
&lt;th&gt;Status in 3.1.1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Streaming end-to-end (HTTP SSE + WS per-frame)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shipped.&lt;/strong&gt; &lt;code&gt;IAsyncEnumerable&amp;lt;string&amp;gt;&lt;/code&gt; lives in &lt;code&gt;Out.Body&lt;/code&gt;, the HTTP consumer flips into SSE, the WS consumer dispatches one frame per message.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ToolCacheStore on REDB&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shipped.&lt;/strong&gt; &lt;code&gt;ToolCacheProps&lt;/code&gt;, opt-in via &lt;code&gt;ToolCachingPolicy.Memoize&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KnowledgeStore — RAG chunks in REDB&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shipped (partial).&lt;/strong&gt; &lt;code&gt;KnowledgeChunkProps&lt;/code&gt; with metadata + tenant + ACL; embeddings land in a follow-up release.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BatchStore + LlmCallbackProcessor&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shipped.&lt;/strong&gt; Anthropic Message Batches and OpenAI Batch, async webhook consumer, idempotent dispatch, retry-aware.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EvalRunStore&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shipped.&lt;/strong&gt; Eval runs are first-class objects with trace ids and prompt-version pinning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PromptTemplateStore&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shipped.&lt;/strong&gt; Versioned prompt registry, referenced from DSL with &lt;code&gt;#name&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sliding-window memory&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not shipped.&lt;/strong&gt; Different bet placed instead: tree-branching conversations via REDB-tree (more on this below).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandboxed tools (per-call container)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not shipped&lt;/strong&gt; in container form. Shipped &lt;code&gt;redb.Route.Exec&lt;/code&gt; with allowlist + working-dir + timeout + byte-cap — the practical enterprise minimum.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bonus, not in the skip-list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-exchange &lt;code&gt;?redb=&amp;lt;name&amp;gt;&lt;/code&gt; hint&lt;/strong&gt; — pick a named REDB instance per Exchange, so one route serves N tenants without N route registrations or N &lt;code&gt;IServiceScope&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DSL/tool package split&lt;/strong&gt; — &lt;code&gt;redb.Route.Llm.Abstractions&lt;/code&gt; (contracts only) and &lt;code&gt;redb.Route.Llm.Tools&lt;/code&gt; (six utility tools), so 22 connectors can register &lt;code&gt;.AsLlmTool()&lt;/code&gt; without bumping a minor on every change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six utility tools out of the box&lt;/strong&gt;: &lt;code&gt;HttpFetch&lt;/code&gt;, &lt;code&gt;JsonPath&lt;/code&gt;, &lt;code&gt;XPath&lt;/code&gt;, &lt;code&gt;RegexExtract&lt;/code&gt;, &lt;code&gt;MathEval&lt;/code&gt;, &lt;code&gt;TavilyWebSearch&lt;/code&gt; — each as both a DSL extension and a standalone tool route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug fixes that only show up in real traffic&lt;/strong&gt;: orphan &lt;code&gt;tool_use&lt;/code&gt; (model asks for a tool, provider 5xxs mid-turn, leaves the conversation in an invalid state) and OEM codepage in &lt;code&gt;Process.StandardOutput&lt;/code&gt; (Windows &lt;code&gt;cmd /c&lt;/code&gt; returns cp866 bytes, breaks the UTF-8 contract every tool implicitly assumes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eleven REDB schemas now sit behind all of this: &lt;code&gt;ConversationProps&lt;/code&gt;, &lt;code&gt;MessageProps&lt;/code&gt;, &lt;code&gt;ApprovalProps&lt;/code&gt;, &lt;code&gt;CostBudgetProps&lt;/code&gt;, &lt;code&gt;ToolCacheProps&lt;/code&gt;, &lt;code&gt;ToolAuditProps&lt;/code&gt;, &lt;code&gt;KnowledgeChunkProps&lt;/code&gt;, &lt;code&gt;PromptTemplateProps&lt;/code&gt;, &lt;code&gt;EvalRunProps&lt;/code&gt;, &lt;code&gt;LlmBatchProps&lt;/code&gt;, &lt;code&gt;ToolIdempotencyProps&lt;/code&gt;. That's not a database for your chat history. That's the &lt;strong&gt;operational layer of an agent platform&lt;/strong&gt;, opt-in via one line: &lt;code&gt;AddRedbLlmStorage()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ten enterprise patterns — each one a single DSL line
&lt;/h2&gt;

&lt;p&gt;Here's where it gets useful. None of these are pseudocode. They all run today.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Hard budget per conversation as a circuit breaker
&lt;/h3&gt;

&lt;p&gt;In a side project, a "token budget" is a log line. In production, it's a &lt;strong&gt;safety primitive&lt;/strong&gt; — your model must not be allowed to burn $10k because of a bad prompt. &lt;code&gt;CostBudgetProps&lt;/code&gt; isn't an observability artifact; it's a &lt;strong&gt;preventive rule&lt;/strong&gt;. Every provider call, the agent engine sums spend by conversation id and adds the worst-case for the pending request (via &lt;code&gt;max_tokens&lt;/code&gt;). If the total would breach the cap, &lt;code&gt;LlmBudgetExceededException&lt;/code&gt; fires &lt;strong&gt;before the network call&lt;/strong&gt;, not after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://support-tickets"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"claude"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Conversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"ticket-"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"TicketId"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// hard ceiling per conversation id&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudgetExceeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BudgetPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FailFast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://support-replies"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same primitive scales to "budget per tenant", "budget per prompt template", "budget per model" — they're all just different keys in &lt;code&gt;CostBudgetProps&lt;/code&gt;. Same shape, different &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Approval gates with a human in the loop
&lt;/h3&gt;

&lt;p&gt;The most under-appreciated production pattern in agent-land. If your model can refund payments or delete records, &lt;strong&gt;a human stands between the tool call and the actual side effect&lt;/strong&gt;. Not a webhook duct-taped on later — a native runtime concept.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct:tool-payments-refund"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsLlmTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"issue_refund"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Refund a payment by id and reason."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* JSON Schema */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SideEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolSideEffect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// governance hook reads this&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolCostClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Expensive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApprovalGate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                        &lt;span class="c1"&gt;// suspend exchange,&lt;/span&gt;
                                                     &lt;span class="c1"&gt;// write ApprovalProps,&lt;/span&gt;
                                                     &lt;span class="c1"&gt;// notify Slack,&lt;/span&gt;
                                                     &lt;span class="c1"&gt;// wait for HTTP callback&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://payments.refund.commands"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ApprovalProps&lt;/code&gt; records the exchange id, conversation id, input arguments, the human approver, the wait deadline, and the outcome. The Slack bot, the email handler, the web form — all of those plug in as ordinary HTTP routes in the same &lt;code&gt;redb.Route&lt;/code&gt;. When the human clicks &lt;strong&gt;Approve&lt;/strong&gt;, the webhook wakes the suspended Exchange and feeds the result back to the agent engine. On timeout, the agent receives a &lt;code&gt;tool_result&lt;/code&gt; with &lt;code&gt;status:"timeout"&lt;/code&gt; and decides what to do next.&lt;/p&gt;

&lt;p&gt;The point: &lt;strong&gt;this is not AI logic.&lt;/strong&gt; It's the EIP &lt;code&gt;Aggregator + Correlation Identifier + Wire-Tap + Reply-To&lt;/code&gt; pattern, which already applies to a refund engine without an LLM. They're both Exchanges in the same runtime, so the pattern is reused.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Idempotent tool retries via &lt;code&gt;ToolIdempotencyProps&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Webhook consumers retry. Network timeouts retry. Anthropic's batch sometimes delivers a payload twice. If your tool call "issue refund $50" runs twice, that's a bad day at standup.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ToolIdempotencyProps&lt;/code&gt; keeps &lt;code&gt;idempotency-key → tool-result&lt;/code&gt; with a TTL. In the DSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct:tool-issue-invoice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsLlmTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"issue_invoice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Caching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolCachingPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Idempotent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildIdempotencyKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;// sha256(args + customer-id + day)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the agent engine sees &lt;code&gt;Caching = Idempotent&lt;/code&gt;, it consults &lt;code&gt;ToolIdempotencyProps&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; the route runs. Hit → returns the saved &lt;code&gt;tool_result&lt;/code&gt;, route never executes. Miss → route runs, result is recorded. Framework-level, not "remember to wrap your handler".&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Multi-tenant via &lt;code&gt;?redb=&amp;lt;name&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;One worker, fifty customers. Each customer is &lt;strong&gt;their own REDB instance&lt;/strong&gt; — their conversations, their cost budgets, their approvals, their knowledge base. In 3.1.0 this would have meant fifty &lt;code&gt;IServiceScope&lt;/code&gt;s or fifty route registrations. In 3.1.1 it's a &lt;strong&gt;per-exchange hint&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http:0.0.0.0:5088/api/llm/ask?inOut=true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LlmHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Redb&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"X-Tenant"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llm://claude?conversationFromHeader=true"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;?redb=acme&lt;/code&gt; or a header — the engine pulls the named REDB instance from the registry, &lt;strong&gt;on the current Exchange&lt;/strong&gt;, no factory, no route swap, no restart. Conversations, audit, approvals all land in the right tenant. Each tenant gets its own billing surface and governance lane, with no awareness of the others.&lt;/p&gt;

&lt;p&gt;This was the single feature most worth the late nights. It's the difference between "we have a multi-tenant agent platform" and "we have a multi-tenant deployment story, here's a wiki page".&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Audit trail without a separate integration
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ToolAuditProps&lt;/code&gt; is a REDB object: tenant id, conversation id, exchange id, tool name, input args (or hash, if PII), output (or hash), duration, status. Every tool invocation is recorded automatically — because &lt;strong&gt;tools are routes&lt;/strong&gt;, and &lt;code&gt;redb.Route&lt;/code&gt; already has post-processors.&lt;/p&gt;

&lt;p&gt;The query "show me every Claude tool call in tenant &lt;code&gt;acme&lt;/code&gt; over the last 7 days, side-effect=mutating, cost=expensive, ordered by time" isn't a separate analytics pipeline. It's a &lt;strong&gt;&lt;code&gt;value_string&lt;/code&gt; index plus one SQL&lt;/strong&gt; — the same indexed-business-id pattern the rest of the REDB ecosystem uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Async batch + callback consumer
&lt;/h3&gt;

&lt;p&gt;Anthropic Message Batches and OpenAI Batch are &lt;strong&gt;up to 50% cheaper&lt;/strong&gt;, with a price tag of up to 24 hours of latency. For offline workloads — classifying a million tickets, extracting fields from a million PDFs — that's the right knob to turn.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LlmBatchProps&lt;/code&gt; stores the batch id, statuses, and the link back to the originating Exchange collection. &lt;code&gt;LlmCallbackProcessor&lt;/code&gt; is &lt;strong&gt;just an &lt;code&gt;HTTP&lt;/code&gt; route&lt;/strong&gt; that the provider calls when the batch finishes. The route reads the batch id, fetches the results, dispatches each one back to its originating Exchange via correlation id — and those Exchanges resume their journey as if the synchronous call had just finished, twenty-four hours later.&lt;/p&gt;

&lt;p&gt;On top of this: idempotency (same &lt;code&gt;ToolIdempotencyProps&lt;/code&gt;), retry (the standard &lt;code&gt;redb.Route&lt;/code&gt; circuit breaker), backpressure (Kafka or queue downstream). No batch dispatcher to write. No webhook handler to write. No "what if the callback arrives twice" to test separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Versioned prompt registry: &lt;code&gt;#&lt;/code&gt;-refs
&lt;/h3&gt;

&lt;p&gt;The single most depressing prod bug pattern: "the model is answering differently". You go check git blame, somebody touched the system prompt three weeks ago, the tests passed because the eval set is small. The prompt is &lt;strong&gt;code&lt;/strong&gt;, and it deserves a registry with versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// where prompts get registered:&lt;/span&gt;
&lt;span class="n"&gt;promptRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"triage-system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"v3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;    &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="n"&gt;triage&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Classify&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abuse&lt;/span&gt;&lt;span class="p"&gt;]...&lt;/span&gt;
    &lt;span class="s"&gt;""");
&lt;/span&gt;
&lt;span class="c1"&gt;// in the route:&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"claude"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#triage-system@v3"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PromptTemplateProps&lt;/code&gt; is a REDB object: name, version, body, metadata (author, date, experiment id). When the engine resolves &lt;code&gt;#name&lt;/code&gt;, it pins &lt;strong&gt;exactly that version&lt;/strong&gt; into &lt;code&gt;MessageProps&lt;/code&gt; for the call. Six months later you can say with certainty "this conversation ran on &lt;code&gt;triage-system&lt;/code&gt; v3", not "probably v3, that's what we were doing then".&lt;/p&gt;

&lt;p&gt;The juicy part: &lt;code&gt;EvalRunProps&lt;/code&gt; records eval runs &lt;strong&gt;bound to a prompt version&lt;/strong&gt;. "v4 gives +12% accuracy on the golden set" stops being a spreadsheet and becomes a query.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Tree-branching conversations for A/B and counterfactuals
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ConversationProps&lt;/code&gt; is stored as a REDB tree via the native &lt;code&gt;parent_id&lt;/code&gt;. That means a conversation isn't a flat list of messages — it's a &lt;strong&gt;tree&lt;/strong&gt;. So you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;branch from any message and run an alternative continuation with a different model or temperature;&lt;/li&gt;
&lt;li&gt;keep a user branch and an experiment branch in parallel;&lt;/li&gt;
&lt;li&gt;compute metrics across pairs of branches ("with-tools vs without-tools on the exact same context").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sliding-window memory &lt;strong&gt;truncates the past&lt;/strong&gt;. Tree-branching memory &lt;strong&gt;writes alternative pasts and compares them&lt;/strong&gt;. For production agents, the second one is an order of magnitude more useful, because improving prompts in production is exactly that loop: take a real conversation, replay with a new prompt, score it, ship the winner.&lt;/p&gt;

&lt;p&gt;And nothing here was bolted on for LLMs. Tree via &lt;code&gt;parent_id&lt;/code&gt; has been a first-class REDB feature from day one — for product hierarchies, org charts, file systems. The LLM stack just wrote its own entity on the same primitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. A jury of cheap models with a senior model as arbiter — Scatter-Gather + Aggregator
&lt;/h3&gt;

&lt;p&gt;One of the most underrated production tricks in agent-land: &lt;strong&gt;don't trust a single model&lt;/strong&gt;. Send the same task to several cheap models in parallel (Haiku, GPT-4o-mini, Mistral-Small, Gemini-Flash, Llama-3.1-70b on Groq), gather their answers, and hand the original prompt &lt;strong&gt;plus all five candidates&lt;/strong&gt; to a &lt;strong&gt;senior model&lt;/strong&gt; (Sonnet, Opus, GPT-4o) acting as arbiter — pick the best, synthesise a new one, or say "nobody nailed it, ask the human". The literature calls this &lt;strong&gt;mixture-of-agents&lt;/strong&gt; or &lt;strong&gt;ensemble voting&lt;/strong&gt;, and on hard tasks it routinely buys you +10 to +20% accuracy at a lower bill than running everything through Opus.&lt;/p&gt;

&lt;p&gt;In a vanilla LangChain shape, this becomes a hundred-line orchestrator with try/catch, per-model timeouts, retry knobs, and a hand-rolled aggregator. In an ESB shape, it's the &lt;strong&gt;bog-standard EIP &lt;code&gt;Scatter-Gather + Aggregator&lt;/code&gt;&lt;/strong&gt;, with twenty-five years of hardening in integration buses. The LLM is just one more endpoint type the Scatter-Gather fans out to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://contract-clauses-to-classify"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contract-jury"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Multicast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                          &lt;span class="c1"&gt;// Scatter&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StopOnException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                           &lt;span class="c1"&gt;// one provider down? carry on&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                &lt;span class="c1"&gt;// per branch&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#contract-classify@v3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-4o-mini"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#contract-classify@v3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"groq-llama-70b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#contract-classify@v3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mistral-small"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#contract-classify@v3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                                &lt;span class="c1"&gt;// Gather: results land in Exchange.Properties["multicast.results"]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JuryAggregator&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                            &lt;span class="c1"&gt;// splice candidates into one arbiter prompt&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sonnet"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                             &lt;span class="c1"&gt;// Arbiter&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#jury-arbiter@v2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://contract-clauses-classified"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;JuryAggregator&lt;/code&gt; is a tiny processor: takes the four answers and the original prompt, builds one arbiter message of the shape "here is the task; here are candidates A/B/C/D; return the final classification or say &lt;code&gt;unclear&lt;/code&gt;". The arbiter answers with structured JSON. Headers &lt;code&gt;llm.tokens.in/out&lt;/code&gt; are recorded &lt;strong&gt;per branch&lt;/strong&gt; plus once for the arbiter — cost is transparent at the route level.&lt;/p&gt;

&lt;p&gt;Why ESB-shape makes this so cheap to express:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallelism for free.&lt;/strong&gt; Multicast EIP already knows how to fan out across N branches, await all-or-N-of-M, handle timeouts and partial failures. No &lt;code&gt;Task.WhenAll&lt;/code&gt; with hand-written failure handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency for free.&lt;/strong&gt; A retry on a failed branch hits the same &lt;code&gt;ToolIdempotencyProps&lt;/code&gt; (or provider-side prompt-hash cache) and dedups. No double billing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget for free.&lt;/strong&gt; &lt;code&gt;.CostBudget(usd: 0.05)&lt;/code&gt; on the arbiter is its own circuit-breaker; per-branch &lt;code&gt;.CostBudget(usd: 0.01)&lt;/code&gt; caps the cheap models. A combined ceiling is a different &lt;code&gt;CostBudgetProps&lt;/code&gt; key. Breach it and the whole jury fails fast — failover to a rule-based classifier (which sits as another branch on the route).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit for free.&lt;/strong&gt; Each of the five LLM calls is captured in &lt;code&gt;ToolAuditProps&lt;/code&gt;, all stitched to one exchange id. A month later you can answer "model X agrees with the arbiter 73% of the time — let's drop it and save".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eval for free.&lt;/strong&gt; &lt;code&gt;EvalRunProps&lt;/code&gt; records the run with five branches plus the arbiter — replay against a golden set to find the &lt;strong&gt;optimal jury composition&lt;/strong&gt; (swap GPT-4o-mini for Gemini-Flash, see if accuracy holds).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In production this pattern wins on two axes. &lt;strong&gt;Accuracy&lt;/strong&gt;: on tasks like "is this contract clause a risk", where a single model wobbles, four cheap models plus a Sonnet arbiter routinely outperforms a lone Opus, &lt;strong&gt;at 2.5× lower cost&lt;/strong&gt;. &lt;strong&gt;Robustness&lt;/strong&gt;: when Anthropic is having a bad afternoon, the Anthropic branch fails, the other four return, the arbiter receives four candidates instead of five and ships a verdict. Graceful degradation, not a hard outage.&lt;/p&gt;

&lt;p&gt;One subtle production gotcha: &lt;strong&gt;never tell the arbiter who wrote which answer&lt;/strong&gt;. If the arbiter's prompt says "candidate from Claude Haiku, candidate from GPT-4o-mini", the arbiter develops favourites (so do humans). So &lt;code&gt;JuryAggregator&lt;/code&gt; anonymises the candidates as &lt;code&gt;A/B/C/D&lt;/code&gt;, shuffles their order (Latin-squared on &lt;code&gt;exchange-id&lt;/code&gt; for reproducibility), and &lt;strong&gt;only after the arbiter responds&lt;/strong&gt; do we map back to "A was the Haiku answer". Clean signal for the post-hoc analysis "which model agrees with the arbiter most often".&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Sub-agents — an agent as another agent's tool
&lt;/h3&gt;

&lt;p&gt;A direct consequence of the "tool = route" architecture: if a route can contain &lt;code&gt;.To("llm://...")&lt;/code&gt;, then &lt;strong&gt;a tool can itself be an agent&lt;/strong&gt;. The parent agent doesn't "know" there's another LLM hiding behind that tool. To it, &lt;code&gt;research_topic&lt;/code&gt; is just another tool, like &lt;code&gt;web_search&lt;/code&gt; or &lt;code&gt;math_eval&lt;/code&gt;. Inside the tool, though, lives a fully-fledged second-tier agent with its own model, prompt, toolset, budget, iteration cap, retry policies, RAG sources.&lt;/p&gt;

&lt;p&gt;In code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sub-agent: a research specialist with its own toolset&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct:research-subagent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsLlmTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"research_topic"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Deep research on a topic. Takes {topic, depth}. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                     &lt;span class="s"&gt;"Returns a structured summary with sources."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""{"&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="s"&gt;":{
&lt;/span&gt;                    &lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="s"&gt;"depth"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s"&gt;"short"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"deep"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;
                  &lt;span class="s"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s"&gt;""")
&lt;/span&gt;        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SideEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolSideEffect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolCostClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Expensive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;// parent sees the call is pricey&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Knowledge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"research-corpus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// sub-agent has its own RAG corpus&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sonnet"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;// mid-tier model — research specialist&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#research-specialist@v3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tavily_web_search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http_fetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"regex_extract"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxIterations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;// sub-agent has its own budget&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ExtractResearchSummary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Parent agent uses the sub-agent as just another tool&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://complex-business-questions"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"opus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                         &lt;span class="c1"&gt;// planner — top-tier model&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#senior-analyst@v1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"research_topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;// ← our sub-agent&lt;/span&gt;
               &lt;span class="s"&gt;"sql_query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                         &lt;span class="c1"&gt;// ← plain data tool&lt;/span&gt;
               &lt;span class="s"&gt;"math_eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                         &lt;span class="c1"&gt;// ← computation&lt;/span&gt;
               &lt;span class="s"&gt;"draft_report"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;// ← another sub-agent (report drafter)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxIterations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;// top-level budget&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://business-answers"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What actually happens under the hood: when Opus decides to call &lt;code&gt;research_topic&lt;/code&gt;, the agent engine builds the JSON input and routes it through &lt;code&gt;RouteToolBridge&lt;/code&gt; to &lt;code&gt;direct:research-subagent&lt;/code&gt;. That route runs as a &lt;strong&gt;fresh Exchange&lt;/strong&gt;, inheriting transaction scope, principal, headers, and DI scope from the parent. Inside it, Sonnet runs its own tool-use loop (web search → fetch → extract), potentially with eight iterations and three tools. It returns a structured summary in &lt;code&gt;Out.Body&lt;/code&gt;, which the engine repackages as a &lt;code&gt;tool_result&lt;/code&gt; and hands back to Opus. Opus sees the result, keeps planning, calls more tools as needed.&lt;/p&gt;

&lt;p&gt;In other words: &lt;strong&gt;the architecture is recursive without a single line of dedicated code&lt;/strong&gt;. A sub-agent inside a sub-agent works the same way. At depth three, audit, budget, idempotency, prompt versioning, RAG, governance — all of it still works, because none of those are tied to "agent level"; they're tied to the Exchange, and the Exchange is the same primitive at any depth.&lt;/p&gt;

&lt;p&gt;This unlocks &lt;strong&gt;three derivative patterns&lt;/strong&gt; that are awkward to assemble in the flat "one agent, many tools" shape:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(a) Hierarchical agents — planner and workers.&lt;/strong&gt; A top-tier model (Opus, GPT-4o) plays planner: decomposes the task and dispatches chunks to specialists. Each specialist is a sub-agent with a narrow prompt and a narrow toolset. The planner may have &lt;strong&gt;no direct data access at all&lt;/strong&gt; — only through sub-agents. That gives you &lt;strong&gt;hard separation of authority&lt;/strong&gt;: the planner can't accidentally call &lt;code&gt;delete_records&lt;/code&gt; because that tool isn't in its set; only &lt;code&gt;data-cleanup-subagent&lt;/code&gt; has it, and the planner has to hand off explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(b) Specialist sub-agents with their own memory.&lt;/strong&gt; A sub-agent can have its own &lt;code&gt;conversationId&lt;/code&gt; (a different branch of the REDB tree), its own &lt;code&gt;Knowledge(...)&lt;/code&gt; sources, its own prompt registry. A &lt;code&gt;legal-review-subagent&lt;/code&gt; lives inside the corporate legal corpus, sees &lt;strong&gt;only&lt;/strong&gt; that, answers strictly in the legal register. The senior agent never gets direct access to those documents. ACL is enforced at the route level, not at the "we hope the model doesn't quote it" level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(c) Cost-shaped escalation.&lt;/strong&gt; A cheap agent (Haiku) takes the first swing. If it returns &lt;code&gt;unclear&lt;/code&gt; or confidence is below threshold, &lt;strong&gt;it calls&lt;/strong&gt; &lt;code&gt;escalate_to_senior&lt;/code&gt; itself as a tool — and behind that tool sits a route to Opus with full context. Most queries land on Haiku for pennies; only the hard ones reach Opus. On high-volume workloads the economics change by an order of magnitude.&lt;/p&gt;

&lt;p&gt;How this relates to the static jury in pattern #9: &lt;strong&gt;the jury is a statically-wired sub-agent pattern&lt;/strong&gt;. The route knows up front there are N candidates and one arbiter; the DAG is hard-coded in the DSL. &lt;strong&gt;Sub-agents-as-tools is the dynamic version&lt;/strong&gt;: the parent agent &lt;strong&gt;decides for itself&lt;/strong&gt; whom to call and how often. Jury wins on predictable classification/forecasting pipelines. Sub-agents win on open-ended research where the number of steps and the toolset can't be known in advance.&lt;/p&gt;

&lt;p&gt;The obvious failure mode is cycles. If sub-agent A can call B and B can call A, you've got an unbounded recursion in theory. In practice three guards keep it bounded: &lt;code&gt;MaxIterations&lt;/code&gt; at every level (no sub-agent loops forever), &lt;code&gt;CostBudgetProps&lt;/code&gt; (each nested call burns parent budget), and an optional depth limit in headers (&lt;code&gt;LlmHeaders.SubAgentDepth&lt;/code&gt; increments per level; the route rejects calls past a configured limit). Real tasks rarely benefit beyond &lt;code&gt;depth = 3-4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And one more piece of architectural elegance: &lt;strong&gt;a sub-agent is just a route&lt;/strong&gt;, which means it has a stock URI. Different parent agents can share one &lt;code&gt;research-subagent&lt;/code&gt;, which has its own &lt;code&gt;?redb=acme&lt;/code&gt; for tenant isolation, its own rate limiter (&lt;code&gt;?throttle=5/sec&lt;/code&gt;), its own circuit breaker. The sub-agent behaves like an &lt;strong&gt;internal LLM-tier microservice&lt;/strong&gt; that you reuse across routes without duplicating prompts or proliferating clients.&lt;/p&gt;




&lt;h2&gt;
  
  
  Streaming: what actually changes when a token leaves the provider
&lt;/h2&gt;

&lt;p&gt;In 3.1.0, providers streamed but the &lt;strong&gt;client never saw it&lt;/strong&gt; — we accumulated tokens into a string and returned the whole thing. In 3.1.1, the chain closes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the provider yields &lt;code&gt;IAsyncEnumerable&amp;lt;string&amp;gt;&lt;/code&gt; — frames as they arrive;&lt;/li&gt;
&lt;li&gt;the agent engine sticks that into &lt;code&gt;Out.Body&lt;/code&gt; as &lt;code&gt;IAsyncEnumerable&amp;lt;string&amp;gt;&lt;/code&gt;, &lt;strong&gt;without materialising&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;the HTTP consumer sees &lt;code&gt;IAsyncEnumerable&amp;lt;string&amp;gt;&lt;/code&gt; and switches to SSE — one &lt;code&gt;data: ...\n\n&lt;/code&gt; per frame;&lt;/li&gt;
&lt;li&gt;the WS consumer sends one WebSocket message per frame;&lt;/li&gt;
&lt;li&gt;non-streaming consumers (Kafka, RabbitMQ, ActiveMQ) materialise the way they used to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architectural payoff: streaming stops being a separate &lt;strong&gt;mode&lt;/strong&gt; and becomes a &lt;strong&gt;payload type&lt;/strong&gt;. Same Exchange can fan out to SSE &lt;em&gt;and&lt;/em&gt; Kafka simultaneously (multicast EIP) — Kafka waits for materialisation, SSE sees frames live. No "streaming endpoint" vs "regular endpoint" duplication.&lt;/p&gt;

&lt;p&gt;Underneath, this is the well-known pipe pattern of "iterator instead of collection". The only LLM-specific bit is making sure async iteration &lt;strong&gt;runs inside the end-to-end Exchange tracking&lt;/strong&gt; so traces and metrics see the whole journey, not "received an Exchange and lost the rest".&lt;/p&gt;




&lt;h2&gt;
  
  
  RAG: chunks as first-class REDB objects
&lt;/h2&gt;

&lt;p&gt;In 3.1.1, &lt;code&gt;KnowledgeChunkProps&lt;/code&gt; is a REDB object with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the text;&lt;/li&gt;
&lt;li&gt;the source (&lt;code&gt;source-uri&lt;/code&gt;, &lt;code&gt;tenant-id&lt;/code&gt;, &lt;code&gt;doc-id&lt;/code&gt;, &lt;code&gt;chunk-index&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;metadata (language, date, tags, ACL — who's allowed to see it);&lt;/li&gt;
&lt;li&gt;a placeholder for embeddings (vector store ships in a follow-up; the MVP is keyword search via &lt;code&gt;value_string&lt;/code&gt; indexes plus FTS).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What this means: &lt;strong&gt;a RAG source is a route&lt;/strong&gt;, not a separate vector service. &lt;code&gt;From("file://docs?include=*.md").To("knowledge://acme")&lt;/code&gt; indexes documents. &lt;code&gt;From("kafka://support-tickets").Knowledge("acme", k: 5)&lt;/code&gt; injects the top-5 chunks into the system prompt before &lt;code&gt;.To("llm://claude")&lt;/code&gt;. ACL and tenant filtering happen as &lt;code&gt;value_*&lt;/code&gt;-indexed SQL &lt;strong&gt;before&lt;/strong&gt; chunks reach the prompt.&lt;/p&gt;

&lt;p&gt;When the vector store lands, it sits next to keyword search behind the same &lt;code&gt;IKnowledgeStore&lt;/code&gt; interface, no route changes. That's the &lt;strong&gt;architectural goal&lt;/strong&gt;: tomorrow's features don't break today's routes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three real-world enterprise scenarios — reports, forecasts, alerts
&lt;/h2&gt;

&lt;p&gt;The patterns above are atomic bricks. Now three end-to-end scenarios where those bricks combine into routes you'd actually run on Tuesday morning. No AI hype, no "cognitive automation", no pretending to "transform the industry". Just &lt;strong&gt;the dull stuff a human currently does by hand every day, taken over by an agent that lives in the same bus as the data&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 1 — A daily financial snapshot for the CFO, in their inbox by 7:00 AM
&lt;/h3&gt;

&lt;p&gt;Every business morning a finance analyst pulls together yesterday's revenue, expenses by category, plan-vs-actual variances, top-5 largest transactions, account balances, FX rates. Then they write a paragraph or two — "revenue +3.2% to plan, expenses +1.7%, the variance is X". An hour and a half, gone. Worth automating? Obvious yes. Worth standing up a separate AI service for it? Hard no.&lt;/p&gt;

&lt;p&gt;The whole route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every weekday at 07:00 local&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cron://daily-cfo-report?schedule=0 0 7 ? * MON-FRI"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"daily-cfo-report"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoadYesterdayMetrics&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                       &lt;span class="c1"&gt;// pulls from ERP/bank API/data warehouse&lt;/span&gt;
                                                            &lt;span class="c1"&gt;// → e.In.Body = {revenue, expenses, accounts, fx, top5}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoadQuarterContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                         &lt;span class="c1"&gt;// same ETL: plan, prior quarter, MTD/QTD&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConvertBody&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FinancialDailySnapshot&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                 &lt;span class="c1"&gt;// strongly-typed payload&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Multicast&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                           &lt;span class="c1"&gt;// branch A: short-form for the CFO&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#cfo-daily-summary@v7"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                           &lt;span class="c1"&gt;// branch B: bulletised for the board chat&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#cfo-daily-bullets@v7"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RenderHtmlReport&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                           &lt;span class="c1"&gt;// mustache template: numbers as a table,&lt;/span&gt;
                                                            &lt;span class="c1"&gt;// model summaries as the lede&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"smtp://mail.acme.com?to=cfo@acme.com,board@acme.com"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"&amp;amp;subject=Daily%20FY%20snapshot%20${date:yyyy-MM-dd}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"teams://board-channel?card=adaptive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;// same HTML body → adaptive card in Teams&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wiretap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://reports.cfo-daily.archive"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// copy to archive for audit and training&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven steps, and those seven steps cover the whole job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt; — &lt;code&gt;cron&lt;/code&gt; lives in the URI; no separate scheduler service. &lt;code&gt;redb.Route&lt;/code&gt; already does this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data pickup&lt;/strong&gt; — &lt;code&gt;LoadYesterdayMetrics&lt;/code&gt; is &lt;strong&gt;just a processor&lt;/strong&gt; that hits your existing integrations through the same &lt;code&gt;redb.Route&lt;/code&gt; (Kafka, REST, JDBC). Whatever retries and circuit breakers you already wired around those integrations apply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-tone output&lt;/strong&gt; — multicast to two branches of cheap Haiku with different prompt templates. Pennies, hard-budgeted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioned prompt&lt;/strong&gt; — &lt;code&gt;#cfo-daily-summary@v7&lt;/code&gt;. Six months from now the CFO complains "the report has gotten worse" — open &lt;code&gt;EvalRunProps&lt;/code&gt;, see v7 regressed against the golden set, roll back to v6 with no redeploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery&lt;/strong&gt; — two transports (SMTP + Teams), both stock &lt;code&gt;redb.Route&lt;/code&gt; connectors. Tomorrow they want it in Slack too: &lt;code&gt;.To("slack://...")&lt;/code&gt;, one line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt; — &lt;code&gt;Wiretap&lt;/code&gt; copies the whole exchange to a Kafka archive. Twelve months later a regulator asks "what did we send on 2026-04-15?" — pulled from the archive with metadata that says "model X, prompt version Y, source data Z" (because &lt;code&gt;MessageProps&lt;/code&gt; records both input and output).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget&lt;/strong&gt; — &lt;code&gt;.CostBudget(usd: 0.02)&lt;/code&gt; per branch. 250 trading days × 2 branches × $0.02 ≈ $10/year on token spend. The analyst's hour a day at fully-loaded cost is $50. Pays itself back in week one of month one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What pushes this from "cute demo" to "actual enterprise asset": when the CFO asks about "that strange number in last Tuesday's report", the auditor pulls one trace id and in &lt;strong&gt;30 seconds&lt;/strong&gt; has: the snapshot at input, the prompt version that ran, the exact output the model produced, the file that hit SMTP, who opened it in Teams. Not "let me get back to you tomorrow". Right now — because it's all REDB objects pinned to one exchange id.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 2 — A weekly cash-flow forecast with a jury and an Opus arbiter
&lt;/h3&gt;

&lt;p&gt;Cash-flow forecasting is the textbook case where &lt;strong&gt;one model is worse than zero models&lt;/strong&gt;: a confident-sounding model error leads to a decision that costs more than the analyst's salary for the month. Pattern #9 (jury + arbiter) earns its keep here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cron://weekly-cashflow-forecast?schedule=0 0 9 ? * MON"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"weekly-cashflow-forecast"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BuildCashflowFeatures&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                      &lt;span class="c1"&gt;// bank balances, AR/AP, scheduled payments,&lt;/span&gt;
                                                            &lt;span class="c1"&gt;// seasonality, FX exposure&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Knowledge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"acme-finance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;// RAG: forecasting playbook, prior reports,&lt;/span&gt;
                                                            &lt;span class="c1"&gt;// methodology, known seasonality notes&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Multicast&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sonnet"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                          &lt;span class="c1"&gt;// four different models — cheap insurance&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#cashflow-forecast@v4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;// against correlated errors&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-4o"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#cashflow-forecast@v4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gemini-pro"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#cashflow-forecast@v4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mistral-large"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#cashflow-forecast@v4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JuryAggregator&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                             &lt;span class="c1"&gt;// anonymise A/B/C/D + shuffle&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"opus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                &lt;span class="c1"&gt;// arbiter — strongest model in the rotation&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#cashflow-arbiter@v2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ExtractStructuredForecast&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                  &lt;span class="c1"&gt;// parse JSON: 30 days × {low, mid, high}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Forecast&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="n"&gt;ConfidenceLow&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApprovalGate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                       &lt;span class="c1"&gt;// low confidence → wait for CFO sign-off&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"smtp://...?to=cfo@acme.com&amp;amp;priority=high"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RenderForecastReport&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"smtp://...?to=treasury@acme.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wiretap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://reports.cashflow.archive"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route runs about $2-3 per execution, once a week — call it $150/year. The analyst would spend a day producing the same artefact. The savings aren't really the point. The point is &lt;strong&gt;robustness&lt;/strong&gt;: four independent forecasts from four different vendors (Anthropic, OpenAI, Google, Mistral) give you &lt;strong&gt;consensus as a confidence signal&lt;/strong&gt;. Four models agree → the arbiter just codifies. Four models diverge → the arbiter writes "model A sees risk X, the rest don't, recommend human review", and the &lt;code&gt;Choice&lt;/code&gt; branch routes the report to an approval gate without anyone wiring anything custom.&lt;/p&gt;

&lt;p&gt;A year in, the treasury team queries &lt;code&gt;EvalRunProps&lt;/code&gt;: "Gemini-Pro disagrees with consensus 18% of the time, and in 70% of those cases Gemini was right". Excellent — it gets &lt;strong&gt;upweighted&lt;/strong&gt; in the jury, not dropped. That's not a guess; it's a SQL query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 3 — A predictive plant alert: XGBoost does the math, the LLM does the sentence
&lt;/h3&gt;

&lt;p&gt;In manufacturing — the real kind, with conveyor belts and SCADA tags, not the metaphorical kind — the bread-and-butter task is "from the metrics, predict that this line will throw a fault in 4-6 hours, and ping the on-shift engineer &lt;strong&gt;before&lt;/strong&gt; it does". The classical play here is a feature store and a gradient-boosted model. That play is still correct, and the LLM is &lt;strong&gt;not&lt;/strong&gt; there to replace it. What the LLM does well is the &lt;strong&gt;last-mile translation&lt;/strong&gt; from "anomaly score + top features + line context" into a sentence a human reads at 3 AM and understands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Tap the SCADA bus (MQTT/Kafka), 5-minute aggregation window&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://scada.metrics?groupId=plant-anomaly-watcher"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"plant-anomaly-watcher"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"LineId"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
               &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;            &lt;span class="c1"&gt;// window per production line&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RunAnomalyModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;                            &lt;span class="c1"&gt;// classical ML — XGBoost, nothing fancy;&lt;/span&gt;
                                                            &lt;span class="c1"&gt;// emits {score, top-features, line-context}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AnomalyReport&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="n"&gt;Score&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Knowledge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"plant-runbooks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;// RAG: this line's runbooks,&lt;/span&gt;
                                                            &lt;span class="c1"&gt;// history of similar incidents&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SystemPromptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#plant-incident-explainer@v9"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"metrics-history"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"fetch-shift-log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// agent fetches more if it needs to&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxIterations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CostBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EnrichWithOnDutyEngineer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;           &lt;span class="c1"&gt;// who's on shift on this line, right now&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Multicast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"teams://engineering-shift-{LineId}?card=adaptive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sms://twilio?to={engineer.phone}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://incidents.predicted.archive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://scada.metrics.normal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// archive the boring days too&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's actually happening, and why this works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The ML model isn't getting kicked out.&lt;/strong&gt; Anomaly scoring is still XGBoost, which has been good at exactly this for a decade. The LLM stands &lt;strong&gt;after&lt;/strong&gt; it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The LLM explains, not predicts.&lt;/strong&gt; The system prompt is "anomaly on Line 7, top-3 features look like X, runbook says Y, prior incidents Z. In one page: what likely happened, what the engineer should do in the next hour, and the three parameters to check first". This is synthesis — the place LLMs are objectively strong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent's tools are the plant's data.&lt;/strong&gt; &lt;code&gt;metrics-history&lt;/code&gt; is a &lt;code&gt;direct:metrics-history&lt;/code&gt; route that queries Influx/Prometheus. &lt;code&gt;fetch-shift-log&lt;/code&gt; reads the shift log from REDB. The agent decides whether to invoke them, how many times. &lt;code&gt;MaxIterations(4)&lt;/code&gt; caps it from looping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-channel delivery.&lt;/strong&gt; Teams adaptive card with an "I've got it" button (which, by the way, fires an &lt;code&gt;ApprovalGate&lt;/code&gt;-style webhook), SMS to the on-shift number via Twilio (already a &lt;code&gt;redb.Route&lt;/code&gt; transport), copy to a Kafka archive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Aggregate by LineId, window 5min&lt;/code&gt;&lt;/strong&gt; is the EIP &lt;code&gt;Aggregator&lt;/code&gt; — bucket metrics by group. Nothing &lt;code&gt;redb.Route.Llm&lt;/code&gt;-specific; this is plain integration code that's been working in your bus for years.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The business outcome: time from "anomaly appears" to "the engineer on the line knows what to look for" drops from ~40 minutes (operator notices, calls, describes, engineer arrives, engineer starts hunting) to ~4-5 minutes. On expensive lines, an hour of unplanned downtime is tens of thousands of dollars; this route pays for itself the &lt;strong&gt;first&lt;/strong&gt; time a near-miss is caught.&lt;/p&gt;

&lt;p&gt;Notice the LLM's role is &lt;strong&gt;narrow&lt;/strong&gt; in all three cases. Not "AI runs the factory". Not "AI runs the books". The LLM does &lt;strong&gt;one specific job&lt;/strong&gt; in each route — summarise, synthesise, pick from candidates, explain. Around it sits all the boring enterprise plumbing: schedules, ETL, RAG, multi-channel delivery, audit, governance, budgets. That plumbing &lt;strong&gt;is&lt;/strong&gt; &lt;code&gt;redb.Route&lt;/code&gt;. The LLM is the last brick that previously demanded its own service.&lt;/p&gt;




&lt;h2&gt;
  
  
  Storytelling: how a chat demo becomes a platform
&lt;/h2&gt;

&lt;p&gt;Here's the timeline I've now watched several times in different teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 1.&lt;/strong&gt; Somebody shows the team that Claude can answer a support ticket. &lt;code&gt;LlmHttpRoutes.cs&lt;/code&gt; — six lines, &lt;code&gt;From("http://...").To("llm://haiku")&lt;/code&gt;, demo works. This is a &lt;strong&gt;project&lt;/strong&gt;, not a platform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http:0.0.0.0:5088/api/llm/ask?inOut=true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConvertBody&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxIterations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Day 7.&lt;/strong&gt; Coworkers say "it doesn't remember what I just told it". A &lt;code&gt;Process(...)&lt;/code&gt; step adds an &lt;code&gt;X-Chat-Id&lt;/code&gt; header, &lt;code&gt;ConversationFromHeader()&lt;/code&gt; is flipped on. That's it — &lt;code&gt;AddRedbLlmStorage()&lt;/code&gt; is already in &lt;code&gt;Program.cs&lt;/code&gt;, &lt;code&gt;MessageProps&lt;/code&gt; are already being written.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LlmHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConversationId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"X-Chat-Id"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Day 14.&lt;/strong&gt; "Can it run a command on the server?" A &lt;code&gt;tool-shell&lt;/code&gt; route appears — &lt;strong&gt;a separate route with &lt;code&gt;.AsLlmTool("shell")&lt;/code&gt;&lt;/strong&gt;, backed by &lt;code&gt;redb.Route.Exec&lt;/code&gt;, allowlist &lt;code&gt;[cmd, pwsh]&lt;/code&gt;, working dir in &lt;code&gt;temp&lt;/code&gt;, timeout 5 sec, 8 KB stdout cap. Safety lives &lt;strong&gt;in the DSL&lt;/strong&gt;, not in advice in a system prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct:tool-shell"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsLlmTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SideEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolSideEffect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadOnly&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolCostClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cheap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExecDsl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AllowedCommands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pwsh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;TimeoutMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxStdoutBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Day 21.&lt;/strong&gt; "Finance says the token bill is 4x plan." Add &lt;code&gt;.CostBudget(usd: 0.50)&lt;/code&gt; per conversation. No new services, no new dashboards — &lt;code&gt;CostBudgetProps&lt;/code&gt; is an existing REDB object, the tsak.web dashboard already knows how to render it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 30.&lt;/strong&gt; "We need every agent action to be auditable." Already done: &lt;code&gt;ToolAuditProps&lt;/code&gt; started writing the day tools became routes. The auditor opens a SQL query — every call this quarter, filtered by tenant and side effect. No "let's set up an integration".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 45.&lt;/strong&gt; "Legal wants human-in-loop on the refund tool." Add &lt;code&gt;.Process&amp;lt;ApprovalGate&amp;gt;()&lt;/code&gt; before exec. &lt;code&gt;ApprovalProps&lt;/code&gt; writes, the Slack bot is just another HTTP route. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 60.&lt;/strong&gt; "We're onboarding a second customer — separate database, separate billing." Add &lt;code&gt;Process(e =&amp;gt; e.In.Headers[LlmHeaders.Redb] = e.In.Headers["X-Tenant"])&lt;/code&gt;. One route, two tenants, zero changes to logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 90.&lt;/strong&gt; "Run a million tickets through the classifier overnight." The route is already there. Change one knob: &lt;code&gt;.Mode(LlmMode.Batch)&lt;/code&gt; — a synchronous call becomes an Anthropic Batch, &lt;code&gt;LlmBatchProps&lt;/code&gt; records the id, &lt;code&gt;LlmCallbackProcessor&lt;/code&gt; waits for the webhook, results land in the same Kafka the live route uses during the day. -50% on the bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 120.&lt;/strong&gt; "Which prompt versions regressed?" &lt;code&gt;EvalRunProps&lt;/code&gt; already stores runs. SQL across &lt;code&gt;PromptTemplateRef&lt;/code&gt;, accuracy diff, v4 is bad, roll back to v3. No redeploy.&lt;/p&gt;

&lt;p&gt;There's no "and then we rewrote it on a Kubernetes operator" beat in this story. There's no "and then we added an AI platform" beat. The &lt;strong&gt;six lines&lt;/strong&gt; that worked as a chat demo on Day 1 are &lt;strong&gt;literally the same file&lt;/strong&gt; that on Day 120 is running a multi-tenant, jury-arbitrated, batch-classifying, audit-grade platform. No migration, no rewrite, no architecture v2 — just lines accreting into the route as the requirements showed up. The chat demo doesn't &lt;em&gt;grow into&lt;/em&gt; a platform — &lt;strong&gt;it was standing on a platform from day one&lt;/strong&gt;, just opting into more of its features over time.&lt;/p&gt;

&lt;p&gt;This is the pitch I couldn't quite articulate at announcement time. Now I can. The deliverable here isn't an agent framework. It's &lt;strong&gt;a runway&lt;/strong&gt;. You walk along the runway and you're already platform-shaped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo routes — see it live
&lt;/h2&gt;

&lt;p&gt;The patterns above aren't concept art. The repo &lt;code&gt;redbase-app/redb-route&lt;/code&gt; ships two demo files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LlmDemoRoutes.cs&lt;/code&gt;&lt;/strong&gt; — three shapes of the LLM call: inline step (&lt;code&gt;.Llm("demo-stub")&lt;/code&gt;), endpoint (&lt;code&gt;.To("llm://demo-stub")&lt;/code&gt;), tool (&lt;code&gt;.AsLlmTool("echo_tool")&lt;/code&gt;). All on the stub provider, no API keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LlmHttpRoutes.cs&lt;/code&gt;&lt;/strong&gt; — two HTTP endpoints (&lt;code&gt;/api/llm/ask&lt;/code&gt; no tools, &lt;code&gt;/api/llm/shell&lt;/code&gt; with the shell tool through &lt;code&gt;redb.Route.Exec&lt;/code&gt;), both passing &lt;code&gt;X-Chat-Id&lt;/code&gt; for conversation memory, both running real Claude Haiku.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet run&lt;/code&gt; from &lt;code&gt;redb.Route.Demo&lt;/code&gt;, port 5088 opens, and curl works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"what time is it on this host?"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Chat-Id: test1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     http://localhost:5088/api/llm/shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt; (Apache 2.0).&lt;/p&gt;




&lt;h2&gt;
  
  
  What's not done, and why I keep saying so
&lt;/h2&gt;

&lt;p&gt;Sliding-window memory isn't done. A real vector store isn't done. There's no AI-graph editor in tsak.web — runtime conversation inspection shows up as plain REDB objects in the existing UI, no special pane. There's no built-in eval-service integration — &lt;code&gt;EvalRunStore&lt;/code&gt; records the runs, but "click compare against prod" is manual.&lt;/p&gt;

&lt;p&gt;That's fine. The skip-list is a &lt;strong&gt;technique&lt;/strong&gt;, not an apology. An open-source project that lies in its README about scope is one you don't come back to. I'd rather say "not yet" and ship it next minor than say "shipped" and field issues for two months.&lt;/p&gt;




&lt;h2&gt;
  
  
  Zooming out: why the ESB shape matters
&lt;/h2&gt;

&lt;p&gt;The phrase "AI-native architecture" is fashionable. What it usually means is "we built everything around the LLM" — and what usually backs that up is &lt;strong&gt;a new dev stack running parallel to the old one&lt;/strong&gt;. That's not an architecture decision. That's a &lt;strong&gt;policy duplication problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you have retry in two stacks, audit in two stacks, governance in two stacks, tenant isolation in two stacks — those two implementations &lt;strong&gt;drift&lt;/strong&gt;. A year later, the AI stack adds bounded-context auditing, the integration stack doesn't. Legal asks for one report — nobody can produce it. The duplication wasn't visible on day one; it's load-bearing on day three hundred.&lt;/p&gt;

&lt;p&gt;The ESB shape is a deliberate choice of &lt;strong&gt;one control point for I/O of all kinds&lt;/strong&gt;. The LLM is a kind of I/O — asynchronous, with tools, with context, but I/O. Putting it inside the ESB isn't a philosophical pose; it's engineering economy: &lt;strong&gt;one governance policy covers everything&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not new. Garland and Ripley wrote it up in the SOA literature of the 2000s. What's new is that in 2026 the argument finally &lt;strong&gt;applies to LLMs&lt;/strong&gt;, because LLMs grew up enough to expose &lt;strong&gt;standard interfaces&lt;/strong&gt;: tool use, streaming, batch APIs, embeddings. Before that, "LLM in the ESB" meant "wrap a REST call and pray". Now it means "use existing EIP patterns with minor adaptations".&lt;/p&gt;

&lt;p&gt;That economy is the entire pitch of &lt;code&gt;redb.Route.Llm&lt;/code&gt;. Not "we have the best agent engine." Microsoft and LangChain have better agent engines. We have &lt;strong&gt;a perfectly average agent engine in the right place in the architecture&lt;/strong&gt;. I'll take that trade.&lt;/p&gt;




&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;3.1.2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sliding-window memory as a built-in policy;&lt;/li&gt;
&lt;li&gt;a vector-store interface behind &lt;code&gt;IKnowledgeStore&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;pgvector and Qdrant as the first back-ends;&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;EvalCompare&lt;/code&gt; DSL for side-by-side prompt-version runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3.2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;ConversationProps&lt;/code&gt; tree UI in tsak.web;&lt;/li&gt;
&lt;li&gt;streaming-aware aggregator EIP (buffer partial frames to semantic boundaries);&lt;/li&gt;
&lt;li&gt;distributed batch — multiple workers behind one &lt;code&gt;LlmCallbackProcessor&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-modal (image input/output as a payload type);&lt;/li&gt;
&lt;li&gt;voice agents as another transport (&lt;code&gt;voice://...&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;routing by cost / latency / accuracy SLA per message.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1 (3.1.0 announcement): &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/"&gt;redb.Route 3.1.0 — LLM as just another connector&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0"&gt;redb.Route — Apache Camel for .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/enterprise-integration-patterns-in-net-the-deep-dive-series-part-1-the-four-in-memory-channels-3e24"&gt;EIP series part 1 — channels and Exchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-301-flat-dsl-navigation-crtp-refactor-and-a-silent-null-fix-3m7n"&gt;redb.Route 3.0.1 patch notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt; (Apache 2.0)&lt;/li&gt;
&lt;li&gt;Demo routes: &lt;code&gt;redb.Route/demos/redb.Route.Demo/Routes/LlmDemoRoutes.cs&lt;/code&gt;, &lt;code&gt;LlmHttpRoutes.cs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Habr companion (Russian, not a translation): link added after publication&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; Three weeks ago I shipped 3.1.0 with an honest skip-list. Three weeks later, most of it is shipped: streaming end-to-end, tool cache, RAG knowledge store, async batch + callback consumer, eval-run store, versioned prompt registry, multi-tenant &lt;code&gt;?redb=&amp;lt;name&amp;gt;&lt;/code&gt;, idempotent tool retries, human-in-loop approval gates, full audit. On top, two patterns that ship the architecture's real punchline: &lt;strong&gt;a jury of cheap models with a senior model as arbiter&lt;/strong&gt;, dropping straight onto stock Scatter-Gather + Aggregator EIP — no custom orchestrator; and &lt;strong&gt;sub-agents as tools&lt;/strong&gt; — recursive agent composition where one agent's tool is itself an agent with its own model, prompt, budget, RAG corpus, all working out of the box because &lt;code&gt;RouteToolBridge → direct: → llm://&lt;/code&gt; is a fixed point. Plus three honest enterprise scenarios with code: a daily CFO snapshot to inbox, a weekly cash-flow forecast with jury arbitration, and a predictive plant alert that hands XGBoost the prediction and the LLM the sentence. The point isn't the feature checklist. The point is that &lt;strong&gt;enterprise-grade properties land on the day you actually need them, with no rewrite&lt;/strong&gt;, because the LLM lives inside the ESB along with every other I/O. Those six lines you typed for the Day-1 chat demo are &lt;strong&gt;literally the same file&lt;/strong&gt; that's running the Day-90 multi-tenant audit-grade platform. There's no "now we rewrite" moment in this story.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>redb.Route 3.1.0 — LLM(AI) as just another connector: `.To("llm://claude")` and tools-as-routes</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Tue, 09 Jun 2026 14:19:01 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/redbroute-310-llmai-as-just-another-connector-tollmclaude-and-tools-as-routes-4fcg</link>
      <guid>https://dev.to/rinat_kozin/redbroute-310-llmai-as-just-another-connector-tollmclaude-and-tools-as-routes-4fcg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznyh7pojjnuxambgkei6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznyh7pojjnuxambgkei6.webp" alt="redb connect llm" width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Series:&lt;/strong&gt; redb ecosystem (announcement; deep-dive separate)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;redb.Route&lt;/code&gt; 3.1.0 just shipped &lt;strong&gt;two new transports&lt;/strong&gt;: &lt;code&gt;redb.Route.Llm&lt;/code&gt; (the 24th) and &lt;code&gt;redb.Route.Exec&lt;/code&gt; (the 25th). The LLM is now an addressable endpoint the same way Kafka, RabbitMQ and HTTP are: calling a model is &lt;code&gt;.To("llm://claude")&lt;/code&gt;, an agent tool is a route with &lt;code&gt;.AsLlmTool("shell")&lt;/code&gt; on it, a scheduled agent is &lt;code&gt;From("llm://factory?schedule=5m")&lt;/code&gt;. Exec is a process spawner with allowlist, working-directory and timeout — both the backend that powers shell tools for agents and a first-class scheduled consumer in its own right (cron-less health probes, backups, deploy glue). No parallel "AI framework" sitting next to the integration framework — same DSL, same retry/throttle/circuit-breaker/audit, same OpenTelemetry traces, same dashboard endpoint counters.&lt;/p&gt;

&lt;p&gt;This post is the announcement. A deep-dive on the agent loop, the governance hooks and the REDB storage internals will follow as a separate piece. Here: what landed, what it looks like in code, and what's honestly not done yet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;First time reading about &lt;code&gt;redb.Route&lt;/code&gt;?&lt;/strong&gt; Short context from earlier posts in the series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0"&gt;redb.Route — Apache Camel for .NET: 22 transports, 30+ EIP patterns, compiled DSL&lt;/a&gt; — why it exists, and what "Apache Camel for .NET" actually means&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbase-redbroute-redbtsak-300-shipped-27pf"&gt;RedBase / redb.Route / redb.Tsak 3.0.0 shipped&lt;/a&gt; — the 3.0.0 release that this LLM connector slots into&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/enterprise-integration-patterns-in-net-the-deep-dive-series-part-1-the-four-in-memory-channels-3e24"&gt;Enterprise Integration Patterns in .NET — Part 1: the four in-memory channels (and the Exchange they carry)&lt;/a&gt; — how the runtime is built, if you want internals&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-301-flat-dsl-navigation-crtp-refactor-and-a-silent-null-fix-3m7n"&gt;redb.Route 3.0.1 — flat DSL navigation, CRTP refactor, and a silent null fix&lt;/a&gt; — the previous patch right before 3.1.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  The shortest possible explanation
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"claude"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders.translated"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One line, one full LLM call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the inbound message body becomes the user prompt;&lt;/li&gt;
&lt;li&gt;the agent runs (provider call → optional &lt;code&gt;tool_use&lt;/code&gt; → next provider call → …) until &lt;code&gt;EndTurn&lt;/code&gt; or &lt;code&gt;MaxIterations&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the assistant text lands in &lt;code&gt;exchange.Out.Body&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;token usage, model id, stop reason, iteration count are pushed into headers;&lt;/li&gt;
&lt;li&gt;OpenTelemetry traces and metrics light up automatically;&lt;/li&gt;
&lt;li&gt;the endpoint shows up in the tsak.web dashboard with msg/sec, average duration, error rate and last-error info — like every other connector.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole reason "connector, not library" matters. If you already have an Apache Camel-class ESB with retry, breaker, idempotent consumer and audit, the only honest move is to turn the LLM into one more endpoint inside it. No re-implementing retries. No separate dashboard. No "AI infra" running parallel to the integration infra.&lt;/p&gt;


&lt;h2&gt;
  
  
  One adapter, 14 OpenAI-compatible APIs
&lt;/h2&gt;

&lt;p&gt;Two production providers ship today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;OpenAiProvider&lt;/code&gt;&lt;/strong&gt; — one universal transport for &lt;strong&gt;14 OpenAI-compatible APIs&lt;/strong&gt;: &lt;code&gt;openai&lt;/code&gt;, &lt;code&gt;anthropic&lt;/code&gt;/&lt;code&gt;claude&lt;/code&gt; (via Anthropic's official OpenAI-compat endpoint), &lt;code&gt;groq&lt;/code&gt;, &lt;code&gt;cerebras&lt;/code&gt;, &lt;code&gt;openrouter&lt;/code&gt;, &lt;code&gt;gemini&lt;/code&gt; (OpenAI-compat surface), &lt;code&gt;github-models&lt;/code&gt;, &lt;code&gt;mistral&lt;/code&gt;, &lt;code&gt;together&lt;/code&gt;, &lt;code&gt;huggingface&lt;/code&gt;, &lt;code&gt;deepseek&lt;/code&gt;, &lt;code&gt;ollama&lt;/code&gt;, &lt;code&gt;lmstudio&lt;/code&gt;, plus a generic &lt;code&gt;custom&lt;/code&gt; for any self-hosted gateway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;StubProvider&lt;/code&gt;&lt;/strong&gt; — deterministic echo for unit tests and CI runs with no keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A native &lt;code&gt;AnthropicProvider&lt;/code&gt; (Messages API) is on deck for features the OpenAI-compat surface doesn't expose.&lt;/p&gt;

&lt;p&gt;Switching provider is one DI registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddLlmConnectionFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"groq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"groq"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelId&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llama-3.3-70b-versatile"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiKey&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"REDB_LLM_GROQ_KEY"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change &lt;code&gt;provider&lt;/code&gt; to &lt;code&gt;"anthropic"&lt;/code&gt; and &lt;code&gt;modelId&lt;/code&gt; to &lt;code&gt;"claude-haiku-4-5"&lt;/code&gt; and the same &lt;code&gt;.To("llm://...")&lt;/code&gt; is calling Claude. The DSL surface doesn't move a character.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools are routes
&lt;/h2&gt;

&lt;p&gt;The central architectural call: &lt;strong&gt;a tool is an ordinary &lt;code&gt;RouteBuilder&lt;/code&gt; route with one extra DSL aspect, &lt;code&gt;.AsLlmTool("name")&lt;/code&gt;&lt;/strong&gt;. Four properties fall out of that and would otherwise have to be paid for separately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct:tool-shell"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsLlmTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Run a small shell command on the host. Input: {\"command\":\"&amp;lt;name&amp;gt;\",\"args\":[\"...\"]}."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
                &lt;span class="s"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="s"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="s"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;""")
&lt;/span&gt;        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SideEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolSideEffect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolCostClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cheap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExecDsl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowedCommands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pwsh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scratchDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TimeoutMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxStdoutBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8_192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxStderrBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8_192&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you get for free:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tools-as-routes.&lt;/strong&gt; The 30+ EIP processors are usable &lt;em&gt;inside&lt;/em&gt; a tool — &lt;code&gt;Splitter&lt;/code&gt;, &lt;code&gt;Aggregator&lt;/code&gt;, &lt;code&gt;CircuitBreaker&lt;/code&gt;, &lt;code&gt;Throttle&lt;/code&gt;, &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;TryCatch&lt;/code&gt;. A tool that talks to a fragile API gets wrapped in a breaker and a throttle without a line of runtime glue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools from existing routes.&lt;/strong&gt; Any &lt;code&gt;From("http://...")&lt;/code&gt; or &lt;code&gt;From("sql://...")&lt;/code&gt; you already have becomes a tool by adding &lt;code&gt;.AsLlmTool("name")&lt;/code&gt;. Its auth, its audit trail, its metrics are already in place — because it's already a route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory-as-data.&lt;/strong&gt; &lt;code&gt;IToolDescriptorRegistry&lt;/code&gt; knows every tool by name with its JSON schema. Filter with &lt;code&gt;?tools=*&lt;/code&gt; or &lt;code&gt;?tools=lookup,shell&lt;/code&gt; on the URI; build "an agent that has every read-only tool" in one line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero bumps on sibling connectors.&lt;/strong&gt; The &lt;code&gt;AsLlmTool()&lt;/code&gt; aspect lives in &lt;code&gt;redb.Route.Llm.Abstractions&lt;/code&gt;. The other 22 connectors can be used as tools today without a NuGet bump on any of them.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  A real example — a standalone "curl → Claude → shell → reply" demo
&lt;/h2&gt;

&lt;p&gt;This is a complete program. Two files — &lt;code&gt;Llm.HttpShell.csproj&lt;/code&gt; and &lt;code&gt;Program.cs&lt;/code&gt;, top-level statements, no host abstractions on top. Drop your Anthropic key into one place and &lt;code&gt;dotnet run&lt;/code&gt; brings up an HTTP endpoint on port 5088 where Claude Haiku can call a shell on the host and answer in the context of the previous turns.&lt;/p&gt;

&lt;h3&gt;
  
  
  csproj — five NuGet packages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net9.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"redb.Route"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"redb.Route.Http"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"redb.Route.Llm"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"redb.Route.Llm.Abstractions"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"redb.Route.Exec"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.Extensions.Logging.Console"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;redb.Route&lt;/code&gt; is the ESB core, &lt;code&gt;redb.Route.Http&lt;/code&gt; is the HTTP transport, &lt;code&gt;redb.Route.Llm[.Abstractions]&lt;/code&gt; is the LLM connector and the &lt;code&gt;.AsLlmTool()&lt;/code&gt; DSL aspect, &lt;code&gt;redb.Route.Exec&lt;/code&gt; is the process spawner with allowlist and timeout. No &lt;code&gt;redb.Core&lt;/code&gt;/&lt;code&gt;redb.Postgres&lt;/code&gt; — the demo is self-contained and keeps conversation memory in process.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ApiKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your-key&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one you get from &lt;code&gt;https://console.anthropic.com/&lt;/code&gt;. Switching to Groq is &lt;code&gt;Provider = "groq"&lt;/code&gt;, &lt;code&gt;ModelId = "llama-3.3-70b-versatile"&lt;/code&gt; and a key from &lt;code&gt;console.groq.com&lt;/code&gt;; the DSL below doesn't move.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. DI and &lt;code&gt;RouteContext&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;RouteContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddLogging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSimpleConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SingleLine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimestampFormat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"HH:mm:ss "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMinimumLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRouteContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ILoggerFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;CreateLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redb.Route"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RouteContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"llm-http-shell"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILoggerFactory&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ILoggerFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RouteContext&lt;/code&gt; is the runtime container — routes, components, services. The &lt;code&gt;_ =&amp;gt; ctx&lt;/code&gt; closure registers &lt;code&gt;IRouteContext&lt;/code&gt; as a DI service before the context itself is constructed (the context in turn needs the &lt;code&gt;IServiceProvider&lt;/code&gt;). The last line hands &lt;code&gt;ILoggerFactory&lt;/code&gt; to the context explicitly; without it &lt;code&gt;.Log(...)&lt;/code&gt; steps in routes silently turn into no-ops (internals belong to the deep-dive piece).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11ifyanthop79fx2a0k6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11ifyanthop79fx2a0k6.png" alt="redb llm connector example" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Three components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ServerManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SharedHttpServerManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LlmComponent&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExecComponent&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A component in redb.Route is a transport plugin owning a URI scheme: &lt;code&gt;http://&lt;/code&gt;, &lt;code&gt;llm://&lt;/code&gt;, &lt;code&gt;exec://&lt;/code&gt;. Pure registration, no business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Connection factory for Claude Haiku
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddToRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LlmConnectionFactory&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Provider&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"anthropic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ModelId&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude-haiku-4-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ApiKey&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ApiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxTokens&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;"haiku"&lt;/code&gt; is the label the route will reference as &lt;code&gt;Llm.Factory("haiku")&lt;/code&gt;. Same trick redb.Route uses framework-wide for named connection factories.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Agent engine + tool registry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolRegistry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ToolDescriptorRegistry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IToolDescriptorRegistry&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;toolRegistry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;producerTemplate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProducerTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IProducerTemplate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;producerTemplate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;           &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;producerTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;producerTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NoopAgentObserver&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;           &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NoopBudgetEnforcer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AutoApproveGate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;redaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NoopRedactionFilter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;           &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NoopShadowRunner&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InMemoryConversationStore&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;idempotency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;approvalStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAgentEngine&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every agent state surface — observability, budget, approval, redaction, shadow, conversation, idempotency — lives behind an interface. The demo wires Noop/InMemory implementations everywhere: the loop runs but nothing is persisted. Production swap is &lt;code&gt;AddRedbLlmStorage()&lt;/code&gt; (covered below): replaces &lt;code&gt;InMemoryConversationStore&lt;/code&gt; with &lt;code&gt;RedbConversationStore&lt;/code&gt;, attaches &lt;code&gt;RedbAuditObserver&lt;/code&gt;, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  6a. The HTTP route
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;isWindows&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OperatingSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsWindows&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isWindows&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pwsh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"powershell"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bash"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scratchDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTempPath&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"redb-llm-shell"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scratchDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="s"&gt;"You can run small shell commands through the 'shell' tool. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;$"The host is &lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="n"&gt;isWindows&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"Windows (use cmd /c)"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Linux (use sh -c)"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"Use the tool when the user asks about the system, files, or commands; "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"then summarise what you learned in one short sentence."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http:0.0.0.0:5088/api/llm/shell?inOut=true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llm-http-shell"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConvertBody&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LlmHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemPrompt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LlmHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConversationId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Chat-Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
                    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[LLM-SHELL] ▶ chat=${header.llm.conversation.id} prompt=${body}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LlmDsl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"haiku"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConversationFromHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxIterations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsUri&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[LLM-SHELL] ◀ iters=${header.llm.tool.iterations} stop=${header.llm.stop_reason} "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
             &lt;span class="s"&gt;"tokensIn=${header.llm.tokens.in} tokensOut=${header.llm.tokens.out}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[LLM-SHELL] ◀ reply=${body}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The POST body is converted to a string and becomes the user prompt. &lt;code&gt;Process&lt;/code&gt; puts the system prompt into the standard &lt;code&gt;LlmHeaders.SystemPrompt&lt;/code&gt; header and resolves the conversation id from the client's &lt;code&gt;X-Chat-Id&lt;/code&gt; (without it — &lt;code&gt;default&lt;/code&gt;). &lt;code&gt;.Tools("shell")&lt;/code&gt; is the only LLM-specific knob: "give the agent the tool registered under this name." &lt;code&gt;MaxIterations(10)&lt;/code&gt; caps the tool loop (otherwise it's an unbounded ping between model and tool).&lt;/p&gt;

&lt;h3&gt;
  
  
  6b. The tool itself is a route
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct:tool-shell"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsLlmTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Run a small shell command on the host. Input: "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"{\"command\":\"&amp;lt;name&amp;gt;\",\"args\":[\"...\"]}. Output: "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"{\"stdout\":\"...\",\"stderr\":\"...\",\"exitCode\":N}. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;$"Allowed commands: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;$"Working directory is pinned to '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;scratchDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""
&lt;/span&gt;                &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="s"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="p"&gt;},&lt;/span&gt;
                  &lt;span class="s"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="s"&gt;""")
&lt;/span&gt;            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SideEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolSideEffect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolCostClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cheap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[SHELL-TOOL] ▶ in=${body}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExecDsl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowedCommands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scratchDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TimeoutMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxStdoutBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8_192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxStderrBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8_192&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[SHELL-TOOL] ◀ exit=${header.redbExec.ExitCode} body=${body}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Description&lt;/code&gt; and the JSON schema are what Claude sees as "this function is available, here's its signature." &lt;code&gt;SideEffect.ReadOnly&lt;/code&gt; and &lt;code&gt;Cost.Cheap&lt;/code&gt; are guidance for the governance hooks (budgets, approval gating). After &lt;code&gt;.Then()&lt;/code&gt; it's an ordinary route: &lt;code&gt;Log&lt;/code&gt; → &lt;code&gt;ExecDsl.Run()&lt;/code&gt; with allowlist, working dir, 5-second timeout and a stdout/stderr cap → &lt;code&gt;Log&lt;/code&gt;. No LLM-specific code below &lt;code&gt;.Then()&lt;/code&gt;. It's still just a route — which means you can wrap it in a &lt;code&gt;CircuitBreaker&lt;/code&gt;, a &lt;code&gt;Throttle&lt;/code&gt;, a &lt;code&gt;Transaction&lt;/code&gt;, a &lt;code&gt;WireTap&lt;/code&gt; shadow, anything from the 30+ EIP processors redb.Route already ships.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3l05zy0dqfcphekj9tqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3l05zy0dqfcphekj9tqz.png" alt="redb llm connector example" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1nvjvv47pa2bb4wlktc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1nvjvv47pa2bb4wlktc.png" alt="redb llm connector example" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Start and block
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;producerTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ManualResetEventSlim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CancelKeyPress&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cancel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run

curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"how much free disk space?"&lt;/span&gt; http://localhost:5088/api/llm/shell
curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"what did you just say?"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Chat-Id: my-chat"&lt;/span&gt; http://localhost:5088/api/llm/shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First request: Claude sees the system prompt ("you have a &lt;code&gt;shell&lt;/code&gt; tool"), decides to check free space, calls the tool with something like &lt;code&gt;{"command":"cmd","args":["/c","fsutil","volume","diskfree","C:"]}&lt;/code&gt;, gets the stdout back, summarises it for the human. Second request, with &lt;code&gt;X-Chat-Id: my-chat&lt;/code&gt;, finds the previous turn in &lt;code&gt;InMemoryConversationStore&lt;/code&gt; and answers in context.&lt;/p&gt;

&lt;p&gt;The logs at that moment show the full path: &lt;code&gt;[LLM-SHELL] ▶ prompt=...&lt;/code&gt; → &lt;code&gt;[SHELL-TOOL] ▶ in={"command":"cmd",...}&lt;/code&gt; → &lt;code&gt;[SHELL-TOOL] ◀ exit=0 body={"stdout":"...","exitCode":0}&lt;/code&gt; → &lt;code&gt;[LLM-SHELL] ◀ iters=2 stop=end_turn tokensIn=... tokensOut=... reply=...&lt;/code&gt;. All of it is just the redb.Route &lt;code&gt;.Log()&lt;/code&gt; step, not a separate LLM-tracing pipeline.&lt;/p&gt;

&lt;p&gt;The command allowlist (&lt;code&gt;cmd&lt;/code&gt;, &lt;code&gt;pwsh&lt;/code&gt;, &lt;code&gt;sh&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;) is the security envelope: anything outside it is rejected by &lt;code&gt;redb.Route.Exec&lt;/code&gt; before a process starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;redb.Route.Exec&lt;/code&gt; — process spawning as the 25th transport
&lt;/h2&gt;

&lt;p&gt;In the demo above &lt;code&gt;ExecDsl.Run()&lt;/code&gt; showed up as "the backend of the &lt;code&gt;shell&lt;/code&gt; tool", but it's a connector in its own right, shipped in 3.1.0 alongside &lt;code&gt;redb.Route.Llm&lt;/code&gt;. It closes a boring but ubiquitous gap: the framework already spoke 22+ transports (HTTP, Kafka, SQL, …), but reaching out to the operating system itself was missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Producer — &lt;code&gt;.To(ExecDsl.Run(...))&lt;/code&gt;&lt;/strong&gt; — synchronous spawn. The command is resolved in this order: JSON body → headers &lt;code&gt;redbExec.Command&lt;/code&gt;/&lt;code&gt;redbExec.Args&lt;/code&gt; → URI options &lt;code&gt;?command=...&lt;/code&gt;. The Out body is JSON shaped for the LLM tool ABI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"stdout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"stderr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"exitCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"timedOut"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly why the shell tool in the demo is &lt;code&gt;.To(ExecDsl.Run().AllowedCommands(...))&lt;/code&gt; with zero glue code in between. The model produces &lt;code&gt;{"command":"cmd","args":[...]}&lt;/code&gt;, the producer parses it, runs it, returns structured JSON. Route-level features (&lt;code&gt;CircuitBreaker&lt;/code&gt;, &lt;code&gt;Throttle&lt;/code&gt;, &lt;code&gt;OnException&lt;/code&gt;, audit) apply automatically — it's an endpoint like every other one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer — &lt;code&gt;From(ExecDsl.Run(...).Schedule("5m"))&lt;/code&gt;&lt;/strong&gt; — same spawner, but as a first-class source endpoint with a built-in scheduler. No cron, no separate worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExecDsl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./scripts/health-check.sh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5m"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TimeoutMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"redbExec.ExitCode"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://alerts.internal/oncall"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://metrics.healthy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every 5 minutes the script runs, the route branches on exit code — one way to an HTTP webhook for the on-call, the other to a Kafka topic. &lt;code&gt;?schedule=&lt;/code&gt; accepts simple intervals (&lt;code&gt;500ms&lt;/code&gt;, &lt;code&gt;30s&lt;/code&gt;, &lt;code&gt;5m&lt;/code&gt;, &lt;code&gt;1h&lt;/code&gt;). For cron expressions: &lt;code&gt;From("quartz://&amp;lt;cron&amp;gt;").To(ExecDsl.Run(...))&lt;/code&gt; — Quartz is already a scheduler, no point re-implementing it inside Exec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The security envelope&lt;/strong&gt; — what gives shell-as-a-tool the right to exist in the first place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AllowedCommands&lt;/code&gt; — case-insensitive on the file name, so &lt;code&gt;/usr/bin/git&lt;/code&gt; and &lt;code&gt;git.exe&lt;/code&gt; both match &lt;code&gt;git&lt;/code&gt;. Anything off the list is rejected with &lt;code&gt;UnauthorizedAccessException&lt;/code&gt; before the process starts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WorkingDirectory&lt;/code&gt; — pins the cwd; the spawned process can't escape it on its own.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EnvironmentOverrides&lt;/code&gt; + &lt;code&gt;ScrubEnvironment&lt;/code&gt; — start from an empty environment and apply only the &lt;code&gt;KEY=VALUE&lt;/code&gt; pairs you set.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TimeoutMs&lt;/code&gt; — wall-clock kill-switch that takes the whole process tree.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MaxStdoutBytes&lt;/code&gt; / &lt;code&gt;MaxStderrBytes&lt;/code&gt; — caps that protect the host from a runaway process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why a separate package, not part of &lt;code&gt;redb.Route.Llm&lt;/code&gt;&lt;/strong&gt; — three reasons: (1) Exec is useful with no LLM in sight (scheduled health probes, log rotation, deploy glue, backups); (2) &lt;code&gt;redb.Route.Llm&lt;/code&gt; shouldn't drag a dependency on process spawning into projects whose agents only call HTTP or SQL tools; (3) the same allowlist/timeout/cap mechanism will be reused by future connectors that share the same security profile.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;From("llm://...")&lt;/code&gt; — the scheduled agent consumer
&lt;/h2&gt;

&lt;p&gt;This is the bit that has no equivalent in Camel's &lt;code&gt;langchain4j-*&lt;/code&gt; family (where LLM is producer-only): &lt;strong&gt;the LLM endpoint is a first-class source&lt;/strong&gt;, with the scheduler baked in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llm://groq?schedule=5m"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
     &lt;span class="s"&gt;"&amp;amp;systemPromptRef=#watchdog-system"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
     &lt;span class="s"&gt;"&amp;amp;initialBodyRef=#daily-brief"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
     &lt;span class="s"&gt;"&amp;amp;tools=*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://alerts"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every 5 minutes: a fresh agent run with the system prompt resolved from registry key &lt;code&gt;#watchdog-system&lt;/code&gt; and the user prompt from &lt;code&gt;#daily-brief&lt;/code&gt;. The reply goes to RabbitMQ. &lt;code&gt;?schedule=&lt;/code&gt; takes simple intervals (&lt;code&gt;500ms&lt;/code&gt;, &lt;code&gt;30s&lt;/code&gt;, &lt;code&gt;5m&lt;/code&gt;, &lt;code&gt;1h&lt;/code&gt;); for cron use &lt;code&gt;From("quartz://...").To("llm://...")&lt;/code&gt; — Quartz is already a scheduler, no point duplicating it inside the LLM consumer.&lt;/p&gt;

&lt;p&gt;Use cases that fit this shape: watchdog agents, scheduled report generation, self-improving agents with conversation memory (same history across runs).&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;#&lt;/code&gt;-prompts — the dynamic registry
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;#&lt;/code&gt; prefix on a URI parameter turns the value into a registry lookup. Some other route can rewrite the prompt by name; the next agent run picks it up without a redeploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddToRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"style.terse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Reply in fewer than 5 words."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct:chat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llm://scripted?systemPromptRef=#style.terse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mock:done"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ... later, from another route ...&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddToRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"style.terse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Reply in French only."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// the next call sees the new value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolution: first &lt;code&gt;IPromptTemplateRegistry&lt;/code&gt; (versioned, what eval replay needs), then the generic context registry (a plain string). Without &lt;code&gt;#&lt;/code&gt; the value is literal and arrives at the provider unchanged.&lt;/p&gt;

&lt;p&gt;This is the same &lt;code&gt;#name&lt;/code&gt; registry convention redb.Route uses framework-wide for connection factories. Same convention, not a separate prompt-ref DSL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent memory — &lt;code&gt;AddRedbLlmStorage()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;By default every state surface is in-memory (conversation transcripts, tool idempotency, approvals, cost ledgers, audit). That keeps &lt;code&gt;AddRedbRouteLlm()&lt;/code&gt; zero-dependency — fine for tests and stateless agents, lost on restart.&lt;/p&gt;

&lt;p&gt;One line swaps the defaults for REDB-backed stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRouteLlm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbIdempotentRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// required for idempotency&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbLlmStorage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;             &lt;span class="c1"&gt;// ← REDB stores on every surface&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What &lt;code&gt;AddRedbLlmStorage()&lt;/code&gt; replaces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Interface&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;REDB store&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IConversationStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InMemoryConversationStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RedbConversationStore&lt;/code&gt; — tree-backed, parent linkage via native &lt;code&gt;parent_id&lt;/code&gt;, message ids on indexed &lt;code&gt;value_string&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IToolIdempotencyStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InMemoryToolIdempotencyStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RedbToolIdempotencyStore&lt;/code&gt; (on top of &lt;code&gt;IIdempotentRepository&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IApprovalStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InMemoryApprovalStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RedbApprovalStore&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ICostBudgetStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InMemoryCostBudgetStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RedbCostBudgetStore&lt;/code&gt; — running totals per tenant/window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IAgentObserver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NoopAgentObserver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RedbAuditObserver&lt;/code&gt; — one row per tool call&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two non-obvious design choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-row business identifiers live on &lt;code&gt;_objects.value_string&lt;/code&gt; (an indexed column), not inside the props JSON. Lookups are O(log n) on one column, not a full scan with JSON decode.&lt;/li&gt;
&lt;li&gt;Transcript integrity is the tree's native &lt;code&gt;parent_id&lt;/code&gt; (&lt;code&gt;IRedbService.CreateChildAsync&lt;/code&gt;), not a soft FK in props. The tree primitive enforces it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stores lazy-sync their scheme on first use. No migration step.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the engine carries for free
&lt;/h2&gt;

&lt;p&gt;This is the central argument for "connector, not library." Everything redb.Route already does applies to LLM calls without extra code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;DSL primitive&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retry / backoff&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RedeliveryPolicy&lt;/code&gt;, &lt;code&gt;OnException&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Throttle&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resilience&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CircuitBreaker&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idempotency&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IdempotentConsumer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compensation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Saga&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit / shadow&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WireTap&lt;/code&gt;, &lt;code&gt;Multicast&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracing &amp;amp; metrics&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RouteActivitySource&lt;/code&gt;, &lt;code&gt;RouteMetrics&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;redb&lt;/code&gt; schemes (typed object engine)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A tool that calls an expensive API gets wrapped in a &lt;code&gt;CircuitBreaker&lt;/code&gt; and a &lt;code&gt;Throttle&lt;/code&gt;. Want a shadow run of a new system prompt alongside the old one? &lt;code&gt;Multicast&lt;/code&gt; + &lt;code&gt;WireTap&lt;/code&gt;. These aren't "features of the LLM connector" — they're the engine, and the LLM is attached to it as an endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's live-tested, and what isn't
&lt;/h2&gt;

&lt;p&gt;The honest status of the provider matrix (this is in the README, repeated here):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Groq + Llama 3.3 70B&lt;/strong&gt; — the most reliable free tier in scope. Carries the strict assertion in &lt;code&gt;BasicChatTests&lt;/code&gt; and &lt;code&gt;ToolRouteTests&lt;/code&gt; (the model has to produce a literal token).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mistral small-latest&lt;/strong&gt; — stable for short-form replies; tool-use on the free tier is flaky, so tool tests assert philosophically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini 2.0 Flash&lt;/strong&gt; — 15 RPM on free; works on a quiet repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic Claude&lt;/strong&gt; (Haiku 4.5 / Sonnet 4.6) — via the OpenAI-compat endpoint, live-tested in &lt;code&gt;ClaudeChatTests&lt;/code&gt; and the &lt;code&gt;redb.Route.Demo&lt;/code&gt; host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenRouter / Cerebras&lt;/strong&gt; — scaffolding works; individual free models rate-limit; tests gated on &lt;code&gt;[EnvFact]&lt;/code&gt; and skipped without a key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honest skip-list, things not in this release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings and vector stores&lt;/strong&gt; — Phase 2 (&lt;code&gt;embed://&lt;/code&gt;, &lt;code&gt;vector://&lt;/code&gt; schemes are planned).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG primitives, document loaders, web search&lt;/strong&gt; — not first-class yet (a Tavily search tool is already shipped in &lt;code&gt;redb.Route.Llm.Tools&lt;/code&gt; and plugs in as any other &lt;code&gt;.AsLlmTool("web_search")&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sliding-window memory shapes&lt;/strong&gt; (window-by-N-messages, window-by-K-tokens) — not first-class. Persistent transcripts via &lt;code&gt;AddRedbLlmStorage()&lt;/code&gt; are shipped; a windowed shape on top is realizable today via &lt;code&gt;Process&lt;/code&gt; + the conversation store, just not as a one-line option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native AnthropicProvider&lt;/strong&gt; — OpenAI-compat surface covers most scenarios; the native Messages API is for the features the compat surface doesn't expose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffh4rbr3jf860xsd8r2vz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffh4rbr3jf860xsd8r2vz.png" alt="redb llm connector example run on tsak side" width="800" height="721"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GitHub leads NuGet.&lt;/strong&gt; Fresh bug fixes (e.g. the &lt;code&gt;tool_use&lt;/code&gt;/&lt;code&gt;tool_result&lt;/code&gt;&lt;br&gt;
pairing recovery on conversation reload, or the Windows OEM-codepage&lt;br&gt;
decoding fix in &lt;code&gt;redb.Route.Exec&lt;/code&gt;) are already cut on &lt;code&gt;main&lt;/code&gt; under version&lt;br&gt;
&lt;strong&gt;3.1.1&lt;/strong&gt; — see &lt;a href="https://github.com/redbase-app/redb-route/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;&lt;code&gt;CHANGELOG.md&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
in the public repo. NuGet packages are batched into the next release, so&lt;br&gt;
if you hit a production bug — check &lt;code&gt;main&lt;/code&gt; and the latest pre-release tag&lt;br&gt;
first, then wait for the NuGet bump. This is normal practice, not a&lt;br&gt;
process bug.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route on GitHub&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route.Llm on NuGet&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.nuget.org/packages/redb.Route.Llm" rel="noopener noreferrer"&gt;nuget.org/packages/redb.Route.Llm&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route.Exec on NuGet&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.nuget.org/packages/redb.Route.Exec" rel="noopener noreferrer"&gt;nuget.org/packages/redb.Route.Exec&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full package README&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/blob/main/redb.Route.Llm/README.md" rel="noopener noreferrer"&gt;redb.Route.Llm/README.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;USER-GUIDE (full walkthrough — DSL, governance, conversation)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/blob/main/redb.Route.Llm/doc/USER-GUIDE.md" rel="noopener noreferrer"&gt;redb.Route.Llm/doc/USER-GUIDE.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STORAGE — REDB schemas for conversation / approval / audit / idempotency&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/blob/main/redb.Route.Llm/doc/STORAGE.md" rel="noopener noreferrer"&gt;redb.Route.Llm/doc/STORAGE.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exec connector README&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/blob/main/redb.Route.Exec/README.md" rel="noopener noreferrer"&gt;redb.Route.Exec/README.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standalone &lt;code&gt;Llm.HttpShell&lt;/code&gt; demo (curl → Claude → shell → reply)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/blob/main/demo/Llm.HttpShell/Program.cs" rel="noopener noreferrer"&gt;demo/Llm.HttpShell/Program.cs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full demo project (22+ routes)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/blob/main/demo/redb.Route.Demo/Routes/LlmHttpRoutes.cs" rel="noopener noreferrer"&gt;demo/redb.Route.Demo/Routes/LlmHttpRoutes.cs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discussions&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route/discussions&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All Apache 2.0. A separate deep-dive will cover the agent tool-loop, the governance hook interfaces, and a tour of the REDB storage schemas. Questions in the comments are exactly what writes the next post — especially anything along the lines of "does the connector do X if I do Y?"&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>opensource</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>REDB inside, part 1.1 — why the same 13 tables stay fast no matter how many classes you throw at them</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Mon, 08 Jun 2026 21:57:41 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/redb-inside-part-11-why-the-same-13-tables-stay-fast-no-matter-how-many-classes-you-throw-at-1gg5</link>
      <guid>https://dev.to/rinat_kozin/redb-inside-part-11-why-the-same-13-tables-stay-fast-no-matter-how-many-classes-you-throw-at-1gg5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8eg570it3m54lls8m4v.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8eg570it3m54lls8m4v.webp" alt="REDB Index" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A couple of weeks ago I published &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redb-inside-part-1-the-13-tables-the-whole-engine-runs-on-with-the-actual-sql-and-why-its-not-18mf"&gt;REDB inside, part 1 — the 13 tables the whole engine runs on&lt;/a&gt;. It walked through &lt;code&gt;_objects&lt;/code&gt;, &lt;code&gt;_values&lt;/code&gt;, &lt;code&gt;_structures&lt;/code&gt;, why this isn't classical EAV, and what the &lt;code&gt;_scheme_metadata_cache&lt;/code&gt; does. If you haven't read it, start there — the rest of this post assumes you know the layout.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;part 1.1&lt;/strong&gt;, not part 2. Same physical-storage conversation, different angle. Part 2 is going to be about code-first schemes (&lt;code&gt;SyncSchemeAsync&amp;lt;T&amp;gt;&lt;/code&gt;), and &lt;strong&gt;the deep C# dive — LINQ translator, CRUD internals, trees — that's parts 3 through 5 of the series&lt;/strong&gt;. This post stays in the database layer.&lt;/p&gt;

&lt;p&gt;The reason for a 1.1 instead of jumping to 2 is simple: every time I publish part 1, the same question comes back in the comments and in DMs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Fine, typed columns beat string EAV. But you've still got 9.7 million rows in &lt;code&gt;_values&lt;/code&gt;. Any real query — &lt;code&gt;WHERE Salary &amp;gt; 80000&lt;/code&gt;, &lt;code&gt;WHERE OrderDate &amp;gt;= '2026-06-01'&lt;/code&gt; — has to scan that table. Without one index per field per schema you're living in Seq Scan."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fair question. Let's actually answer it, with the DDL, the prod numbers, and a side note about how to &lt;em&gt;remove&lt;/em&gt; indexes once your app stabilizes.&lt;/p&gt;

&lt;p&gt;Numbers in this post come from TSUM — a logistics system handling truck movements and orders through distribution centers. Real production, no benchmark setup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; In a classical EF schema, indexes multiply with tables. In REDB, the index set is designed once in DDL and serves any business schema you put on top — adding the 33rd class to your domain doesn't add a single line to &lt;code&gt;redbPostgre.sql&lt;/code&gt;. The active surface for any business class is just &lt;strong&gt;2 tables&lt;/strong&gt; (&lt;code&gt;_objects&lt;/code&gt; + &lt;code&gt;_values&lt;/code&gt;), plus 2 more for lookups and 3 for RTTI metadata; the rest is infrastructure that doesn't grow with your class count. On TSUM prod this gives &lt;strong&gt;999 orders / 991 ms&lt;/strong&gt; on 2 cores with default Postgres settings and zero framework-level cache: one SELECT pulls all 999 existing routes (139 ms), bulk-save writes 32 changed objects through the COPY protocol (154 ms), and 967 unchanged rows never reach the database thanks to an app-level &lt;code&gt;ComputeHash()&lt;/code&gt; short-circuit.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The "9.7 million rows" objection, defused in two sentences
&lt;/h2&gt;

&lt;p&gt;Before we open any DDL, here's the structural answer to the objection above.&lt;/p&gt;

&lt;p&gt;Classical EAV has rows like &lt;code&gt;(object_id, attribute_name TEXT, value TEXT)&lt;/code&gt;. An index on &lt;code&gt;value&lt;/code&gt; is useless there because values are heterogeneous, casts happen at runtime, and selectivity on &lt;code&gt;attribute_name&lt;/code&gt; is low. &lt;strong&gt;REDB is not that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two things change the picture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Values are already split into typed columns.&lt;/strong&gt; &lt;code&gt;_Long&lt;/code&gt;, &lt;code&gt;_String&lt;/code&gt;, &lt;code&gt;_Numeric&lt;/code&gt;, &lt;code&gt;_DateTimeOffset&lt;/code&gt;, &lt;code&gt;_Boolean&lt;/code&gt;, &lt;code&gt;_Guid&lt;/code&gt;, &lt;code&gt;_ListItem&lt;/code&gt;, &lt;code&gt;_Object&lt;/code&gt;. No casts at query time. An index on &lt;code&gt;_Long&lt;/code&gt; is a normal bigint btree, not a polymorphic guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every row in &lt;code&gt;_values&lt;/code&gt; carries &lt;code&gt;_id_structure&lt;/code&gt;&lt;/strong&gt; — a bigint pointing at the schema field it belongs to. With 401 fields across the TSUM domain, &lt;code&gt;WHERE _id_structure = X&lt;/code&gt; discards roughly 99.75% of the table before the optimizer even looks at the value column.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So a facet query never scans 9.7M rows. It scans &lt;code&gt;~_values_count / structure_count&lt;/code&gt; rows, then does an Index Only Scan on a typed column. We'll see this in the EXPLAIN-equivalent reasoning below.&lt;/p&gt;

&lt;p&gt;That's the architecture. Now the question that actually matters: &lt;strong&gt;how exactly are the indexes laid out so that this runs in milliseconds?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The architectural payoff: indexes don't grow with your domain
&lt;/h2&gt;

&lt;p&gt;In a classical EF layout, the table count grows linearly with business entities. Each table averages 3–5 indexes — primary key, FK indexes, business filters. So &lt;strong&gt;the total number of indexes in the database grows multiplicatively&lt;/strong&gt; with project size.&lt;/p&gt;

&lt;p&gt;Take TSUM. Here's what's in &lt;code&gt;tsum.Domain/Entities&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TransportationRoute&lt;/code&gt; (the actual route)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TransportationPoint&lt;/code&gt; (route stops)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Driver&lt;/code&gt;, &lt;code&gt;Vehicle&lt;/code&gt;, &lt;code&gt;ShippingPoint&lt;/code&gt;, &lt;code&gt;YardPlace&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SliceSettings&lt;/code&gt;, &lt;code&gt;SliceSnapshot&lt;/code&gt;, &lt;code&gt;TransportSnapshot&lt;/code&gt;, &lt;code&gt;TransportNorm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SpecialRcSettings&lt;/code&gt;, &lt;code&gt;TonnageGroupSettings&lt;/code&gt;, &lt;code&gt;GarageState&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TsumAdUserRef&lt;/code&gt;, &lt;code&gt;UserFilterPreference&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fifteen "root" entities. But that's just the start. Inside &lt;code&gt;TransportationRoute&lt;/code&gt; alone you'll find &lt;code&gt;RedbListItem&lt;/code&gt; references to: &lt;code&gt;Drivers&lt;/code&gt;, &lt;code&gt;Vehicles&lt;/code&gt;, &lt;code&gt;CarMarks&lt;/code&gt;, &lt;code&gt;ShippingPoints&lt;/code&gt;, &lt;code&gt;BusinessTypes&lt;/code&gt;, &lt;code&gt;YardPlaces&lt;/code&gt; (×2 — &lt;code&gt;PlaceTo&lt;/code&gt; / &lt;code&gt;PlaceFrom&lt;/code&gt;), &lt;code&gt;LoadingZones&lt;/code&gt;, &lt;code&gt;TransportStatuses&lt;/code&gt;, &lt;code&gt;DeliveryStatuses&lt;/code&gt;, &lt;code&gt;TripRisks&lt;/code&gt;. Every other entity has its own set: chassis types, readiness statuses, delay reasons, route classifications.&lt;/p&gt;

&lt;p&gt;In a classical schema, every one of those lookups is its own table. Plus audit/history tables (&lt;code&gt;TransportSnapshot&lt;/code&gt;, &lt;code&gt;SliceSnapshot&lt;/code&gt; are clearly snapshot entities). Plus M2M tables for collection links. &lt;strong&gt;A realistic estimate for TSUM in a classical layout is 60–80 tables.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At 60–80 tables and 3–4 indexes per table, you're looking at &lt;strong&gt;200–320 indexes&lt;/strong&gt; to design, maintain, reindex, and drop when stale. Every release in an EF project ships an &lt;code&gt;AddColumn&lt;/code&gt; + &lt;code&gt;CreateIndex&lt;/code&gt; migration. By year two, somebody walks the schema with a flashlight removing duplicate indexes that piled up because three different developers added similar-but-not-identical indexes for slightly different queries.&lt;/p&gt;

&lt;p&gt;In REDB the picture is different. The same 32 schemes / 401 properties in TSUM physically have &lt;strong&gt;2 primary keys&lt;/strong&gt;: &lt;code&gt;pk__objects&lt;/code&gt; and &lt;code&gt;pk__values&lt;/code&gt;. The full index list is hardcoded in &lt;code&gt;redbPostgre.sql&lt;/code&gt; and runs around 50 indexes — &lt;strong&gt;for the entire system&lt;/strong&gt;. Not per class. For the system.&lt;/p&gt;

&lt;p&gt;Now here's the clarification I owe readers from part 1. I called REDB "13 tables" in that post, and a fair number of people walked away with the impression that all 13 are involved every time you query a class. They aren't. The actual breakdown is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data storage — 2 tables:&lt;/strong&gt; &lt;code&gt;_objects&lt;/code&gt; (the header) and &lt;code&gt;_values&lt;/code&gt; (the field values). Every &lt;code&gt;Query&amp;lt;T&amp;gt;()&lt;/code&gt;, every &lt;code&gt;SaveAsync&lt;/code&gt;, every LINQ filter lands here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lookups — 2 tables:&lt;/strong&gt; &lt;code&gt;_lists&lt;/code&gt; and &lt;code&gt;_list_items&lt;/code&gt;. These power &lt;code&gt;RedbListItem&lt;/code&gt; fields (Driver, Vehicle, ShippingPoint, etc.). Lookups are shared across classes and don't grow when you add a new business entity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RTTI / schema metadata — 3 tables:&lt;/strong&gt; &lt;code&gt;_types&lt;/code&gt; (primitives), &lt;code&gt;_schemes&lt;/code&gt; (classes), &lt;code&gt;_structures&lt;/code&gt; (class properties). The engine reads these through &lt;code&gt;_scheme_metadata_cache&lt;/code&gt;; business queries never touch them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else — infrastructure:&lt;/strong&gt; &lt;code&gt;_users&lt;/code&gt;, &lt;code&gt;_roles&lt;/code&gt;, &lt;code&gt;_permissions&lt;/code&gt;, &lt;code&gt;_links&lt;/code&gt;, &lt;code&gt;_dependencies&lt;/code&gt;, &lt;code&gt;_functions&lt;/code&gt;, &lt;code&gt;_scheme_metadata_cache&lt;/code&gt;, soft-delete via &lt;code&gt;@@__deleted&lt;/code&gt;. These don't grow with class count and aren't on the hot path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So whatever class a developer writes in &lt;code&gt;tsum.Domain&lt;/code&gt;, its data physically lives in &lt;strong&gt;2 tables&lt;/strong&gt;; reference fields hit &lt;strong&gt;2&lt;/strong&gt; more for lookups; schema metadata sits in &lt;strong&gt;3&lt;/strong&gt; RTTI tables. The six infrastructure tables look identical for a 5-class project and a 500-class one. &lt;em&gt;That&lt;/em&gt; is what "indexes invariant of class count" really means — not "all 13 tables on every query," but "the active footprint for business data is two tables, and it doesn't change."&lt;/p&gt;

&lt;p&gt;When a developer adds the 33rd class to &lt;code&gt;tsum.Domain&lt;/code&gt; — say, &lt;code&gt;WarehouseSlot&lt;/code&gt; — the database gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one row in &lt;code&gt;_schemes&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;N rows in &lt;code&gt;_structures&lt;/code&gt; (one per property),&lt;/li&gt;
&lt;li&gt;zero index migrations. None.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the entire query surface for the new class is served by the same indexes on &lt;code&gt;_objects&lt;/code&gt; and &lt;code&gt;_values&lt;/code&gt; that served the previous 32. &lt;code&gt;WHERE _id_scheme = ID(WarehouseSlot)&lt;/code&gt; uses &lt;code&gt;IX__objects__schemes&lt;/code&gt;. Sort by creation date — &lt;code&gt;IX__objects__scheme_date_create&lt;/code&gt;. Find an object by some property value — &lt;code&gt;IX__values__structure_object_lookup&lt;/code&gt;. Parent–child tree — &lt;code&gt;IX__objects__scheme_parent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the architectural payoff the post is built around. &lt;strong&gt;The index plan is fixed at engine design time&lt;/strong&gt;, not smeared across business-app releases. The skeptic will counter: "so generic indexes on &lt;code&gt;_values&lt;/code&gt; must be slower than per-field indexes in an EF schema." They aren't — and we'll see why below.&lt;/p&gt;

&lt;p&gt;The same applies to MSSQL. Worth saying out loud: this post isn't Postgres-specific. &lt;code&gt;redb.MSSql/sql/redbMSSQL.sql&lt;/code&gt; carries a mirror set of indexes with minor dialect differences (covered in a later section). The architectural trick — indexes on the physical schema, not on business tables — ports between engines without giving anything up.&lt;/p&gt;




&lt;h2&gt;
  
  
  A tour of &lt;code&gt;redbPostgre.sql&lt;/code&gt;, grouped by what it's for
&lt;/h2&gt;

&lt;p&gt;If you open the DDL and read top to bottom you'll see ~50 indexes interleaved. They're easier to understand grouped &lt;strong&gt;by use case&lt;/strong&gt; rather than by table. Let's do it that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Storage uniqueness in &lt;code&gt;_values&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Three partial-unique indexes, one per shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Scalar fields: one (object, field) → one value&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;UIX__values__structure_object&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- The marker row for a nested class/array inside a parent&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;UIX__values__structure_object_parent&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Collection elements indexed by position/key&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;UIX__values__structure_object_array_index&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why three instead of one. Each shape has a different uniqueness key. Scalars are unique by &lt;code&gt;(structure, object)&lt;/code&gt;. Nested-structure markers add &lt;code&gt;parent&lt;/code&gt;. Array elements add &lt;code&gt;index&lt;/code&gt;. A single covering unique index can't express this (NULLs would break the semantics), but three partials map cleanly onto the physical model.&lt;/p&gt;

&lt;p&gt;Bonus: each partial only stores its slice of rows, so the btree is smaller than one giant unique index would be. On 9.7M rows that's tens of percent saved in page cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Facet search — the hottest path
&lt;/h3&gt;

&lt;p&gt;A facet query in REDB is "find objects whose field X compares to Y." In SQL terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;structure_id&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;80000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That pattern is served by a single composite index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__structure_object_lookup&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_ListItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Object&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every typed column except &lt;code&gt;_String&lt;/code&gt; is in the key. Index Only Scan: Postgres finds the matching rows and grabs the value &lt;strong&gt;directly from the index&lt;/strong&gt; — no heap fetch.&lt;/p&gt;

&lt;p&gt;In parallel, two covering indexes with &lt;code&gt;INCLUDE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__object_structure_lookup&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_ListItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Object&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__object_array_null&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_ListItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These cover the opposite direction: "load all values of an object in one query," for &lt;code&gt;LoadAsync&amp;lt;T&amp;gt;&lt;/code&gt;. Same trick — Index Only Scan, zero heap reads.&lt;/p&gt;

&lt;p&gt;Why &lt;code&gt;_String&lt;/code&gt; is excluded. Postgres btree caps at ~2700 bytes per index row. If a JWT payload ends up in &lt;code&gt;_String&lt;/code&gt; at 3 KB, the insert blows up. So strings get their own partial index with a length guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__String_not_null&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_String&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Long strings (CMS bodies, base64 payloads) aren't covered — and they aren't filtered with &lt;code&gt;=&lt;/code&gt; either. For substring search there's a separate GIN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;pg_trgm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__String_pattern&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt; &lt;span class="n"&gt;gin_trgm_ops&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_String&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This serves &lt;code&gt;LIKE&lt;/code&gt;, &lt;code&gt;ILIKE&lt;/code&gt;, &lt;code&gt;$contains&lt;/code&gt;, &lt;code&gt;$startsWith&lt;/code&gt;, &lt;code&gt;$endsWith&lt;/code&gt;, regex — all through &lt;code&gt;pg_trgm&lt;/code&gt;. The MSSQL counterpart is full-text search or a persisted computed column with a filtered index; the choice is in the dialect's DDL.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Collections (arrays and dictionaries)
&lt;/h3&gt;

&lt;p&gt;Part 1 covered relational storage of collections — a marker row plus child rows linked by &lt;code&gt;_array_parent_id&lt;/code&gt; and &lt;code&gt;_array_index&lt;/code&gt;. The supporting indexes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- traverse collection elements by index&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__array_parent_index&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- look up by dictionary key (string keys)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__array_key&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- partial: only rows that belong to collections&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__values__parent_structure&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third one feeds the pivot CTE that reads nested Class/Dictionary fields like &lt;code&gt;AddressBook["home"].City&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Object trees (&lt;code&gt;_objects._id_parent&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Trees are the backbone of REDB — sections, categories, org charts, all linked through &lt;code&gt;_id_parent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- direct children: scheme + parent → id&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__scheme_parent&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- covering index for subtree traversal (metadata in INCLUDE)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__parent_id_descendant_lookup&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_date_create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_date_modify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- partial: roots (objects without a parent)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__root_objects&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- reverse path: child → parent → scheme&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__id_parent_scheme&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;WhereHasAncestor&lt;/code&gt;, &lt;code&gt;WhereHasDescendant&lt;/code&gt;, &lt;code&gt;LoadTreeAsync&lt;/code&gt; all run through this quartet.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Sorting and metadata
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- main "newest first" feed index&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__scheme_date_create&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_date_create&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- sort by name within a scheme&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__scheme_name&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- global indexes for cross-scheme search&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__name&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__hash&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the indexes for &lt;code&gt;RedbPrimitive&amp;lt;T&amp;gt;&lt;/code&gt; (when an object &lt;em&gt;is&lt;/em&gt; a primitive — counter, token, single value):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__value_long&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_value_long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_value_long&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__value_string&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_value_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_value_string&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__value_guid&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_value_guid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_value_guid&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__value_datetime&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_value_datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_value_datetime&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__objects__value_numeric&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_value_numeric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_value_numeric&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All partial. Each one only indexes rows where its value column is set.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Schemes and structures (&lt;code&gt;_structures&lt;/code&gt;, &lt;code&gt;_schemes&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;These are the cold tables — orders of magnitude smaller than &lt;code&gt;_values&lt;/code&gt;, mostly read while building the metadata cache. They still need to be sub-millisecond:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- covering index for ORDER BY field name&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__structures__name&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_structures&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_collection_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- covering lookup by structure ID&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__structures__id_lookup&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_structures&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_collection_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- partial: split collection vs non-collection&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__structures__not_collection&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_structures&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_collection_type&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX__structures__collection&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_structures&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_collection_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_collection_type&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DDL comments record the reason: these indexes kill Seq Scan on InitPlan and EXISTS subqueries inside &lt;code&gt;build_hierarchical_properties_optimized&lt;/code&gt;, dropping query cost from 6.10 to 4.29 (-30%).&lt;/p&gt;

&lt;h3&gt;
  
  
  What got removed
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;redbPostgre.sql&lt;/code&gt; has a commented-out block worth quoting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;
&lt;span class="c1"&gt;-- REMOVED REDUNDANT INDEXES (migration_drop_redundant_indexes.sql)&lt;/span&gt;
&lt;span class="c1"&gt;-- Reason: Covered by composite index IX__values__structure_object_lookup&lt;/span&gt;
&lt;span class="c1"&gt;-- Facet search ALWAYS filters by (_id_structure, _id_object) BEFORE value&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================&lt;/span&gt;
&lt;span class="c1"&gt;-- CREATE INDEX "IX__values__String" ON _values (_String) ...;&lt;/span&gt;
&lt;span class="c1"&gt;-- CREATE INDEX "IX__values__Long" ON _values (_Long) ...;&lt;/span&gt;
&lt;span class="c1"&gt;-- CREATE INDEX "IX__values__Guid" ON _values (_Guid) ...;&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a cleanup history committed straight into the schema file. Early REDB shipped one index per typed column. Real workloads showed they never lit up: facet queries &lt;strong&gt;always&lt;/strong&gt; start from &lt;code&gt;_id_structure&lt;/code&gt;, never from a raw value column. The composite &lt;code&gt;(_id_structure, _id_object, _Long, ...)&lt;/code&gt; swallowed them whole. The single-column ones got pulled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is fast on prod
&lt;/h2&gt;

&lt;p&gt;Architecture is half the story. The other half is the actual numbers. Here's a typical TSUM order-processing tick from production logs:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TSUM] orders=999 routes(+2 ~30 =967) drivers(+0 ~0) vehicles(+0 ~0)
       sync=482 query=139 save=154 total=991ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TSUM] orders=999 routes(+2 ~30 =967) drivers(+0 ~0) vehicles(+0 ~0) sync=482 query=139 save=154 total=991ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;999 orders&lt;/strong&gt; arrived in one XML batch from SAP S/4 (the stored procedure &lt;code&gt;usp_TsUM_MonitoringReport_xml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;routes(+2 ~30 =967)&lt;/strong&gt;: of the 999 incoming, 2 were new routes, 30 changed, &lt;strong&gt;967 weren't touched at all&lt;/strong&gt; (content unchanged).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;drivers / vehicles (+0 ~0)&lt;/strong&gt;: the driver and vehicle dictionaries had no changes in this batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sync = 482 ms&lt;/strong&gt; — this is &lt;strong&gt;lookup-dictionary sync&lt;/strong&gt; (&lt;code&gt;DictionarySyncService.SyncFromOrdersAsync&lt;/code&gt;): walk all 999 orders, reconcile drivers / vehicles / shipping points / business types / delivery statuses against REDB lists, upsert any new &lt;code&gt;list_items&lt;/code&gt; if needed. &lt;strong&gt;This is not schema sync.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;query = 139 ms&lt;/strong&gt; — &lt;strong&gt;one SELECT&lt;/strong&gt; to REDB: &lt;code&gt;WhereRedb(o =&amp;gt; codes.Contains(o.ValueString))&lt;/code&gt; — pulls &lt;strong&gt;all 999&lt;/strong&gt; existing routes by their codes in a single query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;save = 154 ms&lt;/strong&gt; — &lt;code&gt;SaveAsync&lt;/code&gt; for &lt;strong&gt;32 objects&lt;/strong&gt; (2 created + 30 updated). The 967 unchanged ones never get here.&lt;/li&gt;
&lt;li&gt;Environment: &lt;strong&gt;2 cores&lt;/strong&gt;, default &lt;code&gt;shared_buffers&lt;/code&gt;, no &lt;code&gt;pg_prewarm&lt;/code&gt;, no framework-level cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Important caveat: schema sync (&lt;code&gt;SyncSchemeAsync&amp;lt;T&amp;gt;&lt;/code&gt; — the bit that compares &lt;code&gt;_structure_hash&lt;/code&gt; and refreshes &lt;code&gt;_scheme_metadata_cache&lt;/code&gt;) is a &lt;strong&gt;separate&lt;/strong&gt; step that runs once at process startup, before any orders flow. It's not in this log line at all. The "sync" number here is application-level dictionary reconciliation, not schema reconciliation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's break down what's physically happening behind each number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query: 139 ms for 999 objects in one call
&lt;/h3&gt;

&lt;p&gt;The most striking number in the log. This isn't 999 separate &lt;code&gt;LoadAsync&lt;/code&gt; calls — it's &lt;strong&gt;one&lt;/strong&gt; facet query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransportationRoute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueString&lt;/span&gt;&lt;span class="p"&gt;!))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It translates roughly to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- find all routes whose _value_string is in the list of 999 codes&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;routeSchemeId&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_value_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, one batched query for &lt;code&gt;_values&lt;/code&gt; of all matched objects via &lt;code&gt;IX__values__object_array_null&lt;/code&gt; (covering INCLUDE) — Index Only Scan, no heap access. Output: 999 fully hydrated &lt;code&gt;RedbObject&amp;lt;TransportationRoute&amp;gt;&lt;/code&gt; instances with all ~50 properties each, including ListItem references to dictionaries.&lt;/p&gt;

&lt;p&gt;139 ms / 999 objects = &lt;strong&gt;0.14 ms per object&lt;/strong&gt; on paper, but that math is misleading — the real cost is two SQL round trips (one for &lt;code&gt;_objects&lt;/code&gt;, one for &lt;code&gt;_values&lt;/code&gt;) plus C# hydration.&lt;/p&gt;

&lt;p&gt;No N+1. No &lt;code&gt;Include&lt;/code&gt;s. ListItem references resolve through &lt;code&gt;RefDataCache&lt;/code&gt; in memory, so &lt;code&gt;Vehicle&lt;/code&gt;, &lt;code&gt;Driver&lt;/code&gt;, &lt;code&gt;ShippingPoint&lt;/code&gt; get filled without extra SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Save: 154 ms for 32 changed objects
&lt;/h3&gt;

&lt;p&gt;Two things matter here, and both are critical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First — application-level change tracking.&lt;/strong&gt; Before &lt;code&gt;SaveAsync&lt;/code&gt;, every existing route runs through &lt;code&gt;ComputeHash()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hashBefore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;EnrichRouteFromOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routeProps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;hashBefore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// data really changed — queue for save&lt;/span&gt;
    &lt;span class="n"&gt;updatedCount&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;skippedCount&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;   &lt;span class="c1"&gt;// byte-identical — don't touch the database&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of the 999 incoming orders, &lt;strong&gt;967 are skipped right here&lt;/strong&gt;. They never hit &lt;code&gt;_objects&lt;/code&gt;/&lt;code&gt;_values&lt;/code&gt;, never generate a single DML statement. This isn't a REDB-engine optimization — it's an application-code pattern, but REDB supports it cheaply via &lt;code&gt;ComputeHash()&lt;/code&gt; over Props.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second — bulk save in a single transaction.&lt;/strong&gt; The remaining 32 objects go in one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// toSave: List&amp;lt;IRedbObject&amp;gt;, 32 items&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffthq4shh662mytnsknty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffthq4shh662mytnsknty.png" alt="logs select and materialize from 9m properties" width="799" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what happens inside. &lt;code&gt;_objects&lt;/code&gt; gets normalized in a batch first — for all 32 objects, one INSERT/UPDATE pass (new rows go via COPY/&lt;code&gt;UNNEST&lt;/code&gt;-INSERT, modified rows via batched UPDATE). Then &lt;code&gt;_values&lt;/code&gt;: for each modified object the value rows are recreated, and again not row-by-row but in one bulk call through Postgres COPY protocol. &lt;strong&gt;All in a single transaction&lt;/strong&gt; — &lt;code&gt;_objects&lt;/code&gt; and &lt;code&gt;_values&lt;/code&gt; commit atomically.&lt;/p&gt;

&lt;p&gt;COPY matters. It's a streaming binary protocol that ingests N rows without a per-row round trip — the driver writes a continuous byte stream into the socket. For 32 objects × ~50 properties = ~1600 value rows, that's one COPY operation instead of 1600 INSERTs. Btree indexes get their batch and update amortized.&lt;/p&gt;

&lt;p&gt;So 154 ms for 32 objects (~4.8 ms per object on full overwrite) isn't "the engine is very fast" — it's "the engine doesn't do unnecessary work." Bulk where it can bulk; early skip where data didn't change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sync: 482 ms — what it really is
&lt;/h3&gt;

&lt;p&gt;482 ms is the heaviest line in the log, and it's also the most commonly misread one. It's &lt;strong&gt;not&lt;/strong&gt; schema sync — schema sync already ran at Worker startup, before any orders showed up. The 482 ms is &lt;code&gt;DictionarySyncService.SyncFromOrdersAsync&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Five parallel dictionary loads from REDB: &lt;code&gt;Drivers&lt;/code&gt;, &lt;code&gt;Vehicles&lt;/code&gt;, &lt;code&gt;ShippingPoints&lt;/code&gt;, &lt;code&gt;BusinessTypes&lt;/code&gt;, &lt;code&gt;DeliveryStatuses&lt;/code&gt; — via &lt;code&gt;redb.ListProvider.GetListByNameWithItemsAsync(...)&lt;/code&gt;. Pulls &lt;code&gt;_lists&lt;/code&gt; plus all their &lt;code&gt;_list_items&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Walk over 999 orders: each &lt;code&gt;DriverId&lt;/code&gt;, &lt;code&gt;CarId&lt;/code&gt;, &lt;code&gt;ShippingPoint&lt;/code&gt;, &lt;code&gt;BusinessTypes&lt;/code&gt; is checked against existing list items. If new, an upsert is queued.&lt;/li&gt;
&lt;li&gt;If dictionaries changed — refresh &lt;code&gt;RefDataCache&lt;/code&gt; (a static in-memory cache so the Order → RouteProps mapping doesn't re-query the DB).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This batch had zero deltas on drivers and vehicles (&lt;code&gt;+0 ~0&lt;/code&gt;), but 482 ms still went into &lt;strong&gt;proving&lt;/strong&gt; they didn't change — that's 999 rows being checked against thousands of existing list items across multiple dictionaries.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;application-level&lt;/strong&gt; sync. It belongs to TSUM's logistics process, not REDB's engine. But it makes a useful point: even when "discovering nothing needs to change" costs more than the actual save (482 ms sync vs 154 ms save), the whole tick still fits in under a second for a thousand orders.&lt;/p&gt;

&lt;p&gt;Schema sync is a separate story. Cold start of the Worker — tens to hundreds of milliseconds, one-off (32 schemes, hash comparison). Hot restart with no model changes — single-digit milliseconds. It happens &lt;strong&gt;before&lt;/strong&gt; the first &lt;code&gt;[TSUM]&lt;/code&gt; log line and isn't counted in the per-tick budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coming back to the 9.7M-row objection
&lt;/h3&gt;

&lt;p&gt;The original objection was "how can this be fast at that table size." The answer: &lt;strong&gt;the engine never scans &lt;code&gt;_values&lt;/code&gt; whole&lt;/strong&gt;. Any query starts from one of these four anchors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PK (&lt;code&gt;LoadAsync&lt;/code&gt; by ID)&lt;/strong&gt; — O(log n) on the btree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;_id_scheme&lt;/code&gt; + &lt;code&gt;_value_string&lt;/code&gt;&lt;/strong&gt; for facets through a &lt;code&gt;RedbPrimitive&lt;/code&gt; column, like the TSUM case above — Index Only Scan on &lt;code&gt;IX__objects__value_string&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;_id_structure&lt;/code&gt;&lt;/strong&gt; (regular facet search) — discards ~99.75% of rows immediately. With 401 properties, the average &lt;code&gt;_id_structure&lt;/code&gt; covers ~24K of the 9.7M total.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;_id_object&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;LoadAsync&lt;/code&gt; — all values for an object) — discards even more aggressively, since an object has tens of values, not thousands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table size in rows only affects the index size in pages. Postgres btree fan-out is in the hundreds, so 9.7M rows is a 4–5 level deep tree. Search is logarithmic, not linear.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz9jq71v5yv3vbv3qlo2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvz9jq71v5yv3vbv3qlo2.png" alt="logs select and materialize from 9m properties" width="799" height="465"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  "You don't always need everything you've got": pruning indexes after stabilization
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;redbPostgre.sql&lt;/code&gt; carries the &lt;strong&gt;full spectrum&lt;/strong&gt; of indexes for every REDB scenario: facet search, trees, collections, GIN full-text, partial indexes for NOT NULL, indexes for recursive permission CTEs. That's right for a library: you don't know up front which workload profile a given app will have.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;in your specific app, some of those indexes may never light up.&lt;/strong&gt; That's fine. TSUM, for instance, never calls &lt;code&gt;WhereHasDescendant&lt;/code&gt; because its domain hierarchies are flat — so &lt;code&gt;IX__objects__parent_id_descendant_lookup&lt;/code&gt; sits in pages but no scan ever goes through it.&lt;/p&gt;

&lt;p&gt;After the app's been in production for two to four weeks under stable load, &lt;strong&gt;pull the index stats&lt;/strong&gt; and trim aggressively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;idx_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sz&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;  &lt;span class="c1"&gt;-- only indexes &amp;gt; 10 MB&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzz7w7ayks13wwkjofqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzz7w7ayks13wwkjofqz.png" alt="idx_scan" width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On TSUM, this query surfaces about &lt;strong&gt;4423 MB&lt;/strong&gt; of indexes with &lt;code&gt;idx_scan = 0&lt;/code&gt;. That &lt;strong&gt;doesn't&lt;/strong&gt; mean those indexes are bad. It means nobody's hitting those query paths in this workload profile.&lt;/p&gt;

&lt;p&gt;Working with the result:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't drop everything immediately.&lt;/strong&gt; First, walk the list: which indexes serve scenarios that "haven't fired yet" (quarterly reports, year-end reconciliations, big imports)? Leave those alone — re-creation later costs more than the disk you'd save now.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The rest are candidates for &lt;code&gt;DROP INDEX CONCURRENTLY&lt;/code&gt;.&lt;/strong&gt; It's safe — no table lock — and you can always restore the index with the same &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;, since the DDL knows what shape it should have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;After dropping, watch for two to four more weeks.&lt;/strong&gt; If no unexpectedly slow queries show up in logs, the call was right. If something regresses, restore.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a &lt;strong&gt;standard PostgreSQL practice&lt;/strong&gt;, not a REDB-specific trick. But it works cleaner in REDB because the index set is designed once and frozen in DDL. Pruning is one targeted decision in one place — not a hunt through three years of accumulated migrations across business tables.&lt;/p&gt;

&lt;p&gt;The other direction works too. If you spot a single facet query that's hot enough to warrant an extra push, nothing stops you from adding a &lt;strong&gt;local&lt;/strong&gt; index just for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- e.g., "find routes for a specific driver in the last 7 days"&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IX_my_app__route_by_driver&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_ListItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;driver_field_id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That kind of index is application-side optimization. It lives in your app's migration, not in the REDB core. And the same &lt;code&gt;pg_stat_user_indexes&lt;/code&gt; audit applies to it after a few weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's deliberately &lt;em&gt;not&lt;/em&gt; in the DDL, and why
&lt;/h2&gt;

&lt;p&gt;A second-level skeptic asks the next question: "Why no hash index on &lt;code&gt;_String&lt;/code&gt;? Why no BRIN on &lt;code&gt;_date_create&lt;/code&gt;? Why no expression index on &lt;code&gt;LOWER(_String)&lt;/code&gt;?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hash indexes.&lt;/strong&gt; Historically weaker than btree on most metrics in Postgres, and they don't support partial conditions. Almost every REDB index is partial — so hash is structurally a non-fit, not a coin-flip choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BRIN on &lt;code&gt;_date_create&lt;/code&gt;.&lt;/strong&gt; A reasonable candidate on big archival tables, but redundant for the current REDB profile. &lt;code&gt;IX__objects__scheme_date_create (DESC, _id)&lt;/code&gt; already serves the exact scenario BRIN would compete with. BRIN would only win in a cold-storage case (&amp;gt;10M rows on a single scheme) — that's a separate "archival classes" phase, and the current core DDL doesn't address it yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expression indexes (&lt;code&gt;LOWER(_String)&lt;/code&gt;, &lt;code&gt;_DateTimeOffset::date&lt;/code&gt;, etc.).&lt;/strong&gt; Deliberately omitted. This is an &lt;strong&gt;application choice&lt;/strong&gt;: case-insensitive search on a specific text field, aggregation by day/month — these are options that belong in the app's migration, not the engine. GIN with &lt;code&gt;pg_trgm&lt;/code&gt; already provides case-insensitive substring search via ILIKE, and that covers most needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bloom, BRIN-multi, GIN on jsonb.&lt;/strong&gt; All candidates for narrow scenarios. If you have one very specific query that's provably hot and provably not covered by the standard set — you add a local index for it in a separate migration. Without touching &lt;code&gt;redbPostgre.sql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The principle is general: &lt;strong&gt;the core DDL covers 95% of scenarios, the remaining 5% lives in the application layer.&lt;/strong&gt; That's the opposite of the EF approach, where the DDL belongs to the application from day one and every index is part of its migration history.&lt;/p&gt;




&lt;h2&gt;
  
  
  MSSQL: nearly the same picture
&lt;/h2&gt;

&lt;p&gt;While we're in DDL territory, worth saying clearly: this post isn't Postgres-specific. &lt;code&gt;redb.MSSql/sql/redbMSSQL.sql&lt;/code&gt; carries a mirror set of indexes, and the architectural trick — "indexes don't grow with class count" — works identically on both engines.&lt;/p&gt;

&lt;p&gt;Ports cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All three partial-unique indexes on &lt;code&gt;_values&lt;/code&gt; (in MSSQL — filtered indexes with &lt;code&gt;WHERE&lt;/code&gt;, different syntax, same semantics).&lt;/li&gt;
&lt;li&gt;Composite indexes for facet search (&lt;code&gt;IX__values__structure_object_lookup&lt;/code&gt; exists in both DDLs with an identical key shape).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INCLUDE&lt;/code&gt; covering indexes — natively supported in MSSQL since 2005, port one-to-one.&lt;/li&gt;
&lt;li&gt;Object-tree indexes (&lt;code&gt;IX__objects__scheme_parent&lt;/code&gt;, &lt;code&gt;IX__objects__parent_id_descendant_lookup&lt;/code&gt;) — unchanged.&lt;/li&gt;
&lt;li&gt;Partial / filtered indexes for &lt;code&gt;RedbPrimitive&amp;lt;T&amp;gt;&lt;/code&gt; — unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GIN + &lt;code&gt;pg_trgm&lt;/code&gt;&lt;/strong&gt; for substring search on &lt;code&gt;_String&lt;/code&gt;. The MSSQL counterpart is full-text search or a filtered index over a computed column. &lt;code&gt;redbMSSQL.sql&lt;/code&gt; takes a different path (&lt;code&gt;LIKE&lt;/code&gt; over a regular btree with a length-bound partial).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NULLS NOT DISTINCT&lt;/code&gt;&lt;/strong&gt; in Postgres 15+ — MSSQL spells it differently (&lt;code&gt;UNIQUE&lt;/code&gt; + filtered &lt;code&gt;WHERE NOT NULL&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ON DELETE CASCADE&lt;/code&gt; from &lt;code&gt;_structures&lt;/code&gt; to &lt;code&gt;_values&lt;/code&gt;&lt;/strong&gt; works directly in Postgres but not in MSSQL because of multiple cascade paths — replaced with an &lt;code&gt;INSTEAD OF DELETE&lt;/code&gt; trigger. The DELETE plan is slightly different in MSSQL but the indexes are the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-type filtered indexes&lt;/strong&gt; are spelled out more explicitly in MSSQL (&lt;code&gt;IX__values__Long_filter&lt;/code&gt;, &lt;code&gt;IX__values__Guid_filter&lt;/code&gt;, ...) — in the Postgres DDL they're commented out as "covered by composite." That's a strategy difference: the MSSQL optimizer is a bit more partial to narrow filtered indexes per type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the main point — &lt;strong&gt;architectural invariance is preserved&lt;/strong&gt;. Same 2+2+3+infrastructure layout, same facet schema, same fact that the index set doesn't grow when you add a class. To a developer writing C# models, the difference between Postgres and MSSQL is invisible.&lt;/p&gt;




&lt;h2&gt;
  
  
  TSUM as the proof case
&lt;/h2&gt;

&lt;p&gt;Bringing the numbers from the top of the post into one place.&lt;/p&gt;

&lt;p&gt;What REDB serves in TSUM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;32 classes in &lt;code&gt;tsum.Domain&lt;/code&gt; (from &lt;code&gt;TransportationRoute&lt;/code&gt; to &lt;code&gt;UserFilterPreference&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;401 properties across those classes,&lt;/li&gt;
&lt;li&gt;227,896 objects at the time these metrics were captured,&lt;/li&gt;
&lt;li&gt;9,773,174 rows in &lt;code&gt;_values&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This whole schema lands not in 13 tables per entity, but in &lt;strong&gt;2 data tables&lt;/strong&gt; (&lt;code&gt;_objects&lt;/code&gt; + &lt;code&gt;_values&lt;/code&gt;) + &lt;strong&gt;2 lookup tables&lt;/strong&gt; (&lt;code&gt;_lists&lt;/code&gt; + &lt;code&gt;_list_items&lt;/code&gt;) + &lt;strong&gt;3 RTTI tables&lt;/strong&gt; (&lt;code&gt;_types&lt;/code&gt; + &lt;code&gt;_schemes&lt;/code&gt; + &lt;code&gt;_structures&lt;/code&gt;). The remaining six are infrastructure — permissions, users, roles, links, dependencies, functions, soft-delete, metadata cache — invariant of business class count. The full index set is the same &lt;code&gt;redbPostgre.sql&lt;/code&gt; you'd ship for a five-class project. In a classical EF layout, those 32 classes with their lookups and snapshots would expand to &lt;strong&gt;roughly 60–80 tables&lt;/strong&gt; and &lt;strong&gt;200+ indexes&lt;/strong&gt;, with a migration history accumulating across the project's lifetime.&lt;/p&gt;

&lt;p&gt;Production metrics for a typical order-processing tick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;999 orders in one XML batch from SAP&lt;/strong&gt;, processed in &lt;strong&gt;991 ms&lt;/strong&gt; total:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sync = 482 ms&lt;/code&gt; — reconciling lookup dictionaries (Drivers, Vehicles, ShippingPoints, BusinessTypes, DeliveryStatuses) against REDB lists,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query = 139 ms&lt;/code&gt; — &lt;strong&gt;one&lt;/strong&gt; SELECT loading all 999 existing routes by their codes,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;save = 154 ms&lt;/code&gt; — &lt;code&gt;SaveAsync&lt;/code&gt; for &lt;strong&gt;32 changed&lt;/strong&gt; objects (2 created + 30 updated). The other 967 are short-circuited by &lt;code&gt;ComputeHash()&lt;/code&gt; before they reach the database.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Infrastructure — &lt;strong&gt;2 cores&lt;/strong&gt;, &lt;strong&gt;stock PostgreSQL settings&lt;/strong&gt; (no &lt;code&gt;shared_buffers&lt;/code&gt; tuning, no &lt;code&gt;pg_prewarm&lt;/code&gt;, no bumped &lt;code&gt;work_mem&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;No REDB-framework-level cache — every &lt;code&gt;Query&amp;lt;T&amp;gt;()&lt;/code&gt; round-trips to the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a hand-picked benchmark — it's a real production tick where a developer writes ordinary C# models with a &lt;code&gt;[RedbScheme]&lt;/code&gt; attribute and the logistics process just runs. Bulk on the read (one SELECT for 999 objects), bulk on the write (&lt;code&gt;_objects&lt;/code&gt; and &lt;code&gt;_values&lt;/code&gt; together in one transaction over the COPY protocol), early skip where data didn't change.&lt;/p&gt;

&lt;p&gt;The technical takeaway: &lt;strong&gt;REDB hits these numbers not through magic, but because the engine doesn't do unnecessary work&lt;/strong&gt;. The index plan is baked into the storage schema; bulk runs where bulk applies; application-level change tracking saves the most expensive thing — DML for changes that aren't there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap-up and what's coming next
&lt;/h2&gt;

&lt;p&gt;Short summary. REDB ships &lt;strong&gt;one&lt;/strong&gt; index set in DDL — about 50 indexes for the entire system. That set serves any business schema: 32 classes, 100 classes, 500 classes — same indexes. That's the inverse of the EF approach, where indexes multiply with tables and slowly become technical debt that needs periodic pruning.&lt;/p&gt;

&lt;p&gt;Operational pattern — once your application stabilizes, run &lt;code&gt;pg_stat_user_indexes&lt;/code&gt;, find the &lt;code&gt;idx_scan = 0&lt;/code&gt; candidates, and prune carefully (with &lt;code&gt;DROP INDEX CONCURRENTLY&lt;/code&gt; for safe rollback). Add your own local indexes for hot application-specific queries — in a separate migration, leaving the engine DDL alone.&lt;/p&gt;

&lt;p&gt;MSSQL is nearly the same picture. Minor differences in text indexing and per-type filtered indexes, but architectural invariance carries over.&lt;/p&gt;

&lt;p&gt;Coming up in the series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 2&lt;/strong&gt; — Code-first schemes: how &lt;code&gt;SyncSchemeAsync&amp;lt;T&amp;gt;&lt;/code&gt; turns a C# class into rows in &lt;code&gt;_schemes&lt;/code&gt; + &lt;code&gt;_structures&lt;/code&gt;, what &lt;code&gt;_structure_hash&lt;/code&gt; is, how automatic onboarding works when you add a new property.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3&lt;/strong&gt; — CRUD internals: &lt;code&gt;SaveAsync&lt;/code&gt; and &lt;code&gt;LoadAsync&lt;/code&gt; from inside, change tracking via TreeDiff, COPY-protocol bulk insert, lazy loading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4&lt;/strong&gt; — LINQ to SQL: how &lt;code&gt;Where(x =&amp;gt; x.Salary &amp;gt; 80000)&lt;/code&gt; becomes &lt;code&gt;CASE WHEN _id_structure = X THEN _Long END &amp;gt; 80000&lt;/code&gt;, how &lt;code&gt;OrderBy&lt;/code&gt; and window functions work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 5&lt;/strong&gt; — Object trees: &lt;code&gt;LoadTreeAsync&lt;/code&gt;, &lt;code&gt;GetDescendantsAsync&lt;/code&gt;, &lt;code&gt;WhereHasAncestor&lt;/code&gt;, recursive CTEs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 6&lt;/strong&gt; — Window functions: &lt;code&gt;Win.RowNumber()&lt;/code&gt;, &lt;code&gt;Win.Rank()&lt;/code&gt;, &lt;code&gt;PartitionBy/OrderBy&lt;/code&gt; over REDB objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deep C# dive is parts 3–5. In the storage layer, one topic remains — schema migrations — and that's where the next post picks up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/redbase-app" rel="noopener noreferrer"&gt;GitHub: redbase-app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;redb.Core repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/redbPostgre.sql" rel="noopener noreferrer"&gt;Postgres DDL (redbPostgre.sql)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/redbMSSQL.sql" rel="noopener noreferrer"&gt;MSSQL DDL (redbMSSQL.sql)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redbase.app/" rel="noopener noreferrer"&gt;Docs and samples (EN)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Earlier in the series (on dev.to)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482"&gt;An EF Core alternative for .NET apps with complex object graphs — full LINQ, no migrations, no DbContext&lt;/a&gt; — the wide-angle intro to RedBase: LINQ surface, production story, generated SQL.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redb-inside-part-1-the-13-tables-the-whole-engine-runs-on-with-the-actual-sql-and-why-its-not-18mf"&gt;REDB inside, part 1 — the 13 tables the whole engine runs on (with the actual SQL, and why it's not EAV)&lt;/a&gt; — the schema walkthrough this post builds on.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-301-flat-dsl-navigation-crtp-refactor-and-a-silent-null-fix-3m7n"&gt;redb.Route 3.0.1 — flat DSL navigation, CRTP refactor, and a silent null fix&lt;/a&gt; — separate cycle on the ESB framework that sits on top of redb.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>postgres</category>
      <category>sql</category>
      <category>opensource</category>
    </item>
    <item>
      <title>REDB inside, part 1 — the 13 tables the whole engine runs on (with the actual SQL, and why it's not EAV)</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Thu, 04 Jun 2026 18:49:10 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/redb-inside-part-1-the-13-tables-the-whole-engine-runs-on-with-the-actual-sql-and-why-its-not-18mf</link>
      <guid>https://dev.to/rinat_kozin/redb-inside-part-1-the-13-tables-the-whole-engine-runs-on-with-the-actual-sql-and-why-its-not-18mf</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4axqh7txqeiopwarxhkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4axqh7txqeiopwarxhkd.png" alt="REDB SQL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A couple of weeks ago I published &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482"&gt;the redb.Core intro post&lt;/a&gt; — what RedBase is at the API level, why I wrote it, what production looks like, the LINQ surface, what generated SQL looks like for nested dictionary lookups. If you haven't read it, start there — it's the wide-angle shot.&lt;/p&gt;

&lt;p&gt;This post starts a new series — &lt;strong&gt;"REDB inside"&lt;/strong&gt; — that drills down into the engine. One article per layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1 (this post) — the database schema. 13 tables, what each one does, why the design is what it is, and the SQL you'd run to dump any object flat.&lt;/li&gt;
&lt;li&gt;Part 2 — code-first schemes. How &lt;code&gt;SyncSchemeAsync&amp;lt;T&amp;gt;&lt;/code&gt; walks a C# class and turns it into rows in &lt;code&gt;_schemes&lt;/code&gt; + &lt;code&gt;_structures&lt;/code&gt;, the &lt;code&gt;_structure_hash&lt;/code&gt; mechanism, automatic onboarding via &lt;code&gt;InitializeAsync&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Part 3 — CRUD internals. &lt;code&gt;SaveAsync&lt;/code&gt;, &lt;code&gt;LoadAsync&lt;/code&gt;, the TreeDiff change-tracking algorithm, COPY-protocol bulk insert, lazy loading.&lt;/li&gt;
&lt;li&gt;Part 4 — LINQ-to-SQL. How &lt;code&gt;Where(x =&amp;gt; x.Salary &amp;gt; 80000)&lt;/code&gt; becomes &lt;code&gt;WHERE _id_structure = X AND _Long &amp;gt; 80000&lt;/code&gt;, the pivot CTE patterns, dialect differences (Postgres &lt;code&gt;array_agg FILTER&lt;/code&gt; vs MSSQL &lt;code&gt;MAX CASE WHEN&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Part 5 — trees. &lt;code&gt;LoadTreeAsync&lt;/code&gt;, &lt;code&gt;GetDescendantsAsync&lt;/code&gt;, &lt;code&gt;WhereHasAncestor&lt;/code&gt;, closure-table vs recursive CTE.&lt;/li&gt;
&lt;li&gt;Part 6 — window functions. &lt;code&gt;Win.RowNumber()&lt;/code&gt;, &lt;code&gt;Win.Rank()&lt;/code&gt;, &lt;code&gt;PartitionBy&lt;/code&gt;/&lt;code&gt;OrderBy&lt;/code&gt; over REDB objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one stands alone. You don't need to read them in order — but if you want to understand why anything in parts 2-6 works the way it does, &lt;strong&gt;you need this post&lt;/strong&gt;. Everything else is built on the 13 tables.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The whole RedBase engine runs on 13 tables. No JSON blob hiding the schema, no &lt;code&gt;NVARCHAR(MAX)&lt;/code&gt; catch-all column — every C# type lands in its own typed column. Let me show you how that works, and why this isn't classical EAV even though it kind of looks like it at first glance.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  "Wait, isn't this just EAV?"
&lt;/h2&gt;

&lt;p&gt;It's the most common reaction I get, and it deserves a real answer before we look at any DDL.&lt;/p&gt;

&lt;p&gt;Classical EAV (Entity–Attribute–Value) looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;object_id | attribute_name | value
----------|----------------|---------
42        | "FirstName"    | "Alice"
42        | "Age"          | "28"
42        | "Salary"       | "85000"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything in one table. Types erased. Attribute names are strings. Any non-trivial query becomes a self-join or a giant &lt;code&gt;PIVOT&lt;/code&gt;. The filter "employees earning over $80k" turns into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;attributes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;attribute_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Salary'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;80000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;-- runtime cast, no usable index&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add three more conditions and you're looking at three self-joins on the same table. Explain plans get embarrassing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;REDB doesn't do that.&lt;/strong&gt; Here's what &lt;code&gt;_values&lt;/code&gt; actually looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_id&lt;/span&gt;              &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_id_structure&lt;/span&gt;    &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- FK → _structures (which field this is)&lt;/span&gt;
    &lt;span class="n"&gt;_id_object&lt;/span&gt;       &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- FK → _objects&lt;/span&gt;
    &lt;span class="c1"&gt;-- typed value columns, exactly one is non-NULL per row:&lt;/span&gt;
    &lt;span class="n"&gt;_String&lt;/span&gt;          &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_Long&lt;/span&gt;            &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_Guid&lt;/span&gt;            &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_Double&lt;/span&gt;          &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;  &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_Boolean&lt;/span&gt;         &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_ByteArray&lt;/span&gt;       &lt;span class="n"&gt;bytea&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_Numeric&lt;/span&gt;         &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_ListItem&lt;/span&gt;        &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;-- FK → _list_items&lt;/span&gt;
    &lt;span class="n"&gt;_Object&lt;/span&gt;          &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;-- FK → _objects (cross-object reference)&lt;/span&gt;
    &lt;span class="c1"&gt;-- relational collection storage:&lt;/span&gt;
    &lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;-- FK → _values (parent element)&lt;/span&gt;
    &lt;span class="n"&gt;_array_index&lt;/span&gt;     &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;          &lt;span class="c1"&gt;-- '0','1','2' for arrays, key for dictionaries&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The field identity is a &lt;strong&gt;foreign key&lt;/strong&gt; (&lt;code&gt;_id_structure&lt;/code&gt;), not a string. The value lives in a &lt;strong&gt;typed column&lt;/strong&gt; chosen at write time based on the field's declared C# type. Think of it as &lt;strong&gt;runtime type information (RTTI)&lt;/strong&gt; persisted into the schema: the engine always knows what type each field is, because that's recorded once in &lt;code&gt;_structures._id_type&lt;/code&gt; and reused for every value of that field.&lt;/p&gt;

&lt;p&gt;Reading values back is one CASE expression dispatched on type, not a self-join:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_type&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'String'&lt;/span&gt;  &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Long'&lt;/span&gt;    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Guid'&lt;/span&gt;    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Guid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical differences:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Classical EAV&lt;/th&gt;
&lt;th&gt;REDB &lt;code&gt;_values&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Field identity&lt;/td&gt;
&lt;td&gt;string in &lt;code&gt;attribute_name&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;FK → &lt;code&gt;_structures&lt;/code&gt; → &lt;code&gt;_types&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the value lives&lt;/td&gt;
&lt;td&gt;one &lt;code&gt;text&lt;/code&gt;/&lt;code&gt;varchar(max)&lt;/code&gt; column&lt;/td&gt;
&lt;td&gt;typed column per C# type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter &lt;code&gt;Salary &amp;gt; 80000&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WHERE attribute_name='Salary' AND value::numeric &amp;gt; 80000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WHERE _id_structure = $1 AND _Long &amp;gt; 80000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index on the value&lt;/td&gt;
&lt;td&gt;string index + runtime cast&lt;/td&gt;
&lt;td&gt;partial B-tree index on the typed column&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrays/dictionaries&lt;/td&gt;
&lt;td&gt;separate table or JSON blob&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_array_parent_id&lt;/code&gt; + &lt;code&gt;_array_index&lt;/code&gt; in the same row&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema metadata&lt;/td&gt;
&lt;td&gt;implicit in attribute names&lt;/td&gt;
&lt;td&gt;first-class rows in &lt;code&gt;_schemes&lt;/code&gt;/&lt;code&gt;_structures&lt;/code&gt;/&lt;code&gt;_types&lt;/code&gt;, denormalized into a metadata cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So yes, the row shape rhymes with EAV — but the type system and the indexing story are completely different. That's why I've been pushing back on the EAV label.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 13 tables, at a glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_types          — type catalog (~37 system rows)
_schemes        — schemes (C# classes mapped to DB rows)
_structures     — fields of schemes (with nesting and collection metadata)
_objects        — objects (data rows, tree-shaped via self-FK)
_values         — field values (typed columns + relational collections)
_lists          — pick-list/dictionary catalog
_list_items     — pick-list entries
_users          — users (system IDs −1, 0, 1)
_roles          — roles
_users_roles    — M2M user ↔ role
_permissions    — permissions on objects (inherited along the tree)
_links          — M2M relations between objects
_functions      — stored expressions attached to schemes
_dependencies   — cross-scheme dependencies
─────────────────────────────────────────────
_scheme_metadata_cache   — denormalized cache of structures × types
_migrations              — history of props-schema migrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two live in their own SQL files but matter just as much in practice. Let's walk through them layer by layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1 — the type catalog: &lt;code&gt;_types&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;_types&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_id&lt;/span&gt;      &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_name&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_db_type&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- which _values column to use: 'Long', 'String', 'Guid', ...&lt;/span&gt;
    &lt;span class="n"&gt;_type&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;    &lt;span class="c1"&gt;-- C# type name: 'long', 'string', 'Guid', ...&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System type IDs are &lt;strong&gt;negative constants&lt;/strong&gt; near &lt;code&gt;long.MinValue&lt;/code&gt;. A small sample:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;_id&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;_name&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;_db_type&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;C# type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775709&lt;/td&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775708&lt;/td&gt;
&lt;td&gt;DateTime&lt;/td&gt;
&lt;td&gt;DateTimeOffset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DateTime&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775704&lt;/td&gt;
&lt;td&gt;Long&lt;/td&gt;
&lt;td&gt;Long&lt;/td&gt;
&lt;td&gt;&lt;code&gt;long&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775700&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775695&lt;/td&gt;
&lt;td&gt;Decimal&lt;/td&gt;
&lt;td&gt;Numeric&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decimal&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775675&lt;/td&gt;
&lt;td&gt;Class&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;nested class (marker only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775668&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;T[]&lt;/code&gt; / &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; (marker only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-9223372036854775667&lt;/td&gt;
&lt;td&gt;Dictionary&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Dictionary&amp;lt;K,V&amp;gt;&lt;/code&gt; (marker only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scary-looking numbers are just constants picked far from anything a user-generated key could ever hit (user IDs start at &lt;code&gt;1_000_000&lt;/code&gt; via a &lt;code&gt;global_identity&lt;/code&gt; sequence). &lt;code&gt;Class&lt;/code&gt;, &lt;code&gt;Array&lt;/code&gt;, and &lt;code&gt;Dictionary&lt;/code&gt; have &lt;strong&gt;no column of their own&lt;/strong&gt; in &lt;code&gt;_values&lt;/code&gt; — they're marker types; the actual leaves live in child rows.&lt;/p&gt;

&lt;p&gt;There are ~37 built-in types total. Numeric ones (&lt;code&gt;Int&lt;/code&gt;, &lt;code&gt;Short&lt;/code&gt;, &lt;code&gt;Byte&lt;/code&gt;, &lt;code&gt;Float&lt;/code&gt;) physically store as &lt;code&gt;Long&lt;/code&gt;/&lt;code&gt;Double&lt;/code&gt;. Strings include semantic variants (&lt;code&gt;Email&lt;/code&gt;, &lt;code&gt;Url&lt;/code&gt;, &lt;code&gt;Phone&lt;/code&gt;) that all use &lt;code&gt;_String&lt;/code&gt;. Then &lt;code&gt;DateOnly&lt;/code&gt;/&lt;code&gt;TimeOnly&lt;/code&gt;/&lt;code&gt;TimeSpan&lt;/code&gt;, geo (&lt;code&gt;Latitude&lt;/code&gt;/&lt;code&gt;Longitude&lt;/code&gt;), file metadata (&lt;code&gt;FilePath&lt;/code&gt;/&lt;code&gt;MimeType&lt;/code&gt;), &lt;code&gt;Enum&lt;/code&gt;/&lt;code&gt;EnumInt&lt;/code&gt;, and collection markers (&lt;code&gt;Array&lt;/code&gt;/&lt;code&gt;Dictionary&lt;/code&gt;/&lt;code&gt;JsonDocument&lt;/code&gt;/&lt;code&gt;XDocument&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2 — schemes and fields: &lt;code&gt;_schemes&lt;/code&gt; + &lt;code&gt;_structures&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;_schemes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_id&lt;/span&gt;             &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_id_parent&lt;/span&gt;      &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- nesting (namespaces)&lt;/span&gt;
    &lt;span class="n"&gt;_name&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- e.g. 'MyApp.Models.EmployeeProps'&lt;/span&gt;
    &lt;span class="n"&gt;_alias&lt;/span&gt;          &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_structure_hash&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;-- field hash for fast change detection&lt;/span&gt;
    &lt;span class="n"&gt;_type&lt;/span&gt;           &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;       &lt;span class="c1"&gt;-- FK → _types (Class by default)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;_structures&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_id&lt;/span&gt;              &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_id_parent&lt;/span&gt;       &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- nested props class&lt;/span&gt;
    &lt;span class="n"&gt;_id_scheme&lt;/span&gt;       &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- FK → _schemes&lt;/span&gt;
    &lt;span class="n"&gt;_id_type&lt;/span&gt;         &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- FK → _types&lt;/span&gt;
    &lt;span class="n"&gt;_id_list&lt;/span&gt;         &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- FK → _lists (for ListItem fields)&lt;/span&gt;
    &lt;span class="n"&gt;_name&lt;/span&gt;            &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- C# property name&lt;/span&gt;
    &lt;span class="n"&gt;_alias&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_order&lt;/span&gt;           &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_collection_type&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- NULL=scalar, Array_ID or Dictionary_ID&lt;/span&gt;
    &lt;span class="n"&gt;_key_type&lt;/span&gt;        &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- key type for Dictionary&amp;lt;K,V&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;_readonly&lt;/span&gt;        &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_allow_not_null&lt;/span&gt;  &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_is_compress&lt;/span&gt;     &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_store_null&lt;/span&gt;      &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_default_value&lt;/span&gt;   &lt;span class="n"&gt;bytea&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;Age&lt;/span&gt;                  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt;              &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]?&lt;/span&gt; &lt;span class="n"&gt;Skills&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HomeAddress&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// nested class&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and &lt;code&gt;await redb.SyncSchemeAsync&amp;lt;EmployeeProps&amp;gt;()&lt;/code&gt; fires for the first time, the engine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inserts a row into &lt;code&gt;_schemes&lt;/code&gt; with &lt;code&gt;_name = "MyApp.EmployeeProps"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Inserts one &lt;code&gt;_structures&lt;/code&gt; row per public property.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;Skills&lt;/code&gt;: sets &lt;code&gt;_collection_type = Array_ID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;HomeAddress&lt;/code&gt;: sets &lt;code&gt;_id_type = Class_ID&lt;/code&gt; and recursively creates child &lt;code&gt;_structures&lt;/code&gt; rows whose &lt;code&gt;_id_parent&lt;/code&gt; points back at the parent structure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then it computes a hash over all those structures and writes it to &lt;code&gt;_schemes._structure_hash&lt;/code&gt;. Next time you call sync, comparing one UUID tells the engine whether anything actually changed — no row-by-row diff needed.&lt;/p&gt;

&lt;p&gt;There's a &lt;strong&gt;DB-level trigger&lt;/strong&gt; that validates field names: no system-reserved (&lt;code&gt;_id&lt;/code&gt;, &lt;code&gt;_name&lt;/code&gt;, &lt;code&gt;_date_create&lt;/code&gt;), no C# keywords (&lt;code&gt;class&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;), no leading digits. If you accidentally try to name a property &lt;code&gt;int&lt;/code&gt;, the INSERT throws before the bad row ever lands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3 — objects: &lt;code&gt;_objects&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_id&lt;/span&gt;             &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_id_parent&lt;/span&gt;      &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- tree parent (self-FK)&lt;/span&gt;
    &lt;span class="n"&gt;_id_scheme&lt;/span&gt;      &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- FK → _schemes&lt;/span&gt;
    &lt;span class="n"&gt;_id_owner&lt;/span&gt;       &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- FK → _users&lt;/span&gt;
    &lt;span class="n"&gt;_id_who_change&lt;/span&gt;  &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- FK → _users&lt;/span&gt;
    &lt;span class="n"&gt;_date_create&lt;/span&gt;    &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_date_modify&lt;/span&gt;    &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_date_begin&lt;/span&gt;     &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_date_complete&lt;/span&gt;  &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_key&lt;/span&gt;            &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_name&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_note&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_hash&lt;/span&gt;           &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;-- value columns for RedbPrimitive&amp;lt;T&amp;gt;:&lt;/span&gt;
    &lt;span class="n"&gt;_value_long&lt;/span&gt;     &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_value_string&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_value_guid&lt;/span&gt;     &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_value_bool&lt;/span&gt;     &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_value_double&lt;/span&gt;   &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_value_numeric&lt;/span&gt;  &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_value_datetime&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_value_bytes&lt;/span&gt;    &lt;span class="n"&gt;bytea&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things worth pointing out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tree via &lt;code&gt;_id_parent&lt;/code&gt;&lt;/strong&gt; is &lt;code&gt;ON DELETE CASCADE&lt;/code&gt;. Drop a root, the whole subtree goes with it. Depth is unbounded. This is the &lt;strong&gt;primary organizational structure&lt;/strong&gt; in REDB: sections, categories, folders, org charts, project trees — they're all just &lt;code&gt;_objects&lt;/code&gt; rows pointing at a parent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;_value_*&lt;/code&gt; columns&lt;/strong&gt; are for &lt;code&gt;RedbPrimitive&amp;lt;T&amp;gt;&lt;/code&gt;. When an object is conceptually a single primitive (e.g. &lt;code&gt;RedbObject&amp;lt;long&amp;gt;&lt;/code&gt; for a counter, &lt;code&gt;RedbObject&amp;lt;string&amp;gt;&lt;/code&gt; for a token), there's no need to spin up &lt;code&gt;_values&lt;/code&gt; rows — the value rides in the object row itself. Eight columns, one per &lt;code&gt;_db_type&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soft delete&lt;/strong&gt; is a scheme called &lt;code&gt;@@__deleted&lt;/code&gt; (&lt;code&gt;_id = -10&lt;/code&gt;). &lt;code&gt;mark_for_deletion()&lt;/code&gt; walks the subtree via recursive CTE and atomically reparents everything under a trash container with &lt;code&gt;_id_scheme = -10&lt;/code&gt;. Actual physical deletion is a separate, batched &lt;code&gt;purge_trash()&lt;/code&gt;. This means you can offer "undelete" cheaply, and your data lake/CDC tools never see a destructive delete on the hot path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 4 — the values: &lt;code&gt;_values&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the table that earns its keep. Everything else exists to make this one fast and consistent.&lt;/p&gt;

&lt;p&gt;The DDL was up top. The interesting part is &lt;strong&gt;how collections fit into a flat row layout&lt;/strong&gt; without a side table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalar field
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Age = 28&lt;/code&gt; produces exactly one row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_id_structure=struct_Age   _id_object=42   _Long=28   _array_parent_id=NULL   _array_index=NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Array of primitives
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Skills = ["C#", "SQL", "React"]&lt;/code&gt; produces a &lt;strong&gt;marker row&lt;/strong&gt; plus one row per element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- marker: "the array property exists" (without it, the property is NULL, not [])&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;   &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;struct_Skills&lt;/span&gt;   &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;   &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;   &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;

&lt;span class="c1"&gt;-- elements; _array_parent_id points at the marker; _array_index is the position&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;   &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;struct_Skills&lt;/span&gt;   &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;   &lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"C#"&lt;/span&gt;     &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'0'&lt;/span&gt;   &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;   &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;struct_Skills&lt;/span&gt;   &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;   &lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"SQL"&lt;/span&gt;    &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;   &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;   &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;struct_Skills&lt;/span&gt;   &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;   &lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"React"&lt;/span&gt;  &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;   &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That marker row matters: it's how the engine tells &lt;code&gt;null&lt;/code&gt; (no marker, no elements) apart from &lt;code&gt;[]&lt;/code&gt; (marker present, zero children). The same shape works for empty dictionaries too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dictionary
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PhoneDir = { "home": "+7 999…", "work": "+7 495…" }&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- marker&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;   &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;struct_PhoneDir&lt;/span&gt;   &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;   &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;   &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;

&lt;span class="c1"&gt;-- entries; _array_index holds the dictionary key (as text)&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;   &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;struct_PhoneDir&lt;/span&gt;   &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;   &lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"+7 999..."&lt;/span&gt;   &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'home'&lt;/span&gt;   &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;   &lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;struct_PhoneDir&lt;/span&gt;   &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;   &lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"+7 495..."&lt;/span&gt;   &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'work'&lt;/span&gt;   &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_array_index&lt;/code&gt; is &lt;code&gt;text&lt;/code&gt; precisely so dictionaries with string keys work without a separate table. Numeric dictionaries store keys as their string representation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nested class
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;HomeAddress.City = "Moscow"&lt;/code&gt; works the same way. The &lt;code&gt;_structures&lt;/code&gt; rows for &lt;code&gt;Address.City&lt;/code&gt;, &lt;code&gt;Address.Street&lt;/code&gt;, etc. carry an &lt;code&gt;_id_parent&lt;/code&gt; pointing at the parent structure (&lt;code&gt;HomeAddress&lt;/code&gt;). The &lt;code&gt;_values&lt;/code&gt; rows for those leaves carry an &lt;code&gt;_array_parent_id&lt;/code&gt; pointing at the marker row for &lt;code&gt;HomeAddress&lt;/code&gt; on this particular object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three unique indexes keep all of this consistent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;UIX__values__structure_object&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;UIX__values__structure_object_parent&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;UIX__values__structure_object_array_index&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These three together guarantee: (a) at most one scalar/marker row per &lt;code&gt;(structure, object)&lt;/code&gt;, (b) at most one nested-class marker per &lt;code&gt;(structure, object, parent)&lt;/code&gt;, and (c) at most one element per &lt;code&gt;(structure, object, parent, index)&lt;/code&gt;. Try to insert a duplicate array element and the DB rejects it before any logic bug can corrupt the shape.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 5 — permissions: &lt;code&gt;_permissions&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;_permissions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_id&lt;/span&gt;      &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_id_role&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- XOR with _id_user (CHECK constraint)&lt;/span&gt;
    &lt;span class="n"&gt;_id_user&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_id_ref&lt;/span&gt;  &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- 0 = global, otherwise FK → _objects&lt;/span&gt;
    &lt;span class="n"&gt;_select&lt;/span&gt;  &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_insert&lt;/span&gt;  &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_update&lt;/span&gt;  &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_delete&lt;/span&gt;  &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions inherit along the object tree. A recursive CTE walks &lt;strong&gt;upwards from the target object&lt;/strong&gt; up to 50 levels looking for the nearest ancestor that has an explicit permission row. &lt;code&gt;_id_ref = 0&lt;/code&gt; is the global fallback ("can this principal touch anything at all?"). Resolution priority is: user &amp;gt; role, specific object &amp;gt; global.&lt;/p&gt;

&lt;p&gt;There's an automatic trigger that, when a child object is created without its own permission row, &lt;strong&gt;copies down&lt;/strong&gt; the resolved permission from the nearest ancestor. The point isn't to materialize every permission — it's to keep the recursive CTE short. After a few months of activity the depth the resolver has to climb stays roughly constant.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;_scheme_metadata_cache&lt;/code&gt; — why a cache table
&lt;/h2&gt;

&lt;p&gt;Every query needs to know: "for an object with &lt;code&gt;_id_scheme = X&lt;/code&gt;, which &lt;code&gt;_structures&lt;/code&gt; rows exist, and what's the type of each?" That's a JOIN through &lt;code&gt;_structures → _types&lt;/code&gt; that would otherwise fire &lt;strong&gt;on every single read&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So that JOIN is denormalized into a separate table that gets refreshed when the scheme changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;_scheme_metadata_cache&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_scheme_id&lt;/span&gt;           &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_structure_id&lt;/span&gt;        &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_parent_structure_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_name&lt;/span&gt;                &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;type_name&lt;/span&gt;            &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- 'Long', 'String', 'Guid', ...&lt;/span&gt;
    &lt;span class="n"&gt;db_type&lt;/span&gt;              &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- 'Long', 'String', 'Guid', ...&lt;/span&gt;
    &lt;span class="n"&gt;type_semantic&lt;/span&gt;        &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- 'Object', '_RObject', 'Array', ...&lt;/span&gt;
    &lt;span class="n"&gt;_collection_type&lt;/span&gt;     &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;collection_type_name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_key_type&lt;/span&gt;            &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;key_type_name&lt;/span&gt;        &lt;span class="nb"&gt;text&lt;/span&gt;
    &lt;span class="c1"&gt;-- ... all the other _structures attributes inlined&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A trigger on &lt;code&gt;_schemes._structure_hash&lt;/code&gt; invalidates the cache for that scheme; on the next read, &lt;code&gt;sync_metadata_cache_for_scheme(scheme_id)&lt;/code&gt; rebuilds it lazily. The big &lt;code&gt;build_hierarchical_properties_optimized()&lt;/code&gt; function — the one that materializes an object's full property tree into JSON — never JOINs &lt;code&gt;_structures&lt;/code&gt; or &lt;code&gt;_types&lt;/code&gt; directly. It reads from this cache, and only from this cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two SQL queries: dump any object flat
&lt;/h2&gt;

&lt;p&gt;Here are two queries that show exactly what's in the box for a given object. The first uses raw JOINs (use this for ad-hoc debugging in DataGrip/SSMS). The second uses &lt;code&gt;_scheme_metadata_cache&lt;/code&gt; — what the engine actually runs at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query 1 — flat pivot, no cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;scheme_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;                                         &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;type_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_db_type&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'String'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Long'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Guid'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Guid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Double'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Double&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Boolean'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Boolean&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'DateTimeOffset'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Numeric'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ListItem'&lt;/span&gt;       &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ListItem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Object'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Object&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ByteArray'&lt;/span&gt;      &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ByteArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'base64'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt;                       &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;value_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;     &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Array'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Dictionary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Class'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'collection_marker'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
         &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;     &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;                 &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'scalar'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;             &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'element['&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;']'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt;                                                 &lt;span class="s1"&gt;'scalar'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt;      &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_structures&lt;/span&gt;  &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_schemes&lt;/span&gt;     &lt;span class="n"&gt;s&lt;/span&gt;  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_types&lt;/span&gt;       &lt;span class="n"&gt;t&lt;/span&gt;  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_type&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_order&lt;/span&gt; &lt;span class="n"&gt;NULLS&lt;/span&gt; &lt;span class="k"&gt;LAST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="n"&gt;NULLS&lt;/span&gt; &lt;span class="k"&gt;FIRST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- MS SQL Server&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;scheme_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;                                         &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;type_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_db_type&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'String'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Long'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Guid'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Guid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Double'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Double&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Boolean'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Boolean&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'DateTimeOffset'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Numeric'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ListItem'&lt;/span&gt;       &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ListItem&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Object'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Object&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ByteArray'&lt;/span&gt;      &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;binary, base64 in app code&amp;gt;'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt;                       &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;value_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'scalar'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;                        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'element['&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;ISNULL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;']'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt;                                                            &lt;span class="s1"&gt;'scalar'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;_structures&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;_schemes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="n"&gt;s&lt;/span&gt;  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;_types&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;       &lt;span class="n"&gt;t&lt;/span&gt;  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_type&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;strong&gt;diagnostic&lt;/strong&gt; query — paste it into psql/DataGrip/SSMS, plug in any object ID, and you see exactly what's stored: which fields, which slot (scalar / collection marker / array element), which type. The marker rows for arrays show up too, which is exactly what you want when you're hunting down a &lt;code&gt;null&lt;/code&gt; vs &lt;code&gt;[]&lt;/code&gt; regression.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query 2 — same result via &lt;code&gt;_scheme_metadata_cache&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- PostgreSQL (via _scheme_metadata_cache)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_scheme_id&lt;/span&gt;                                      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;scheme_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;                                           &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type_name&lt;/span&gt;                                       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;type_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_type&lt;/span&gt;                                         &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;db_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collection_type_name&lt;/span&gt;                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;collection_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_type&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'String'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Long'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Guid'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Guid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Double'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Double&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Boolean'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Boolean&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'DateTimeOffset'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Numeric'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ListItem'&lt;/span&gt;       &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ListItem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Object'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Object&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ByteArray'&lt;/span&gt;      &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ByteArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'base64'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt;                       &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                               &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;value_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_order&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt;                &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_scheme_metadata_cache&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_structure_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_order&lt;/span&gt; &lt;span class="n"&gt;NULLS&lt;/span&gt; &lt;span class="k"&gt;LAST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="n"&gt;NULLS&lt;/span&gt; &lt;span class="k"&gt;FIRST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- MS SQL Server (via _scheme_metadata_cache)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_scheme_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;scheme_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                         &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;type_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                     &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;type_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;db_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;db_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;collection_type_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;collection_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;db_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'String'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Long'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Guid'&lt;/span&gt;           &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Guid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Double'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Double&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Boolean'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Boolean&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'DateTimeOffset'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Numeric'&lt;/span&gt;        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ListItem'&lt;/span&gt;       &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ListItem&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'Object'&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Object&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="s1"&gt;'ByteArray'&lt;/span&gt;      &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;binary&amp;gt;'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt;                       &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                               &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;value_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;_scheme_metadata_cache&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_structure_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_order&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why the second one is the production query:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Two heavy JOINs (&lt;code&gt;_structures&lt;/code&gt;, &lt;code&gt;_types&lt;/code&gt;) gone. The cache row carries everything those joins would have produced.&lt;/li&gt;
&lt;li&gt;The cache already has a B-tree on &lt;code&gt;_structure_id&lt;/code&gt; and a stable &lt;code&gt;_order&lt;/code&gt; for ordering — no extra sorts.&lt;/li&gt;
&lt;li&gt;The cache is refreshed only when the scheme changes (&lt;code&gt;_structure_hash&lt;/code&gt; flips), not on every read. Steady-state queries pay zero cost for it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build_hierarchical_properties_optimized()&lt;/code&gt; goes one step further: it slurps &lt;strong&gt;all &lt;code&gt;_values&lt;/code&gt; rows for one object into a &lt;code&gt;_values[]&lt;/code&gt; array in a single SELECT&lt;/strong&gt;, then walks the property tree purely in memory using &lt;code&gt;unnest()&lt;/code&gt;. Recursion into nested classes and array elements never touches the table again. For deeply nested object graphs this is a big deal — you can materialize a 40-row, 8-level-deep object with two SELECTs total.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How this connects to the C# API
&lt;/h2&gt;

&lt;p&gt;For context — what those tables look like from a &lt;code&gt;SaveAsync&lt;/code&gt;/&lt;code&gt;LoadAsync&lt;/code&gt; perspective. The full API tour is in &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482"&gt;the intro post&lt;/a&gt;; here's just the mapping back to the tables we just looked at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="n"&gt;LastName&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;     &lt;span class="n"&gt;Age&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]?&lt;/span&gt; &lt;span class="n"&gt;Skills&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// InitializeAsync scans the assembly →&lt;/span&gt;
&lt;span class="c1"&gt;//   - inserts/updates rows in _schemes + _structures for each [RedbScheme]&lt;/span&gt;
&lt;span class="c1"&gt;//   - refreshes _scheme_metadata_cache on changed schemes&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InitializeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// SaveAsync →&lt;/span&gt;
&lt;span class="c1"&gt;//   - one row into _objects&lt;/span&gt;
&lt;span class="c1"&gt;//   - one row per scalar into _values (using the typed column for the C# type)&lt;/span&gt;
&lt;span class="c1"&gt;//   - for Skills: one marker row + one row per element, linked via _array_parent_id&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Alice Johnson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EmployeeProps&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LastName&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Johnson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Age&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Salary&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;120_000m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Skills&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"C#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PostgreSQL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Redis"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;// → 1 marker + 3 element rows&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// LoadAsync →&lt;/span&gt;
&lt;span class="c1"&gt;//   - SELECT _objects WHERE _id = id&lt;/span&gt;
&lt;span class="c1"&gt;//   - SELECT * FROM _values WHERE _id_object = id  (one shot, into a _values[])&lt;/span&gt;
&lt;span class="c1"&gt;//   - build_hierarchical_properties_optimized() materializes the C# graph&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;loaded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SaveAsync&lt;/code&gt; reads &lt;code&gt;_scheme_metadata_cache&lt;/code&gt; to know which column to write each value to, mints IDs from the &lt;code&gt;global_identity&lt;/code&gt; sequence, and writes &lt;code&gt;_objects&lt;/code&gt; + &lt;code&gt;_values&lt;/code&gt;. &lt;code&gt;LoadAsync&lt;/code&gt; reads &lt;code&gt;_objects&lt;/code&gt; first, then &lt;strong&gt;one&lt;/strong&gt; SELECT pulls every &lt;code&gt;_values&lt;/code&gt; row for that object into a memory array, and the recursive materializer never goes back to the database for that load.&lt;/p&gt;




&lt;h2&gt;
  
  
  A few design choices that aren't obvious
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Negative constants for system IDs.&lt;/strong&gt; User-generated keys come from a sequence starting at &lt;code&gt;1_000_000&lt;/code&gt;. System types, schemes, users live near &lt;code&gt;long.MinValue&lt;/code&gt;. The two ranges can never collide. This means &lt;code&gt;_types._id = -10&lt;/code&gt; for the &lt;code&gt;@@__deleted&lt;/code&gt; scheme isn't a special-case in any query — it's just a perfectly normal FK that happens to be negative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;_structure_hash&lt;/code&gt; on &lt;code&gt;_schemes&lt;/code&gt;.&lt;/strong&gt; Without it, every &lt;code&gt;SyncSchemeAsync&amp;lt;T&amp;gt;&lt;/code&gt; call would have to re-read the schema structure and compare row-by-row. With it, comparing one UUID tells you whether anything changed. The cache-invalidation trigger fires only on real changes, so steady-state operation pays nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marker rows.&lt;/strong&gt; The trickiest design choice in &lt;code&gt;_values&lt;/code&gt;. There's no separate "collections" table — instead, a row with &lt;code&gt;_array_index = NULL AND _array_parent_id = NULL&lt;/code&gt; and a collection-typed &lt;code&gt;_id_structure&lt;/code&gt; is the marker, and child rows fan out from it via &lt;code&gt;_array_parent_id&lt;/code&gt;. This is what makes &lt;code&gt;null&lt;/code&gt; vs &lt;code&gt;[]&lt;/code&gt; distinguishable, lets dictionaries with string keys work without a side table, and keeps nested-class hierarchies in the same physical structure as flat fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;_Numeric NUMERIC(38, 18)&lt;/code&gt;.&lt;/strong&gt; Deliberate. &lt;code&gt;double&lt;/code&gt; for money quietly loses pennies; nobody wants a &lt;code&gt;0.0000000001&lt;/code&gt;-off invoice total in production. The 38/18 precision/scale is more than enough for currency, percentage, weight, even small molar quantities. The cost is storage size (16 bytes vs 8), which on the kind of property volume REDB sees is noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Postgres vs MSSQL — the cascade-delete story.&lt;/strong&gt; On Postgres, &lt;code&gt;_values&lt;/code&gt; has &lt;code&gt;ON DELETE CASCADE&lt;/code&gt; on its FK to &lt;code&gt;_structures&lt;/code&gt;. On MSSQL the same constraint would create multiple cascade paths and SQL Server refuses to compile that. The workaround: an &lt;code&gt;INSTEAD OF DELETE&lt;/code&gt; trigger on &lt;code&gt;_structures&lt;/code&gt; that manually cascades into &lt;code&gt;_values&lt;/code&gt;. Same observable behavior, different machinery. There are a handful of places in the codebase where the dialect abstraction exists specifically to paper over this kind of thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intro post&lt;/strong&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482"&gt;An EF Core alternative for .NET apps with complex object graphs&lt;/a&gt; &lt;em&gt;(published)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 1 (this post)&lt;/strong&gt; — the 13 tables, RTTI vs EAV, &lt;code&gt;_values&lt;/code&gt;, collection storage, &lt;code&gt;_scheme_metadata_cache&lt;/code&gt;, two diagnostic queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2&lt;/strong&gt; — code-first schemes: &lt;code&gt;SyncSchemeAsync&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;_structure_hash&lt;/code&gt;, automatic onboarding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3&lt;/strong&gt; — CRUD internals: &lt;code&gt;SaveAsync&lt;/code&gt;/&lt;code&gt;LoadAsync&lt;/code&gt;, TreeDiff change tracking, COPY-protocol bulk insert&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4&lt;/strong&gt; — LINQ → SQL: pivot CTEs, dialect splits, the &lt;code&gt;OfficeLocations["HQ"].City&lt;/code&gt; walkthrough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 5&lt;/strong&gt; — trees: &lt;code&gt;LoadTreeAsync&lt;/code&gt;, &lt;code&gt;WhereHasAncestor&lt;/code&gt;, the closure-table vs recursive-CTE story&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 6&lt;/strong&gt; — window functions: &lt;code&gt;Win.RowNumber()&lt;/code&gt;, &lt;code&gt;Win.Rank()&lt;/code&gt;, &lt;code&gt;PartitionBy&lt;/code&gt;/&lt;code&gt;OrderBy&lt;/code&gt; over REDB objects&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where to look
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/redbase-app" rel="noopener noreferrer"&gt;github.com/redbase-app&lt;/a&gt; — all repos in the ecosystem&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt; — the redb.Core repo&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/redbPostgre.sql" rel="noopener noreferrer"&gt;Postgres schema (redbPostgre.sql)&lt;/a&gt; — all 13 tables + 40+ indexes + triggers + stored functions in one file&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/redbMSSQL.sql" rel="noopener noreferrer"&gt;MSSQL schema (redbMSSQL.sql)&lt;/a&gt; — same shape, dialect-adjusted&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/" rel="noopener noreferrer"&gt;redbase.app&lt;/a&gt; — docs and worked examples (EN)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions/critique very welcome in the comments — especially if you've built something similar and have war stories about indexing strategies on the values table, that's exactly the discussion I want to have.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>postgres</category>
      <category>sql</category>
      <category>opensource</category>
    </item>
    <item>
      <title>redb.Route 3.0.1 — flat DSL navigation, CRTP refactor, and a silent null fix</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Wed, 03 Jun 2026 16:53:16 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/redbroute-301-flat-dsl-navigation-crtp-refactor-and-a-silent-null-fix-3m7n</link>
      <guid>https://dev.to/rinat_kozin/redbroute-301-flat-dsl-navigation-crtp-refactor-and-a-silent-null-fix-3m7n</guid>
      <description>&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; &lt;code&gt;redb ecosystem&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;Before 3.0.1, deep nested scopes required closing in exact reverse order — tedious and easy to get wrong. Three things changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flat &lt;code&gt;End*()&lt;/code&gt; navigation
&lt;/h2&gt;

&lt;p&gt;A typed closer walks the &lt;code&gt;Parent&lt;/code&gt; chain and exits to the right level in one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://demo-cascade-endchoice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* per-item work */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item=${body}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// walks past Split → When → lands at route root&lt;/span&gt;

    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"post-cascade"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cascade done"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Universal &lt;code&gt;.End()&lt;/code&gt; exits to the nearest scope without naming it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"b"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inside"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// closes RichLog → returns Split body&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="c1"&gt;// closes Split   → returns When body&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;// closes Choice  → returns route root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sibling branches open naturally after a closed sub-scope — &lt;code&gt;.When()&lt;/code&gt; and &lt;code&gt;.Otherwise()&lt;/code&gt; walk up to the enclosing &lt;code&gt;ChoiceDefinition&lt;/code&gt; via the &lt;code&gt;Parent&lt;/code&gt; chain, so this compiles as-is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndSplit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"list branch done [${routeId}]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// still on the When body, not on Split&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// ← sibling, works after EndSplit&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* fallback */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Three logging styles, same pipeline step
&lt;/h2&gt;

&lt;p&gt;The updated demo shows all three forms side by side — useful to see the tradeoffs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (A) Lambda — arbitrary C# at runtime&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[lambda] item=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; branch=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// (B) String template — compiled by the expression engine, zero alloc when level is off&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[tmpl] item=${body} branch=${header.branch} [${routeId}]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// (C) Rich-log scope — structured, multi-message, headers/properties as separate fields&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[rich-tmpl]   item=${body}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"[rich-lambda] upper=&lt;/span&gt;&lt;span class="p"&gt;{((&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;!).&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item-index"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShowRouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;${body}&lt;/code&gt;, &lt;code&gt;${header.x}&lt;/code&gt;, &lt;code&gt;${property.y}&lt;/code&gt;, &lt;code&gt;${routeId}&lt;/code&gt;, &lt;code&gt;${exception.type}&lt;/code&gt;, &lt;code&gt;${exception.message}&lt;/code&gt; — all resolved by the compiled expression engine (Tokenizer → Parser → AST → IL). The template compiles once, runs as a cached delegate. &lt;code&gt;.Message()&lt;/code&gt; in a rich-log scope accepts both forms simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  CRTP base — 27 duplicate class bodies removed
&lt;/h2&gt;

&lt;p&gt;Every leaf method (&lt;code&gt;To&lt;/code&gt;, &lt;code&gt;Process&lt;/code&gt;, &lt;code&gt;SetBody&lt;/code&gt;, &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Split&lt;/code&gt;, &lt;code&gt;Transaction&lt;/code&gt;, ~40 more) now lives once in &lt;code&gt;RouteDefinitionBase&amp;lt;TSelf&amp;gt;&lt;/code&gt;. Each returns &lt;code&gt;TSelf&lt;/code&gt; — chaining stays on the concrete scope type throughout. Public API and route AST identical to 3.0.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix: &lt;code&gt;GetContext()&lt;/code&gt; silently returned null inside nested scopes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IRouteDefinition.GetContext()&lt;/code&gt; was casting to &lt;code&gt;RouteDefinition&lt;/code&gt;, which only matched the route root. Inside &lt;code&gt;When&lt;/code&gt;, &lt;code&gt;Loop&lt;/code&gt;, &lt;code&gt;Traced&lt;/code&gt;, &lt;code&gt;Catch&lt;/code&gt; — it returned &lt;code&gt;null&lt;/code&gt; without throwing. Now walks &lt;code&gt;Parent&lt;/code&gt; to the owning route. Matters if you have extension methods that read context at DSL build time.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Full demo:&lt;/strong&gt; &lt;code&gt;DeepDslShowcaseRoutes.cs&lt;/code&gt; · &lt;strong&gt;Changelog:&lt;/strong&gt; &lt;a href="https://github.com/redbase-app/redb-route/blob/main/CHANGELOG.md#301--2026-06-03" rel="noopener noreferrer"&gt;3.0.1&lt;/a&gt; · Apache 2.0&lt;/p&gt;




</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>opensource</category>
      <category>new</category>
    </item>
    <item>
      <title>Enterprise Integration Patterns in .NET, the deep-dive series — Part 1: the four in-memory channels (and the Exchange they carry)</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Mon, 01 Jun 2026 21:51:33 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/enterprise-integration-patterns-in-net-the-deep-dive-series-part-1-the-four-in-memory-channels-3e24</link>
      <guid>https://dev.to/rinat_kozin/enterprise-integration-patterns-in-net-the-deep-dive-series-part-1-the-four-in-memory-channels-3e24</guid>
      <description>&lt;p&gt;The earlier posts were the tour: what redb.Route is, an Apache Camel-style ESB for .NET — a fluent C# integration DSL with &lt;strong&gt;22 connector projects&lt;/strong&gt; (~30 URI schemes once you count the &lt;code&gt;https&lt;/code&gt;/&lt;code&gt;wss&lt;/code&gt;/&lt;code&gt;es&lt;/code&gt; variants), &lt;strong&gt;~30 Enterprise Integration Patterns&lt;/strong&gt; implemented natively across &lt;strong&gt;41 processors&lt;/strong&gt;, &lt;strong&gt;8 in-process components&lt;/strong&gt;, a compiled expression engine, and a pluggable marshal/unmarshal layer. Enough touring.&lt;/p&gt;

&lt;p&gt;This starts a &lt;strong&gt;deep-dive series&lt;/strong&gt; — one piece at a time, the actual DSL, the actual semantics, the gotchas that bite you the first time. Not a feature list. A working manual. The series runs on four parallel tracks; you can follow whichever maps to the problem in front of you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Track A — the in-process foundation (8 components)
&lt;/h3&gt;

&lt;p&gt;The wires and the message model everything else stands on. Small surface, deep behavior.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The four channels + the &lt;code&gt;Exchange&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;direct&lt;/code&gt;, &lt;code&gt;direct-vm&lt;/code&gt;, &lt;code&gt;seda&lt;/code&gt;, &lt;code&gt;vm&lt;/code&gt;, and the object they carry. &lt;em&gt;(this post — the foundation)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling &amp;amp; test components&lt;/strong&gt; — &lt;code&gt;timer&lt;/code&gt;, plus &lt;code&gt;quartz&lt;/code&gt;/&lt;code&gt;cron&lt;/code&gt; built on the same idea, and the &lt;code&gt;log&lt;/code&gt; / &lt;code&gt;mock&lt;/code&gt; / &lt;code&gt;validator&lt;/code&gt; components you'll actually lean on in tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Track B — the Enterprise Integration Patterns (~30, across 41 processors)
&lt;/h3&gt;

&lt;p&gt;Grouped the way Hohpe &amp;amp; Woolf's &lt;em&gt;Enterprise Integration Patterns&lt;/em&gt; groups them, one article per cluster, each dissected against the shipped processor. (The codebase has 41 processors total; a handful — &lt;code&gt;To&lt;/code&gt;, &lt;code&gt;Log&lt;/code&gt;, &lt;code&gt;Delegate&lt;/code&gt;, &lt;code&gt;RoutePolicy&lt;/code&gt; — are plumbing rather than named patterns, so the honest count of distinct EIP is around thirty plus the 6 load-balancer strategies.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Message Routing&lt;/strong&gt; — &lt;code&gt;Choice&lt;/code&gt; (Content-Based Router), &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Splitter&lt;/code&gt; (+ streaming splitter), &lt;code&gt;Aggregator&lt;/code&gt;, &lt;code&gt;Resequencer&lt;/code&gt;, &lt;code&gt;Multicast&lt;/code&gt;, &lt;code&gt;RecipientList&lt;/code&gt;, &lt;code&gt;ScatterGather&lt;/code&gt;, &lt;code&gt;DynamicRouter&lt;/code&gt;, &lt;code&gt;LoadBalance&lt;/code&gt; (Round-Robin / Random / Weighted / Sticky / Failover).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message Transformation&lt;/strong&gt; — &lt;code&gt;Marshal&lt;/code&gt;/&lt;code&gt;Unmarshal&lt;/code&gt; and the data-format registry, &lt;code&gt;ConvertBody&lt;/code&gt;, &lt;code&gt;Enrich&lt;/code&gt;/&lt;code&gt;PollEnrich&lt;/code&gt; (Content Enricher), &lt;code&gt;ClaimCheck&lt;/code&gt;, &lt;code&gt;StreamCaching&lt;/code&gt;, &lt;code&gt;Normalize&lt;/code&gt;, and &lt;code&gt;Transform&lt;/code&gt;/&lt;code&gt;SetBody&lt;/code&gt;/&lt;code&gt;SetHeader&lt;/code&gt; over the expression engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messaging Endpoints&lt;/strong&gt; — &lt;code&gt;IdempotentConsumer&lt;/code&gt;, &lt;code&gt;WireTap&lt;/code&gt;, competing consumers (&lt;code&gt;seda&lt;/code&gt;/&lt;code&gt;vm&lt;/code&gt; &lt;code&gt;concurrentConsumers&lt;/code&gt;), &lt;code&gt;Pipeline&lt;/code&gt;, request/reply (&lt;code&gt;InOut&lt;/code&gt;), &lt;code&gt;Bean&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Management &amp;amp; Reliability&lt;/strong&gt; — &lt;code&gt;OnException&lt;/code&gt; / &lt;code&gt;TryCatch&lt;/code&gt; / &lt;code&gt;DeadLetterChannel&lt;/code&gt;, &lt;code&gt;CircuitBreaker&lt;/code&gt;, &lt;code&gt;Throttle&lt;/code&gt; / &lt;code&gt;KeyedThrottle&lt;/code&gt;, &lt;code&gt;Delay&lt;/code&gt; / &lt;code&gt;Debounce&lt;/code&gt; / &lt;code&gt;Sampling&lt;/code&gt;, &lt;code&gt;Timeout&lt;/code&gt;, &lt;code&gt;Loop&lt;/code&gt;, &lt;code&gt;Saga&lt;/code&gt; (+ compensation), &lt;code&gt;RoutePolicy&lt;/code&gt;, &lt;code&gt;Transaction&lt;/code&gt;/&lt;code&gt;Transacted&lt;/code&gt;, and &lt;code&gt;Validate&lt;/code&gt; (JSON Schema / XSD).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Track C — the expression &amp;amp; predicate engine (its own article, and it earns it)
&lt;/h3&gt;

&lt;p&gt;The one piece every other track quietly depends on. I almost wrote it off as string interpolation in the roadmap — then I read the source. It's a genuine &lt;strong&gt;compiled little language&lt;/strong&gt;: &lt;code&gt;Tokenizer&lt;/code&gt; → &lt;code&gt;Parser&lt;/code&gt; → an AST (&lt;code&gt;BinaryOperationNode&lt;/code&gt;, &lt;code&gt;UnaryOperationNode&lt;/code&gt;, &lt;code&gt;FunctionCallNode&lt;/code&gt;, &lt;code&gt;TernaryNode&lt;/code&gt;, &lt;code&gt;PostfixOperationNode&lt;/code&gt;, property/index access), lowered to &lt;code&gt;System.Linq.Expressions&lt;/code&gt; and &lt;code&gt;.Compile()&lt;/code&gt;d to real IL — not tree-walked at runtime. There's a dedicated article because there's a dedicated language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;${...}&lt;/code&gt; Simple-style template&lt;/strong&gt; isn't formatting — it's the entry point to the whole grammar: arithmetic (&lt;code&gt;+ - * /&lt;/code&gt;), comparison, &lt;code&gt;AND&lt;/code&gt;/&lt;code&gt;OR&lt;/code&gt;/&lt;code&gt;XOR&lt;/code&gt;/&lt;code&gt;NOT&lt;/code&gt;, the &lt;strong&gt;ternary &lt;code&gt;?:&lt;/code&gt;&lt;/strong&gt;, even &lt;strong&gt;postfix &lt;code&gt;++&lt;/code&gt;/&lt;code&gt;--&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~22 built-in functions&lt;/strong&gt;, mined from the AST's &lt;code&gt;FunctionCallNode&lt;/code&gt;: string (&lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;upper&lt;/code&gt;, &lt;code&gt;lower&lt;/code&gt;, &lt;code&gt;trim&lt;/code&gt;, &lt;code&gt;substring&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;length&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;startswith&lt;/code&gt;, &lt;code&gt;endswith&lt;/code&gt;), numeric (&lt;code&gt;abs&lt;/code&gt;, &lt;code&gt;round&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;), &lt;strong&gt;aggregates&lt;/strong&gt; (&lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;avg&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;), date math (&lt;code&gt;now&lt;/code&gt;, &lt;code&gt;dateformat&lt;/code&gt;, &lt;code&gt;dateadd&lt;/code&gt;), and the two that bridge into the structured world: &lt;code&gt;jpath(...)&lt;/code&gt; and &lt;code&gt;xpath(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSONPath and XPath&lt;/strong&gt; as first-class expression types — typed (&lt;code&gt;TypedJsonPathExpression&lt;/code&gt;, &lt;code&gt;TypedXPathExpression&lt;/code&gt;) and &lt;strong&gt;precompiled&lt;/strong&gt; (&lt;code&gt;CompiledJPathExpression&lt;/code&gt;, &lt;code&gt;CompiledXPathExpression&lt;/code&gt;) when the path itself is static.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The predicate builders&lt;/strong&gt; — the fluent half, for &lt;code&gt;.Filter&lt;/code&gt;/&lt;code&gt;.Choice&lt;/code&gt;/&lt;code&gt;.Validate&lt;/code&gt;: &lt;code&gt;isEqualTo&lt;/code&gt;, &lt;code&gt;isNotEqualTo&lt;/code&gt;, &lt;code&gt;isGreaterThan&lt;/code&gt;/&lt;code&gt;isLessThan&lt;/code&gt; (&lt;code&gt;…OrEqualTo&lt;/code&gt;), &lt;code&gt;isBetween&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;startsWith&lt;/code&gt;, &lt;code&gt;endsWith&lt;/code&gt;, &lt;code&gt;regex&lt;/code&gt;, &lt;code&gt;In(...)&lt;/code&gt;, &lt;code&gt;isNull&lt;/code&gt;/&lt;code&gt;isNotNull&lt;/code&gt;, composed with &lt;code&gt;and&lt;/code&gt;/&lt;code&gt;or&lt;/code&gt;/&lt;code&gt;not&lt;/code&gt;. Seventeen of them, each a real &lt;code&gt;IPredicate&lt;/code&gt;, not a closure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four separate &lt;code&gt;ConcurrentDictionary&lt;/code&gt; compile-and-cache pools&lt;/strong&gt; (template / property-resolver / logical / value) so every one of the above compiles &lt;strong&gt;once&lt;/strong&gt; per distinct expression string and runs as a cached delegate forever after.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why it's its own installment, and honestly why it might be two: the template grammar and the predicate DSL are different front-ends onto the same compiler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Track D — the 22 connectors, one article each
&lt;/h3&gt;

&lt;p&gt;One focused article per connector, every example &lt;strong&gt;lifted from real production routes&lt;/strong&gt;, not toy snippets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brokers &amp;amp; messaging&lt;/strong&gt; — &lt;code&gt;kafka&lt;/code&gt;, &lt;code&gt;rabbitmq&lt;/code&gt;, &lt;code&gt;amqp&lt;/code&gt;, &lt;code&gt;asb&lt;/code&gt; (Azure Service Bus), &lt;code&gt;wmq&lt;/code&gt; (IBM MQ), &lt;code&gt;mqtt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RPC &amp;amp; web&lt;/strong&gt; — &lt;code&gt;http&lt;/code&gt;/&lt;code&gt;https&lt;/code&gt;, &lt;code&gt;grpc&lt;/code&gt;, &lt;code&gt;signalr&lt;/code&gt;, &lt;code&gt;ws&lt;/code&gt;/&lt;code&gt;wss&lt;/code&gt;, &lt;code&gt;tcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data &amp;amp; storage&lt;/strong&gt; — &lt;code&gt;sql&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt;, &lt;code&gt;elasticsearch&lt;/code&gt;, &lt;code&gt;s3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files &amp;amp; transfer&lt;/strong&gt; — &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;ftp&lt;/code&gt;, &lt;code&gt;sftp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise &amp;amp; misc&lt;/strong&gt; — &lt;code&gt;smtp&lt;/code&gt;/&lt;code&gt;pop3&lt;/code&gt;/&lt;code&gt;imap&lt;/code&gt; (mail), &lt;code&gt;ldap&lt;/code&gt;, &lt;code&gt;fcm&lt;/code&gt;/&lt;code&gt;fstore&lt;/code&gt;/&lt;code&gt;fbstorage&lt;/code&gt; (Firebase), &lt;code&gt;qtimer&lt;/code&gt;/&lt;code&gt;cron&lt;/code&gt; (Quartz).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We start with Track A on purpose. Every pattern in Track B, every expression in Track C, and every connector in Track D is built on two things: a &lt;strong&gt;channel&lt;/strong&gt; that carries a message from one route segment to the next, and the &lt;strong&gt;&lt;code&gt;Exchange&lt;/code&gt;&lt;/strong&gt; that &lt;em&gt;is&lt;/em&gt; the message. Get these two right and the rest of the series is just composition. Get them wrong and you'll spend an afternoon wondering why your transaction silently didn't roll back.&lt;/p&gt;

&lt;p&gt;A pipeline in redb.Route is &lt;code&gt;From → [processors] → To&lt;/code&gt;. Let's look at what actually flows through it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Exchange — the heart, and it isn't simple
&lt;/h2&gt;

&lt;p&gt;Everything that moves through a route is an &lt;code&gt;IExchange&lt;/code&gt;. Not a &lt;code&gt;byte[]&lt;/code&gt;, not your DTO — an &lt;code&gt;Exchange&lt;/code&gt;, a small object with more going on inside it than the name suggests. Here's the shape that matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IExchange&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAsyncDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IMessage&lt;/span&gt;  &lt;span class="n"&gt;In&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// the primary message — always present&lt;/span&gt;
    &lt;span class="n"&gt;IMessage&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Out&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// the reply — lazy, null until you need it&lt;/span&gt;
    &lt;span class="n"&gt;ExchangePattern&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// InOnly (default), InOut, or OutOnly&lt;/span&gt;

    &lt;span class="n"&gt;IDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// route-level metadata&lt;/span&gt;
    &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                 &lt;span class="c1"&gt;// error state, in-band&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ExceptionHandled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ExchangeId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                         &lt;span class="c1"&gt;// identity, stable across clones&lt;/span&gt;

    &lt;span class="n"&gt;IExchange&lt;/span&gt; &lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                                 &lt;span class="c1"&gt;// deep-ish copy + NEW DI scope&lt;/span&gt;
    &lt;span class="n"&gt;IServiceProvider&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ServiceProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;         &lt;span class="c1"&gt;// per-exchange DI scope&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the message it carries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IMessage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                        &lt;span class="c1"&gt;// your payload — any object, or null&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;IDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// metadata that DOES travel to brokers&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;IMessage&lt;/span&gt; &lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five things about this object decide how every channel and every pattern behaves. None of them are obvious from the type signature.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;In&lt;/code&gt; vs &lt;code&gt;Out&lt;/code&gt;, and the &lt;code&gt;Pattern&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;In&lt;/code&gt; is the message coming through. &lt;code&gt;Out&lt;/code&gt; is the reply, and it's &lt;strong&gt;lazy&lt;/strong&gt; — for the default &lt;code&gt;InOnly&lt;/code&gt; pattern it stays &lt;code&gt;null&lt;/code&gt; and is never allocated. It only appears when a processor explicitly sets it (request/reply, or &lt;code&gt;.Respond()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;three&lt;/strong&gt; patterns, not two — this is the Apache Camel 2.x model, ported wholesale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ExchangePattern&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;InOnly&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// fire-and-forget. Producer result is written to In; Out stays null. (default)&lt;/span&gt;
    &lt;span class="n"&gt;InOut&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// request/reply. Original preserved in In, response written to Out.&lt;/span&gt;
    &lt;span class="n"&gt;OutOnly&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// explicit response via .Respond(); the RPC reply is taken from Out.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the part the type signature hides, and the one place people get it wrong: &lt;strong&gt;&lt;code&gt;HasOut&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; tell you where the answer is.&lt;/strong&gt; Even on an &lt;code&gt;InOut&lt;/code&gt; exchange, a processor isn't obligated to populate &lt;code&gt;Out&lt;/code&gt; — if it just mutates &lt;code&gt;In.Body&lt;/code&gt;, the result lives in &lt;code&gt;In&lt;/code&gt;. So the framework never trusts &lt;code&gt;HasOut&lt;/code&gt; to find a reply. It reads &lt;strong&gt;&lt;code&gt;Out ?? In&lt;/code&gt;&lt;/strong&gt;, every time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ProducerTemplate.RequestBody — the canonical reply-extraction rule&lt;/span&gt;
&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExchangePattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InOut&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Out if present, otherwise In&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is verbatim how Camel's &lt;code&gt;ProducerTemplate&lt;/code&gt; extracts a result (&lt;code&gt;getResultMessage&lt;/code&gt;: "has Out → use Out, else In"). Copy that rule into your own code — &lt;code&gt;exchange.Out ?? exchange.In&lt;/code&gt; — and you'll never chase a reply that quietly stayed in &lt;code&gt;In&lt;/code&gt;. &lt;code&gt;HasOut&lt;/code&gt; is a fact about &lt;em&gt;allocation&lt;/em&gt;, not about &lt;em&gt;where the data is&lt;/em&gt;; don't use it to route on the answer.&lt;/p&gt;

&lt;p&gt;One honesty note for the JVM crowd: the live &lt;code&gt;Out&lt;/code&gt; message and the &lt;code&gt;OutOnly&lt;/code&gt; pattern are &lt;strong&gt;Camel 2.x semantics.&lt;/strong&gt; Camel 3+ deprecated &lt;code&gt;getOut()&lt;/code&gt;/&lt;code&gt;setOut()&lt;/code&gt; and collapsed the pattern set toward &lt;code&gt;InOnly&lt;/code&gt;/&lt;code&gt;InOut&lt;/code&gt;, precisely because a separate Out message copied headers and bred subtle bugs. redb.Route keeps the fuller 2.x model on purpose — but if you're coming from modern Camel, that's the difference you'll notice first.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;Properties&lt;/code&gt; vs &lt;code&gt;Headers&lt;/code&gt; — the distinction that leaks bugs
&lt;/h3&gt;

&lt;p&gt;Both are &lt;code&gt;IDictionary&amp;lt;string, object?&amp;gt;&lt;/code&gt;. They are &lt;em&gt;not&lt;/em&gt; interchangeable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;In.Headers&lt;/code&gt;&lt;/strong&gt; travel &lt;strong&gt;with the message to the broker.&lt;/strong&gt; Put a &lt;code&gt;correlationId&lt;/code&gt; here and Kafka/RabbitMQ carry it downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;exchange.Properties&lt;/code&gt;&lt;/strong&gt; are &lt;strong&gt;route-level metadata&lt;/strong&gt; — &lt;code&gt;RouteId&lt;/code&gt;, transaction markers, your own scratch state. They &lt;strong&gt;do not&lt;/strong&gt; leave the process (the interface XML doc says exactly that: &lt;em&gt;"Does NOT travel to brokers — use In.Headers for that"&lt;/em&gt;). Stash a &lt;code&gt;DbContext&lt;/code&gt; handle or a retry counter here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put a value in the wrong dictionary and it either fails to reach the consumer (you used Properties) or leaks internal state onto the wire (you used Headers). The compiler won't catch it; both are just string-keyed dictionaries. Knowing which is which is half of using the framework correctly.&lt;/p&gt;

&lt;p&gt;Read either with the typed accessors instead of casting by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"retryCount"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// route-level, stays in-process&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;corr&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"correlationId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// travels to the broker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The well-known keys the framework itself writes
&lt;/h4&gt;

&lt;p&gt;This is the part that makes the section concrete, and it's the answer to "what's actually &lt;em&gt;in&lt;/em&gt; Properties?" The pipeline and the processors populate a set of reserved keys as your exchange flows. There is no single &lt;code&gt;ExchangeProperties&lt;/code&gt; constants file — they live next to the processor that owns each one — but here is the real registry, mined from source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;exchange.Properties&lt;/code&gt; — route-level, never leave the process:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Constant in code&lt;/th&gt;
&lt;th&gt;Written by&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TRANSACT_ACTION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TransactedProcessor.TransactActionPropertyKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.Transacted()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the transacted-action stack for synchronization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TRANSACTION_SCOPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BeginTransactionProcessor.ScopePropertyKey&lt;/code&gt; (internal)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.Transaction()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the live &lt;code&gt;TransactionScope&lt;/code&gt; for the block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CamelDuplicateMessage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IdempotentConsumerProcessor.DuplicatePropertyKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Idempotent Consumer&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt; when the message was seen before&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ClaimCheck.Stack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ClaimCheckHeaders.StackPropertyKey&lt;/code&gt; (internal)&lt;/td&gt;
&lt;td&gt;Claim Check&lt;/td&gt;
&lt;td&gt;the stack of stored payload keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ValidationErrors&lt;/code&gt; / &lt;code&gt;ValidationResult&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ValidateProcessor.*Property&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.Validate()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;validation outcome for the current exchange&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CamelSplitSize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;— (streaming splitter)&lt;/td&gt;
&lt;td&gt;streaming Splitter&lt;/td&gt;
&lt;td&gt;running count of split parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__redb_scope:*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;— (prefix)&lt;/td&gt;
&lt;td&gt;DI plumbing&lt;/td&gt;
&lt;td&gt;named child DI scopes, freed by &lt;code&gt;ReleaseScopes()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus &lt;code&gt;RouteId&lt;/code&gt; is promoted to a &lt;strong&gt;first-class property&lt;/strong&gt; on the exchange (&lt;code&gt;exchange.RouteId&lt;/code&gt;), not just a dictionary entry — that's what the logger prints as &lt;code&gt;[rId:…]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;In.Headers&lt;/code&gt; — travel with the message:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Camel-compatible message headers come from the same world Camel users expect. The Splitter, for example, stamps every part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SplitterProcessor — each split message carries its coordinates&lt;/span&gt;
&lt;span class="n"&gt;splitMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CamelSplitIndex"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// 0-based position&lt;/span&gt;
&lt;span class="n"&gt;splitMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CamelSplitSize"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// batch size&lt;/span&gt;
&lt;span class="n"&gt;splitMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CamelSplitComplete"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// last one?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and every transport contributes its own namespaced header constants — &lt;code&gt;KafkaHeaders&lt;/code&gt; (&lt;code&gt;redbKafka.Topic&lt;/code&gt;, &lt;code&gt;redbKafka.Partition&lt;/code&gt;, &lt;code&gt;redbKafka.Offset&lt;/code&gt;, …), &lt;code&gt;SqlHeaders&lt;/code&gt; (&lt;code&gt;redbSql.rowCount&lt;/code&gt;, &lt;code&gt;redbSql.generatedKeys&lt;/code&gt;, …), &lt;code&gt;SignalRHeaders&lt;/code&gt; (&lt;code&gt;redbSignalR.ConnectionId&lt;/code&gt;, …), &lt;code&gt;TcpHeaders&lt;/code&gt;, &lt;code&gt;WsHeaders&lt;/code&gt;, &lt;code&gt;ElasticsearchHeaders&lt;/code&gt;. Each is a &lt;code&gt;static class&lt;/code&gt; of &lt;code&gt;public const string&lt;/code&gt; so you bind against &lt;code&gt;KafkaHeaders.Offset&lt;/code&gt;, not a stringly-typed &lt;code&gt;"redbKafka.Offset"&lt;/code&gt; you might misspell. The rule of thumb: anything prefixed &lt;code&gt;Camel*&lt;/code&gt; or &lt;code&gt;redb&amp;lt;Transport&amp;gt;.*&lt;/code&gt; is a header (on the wire); anything in &lt;code&gt;Properties&lt;/code&gt; is yours and the process's alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The exception travels &lt;em&gt;in-band&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;When a processor throws, the exception doesn't just unwind the stack — it's captured onto &lt;code&gt;exchange.Exception&lt;/code&gt;, with an &lt;code&gt;ExceptionHandled&lt;/code&gt; flag beside it. That's what makes a dead-letter route able to branch on &lt;em&gt;why&lt;/em&gt; something failed (&lt;code&gt;when e.Exception is TimeoutException → …&lt;/code&gt;) instead of just &lt;em&gt;that&lt;/em&gt; it failed. The error becomes data you can route on. We lean on this hard in the error-handling installment.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;ExchangeId&lt;/code&gt; is stable across clones
&lt;/h3&gt;

&lt;p&gt;Each exchange gets a &lt;code&gt;Guid&lt;/code&gt;-based &lt;code&gt;ExchangeId&lt;/code&gt; at creation. The non-obvious part: &lt;strong&gt;&lt;code&gt;Clone()&lt;/code&gt; preserves it.&lt;/strong&gt; A split into 500 parts, or a &lt;code&gt;seda&lt;/code&gt; hop that clones the exchange, keeps the same id — so your logs and traces stitch the whole flow back to one origin. Identity survives copying; that's deliberate.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The DI scope — and four ways to copy an exchange
&lt;/h3&gt;

&lt;p&gt;This is the part that's genuinely not simple, and it's the reason the channels behave the way they do.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;Exchange&lt;/code&gt; can own a &lt;strong&gt;DI scope&lt;/strong&gt; — a per-message &lt;code&gt;IServiceScope&lt;/code&gt;. Processors resolve scoped services (&lt;code&gt;DbContext&lt;/code&gt;, &lt;code&gt;IRedbService&lt;/code&gt;, …) from &lt;code&gt;exchange.ServiceProvider&lt;/code&gt;, and they get the &lt;em&gt;same&lt;/em&gt; instances for the lifetime of that exchange. A &lt;code&gt;TransactionScope&lt;/code&gt; lives in exactly that scope. So the question "are these two exchanges in the same transaction?" reduces to "do they share a DI scope?"&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;four&lt;/strong&gt; ways an exchange gets copied, and they differ &lt;em&gt;only&lt;/em&gt; in what they do with that scope:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Body/Headers&lt;/th&gt;
&lt;th&gt;DI scope&lt;/th&gt;
&lt;th&gt;Owns scope?&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Clone()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;copied&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;new scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;hand-off to another thread (&lt;code&gt;seda&lt;/code&gt;, &lt;code&gt;vm&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CloneLinked()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;copied&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;shares parent's&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;parallel fan-out inside the parent's transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CreateChild(msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;new message&lt;/td&gt;
&lt;td&gt;new scope&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;a derived exchange, independent lifetime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CreateLinkedChild(msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;new message&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;shares parent's&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;sequential children reusing the same connection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from Exchange.Clone() — the scope-creating branch&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_scopeFactory&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_scopeFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- a brand-new scope&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from Exchange.CloneLinked() — the scope-sharing branch&lt;/span&gt;
&lt;span class="n"&gt;_ownsScope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;_scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// &amp;lt;-- the SAME scope, and we won't dispose it&lt;/span&gt;
&lt;span class="n"&gt;_scopeFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hold onto that table. The entire transaction story of &lt;code&gt;seda&lt;/code&gt; vs &lt;code&gt;direct&lt;/code&gt;, and of Multicast vs a broker hop, is just &lt;em&gt;which row got used&lt;/em&gt;. &lt;code&gt;Clone()&lt;/code&gt; (new scope) means a new transaction; &lt;code&gt;CloneLinked()&lt;/code&gt; (shared scope) means the same one.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;ReleaseScopes()&lt;/code&gt; — it disposes the DI scopes &lt;strong&gt;without touching the &lt;code&gt;Body&lt;/code&gt;&lt;/strong&gt;, so an aggregator can free database connections early while still holding the message data it's accumulating. And &lt;code&gt;DisposeAsync()&lt;/code&gt; cleans up both the body (streams, stream caches) and the scopes. The object is &lt;code&gt;IAsyncDisposable&lt;/code&gt; for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gotcha: &lt;code&gt;Clone()&lt;/code&gt; does NOT deep-copy the Body
&lt;/h3&gt;

&lt;p&gt;Read &lt;code&gt;Message.Clone()&lt;/code&gt; literally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IMessage&lt;/span&gt; &lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;  &lt;span class="c1"&gt;// Body reference copied&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kvp&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kvp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kvp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers get a fresh dictionary. &lt;strong&gt;&lt;code&gt;Body&lt;/code&gt; is copied by reference.&lt;/strong&gt; After a &lt;code&gt;seda&lt;/code&gt; hop the producer's exchange and the worker's clone have independent headers, properties, and DI scopes — but they point at the &lt;strong&gt;same body object.&lt;/strong&gt; If that body is a mutable &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; or a POCO and both sides write to it, you have a data race the cloning &lt;em&gt;looks&lt;/em&gt; like it prevented. The XML doc says "deep copy"; the honest truth is "deep copy of everything except the payload." Treat the body as immutable once it's in flight, or clone it yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two APIs on purpose
&lt;/h3&gt;

&lt;p&gt;One last thing you'll notice in IntelliSense: every member has a C# idiomatic form (&lt;code&gt;In&lt;/code&gt;, &lt;code&gt;Body&lt;/code&gt;, &lt;code&gt;GetHeader&amp;lt;T&amp;gt;&lt;/code&gt;) and a Java-style alias (&lt;code&gt;getIn()&lt;/code&gt;, &lt;code&gt;setBody()&lt;/code&gt;, &lt;code&gt;getHeader&amp;lt;T&amp;gt;()&lt;/code&gt;). They're default interface methods over the same state, kept so the model reads the same as Apache Camel for anyone coming from the JVM. Use whichever; they're the same object.&lt;/p&gt;

&lt;p&gt;Now — the wires that carry this thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four channels — two axes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;direct&lt;/code&gt;, &lt;code&gt;direct-vm&lt;/code&gt;, &lt;code&gt;seda&lt;/code&gt;, &lt;code&gt;vm&lt;/code&gt; are how route segments talk to each other &lt;em&gt;inside&lt;/em&gt; a process. Choosing between them is the single most common thing newcomers get wrong, and now you have the vocabulary for &lt;em&gt;why&lt;/em&gt;: it comes down to threading and scope. They split on two axes — &lt;strong&gt;sync vs async&lt;/strong&gt;, and &lt;strong&gt;one context vs across contexts&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scheme&lt;/th&gt;
&lt;th&gt;Sync/Async&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Clones the exchange?&lt;/th&gt;
&lt;th&gt;Same transaction?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;direct://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;synchronous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;one context&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;yes&lt;/strong&gt; — same thread, same scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;direct-vm://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;synchronous&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;across contexts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seda://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;asynchronous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;one context&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;yes&lt;/strong&gt; (&lt;code&gt;Clone()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;no&lt;/strong&gt; — new scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vm://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;asynchronous&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;across contexts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;direct&lt;/code&gt; is the contrast. &lt;code&gt;seda&lt;/code&gt; is where the real work — and the real footguns — live, so that's where we'll spend the page.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;direct://&lt;/code&gt; — a method call wearing a URI
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;direct&lt;/code&gt; is not a queue. No thread, no buffer. A producer sending to a &lt;code&gt;direct&lt;/code&gt; endpoint invokes the consumer's processor &lt;strong&gt;synchronously, on the same thread&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the whole of DirectProducer.Process&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConsumerProcessor&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No consumer registered for direct endpoint ..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exchange is &lt;strong&gt;not cloned&lt;/strong&gt;. Same object, same thread, same DI scope — straight to the consumer. Three consequences follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exceptions propagate back to the caller.&lt;/strong&gt; A throw in the &lt;code&gt;direct&lt;/code&gt; consumer surfaces in the producer's route, where &lt;code&gt;OnException&lt;/code&gt;/&lt;code&gt;DoTry&lt;/code&gt; can catch it. (Remember §3 — it also lands on &lt;code&gt;exchange.Exception&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's the same transaction.&lt;/strong&gt; Same scope from the table above, so a &lt;code&gt;direct&lt;/code&gt; hop inside a &lt;code&gt;.Transaction()&lt;/code&gt; block commits and rolls back with everything around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The consumer must be started first&lt;/strong&gt;, or the send throws. &lt;code&gt;direct&lt;/code&gt; decouples your &lt;em&gt;route definitions&lt;/em&gt; into named sub-routes — not your &lt;em&gt;threads&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the entire personality of &lt;code&gt;direct&lt;/code&gt;: a zero-cost, in-transaction call you can give a URI and reuse. It has no parameters because it has no machinery. Use it to break a big route into readable, reusable pieces.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;seda://&lt;/code&gt; — the async queue, in detail
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;seda&lt;/code&gt; (Staged Event-Driven Architecture) is the opposite of &lt;code&gt;direct&lt;/code&gt; in every cell of the table. It's a real in-memory queue built on &lt;code&gt;System.Threading.Channels&lt;/code&gt;. The producer &lt;strong&gt;enqueues and returns immediately&lt;/strong&gt;; one or more background workers drain the queue on their own threads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SedaProducer.Process — the whole thing&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                       &lt;span class="c1"&gt;// §5: new scope. the trap lives here.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two facts are baked into those two lines, and everything else about &lt;code&gt;seda&lt;/code&gt; follows from them: it &lt;strong&gt;clones&lt;/strong&gt; (so the worker and producer never share scope — that &lt;code&gt;Clone()&lt;/code&gt; is row 1 of the table, a &lt;em&gt;new&lt;/em&gt; scope), and it &lt;strong&gt;returns before the work is done&lt;/strong&gt; (so the producer's thread, and its transaction, move on).&lt;/p&gt;

&lt;h3&gt;
  
  
  The parameters
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;seda&lt;/code&gt; takes three, all on the URI: &lt;code&gt;seda://name?concurrentConsumers=4&amp;amp;size=1000&amp;amp;timeout=30000&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When to change it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;concurrentConsumers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of worker loops draining the queue in parallel&lt;/td&gt;
&lt;td&gt;Raise when the downstream is slower than the inflow and order doesn't matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0&lt;/code&gt; (unbounded)&lt;/td&gt;
&lt;td&gt;Max queued exchanges. &lt;code&gt;0&lt;/code&gt; = grow without limit; &lt;code&gt;&amp;gt;0&lt;/code&gt; = bounded with &lt;code&gt;Wait&lt;/code&gt; backpressure&lt;/td&gt;
&lt;td&gt;Set a bound whenever the producer can outpace the consumer (almost always, in production)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;30000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declared as the enqueue wait for a bounded queue — &lt;strong&gt;see the honesty note below&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;concurrentConsumers&lt;/code&gt; — throughput, at the cost of order
&lt;/h3&gt;

&lt;p&gt;One worker is the default and keeps strict FIFO. Raising it spins up N independent loops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SedaConsumer.RunAsync&lt;/span&gt;
&lt;span class="n"&gt;_workers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConcurrentConsumers&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConcurrentConsumers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="n"&gt;_workers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;WorkerLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pollCt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processingCt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// each worker&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pollCt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessWithTracking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processingCt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;_processedCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two consequences worth stating plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You trade ordering for throughput.&lt;/strong&gt; With &lt;code&gt;concurrentConsumers=1&lt;/code&gt; the channel runs in &lt;code&gt;SingleReader&lt;/code&gt; mode (a real optimization in &lt;code&gt;System.Threading.Channels&lt;/code&gt;) and messages come out in order. With N&amp;gt;1, N workers pull concurrently and &lt;strong&gt;strict FIFO is gone&lt;/strong&gt; — message 2 can finish before message 1. Only raise it when out-of-order processing is acceptable.&lt;/li&gt;
&lt;li&gt;It's per-endpoint backpressure relief: a slow downstream stops blocking the upstream producer, because the producer only ever writes to the queue and leaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;size&lt;/code&gt; — bounded vs unbounded, and why you almost always want bounded
&lt;/h3&gt;

&lt;p&gt;This is the parameter people skip and regret. The endpoint picks the channel implementation off &lt;code&gt;size&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IExchange&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BoundedChannelOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;FullMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BoundedChannelFullMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// producer awaits a free slot&lt;/span&gt;
          &lt;span class="n"&gt;SingleReader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConcurrentConsumers&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;SingleWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateUnbounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IExchange&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// grows until you run out of memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;size=0&lt;/code&gt; (default, unbounded):&lt;/strong&gt; the queue grows as fast as producers write. If the consumer can't keep up, that's an unbounded memory leak with extra steps. Fine for bursty, bounded-volume work; dangerous for a firehose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;size&amp;gt;0&lt;/code&gt; (bounded):&lt;/strong&gt; &lt;code&gt;FullMode = Wait&lt;/code&gt; means a full queue makes the &lt;em&gt;producer await a free slot&lt;/em&gt; — backpressure that pushes the slowdown upstream instead of into your heap. This is what you want in production.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bounded SEDA: 4 workers, at most 1000 queued, producer waits when full&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://enrich?concurrentConsumers=4&amp;amp;size=1000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://enriched"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;timeout&lt;/code&gt; — an honesty note
&lt;/h3&gt;

&lt;p&gt;The options object documents &lt;code&gt;timeout&lt;/code&gt; (default &lt;code&gt;30000&lt;/code&gt; ms) as the enqueue wait for a bounded queue. Being straight with you: in the current code the producer enqueues with &lt;code&gt;WriteAsync(copy, ct)&lt;/code&gt; and &lt;strong&gt;does not&lt;/strong&gt; apply that timeout — a full bounded queue makes the producer wait on the channel until a slot frees or the &lt;code&gt;CancellationToken&lt;/code&gt; fires, not until 30 seconds elapse. So today, plan around &lt;code&gt;size&lt;/code&gt; and the cancellation token; treat &lt;code&gt;timeout&lt;/code&gt; as declared-but-not-yet-wired and don't build a deadline assumption on it. (Flagging it here because guessing at framework behavior from a doc-comment is exactly how you ship a bug.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful shutdown — &lt;code&gt;seda&lt;/code&gt; drains, it doesn't drop
&lt;/h3&gt;

&lt;p&gt;When a route stops, &lt;code&gt;seda&lt;/code&gt; doesn't throw away what's already queued:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SedaConsumer.OnStopAccepting&lt;/span&gt;
&lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryComplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// stop accepting new; let readers finish&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completing the writer makes the workers' &lt;code&gt;ReadAllAsync&lt;/code&gt; loop finish the &lt;strong&gt;remaining&lt;/strong&gt; items and then exit cleanly (&lt;code&gt;SedaConsumer&lt;/code&gt; is a &lt;code&gt;DrainableConsumer&lt;/code&gt;). On a graceful stop, in-flight queued exchanges are processed, not lost.&lt;/p&gt;

&lt;h3&gt;
  
  
  The durability caveat
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;seda&lt;/code&gt; is &lt;strong&gt;in-memory and non-durable.&lt;/strong&gt; A graceful stop drains; a crash or a hard kill does &lt;strong&gt;not&lt;/strong&gt; — whatever was sitting in the channel is gone. &lt;code&gt;seda&lt;/code&gt; is at-most-once across a process restart. When you need the queue to survive a restart, that's a broker (&lt;code&gt;rabbitmq&lt;/code&gt;, &lt;code&gt;kafka&lt;/code&gt;), not &lt;code&gt;seda&lt;/code&gt;. &lt;code&gt;seda&lt;/code&gt; is for decoupling &lt;em&gt;within&lt;/em&gt; a process, not for durability.&lt;/p&gt;

&lt;h3&gt;
  
  
  The transaction trap, stated once and for all
&lt;/h3&gt;

&lt;p&gt;Now §5 pays off. Because &lt;code&gt;seda&lt;/code&gt; calls &lt;code&gt;Clone()&lt;/code&gt; — row 1, a &lt;strong&gt;new&lt;/strong&gt; DI scope on a &lt;strong&gt;different&lt;/strong&gt; thread — &lt;strong&gt;anything past a &lt;code&gt;seda://&lt;/code&gt; hop is not in the caller's transaction.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSERT …"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://post-process"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// runs in a NEW scope, on another thread,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                 &lt;span class="c1"&gt;//    OUTSIDE this transaction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;post-process&lt;/code&gt; throws, the &lt;code&gt;INSERT&lt;/code&gt; above has &lt;strong&gt;already committed&lt;/strong&gt; — the &lt;code&gt;seda&lt;/code&gt; hop left the transaction the moment it cloned. This is the one mistake everyone makes exactly once. The fix is the table: if the hop must share the transaction, use &lt;code&gt;direct&lt;/code&gt; (no clone, same scope); if you genuinely want to hand the work off and move on, &lt;code&gt;seda&lt;/code&gt; is correct and you accept the new boundary. The DSL is the same; the scope is everything.&lt;/p&gt;

&lt;p&gt;Mental model: &lt;strong&gt;&lt;code&gt;direct&lt;/code&gt; = function call, &lt;code&gt;seda&lt;/code&gt; = mailbox.&lt;/strong&gt; One preserves your thread and your transaction; the other trades both for throughput and isolation.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;direct-vm://&lt;/code&gt; and &lt;code&gt;vm://&lt;/code&gt; — the same two, across module boundaries
&lt;/h2&gt;

&lt;p&gt;In a multi-module host (this is how redb.Tsak runs several modules in one process) each module is its own &lt;code&gt;RouteContext&lt;/code&gt;. Plain &lt;code&gt;direct&lt;/code&gt; and &lt;code&gt;seda&lt;/code&gt; are scoped to a single context — a producer in module A can't see a &lt;code&gt;direct&lt;/code&gt; consumer in module B. The &lt;code&gt;-vm&lt;/code&gt; variants lift exactly that wall by sharing the processor registry, and for &lt;code&gt;vm&lt;/code&gt; the channel, through a &lt;code&gt;SharedVmRegistry&lt;/code&gt; DI singleton:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;direct-vm://&lt;/code&gt;&lt;/strong&gt; — synchronous, &lt;strong&gt;cross-context&lt;/strong&gt;, no clone. A consumer in the &lt;code&gt;billing&lt;/code&gt; module exposes &lt;code&gt;direct-vm://charge&lt;/code&gt;; a producer in &lt;code&gt;orders&lt;/code&gt; calls it like a local in-transaction method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vm://&lt;/code&gt;&lt;/strong&gt; — asynchronous, cross-context, cloned-and-queued. The cross-module twin of &lt;code&gt;seda&lt;/code&gt;, with the &lt;strong&gt;same&lt;/strong&gt; &lt;code&gt;concurrentConsumers&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; parameters (and the same &lt;code&gt;Clone()&lt;/code&gt;, so the same transaction boundary and the same shared-Body caveat).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule transfers cleanly: &lt;code&gt;direct-vm&lt;/code&gt; for a synchronous cross-module call that shares the caller's transaction; &lt;code&gt;vm&lt;/code&gt; for hand-it-off-and-move-on across modules. Same semantics as their in-context twins — just a wider blast radius.&lt;/p&gt;




&lt;h2&gt;
  
  
  Picking a channel
&lt;/h2&gt;

&lt;p&gt;The whole decision in one table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You want…&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A reusable sub-route, same thread, &lt;strong&gt;inside my transaction&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;direct://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The above, but the consumer lives in &lt;strong&gt;another module&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;direct-vm://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;To &lt;strong&gt;hand work off&lt;/strong&gt; to a background worker and not wait&lt;/td&gt;
&lt;td&gt;&lt;code&gt;seda://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The above, across &lt;strong&gt;module&lt;/strong&gt; boundaries&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vm://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survival across a &lt;strong&gt;process restart&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;not a channel — a broker (&lt;code&gt;rabbitmq&lt;/code&gt;, &lt;code&gt;kafka&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the two facts that drive every row: &lt;strong&gt;does it clone (new scope = new transaction), and does it return before the work is done.&lt;/strong&gt; Everything else is detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;That's the foundation. You now know what flows through a route (&lt;code&gt;Exchange&lt;/code&gt; — In/Out, Properties vs Headers, in-band exceptions, and the four scope-aware clone variants) and the four wires that carry it (&lt;code&gt;direct&lt;/code&gt;/&lt;code&gt;direct-vm&lt;/code&gt; sync-and-in-transaction, &lt;code&gt;seda&lt;/code&gt;/&lt;code&gt;vm&lt;/code&gt; async-and-isolated), down to the parameter that decides whether your queue applies backpressure or eats your heap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series — Part 2: Splitter + Aggregator.&lt;/strong&gt; We fan one message into many, process them with bounded parallelism, and re-assemble — and the &lt;code&gt;Clone()&lt;/code&gt; vs &lt;code&gt;CloneLinked()&lt;/code&gt; distinction from §5 turns out to be the whole story of whether the split shares the parent's transaction. Plus the aggregation-strategy contract where the first call hands you a &lt;code&gt;null&lt;/code&gt; accumulator. Subscribe to the series if you want it when it lands.&lt;/p&gt;

&lt;p&gt;If anything here fought you — especially the &lt;code&gt;seda&lt;/code&gt; transaction boundary or the shared-&lt;code&gt;Body&lt;/code&gt; clone — say so in the comments. That feedback is exactly what an early OSS release is for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Tsak (runtime / multi-module host)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Core (storage)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discussions&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route/discussions&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All Apache 2.0. Questions in the comments are welcome — especially "does channel X do Y?", because that's how the docs get written.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>opensource</category>
    </item>
    <item>
      <title>RedBase / redb.Route / redb.Tsak 3.0.0 shipped</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Sun, 31 May 2026 14:26:56 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/redbase-redbroute-redbtsak-300-shipped-27pf</link>
      <guid>https://dev.to/rinat_kozin/redbase-redbroute-redbtsak-300-shipped-27pf</guid>
      <description>&lt;p&gt;redb hit 20k NuGet downloads&lt;/p&gt;

&lt;p&gt;Three packages, one version bump.&lt;/p&gt;

&lt;p&gt;RedBase 3.0.0 — Free tier now generates the same pivot CTE as Pro. array_agg FILTER on PostgreSQL, MAX CASE WHEN on MSSql. Same base-field pushdown, same index path. MSSql Free went from 0 to 145/145 parity tests. Auto-deployed SQL bundle on version mismatch — no DBA in the loop.&lt;/p&gt;

&lt;p&gt;redb.Route 3.0.0 — the entire v1/v2 dual compiler stack is gone. One canonical RouteDefinition, AST built from IProcessorDefinition nodes. Dynamic endpoints (ToD, dynamic WireTap/Enrich), string-template DSL (${header.x}, ${body}), full OnException fluent parity with Apache Camel.&lt;/p&gt;

&lt;p&gt;redb.Tsak 3.0.0 — runtime container for redb.Route pipelines. Rebuilt on Core 3.0.0 + Route 3.0.0. No breaking changes in Tsak's own API.&lt;/p&gt;

&lt;p&gt;Upgrade: package bump, no migrations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b"&gt;https://dev.to/rinat_kozin_d0a2ef43e7824/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;https://redbase.app/architecture&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redbase-app/redb/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb/blob/main/CHANGELOG.md&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/redbase-app/redb-route/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb-route/blob/main/CHANGELOG.md&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/redbase-app/redb-tsak/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb-tsak/blob/main/CHANGELOG.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>status</category>
      <category>dotnet</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why our Free was so far behind Pro — and what we just shipped in RedBase 3.0.0 (with the actual SQL)</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Fri, 29 May 2026 23:18:10 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b</link>
      <guid>https://dev.to/rinat_kozin/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb39rp5la1lk52ev1d2wy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb39rp5la1lk52ev1d2wy.jpg" alt="RedBase (redB) logo rendered in fire-explosion style, with C# code snippets and data charts in the background" width="784" height="1168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the launch posts I keep getting the same question, in DMs, issues, private chats:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Why is the Free version so different from Pro? It feels like two different products."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a fair question, and the honest answer isn't a marketing answer — it's a "this is how the codebase grew over the years" answer. So let me just tell you, and then show you the SQL — both tiers, with the actual parameter values, so you can see exactly what hits the database.&lt;/p&gt;

&lt;p&gt;This is one post. There won't be a second one. I'd rather over-explain once than dribble out a series.&lt;/p&gt;




&lt;h2&gt;
  
  
  30-second background
&lt;/h2&gt;

&lt;p&gt;RedBase is a typed object store. You write a C# class, decorate it with &lt;code&gt;[RedbScheme("name")]&lt;/code&gt;, the engine syncs the scheme into PostgreSQL or MSSql, and you query with normal LINQ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="n"&gt;HomeAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OfficeLocations&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;RedbListItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt; &lt;span class="n"&gt;DateCreate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storage is six tables: &lt;code&gt;_objects&lt;/code&gt;, &lt;code&gt;_values&lt;/code&gt;, &lt;code&gt;_structures&lt;/code&gt;, &lt;code&gt;_schemes&lt;/code&gt;, &lt;code&gt;_list_items&lt;/code&gt;, &lt;code&gt;_users&lt;/code&gt;. One row per object lives in &lt;code&gt;_objects&lt;/code&gt;. Each property of that object lives as one or more rows in &lt;code&gt;_values&lt;/code&gt;, keyed by &lt;code&gt;(_id_object, _id_structure, _array_index)&lt;/code&gt;. So filtering by N properties means joining/pivoting N rows back into one logical row per object.&lt;/p&gt;

&lt;p&gt;How you do that pivot defines the performance ceiling of the engine. That's the part 3.0.0 fixes for the Free tier.&lt;/p&gt;




&lt;h2&gt;
  
  
  How we got here — the two paths that existed before 3.0.0
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pro path (since ~2024) — compiled in C
&lt;/h3&gt;

&lt;p&gt;Pro walks the LINQ expression tree in process via &lt;code&gt;ExpressionToSqlCompiler&lt;/code&gt; + &lt;code&gt;SqlExpressionVisitor&lt;/code&gt;, registers bind variables through &lt;code&gt;SqlParameterCollector&lt;/code&gt;, and emits a single PVT (pivot) CTE per query. For the LINQ above, with &lt;code&gt;_id_scheme=42&lt;/code&gt;, structure id &lt;code&gt;101=Age&lt;/code&gt;, &lt;code&gt;102=City&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Pro on PostgreSQL — exact text Npgsql sends to the server&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"City"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"City"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;-- bound parameters (NpgsqlParameter, never interpolated into text):&lt;/span&gt;
&lt;span class="c1"&gt;--   $1 = 101         (bigint, structure id of Age)&lt;/span&gt;
&lt;span class="c1"&gt;--   $2 = 102         (bigint, structure id of City)&lt;/span&gt;
&lt;span class="c1"&gt;--   $3 = 42          (bigint, scheme id)&lt;/span&gt;
&lt;span class="c1"&gt;--   $4 = {101, 102}  (bigint[])&lt;/span&gt;
&lt;span class="c1"&gt;--   $5 = 30          (bigint)&lt;/span&gt;
&lt;span class="c1"&gt;--   $6 = 'London'    (text)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this gets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One index hit&lt;/strong&gt; on &lt;code&gt;(_id_structure, _id_object)&lt;/code&gt; — &lt;code&gt;_id_structure = ANY($4)&lt;/code&gt; uses the structure-id covering index, narrowing to only the props the query touches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One GROUP BY pass&lt;/strong&gt; — &lt;code&gt;FILTER (WHERE …)&lt;/code&gt; is PostgreSQL's way of splitting values into columns &lt;em&gt;during&lt;/em&gt; aggregation. Each scalar pivot field costs one &lt;code&gt;array_agg&lt;/code&gt; aggregator, all running in the same scan. The &lt;code&gt;[1]&lt;/code&gt; subscript picks the single element because scalar fields have at most one &lt;code&gt;_values&lt;/code&gt; row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outer WHERE on flat columns&lt;/strong&gt; — &lt;code&gt;pvt."Age" &amp;gt; $5&lt;/code&gt; is a normal B-tree comparison on the CTE's projected columns. The optimizer reorders these freely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable parameterized SQL text&lt;/strong&gt; — same &lt;code&gt;$1..$N&lt;/code&gt; placeholders every call, so PostgreSQL's prepared-statement plan cache works. Connection pools love this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5 fields or 50 — same query shape, same cost class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And here is the same query through Free 3.0.0 on PostgreSQL — byte-for-byte the same shape:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free 3.0.0 on PostgreSQL — emitted by pvt_build_query_sql(scheme, facets_jsonb)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"City"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"City"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'London'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- The literals you see here are NOT string-concatenated user input.&lt;/span&gt;
&lt;span class="c1"&gt;-- They are emitted by format(..., %L) with explicit type casts inside&lt;/span&gt;
&lt;span class="c1"&gt;-- the plpgsql builder. The C# caller made two parameterized roundtrips:&lt;/span&gt;
&lt;span class="c1"&gt;--   1) SELECT pvt_build_query_sql($1, $2::jsonb, $3, $4)   -- returns SQL text&lt;/span&gt;
&lt;span class="c1"&gt;--        with ($1=schemeId, $2=facets, $3=limit, $4=offset) as Npgsql parameters&lt;/span&gt;
&lt;span class="c1"&gt;--   2) EXECUTE the returned text on the connection → collect _id values&lt;/span&gt;
&lt;span class="c1"&gt;--   3) SELECT * FROM get_objects_json($1::bigint[], $2)    -- hydrate by ids&lt;/span&gt;
&lt;span class="c1"&gt;--        with ($1={...matched ids...}, $2=maxDepth) as Npgsql parameters&lt;/span&gt;
&lt;span class="c1"&gt;-- See the SQL-injection section below for the full chain.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same CTE, same &lt;code&gt;array_agg FILTER&lt;/code&gt;, same outer flat WHERE. The only structural difference: the SQL text is built once per call by &lt;code&gt;pvt_build_query_sql&lt;/code&gt; inside the database, so the &lt;em&gt;outer&lt;/em&gt; &lt;code&gt;EXECUTE&lt;/code&gt; runs a unique string each call — values come baked in as &lt;code&gt;%L&lt;/code&gt;-quoted literals with type casts (&lt;code&gt;30&lt;/code&gt;, &lt;code&gt;'London'&lt;/code&gt;) rather than as &lt;code&gt;$N&lt;/code&gt; placeholders. That has plan-cache implications versus Pro (the SQL text varies, so PG can't reuse a plan across calls with different values), but the &lt;em&gt;index path&lt;/em&gt; and &lt;em&gt;join shape&lt;/em&gt; the planner picks are identical. For the parameterized story, see how this gets called from any language a few sections down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free path (until 3.0.0) — interpreted on the database side
&lt;/h3&gt;

&lt;p&gt;The Free path serialized the LINQ filter into a JSON facet structure, shipped it to a plpgsql function, and that function built dynamic SQL inside the database. For the same LINQ above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// C# side just packs the facets and calls one function&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;@and&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM search_objects_with_facets($1, $2)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;schemeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function then emitted, for those exact arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Old Free on PostgreSQL — N props in WHERE → N correlated EXISTS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'London'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL optimizes correlated &lt;code&gt;EXISTS&lt;/code&gt; well when the right index is there, so this is &lt;em&gt;fine&lt;/em&gt; at 1–3 props. By 10 props the planner is doing real work to choose join order. By 30 props you're seeing query times you don't want. (For curiosity: the legacy path stays in the database under the name &lt;code&gt;search_objects_with_facets()&lt;/code&gt; for back-compat — old callers keep working.)&lt;/p&gt;

&lt;p&gt;On MSSql Free the picture was uglier still — a wide-CASE inline pivot that hadn't received the same iterations as the PG side. Different SQL shape from PG, different code path inside the library, different bugs. The two Free implementations had drifted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the gap existed for so long
&lt;/h3&gt;

&lt;p&gt;I built the Pro engine because we needed it. We use Pro internally — that's what runs the production workloads. The Free path kept working for its users, but parity was always "next quarter." It's not a single feature, it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pivot CTE generation for both dialects;&lt;/li&gt;
&lt;li&gt;base-field pushdown into the inner &lt;code&gt;_objects&lt;/code&gt; subquery;&lt;/li&gt;
&lt;li&gt;nested-dict accessor (&lt;code&gt;Field[key].Child&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;ListItem &lt;code&gt;.Value&lt;/code&gt; / &lt;code&gt;.Alias&lt;/code&gt; via a single JOIN;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GroupBy&lt;/code&gt;, &lt;code&gt;HAVING&lt;/code&gt;, &lt;code&gt;ArrayGroupBy&lt;/code&gt;, &lt;code&gt;DistinctBy&lt;/code&gt;, &lt;code&gt;Sql.Function&lt;/code&gt; whitelist, &lt;code&gt;$expr&lt;/code&gt; trees;&lt;/li&gt;
&lt;li&gt;null semantics, &lt;code&gt;$exists&lt;/code&gt; / &lt;code&gt;$notNull&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;a filter-splitting optimizer that decides what's a Shape A (pure base), B (narrow) or C (wide pivot) query;&lt;/li&gt;
&lt;li&gt;and all of it round-tripping through 200+ integration tests per backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spread that across two databases and you understand why it kept slipping. The interest spike from the launch posts is what pushed me to drop the half-written stuff and ship this. It's not finished-finished — that's why it's an OSS release instead of a private polish round.&lt;/p&gt;




&lt;h2&gt;
  
  
  What 3.0.0 actually changes
&lt;/h2&gt;

&lt;p&gt;The headline: &lt;strong&gt;Free and Pro now emit the same PVT CTE shape&lt;/strong&gt;, on both PostgreSQL and MSSql.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. MSSql Free hit full v2-pvt parity — 145/145
&lt;/h3&gt;

&lt;p&gt;The old MSSql Free path is gone. The new one is a 27-file SQL module under &lt;a href="https://github.com/redbase-app/redb/tree/main/redb.MSSql/sql/v2-pvt" rel="noopener noreferrer"&gt;&lt;code&gt;redb.MSSql/sql/v2-pvt/&lt;/code&gt;&lt;/a&gt;, assembled by an MSBuild target into a single &lt;code&gt;pvt_bundle.sql&lt;/code&gt; resource embedded in &lt;code&gt;RedBase.MSSql.dll&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00_module_init.sql           -- version sentinel, drop-and-replace bootstrap
10_pvt_field_collection.sql  -- walks the facet JSON, harvests structure ids
13_pvt_condition.sql         -- per-field WHERE fragments
14_pvt_where.sql             -- recursive $and/$or/$expr walker
15_pvt_order.sql             -- ORDER BY building (incl. $expr ordering)
16_pvt_split.sql             -- Shape A/B/C classifier, pushdown engine
17_pvt_expr.sql              -- $expr classifier + scalar expression compiler
20_pvt_build_query_sql.sql   -- the entry point — returns the final SQL text
99_smoke_auto.sql            -- 195 PASS / 0 FAIL / 1 SKIP regression suite
... (and friends)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MSSql dialect uses &lt;code&gt;MAX(CASE WHEN …)&lt;/code&gt; instead of &lt;code&gt;array_agg FILTER&lt;/code&gt; (SQL Server has no FILTER clause), but the rest of the shape is the same. Same LINQ as above, on MSSql Free, becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free MSSql 3.0.0 — emitted by the T-SQL builder under [dbo].[pvt_build_query_sql]&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'London'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Same idea as the PG Free block above: literals are inlined by the T-SQL&lt;/span&gt;
&lt;span class="c1"&gt;-- builder via QUOTENAME() for identifiers and quoted-literal emission for&lt;/span&gt;
&lt;span class="c1"&gt;-- values (numeric literals stay bare, strings become N'...' with embedded&lt;/span&gt;
&lt;span class="c1"&gt;-- quotes escaped). The C# caller made two parameterized roundtrips:&lt;/span&gt;
&lt;span class="c1"&gt;--   1) EXEC sp_executesql N'SELECT [dbo].[pvt_build_query_sql](@scheme, @facets, @limit, @offset)',&lt;/span&gt;
&lt;span class="c1"&gt;--        N'@scheme bigint, @facets nvarchar(max), @limit int, @offset int', ...   -- returns SQL text&lt;/span&gt;
&lt;span class="c1"&gt;--   2) EXEC sp_executesql @sql                                                    -- collect _id values&lt;/span&gt;
&lt;span class="c1"&gt;--   3) SELECT * FROM [dbo].[get_objects_json](@ids, @maxDepth)                    -- hydrate by ids&lt;/span&gt;
&lt;span class="c1"&gt;-- User input never touches a SQL parser unquoted. The SQL-injection section&lt;/span&gt;
&lt;span class="c1"&gt;-- below walks the chain.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MAX(CASE WHEN ...)&lt;/code&gt; is the SQL Server idiom that mirrors &lt;code&gt;array_agg FILTER&lt;/code&gt;: NULLs are ignored by aggregates, so the CASE acts as a "pass through if matching, otherwise null" gate. Same one-pass GROUP BY, same outer WHERE on flat columns. (Unlike Pro — which sends a parameterized statement with &lt;code&gt;@p1..@pN&lt;/code&gt; placeholders and benefits from SQL Server's plan cache — Free's text varies per call, so plan reuse depends on SQL Server's auto-parameterization. Shape and index path are identical to Pro either way.)&lt;/p&gt;

&lt;p&gt;Everything PG Free had now works identically on MSSql Free: flat + tree queries, scalar / array / dict fields, nested-dict &lt;code&gt;Field[key].Child&lt;/code&gt;, ListItem joins for &lt;code&gt;.Id&lt;/code&gt; / &lt;code&gt;.Value&lt;/code&gt; / &lt;code&gt;.Alias&lt;/code&gt;, same-scheme nested POCO compound paths, &lt;code&gt;OrderBy&lt;/code&gt; / &lt;code&gt;Take&lt;/code&gt; / &lt;code&gt;Skip&lt;/code&gt; / &lt;code&gt;DistinctBy&lt;/code&gt;, full &lt;code&gt;GroupBy&lt;/code&gt; with &lt;code&gt;HAVING&lt;/code&gt;, &lt;code&gt;ArrayGroupBy&lt;/code&gt; via &lt;code&gt;OUTER APPLY&lt;/code&gt;, array aggregates, &lt;code&gt;Sql.Function&lt;/code&gt; whitelist, &lt;code&gt;$expr&lt;/code&gt;, null semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Auto-deploy of the v2-pvt bundle
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ISqlDialect&lt;/code&gt; got a new method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ISqlDialect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... existing dialect surface ...&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;Query_PvtRequiredVersion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// semver the embedded bundle ships&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RedbServiceBase.EnsurePvtModuleDeployedAsync&lt;/code&gt; runs on &lt;code&gt;InitializeAsync()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dialect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Query_PvtRequiredVersion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// dialect doesn't use v2-pvt&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;deployed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteScalarAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Dialect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectPvtModuleVersionSql&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;     &lt;span class="c1"&gt;// calls pvt_module_version()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deployed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordinal&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                    &lt;span class="c1"&gt;// exact match — already deployed&lt;/span&gt;

&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v2-pvt mismatch (deployed={D}, required={R}). Applying bundle."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;deployed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ReadEmbeddedBundle&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The match is &lt;strong&gt;exact-string&lt;/strong&gt;, not semver-numeric. Any change to any of the 27 source files bumps the bundle version, the MSBuild target regenerates &lt;code&gt;pvt_bundle.sql&lt;/code&gt; on the next build, and on next service startup the new bundle gets applied. No DBA in the loop, no "did you forget to run the migration?" tickets.&lt;/p&gt;

&lt;p&gt;(One MSBuild gotcha cost me half a day: declaring &lt;code&gt;&amp;lt;EmbeddedResource Include="sql\v2-pvt\pvt_bundle.sql" /&amp;gt;&lt;/code&gt; silently renames the manifest resource from &lt;code&gt;redb.MSSql.sql.v2-pvt.pvt_bundle.sql&lt;/code&gt; to &lt;code&gt;redb.MSSql.sql.v2_pvt.pvt_bundle.sql&lt;/code&gt; — dashes become underscores. &lt;code&gt;GetManifestResourceStream&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; and you stare at it for a while. Fix: pin &lt;code&gt;&amp;lt;LogicalName&amp;gt;redb.MSSql.sql.v2-pvt.pvt_bundle.sql&amp;lt;/LogicalName&amp;gt;&lt;/code&gt; explicitly. Free tip if you ever ship SQL through embedded resources.)&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Base-field pushdown — the 894 ms → 11 ms one
&lt;/h3&gt;

&lt;p&gt;This is the regression that originally motivated &lt;code&gt;2.0.1&lt;/code&gt;. When a query mixes a base-field predicate (on &lt;code&gt;_objects&lt;/code&gt; columns) with a props predicate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;555&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// base field, manager id 555&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;// props field&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The naive layout puts the base predicate in the outer WHERE — that's what &lt;code&gt;2.0.0&lt;/code&gt; did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Bad: 894 ms on a scheme with millions of _values rows&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;    &lt;span class="c1"&gt;-- outer: applies AFTER the CTE pivoted the entire scheme&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- $1=101, $2=42, $3={101}, $4=555, $5=30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL aggregates &lt;code&gt;_values&lt;/code&gt; for &lt;em&gt;every&lt;/em&gt; employee in the scheme and only then narrows by &lt;code&gt;_id_parent&lt;/code&gt;. Self-inflicted full pivot.&lt;/p&gt;

&lt;p&gt;3.0.0 (both Free and Pro, both dialects) classifies the base predicate at compile time and injects it &lt;em&gt;into the inner &lt;code&gt;_objects&lt;/code&gt; subquery&lt;/em&gt; of the PVT CTE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Good: 11 ms&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="c1"&gt;-- pushed in&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- $1=101, $2=42, $3=555, $4={101}, $5=30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free 3.0.0 emits the same shape — here it is on PostgreSQL, with the values baked in by &lt;code&gt;pvt_split_filter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free 3.0.0 on PostgreSQL — pushdown identical to Pro&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;555&lt;/span&gt;           &lt;span class="c1"&gt;-- pushed in by pvt_split_filter&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on MSSql Free 3.0.0 — again, literals inlined by the T-SQL builder, not parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free MSSql 3.0.0 — pushdown via the same classifier, MAX/CASE pivot, values inlined&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_parent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;555&lt;/span&gt;         &lt;span class="c1"&gt;-- pushed in by the T-SQL split classifier&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner subquery resolves via the &lt;code&gt;(_id_scheme, _id_parent)&lt;/code&gt; covering index, returns ~N matching &lt;code&gt;_id&lt;/code&gt;s, and the pivot only sees &lt;code&gt;_values&lt;/code&gt; rows for those N objects. Pro got this in &lt;code&gt;2.0.1&lt;/code&gt;; 3.0.0 brings the same to Free through &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/16_pvt_split.sql" rel="noopener noreferrer"&gt;&lt;code&gt;pvt_split_filter&lt;/code&gt;&lt;/a&gt; and the &lt;code&gt;pvt_expr_is_base_only&lt;/code&gt; classifier in &lt;code&gt;17_pvt_expr.sql&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Nested-dict accessor short-circuit
&lt;/h3&gt;

&lt;p&gt;For &lt;code&gt;Dictionary&amp;lt;string, T&amp;gt;&lt;/code&gt; fields, &lt;code&gt;Where(o =&amp;gt; o.OfficeLocations["HQ"].City == "New York")&lt;/code&gt; builds the dict-keyed pivot column in the CTE. There's no separate &lt;code&gt;_dict_key&lt;/code&gt; column in &lt;code&gt;_values&lt;/code&gt; — the existing &lt;code&gt;_array_index&lt;/code&gt; slot (&lt;code&gt;text&lt;/code&gt;) doubles as the dict key for dictionary fields (it stays an integer-as-text for arrays, holds the key for dicts, is &lt;code&gt;NULL&lt;/code&gt; for scalars):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;117&lt;/span&gt;    &lt;span class="c1"&gt;-- structure id of City inside OfficeLocations&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'HQ'&lt;/span&gt;    &lt;span class="c1"&gt;-- _array_index doubles as the dict key&lt;/span&gt;
&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"OfficeLocations[HQ].City"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug fixed in this cycle (MSSql 0.1.3, PG 0.6.1 earlier): the outer WHERE was &lt;em&gt;also&lt;/em&gt; emitting a separate &lt;code&gt;EXISTS&lt;/code&gt; over &lt;code&gt;_values&lt;/code&gt; to re-check the same dict key, even though the CTE had already done that work. The six-line fix in &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/13_pvt_condition.sql" rel="noopener noreferrer"&gt;&lt;code&gt;13_pvt_condition.sql&lt;/code&gt;&lt;/a&gt; makes the outer WHERE just reference the pivot column directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Before&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;117&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'HQ'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'New York'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- After&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"OfficeLocations[HQ].City"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;    &lt;span class="c1"&gt;-- $4='New York'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One predicate, no subquery, the optimizer can reorder it freely with the rest of the WHERE.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What landed in Pro this cycle
&lt;/h3&gt;

&lt;p&gt;While Free was catching up, Pro added its own things (the version is &lt;code&gt;3.0.0&lt;/code&gt; for both):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;GroupBy + HAVING&lt;/code&gt; in Pro.&lt;/strong&gt; &lt;code&gt;HavingAsync&lt;/code&gt; existed in Free since &lt;code&gt;1.2.x&lt;/code&gt; but had no Pro counterpart. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Department&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// HAVING&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Dept&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_listitem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Department_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Dept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"N"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_list_items&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Department_id"&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- $1=130, $2=101, $3=42, $4={130,101}, $5=10, $6=40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Free side has had &lt;code&gt;HavingAsync&lt;/code&gt; since &lt;code&gt;1.2.x&lt;/code&gt; — here's the same LINQ through Free 3.0.0 on PostgreSQL for parity, so you can see they really do meet in the middle now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free 3.0.0 PG — same HAVING shape, values inlined by pvt_build_query_sql&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_listitem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Department_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Dept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"N"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_list_items&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Department_id"&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on MSSql Free 3.0.0 — same shape, literals inlined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free MSSql 3.0.0 — same HAVING shape, MAX/CASE pivot, values inlined by the T-SQL builder&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_listitem&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Department_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dept&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_list_items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Department_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;33/33 HAVING + 6/6 no-HAVING in &lt;code&gt;GroupByHavingTestsBase&lt;/code&gt;, green on PG.Pro and MSSql.Pro — and identical row counts on the Free side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ArrayGroupBy&lt;/code&gt; unified&lt;/strong&gt; across all four tiers — &lt;code&gt;GroupBy(items =&amp;gt; items.SelectMany(o =&amp;gt; o.Skills))&lt;/code&gt; works identically on PG Free, PG.Pro, MSSql Free, MSSql.Pro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MSSql.Pro &lt;code&gt;AggregateBatch&lt;/code&gt; parity with PG.Pro&lt;/strong&gt; — non-numeric &lt;code&gt;MinAsync&lt;/code&gt; / &lt;code&gt;MaxAsync&lt;/code&gt; (over &lt;code&gt;string&lt;/code&gt;/&lt;code&gt;DateTime&lt;/code&gt;/&lt;code&gt;Guid&lt;/code&gt;) and a &lt;code&gt;Where(...)&lt;/code&gt; filter inside a batch aggregation now produce the same plan shape on MSSql.Pro as PG.Pro.&lt;/p&gt;

&lt;p&gt;Plus a stack of bug-fixes documented in &lt;a href="https://github.com/redbase-app/redb/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  "Wait — Free builds SQL dynamically inside plpgsql. What about SQL injection?"
&lt;/h2&gt;

&lt;p&gt;Right question. If you skimmed the Free SQL above and noticed it's &lt;code&gt;EXECUTE&lt;/code&gt;-ing a dynamically-built string inside a database function — your instinct is correct, that &lt;em&gt;would&lt;/em&gt; be a problem in a naively-written generator. Here's what actually guards against it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pro side (C#) — boring and safe
&lt;/h3&gt;

&lt;p&gt;Pro builds parameterized SQL via &lt;code&gt;SqlParameterCollector&lt;/code&gt;. Every value the user expression captured (&lt;code&gt;30&lt;/code&gt;, &lt;code&gt;"London"&lt;/code&gt;, &lt;code&gt;managerId&lt;/code&gt;) becomes an &lt;code&gt;NpgsqlParameter&lt;/code&gt; / &lt;code&gt;SqlParameter&lt;/code&gt;. The text Npgsql sends to PostgreSQL contains &lt;code&gt;$1..$N&lt;/code&gt; placeholders only; values travel out-of-band through the wire protocol and never see a SQL parser. No template, no concatenation, no &lt;code&gt;format()&lt;/code&gt;. There's literally no path for an injection because there's no string-to-SQL bridge for values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free side on PostgreSQL (plpgsql) — three layers, on purpose
&lt;/h3&gt;

&lt;p&gt;Free has it harder than Pro because the SQL builder runs &lt;em&gt;inside&lt;/em&gt; the database, so the values arrive baked into the JSON. The defenses, layer by layer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Values pass through &lt;code&gt;format(..., %L)&lt;/code&gt; with explicit type casts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;%L&lt;/code&gt; is PostgreSQL's literal-quoting format specifier — same semantics as &lt;code&gt;quote_literal()&lt;/code&gt;. It single-quotes the value, escapes embedded quotes, and returns a SQL-safe literal. Example from &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/v2-pvt/04_pvt_inner_condition.sql" rel="noopener noreferrer"&gt;&lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The leaf emitter for "Long $gt N" looks like this:&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fv._Long %s %L::bigint'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op_symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator_value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a malicious payload arrives as &lt;code&gt;operator_value = "1; DROP TABLE _objects --"&lt;/code&gt;, &lt;code&gt;%L&lt;/code&gt; produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;fv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'1; DROP TABLE _objects --'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;::bigint&lt;/code&gt; cast then fails at parse time with a normal type error. The single quotes from &lt;code&gt;%L&lt;/code&gt; make the injection inert; the type cast makes it a hard error rather than silent data corruption. Every value branch in &lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt; uses this pattern: &lt;code&gt;%L::bigint&lt;/code&gt;, &lt;code&gt;%L::numeric&lt;/code&gt;, &lt;code&gt;%L::timestamptz&lt;/code&gt;, &lt;code&gt;%L::uuid&lt;/code&gt;, &lt;code&gt;%L::boolean&lt;/code&gt;, &lt;code&gt;%L::interval&lt;/code&gt;. Strings use plain &lt;code&gt;%L&lt;/code&gt; (which is &lt;code&gt;'…'&lt;/code&gt;-quoting), and any further use as text is still inside single quotes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Identifiers come from the database, not from the JSON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The user's JSON facets refer to fields by name (&lt;code&gt;"Age"&lt;/code&gt;, &lt;code&gt;"OfficeLocations[HQ].City"&lt;/code&gt;). The builder doesn't concatenate that string into the SQL. Instead, &lt;code&gt;pvt_field_collection&lt;/code&gt; looks the name up in &lt;code&gt;_structures&lt;/code&gt; &lt;em&gt;for the queried scheme&lt;/em&gt; — by joining &lt;code&gt;_structures._name = $jsonKey AND _structures._id_scheme = $scheme&lt;/code&gt;. If the name doesn't exist, the field is rejected. If it does exist, what gets emitted is the matched &lt;code&gt;_id_structure&lt;/code&gt; (a &lt;code&gt;bigint&lt;/code&gt;, always safe) and a pivot column alias built with &lt;code&gt;quote_ident(structure_name_from_db)&lt;/code&gt; or &lt;code&gt;%I&lt;/code&gt;. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The structure id is a number from the DB.&lt;/li&gt;
&lt;li&gt;The pivot column name is &lt;code&gt;quote_ident&lt;/code&gt;'d, so &lt;code&gt;"weird name with quotes"&lt;/code&gt; becomes a double-quoted SQL identifier.&lt;/li&gt;
&lt;li&gt;Reserved bases (&lt;code&gt;0$:id&lt;/code&gt;, &lt;code&gt;0$:parent_id&lt;/code&gt;, etc.) are dispatched through a fixed CASE — unknown bases raise:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;EXCEPTION&lt;/span&gt; &lt;span class="s1"&gt;'Unknown RedbObject base field: "%" ...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No path lets arbitrary text become an unquoted identifier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Operator names are a fixed whitelist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$eq&lt;/code&gt;, &lt;code&gt;$gt&lt;/code&gt;, &lt;code&gt;$lt&lt;/code&gt;, &lt;code&gt;$ne&lt;/code&gt;, &lt;code&gt;$in&lt;/code&gt;, &lt;code&gt;$like&lt;/code&gt;, &lt;code&gt;$ilike&lt;/code&gt;, &lt;code&gt;$arrayContains&lt;/code&gt;, &lt;code&gt;$expr&lt;/code&gt;, etc. are matched by a &lt;code&gt;CASE&lt;/code&gt;/&lt;code&gt;IF&lt;/code&gt; tree in &lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt; and friends. Unknown operators don't fall through to anything — they &lt;code&gt;RAISE EXCEPTION&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;EXCEPTION&lt;/span&gt; &lt;span class="s1"&gt;'pvt_build_agg_expr: unsupported aggregate "%"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same for &lt;code&gt;Sql.Function&lt;/code&gt; — only names in the whitelist (&lt;code&gt;COALESCE&lt;/code&gt;, &lt;code&gt;LOWER&lt;/code&gt;, &lt;code&gt;UPPER&lt;/code&gt;, …) survive; everything else raises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The final &lt;code&gt;EXECUTE&lt;/code&gt; evaluates a string built only from the three pieces above.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So even though &lt;code&gt;EXECUTE&lt;/code&gt; is involved, every byte of that string came from either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;quote_literal&lt;/code&gt;'d (&lt;code&gt;%L&lt;/code&gt;) value with a type cast, or&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;quote_ident&lt;/code&gt;'d (&lt;code&gt;%I&lt;/code&gt;) identifier sourced from &lt;code&gt;_structures&lt;/code&gt; by id lookup, or&lt;/li&gt;
&lt;li&gt;a hardcoded operator template chosen by a whitelist switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's three independent gates, and any of them is sufficient to defeat injection. The plpgsql generator is paranoid by design — &lt;code&gt;format&lt;/code&gt;/&lt;code&gt;%L&lt;/code&gt;/&lt;code&gt;%I&lt;/code&gt; are used uniformly, not as a one-off. The 99-test smoke suite (&lt;code&gt;99_smoke_auto.sql&lt;/code&gt;) includes adversarial payloads (&lt;code&gt;"; DROP TABLE …&lt;/code&gt;, embedded quotes, type-mismatched literals) and expects parse errors or rejection, not successful execution.&lt;/p&gt;

&lt;p&gt;If you want to audit the PG side: every emitter is in &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/v2-pvt/04_pvt_inner_condition.sql" rel="noopener noreferrer"&gt;&lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/v2-pvt/05_pvt_single_facet.sql" rel="noopener noreferrer"&gt;&lt;code&gt;05_pvt_single_facet.sql&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;13_pvt_condition.sql&lt;/code&gt;. Grep for &lt;code&gt;format(&lt;/code&gt; — every value site has &lt;code&gt;%L&lt;/code&gt; and a cast, every identifier site has &lt;code&gt;%I&lt;/code&gt; or &lt;code&gt;quote_ident&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free side on MSSql (T-SQL) — the same three layers, different primitives
&lt;/h3&gt;

&lt;p&gt;SQL Server has no &lt;code&gt;format(..., %L/%I)&lt;/code&gt; and no &lt;code&gt;quote_literal/quote_ident&lt;/code&gt; shorthand, so the MSSql builder under &lt;a href="https://github.com/redbase-app/redb/tree/main/redb.MSSql/sql/v2-pvt" rel="noopener noreferrer"&gt;&lt;code&gt;redb.MSSql/sql/v2-pvt/&lt;/code&gt;&lt;/a&gt; implements each layer with the T-SQL primitives that are available. Same three gates, just spelled differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Values are quoted with &lt;code&gt;REPLACE(..., '''', '''''')&lt;/code&gt; and/or validated with &lt;code&gt;TRY_CAST&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; they get inlined.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For strings (including dict keys and &lt;code&gt;$expr&lt;/code&gt; literals) the builder wraps the value in &lt;code&gt;N'…'&lt;/code&gt; after doubling embedded single quotes — the SQL Server equivalent of &lt;code&gt;quote_literal&lt;/code&gt;. From the actual pivot builder (&lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/pvt_bundle.sql" rel="noopener noreferrer"&gt;&lt;code&gt;pvt_bundle.sql&lt;/code&gt;&lt;/a&gt;, the dict-key branch):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Real emitter for dict-key match in the CTE&lt;/span&gt;
&lt;span class="c1"&gt;-- (the dict key goes into _values._array_index — same slot used for array indices)&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;dict_key_literal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'N&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;dict_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Then concatenated into:&lt;/span&gt;
&lt;span class="c1"&gt;--   ... AND v.[_array_index] = N'HQ' THEN v.[_String] END) AS [...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;@dict_key = "HQ'; DROP TABLE _values; --"&lt;/code&gt;, the &lt;code&gt;REPLACE&lt;/code&gt; doubles the embedded quote to &lt;code&gt;''&lt;/code&gt;, and the emitted SQL becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'HQ&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;; DROP TABLE _values; --'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole payload stays a single string literal. The tail of the payload never escapes the quotes, so it never gets executed.&lt;/p&gt;

&lt;p&gt;For numerics the builder doesn't even concatenate raw user text — it first parses it with &lt;code&gt;TRY_CAST&lt;/code&gt; and only inlines the parsed value if the parse succeeded. From &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/06_pvt_hierarchical.sql" rel="noopener noreferrer"&gt;&lt;code&gt;06_pvt_hierarchical.sql&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TRY_CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_raw&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;' AND dbo.pvt_is_descendant_of('&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'.[_id], '&lt;/span&gt;
                 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;') = 1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- If @ha_id is NULL the clause is silently dropped — bad input never&lt;/span&gt;
&lt;span class="c1"&gt;-- becomes SQL text. The only thing that ever gets inlined is a real BIGINT.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TRY_CAST&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt; rather than raising on bad input, and the builder simply omits the clause in that case — so &lt;code&gt;"1; DROP TABLE _objects --"&lt;/code&gt; never reaches the SQL text. The behaviour is fail-closed (no clause emitted) rather than fail-loud (no exception thrown); the predicate effectively becomes a no-op. Same pattern for &lt;code&gt;WhereLevel&lt;/code&gt; (&lt;code&gt;TRY_CAST AS INT&lt;/code&gt;), &lt;code&gt;WhereHasDescendant&lt;/code&gt;, &lt;code&gt;WhereContainsObject&lt;/code&gt;, and the other base-id predicates. For dates it's &lt;code&gt;TRY_CONVERT(datetimeoffset, …)&lt;/code&gt;, for GUIDs it's &lt;code&gt;TRY_CONVERT(uniqueidentifier, …)&lt;/code&gt; — quote-then-typed-parse, drop-on-fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Identifiers come from &lt;code&gt;_structures&lt;/code&gt; via &lt;code&gt;QUOTENAME()&lt;/code&gt;, never from raw JSON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every pivot column alias, every projection alias, every table/column name in the dynamic SQL goes through &lt;code&gt;QUOTENAME()&lt;/code&gt;. From the pivot CTE builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;420&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QUOTENAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;-- @field_name comes from _structures&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'o.'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;QUOTENAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;' AS '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;QUOTENAME('weird]name')&lt;/code&gt; returns &lt;code&gt;[weird]]name]&lt;/code&gt; — embedded &lt;code&gt;]&lt;/code&gt; is doubled, the result is always a single safely-bracketed identifier. As on the PG side, the &lt;em&gt;source&lt;/em&gt; of &lt;code&gt;@field_name&lt;/code&gt; is a &lt;code&gt;_structures._name&lt;/code&gt; row matched by &lt;code&gt;(_id_scheme = @scheme AND _name = @json_key)&lt;/code&gt; — if the JSON refers to a field the queried scheme doesn't have, the lookup returns nothing and the field is rejected with a &lt;code&gt;RAISERROR&lt;/code&gt; before any SQL text is built.&lt;/p&gt;

&lt;p&gt;For base-fields (&lt;code&gt;0$:id&lt;/code&gt;, &lt;code&gt;0$:parent_id&lt;/code&gt;, &lt;code&gt;0$:scheme_id&lt;/code&gt;, …) there's a fixed CASE; unknown bases &lt;code&gt;RAISERROR&lt;/code&gt; with the same "Unknown RedbObject base field" message as the PG side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Operator names are a fixed CASE/IF whitelist with &lt;code&gt;RAISERROR&lt;/code&gt; on miss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same logic as PG: &lt;code&gt;$eq&lt;/code&gt;, &lt;code&gt;$gt&lt;/code&gt;, &lt;code&gt;$lt&lt;/code&gt;, &lt;code&gt;$in&lt;/code&gt;, &lt;code&gt;$like&lt;/code&gt;, &lt;code&gt;$arrayContains&lt;/code&gt;, &lt;code&gt;$expr&lt;/code&gt;, etc. are matched by a CASE tree. Anything not on the list raises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;RAISERROR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'pvt: unsupported operator "%s"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Sql.Function&lt;/code&gt; calls go through the same whitelist — &lt;code&gt;COALESCE&lt;/code&gt;, &lt;code&gt;LOWER&lt;/code&gt;, &lt;code&gt;UPPER&lt;/code&gt;, &lt;code&gt;LEN&lt;/code&gt;, &lt;code&gt;LEFT&lt;/code&gt;, &lt;code&gt;RIGHT&lt;/code&gt;, &lt;code&gt;ISNULL&lt;/code&gt;, etc. Unknown function names raise; the user can't smuggle &lt;code&gt;xp_cmdshell&lt;/code&gt; through &lt;code&gt;Sql.Function&amp;lt;int&amp;gt;("xp_cmdshell", "...")&lt;/code&gt; because that name isn't in the CASE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The dynamic SQL is built only from the three pieces above and executed with &lt;code&gt;sp_executesql&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Final execution is &lt;code&gt;EXEC sp_executesql @sql&lt;/code&gt; (with parameterized &lt;code&gt;@scheme_id&lt;/code&gt;, &lt;code&gt;@limit&lt;/code&gt;, &lt;code&gt;@offset&lt;/code&gt; etc. as &lt;code&gt;sp_executesql&lt;/code&gt; arguments, not concatenated). Every byte of &lt;code&gt;@sql&lt;/code&gt; came from either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;REPLACE&lt;/code&gt;-quoted string literal in &lt;code&gt;N'…'&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;TRY_CAST&lt;/code&gt;-validated typed value inlined as its parsed form, or&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;QUOTENAME&lt;/code&gt;-bracketed identifier sourced from &lt;code&gt;_structures&lt;/code&gt; by id lookup, or&lt;/li&gt;
&lt;li&gt;a hardcoded operator template chosen by a whitelist CASE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;99_smoke_auto.sql&lt;/code&gt; regression suite on MSSql includes the same adversarial payloads as the PG suite — &lt;code&gt;'; DROP TABLE …&lt;/code&gt;, embedded quotes, &lt;code&gt;]&lt;/code&gt; in field names, type-mismatched literals, unknown operators — and expects each one to be either rejected with &lt;code&gt;RAISERROR&lt;/code&gt; (operators / unknown bases / unknown identifiers), silently dropped from the predicate (typed-value parse failures), or come out the other side as an inert quoted literal that does nothing (string values).&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;PG Free emits via&lt;/th&gt;
&lt;th&gt;MSSql Free emits via&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;String values&lt;/td&gt;
&lt;td&gt;&lt;code&gt;format('… %L', val)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N'' + REPLACE(val, '''', '''''') + N''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Numeric / date / GUID values&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;%L::bigint&lt;/code&gt; / &lt;code&gt;%L::numeric&lt;/code&gt; / &lt;code&gt;%L::timestamptz&lt;/code&gt; / &lt;code&gt;%L::uuid&lt;/code&gt; (parse-or-die at runtime)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TRY_CAST(... AS BIGINT)&lt;/code&gt; / &lt;code&gt;TRY_CONVERT(datetimeoffset, ...)&lt;/code&gt; / &lt;code&gt;TRY_CONVERT(uniqueidentifier, ...)&lt;/code&gt; (parse-or-drop-clause before inline)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identifiers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;%I&lt;/code&gt; / &lt;code&gt;quote_ident(name_from__structures)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QUOTENAME(name_from__structures)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operators / function names&lt;/td&gt;
&lt;td&gt;CASE whitelist → &lt;code&gt;RAISE EXCEPTION&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CASE whitelist → &lt;code&gt;RAISERROR&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final execution&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EXECUTE&lt;/code&gt; of the assembled string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EXEC sp_executesql&lt;/code&gt; of the assembled string&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same three independent gates on both databases. Any one of them is sufficient to defeat injection; the suite tests all three.&lt;/p&gt;

&lt;p&gt;If you find a leak in either dialect, file an issue with the JSON payload that triggers it. I'll treat it as a security bug, not a feature request.&lt;/p&gt;




&lt;h2&gt;
  
  
  The polyglot facet API — Free's one structural advantage
&lt;/h2&gt;

&lt;p&gt;A side-effect of Free's SQL-side builder: it's accessible from any language. Pro compiles in C#, which is its strength but also its limitation — you need a .NET process. Free compiles in SQL, so the same builder C# Free clients hit is callable from any driver that can run SQL.&lt;/p&gt;

&lt;p&gt;The shipped surface is two functions, not one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pvt_build_query_sql(scheme_id, facets jsonb, limit, offset, order jsonb, max_depth, …)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;facets&lt;/td&gt;
&lt;td&gt;the assembled &lt;code&gt;SELECT _id FROM …&lt;/code&gt; as &lt;code&gt;text&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_objects_json(ids bigint[], max_depth integer DEFAULT 10)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;matched ids&lt;/td&gt;
&lt;td&gt;table of &lt;code&gt;(Id bigint, JsonData text)&lt;/code&gt; with fully hydrated objects&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest flow is therefore two SQL roundtrips: build → &lt;code&gt;EXECUTE&lt;/code&gt; the returned text → hydrate. The 10-line plpgsql wrapper below collapses that into a single call you can ship into your DB once and then call from any language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- one-time setup: paste into your DB as a wrapper of the two shipped functions&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;redb_run_facets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;p_scheme_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_facets&lt;/span&gt;    &lt;span class="n"&gt;jsonb&lt;/span&gt;   &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_limit&lt;/span&gt;     &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_offset&lt;/span&gt;    &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_order&lt;/span&gt;     &lt;span class="n"&gt;jsonb&lt;/span&gt;   &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_max_depth&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Id"&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"JsonData"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;STABLE&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;v_sql&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;v_ids&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;v_sql&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt_build_query_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_scheme_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_facets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_max_depth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="s1"&gt;'SELECT array_agg(_id) FROM ('&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;v_sql&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;') t'&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;v_ids&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;v_ids&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;RETURN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"JsonData"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;get_objects_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_max_depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that wrapper in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python — psycopg
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;psycopg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$and&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$gt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;City&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;London&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;psycopg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DSN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SELECT &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JsonData&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; FROM redb_run_facets(%s, %s::jsonb, %s, %s)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheme_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;# limit, offset
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="c1"&gt;# each row is JSON text — nested objects, arrays, dictionaries,
&lt;/span&gt;    &lt;span class="c1"&gt;# ListItem references — all fully reconstructed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node — pg&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$and&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$gt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;City&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$eq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT "JsonData" FROM redb_run_facets($1, $2::jsonb, $3, $4)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;schemeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JsonData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Go — pgx&lt;/span&gt;
&lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"$and":[{"Age":{"$gt":30}},{"City":{"$eq":"London"}}]}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;`SELECT "JsonData" FROM redb_run_facets($1, $2::jsonb, $3, $4)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;schemeID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// scan each row into a json.RawMessage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Java — JDBC&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{\"$and\":[{\"Age\":{\"$gt\":30}},{\"City\":{\"$eq\":\"London\"}}]}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"SELECT \"JsonData\" FROM redb_run_facets(?, ?::jsonb, ?, ?)"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schemeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;hydratedJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- raw psql — same engine, no driver at all&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"JsonData"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;redb_run_facets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{"$and":[{"Age":{"$gt":30}},{"City":{"$eq":"London"}}]}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather skip the wrapper and call the shipped functions directly, the two-roundtrip pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 1. build the query text&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pvt_build_query_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{"$and":[…]}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 2. EXECUTE the returned text on the client → collect _id values&lt;/span&gt;
&lt;span class="c1"&gt;-- 3. hydrate&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;get_objects_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON facet schema is a stable contract — see &lt;a href="https://github.com/redbase-app/redb/blob/main/docs/FreePvtQuery/FREE-OVER-PRO.md" rel="noopener noreferrer"&gt;&lt;code&gt;FREE-OVER-PRO.md&lt;/code&gt;&lt;/a&gt; for the full operator list. Tuple-key dictionaries, nested &lt;code&gt;RedbObject&amp;lt;T&amp;gt;&lt;/code&gt; references, &lt;code&gt;RedbListItem&lt;/code&gt; references with &lt;code&gt;.Id&lt;/code&gt;/&lt;code&gt;.Value&lt;/code&gt;/&lt;code&gt;.Alias&lt;/code&gt; — all reconstruct correctly in the JSON &lt;code&gt;get_objects_json&lt;/code&gt; emits. No schema knowledge needed on the client side.&lt;/p&gt;

&lt;p&gt;And the injection story above applies the same way here — your &lt;code&gt;psycopg&lt;/code&gt;/&lt;code&gt;pgx&lt;/code&gt;/&lt;code&gt;JDBC&lt;/code&gt; driver binds the JSON as a &lt;code&gt;jsonb&lt;/code&gt; parameter, so even your facet &lt;em&gt;payload&lt;/em&gt; doesn't touch a SQL parser until it's already inside the parameterized builder.&lt;/p&gt;

&lt;p&gt;This is Free-only by design. Pro's edge has always been the CLR-side compiled pipeline plus parallel materialization (&lt;code&gt;ProLazyPropsLoader&lt;/code&gt; does 2 bulk &lt;code&gt;_values&lt;/code&gt; SELECTs, indexes them into &lt;code&gt;ILookup&lt;/code&gt;, and &lt;code&gt;ProPropsMaterializer&lt;/code&gt; runs &lt;code&gt;Parallel.ForEach&lt;/code&gt; to assemble 20+ value types, nested objects, arrays, dictionaries, ListItem refs into typed &lt;code&gt;RedbObject&amp;lt;T&amp;gt;&lt;/code&gt; — all CPU-bound, zero further DB access). That stays where it is.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Free 3.0.0&lt;/th&gt;
&lt;th&gt;Pro 3.0.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query shape (PVT CTE, base pushdown, MAX/CASE ↔ array_agg)&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GroupBy + HAVING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ArrayGroupBy&lt;/code&gt; (unnest / OUTER APPLY)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;DistinctBy&lt;/code&gt;, &lt;code&gt;Sql.Function&lt;/code&gt; (whitelist), &lt;code&gt;$expr&lt;/code&gt; trees&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree queries (&lt;code&gt;WhereHasAncestor&lt;/code&gt;, &lt;code&gt;WhereLevel&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Window functions (&lt;code&gt;Lag&lt;/code&gt;/&lt;code&gt;Lead&lt;/code&gt;/&lt;code&gt;Rank&lt;/code&gt;/&lt;code&gt;DenseRank&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Polyglot facet API (Python / Node / Go / Java / raw SQL)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiled in C# (no JSON intermediate)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel CLR materialization (&lt;code&gt;Parallel.ForEach&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk save via &lt;code&gt;COPY&lt;/code&gt; protocol&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash-based &lt;code&gt;ChangeTracking&lt;/code&gt; (diff only what changed)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined &lt;code&gt;GroupBy&lt;/code&gt; + window + &lt;code&gt;AggregateBatch&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things to be honest about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free has higher latency on full-object load&lt;/strong&gt; than Pro — &lt;code&gt;get_object_json&lt;/code&gt; builds JSON in plpgsql and the client deserializes. Pro's bulk-+-parallel materialization is a real win when you're loading thousands of complex objects with 20+ nested fields each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro still has features Free won't get&lt;/strong&gt; — parallel bulk materialization (&lt;code&gt;ProLazyPropsLoader&lt;/code&gt; + &lt;code&gt;ProPropsMaterializer&lt;/code&gt;) and hash-based &lt;code&gt;ChangeTracking&lt;/code&gt; remain Pro-only. The query shape itself is now identical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the &lt;em&gt;query shape&lt;/em&gt; — the part that determines whether the database uses your indexes properly, whether the plan caches, whether 30 props in a WHERE becomes 30 &lt;code&gt;EXISTS&lt;/code&gt; or one pivot — that's the same in both tiers now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration notes
&lt;/h2&gt;

&lt;p&gt;If you're on &lt;code&gt;2.0.x&lt;/code&gt; Free, upgrading to &lt;code&gt;3.0.0&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update packages&lt;/strong&gt; — &lt;code&gt;RedBase.Core&lt;/code&gt;, &lt;code&gt;RedBase.Postgres&lt;/code&gt; / &lt;code&gt;RedBase.MSSql&lt;/code&gt; to &lt;code&gt;3.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First startup applies the v2-pvt bundle automatically.&lt;/strong&gt; The new module is additive at the C# API level — your existing LINQ-style queries just compile through the new path. Note: the legacy plpgsql function &lt;code&gt;search_objects_with_facets()&lt;/code&gt; (used only by pre-2.0 raw-SQL callers) is &lt;strong&gt;no longer installed by &lt;code&gt;redb_init.sql&lt;/code&gt;&lt;/strong&gt; — it lives under &lt;code&gt;redb.Postgres/sql/deprecated/&lt;/code&gt; if you still need it; paste it manually before upgrading if you had direct SQL callers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-existing config flag is now honored.&lt;/strong&gt; &lt;code&gt;DefaultStrictDeleteExtra&lt;/code&gt; in built-in presets &lt;code&gt;Development&lt;/code&gt;, &lt;code&gt;HighPerformance&lt;/code&gt;, &lt;code&gt;Migration&lt;/code&gt; was set to &lt;code&gt;false&lt;/code&gt; but silently ignored before 3.0.0. It now actually works — meaning those presets will no longer auto-delete &lt;code&gt;_structures&lt;/code&gt; rows for fields removed from your &lt;code&gt;Props&lt;/code&gt; class on startup. If you were relying on the old (silently-broken) behavior, set &lt;code&gt;c.DefaultStrictDeleteExtra = true&lt;/code&gt; explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSSql Free users&lt;/strong&gt;: the wide-CASE inline pivot is gone. If you had query-text-matching log scrapers, the new shape is &lt;code&gt;WITH pvt_cte AS (... MAX(CASE WHEN _id_structure = @p1 AND _array_index IS NULL THEN _X END) ...) SELECT o.* FROM _objects o JOIN pvt_cte pvt ON ...&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No source-level breaking API changes. No data migrations. If you've never touched the SQL surface directly, you can upgrade and not notice anything except your slow queries getting faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Known gaps I'd like help finding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nested-field cross-scheme JOIN&lt;/strong&gt; — &lt;code&gt;Where(o =&amp;gt; o.CurrentProject.Props.Name == "X")&lt;/code&gt; where &lt;code&gt;CurrentProject&lt;/code&gt; is a &lt;code&gt;RedbObject&amp;lt;OtherScheme&amp;gt;&lt;/code&gt; reference. Free PVT rejects the path; Pro &lt;code&gt;SchemeFieldResolver&lt;/code&gt; throws. Needs new infrastructure on both sides. Tracked in &lt;a href="https://github.com/redbase-app/redb/blob/main/docs/FreePvtQuery/FREE-OVER-PRO.md" rel="noopener noreferrer"&gt;&lt;code&gt;FREE-OVER-PRO.md §2 #6b&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSSql Free is fresh.&lt;/strong&gt; Two weeks of intensive testing, not two years. &lt;code&gt;EXPLAIN&lt;/code&gt; traces and failing repros are very welcome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polyglot facet API documentation&lt;/strong&gt; is thinner than the C# API. The operator surface is stable (it's what the C# parser emits), but it's not yet a documented wire-protocol RFC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There will be follow-up fixes after this post lands — that's expected. If you hit something, please open a thread in &lt;a href="https://github.com/redbase-app/redb/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; rather than sitting on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Free&lt;/span&gt;
dotnet add package RedBase.Core      &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0
dotnet add package RedBase.Postgres  &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0     &lt;span class="c"&gt;# or RedBase.MSSql&lt;/span&gt;

&lt;span class="c"&gt;# Pro (commercial)&lt;/span&gt;
dotnet add package RedBase.Core.Pro       &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0
dotnet add package RedBase.Postgres.Pro   &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0   &lt;span class="c"&gt;# or RedBase.MSSql.Pro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full 3.0.0 changelog: &lt;a href="https://github.com/redbase-app/redb/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Architecture deep-dive (with the same LINQ→SQL examples): &lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;https://redbase.app/architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Issues / bug reports: &lt;a href="https://github.com/redbase-app/redb/issues" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb/issues&lt;/a&gt; — please cite the SQL and the dialect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you read all the way to here, thank you. Tell me what's wrong with it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>postgres</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From Closed Internal Stack to Open-Source Ecosystem: I Finally Shipped Three Years of .NET Infrastructure</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Sat, 23 May 2026 16:07:54 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/from-closed-internal-stack-to-open-source-ecosystem-i-finally-shipped-three-years-of-net-4lcf</link>
      <guid>https://dev.to/rinat_kozin/from-closed-internal-stack-to-open-source-ecosystem-i-finally-shipped-three-years-of-net-4lcf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the&lt;br&gt;
&lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this exists (the actual pain)
&lt;/h2&gt;

&lt;p&gt;Before the architecture talk, the &lt;em&gt;why&lt;/em&gt;. Three years ago&lt;br&gt;
I watched my team — and every other .NET team I knew —&lt;br&gt;
burn most of their hours not on business logic but on&lt;br&gt;
plumbing the same four layers over and over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EF Core + migrations.&lt;/strong&gt; &lt;code&gt;DbContext&lt;/code&gt;, fluent mappings,
navigation properties, N+1 puzzles, lazy/eager loading,
a migration per field, separate read/write models if you
go CQRS. On a non-trivial object graph that's hundreds
of hours just for the data layer, before any business
logic is written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pile of integration connectors.&lt;/strong&gt; Kafka here,
RabbitMQ there, S3 for files, SFTP for the legacy
partner, an HTTP webhook for the new one, each one
hand-rolled with its own retry policy, dead-letter,
idempotency, serialization, health check, telemetry.
Ten integrations × 40-80 hours each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth.&lt;/strong&gt; OAuth flows, refresh tokens, scope checking,
claims transformation, multi-tenant, M2M, DPoP if you
want to be modern — built from &lt;code&gt;IdentityServer&lt;/code&gt; /
&lt;code&gt;Auth0&lt;/code&gt; / &lt;code&gt;Keycloak&lt;/code&gt; plus 200-600 hours of glue per
serious deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime.&lt;/strong&gt; Hot-reload without dropping in-flight
messages, graceful drain on redeploy, cluster
coordination, leader election, watchdog, dashboard —
usually missing entirely on .NET, or assembled from 5-7
unrelated NuGets that don't share conventions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst part isn't any single layer — it's the &lt;strong&gt;seams&lt;br&gt;
between them&lt;/strong&gt;. Where the auth user becomes the EF entity&lt;br&gt;
becomes the Kafka message becomes the S3 blob, every seam&lt;br&gt;
is one more serialization, one more mapping, one more&lt;br&gt;
versioning headache, one more place a 3 AM page comes from.&lt;/p&gt;

&lt;p&gt;RedBase is built so that &lt;strong&gt;business code is the only thing&lt;br&gt;
left to write&lt;/strong&gt;. The class IS the schema (no EF, no&lt;br&gt;
migrations). The 22 transports share one DSL (no per-connector&lt;br&gt;
plumbing). Identity is a &lt;code&gt;direct-vm://&lt;/code&gt; Route (calling auth&lt;br&gt;
is calling a function, not a network round-trip). Tsak gives&lt;br&gt;
you hot-reload, cluster, dashboard, drain out of the box. The&lt;br&gt;
seams collapse because everything lives on the same fabric.&lt;/p&gt;

&lt;p&gt;On the one full business workflow where I had honest before/after&lt;br&gt;
numbers — built the traditional way vs built on the RedBase&lt;br&gt;
stack — the human effort was &lt;strong&gt;roughly ~3,000 hours vs ~128&lt;br&gt;
hours&lt;/strong&gt;. That ratio isn't magic on any single layer (where&lt;br&gt;
each subsystem gives 3-5× at most); it comes from the seams&lt;br&gt;
no longer existing. When I asked Claude Opus 4.7 to&lt;br&gt;
sanity-check that estimate against typical .NET project&lt;br&gt;
breakdowns (data layer + integrations + auth + runtime +&lt;br&gt;
inter-seam testing), the order of magnitude held up.&lt;/p&gt;

&lt;p&gt;One stack, one team, one architectural style — so the team&lt;br&gt;
gets to write features instead of wiring infrastructure.&lt;br&gt;
&lt;strong&gt;That&lt;/strong&gt; is the project. Everything below is just how I got&lt;br&gt;
there.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RedBase&lt;/strong&gt; — a four-pillar open-source ecosystem for .NET&lt;br&gt;
that grew up inside one production system over three years&lt;br&gt;
and finally got shipped to the world this spring. Three&lt;br&gt;
pillars are now public on GitHub under Apache 2.0, and the&lt;br&gt;
fourth is in pre-release polish:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;redb&lt;/a&gt;&lt;/strong&gt; — typed&lt;br&gt;
object storage engine. Your C# class IS the schema. Two&lt;br&gt;
physical tables (&lt;code&gt;_objects&lt;/code&gt; + &lt;code&gt;_values&lt;/code&gt;), full LINQ, zero&lt;br&gt;
migrations, recursive-CTE tree queries, bulk &lt;code&gt;COPY BINARY&lt;/code&gt;&lt;br&gt;
saves on PostgreSQL and &lt;code&gt;SqlBulkCopy&lt;/code&gt; on SQL Server. Free&lt;br&gt;
core + Pro tier with tree-diff ChangeTracking saves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;redb-route&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
— Apache Camel for .NET. Fluent C# DSL for&lt;br&gt;
&lt;code&gt;From → Process → To&lt;/code&gt; pipelines, &lt;strong&gt;22 transport packages&lt;/strong&gt;&lt;br&gt;
(Kafka, RabbitMQ, MQTT, S3, gRPC, SFTP, AMQP, Azure&lt;br&gt;
Service Bus, IBM MQ, Elasticsearch, Redis, LDAP, FTP, HTTP,&lt;br&gt;
WebSocket, SignalR, Firebase, TCP, Mail, SQL, Quartz,&lt;br&gt;
generic File), &lt;strong&gt;80+ EIP patterns&lt;/strong&gt; (Splitter, Aggregator,&lt;br&gt;
CBR, Circuit Breaker, Saga, ...), compiled expression&lt;br&gt;
engine, transactional routes, OpenTelemetry built-in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;redb-tsak&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
— the .NET analogue of Apache Karaf / Camel K. Production&lt;br&gt;
runtime container: drop a &lt;code&gt;.tpkg&lt;/code&gt; bundle (ZIP with&lt;br&gt;
&lt;code&gt;manifest.json&lt;/code&gt; + entry-point DLLs + dependency DLLs +&lt;br&gt;
per-module JSON config) — or just a bare &lt;code&gt;.dll&lt;/code&gt; for the&lt;br&gt;
simplest cases — into &lt;code&gt;Libs/&lt;/code&gt;, get hot-reload without&lt;br&gt;
dropping in-flight messages, REST + Blazor dashboard,&lt;br&gt;
watchdog, Quartz scheduler, three deployment modes&lt;br&gt;
(Standalone / Single-node+redb / Cluster with leader&lt;br&gt;
election &amp;amp; auto-rebalance), per-module&lt;br&gt;
&lt;code&gt;AssemblyLoadContext&lt;/code&gt; isolation, API Key + HMAC-SHA256&lt;br&gt;
security, 30-command CLI, typed C# client. &lt;strong&gt;415+ tests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. redb.Identity&lt;/strong&gt; &lt;em&gt;(pre-release, repo opening soon)&lt;/em&gt; —&lt;br&gt;
OAuth 2.1 / OIDC server, architecturally transport-agnostic:&lt;br&gt;
every endpoint is a &lt;code&gt;direct-vm://&lt;/code&gt; Route. That gives two&lt;br&gt;
ready usage modes today: &lt;strong&gt;(a) in-process&lt;/strong&gt; — call the&lt;br&gt;
auth server directly from another module in the same&lt;br&gt;
run-time with zero network hop, zero serialization, zero&lt;br&gt;
HTTP stack (this is what &lt;code&gt;direct-vm://&lt;/code&gt; &lt;em&gt;is&lt;/em&gt;), and&lt;br&gt;
&lt;strong&gt;(b) HTTP&lt;/strong&gt; — a full OAuth 2.1 / OIDC HTTP facade is&lt;br&gt;
shipped and working. The other RPC-capable facades —&lt;br&gt;
gRPC, RabbitMQ RPC, AMQP request/reply, IBM MQ&lt;br&gt;
request/reply, WebSocket, SignalR, TCP — are on the&lt;br&gt;
roadmap and become near-trivial to add because the core&lt;br&gt;
logic is already wire-format independent. (The&lt;br&gt;
fire-and-forget transports in the Route set — Kafka&lt;br&gt;
producer, File, S3, Mail, etc. — don't fit an auth&lt;br&gt;
server, so they're correctly out of scope.) OpenIddict&lt;br&gt;
under the hood, REDB-backed storage, DPoP / PAR / Dynamic&lt;br&gt;
Client Registration / SCIM 2.0 / FIDO2 / WebAuthn /&lt;br&gt;
RFC 8417 shared-signals support.&lt;br&gt;
&lt;strong&gt;1751+ tests&lt;/strong&gt; passing before public release.&lt;/p&gt;

&lt;p&gt;Total scope: &lt;strong&gt;~385k LOC&lt;/strong&gt; (326k C# + 58k SQL) across&lt;br&gt;
~2200 source files, &lt;strong&gt;Apache 2.0&lt;/strong&gt;, NuGet-published, all&lt;br&gt;
docs at &lt;a href="https://redbase.app/why-redbase" rel="noopener noreferrer"&gt;redbase.app/why-redbase&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live docs and positioning:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/why-redbase" rel="noopener noreferrer"&gt;redbase.app/why-redbase&lt;/a&gt;
— full "Sound familiar?" pain-point walkthrough with
side-by-side Traditional-vs-RedBase code examples&lt;/li&gt;
&lt;li&gt;The docs site itself is powered by RedBase — every page,
example, and API reference is a REDB object in MSSQL. We
eat our own cooking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Published article series on dev.to&lt;/strong&gt;&lt;br&gt;
(&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824"&gt;author page&lt;/a&gt;) —&lt;br&gt;
the ecosystem is too large for one post, so I'm publishing&lt;br&gt;
a deep-dive series, one pillar at a time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;May 13&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/we-built-an-enterprise-integration-stack-for-net-from-scratch-eav-dsl-runtime-2l16"&gt;We built an enterprise integration stack
for .NET from scratch: EAV + DSL +
runtime&lt;/a&gt;
— overview of the whole stack&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;May 14&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/i-spent-a-year-building-apache-camel-for-net-heres-the-honest-state-of-it-150e"&gt;I spent a year building Apache Camel for
.NET. Here's the honest state of
it.&lt;/a&gt;
— discussion piece, what's done / what isn't&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;May 17&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0"&gt;redb.Route — Apache Camel for .NET:
22 transports, 30+ EIP patterns, compiled
DSL&lt;/a&gt;
— integration framework deep dive&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;May 21&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482"&gt;An EF Core alternative for .NET apps with
complex object graphs — full LINQ, no migrations, no
DbContext&lt;/a&gt;
— REDB storage engine deep dive (this is the one
that already triggered the multi-version-deploy thread
referenced below)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;…and the series continues. Tsak (runtime container,&lt;br&gt;
hot-reload, cluster, watchdog) gets its own post next,&lt;br&gt;
then Identity (transport-agnostic OAuth 2.1 / OIDC) when&lt;br&gt;
the repo opens, then a deep dive on the tree-diff&lt;br&gt;
ChangeTracking path, then a multi-DB benchmark post.&lt;br&gt;
A stack this size is a genuinely hard engineering&lt;br&gt;
problem to explain — trying to cram it into one article&lt;br&gt;
would lie about the depth. So I'm taking it one layer&lt;br&gt;
at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 30-second flavor:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Define schema — no migration needed&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="n"&gt;ShippingAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderLineProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Lines&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CouponProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Coupons&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Save entire object graph in one call&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Query with full LINQ over nested props&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;
             &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Qty&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateModify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Tree query with recursive CTE under the hood&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allProducts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TreeQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;londonHQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InStock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same dev defines a routing context with redb.Route.&lt;/span&gt;
&lt;span class="c1"&gt;// Real production patterns: REDB isn't a transport URI —&lt;/span&gt;
&lt;span class="c1"&gt;// it's accessed inside processors via DI (.ProcessWithRedb).&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersRouteBuilder&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilderBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders-raw"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EipParallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderLineProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// REDB store&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"OrderId"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                       &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Seconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://orders-enriched"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Staged queue with backpressure — exactly how the&lt;/span&gt;
        &lt;span class="c1"&gt;// production garage-sync route does it:&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://orders?concurrentConsumers=4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EnrichFromInventoryAsync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://partner-api/orders"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Pack as a .tpkg bundle (manifest.json + entry-point DLL&lt;/span&gt;
&lt;span class="c1"&gt;// + dependency DLLs + module config.json) — or for trivial&lt;/span&gt;
&lt;span class="c1"&gt;// modules, just drop the bare .dll — into Libs/ of a Tsak&lt;/span&gt;
&lt;span class="c1"&gt;// node. Tsak picks it up, runs it across the cluster,&lt;/span&gt;
&lt;span class="c1"&gt;// rebalances on node failure, drains old versions on hot&lt;/span&gt;
&lt;span class="c1"&gt;// redeploy.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Need the same flow gated by an OAuth scope?&lt;/span&gt;
&lt;span class="c1"&gt;// Two ready modes today:&lt;/span&gt;
&lt;span class="c1"&gt;//   * in-process — the call goes through direct-vm://&lt;/span&gt;
&lt;span class="c1"&gt;//     straight into redb.Identity, no network, no HTTP;&lt;/span&gt;
&lt;span class="c1"&gt;//   * over HTTP — standard OAuth 2.1 / OIDC endpoints.&lt;/span&gt;
&lt;span class="c1"&gt;// Tomorrow, the same auth server can also be exposed over&lt;/span&gt;
&lt;span class="c1"&gt;// any other RPC-capable Route transport (gRPC, RabbitMQ&lt;/span&gt;
&lt;span class="c1"&gt;// RPC, AMQP request/reply, IBM MQ, WebSocket, SignalR,&lt;/span&gt;
&lt;span class="c1"&gt;// TCP, ...) as those facades land — no change to the&lt;/span&gt;
&lt;span class="c1"&gt;// auth server itself:&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//     .Process(ctx =&amp;gt; ctx.RequireScope("orders.write"))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last paragraph — "drops a &lt;code&gt;.tpkg&lt;/code&gt; (or just a &lt;code&gt;.dll&lt;/code&gt;),&lt;br&gt;
gets hot-reload + cluster + dashboard + REST API + drain on&lt;br&gt;
redeploy + an auth server callable from the same routing&lt;br&gt;
fabric" — is the part I'm proudest of, because it's exactly&lt;br&gt;
the piece&lt;br&gt;
that's been missing from the .NET integration story for a&lt;br&gt;
decade. Java had Karaf, Camel K, and a mature OIDC story&lt;br&gt;
glued together. .NET had nothing equivalent that wasn't&lt;br&gt;
either vendor-locked or hand-rolled per project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before (early 2026):&lt;/strong&gt; all four pillars existed and&lt;br&gt;
worked. Years of work. Real production load — 3-node&lt;br&gt;
cluster, ~550 internal users at a HoReCa distributor,&lt;br&gt;
~500k business objects, ~15M &lt;code&gt;_values&lt;/code&gt; rows, three months&lt;br&gt;
running every operational integration in the company.&lt;br&gt;
And yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0 public repos.&lt;/strong&gt; Everything in a closed monorepo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 NuGet packages.&lt;/strong&gt; Even our own internal services
built it from source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 published docs.&lt;/strong&gt; A 14-line &lt;code&gt;README.md&lt;/code&gt; in each
project. "TODO: document this properly" in two of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 external users.&lt;/strong&gt; Nobody outside the company even
knew it existed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 stars, 0 issues, 0 community.&lt;/strong&gt; The classic
"we'll open-source it when it's ready" trap — except
it had been "ready enough for production" for over
a year and "ready to share" still felt one polish-pass
away every quarter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest version: I was scared. ~385k LOC of opinionated&lt;br&gt;
architecture decisions, exposed to public criticism, with&lt;br&gt;
my real name on it. Easier to keep shipping it inside a&lt;br&gt;
contract than to put it on the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After (May 2026):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 public GitHub repos&lt;/strong&gt; under
&lt;a href="https://github.com/redbase-app" rel="noopener noreferrer"&gt;github.com/redbase-app&lt;/a&gt;:
&lt;code&gt;redb&lt;/code&gt;, &lt;code&gt;redb-route&lt;/code&gt;, &lt;code&gt;redb-tsak&lt;/code&gt;. Apache 2.0.
CI, badges, real READMEs. The fourth (&lt;code&gt;redb-identity&lt;/code&gt;)
is in final polish — 1751+ tests green, repo opens
before the contest deadline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;43 NuGet packages published&lt;/strong&gt; (&lt;code&gt;redb.Core&lt;/code&gt;,
&lt;code&gt;redb.Postgres&lt;/code&gt;, &lt;code&gt;redb.Route.Kafka&lt;/code&gt;, &lt;code&gt;redb.Tsak.CLI&lt;/code&gt;,
...) with &lt;strong&gt;~18k cumulative downloads&lt;/strong&gt;, the bulk of
that (~15k) added in the active-launch window over the
last ~10 days, on a clean upward curve day over day.
External developers can &lt;code&gt;dotnet add package&lt;/code&gt; today.
Early signal: &lt;code&gt;redb.Route&lt;/code&gt; (the integration framework)
has already overtaken &lt;code&gt;redb.Core&lt;/code&gt; in download count
— 1497 vs 1482 — meaning the "Apache Camel for .NET"
positioning is landing, not just the storage story.
Inside Route, &lt;code&gt;redb.Route.Http&lt;/code&gt; is the fastest-growing
transport — confirming the hypothesis that HTTP is the
natural first entry-point for new users exploring the
DSL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketing/docs site live&lt;/strong&gt; at
&lt;a href="https://redbase.app" rel="noopener noreferrer"&gt;redbase.app&lt;/a&gt; — quick start,
architecture page, pricing, full API docs. The site
itself runs on REDB to prove the engine handles real
content load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 4-post dev.to series&lt;/strong&gt; already out (May 13 → May 21),
with more deep dives queued. The REDB-storage post is
already getting sharp technical questions in comments —
including one about multi-version rolling deploys that
surfaced a genuinely unsafe default in
&lt;code&gt;SyncStructuresFromTypeAsync&lt;/code&gt; (&lt;code&gt;strictDeleteExtra = true&lt;/code&gt;)
that I'd never noticed in three years of production use
because we always single-version-deploy in our internal
workflow. That defect went straight onto the fix list —
exactly the kind of feedback that justifies open-sourcing
in the first place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First reactions and shares from the broader .NET
community.&lt;/strong&gt; Validation that the niche is real.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The completion arc isn't "I wrote a thing." The arc is&lt;br&gt;
"I had a thing that worked for years inside one company&lt;br&gt;
and I finally let it leave the building." Different muscle&lt;br&gt;
entirely. Documentation, positioning, choosing what to call&lt;br&gt;
it (RedBase / REDB / redb — we picked one and renamed&lt;br&gt;
everything), writing READMEs for repos that had no audience&lt;br&gt;
for years, recording first impressions, drafting tweets,&lt;br&gt;
answering the first technical comment in public.&lt;/p&gt;

&lt;p&gt;That last one is the hardest part of shipping. The code&lt;br&gt;
was done. Letting it be judged was the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Honest framing first: a large part of this codebase is my&lt;br&gt;
own work, written before Copilot was a serious factor.&lt;br&gt;
The architectural decisions, the storage model, the&lt;br&gt;
two-table layout, the tree-diff save strategy, the&lt;br&gt;
&lt;code&gt;direct-vm://&lt;/code&gt; transport-agnostic routing fabric that lets&lt;br&gt;
&lt;code&gt;redb.Identity&lt;/code&gt; be called in-process today with zero&lt;br&gt;
network hop &lt;em&gt;and&lt;/em&gt; exposed over HTTP today &lt;em&gt;and&lt;/em&gt; grow&lt;br&gt;
additional facades over the RPC-capable Route transports&lt;br&gt;
(gRPC, RabbitMQ RPC, AMQP, IBM MQ, WebSocket, SignalR,&lt;br&gt;
TCP, ...) tomorrow — all without touching the auth server&lt;br&gt;
itself — all of that came from years of sitting, thinking,&lt;br&gt;
prototyping, and throwing prototypes away. No AI invented&lt;br&gt;
those.&lt;/p&gt;

&lt;p&gt;What Copilot changed was the &lt;em&gt;finishing arc&lt;/em&gt;. The codebase&lt;br&gt;
had the right shape, but it also had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;features sketched-in but not closed out;&lt;/li&gt;
&lt;li&gt;helper layers half-typed because "I'll do this properly
later";&lt;/li&gt;
&lt;li&gt;XML doc-comments missing on 80% of public surfaces;&lt;/li&gt;
&lt;li&gt;READMEs with &lt;code&gt;TODO: document this&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;corner cases I knew existed and had been deferring;&lt;/li&gt;
&lt;li&gt;six different naming conventions across four years of
rewrites that nobody had unified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copilot is what let me finally close all of that out&lt;br&gt;
without burning a year of evenings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it was a real force multiplier:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finishing deferred features.&lt;/strong&gt; Pieces I had on a&lt;br&gt;
mental backlog for months — round-trip serialization&lt;br&gt;
edge cases, missing operators on the LINQ provider,&lt;br&gt;
Tsak CLI commands I'd been meaning to add, missing&lt;br&gt;
transport options in &lt;code&gt;redb.Route&lt;/code&gt; — Copilot let me&lt;br&gt;
knock them out in evenings instead of weekends because&lt;br&gt;
it could read the surrounding code, infer the&lt;br&gt;
convention, and propose an implementation that I then&lt;br&gt;
reviewed and corrected. The decisions stayed mine.&lt;br&gt;
The typing speed and the "what was I about to do here"&lt;br&gt;
recovery time collapsed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structuring code I already had.&lt;/strong&gt; A lot of the work&lt;br&gt;
wasn't writing new logic — it was taking working but&lt;br&gt;
messy code, splitting it into the right files,&lt;br&gt;
extracting the right interfaces, renaming things&lt;br&gt;
consistently across ~2200 files. Copilot is excellent&lt;br&gt;
at that kind of mechanical-but-context-sensitive&lt;br&gt;
refactor where you need to keep semantics intact while&lt;br&gt;
moving things around.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comments and XML documentation at scale.&lt;/strong&gt; Adding&lt;br&gt;
&lt;code&gt;/// &amp;lt;summary&amp;gt;&lt;/code&gt; to thousands of public members with&lt;br&gt;
accurate descriptions of what each method actually&lt;br&gt;
does — by hand this is a multi-week slog and you'll&lt;br&gt;
give up after two days. With Copilot reading the&lt;br&gt;
implementation and proposing the comment, then me&lt;br&gt;
correcting where it was wrong, it became a steady&lt;br&gt;
background task that actually finished.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documentation and READMEs.&lt;/strong&gt; ~12 projects, each&lt;br&gt;
needing a consistent voice, accurate API examples,&lt;br&gt;
correct cross-references, and a quick-start that&lt;br&gt;
actually runs. With Copilot it became "draft → review&lt;br&gt;
→ correct → ship" instead of "stare at empty file →&lt;br&gt;
procrastinate." Same for the redbase.app positioning&lt;br&gt;
pages — the "Sound familiar? / WITH REDBASE" pattern&lt;br&gt;
you see on the site got drafted in one afternoon and&lt;br&gt;
then iterated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-file architectural navigation while debugging.&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;redb.Core.Pro&lt;/code&gt; ChangeTracking save path crosses&lt;br&gt;
&lt;code&gt;ValueTreeBuilder&lt;/code&gt;, &lt;code&gt;ValueTreeDiff&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;ProObjectStorageProviderBase&lt;/code&gt;, and the SQL bulk-ops&lt;br&gt;
layer. Holding all four files in working memory&lt;br&gt;
simultaneously while chasing a deduplication bug in&lt;br&gt;
array hash updates — Copilot did the "where is this&lt;br&gt;
called from, what's the contract" lookup, my brain did&lt;br&gt;
the fix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Honest fact-checking before publishing.&lt;/strong&gt; I once&lt;br&gt;
asked it to draft a reply claiming "old readers ignore&lt;br&gt;
unknown structures gracefully on multi-version deploys."&lt;br&gt;
It pushed back, we read the actual code together, and&lt;br&gt;
the answer turned out to be "reads are graceful, writes&lt;br&gt;
are destructive, and the production playbook&lt;br&gt;
compensates with runtime drain." That nuance got&lt;br&gt;
published. The first version would have been a small&lt;br&gt;
lie. Same loop surfaced the &lt;code&gt;strictDeleteExtra = true&lt;/code&gt;&lt;br&gt;
default that's been on the fix list ever since.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RFC-driven implementation (this one mattered a lot&lt;br&gt;
for Identity).&lt;/strong&gt; &lt;code&gt;redb.Identity&lt;/code&gt; is built almost&lt;br&gt;
entirely on top of public RFCs: OAuth 2.1 / 6749,&lt;br&gt;
Token Revocation 7009, Introspection 7662, Dynamic&lt;br&gt;
Client Registration 7591 / 7592, JWK Thumbprint 7638,&lt;br&gt;
SCIM 7644, OAuth for Native Apps 8252, Device&lt;br&gt;
Authorization 8628, Token Exchange 8693, DPoP 9449,&lt;br&gt;
Shared Signals 8417 — and that's not the full list.&lt;br&gt;
For a solo developer, staying strictly compliant with&lt;br&gt;
that many specs is &lt;em&gt;brutal&lt;/em&gt;: every endpoint has&lt;br&gt;
"MUST / SHOULD / MAY" clauses scattered across multiple&lt;br&gt;
§-sections of multiple documents, edge cases like&lt;br&gt;
"what status code on revoke of unknown token" (7009&lt;br&gt;
§2.1: still 200), "must DPoP-Nonce rotate on every&lt;br&gt;
bearing response" (9449 §8: yes), "is &lt;code&gt;dpop_jkt&lt;/code&gt; binding&lt;br&gt;
enforced at the token endpoint" (9449 §10.1: yes) \u2014&lt;br&gt;
miss one and you've shipped a non-compliant auth&lt;br&gt;
server. Copilot was constantly the "wait, RFC 7591 §3.2.1&lt;br&gt;
says the response MUST include a registration access&lt;br&gt;
token, did you cover that?" voice in the room. The&lt;br&gt;
test suite reflects that: 1751+ tests with explicit&lt;br&gt;
&lt;code&gt;"RFC XXXX §Y.Z: ..."&lt;/code&gt; assertion messages, written&lt;br&gt;
in the loop of &lt;em&gt;"I remember the spirit of this clause,&lt;br&gt;
Copilot, pull up the exact wording and let's make a&lt;br&gt;
test out of it."&lt;/em&gt; Without that, achieving real RFC&lt;br&gt;
compliance solo would have been a multi-year sub-project&lt;br&gt;
on its own.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The pattern that emerged:&lt;/strong&gt; I came in with the design&lt;br&gt;
and most of the code already in place. Copilot saw what&lt;br&gt;
was there, understood the conventions, and helped me&lt;br&gt;
&lt;em&gt;finish&lt;/em&gt;. Polish XML docs. Close out deferred TODOs.&lt;br&gt;
Write the documentation pages. Refactor the inconsistent&lt;br&gt;
bits. Catch the contradictions before they hit a public&lt;br&gt;
comment thread. That's a different relationship than&lt;br&gt;
"AI wrote my project." It's closer to having a very&lt;br&gt;
fast pair-programmer who actually read the codebase&lt;br&gt;
before sitting down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest summary:&lt;/strong&gt; ~385k LOC of infrastructure in three&lt;br&gt;
years of part-time work was the human heroic effort.&lt;br&gt;
&lt;em&gt;Finishing&lt;/em&gt; it — closing out the deferred features,&lt;br&gt;
filling in all the missing comments and documentation,&lt;br&gt;
unifying the naming, writing the READMEs, shipping the&lt;br&gt;
NuGet packages, drafting the dev.to articles, answering&lt;br&gt;
the first hard comment in public — that's where Copilot&lt;br&gt;
collapsed months into weeks. The contest prompt is&lt;br&gt;
"revive and finish a project you started but never&lt;br&gt;
completed." My project wasn't unfinished in design.&lt;br&gt;
It was unfinished in &lt;em&gt;all the small, deferred, boring,&lt;br&gt;
necessary things&lt;/em&gt; that turn a working codebase into a&lt;br&gt;
shippable product. Copilot is what made that finishing&lt;br&gt;
arc actually fit inside one spring.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next (already in flight, not on a wish-list)
&lt;/h2&gt;

&lt;p&gt;Shipping the four pillars wasn't the finish line — it was&lt;br&gt;
the &lt;em&gt;beginning&lt;/em&gt; of the public phase. Three big bets are&lt;br&gt;
already underway, in source, in this repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Free engine is overtaking Pro.&lt;/strong&gt; The&lt;br&gt;
&lt;a href="//../FreePvtQuery/FREE-OVER-PRO.md"&gt;docs/FreePvtQuery&lt;/a&gt;&lt;br&gt;
initiative is rewriting the REDB query path on top of&lt;br&gt;
PostgreSQL PVT functions, and the free tier is already&lt;br&gt;
&lt;em&gt;ahead&lt;/em&gt; of Pro on a long list of features:&lt;br&gt;
&lt;code&gt;$case&lt;/code&gt; / &lt;code&gt;$coalesce&lt;/code&gt; / &lt;code&gt;$cast&lt;/code&gt; / n-ary &lt;code&gt;$concat&lt;/code&gt; in&lt;br&gt;
projections, full regex predicates (&lt;code&gt;$regex&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;$iregex&lt;/code&gt;, &lt;code&gt;$regexReplace&lt;/code&gt;), extended math&lt;br&gt;
(&lt;code&gt;$power&lt;/code&gt;, &lt;code&gt;$sqrt&lt;/code&gt;, &lt;code&gt;$log&lt;/code&gt;, &lt;code&gt;$sin/cos/tan&lt;/code&gt;, ...),&lt;br&gt;
extended string ops (&lt;code&gt;$substring&lt;/code&gt;, &lt;code&gt;$replace&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;$indexOf&lt;/code&gt;, &lt;code&gt;$padLeft/Right&lt;/code&gt;), projection-level&lt;br&gt;
&lt;code&gt;DISTINCT ON&lt;/code&gt;, date-extract in &lt;code&gt;Select&lt;/code&gt;, and as of&lt;br&gt;
2026-05-23 — &lt;code&gt;HAVING&lt;/code&gt; for both regular and array&lt;br&gt;
&lt;code&gt;GroupBy&lt;/code&gt; (33/33 integration tests green across PG&lt;br&gt;
Free + PG Pro + MSSql Pro). Net effect: most of what&lt;br&gt;
used to be Pro-only is becoming free, and the&lt;br&gt;
free engine is gaining capabilities Pro doesn't have&lt;br&gt;
at all.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;REDB outside C#.&lt;/strong&gt; The whole point of building the&lt;br&gt;
query layer as language-neutral JSON AST evaluated by&lt;br&gt;
database-side PVT functions is that &lt;em&gt;the C# LINQ&lt;br&gt;
provider is just one front-end&lt;/em&gt;. The same &lt;code&gt;_objects&lt;/code&gt; +&lt;br&gt;
&lt;code&gt;_values&lt;/code&gt; storage, the same scheme registry, the same&lt;br&gt;
query AST can be driven from Python, from Google Apps&lt;br&gt;
Script, from any language that can speak Postgres or&lt;br&gt;
MSSQL — without porting the engine. That unlocks REDB&lt;br&gt;
as a shared object store across heterogeneous stacks,&lt;br&gt;
not just .NET shops. Architecturally, the work is&lt;br&gt;
already done: every query goes through JSON AST →&lt;br&gt;
PVT functions, no C#-side filter compilation required&lt;br&gt;
for the heavy path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Route keeps growing.&lt;/strong&gt; New transport connectors are&lt;br&gt;
on the roadmap, deep-dive articles for the Tsak&lt;br&gt;
runtime container and for &lt;code&gt;redb.Identity&lt;/code&gt; are queued&lt;br&gt;
up after the contest deadline, and the EIP catalogue&lt;br&gt;
is being expanded incrementally on top of the&lt;br&gt;
existing 80+ patterns. The fastest-growing transport&lt;br&gt;
(&lt;code&gt;redb.Route.Http&lt;/code&gt;) is also driving a focused docs&lt;br&gt;
pass on the HTTP path specifically.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest version: the ecosystem has been moving for&lt;br&gt;
years and barely anyone outside one production deployment&lt;br&gt;
knew. The potential is genuinely large — language-neutral&lt;br&gt;
object storage with a full EIP runtime &lt;em&gt;and&lt;/em&gt; a compliant&lt;br&gt;
identity server on top of the same fabric is not a thing&lt;br&gt;
that currently exists on .NET. That's the bet I'm&lt;br&gt;
finishing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Team:&lt;/strong&gt; solo submission. All commits, all docs, all&lt;br&gt;
articles by one author with Copilot in the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live in production:&lt;/strong&gt; 3-node cluster, ~550 internal&lt;br&gt;
users, ~500k objects, ~15M &lt;code&gt;_values&lt;/code&gt; rows, ~2500 hours&lt;br&gt;
uptime, zero data-layer incidents in 3 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; .NET 8 / 9, PostgreSQL 17, MS SQL 2022,&lt;br&gt;
Blazor Server, Quartz.NET, Npgsql, OpenIddict,&lt;br&gt;
OpenTelemetry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0 across all four pillars.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>An EF Core alternative for .NET apps with complex object graphs — full LINQ, no migrations, no DbContext</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Thu, 21 May 2026 17:36:11 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482</link>
      <guid>https://dev.to/rinat_kozin/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482</guid>
      <description>&lt;p&gt;Today I'd like to slow down a bit and talk about &lt;code&gt;redb.Core&lt;/code&gt; — the data engine at the heart of the RedBase ecosystem. The other pieces (&lt;code&gt;redb.Route&lt;/code&gt; for pipelines, &lt;code&gt;redb.Tsak&lt;/code&gt; for cluster runtime) lean on it, but this post is just about the database part.&lt;/p&gt;

&lt;p&gt;I've been working on this project for several years. It started as an attempt to get rid of migrations and turned into what it is now — a typed object store for .NET over PostgreSQL and MSSQL.&lt;/p&gt;

&lt;p&gt;It's not a weekend prototype. The free packages on NuGet are at version 2.0, there are 43 packages across the ecosystem, the architecture went through three rewrites, and as of this week it's been running 3 months on production at a 30-year national food distributor (more on that below).&lt;/p&gt;

&lt;p&gt;This post is a technical walkthrough of &lt;code&gt;redb.Core&lt;/code&gt; — what it is, how it differs from EF Core, what the generated SQL actually looks like, what the production workload looks like, and what's shipping next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What redb.Core actually is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/strong&gt; — Apache 2.0&lt;/p&gt;

&lt;p&gt;RedBase stores typed C# objects in &lt;strong&gt;two tables&lt;/strong&gt; (&lt;code&gt;_objects&lt;/code&gt; + &lt;code&gt;_values&lt;/code&gt;) over PostgreSQL or Microsoft SQL Server. Not JSON blobs. Not JSONB. Real typed columns with FK constraints — &lt;code&gt;NUMERIC(38,18)&lt;/code&gt; for money, &lt;code&gt;timestamptz&lt;/code&gt; for dates, &lt;code&gt;uuid&lt;/code&gt; for GUIDs. Real B-tree indexes. ACID transactions.&lt;/p&gt;

&lt;p&gt;The schema is your C# class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;Age&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;HireDate&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]?&lt;/span&gt; &lt;span class="n"&gt;Skills&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HomeAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;BonusByYear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That attribute is the entire schema definition. Call &lt;code&gt;SyncSchemeAsync&amp;lt;EmployeeProps&amp;gt;()&lt;/code&gt; once — done. Add a property next sprint — redeploy, call sync again. Old objects still work. No migration files. No DBA ticket. No 2am rollback story.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Save — entire object graph, one call&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Load — full graph, arrays, dicts, nested classes — all materialized&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Query — real LINQ, real SQL&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;seniors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;100_000&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;35&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;DbContext&lt;/code&gt;. No &lt;code&gt;Include&lt;/code&gt; chains. No &lt;code&gt;Add-Migration&lt;/code&gt;. No mapper layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Object graphs in one call
&lt;/h2&gt;

&lt;p&gt;This is the part that surprises people coming from EF. Props can contain other Props — single references, arrays, dictionaries — and the entire graph saves and loads as one operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                          &lt;span class="c1"&gt;// nested class&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="n"&gt;ShippingAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                    &lt;span class="c1"&gt;// nested class&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                         &lt;span class="c1"&gt;// array of classes&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[]&lt;/span&gt; &lt;span class="n"&gt;Payments&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;        &lt;span class="c1"&gt;// array of full objects with own IDs&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CouponProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Coupons&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// dict of objects&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Quarter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Reviews&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// tuple-key dict&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// entire graph persisted, FK ordering handled&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;loaded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// loaded.Props.Customer.Address — ready&lt;/span&gt;
&lt;span class="c1"&gt;// loaded.Props.Payments[0].Props — ready (full RedbObject with own Id, DateCreate, etc.)&lt;/span&gt;
&lt;span class="c1"&gt;// loaded.Props.Coupons["SUMMER20"].Props — ready&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In EF Core this would be 28 tables, ~40 &lt;code&gt;Include&lt;/code&gt;/&lt;code&gt;ThenInclude&lt;/code&gt; calls, manual junction tables for the many-to-many, and INSERT ordering that breaks every time someone adds a non-nullable FK without a default.&lt;/p&gt;

&lt;p&gt;In RedBase: one &lt;code&gt;SaveAsync&lt;/code&gt;, one &lt;code&gt;LoadAsync&lt;/code&gt;. The nested &lt;code&gt;RedbObject&lt;/code&gt; instances are real first-class objects — they have their own IDs, their own timestamps, they can be queried independently, they participate in tree structures. They are not denormalised JSON glued to the parent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the LINQ actually compiles to
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Where(e =&amp;gt; e.Salary &amp;gt; 100_000 &amp;amp;&amp;amp; e.Age &amp;gt;= 35)&lt;/code&gt; doesn't get serialized to JSON and re-parsed (that's the Free engine's path — covered later). In Pro, the C# expression tree is walked node-by-node and emitted as parameterized SQL. Roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;    &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
 &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parameterized. Plan-cached by PostgreSQL. One index scan on &lt;code&gt;(_id_structure, _id_object)&lt;/code&gt;, one aggregation pass, B-tree filter on flat columns. The number of filter fields doesn't change the shape of the query.&lt;/p&gt;

&lt;p&gt;The C# → SQL compiler handles arithmetic (&lt;code&gt;*&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;), &lt;code&gt;Math.*&lt;/code&gt;, &lt;code&gt;String.Contains/StartsWith/Trim/ToLower&lt;/code&gt;, &lt;code&gt;DateTime.Year/Month/...&lt;/code&gt;, nullable navigation (&lt;code&gt;x.Address?.City&lt;/code&gt;) compiled to &lt;code&gt;IS NOT NULL&lt;/code&gt;, the ternary operator compiled to &lt;code&gt;CASE WHEN&lt;/code&gt;, &lt;code&gt;StringComparison.OrdinalIgnoreCase&lt;/code&gt; compiled to native &lt;code&gt;ILIKE&lt;/code&gt;, dictionary access &lt;code&gt;dict["key"]&lt;/code&gt; compiled to pivot columns, and a few more edge cases that EF Core itself doesn't always handle.&lt;/p&gt;

&lt;p&gt;You can preview the SQL of any query without executing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToSqlStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like &lt;code&gt;IQueryable.ToQueryString()&lt;/code&gt; in EF, but works for trees, GroupBy, window functions too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why bulk save is so fast — two tables, two streams
&lt;/h2&gt;

&lt;p&gt;Something to understand before the change-tracking section: the storage layout is &lt;strong&gt;two tables&lt;/strong&gt;, so &lt;code&gt;SaveAsync&lt;/code&gt; of a batch is &lt;strong&gt;two bulk operations&lt;/strong&gt;, not N round-trips.&lt;/p&gt;

&lt;p&gt;On PostgreSQL the provider uses Npgsql's &lt;code&gt;BeginBinaryImportAsync&lt;/code&gt; — native COPY protocol, binary format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from redb.Postgres/Data/NpgsqlBulkOperations.cs&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginBinaryImportAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"COPY _objects (_id, _id_parent, _id_scheme, _name, ...) FROM STDIN (FORMAT BINARY)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;objectsList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartRowAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bigint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bigint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ... typed writes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompleteAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a batch save: one COPY stream writes the &lt;code&gt;_objects&lt;/code&gt; rows, another writes the &lt;code&gt;_values&lt;/code&gt; rows. Two streams, no per-row round-trips, no string-formatted INSERTs. MSSQL uses &lt;code&gt;SqlBulkCopy&lt;/code&gt; for the same role.&lt;/p&gt;

&lt;p&gt;This is why 1000 routes × ~40 fields = ~40,000 value rows save in tens of milliseconds inside the 200–300 ms budget. The bottleneck is the network round-trip and the COPY write, not the ORM machinery — there isn't any ORM machinery in the hot path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Change tracking without DbContext (Pro)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DbContext&lt;/code&gt; keeps an in-memory snapshot of every entity you load — that's how it knows what changed. It's also why it isn't thread-safe and why the cache dies with the request.&lt;/p&gt;

&lt;p&gt;RedBase Pro takes a different approach. With &lt;code&gt;PropsSaveStrategy.ChangeTracking&lt;/code&gt; (Pro only; the free tier uses &lt;code&gt;DeleteInsert&lt;/code&gt;), &lt;code&gt;SaveAsync&lt;/code&gt; does this on a batch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One bulk SELECT&lt;/strong&gt; of existing values for all object IDs being saved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build two &lt;code&gt;ValueTreeNode&lt;/code&gt; trees&lt;/strong&gt; — one from your in-memory objects, one from the DB state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structural diff&lt;/strong&gt; — subtrees with matching hashes are skipped entirely (no value comparison, no child traversal). Inserts, updates, and deletes are computed per node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three bulk operations&lt;/strong&gt; — &lt;code&gt;BulkInsertValuesAsync&lt;/code&gt;, &lt;code&gt;BulkUpdateValuesAsync&lt;/code&gt;, &lt;code&gt;BulkDeleteValuesAsync&lt;/code&gt; — each a single round-trip. Inserts go through COPY BINARY again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Net effect: changing one field in a deeply nested object emits one &lt;code&gt;UPDATE&lt;/code&gt;, not a full delete-and-reinsert of the entire props graph. And the comparison happens in C# on the application side — no &lt;code&gt;DbContext&lt;/code&gt; lifetime to worry about, safe to run from a background &lt;code&gt;Channel&lt;/code&gt; consumer or a &lt;code&gt;Parallel.ForEach&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(In the production code from the previous section, the application also does its &lt;strong&gt;own&lt;/strong&gt; &lt;code&gt;obj.ComputeHash()&lt;/code&gt; check at the route level. That part is optional business-code — you could call &lt;code&gt;SaveAsync&lt;/code&gt; on every route and the Pro tree-diff would still skip unchanged values internally. It's there as a coarse pre-filter so unchanged routes don't even enter the save pipeline at all, saving the diff work too.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Production deployment — the numbers
&lt;/h2&gt;

&lt;p&gt;The biggest deployment right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30-year national HoReCa food distributor&lt;/li&gt;
&lt;li&gt;~150,000 orders/month, ~20,000 B2B customers, 600+ cities&lt;/li&gt;
&lt;li&gt;3-node cluster, 4 cores / 8 GB RAM / 50 GB SSD per node&lt;/li&gt;
&lt;li&gt;~550 daily internal users (operators, drivers, supervisors, dispatch, back-office)&lt;/li&gt;
&lt;li&gt;10–15% CPU under full load&lt;/li&gt;
&lt;li&gt;Integrations: SAP, Kafka, RabbitMQ, GPS feeds, Mercury / EGAIS / government APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three months in production, no data-layer incidents. Two projects in the company use it, the second one came after the first one proved stable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real workload, real timings
&lt;/h3&gt;

&lt;p&gt;The hottest pipeline is the SAP monitoring sync. Every 60 seconds a SQL polling consumer calls a stored procedure on SAP S/4 (&lt;code&gt;usp_TsUM_MonitoringReport_xml&lt;/code&gt;), gets ~1000 transportation orders back as XML, syncs reference dictionaries (drivers, vehicles, list items), bulk-loads existing routes from RedBase, hash-compares each one, and saves only the changed ones.&lt;/p&gt;

&lt;p&gt;The whole loop fits in ~200–300 ms for ~1000 routes. The actual production code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://tsum"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tsum-processing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransportationOrder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Sync dictionaries (Drivers, Vehicles, ListItems) — only new/changed&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;DictionarySyncService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SyncFromOrdersAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversNew&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversChanged&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesNew&lt;/span&gt;
            &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesChanged&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListItemsRelinked&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;RefDataCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RefreshAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;syncMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Bulk load existing routes by Code — one query, ~1000 codes&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransportationRoute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueString&lt;/span&gt;&lt;span class="p"&gt;!))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existingByCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueString&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Merge: update if hash changed, insert if new, skip if unchanged&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toSave&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;skippedCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;routeProps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;MapOrderToRouteProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existingByCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hashBefore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nf"&gt;EnrichRouteFromOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt; &lt;span class="n"&gt;routeProps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;hashBefore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;skippedCount&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransportationRoute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Route &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;value_string&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;routeProps&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 4. One batched save — mixed inserts + updates&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"[TSUM] orders={Orders} routes(+{Created} ~{Updated} ={Skipped}) "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"drivers(+{DN} ~{DC}) vehicles(+{VN} ~{VC}) "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"sync={Sync} query={Query} save={Save} total={Total}ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skippedCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversNew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesNew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;syncMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saveMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TransportationRoute&lt;/code&gt; has ~40 fields and 12 &lt;code&gt;RedbListItem&lt;/code&gt; references (Driver, Vehicle, CarMark, ShippingPoint, BusinessType, PlaceTo, PlaceFrom, LoadingZone, TransportStatus, Risk, DeliveryStatus, LoadStatus) plus 2 object references to AD users. Every single one is a foreign key in the database. None of them require a JOIN at query time — the materializer handles it.&lt;/p&gt;

&lt;p&gt;Smaller queries (point-lookup of a single route, dictionary fetch, REST endpoints for the UI) run in &lt;strong&gt;50–100 ms&lt;/strong&gt; including HTTP overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's actually in the database
&lt;/h3&gt;

&lt;p&gt;After running this in production, the storage looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~1000 transportation routes/day, plus delivery points, transport snapshots, garage states, slice settings, slice snapshots, drivers, vehicles, yard places, AD user refs — about a dozen &lt;code&gt;[RedbScheme]&lt;/code&gt; classes in active use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~500,000 objects&lt;/strong&gt; in &lt;code&gt;_objects&lt;/code&gt; (routes, points, snapshots, dictionary items)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~15,000,000 rows&lt;/strong&gt; in &lt;code&gt;_values&lt;/code&gt; (every typed property of every object)&lt;/li&gt;
&lt;li&gt;All of that lives in &lt;strong&gt;2 tables&lt;/strong&gt; — &lt;code&gt;_objects&lt;/code&gt; and &lt;code&gt;_values&lt;/code&gt; — plus the system tables for schemes, structures, lists, users, permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;15 million typed value rows. Two tables. No 30-table schema. No 200 migration files. The query times above are on this dataset.&lt;/p&gt;

&lt;p&gt;If you tried to model the same domain in EF Core flat tables, you'd end up with roughly: &lt;code&gt;Routes&lt;/code&gt;, &lt;code&gt;RoutePoints&lt;/code&gt;, &lt;code&gt;TransportSnapshots&lt;/code&gt;, &lt;code&gt;GarageStates&lt;/code&gt;, &lt;code&gt;SliceSettings&lt;/code&gt;, &lt;code&gt;SliceSnapshots&lt;/code&gt;, &lt;code&gt;Drivers&lt;/code&gt;, &lt;code&gt;Vehicles&lt;/code&gt;, &lt;code&gt;CarMarks&lt;/code&gt;, &lt;code&gt;ShippingPoints&lt;/code&gt;, &lt;code&gt;BusinessTypes&lt;/code&gt;, &lt;code&gt;YardPlaces&lt;/code&gt;, &lt;code&gt;LoadingZones&lt;/code&gt;, &lt;code&gt;TransportStatuses&lt;/code&gt;, &lt;code&gt;DeliveryStatuses&lt;/code&gt;, &lt;code&gt;Risks&lt;/code&gt;, &lt;code&gt;TripRisks&lt;/code&gt;, &lt;code&gt;AdUsers&lt;/code&gt; — plus junction tables and audit tables for each. 30+ tables, dozens of migrations, and every schema change is a deploy event.&lt;/p&gt;

&lt;p&gt;In RedBase the schema change is &lt;code&gt;git push&lt;/code&gt;. The next &lt;code&gt;InitializeAsync()&lt;/code&gt; call adds the new structure rows. Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  What would EF Core look like here?
&lt;/h3&gt;

&lt;p&gt;I asked myself the same question before starting. Let's count what EF would need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TransportationRoute&lt;/code&gt; entity → 1 table&lt;/li&gt;
&lt;li&gt;12 &lt;code&gt;ListItem&lt;/code&gt; references → 12 lookup tables + 12 nullable FKs&lt;/li&gt;
&lt;li&gt;2 AD user references → 2 more FKs&lt;/li&gt;
&lt;li&gt;The 1000-route batch update → 1000 entities tracked in &lt;code&gt;DbContext&lt;/code&gt; for change detection&lt;/li&gt;
&lt;li&gt;Hash-based skip of unchanged objects → doesn't exist in EF out of the box; you write it manually&lt;/li&gt;
&lt;li&gt;The same logic across multiple processes/routes → &lt;code&gt;DbContext&lt;/code&gt; is per-request, the change-tracker cache dies at the end of every batch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The realistic EF flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load existing — needs Include for every lookup or you get N+1&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Routes&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Vehicle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CarMark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShippingPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusinessType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PlaceTo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PlaceFrom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadingZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransportStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Risk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeliveryStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RiskSetBy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KcResponsible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Map, mutate, SaveChanges — 1000 tracked entities, full diff snapshot in RAM&lt;/span&gt;
&lt;span class="c1"&gt;// SaveChanges fires N UPDATE statements (one per row) in a transaction&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice on a workload like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Include&lt;/code&gt; chain on 14 lookups produces a query that PostgreSQL/MSSQL can't always optimise cleanly (cartesian explosion risk; you split-query and pay multiple round trips).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SaveChanges&lt;/code&gt; on 1000 modified entities emits 1000 individual UPDATEs unless you reach for &lt;code&gt;EFCore.BulkExtensions&lt;/code&gt; or similar third-party libraries. RedBase ships COPY BINARY for inserts and a batched UPDATE path for updates in the box.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;DbContext&lt;/code&gt; snapshot of 1000 tracked entities, each with 14 included lookups, is real memory pressure. &lt;code&gt;AsNoTracking()&lt;/code&gt; is faster but you lose change detection — you have to re-implement it.&lt;/li&gt;
&lt;li&gt;The 200–300 ms budget on a 4-core container is not realistic in this scenario without significant manual optimisation and probably a separate bulk-update path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not saying EF can't do it. I'm saying the equivalent EF implementation is more code, more moving parts, and significantly slower without third-party packages. RedBase ships a single &lt;code&gt;SaveAsync(toSave)&lt;/code&gt; and a hash-based skip primitive in the box.&lt;/p&gt;

&lt;p&gt;The hash comparison itself (&lt;code&gt;obj.ComputeHash() != hashBefore&lt;/code&gt;) is application-level business code — it's optional; the Pro tree-diff inside &lt;code&gt;SaveAsync&lt;/code&gt; would skip unchanged values anyway. But used like this it lets the application skip the ~95% of routes that didn't change between SAP polls before they even enter the save pipeline. Combined with COPY BINARY into two tables and the Pro tree-diff for the routes that do change, the whole loop fits the 200–300 ms budget. No equivalent set of built-in primitives exists in EF Core; you either snapshot manually, re-save everything, or pull in third-party bulk packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  What EF Core does with complex objects (vs what RedBase does)
&lt;/h2&gt;

&lt;p&gt;The E000 benchmark uses &lt;code&gt;EmployeeProps&lt;/code&gt; — a realistic model with nested classes, arrays, dictionaries, and &lt;code&gt;RedbObject&lt;/code&gt; references:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What classic ORM needs&lt;/th&gt;
&lt;th&gt;RedBase&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~28 tables&lt;/td&gt;
&lt;td&gt;2 tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FK ordering on every INSERT&lt;/td&gt;
&lt;td&gt;Single &lt;code&gt;SaveAsync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40+ &lt;code&gt;Include&lt;/code&gt;/&lt;code&gt;ThenInclude&lt;/code&gt; calls&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LoadAsync&amp;lt;T&amp;gt;(id)&lt;/code&gt; — one line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration file for every new field&lt;/td&gt;
&lt;td&gt;Add property, call &lt;code&gt;SyncSchemeAsync&lt;/code&gt;, done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~5,000 INSERTs for 100 employees&lt;/td&gt;
&lt;td&gt;1 &lt;code&gt;BulkInsert&lt;/code&gt; via COPY protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For 100 complex employees: EF Core ≈ 4,000–6,000 INSERTs across 28 tables in FK order. RedBase: ~3,000 typed value rows, one COPY command.&lt;/p&gt;

&lt;p&gt;And the test results are published — 525 automated tests across all editions and both databases. All green.&lt;/p&gt;




&lt;h2&gt;
  
  
  The query engine: free vs Pro
&lt;/h2&gt;

&lt;p&gt;This is where it gets technically interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free edition&lt;/strong&gt; compiles your LINQ lambda to a JSON facet format, then calls a plpgsql stored procedure that parses that JSON and generates SQL dynamically. For simple 1–2 field filters, PostgreSQL optimizes correlated EXISTS subqueries well. It works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro edition&lt;/strong&gt; walks the C# expression tree directly in C# (&lt;code&gt;ExpressionToSqlCompiler&lt;/code&gt;), emits native parameterized SQL. Same plan every call — PostgreSQL can cache it. 3–10× faster on complex multi-field filters.&lt;/p&gt;

&lt;p&gt;But here's what's happening right now: &lt;strong&gt;I'm porting the Pro PVT CTE query engine into the free tier.&lt;/strong&gt; The plan is &lt;strong&gt;full parity on PVT&lt;/strong&gt; — same CTE shape, same expression coverage, same projection capabilities. Free and Pro will speak the same query dialect.&lt;/p&gt;

&lt;p&gt;The remaining differences between Free and Pro will be elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query plan caching.&lt;/strong&gt; Pro builds parameterized SQL in C# (&lt;code&gt;ExpressionToSqlCompiler&lt;/code&gt;) — same shape every call, PostgreSQL caches the plan once and reuses it. The free tier generates SQL inside plpgsql with literal values inlined (properly escaped to prevent injection), so each call can produce a slightly different plan. Same correctness, different plan-cache behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materialization.&lt;/strong&gt; Pro has a parallel materializer (multiple values streams hydrated concurrently into the object graph). Free uses a simpler sequential path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save strategy.&lt;/strong&gt; &lt;code&gt;PropsSaveStrategy.ChangeTracking&lt;/code&gt; (tree-diff with hash-based subtree skip) is Pro-only. Free uses &lt;code&gt;DeleteInsert&lt;/code&gt; — still bulk via COPY, but no per-field diff.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Pro stays faster on hot paths and large object graphs, but the query language itself — what you can write in &lt;code&gt;.Where&lt;/code&gt;, &lt;code&gt;.Select&lt;/code&gt;, projections, grouping, having, FTS, regex — will be the same on both sides.&lt;/p&gt;




&lt;h2&gt;
  
  
  Show me the SQL it generates
&lt;/h2&gt;

&lt;p&gt;Here's a real example — a projection query on &lt;code&gt;EmployeeProps&lt;/code&gt; with 16 computed columns:&lt;br&gt;
full names, salary calculations, date extractions, seniority classification via CASE, coalesce for nullable fields, explicit type cast. This is the &lt;strong&gt;free tier&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;_pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000017&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000020&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000018&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000016&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000015&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"FirstName"&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1000015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000016&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000018&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000020&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000014&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;                                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;UPPER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                             &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;upper_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;yearly_x12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;013&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;bonus_after_tax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                                       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_per_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;age_mod10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                                              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;abs_diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;floor_half_salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;YEAR&lt;/span&gt;  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                 &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hire_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MONTH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                 &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hire_month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;YEAR&lt;/span&gt;  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;AGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-20'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tenure_years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'senior'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'mid'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'junior'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;seniority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;unknown&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                             &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;                                                  &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;age_as_text&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_pvt_cte&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_pvt_cte&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;_values&lt;/code&gt; scan. One &lt;code&gt;GROUP BY&lt;/code&gt;. Sixteen computed output columns. Standard SQL — no magic, fully readable in &lt;code&gt;pgAdmin&lt;/code&gt;, fully &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;-able.&lt;/p&gt;

&lt;p&gt;This query was generated by &lt;code&gt;pvt_build_projection_sql()&lt;/code&gt; — a plpgsql function that is shipping in the next free release.&lt;/p&gt;




&lt;h2&gt;
  
  
  When this fits and when it doesn't
&lt;/h2&gt;

&lt;p&gt;Honest take.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits well:&lt;/strong&gt; business apps with collections, nested structures, hierarchies, dictionaries, or schemas that change often. Apps where a business analyst can ask "add a field" and you want to ship the same day. Anything where you'd otherwise end up with 28 tables for one logical entity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't fit:&lt;/strong&gt; flat reporting databases with two or three fixed tables. Use Dapper there — it's honest and fast.&lt;/p&gt;

&lt;p&gt;The common pushback is: &lt;em&gt;"EF Core is good enough for 80% of projects."&lt;/em&gt; We'd flip that around: RedBase fits &lt;strong&gt;80% of typical business projects&lt;/strong&gt; — anything with collections, nested structures, lookup tables, hierarchies, dictionaries, or a schema that evolves alongside the product. The 20% where it's overkill is the genuinely flat case: two or three fixed tables, pure reporting, no evolving model. Use Dapper there; it's honest and fast. For everything else — which is most business software — the EF migration tax is real and it compounds with every sprint.&lt;/p&gt;

&lt;p&gt;Three more axes that usually get left out of that comparison:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility.&lt;/strong&gt; Adding a field is a property in C# and one &lt;code&gt;InitializeAsync()&lt;/code&gt; call — no migration script, no review, no deploy window. The same change in an EF stack is at minimum: model edit + &lt;code&gt;Add-Migration&lt;/code&gt; + reviewed SQL + coordinated deploy. Multiply by every iteration in a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Floor skill level.&lt;/strong&gt; With RedBase, a junior developer who knows C# can add entities, fields, queries, and relationships on day one — the schema is just a class, the query is just LINQ. With EF Core on a complex model, you need to understand N+1, cartesian explosion from &lt;code&gt;Include&lt;/code&gt; chains, &lt;code&gt;DbContext&lt;/code&gt; lifetime, migration conflicts on shared branches, and when to reach for &lt;code&gt;AsNoTracking()&lt;/code&gt;. That knowledge takes months to build and years to apply consistently. RedBase moves that complexity into the framework and out of the application code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost of development.&lt;/strong&gt; On the TsUM project the data-layer work — schemas, storage, queries, sync pipelines, change-tracking, bulk save — came in at roughly &lt;strong&gt;128 person-hours&lt;/strong&gt;. The estimate for the same scope on a classic EF + handwritten bulk-update + ASP.NET Identity + 30-table migration stack was on the order of &lt;strong&gt;3000 person-hours&lt;/strong&gt;. That's not a typo and it's not marketing — it's the gap between "add a property, redeploy" and "add a column, write migration, update DTOs, update mapper, update repository, write tests for all of it". The 80%/20% framing only holds if you count lines of code; once you count engineer-hours over a project lifetime it inverts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What else is in the box
&lt;/h2&gt;

&lt;p&gt;195+ working examples in the repo. The shortlist of things people usually don't expect to be built-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Tree queries — recursive CTE underneath, no CTE to write yourself&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TreeQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;londonHQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InStock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Soft delete with atomic mark + background purge + progress in DB&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SoftDeleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also shipping in the core (this is the short list — the real list is much longer):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GroupBy + window functions in one query&lt;/li&gt;
&lt;li&gt;Built-in users / roles / permissions (no ASP.NET Identity dependency, no separate auth schema)&lt;/li&gt;
&lt;li&gt;Export / import via &lt;code&gt;.redb&lt;/code&gt; files (PostgreSQL ↔ MSSQL portability)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-database in one process&lt;/strong&gt; with full domain isolation — separate connections, separate scheme caches, separate object caches per domain; one app can talk to several independent RedBase databases at the same time without their metadata leaking into each other&lt;/li&gt;
&lt;li&gt;Scheme cache and object cache (both per-domain), invalidated on &lt;code&gt;SyncSchemeAsync&lt;/code&gt; / &lt;code&gt;SaveAsync&lt;/code&gt;, used by the materializer to skip repeated metadata lookups&lt;/li&gt;
&lt;li&gt;Atomic soft-delete with background purge and progress visible in the DB&lt;/li&gt;
&lt;li&gt;Tree queries with recursive CTE&lt;/li&gt;
&lt;li&gt;Polymorphic trees — different C# types at each level of the same tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a lot more that doesn't fit in one section. The architecture page has the full map.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's coming next
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;FreePvtQuery&lt;/strong&gt; release is the nearest milestone. This is a complete rewrite of the free tier's query engine based on the PVT CTE architecture — bringing it to full parity with Pro on the query side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-pass &lt;code&gt;_values&lt;/code&gt; scan (vs correlated EXISTS per field)&lt;/li&gt;
&lt;li&gt;Full expression system: arithmetic, string, date, math, regex, FTS, CASE, coalesce, cast&lt;/li&gt;
&lt;li&gt;Projections with arbitrary computed columns (like the SQL above)&lt;/li&gt;
&lt;li&gt;HAVING, DISTINCT ON, GROUP BY extensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that: MSSQL support for the new engine, C# LINQ-to-JSON bridge so you keep writing &lt;code&gt;.Where(e =&amp;gt; e.Salary &amp;gt; 100_000)&lt;/code&gt; without touching JSON. Pro will keep its edge on plan caching (parameterized SQL), parallel materialization, and tree-diff change tracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is maybe 10% of what's in there
&lt;/h2&gt;

&lt;p&gt;This post covered the basics: storage model, query engine, free PVT demo, production deployment. Topics I didn't touch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polymorphic tree hierarchies (different C# types at every level, same tree API)&lt;/li&gt;
&lt;li&gt;Object graphs with &lt;code&gt;RedbObject&amp;lt;T&amp;gt;&lt;/code&gt; references inside Props&lt;/li&gt;
&lt;li&gt;Data migrations (Pro): fluent API, computed columns, in-place type changes&lt;/li&gt;
&lt;li&gt;Export/import: &lt;code&gt;.redb&lt;/code&gt; files (JSONL/ZIP), PostgreSQL ↔ MSSQL portability&lt;/li&gt;
&lt;li&gt;Multi-database in one process (domain-isolated caches)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redb.CLI&lt;/code&gt; — schema management from command line&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redb.PropsEditor&lt;/code&gt; — runtime props editing UI&lt;/li&gt;
&lt;li&gt;Integration with &lt;code&gt;redb.Route&lt;/code&gt; (22-transport pipeline engine) — separate article&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there's a direction you want covered next — migrations, polymorphic trees, benchmarks, internals of the diff-tree change tracking — drop it in the comments and that's what the next post will be.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;There's a lot of material. If you want to go deep without reading three articles, paste these into your AI of choice and ask it to analyse the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;redbase.app/architecture&lt;/a&gt; — expression compiler, PVT CTE, parallel materialization, diff tree save, caching (with actual class names)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/why-redbase" rel="noopener noreferrer"&gt;redbase.app/why-redbase&lt;/a&gt; — 14 pain points, side-by-side SQL&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/examples" rel="noopener noreferrer"&gt;redbase.app/examples&lt;/a&gt; — 195+ examples, every one runs against a real DB&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/results" rel="noopener noreferrer"&gt;redbase.app/results&lt;/a&gt; — 525 tests, MS/PG × Free/Pro, with timings&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt; — source, Apache 2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions in the comments — I'll answer them directly.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>redb.Route — Apache Camel for .NET: 22 transports, 30+ EIP patterns, compiled DSL</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Sun, 17 May 2026 10:26:13 +0000</pubDate>
      <link>https://dev.to/rinat_kozin/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0</link>
      <guid>https://dev.to/rinat_kozin/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0</guid>
      <description>&lt;p&gt;Apache Camel has been solving enterprise integration on the JVM since 2007 — 22k stars, 300+ transports, hundreds of production deployments at banks, telcos, governments. The .NET ecosystem never got a real equivalent. MassTransit and Wolverine cover message-bus and saga scenarios well, but they aren't pipeline engines and they don't pretend to be.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;redb.Route&lt;/a&gt; is the missing piece: a fluent C# DSL that wires Kafka, RabbitMQ, Redis, SQL, HTTP, gRPC, SFTP, MQTT, S3 and 14 more transports through &lt;code&gt;From → Process → To&lt;/code&gt; pipelines, with 30+ Enterprise Integration Patterns and a compiled expression engine. Apache 2.0, .NET 8 / 9 / 10. This post is a technical walkthrough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The shape of every pipeline
&lt;/h2&gt;

&lt;p&gt;Every redb.Route pipeline is &lt;code&gt;From → [processors] → To&lt;/code&gt;. Messages flow as &lt;code&gt;IExchange&lt;/code&gt; instances carrying a body, headers, and properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders?groupId=svc&amp;amp;brokers=localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://events?host=localhost"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For complex routing, group routes into a &lt;code&gt;RouteBuilder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderRoutes&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders?groupId=svc&amp;amp;brokers=localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-pipeline"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"High priority order"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://fast-lane"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://batch-queue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://standard"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://fast-lane"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"processed-at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"PROCESSED: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed?host=localhost"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddRouteBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderRoutes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRouteKafka&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRouteRabbitMQ&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  22 transports as first-class URI schemes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kafka://          rabbitmq://       redis://          sql://
http://           grpc://           sftp://           ftp://
mqtt://           s3://             ibmmq://          amqp://
azuresb://        elasticsearch://  firebase://       ldap://
mail://           tcp://            websocket://      signalr://
cron://           file://

+ built-in: direct://   seda://   timer://   log://   mock://
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every transport uses the same &lt;code&gt;IExchange&lt;/code&gt; contract. Swapping Kafka for RabbitMQ means changing the &lt;code&gt;From&lt;/code&gt; URI — the pipeline logic is unchanged.&lt;/p&gt;

&lt;p&gt;Type-safe fluent builders are available as an alternative to URI strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Brokers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"broker1:9092,broker2:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-svc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Acks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"All"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://internal"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  EIP patterns as DSL steps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Content-Based Router&lt;/strong&gt; — route by message content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://events"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://invoices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://unclassified"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Splitter + Aggregator&lt;/strong&gt; — split a batch, process each item, re-aggregate by correlation key. Sequential by default; one method turns the split into a bounded-parallel pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://batch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                                     &lt;span class="c1"&gt;// Body() helper — split the IEnumerable body&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParallelProcessing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                          &lt;span class="c1"&gt;// process items concurrently&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxDegreeOfParallelism&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                     &lt;span class="c1"&gt;// bounded fan-out&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;EnrichOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://enriched"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndSplit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://enriched"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"batch-id"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ListAggregationStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed-batches"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For true fan-out to multiple endpoints in parallel use &lt;strong&gt;Multicast&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Multicast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"direct://inventory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"direct://fraud-check"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WireTap&lt;/strong&gt; — copy every message to an audit sink without interrupting the main flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://transactions"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://audit-log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://completed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Idempotent Consumer&lt;/strong&gt; — deduplicate across a cluster. Plug in the repository (in-memory, SQL, or redb.Core EAV) and the same message ID is rejected on every node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://payments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdempotentConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"payment-id"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RedbIdempotentRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redbService&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// cluster-wide, two-phase commit&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Saga&lt;/strong&gt; — pipeline-level choreography with compensating steps. If &lt;code&gt;ChargePayment&lt;/code&gt; throws, &lt;code&gt;ReleaseInventory&lt;/code&gt; runs automatically. No external state-machine framework required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://checkout"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;shipments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;shipments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;PublishOrderCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://order-confirmed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full catalogue: Filter, Choice, Splitter, Aggregator, Multicast, WireTap, Recipient List, Dynamic Router, Resequencer, Scatter-Gather, Claim Check, Idempotent Consumer, Saga, Circuit Breaker, Throttle, Retry, Dead Letter, Loop, Delay, Debounce, Enrich, Timeout, TryCatch, Transacted, Process, Validate. &lt;strong&gt;30+ patterns, all first-class DSL.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Error handling — four composable layers
&lt;/h2&gt;

&lt;p&gt;Most .NET libraries give you one mechanism for failures: a retry policy on the consumer. redb.Route exposes four, designed to compose: per-step &lt;code&gt;Retry&lt;/code&gt;, scoped &lt;code&gt;DoTry/DoCatch&lt;/code&gt;, route-local &lt;code&gt;OnException&lt;/code&gt;, and global &lt;code&gt;OnException&lt;/code&gt; declared at the &lt;code&gt;RouteBuilder&lt;/code&gt; level. Plus &lt;code&gt;DeadLetterChannel&lt;/code&gt; for messages that exhaust retries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dead Letter Channel + a DLQ sub-route that knows why it failed
&lt;/h3&gt;

&lt;p&gt;The failing exception travels with the exchange. The DLQ sub-route can read it, branch on the type, log structured info, archive, retry later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeadLetterChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://orders-dlq"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The DLQ is a real route — inspect, branch, react&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://orders-dlq"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DLQ: ${header.correlationId} — ${exception.message}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://retry-later"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sftp://archive/http-failures/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sftp://archive/poison/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OnException — per-exception redelivery with exponential backoff
&lt;/h3&gt;

&lt;p&gt;Declared globally at &lt;code&gt;RouteBuilder&lt;/code&gt; level (applies to every route in the builder) or scoped to a single route. Each block configures attempts, delay, backoff, and what to do when the handler succeeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderRoutes&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Global — applies to every From(...) below&lt;/span&gt;
        &lt;span class="n"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaximumRedeliveries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RedeliveryDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseExponentialBackOff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BackOffMultiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                          &lt;span class="c1"&gt;// mark as handled — exchange continues normally&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://http-failures"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndOnException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DbException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaximumRedeliveries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseOriginalMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;               &lt;span class="c1"&gt;// restore original body before sending to handler&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnWhen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!((&lt;/span&gt;&lt;span class="n"&gt;DbException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetException&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"deadlock"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://db-failures"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndOnException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Multiple exception types in one block&lt;/span&gt;
        &lt;span class="nf"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketException&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaximumRedeliveries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RedeliveryDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://network-failures"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndOnException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://payments-svc/charge"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://shipments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://logistics-svc/dispatch"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Handled()&lt;/code&gt;, &lt;code&gt;Continued()&lt;/code&gt;, &lt;code&gt;OnWhen(predicate)&lt;/code&gt;, &lt;code&gt;RetryWhile(predicate)&lt;/code&gt;, &lt;code&gt;UseOriginalMessage()&lt;/code&gt; — all standard Camel error-handling primitives, and none of the .NET alternatives ship them as DSL.&lt;/p&gt;

&lt;h3&gt;
  
  
  TryCatch — scoped try/catch/finally inside a pipeline
&lt;/h3&gt;

&lt;p&gt;When only one section of a route needs special handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DoTry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://external-api/submit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;PostProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoCatch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP failure: ${exception.message}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://retry-queue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoCatch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sftp://archive/timeouts/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DoFinally&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Attempt complete"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Compiled expression engine
&lt;/h2&gt;

&lt;p&gt;This is the one feature that distinguishes redb.Route from both Apache Camel and every .NET alternative. Inline expressions — string templates, arithmetic, comparisons, JSONPath, XPath — are translated to real &lt;code&gt;Func&amp;lt;IExchange, T&amp;gt;&lt;/code&gt; delegates via &lt;code&gt;System.Linq.Expressions&lt;/code&gt; at route-build time. No interpreter, no per-message parsing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// String templates&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.orderId}-${body}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.source}-${header.correlationId}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Pre/post-increment&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.attempt++}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// returns old value&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${++header.attempt}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// returns new value&lt;/span&gt;

&lt;span class="c1"&gt;// Arithmetic&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.qty * header.price}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.gross - header.tax}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Predicates — fluent on top of compiled expressions&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"header.amount"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apache Camel's Simple Language is interpreted at every message dispatch. MassTransit, Wolverine and NServiceBus have &lt;strong&gt;no expression engine at all&lt;/strong&gt; — every conditional is a hand-written C# lambda. With redb.Route you get both: terse string DSL for configuration-driven rules &lt;strong&gt;and&lt;/strong&gt; strongly typed lambdas where you want them, with the same zero-overhead delegate at the bottom.&lt;/p&gt;

&lt;p&gt;The engine supports 9 value types and 17 predicates, and the result of every &lt;code&gt;Expr(...)&lt;/code&gt; is cached per route. You pay for parsing once at startup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transactional pipelines
&lt;/h2&gt;

&lt;p&gt;A pipeline can wrap several steps in a single transaction. &lt;code&gt;.Transacted()&lt;/code&gt; opens a &lt;code&gt;TransactionScope&lt;/code&gt;; transports that implement &lt;code&gt;ITransactedAction&lt;/code&gt; enlist into it. That means the Kafka commit, the RabbitMQ publisher confirm, and the SQL &lt;code&gt;UPDATE&lt;/code&gt; all succeed or fail together — no half-processed messages, no manual two-phase coordination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Brokers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"broker:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-svc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsolationLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReadCommitted"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableAutoCommit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                      &lt;span class="c1"&gt;// commit driven by .Transacted()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSERT INTO orders (...) VALUES (...)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                              &lt;span class="c1"&gt;// enlists into the same scope&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Rabbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-events"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Confirms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;                        &lt;span class="c1"&gt;// publisher confirm before commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MassTransit, NServiceBus and Wolverine all solve this for their own bus, but only for the &lt;em&gt;bus&lt;/em&gt;. redb.Route makes it work across &lt;strong&gt;any combination of transports&lt;/strong&gt; that implement &lt;code&gt;ITransactedAction&lt;/code&gt; — Kafka EOS, RabbitMQ tx channels, IBM MQ, AMQP 1.0, SQL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Outbox without an outbox framework
&lt;/h2&gt;

&lt;p&gt;The transactional outbox pattern is usually presented as a feature you opt into via a framework (MassTransit, NServiceBus, Wolverine all bundle one). In redb.Route it's just four lines composed from existing primitives — SQL polling, transactional sink, idempotent consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT id, payload FROM outbox WHERE processed = 0 LIMIT 100"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UPDATE outbox SET processed = 1 WHERE id = ANY(@ids)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                                  &lt;span class="c1"&gt;// atomic claim + publish&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdempotentConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"eventId"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RedbIdempotentRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redbService&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;// dedup on republish&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableTransactionalProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Acks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"All"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;                             &lt;span class="c1"&gt;// exactly-once on the broker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No magic table conventions, no separate &lt;code&gt;IOutbox&lt;/code&gt; interface to register, no required ORM. It's pipelines all the way down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Request-Response — HTTP and gRPC as first-class endpoints
&lt;/h2&gt;

&lt;p&gt;The same DSL that handles fire-and-forget Kafka also handles synchronous RPC. Mark the listener &lt;code&gt;InOut()&lt;/code&gt;, set &lt;code&gt;In.Body&lt;/code&gt; to the response anywhere in the pipeline — the HTTP transport sends it back to the caller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/orders"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;InOut&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonMessageSerializer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="s"&gt;"Amount must be positive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                      &lt;span class="c1"&gt;// HTTP transport returns In.Body to the caller&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://order-created"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// audit — fire-and-forget, non-blocking&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonMessageSerializer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;Http.Listen&lt;/code&gt; with &lt;code&gt;Grpc.Listen&lt;/code&gt; or &lt;code&gt;Ws.Listen&lt;/code&gt; — same pipeline. RPC, validation, business logic, audit, and serialization in one declarative route. No separate controller layer, no separate consumer layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing without a broker — &lt;code&gt;mock://&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;mock:&lt;/code&gt; transport records every message it receives so unit tests can assert against an in-memory endpoint. No Kafka container, no RabbitMQ container, no Testcontainers — a plain &lt;code&gt;Host&lt;/code&gt; and a few lines of xUnit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Filter_only_forwards_new_orders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDefaultBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mock://received"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRouteProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MockComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"payload-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"new"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"payload-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"old"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReceivedCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payload-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReceivedExchanges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For async routes use &lt;code&gt;MockDsl.Endpoint("name").ExpectedMessageCount(n)&lt;/code&gt; — it awaits the expected count with a timeout. This is the Camel testing idiom and one of the reasons unit tests on Camel routes are pleasant. .NET integration libraries usually leave you to Testcontainers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Telemetry — OpenTelemetry built in, on by default
&lt;/h2&gt;

&lt;p&gt;Every step in every route emits an &lt;code&gt;Activity&lt;/code&gt; and a &lt;code&gt;Meter&lt;/code&gt; sample. No &lt;code&gt;AddInstrumentation&lt;/code&gt;, no per-step manual spans, no decorator wrapping. &lt;code&gt;EnableTelemetry&lt;/code&gt; and &lt;code&gt;EnableMetrics&lt;/code&gt; are &lt;code&gt;true&lt;/code&gt; out of the box — point your collector at the process and you immediately see per-route traces and per-step latency.&lt;/p&gt;

&lt;p&gt;For named sections that should show up as their own span or counter in Grafana / Jaeger / Tempo, the DSL has &lt;code&gt;Traced&lt;/code&gt; and &lt;code&gt;Metered&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Traced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-processing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                   &lt;span class="c1"&gt;// one Activity for the whole block&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;JPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$.order"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndTraced&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Metered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-throughput"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;// counter + histogram for the block&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndMetered&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Inline form when the named span wraps a single step&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Traced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ValidateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Metered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard metric names: &lt;code&gt;redb.route.messages.processed&lt;/code&gt;, &lt;code&gt;redb.route.messages.failed&lt;/code&gt;, &lt;code&gt;redb.route.processing.duration&lt;/code&gt; — emitted per route and per named step. They drop straight into any OTel-compatible backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a real production route looks like
&lt;/h2&gt;

&lt;p&gt;The examples above are deliberately short. Real routes nest: HTTP listener → auth → permission check → method dispatch → business handler → audit tap. Indentation is the route hierarchy. The whole shape of the request is visible top-down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;From  http://0.0.0.0:5090/api/.../settings   (inOut, cors)
  Process            Auth.ProcessAsync
  ConvertBody&amp;lt;string&amp;gt;
  Choice on Header redbHttp.Method
  ├─ POST
  │    RequirePermission   EditSettingsTables
  │    ProcessWithRedb     HandlePost
  │    WireTap → direct://audit   (onPrepare + newBodyFactory)
  ├─ DELETE
  │    RequirePermission   EditSettingsTables
  │    ProcessWithRedb     HandleDelete
  │    WireTap → direct://audit
  └─ else
       ProcessWithRedb     HandleGet
  EndChoice
Respond → HTTP caller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one route from the production system mentioned at the bottom of the post — settings API for a logistics admin panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http:0.0.0.0:5090/api/tsum/special-rc-settings?inOut=true&amp;amp;cors=true&amp;amp;corsOrigins=*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tsum-api-special-rc-settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessAsync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;// attach IPrincipal to exchange&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConvertBody&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redbHttp.Method"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumAuthProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequirePermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumPermission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EditSettingsTables&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandlePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://tsum-audit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;onPrepare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DATA_CHANGE"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;newBodyFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildDetailsJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redbHttp.Method"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumAuthProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequirePermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumPermission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EditSettingsTables&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://tsum-audit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;onPrepare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DATA_CHANGE"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;newBodyFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildDetailsJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;HandleGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IRedbService&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IExchange&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;shippingPointId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ParseQueryLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shippingPointId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                       &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;ParseQueryLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rcId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;shippingPointId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shippingPointId required"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SpecialRcSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShippingPoint&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;shippingPointId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;JsonRouteHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MapToDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two features worth pointing out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ProcessWithRedb((redb, ex, ct) =&amp;gt; ...)&lt;/code&gt;&lt;/strong&gt; — typed access to a named &lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;redb.Core&lt;/a&gt; service inside the pipeline. &lt;code&gt;redb.Query&amp;lt;SpecialRcSettings&amp;gt;().Where(...).ToListAsync()&lt;/code&gt; is just LINQ over a typed EAV scheme. No DbContext, no migrations, no separate repository class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WireTap("direct://tsum-audit", onPrepare: ..., newBodyFactory: ...)&lt;/code&gt;&lt;/strong&gt; — the audit hop runs in parallel with the main response, gets its own headers and its own body built fresh from the exchange. The HTTP client doesn't wait for audit, but audit sees the exact state of the exchange at that step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the actual shape of a real route. Method dispatch, auth, permission, business handler, audit — in one declarative block that reads top-down.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Apache Camel&lt;/th&gt;
&lt;th&gt;MassTransit&lt;/th&gt;
&lt;th&gt;NServiceBus&lt;/th&gt;
&lt;th&gt;Wolverine&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;redb.Route&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Java/JVM&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transports&lt;/td&gt;
&lt;td&gt;300+&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EIP patterns (DSL)&lt;/td&gt;
&lt;td&gt;80+&lt;/td&gt;
&lt;td&gt;~5&lt;/td&gt;
&lt;td&gt;~5&lt;/td&gt;
&lt;td&gt;~5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expression engine&lt;/td&gt;
&lt;td&gt;Interpreted&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Compiled&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactional pipelines across transports&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Bus only&lt;/td&gt;
&lt;td&gt;Bus only&lt;/td&gt;
&lt;td&gt;Bus only&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime container&lt;/td&gt;
&lt;td&gt;Karaf / Camel K&lt;/td&gt;
&lt;td&gt;Worker Service&lt;/td&gt;
&lt;td&gt;Worker Service&lt;/td&gt;
&lt;td&gt;Worker Service&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;redb.Tsak&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Commercial (&amp;gt;2 endpoints)&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Apache 2.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;MassTransit, NServiceBus and Wolverine solve a different problem — they are &lt;strong&gt;message-bus frameworks&lt;/strong&gt; with handler discovery, durable sagas and managed outbox. redb.Route is a &lt;strong&gt;pipeline and transport integration engine&lt;/strong&gt;. They are not mutually exclusive: use redb.Route for cross-protocol routing and transformation, MassTransit for handler-style messaging and long-running saga state.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Deploying to production — redb.Tsak
&lt;/h2&gt;

&lt;p&gt;Writing &lt;code&gt;RouteBuilder&lt;/code&gt; classes is one thing. Running them in production across multiple nodes is another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;redb.Tsak&lt;/a&gt; is the runtime container built for redb.Route:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drop a &lt;code&gt;.dll&lt;/code&gt; or &lt;code&gt;.tpkg&lt;/code&gt; (ZIP + manifest) into &lt;code&gt;Libs/&lt;/code&gt; — Tsak loads it without restart&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot-reload&lt;/strong&gt; — update the file while running, zero downtime for other routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster mode&lt;/strong&gt; — leader election, automatic context redistribution across nodes. No ZooKeeper, no etcd. Coordination uses row locks in redb.Core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API&lt;/strong&gt; (32 endpoints), &lt;strong&gt;CLI&lt;/strong&gt; (30 commands), &lt;strong&gt;Blazor dashboard&lt;/strong&gt; with per-route metrics, logs, watchdog, cluster view&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not change a single line of &lt;code&gt;RouteBuilder&lt;/code&gt; code to go from &lt;code&gt;dotnet run&lt;/code&gt; to a 3-node production cluster.&lt;/p&gt;

&lt;p&gt;Full writeup on Tsak is coming in the next article. For now: &lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;p&gt;Running at &lt;a href="https://ews.ru" rel="noopener noreferrer"&gt;EWS&lt;/a&gt; — a 30-year-old national HoReCa food distributor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3-node cluster (4 cores / 8 GB / 50 GB SSD per node)&lt;/li&gt;
&lt;li&gt;~150k orders/month, ~3 months stable, 10–15% CPU under full load&lt;/li&gt;
&lt;li&gt;Active transports: SAP, Kafka, RabbitMQ, GPS feeds, Mercury / EGAIS / Chestny Znak / FGIS Grain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;27 NuGet packages (core engine + 22 transports + 5 support libraries). Apache 2.0.&lt;/p&gt;

&lt;p&gt;It's not Apache Camel — not in transport count, not in maturity, not in ecosystem. But for the kind of integration work most .NET teams actually ship, it covers the ground a single library reasonably can. Honest feedback, missing transports, and breaking-case bug reports are all welcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Tsak&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;redbase.app/architecture&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discussions&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route/discussions&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;This is exactly the moment when honest outside input is worth more than another internal sprint. A few specific things I'd love to hear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transports.&lt;/strong&gt; 22 cover most stacks I've seen, but obvious gaps remain. NATS / NATS JetStream? Pulsar? Service Bus topics with sessions? Google Pub/Sub? SQS+SNS as a pair? Salesforce Streaming API? OPC UA? Tell me what would make redb.Route a real fit for your stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EIP patterns.&lt;/strong&gt; 30+ ship today. Anything from the Hohpe/Woolf catalogue you'd actually use and can't easily get elsewhere in .NET? Message Store with replay? Routing Slip? Process Manager? Normalizer? Format Indicator?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DSL ergonomics.&lt;/strong&gt; Anything in the examples above that reads awkwardly in C#? Where would &lt;code&gt;[Source]&lt;/code&gt;/&lt;code&gt;[Sink]&lt;/code&gt; attributes, source generators, minimal-API-style &lt;code&gt;MapRoute("/orders")&lt;/code&gt;, or top-level statement DSL feel better than &lt;code&gt;RouteBuilder&lt;/code&gt; classes?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability.&lt;/strong&gt; OpenTelemetry is built in and on by default; Grafana dashboards and the live metrics / logs UI live in redb.Tsak (full writeup in the next article). What's still missing from your point of view — opinionated dashboard JSON, a turnkey OTel collector recipe, exemplars wired to trace IDs?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration.&lt;/strong&gt; If you have an existing Apache Camel route in production, would a side-by-side translation walkthrough (Camel Java → redb.Route C#) be useful? Which patterns hurt most?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What stops you from trying it.&lt;/strong&gt; "License is good, but…" / "I like the DSL, but…" — the &lt;em&gt;but&lt;/em&gt; is the most valuable feedback I can get right now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop a comment, open an issue, or start a thread in &lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;. Critical responses get the same priority as kind ones — both move the project forward.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>integration</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
