<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Arion - Blog</title>
    <subtitle>The fastest programmable gateway for models, tools and services.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://arion-gateway.dev/blog/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://arion-gateway.dev/blog/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-10-13T00:00:00+00:00</updated>
    <id>https://arion-gateway.dev/blog/atom.xml</id>
    <entry xml:lang="en">
        <title>Code mode: let the model write the code</title>
        <published>2026-10-13T00:00:00+00:00</published>
        <updated>2026-10-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            Arion maintainers
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://arion-gateway.dev/blog/code-mode/"/>
        <id>https://arion-gateway.dev/blog/code-mode/</id>
        
        <content type="html" xml:base="https://arion-gateway.dev/blog/code-mode/">&lt;p&gt;Arion MCP gateway turns REST services, OpenAPI documents and upstream MCP servers into one catalog of tools that agents can discover and call. Arion Code mode provides an alternative way for agents to discover and interact with MCP tools.&lt;/p&gt;
&lt;p&gt;Arion MCP code mode works similarly to how code mode works in agent harnesses, but it runs in the context of the gateway. Every tool call made through Arion&#39;s code mode SDK goes through the full gateway policy stack in the same way a direct MCP tool call would.&lt;/p&gt;
&lt;h2 id=&quot;why-support-code-mode&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-support-code-mode&quot; aria-label=&quot;Anchor link for: why-support-code-mode&quot;&gt;Why support Code Mode&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With conventional MCP, an agent calls one tool, waits, reads the result and decides the next call, over and over. Take a routine support request:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the customer by email.&lt;/li&gt;
&lt;li&gt;Fetch their recent tickets.&lt;/li&gt;
&lt;li&gt;Fetch their billing plan.&lt;/li&gt;
&lt;li&gt;Open a ticket, at high priority if they are on the enterprise tier.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In many agent harnesses, this results in at least four tool calls, with model turns and eight crossings between the model and the gateway. This introduces additional latency. Worse, if the agent harness does not provide code mode natively or a similar optimization method, then all the raw responses are added to the model&#39;s context, including the ticket history it needed only long enough to count. This is both inefficient and wasteful.&lt;/p&gt;
&lt;p&gt;A large catalog can add a lot of unncessary cost before any work starts. In many settings, adding a few MCP servers can result in tens and often hundreds of tool definitions that the model has to select from. &lt;a href=&quot;https://arion-gateway.dev/docs/mcp-gateway/tool-search/&quot;&gt;Tool search&lt;/a&gt; helps, but, until MCP progressive discovery is standardized, the harness again needs custom code to effectively use tool search.&lt;/p&gt;
&lt;p&gt;The model ends up doing coordination work that a few lines of code could do faster, more cheaply and more reliably.&lt;/p&gt;
&lt;h2 id=&quot;the-approach-one-tool-one-script&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-approach-one-tool-one-script&quot; aria-label=&quot;Anchor link for: the-approach-one-tool-one-script&quot;&gt;The approach: one tool, one script&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Code mode is enabled in the MCP gateway configuration. When code mode is enabled, it exposes exactly one tool called &lt;code&gt;execute&lt;/code&gt;. The model, following instructions from the execute tool description generates code to call the Arion SDK and passes the program to Arion, Arion then runs the provided code in a sandbox. The code can call any MCP tool the agent is allowed to use, combine results, loop, branch and run calls in parallel, then return only the overall result of the program.&lt;/p&gt;
&lt;figure role=&quot;group&quot; aria-label=&quot;In tools mode the agent and Arion exchange one request and one response per tool call; in code mode the agent sends one script, Arion runs it in a sandbox that calls the tools, and one result comes back.&quot;&gt;
&lt;svg viewBox=&quot;0 0 720 320&quot; role=&quot;img&quot; aria-hidden=&quot;true&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
  &lt;text x=&quot;16&quot; y=&quot;24&quot; style=&quot;font-family: var(--font-mono); font-size: 10px; letter-spacing: 2px; fill: var(--ink-subtle)&quot;&gt;TOOLS MODE · THE AGENT DRIVES EVERY CALL&lt;/text&gt;
  &lt;rect x=&quot;16&quot; y=&quot;48&quot; width=&quot;120&quot; height=&quot;72&quot; rx=&quot;8&quot; style=&quot;fill: var(--surface-raised); stroke: var(--line-strong); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;76&quot; y=&quot;80&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 12px; fill: var(--ink)&quot;&gt;agent&lt;/text&gt;
  &lt;text x=&quot;76&quot; y=&quot;97&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-subtle)&quot;&gt;one turn per call&lt;/text&gt;
  &lt;line x1=&quot;140&quot; y1=&quot;58&quot; x2=&quot;286&quot; y2=&quot;58&quot; style=&quot;stroke: var(--line-strong); stroke-width: 1.5&quot;/&gt;
  &lt;path d=&quot;M294 58 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;line x1=&quot;150&quot; y1=&quot;68&quot; x2=&quot;292&quot; y2=&quot;68&quot; style=&quot;stroke: var(--line-strong); stroke-width: 1; stroke-dasharray: 3 4&quot;/&gt;
  &lt;path d=&quot;M140 68 l9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;line x1=&quot;140&quot; y1=&quot;82&quot; x2=&quot;286&quot; y2=&quot;82&quot; style=&quot;stroke: var(--line-strong); stroke-width: 1.5&quot;/&gt;
  &lt;path d=&quot;M294 82 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;line x1=&quot;150&quot; y1=&quot;92&quot; x2=&quot;292&quot; y2=&quot;92&quot; style=&quot;stroke: var(--line-strong); stroke-width: 1; stroke-dasharray: 3 4&quot;/&gt;
  &lt;path d=&quot;M140 92 l9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;line x1=&quot;140&quot; y1=&quot;106&quot; x2=&quot;286&quot; y2=&quot;106&quot; style=&quot;stroke: var(--line-strong); stroke-width: 1.5&quot;/&gt;
  &lt;path d=&quot;M294 106 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;line x1=&quot;150&quot; y1=&quot;116&quot; x2=&quot;292&quot; y2=&quot;116&quot; style=&quot;stroke: var(--line-strong); stroke-width: 1; stroke-dasharray: 3 4&quot;/&gt;
  &lt;path d=&quot;M140 116 l9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;text x=&quot;216&quot; y=&quot;140&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-subtle)&quot;&gt;6 crossings · every result back through the model&lt;/text&gt;
  &lt;rect x=&quot;296&quot; y=&quot;48&quot; width=&quot;160&quot; height=&quot;72&quot; rx=&quot;8&quot; style=&quot;fill: var(--surface-raised); stroke: var(--line-strong); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;376&quot; y=&quot;80&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 12px; fill: var(--ink)&quot;&gt;Arion&lt;/text&gt;
  &lt;text x=&quot;376&quot; y=&quot;97&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-subtle)&quot;&gt;one tool per request&lt;/text&gt;
  &lt;path d=&quot;M456 84 H496 V58 H527&quot; style=&quot;fill: none; stroke: var(--line-strong); stroke-width: 1&quot;/&gt;
  &lt;path d=&quot;M536 58 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;path d=&quot;M456 84 H527&quot; style=&quot;fill: none; stroke: var(--line-strong); stroke-width: 1&quot;/&gt;
  &lt;path d=&quot;M536 84 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;path d=&quot;M456 84 H496 V110 H527&quot; style=&quot;fill: none; stroke: var(--line-strong); stroke-width: 1&quot;/&gt;
  &lt;path d=&quot;M536 110 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--line-strong)&quot;/&gt;
  &lt;rect x=&quot;536&quot; y=&quot;46&quot; width=&quot;168&quot; height=&quot;24&quot; rx=&quot;12&quot; style=&quot;fill: var(--surface-sunken); stroke: var(--line); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;620&quot; y=&quot;62&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;crm.find_customer&lt;/text&gt;
  &lt;rect x=&quot;536&quot; y=&quot;72&quot; width=&quot;168&quot; height=&quot;24&quot; rx=&quot;12&quot; style=&quot;fill: var(--surface-sunken); stroke: var(--line); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;620&quot; y=&quot;88&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;billing.current_plan&lt;/text&gt;
  &lt;rect x=&quot;536&quot; y=&quot;98&quot; width=&quot;168&quot; height=&quot;24&quot; rx=&quot;12&quot; style=&quot;fill: var(--surface-sunken); stroke: var(--line); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;620&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;support.create_ticket&lt;/text&gt;
  &lt;line x1=&quot;16&quot; y1=&quot;162&quot; x2=&quot;704&quot; y2=&quot;162&quot; style=&quot;stroke: var(--line); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;16&quot; y=&quot;188&quot; style=&quot;font-family: var(--font-mono); font-size: 10px; letter-spacing: 2px; fill: var(--ink-subtle)&quot;&gt;CODE MODE · ARION RUNS THE WORKFLOW&lt;/text&gt;
  &lt;rect x=&quot;16&quot; y=&quot;210&quot; width=&quot;120&quot; height=&quot;72&quot; rx=&quot;8&quot; style=&quot;fill: var(--surface-raised); stroke: var(--line-strong); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;76&quot; y=&quot;242&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 12px; fill: var(--ink)&quot;&gt;agent&lt;/text&gt;
  &lt;text x=&quot;76&quot; y=&quot;259&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-subtle)&quot;&gt;one turn&lt;/text&gt;
  &lt;text x=&quot;216&quot; y=&quot;226&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;execute(script)&lt;/text&gt;
  &lt;line x1=&quot;140&quot; y1=&quot;236&quot; x2=&quot;286&quot; y2=&quot;236&quot; style=&quot;stroke: var(--accent); stroke-width: 1.5&quot;/&gt;
  &lt;path d=&quot;M294 236 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--accent)&quot;/&gt;
  &lt;line x1=&quot;150&quot; y1=&quot;258&quot; x2=&quot;292&quot; y2=&quot;258&quot; style=&quot;stroke: var(--accent); stroke-width: 1.5; stroke-dasharray: 3 4&quot;/&gt;
  &lt;path d=&quot;M140 258 l9 -4.5 v9 z&quot; style=&quot;fill: var(--accent)&quot;/&gt;
  &lt;text x=&quot;216&quot; y=&quot;276&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;one result&lt;/text&gt;
  &lt;text x=&quot;216&quot; y=&quot;306&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-subtle)&quot;&gt;2 crossings · intermediate results stay in Arion&lt;/text&gt;
  &lt;rect x=&quot;296&quot; y=&quot;202&quot; width=&quot;160&quot; height=&quot;88&quot; rx=&quot;8&quot; style=&quot;fill: none; stroke: var(--line-strong); stroke-width: 1; stroke-dasharray: 3 4&quot;/&gt;
  &lt;text x=&quot;308&quot; y=&quot;219&quot; style=&quot;font-family: var(--font-mono); font-size: 10px; letter-spacing: 2px; fill: var(--ink-subtle)&quot;&gt;ARION&lt;/text&gt;
  &lt;rect x=&quot;308&quot; y=&quot;228&quot; width=&quot;136&quot; height=&quot;50&quot; rx=&quot;8&quot; style=&quot;fill: var(--accent-surface); stroke: var(--accent); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;376&quot; y=&quot;249&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 12px; fill: var(--ink)&quot;&gt;sandbox&lt;/text&gt;
  &lt;text x=&quot;376&quot; y=&quot;266&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;runs the script&lt;/text&gt;
  &lt;circle cx=&quot;444&quot; cy=&quot;253&quot; r=&quot;3.5&quot; style=&quot;fill: var(--glow)&quot;/&gt;
  &lt;path d=&quot;M444 253 H490 V220 H527&quot; style=&quot;fill: none; stroke: var(--accent); stroke-width: 1&quot;/&gt;
  &lt;path d=&quot;M536 220 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--accent)&quot;/&gt;
  &lt;path d=&quot;M444 253 H490 V246 H527&quot; style=&quot;fill: none; stroke: var(--accent); stroke-width: 1&quot;/&gt;
  &lt;path d=&quot;M536 246 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--accent)&quot;/&gt;
  &lt;path d=&quot;M444 253 H490 V272 H527&quot; style=&quot;fill: none; stroke: var(--accent); stroke-width: 1&quot;/&gt;
  &lt;path d=&quot;M536 272 l-9 -4.5 v9 z&quot; style=&quot;fill: var(--accent)&quot;/&gt;
  &lt;rect x=&quot;536&quot; y=&quot;208&quot; width=&quot;168&quot; height=&quot;24&quot; rx=&quot;12&quot; style=&quot;fill: var(--surface-sunken); stroke: var(--line); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;620&quot; y=&quot;224&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;crm.find_customer&lt;/text&gt;
  &lt;rect x=&quot;536&quot; y=&quot;234&quot; width=&quot;168&quot; height=&quot;24&quot; rx=&quot;12&quot; style=&quot;fill: var(--surface-sunken); stroke: var(--line); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;620&quot; y=&quot;250&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;billing.current_plan&lt;/text&gt;
  &lt;rect x=&quot;536&quot; y=&quot;260&quot; width=&quot;168&quot; height=&quot;24&quot; rx=&quot;12&quot; style=&quot;fill: var(--surface-sunken); stroke: var(--line); stroke-width: 1&quot;/&gt;
  &lt;text x=&quot;620&quot; y=&quot;276&quot; text-anchor=&quot;middle&quot; style=&quot;font-family: var(--font-mono); font-size: 11px; fill: var(--ink-muted)&quot;&gt;support.create_ticket&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption&gt;&lt;strong&gt;Figure 1.&lt;/strong&gt; The tool calls do not disappear: they move to the gateway&#39;s side of the boundary.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The support request becomes one script:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;typescript&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-2&quot;&gt;const&lt;/span&gt;&lt;span&gt; customer&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-2&quot;&gt; await&lt;/span&gt;&lt;span&gt; tools&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;&amp;quot;crm.find_customer&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;]({&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; email&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;ada@example.com&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-2&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; (!&lt;/span&gt;&lt;span&gt;customer&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-2&quot;&gt; return&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; {&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; error&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;unknown customer&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-2&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; [&lt;/span&gt;&lt;span&gt;history&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;,&lt;/span&gt;&lt;span&gt; plan&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;] =&lt;/span&gt;&lt;span class=&quot;z-2&quot;&gt; await&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; Promise&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tools&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;&amp;quot;support.recent_tickets&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;]({&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; customer_id&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; customer&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tools&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;&amp;quot;billing.current_plan&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;]({&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; customer_id&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; customer&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-2&quot;&gt;const&lt;/span&gt;&lt;span&gt; ticket&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-2&quot;&gt; await&lt;/span&gt;&lt;span&gt; tools&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;&amp;quot;support.create_ticket&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;]({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;  ticket&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    customer_id&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; customer&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    priority&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; plan&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; plan&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;tier&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;enterprise&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;high&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;normal&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    summary&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;Refund not received&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-2&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; {&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; ticket&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; ticket&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; seen&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; history&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;items&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The history and plan lookups run in parallel, the script branches on the plan, and the model gets back a small object instead of four raw responses. The four calls never touch its context.&lt;/p&gt;
&lt;p&gt;Some small design choices help make this practical.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The tool describes itself, per caller.&lt;/strong&gt; The &lt;code&gt;execute&lt;/code&gt; description is written fresh for each caller. It lists the toolkits and namespaces that caller is allowed to see, and how to find more. It has a 4 KiB budget, so a gateway with thousands of tools still gives the model a compact starting point instead of a wall of text.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discovery happens inside the script.&lt;/strong&gt; A small &lt;code&gt;catalog&lt;/code&gt; API lets the script search, list and describe tools on demand, so the model pulls in only the definitions it needs. &lt;code&gt;catalog.tools.search({ query })&lt;/code&gt; uses the same BM25 or embedding ranking as tool search, but without the extra round trip.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every call returns one predictable shape.&lt;/strong&gt; A tool call resolves to &lt;code&gt;{ ok: true, data }&lt;/code&gt; or &lt;code&gt;{ ok: false, error: { code, message, retryable } }&lt;/code&gt;. A failure is a value, not an exception, so the script checks &lt;code&gt;ok&lt;/code&gt; and moves on, and failures stay readable.&lt;/p&gt;
&lt;p&gt;On top of that, operators can give the model a head start. A &lt;strong&gt;toolkit&lt;/strong&gt; is a named bundle of related tools with a short &quot;how to use them together&quot; note, authored in configuration. Where the raw tool list tells the model what exists, a toolkit guides the model in how common tasks can be performed: find the customer, then open a ticket, then notify the owner. A toolkit appears only if the caller may use every tool in it, so partial bundles never leak, and toolkits can be added or removed live over xDS. Toolkits are somewhat similar to skills, but are more tightly coupled to describing how to use a subset of tools from the catalog to achieve a specific task.&lt;/p&gt;
&lt;h2 id=&quot;how-it-runs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-it-runs&quot; aria-label=&quot;Anchor link for: how-it-runs&quot;&gt;How it runs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Code mode is just an alternative operating mode for Arion MCP gateway. It changes how tools are called, never what is allowed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Policy is always the same for both modes.&lt;/strong&gt; A tool call from a script travels the same path as an ordinary &lt;code&gt;tools/call&lt;/code&gt;. It lands on the one shared tool registry, which checks that the caller is permitted, validates the arguments against the tool&#39;s schema, enforces size and time limits, and records the same metrics. A script cannot reach anything its caller could not already reach, and it cannot skip a single check. The registry was reorganized into small, focused pieces so that both modes share it with no duplicated logic to drift apart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sandbox runtime&lt;/strong&gt; Agent provided code runs in WASM using &lt;a rel=&quot;external&quot; href=&quot;https://wasmtime.dev/&quot;&gt;Wasmtime&lt;/a&gt;, a mature WebAssembly runtime, on QuickJS, a small and fast JavaScript engine compiled as a WASI component. Inside, the module JS runtime is highly constrained: no files, no network, no environment. The only interaction with outside world is through a very limited set of host calls: logging, tool invocations or tool catalog search.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fast to start, nothing running when idle.&lt;/strong&gt; The engine is initialized ahead of time with Wizer, so each run starts from a snapshot rather than booting an interpreter. Instances come from a pool, so starting one is quick. Each run still gets a fresh, empty environment, so one caller can never see another&#39;s data. The thread that enforces the compute budget runs only while a script is executing, so an idle gateway does no background work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Two clocks, and limits on everything.&lt;/strong&gt; A 5-second compute budget stops runaway code. A total-time limit, 30 seconds by default, bounds the whole run. Waiting on a slow backend does not count against compute time. Memory (64 MiB), tool calls per run (100), runs at once per listener (32), parallel calls within a run (8), result size (1 MiB) and script size (256 KiB) are all bounded too. A script that hits the call limit cannot hide the error by catching it. Excess runs are turned away rather than queued. Operators also set a process-wide ceiling on sandboxes and memory, and per-listener settings cannot exceed it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result Composition&lt;/strong&gt; A run returns a single object: a status, the script&#39;s return value, any log lines, and a list of every tool it called with timing and outcome. If that object is too big, the gateway keeps the actual answer, trims the extras and says exactly what it trimmed. A script error gets a plain reason and a line number for the agent to resolve.&lt;/p&gt;
&lt;h2 id=&quot;what-it-changes&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-changes&quot; aria-label=&quot;Anchor link for: what-it-changes&quot;&gt;What it changes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the support request above, the comparison is plain:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Tools mode&lt;/th&gt;&lt;th&gt;Code mode&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Model turns&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Crossings between model and gateway&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tool responses in the model&#39;s context&lt;/td&gt;&lt;td&gt;4, in full&lt;/td&gt;&lt;td&gt;0; one small object&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tool definitions offered up front&lt;/td&gt;&lt;td&gt;every listed tool&lt;/td&gt;&lt;td&gt;one, &lt;code&gt;execute&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Those are counts from the example. The backend calls take as long as they always did, and the two lookups now run in parallel. The model turns in between are gone, and those turns are usually where the time and the tokens go. Workflows with more steps or more fan-out gain more. We have not published timings for code mode yet, so we are not quoting any here.&lt;/p&gt;
&lt;p&gt;The catalog scales the same way. &lt;code&gt;tools/list&lt;/code&gt; returns one tool whether the gateway serves ten tools or ten thousand, and the model reads a definition only when its script asks for one.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#trade-offs&quot; aria-label=&quot;Anchor link for: trade-offs&quot;&gt;Trade-offs&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The model has to write working code.&lt;/strong&gt; The errors are built for a model to act on: a parse error gives a line and column, and a failed call is a value with a code. Toolkits cut the guesswork further. If it takes a model multiple attempts to generate the correct program, some benefits are lost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A listener is one mode or the other.&lt;/strong&gt; A code-mode listener serves only &lt;code&gt;execute&lt;/code&gt;. Clients that expect the classic tool list need a separate listener in tools mode. Both can front the same tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scripts keep no state.&lt;/strong&gt; Every run starts fresh. Anything that must survive between runs belongs in your tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The TypeScript is strip-only.&lt;/strong&gt; Annotations are removed, but &lt;code&gt;enum&lt;/code&gt;, &lt;code&gt;namespace&lt;/code&gt; declarations and decorators are rejected. In practice, they are typically not necessary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It is opt-in at build time.&lt;/strong&gt; Code mode sits behind the &lt;code&gt;codemode&lt;/code&gt; Cargo feature, which adds the Wasmtime runtime and a sandbox component of just under 1 MiB. A build without it rejects a code-mode configuration cleanly instead of misbehaving.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;turning-it-on&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#turning-it-on&quot; aria-label=&quot;Anchor link for: turning-it-on&quot;&gt;Turning it on&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Build with &lt;code&gt;--features codemode&lt;/code&gt;, then pick the mode per listener:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;yaml&quot; data-arion=&quot;fragment&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;typed_config&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;  &amp;quot;@type&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.McpGateway&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;  server_info&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    name&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; support-tools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    version&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;1.0.0&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;  code_mode&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;: {}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every setting has a sensible default. Tools, upstream MCP servers, OpenAPI sources and toolkits can all be delivered and changed at runtime over xDS. It is still standard MCP, so existing clients keep working; the model just sees one tool. The sandbox itself can be swapped for a vetted custom build, which Arion checks when the listener loads.&lt;/p&gt;
&lt;h2 id=&quot;what-is-next&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-is-next&quot; aria-label=&quot;Anchor link for: what-is-next&quot;&gt;What is next&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To try it end to end, follow &lt;a href=&quot;https://arion-gateway.dev/guides/mcp/code-mode-for-agents/&quot;&gt;code mode for agents&lt;/a&gt;. It builds the support workflow above against three REST services. The &lt;a href=&quot;https://arion-gateway.dev/docs/mcp-gateway/code-mode/&quot;&gt;code mode page&lt;/a&gt; covers when to use it, and the &lt;a href=&quot;https://arion-gateway.dev/reference/sdk/code-mode-sdk-and-limits/&quot;&gt;SDK reference&lt;/a&gt; lists every call, error code and limit. For a quick way to fill the catalog, pair it with &lt;a href=&quot;https://arion-gateway.dev/docs/mcp-gateway/openapi-tools/&quot;&gt;OpenAPI tools&lt;/a&gt;: point a source at an API&#39;s spec, and every operation becomes a tool that a script can call.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Introducing the Arion control plane</title>
        <published>2026-10-06T00:00:00+00:00</published>
        <updated>2026-10-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            Arion maintainers
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://arion-gateway.dev/blog/introducing-arion-control-plane/"/>
        <id>https://arion-gateway.dev/blog/introducing-arion-control-plane/</id>
        
        <content type="html" xml:base="https://arion-gateway.dev/blog/introducing-arion-control-plane/">&lt;p&gt;When we set out to build Arion, we made the decision to keep close compatability the Envoy configuration model. We made this choice because, whilst the Envoy configuration model has some ergonomic issues and had become a little bloated in places, it had extensive coverage. Coupled with the xDS discovery protocol, Envoy led the way in terms of dynamic (re)configuration in Cloud L7 networking. In choosing Envoy&#39;s native configuration model over xDS, we could also leverage the existing Envoy ecosystem of xDS control plane software.&lt;/p&gt;
&lt;p&gt;Having worked with a bunch of the controllers in the Envoy xDS ecosystem over a number of years, however, we got frustrated and decided to build our own. Arion-ctl is our own control plane, still fully xDS compatible, but small, neat and purpose built with a bunch of usability features that we felt were missing from others in the ecosystem.&lt;/p&gt;
&lt;p&gt;You can deploy it in two ways: as a &lt;strong&gt;standalone service&lt;/strong&gt; that you manage with files and operator commands, or as a &lt;strong&gt;Kubernetes controller&lt;/strong&gt; that reads Gateway API resources from your cluster. Each option delivers configuration to running Arion proxies.&lt;/p&gt;
&lt;p&gt;Arion remains the data plane: it accepts connections and forwards traffic. Arion-ctl supplies listeners, routes, clusters, endpoints, and secrets as configuration dynamically over xDS. Once a proxy has its configuration, handling a request does not require any interaction with the control plane.&lt;/p&gt;
&lt;h2 id=&quot;choose-a-deployment-option&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#choose-a-deployment-option&quot; aria-label=&quot;Anchor link for: choose-a-deployment-option&quot;&gt;Choose a deployment option&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The standalone service and Kubernetes controller are &lt;strong&gt;separate deployment options&lt;/strong&gt;. Choose the one that fits how you manage your gateways. You do not need to install or configure the standalone service before using the Kubernetes controller.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Deployment option&lt;/th&gt;&lt;th&gt;How you manage configuration&lt;/th&gt;&lt;th&gt;Where configuration is stored&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Standalone service&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Apply YAML resource files with &lt;code&gt;arionctl&lt;/code&gt;, or use the optional &lt;code&gt;arionctl shell&lt;/code&gt;&lt;/td&gt;&lt;td&gt;A durable snapshot saved by the standalone service&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Kubernetes controller&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Create Gateway API resources through the Kubernetes API&lt;/td&gt;&lt;td&gt;Kubernetes cluster state&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The command line and interactive shell are two interfaces to the &lt;strong&gt;same standalone service&lt;/strong&gt;. The Kubernetes controller has its own setup and reads its configuration from Kubernetes resources. Both deployments use the same library internally to deliver configuration over xDS.&lt;/p&gt;
&lt;h2 id=&quot;standalone-deployment-files-and-an-optional-shell&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#standalone-deployment-files-and-an-optional-shell&quot; aria-label=&quot;Anchor link for: standalone-deployment-files-and-an-optional-shell&quot;&gt;Standalone deployment: files and an optional shell&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Choose standalone when you want to run a control-plane service without depending on Kubernetes. The service stores your configuration and supplies it to connected proxies. You can manage it entirely through YAML files and &lt;code&gt;arionctl&lt;/code&gt; commands; the Elixir shell is optional.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;fleet&lt;/strong&gt; groups the configuration delivered to a set of proxies. Each proxy selects its fleet with &lt;code&gt;node.cluster&lt;/code&gt; in its bootstrap. The standalone default is &lt;code&gt;arion&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;apply-configuration-from-a-file&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#apply-configuration-from-a-file&quot; aria-label=&quot;Anchor link for: apply-configuration-from-a-file&quot;&gt;Apply configuration from a file&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The standalone service accepts YAML resource documents. Each identifies a fleet, resource kind, and name, followed by the resource specification. These are the standalone service&#39;s resource documents; Kubernetes Gateway API uses its own resource definitions. The proxy still has a separate bootstrap containing its identity and control-plane connection settings.&lt;/p&gt;
&lt;p&gt;For a working example, follow the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/docs/standalone-walkthrough.md&quot;&gt;standalone walkthrough&lt;/a&gt; through the initial HTTP configuration. This starts backend A on port 18081, the control plane on 15051, and a proxy listener on 18080. Keep those processes running and retain the operator terminal&#39;s release environment.&lt;/p&gt;
&lt;p&gt;Save the &lt;a href=&quot;https://arion-gateway.dev/docs/operate/arion-control-plane/#apply-a-configuration-change&quot;&gt;overview&#39;s Cluster example&lt;/a&gt; as &lt;code&gt;backend.yaml&lt;/code&gt;. It describes the existing &lt;code&gt;backend&lt;/code&gt; resource in fleet &lt;code&gt;arion&lt;/code&gt;, pointing to backend A. From the operator terminal:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;shellscript&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;arionctl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; validate -f backend.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;arionctl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; apply -f backend.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;arionctl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; get --fleet arion&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;arionctl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; --fail http://127.0.0.1:18080/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The request returns &lt;code&gt;hello from backend-a&lt;/code&gt;. Reapplying this Cluster preserves the Listener from the walkthrough. Apply replaces each supplied resource&#39;s full specification and keeps identities absent from the input. Removal uses an explicit delete command. Editing the file alone changes nothing; the service loads it when instructed.&lt;/p&gt;
&lt;p&gt;The service validates the whole input, writes and syncs a temporary snapshot, renames it into place, then publishes the desired state to the fleet caches. Invalid input is rejected before saving. If writing the snapshot fails, the previous desired state remains active.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;get&lt;/code&gt; shows saved configuration. &lt;code&gt;status&lt;/code&gt; shows resource counts and connected proxies&#39; acknowledgement state. An accepted version appears as &lt;code&gt;[&quot;ack&quot;, &quot;&amp;lt;version&amp;gt;&quot;]&lt;/code&gt;; a rejected update appears as &lt;code&gt;[&quot;nack&quot;, &quot;&amp;lt;version&amp;gt;&quot;, &quot;&amp;lt;error&amp;gt;&quot;]&lt;/code&gt;. A successful apply means the desired state was saved and published. Proxy responses arrive separately, and a real request checks whether the intended routing works.&lt;/p&gt;
&lt;h3 id=&quot;optional-update-the-same-service-from-the-shell&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#optional-update-the-same-service-from-the-shell&quot; aria-label=&quot;Anchor link for: optional-update-the-same-service-from-the-shell&quot;&gt;Optional: update the same service from the shell&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you want to construct configuration in code, attach to the running standalone service through IEx, Elixir&#39;s interactive shell. This uses the same configuration store as the file commands above. Start a second backend in another terminal, from the control-plane repository:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;shellscript&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;python3&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; docs/examples/backend.py --port&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; 18082&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; --name backend-b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the operator terminal, attach to the service:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;shellscript&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;arionctl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; shell&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the IEx prompt, construct a Cluster and apply it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;elixir&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-2&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; Arion&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;ControlPlane&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;Helpers&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;Cluster&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cluster_b &lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; Cluster&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;&amp;quot;backend&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;127.0.0.1&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;, [&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;18082&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;],&lt;/span&gt;&lt;span&gt; connect_timeout&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-8&quot;&gt;{:&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-1&quot;&gt; _&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;} =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; Arion&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;Ctl&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;apply_resources&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;([&lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; cluster_b&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;],&lt;/span&gt;&lt;span&gt; fleet&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; &amp;quot;arion&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;Arion&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;Ctl&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;&amp;quot;arion&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;Arion&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;Ctl&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Cluster.static&lt;/code&gt; builds a protobuf value. &lt;code&gt;apply_resources&lt;/code&gt; converts it into the same resource documents used by the file interface, validates them, and saves them through &lt;code&gt;Arion.Ctl&lt;/code&gt;. The new backend survives a service restart using the same state directory.&lt;/p&gt;
&lt;p&gt;From an ordinary terminal, request the proxy again:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;shellscript&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; --fail http://127.0.0.1:18080/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the update is accepted, it returns &lt;code&gt;hello from backend-b&lt;/code&gt;. The Listener stayed in place. The proxy and control plane kept running.&lt;/p&gt;
&lt;p&gt;The shell also permits ordinary Elixir functions, loops, and transformations. You can compute a collection of resources, inspect it, then apply the collection together. Keep standalone mutations on &lt;code&gt;Arion.Ctl&lt;/code&gt;; publishing directly through the library&#39;s &lt;code&gt;Service&lt;/code&gt; API would bypass the snapshot.&lt;/p&gt;
&lt;p&gt;To leave the attached shell, press Ctrl+G, then enter &lt;code&gt;q&lt;/code&gt;. Back in the operator terminal, export the desired state:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; data-lang=&quot;shellscript&quot;&gt;&lt;code &gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-8&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt;umask&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; 077&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-5&quot;&gt; arionctl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; get --fleet arion&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; arion-resources.json&lt;/span&gt;&lt;span class=&quot;z-8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;arionctl&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; validate -f arion-resources.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JSON can be applied again with the same CLI. Exports can contain private keys from Secret resources, so keep them in protected storage. The &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/docs/shell-walkthrough.md&quot;&gt;shell walkthrough&lt;/a&gt; covers inspection, deletion, and restart verification.&lt;/p&gt;
&lt;h2 id=&quot;kubernetes-deployment-gateway-api&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kubernetes-deployment-gateway-api&quot; aria-label=&quot;Anchor link for: kubernetes-deployment-gateway-api&quot;&gt;Kubernetes deployment: Gateway API&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Choose this option when you want Kubernetes resources to define your gateways and routes. Install the Kubernetes controller with the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/charts/arion-ctl/README.md&quot;&gt;Helm chart&lt;/a&gt;, then manage configuration through the Kubernetes API, using tools such as &lt;code&gt;kubectl&lt;/code&gt; or your GitOps workflow. This is an alternative to the standalone setup above; you do not apply these resources with &lt;code&gt;arionctl&lt;/code&gt; or use its shell.&lt;/p&gt;
&lt;p&gt;A GatewayClass selects the controller, a Gateway defines traffic entry points, and routes attach rules to them. The controller watches these Kubernetes objects, resolves references and attachment permissions, then translates the result into the xDS configuration that Arion proxies receive.&lt;/p&gt;
&lt;p&gt;HTTPRoute covers matching, weighted backends, header changes, redirects, rewrites, timeouts, and retries. GRPCRoute adds service and method matching. Kubernetes Secrets supply certificates for HTTPS, while TCPRoute and TLSRoute describe stream forwarding and TLS handling. Services resolve to ready EndpointSlice addresses. InferencePool integration uses an endpoint picker and the pool&#39;s ready Pods. The &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/apps/arion_k8s_controller/README.md&quot;&gt;controller reference&lt;/a&gt; describes the supported fields and prerequisites for each feature.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://arion-gateway.dev/docs/ai-inference-gateway/overview/&quot;&gt;AI inference gateway overview&lt;/a&gt; explains how Arion asks the endpoint picker to select a ready model server Pod for each request. The &lt;a href=&quot;https://arion-gateway.dev/guides/inference/inference-routing/&quot;&gt;inference routing guide&lt;/a&gt; shows the Gateway API and InferencePool resources that configure it.&lt;/p&gt;
&lt;p&gt;Each Gateway gets a fleet named &lt;code&gt;gateway/&amp;lt;namespace&amp;gt;/&amp;lt;name&amp;gt;&lt;/code&gt;. Every controller replica derives and serves its own configuration from Kubernetes. With the chart&#39;s clustering enabled, an elected leader handles Kubernetes status and proxy provisioning. Replicas do not share a standalone snapshot or replicate xDS stream state.&lt;/p&gt;
&lt;p&gt;The controller can provision a Deployment, Service, ServiceAccount, and bootstrap ConfigMap for each Gateway. &lt;code&gt;ArionGatewayParameters&lt;/code&gt; controls details such as image, replicas, and service type. Within this Kubernetes deployment, you can instead manage the proxy workloads yourself while the controller continues to deliver their configuration.&lt;/p&gt;
&lt;h2 id=&quot;why-elixir-fits-the-design&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-elixir-fits-the-design&quot; aria-label=&quot;Anchor link for: why-elixir-fits-the-design&quot;&gt;Why Elixir fits the design&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A controller repeatedly loads or watches resource definitions, resolves their relationships, transforms them into configuration a data plane understands, and delivers updates as the desired state changes. We chose Elixir because it makes both the transformations and the ongoing coordination simpler to express and maintain.&lt;/p&gt;
&lt;h3 id=&quot;a-runtime-built-for-distributed-systems&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-runtime-built-for-distributed-systems&quot; aria-label=&quot;Anchor link for: a-runtime-built-for-distributed-systems&quot;&gt;A runtime built for distributed systems&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Elixir runs on the Erlang virtual machine, the BEAM. It provides lightweight, isolated processes that communicate by passing messages. Erlang&#39;s OTP libraries add supervision: processes can be organised into trees that define how their work is started and restarted after failures. Communication also extends across connected BEAM nodes, with built-in remote messaging, function calls, and process monitoring.&lt;/p&gt;
&lt;p&gt;These capabilities make writing distributed applications much simpler. A controller needs to keep resource watches running, schedule reconciliation, track connected proxies, and coordinate work across replicas. The Erlang process model gives us a consistent way to organise those activities, backed by runtime features and libraries built for that purpose. In Arion, this also supports direct interaction with a running service through remote commands and an interactive shell.&lt;/p&gt;
&lt;h3 id=&quot;a-concise-language-for-resource-transformations&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-concise-language-for-resource-transformations&quot; aria-label=&quot;Anchor link for: a-concise-language-for-resource-transformations&quot;&gt;A concise language for resource transformations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Much of a controller&#39;s work is data transformation: take resources in one representation, resolve what they mean together, and construct another representation. Elixir&#39;s concise syntax, pattern matching, collection functions, and pipelines suit this work particularly well. We can express the transformation as a sequence of small functions, each handling one part of the problem. Immutable data makes the inputs and outputs of each step easier to follow.&lt;/p&gt;
&lt;p&gt;In other controllers we&#39;ve worked with, we&#39;ve seen many thousands of lines of code devoted just to translating between representations. A language can be straightforward to get started with and still require a surprising amount of code for that task. We think that is a reason to question the fit between the language and the problem. Elixir lets us express these transformations more directly, keeping the implementation smaller and easier to understand.&lt;/p&gt;
&lt;h3 id=&quot;more-room-for-the-operator-experience&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#more-room-for-the-operator-experience&quot; aria-label=&quot;Anchor link for: more-room-for-the-operator-experience&quot;&gt;More room for the operator experience&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A smaller, simpler implementation also gives us more room to build a richer operator experience. The functions used to construct, inspect, validate, and apply configuration are available in the live Elixir shell. An operator can explore the current state, transform a collection of resources, and apply the result without building a separate tool for each operation.&lt;/p&gt;
&lt;p&gt;In the standalone service, the CLI and shell call the same application API. They share validation and durable storage, so using the programmable interface keeps the same persistence behaviour as applying a file. Elixir makes that kind of interactive experience a natural extension of the implementation.&lt;/p&gt;
&lt;h3 id=&quot;for-elixir-developers-embed-the-library&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#for-elixir-developers-embed-the-library&quot; aria-label=&quot;Anchor link for: for-elixir-developers-embed-the-library&quot;&gt;For Elixir developers: embed the library&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An Elixir application can also supervise &lt;code&gt;Arion.ControlPlane&lt;/code&gt; directly and construct configuration from its own service data. This is useful when you are building a custom control plane. The application owns persistence and recovery, and publishes through &lt;code&gt;Arion.ControlPlane.Service&lt;/code&gt;; &lt;code&gt;Service.replace&lt;/code&gt; replaces a fleet&#39;s complete resource set. See the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/apps/arion_control_plane/README.md#complete-http-example&quot;&gt;complete library example&lt;/a&gt; for startup and publication details.&lt;/p&gt;
&lt;h2 id=&quot;state-ownership-and-operational-checks&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#state-ownership-and-operational-checks&quot; aria-label=&quot;Anchor link for: state-ownership-and-operational-checks&quot;&gt;State ownership and operational checks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recovery follows the configuration owner. Standalone restores its snapshot before accepting xDS connections and requires one service instance per state directory. Kubernetes controller replicas rebuild their configuration from cluster resources and wait for their initial lists and first publication before serving discovery.&lt;/p&gt;
&lt;p&gt;A cache replacement does not make updates across resource types atomic at every proxy. ACKs describe accepted resource versions; they do not establish backend reachability. Inspect status and proxy logs, then send traffic through the route being changed.&lt;/p&gt;
&lt;p&gt;Fleets separate configuration but do not authenticate clients. Keep discovery and operator connections on trusted networks. The &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/docs/deployment-options.md&quot;&gt;deployment guide&lt;/a&gt; covers placement, storage, and availability responsibilities.&lt;/p&gt;
&lt;p&gt;To get started, choose the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/docs/standalone-walkthrough.md&quot;&gt;standalone walkthrough&lt;/a&gt; for files and operator commands, or the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion-ctl/blob/main/charts/arion-ctl/README.md&quot;&gt;Helm guide&lt;/a&gt; for Kubernetes Gateway API. The &lt;a href=&quot;https://arion-gateway.dev/docs/operate/arion-control-plane/&quot;&gt;control-plane overview&lt;/a&gt; has more examples, and the &lt;a href=&quot;https://arion-gateway.dev/docs/operate/xds-control-planes/&quot;&gt;general xDS guide&lt;/a&gt; explains the proxy side of the connection.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Introducing Arion</title>
        <published>2026-09-29T00:00:00+00:00</published>
        <updated>2026-09-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            Arion maintainers
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://arion-gateway.dev/blog/introducing-arion/"/>
        <id>https://arion-gateway.dev/blog/introducing-arion/</id>
        
        <content type="html" xml:base="https://arion-gateway.dev/blog/introducing-arion/">&lt;p&gt;Arion is an open-source L7 gateway written in Rust. It reads Envoy configuration natively and runs as an API gateway and proxy, an &lt;a href=&quot;https://arion-gateway.dev/docs/llm-gateway/overview/&quot;&gt;LLM gateway&lt;/a&gt; and an &lt;a href=&quot;https://arion-gateway.dev/docs/mcp-gateway/overview/&quot;&gt;MCP gateway&lt;/a&gt; — one data plane for services, models and tools.&lt;/p&gt;
&lt;p&gt;This is the first post on this blog, so before we write about internals and benchmarks, we want to start with the story: where Arion came from, and why we built it.&lt;/p&gt;
&lt;h2 id=&quot;where-arion-came-from&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#where-arion-came-from&quot; aria-label=&quot;Anchor link for: where-arion-came-from&quot;&gt;Where Arion came from&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The project behind Arion began as a research project and was later donated to the Kmesh GitHub organisation as an open-source subproject called Orion. Arion is an independent fork of Orion, led by its original authors and long-term maintainers. Arion builds on what we learned over a number of years of developing a high-throughput data plane for cloud services.&lt;/p&gt;
&lt;p&gt;We started this work initially while employed in Huawei&#39;s European research labs, aiming to address the many challenges that Cloud service teams and product teams had running large fleets of proxies and gateway services at scale. It was clear that the tools available to them each solved only half of the problem.&lt;/p&gt;
&lt;p&gt;We used nginx heavily, and on raw performance it is outstanding. But its configuration model comes from a world where configuration changes rarely: pushing changes across large fleets of nginx instances, continuously and safely, is brittle and error-prone.&lt;/p&gt;
&lt;p&gt;Envoy solves exactly that problem: it is battle-tested, and its dynamic configuration over xDS lets whole fleets reconfigure continuously without dropping traffic. But Envoy is heavy. It uses a lot of CPU and memory for the traffic it moves, and its throughput sits far below nginx&#39;s.&lt;/p&gt;
&lt;p&gt;Neither system gave us both. We wanted the performance of nginx and the operability of Envoy in one data plane.&lt;/p&gt;
&lt;h2 id=&quot;a-better-envoy-in-rust&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-better-envoy-in-rust&quot; aria-label=&quot;Anchor link for: a-better-envoy-in-rust&quot;&gt;A better Envoy, in Rust&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We set out to build a better Envoy. The requirements were written down early and never really changed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dynamically reconfigurable.&lt;/strong&gt; Configuration arrives over xDS and applies without pausing traffic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stable and predictable.&lt;/strong&gt; Every request passes through the proxy, so it has to run reliably for long periods and fail in predictable ways.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple to deploy and operate.&lt;/strong&gt; One binary, one config file or one control-plane connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lightweight.&lt;/strong&gt; Frugal with memory and CPU, because at fleet scale resource usage translates directly into cost and capacity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rust made those goals compatible. It gives us memory safety without a garbage collector: whole classes of memory bugs are ruled out at compile time, with no runtime cost on the request path. And speaking Envoy&#39;s configuration natively, rather than inventing our own, meant the existing ecosystem of control planes and tooling carried over.&lt;/p&gt;
&lt;p&gt;The performance target was clear from production experience running high-throughput services: nginx. For our scenarios it has always been the fastest option available, and that was the bar we set for Arion.&lt;/p&gt;
&lt;h2 id=&quot;from-prototype-to-production&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#from-prototype-to-production&quot; aria-label=&quot;Anchor link for: from-prototype-to-production&quot;&gt;From prototype to production&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The codebase behind Arion grew in stages: first a prototype, then a pre-production system, then versions of it carrying real production traffic.&lt;/p&gt;
&lt;p&gt;Arion builds on learning and experience gained from an internal production version deployed across thousands of instances powering AI cloud services. We now carry that work forward independently through Arion.&lt;/p&gt;
&lt;p&gt;Building for stability in production shaped the design. A few early decisions — configuration updates delivered as messages rather than guarded by locks, a data path that scales by adding whole runtimes, nothing on the hot path that can block — were made for stability and operability first. The same decisions, it turns out, are what make a proxy fast. The &lt;a href=&quot;https://arion-gateway.dev/docs/concepts/architecture/&quot;&gt;architecture page&lt;/a&gt; describes how those pieces fit together.&lt;/p&gt;
&lt;p&gt;We kept tuning, and release after release the gap to nginx narrowed, then closed. Arion now matches nginx, and beats it in a number of scenarios. Against agentgateway, Envoy and other gateways with similar feature sets, Arion delivers multiples of their throughput. The &lt;a href=&quot;https://arion-gateway.dev/benchmarks/&quot;&gt;benchmarks&lt;/a&gt; show the numbers and exactly how we produce them, so you can rerun them yourself.&lt;/p&gt;
&lt;h2 id=&quot;what-arion-is-today&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-arion-is-today&quot; aria-label=&quot;Anchor link for: what-arion-is-today&quot;&gt;What Arion is today&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A short tour, with the details left to the docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;An L7 and L4 proxy and API gateway.&lt;/strong&gt; Routing, load balancing, TLS, JWT authentication, rate limiting, circuit breaking, health checks — the most commonly used Envoy features, described as standard Envoy configuration. Start with &lt;a href=&quot;https://arion-gateway.dev/docs/get-started/what-is-arion/&quot;&gt;What is Arion&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Envoy-compatible, strictly.&lt;/strong&gt; If Arion accepts a configuration meant for Envoy, it behaves like Envoy. If it cannot honor one, it refuses to start rather than silently ignoring part of it. The &lt;a href=&quot;https://arion-gateway.dev/docs/concepts/envoy-compatibility/&quot;&gt;compatibility page&lt;/a&gt; defines the supported subset, and there is a &lt;a href=&quot;https://arion-gateway.dev/guides/proxy/migrate-from-envoy/&quot;&gt;guide for migrating from Envoy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A gateway for AI traffic.&lt;/strong&gt; An &lt;a href=&quot;https://arion-gateway.dev/docs/llm-gateway/overview/&quot;&gt;LLM gateway&lt;/a&gt; puts one governed front door in front of model providers, and an &lt;a href=&quot;https://arion-gateway.dev/docs/mcp-gateway/overview/&quot;&gt;MCP gateway&lt;/a&gt; serves tools to agents — including &lt;a href=&quot;https://arion-gateway.dev/blog/code-mode/&quot;&gt;code mode&lt;/a&gt;, where an agent sends one program instead of many tool calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Programmable.&lt;/strong&gt; WebAssembly filters with a Rust SDK, native Rust filters, and external processing over gRPC, for the cases configuration cannot express.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-s-next&quot; aria-label=&quot;Anchor link for: what-s-next&quot;&gt;What&#39;s next&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A lot has changed in the last couple of years. AI and agentic workloads emerged as a new and important class of traffic. It turned out that the properties we built for scale — a fast, safe, dynamically configurable data plane that is simple to extend — are exactly what that traffic needs. Most of Arion&#39;s recent development has gone into these AI features, and most of what is coming next will too. The posts that follow this one will go deeper: how we benchmark, how the runtime is structured, and what we learned building AI features on a proxy.&lt;/p&gt;
&lt;p&gt;If you want to try Arion, the &lt;a href=&quot;https://arion-gateway.dev/docs/get-started/quickstart/&quot;&gt;quickstart&lt;/a&gt; proxies your first request in five minutes. The code is on &lt;a rel=&quot;external&quot; href=&quot;https://github.com/arion-gateway/arion&quot;&gt;GitHub&lt;/a&gt;, and the &lt;a href=&quot;https://arion-gateway.dev/community/&quot;&gt;community page&lt;/a&gt; lists where to ask questions. We look forward to seeing what you build with Arion.&lt;/p&gt;
</content>
        
    </entry>
</feed>
