<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Agent on MyBrew</title>
    <link>https://aibrew.ai/tags/agent/</link>
    <description>Recent content in Agent on MyBrew</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 29 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://aibrew.ai/tags/agent/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>DeepSeek V4 in Claude Code: Anthropic-Compatible API with Native Web Search</title>
      <link>https://aibrew.ai/2026/05/deepseek-v4-in-claude-code-anthropic-compatible-api-with-native-web-search/</link>
      <pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
      <guid>https://aibrew.ai/2026/05/deepseek-v4-in-claude-code-anthropic-compatible-api-with-native-web-search/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — DeepSeek ships an Anthropic-compatible API endpoint that lets Claude Code treat DeepSeek V4 Pro like a drop-in replacement for Claude Opus. The setup is eight environment variables, and it works — including tool calling, sub-agent spawning, and native web search. At $0.435/M input tokens (permanent price after the initial launch promo), it&amp;rsquo;s roughly 4–17× cheaper than Claude Opus 4.7. This is a practical guide based on a real setup we run daily.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong> — DeepSeek ships an Anthropic-compatible API endpoint that lets Claude Code treat DeepSeek V4 Pro like a drop-in replacement for Claude Opus. The setup is eight environment variables, and it works — including tool calling, sub-agent spawning, and native web search. At $0.435/M input tokens (permanent price after the initial launch promo), it&rsquo;s roughly 4–17× cheaper than Claude Opus 4.7. This is a practical guide based on a real setup we run daily.</p>
</blockquote>
<hr>
<h2 id="why-this-matters">Why This Matters</h2>
<p>Claude Code is Anthropic&rsquo;s terminal-based AI coding agent. It reads your codebase, runs bash commands, spawns sub-agents, and writes edits — all through Anthropic&rsquo;s API. The problem: <strong>it only speaks Anthropic&rsquo;s message format</strong>. You can&rsquo;t point it at OpenAI, Gemini, or a local Ollama instance without a translation layer.</p>
<p>DeepSeek solved this the obvious way: they built an Anthropic-compatible API endpoint at <code>https://api.deepseek.com/anthropic</code> and documented the Claude Code integration on day one. No proxy, no wrapper, no SDK fork. Just environment variables.</p>
<p>We&rsquo;ve been running this setup in production — managing a multi-project workspace with six active sub-projects, MCP servers, and daily coding sessions. Here&rsquo;s what works, what doesn&rsquo;t, and the exact configuration.</p>
<hr>
<h2 id="step-1-get-a-deepseek-api-key">Step 1: Get a DeepSeek API Key</h2>
<p>Sign up at <a href="https://platform.deepseek.com/api_keys">platform.deepseek.com</a> and create an API key. DeepSeek uses prepaid balance — top up what you need, no subscription.</p>
<p>Two models matter for Claude Code:</p>
<table>
  <thead>
      <tr>
          <th>Model</th>
          <th>Role</th>
          <th>Context</th>
          <th>Max Output</th>
          <th>Input (cache miss)</th>
          <th>Output</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>deepseek-v4-pro</code></td>
          <td>Heavy reasoning, main agent</td>
          <td>1M tokens</td>
          <td>384K</td>
          <td>$0.435/M</td>
          <td>$0.87/M</td>
      </tr>
      <tr>
          <td><code>deepseek-v4-flash</code></td>
          <td>Sub-agents, fast tasks</td>
          <td>1M tokens</td>
          <td>384K</td>
          <td>$0.14/M</td>
          <td>$0.28/M</td>
      </tr>
  </tbody>
</table>
<p>After the initial launch promotion ends (2026-05-31), DeepSeek permanently adjusts the official price to 1/4 of the original — so $0.435/M input becomes the new normal, not a temporary deal. That&rsquo;s still <del>34× cheaper than Claude Opus 4.7 (</del>$15/M input, ~$75/M output) on input tokens. V4 Flash stays as-is.</p>
<p>Cache hits are absurdly cheap: <strong>$0.003625/M</strong> for V4 Pro and <strong>$0.0028/M</strong> for V4 Flash. Claude Code generates a lot of repetitive context (system prompts, CLAUDE.md files, tool definitions), so cache hits dominate real usage.</p>
<hr>
<h2 id="step-2-configure-environment-variables">Step 2: Configure Environment Variables</h2>
<p>Create a shell script (we call ours <code>claude.sh</code>) and source it before launching Claude Code:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_BASE_URL</span><span style="color:#ff79c6">=</span>https://api.deepseek.com/anthropic
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_AUTH_TOKEN</span><span style="color:#ff79c6">=</span>&lt;your-deepseek-api-key&gt;
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_OPUS_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_SONNET_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_HAIKU_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-flash
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">CLAUDE_CODE_SUBAGENT_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-flash
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">CLAUDE_CODE_EFFORT_LEVEL</span><span style="color:#ff79c6">=</span>max
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then launch:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">source</span> claude.sh
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">cd</span> /path/to/your/project
</span></span><span style="display:flex;"><span>claude
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>What each variable does:</strong></p>
<table>
  <thead>
      <tr>
          <th>Variable</th>
          <th>Purpose</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>ANTHROPIC_BASE_URL</code></td>
          <td>Redirects all API calls to DeepSeek&rsquo;s Anthropic-compatible endpoint</td>
      </tr>
      <tr>
          <td><code>ANTHROPIC_AUTH_TOKEN</code></td>
          <td>Your DeepSeek API key (not <code>ANTHROPIC_API_KEY</code> — Claude Code uses <code>AUTH_TOKEN</code>)</td>
      </tr>
      <tr>
          <td><code>ANTHROPIC_MODEL</code></td>
          <td>Default model for the main agent loop</td>
      </tr>
      <tr>
          <td><code>ANTHROPIC_DEFAULT_OPUS_MODEL</code></td>
          <td>What Claude Code uses when it internally requests Opus</td>
      </tr>
      <tr>
          <td><code>ANTHROPIC_DEFAULT_SONNET_MODEL</code></td>
          <td>What Claude Code uses when it internally requests Sonnet</td>
      </tr>
      <tr>
          <td><code>ANTHROPIC_DEFAULT_HAIKU_MODEL</code></td>
          <td>What Claude Code uses when it internally requests Haiku</td>
      </tr>
      <tr>
          <td><code>CLAUDE_CODE_SUBAGENT_MODEL</code></td>
          <td>Model for spawned sub-agents (Explore, Plan, etc.)</td>
      </tr>
      <tr>
          <td><code>CLAUDE_CODE_EFFORT_LEVEL</code></td>
          <td>Thinking budget — <code>max</code> gives the model the most reasoning tokens</td>
      </tr>
  </tbody>
</table>
<p>The <code>[1m]</code> suffix on model names is a DeepSeek convention for requesting the 1M-token context window. Without it, you get the default context length.</p>
<hr>
<h2 id="step-3-understand-model-mapping">Step 3: Understand Model Mapping</h2>
<p>DeepSeek does automatic model name mapping. When Claude Code internally requests <code>claude-opus-4-7</code>, DeepSeek&rsquo;s API maps it:</p>
<pre tabindex="0"><code>claude-opus-*    →  deepseek-v4-pro
claude-sonnet-*  →  deepseek-v4-flash
claude-haiku-*   →  deepseek-v4-flash
</code></pre><p>This means you don&rsquo;t need to patch Claude Code&rsquo;s source. When the agent decides it needs &ldquo;Opus-level&rdquo; reasoning, DeepSeek routes it to V4 Pro. When it wants Haiku for a fast sub-agent, it gets V4 Flash.</p>
<p>We explicitly set the model variables anyway (rather than relying on mapping) because it gives us control over which model handles sub-agents. V4 Flash is fast enough for search and file-reading sub-agents, and it&rsquo;s 3× cheaper than V4 Pro on input.</p>
<hr>
<h2 id="what-actually-works">What Actually Works</h2>
<h3 id="tool-calling">Tool Calling</h3>
<p>DeepSeek&rsquo;s Anthropic API fully supports <code>tool_use</code> and <code>tool_result</code> message types. Claude Code&rsquo;s entire agent loop is built on tool calling — Read, Write, Edit, Bash, Grep, Glob — and all of it works.</p>
<pre tabindex="0"><code>Message: array, type = &#34;tool_use&#34;
  - id:           Fully Supported
  - input:        Fully Supported
  - name:         Fully Supported
  - cache_control: Ignored

Message: array, type = &#34;tool_result&#34;
  - tool_use_id:  Fully Supported
  - content:      Fully Supported
  - is_error:     Ignored
</code></pre><h3 id="sub-agent-spawning">Sub-Agent Spawning</h3>
<p>Claude Code spawns specialized sub-agents (Explore for file search, Plan for architecture, etc.) using the <code>Agent</code> tool. Each sub-agent is itself a tool-calling loop with restricted permissions. This works on DeepSeek — we&rsquo;ve tested multi-agent sessions where the main V4 Pro agent spawns V4 Flash sub-agents for file search, and the results flow back correctly.</p>
<h3 id="web-search-the-surprising-part">Web Search (the surprising part)</h3>
<p>This is the feature that caught us off guard. <strong>DeepSeek&rsquo;s API natively supports Claude Code&rsquo;s built-in Web Search tool.</strong> When the model determines your question needs web results, it invokes the search tool through DeepSeek&rsquo;s own search infrastructure — not Anthropic&rsquo;s.</p>
<p>From DeepSeek&rsquo;s documentation:</p>
<blockquote>
<p>&ldquo;The DeepSeek API natively supports the Web Search feature in Claude Code. When using Claude Code, if the model determines that your question requires a web search, it will invoke the Web Search tool and perform the search through the API provided by DeepSeek.&rdquo;</p>
</blockquote>
<p>In practice: ask Claude Code &ldquo;what&rsquo;s the latest version of LangGraph?&rdquo; and it will trigger a web search, get results, and summarize them — all through DeepSeek. The <code>web_search_tool_result</code> message type is fully supported in the API.</p>
<p><strong>Cost caveat:</strong> Each web search triggers additional LLM API calls to summarize the retrieved content. DeepSeek bills these as normal token usage. A single search-then-summarize cycle might consume 5–20K extra input tokens.</p>
<h3 id="thinking-mode">Thinking Mode</h3>
<p>DeepSeek V4 supports thinking mode (extended reasoning). The <code>thinking</code> field in the API is supported, though <code>budget_tokens</code> is ignored — DeepSeek manages its own reasoning budget internally. Setting <code>CLAUDE_CODE_EFFORT_LEVEL=max</code> gives the model maximum latitude to think.</p>
<h3 id="streaming">Streaming</h3>
<p>Fully supported. Responses stream token-by-token just like native Claude.</p>
<hr>
<h2 id="what-doesnt-work">What Doesn&rsquo;t Work</h2>
<p>Being honest about limitations matters. DeepSeek&rsquo;s Anthropic API is not a perfect clone — it&rsquo;s a pragmatic subset.</p>
<h3 id="no-image-or-document-input">No Image or Document Input</h3>
<pre tabindex="0"><code>array, type = &#34;image&#34;     →  Not Supported
array, type = &#34;document&#34;  →  Not Supported
</code></pre><p>You can&rsquo;t paste screenshots or upload PDFs through Claude Code when using DeepSeek. If your workflow involves vision tasks (analyzing UI mockups, reading diagrams), you need native Claude or a vision-capable model for those sessions.</p>
<h3 id="no-prompt-caching">No Prompt Caching</h3>
<pre tabindex="0"><code>cache_control  →  Ignored (on tools, messages, and tool results)
</code></pre><p>DeepSeek has its own context caching (cache hits are priced separately), but the Anthropic-compatible endpoint ignores <code>cache_control</code> markers. Caching happens at DeepSeek&rsquo;s discretion based on content similarity, not explicit breakpoints.</p>
<h3 id="no-mcp-tool-passthrough">No MCP Tool Passthrough</h3>
<pre tabindex="0"><code>array, type = &#34;mcp_tool_use&#34;     →  Not Supported
array, type = &#34;mcp_tool_result&#34;  →  Not Supported
</code></pre><p>MCP (Model Context Protocol) tools work differently — they&rsquo;re handled client-side by Claude Code, not server-side by the API. So MCP tools like SearXNG, filesystem watchers, or database connectors still work because Claude Code intercepts them before they hit the API. The &ldquo;not supported&rdquo; here means DeepSeek&rsquo;s API won&rsquo;t process MCP messages natively, which doesn&rsquo;t affect actual functionality.</p>
<h3 id="minor-field-ignorances">Minor Field Ignorances</h3>
<ul>
<li><code>top_k</code> — ignored</li>
<li><code>anthropic-beta</code> / <code>anthropic-version</code> headers — ignored</li>
<li><code>stop_sequences</code> — fully supported</li>
<li><code>container</code>, <code>mcp_servers</code>, <code>service_tier</code> — ignored</li>
</ul>
<p>None of these affect core Claude Code functionality.</p>
<hr>
<h2 id="cost-comparison">Cost Comparison</h2>
<p>A realistic Claude Code session: ~500K input tokens (system prompt + context + tool definitions) and ~50K output tokens.</p>
<table>
  <thead>
      <tr>
          <th>Provider</th>
          <th>Input Cost</th>
          <th>Output Cost</th>
          <th>Session Total</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Opus 4.7 (direct)</td>
          <td>~$7.50</td>
          <td>~$3.75</td>
          <td><strong>~$11.25</strong></td>
      </tr>
      <tr>
          <td>DeepSeek V4 Pro</td>
          <td>~$0.22</td>
          <td>~$0.04</td>
          <td><strong>~$0.26</strong></td>
      </tr>
      <tr>
          <td>DeepSeek V4 Flash (sub-agents)</td>
          <td>~$0.07</td>
          <td>~$0.01</td>
          <td><strong>~$0.08</strong></td>
      </tr>
  </tbody>
</table>
<p>With the main agent on V4 Pro and sub-agents on V4 Flash, a typical mixed session costs around <strong>$0.15–0.30</strong>. That&rsquo;s roughly <strong>30–70× cheaper</strong> than Claude Opus direct — and it&rsquo;s the permanent price, not a limited-time promo.</p>
<p>The cache hit pricing makes repetitive sessions (same project, same CLAUDE.md, same tool definitions) even cheaper. Our workspace loads ~80K tokens of context on every session start — most of that hits cache at $0.003625/M.</p>
<hr>
<h2 id="real-world-tips">Real-World Tips</h2>
<h3 id="use-a-wrapper-script">Use a wrapper script</h3>
<p>Don&rsquo;t export environment variables in your <code>.bashrc</code> globally — you&rsquo;ll accidentally use DeepSeek for tools that need native Claude (like vision tasks). We use a <code>claude.sh</code> script:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#ff79c6">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_BASE_URL</span><span style="color:#ff79c6">=</span>https://api.deepseek.com/anthropic
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_AUTH_TOKEN</span><span style="color:#ff79c6">=</span>sk-your-key-here
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_OPUS_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_SONNET_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_HAIKU_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-flash
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">CLAUDE_CODE_SUBAGENT_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-flash
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">CLAUDE_CODE_EFFORT_LEVEL</span><span style="color:#ff79c6">=</span>max
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_USER_ID</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;your-username&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">exec</span> claude <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$@</span><span style="color:#f1fa8c">&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Run it with <code>bash claude.sh</code> or <code>source claude.sh &amp;&amp; claude</code>.</p>
<h3 id="the-anthropic_user_id-matters">The <code>ANTHROPIC_USER_ID</code> matters</h3>
<p>DeepSeek supports the <code>user_id</code> metadata field for rate limit isolation. Setting <code>ANTHROPIC_USER_ID</code> ensures your requests are bucketed separately from other users on the same API key — useful if you share a key across projects.</p>
<h3 id="v4-flash-for-routine-work">V4 Flash for routine work</h3>
<p>If you&rsquo;re doing routine file editing, formatting, or batch operations, swap <code>ANTHROPIC_MODEL</code> to <code>deepseek-v4-flash</code>. It&rsquo;s 3× cheaper and fast enough for non-reasoning tasks. Save V4 Pro for architecture decisions, debugging, and complex multi-step problems.</p>
<hr>
<h2 id="the-bottom-line">The Bottom Line</h2>
<p>DeepSeek&rsquo;s Anthropic-compatible API is the most seamless third-party Claude Code integration available. No proxy server, no SDK patches, no feature gaps on the things that matter (tool calling, sub-agents, web search). The only real limitation is vision — if you need image input, you still need native Claude.</p>
<p>For pure coding work, the cost savings are dramatic enough that there&rsquo;s no reason not to try it. Eight environment variables, one API key, and you&rsquo;re running.</p>
<hr>
<h2 id="the-configuration">The Configuration</h2>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># claude.sh — Source this before running `claude`</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_BASE_URL</span><span style="color:#ff79c6">=</span>https://api.deepseek.com/anthropic
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_AUTH_TOKEN</span><span style="color:#ff79c6">=</span>&lt;your-key&gt;
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_OPUS_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_SONNET_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-pro<span style="color:#ff79c6">[</span>1m<span style="color:#ff79c6">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">ANTHROPIC_DEFAULT_HAIKU_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-flash
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">CLAUDE_CODE_SUBAGENT_MODEL</span><span style="color:#ff79c6">=</span>deepseek-v4-flash
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">CLAUDE_CODE_EFFORT_LEVEL</span><span style="color:#ff79c6">=</span>max
</span></span></code></pre></td></tr></table>
</div>
</div><hr>
<p><em>References</em></p>
<ul>
<li><a href="https://api-docs.deepseek.com/quick_start/agent_integrations/claude_code">DeepSeek API: Integrate with Claude Code</a></li>
<li><a href="https://api-docs.deepseek.com/guides/anthropic_api">DeepSeek API: Anthropic API Compatibility</a></li>
<li><a href="https://api-docs.deepseek.com/quick_start/pricing">DeepSeek API: Models &amp; Pricing</a></li>
<li><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code Official Documentation</a></li>
</ul>
<hr>
<p><em>Built with: Claude Code (latest), DeepSeek V4 Pro + V4 Flash, Node.js 22. Written from a real multi-project workspace running this setup daily.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>How Claude Code&#39;s Agent Architecture Works — and How We Built a Similar System for a Terraria Server</title>
      <link>https://aibrew.ai/2026/05/how-claude-codes-agent-architecture-works-and-how-we-built-a-similar-system-for-a-terraria-server/</link>
      <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
      <guid>https://aibrew.ai/2026/05/how-claude-codes-agent-architecture-works-and-how-we-built-a-similar-system-for-a-terraria-server/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — We reverse-engineered Claude Code&amp;rsquo;s agent architecture from its TypeScript source to understand how it handles security, complex tasks, and tool permissions. Then we applied those patterns to an open-source Terraria AI bridge that lets players talk to an LLM inside the game. Here&amp;rsquo;s what we found, what we built, and what we learned about practical agent design.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-we-cracked-open-claude-codes-source&#34;&gt;Why We Cracked Open Claude Code&amp;rsquo;s Source&lt;/h2&gt;
&lt;p&gt;Claude Code isn&amp;rsquo;t just a coding assistant. Under the hood it&amp;rsquo;s an agent runtime — it spawns sub-agents, manages file permissions, runs bash commands, and decides when to ask the user vs. just doing the thing. We wanted to understand how it works so we could apply the same ideas to a completely different domain: a Terraria game server.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong> — We reverse-engineered Claude Code&rsquo;s agent architecture from its TypeScript source to understand how it handles security, complex tasks, and tool permissions. Then we applied those patterns to an open-source Terraria AI bridge that lets players talk to an LLM inside the game. Here&rsquo;s what we found, what we built, and what we learned about practical agent design.</p>
</blockquote>
<hr>
<h2 id="why-we-cracked-open-claude-codes-source">Why We Cracked Open Claude Code&rsquo;s Source</h2>
<p>Claude Code isn&rsquo;t just a coding assistant. Under the hood it&rsquo;s an agent runtime — it spawns sub-agents, manages file permissions, runs bash commands, and decides when to ask the user vs. just doing the thing. We wanted to understand how it works so we could apply the same ideas to a completely different domain: a Terraria game server.</p>
<p>Our project, <a href="https://github.com/d99sfrmdbz-debug/terra_llm_bridge">terra_llm_bridge</a>, connects a Terraria TShock server to an LLM. Players type <code>@ai</code> in chat and get responses — but the LLM can also <em>act</em>: give items, change weather, teleport players, even toggle hardmode. That last one is where we learned our lesson.</p>
<p>The first time a player asked the AI to set the weather to rain, the LLM autonomously decided to call <code>terra_world_hardmode(confirm=True)</code> — toggling <em>irreversible</em> hardmode for the entire server. No player had asked for it. The model just&hellip; did it.</p>
<p>We needed a real permission system. So we went looking at how Claude Code does it.</p>
<hr>
<h2 id="claude-codes-7-layer-permission-architecture">Claude Code&rsquo;s 7-Layer Permission Architecture</h2>
<p>Reading through ~1,500 lines of <code>src/utils/permissions/permissions.ts</code> plus the Agent tool infrastructure (~3,800 lines), a clear architecture emerged. Claude Code doesn&rsquo;t have one security check — it has <strong>seven</strong>:</p>
<pre tabindex="0"><code>Layer 1a: Deny rules   →  &#34;Never allow Bash(git push --force)&#34;
Layer 1b: Ask rules    →  &#34;Always prompt for Bash(curl *)&#34;
Layer 1c: Tool self-check  →  Each tool&#39;s checkPermissions() method
Layer 1d: Tool self-deny   →  Read tool whitelists specific paths
Layer 1f: Content-specific rules  →  &#34;Even in bypass mode, ask for npm publish&#34;
Layer 1g: Safety checks  →  &#34;.git/, .claude/ are ALWAYS bypass-immune&#34;
Layer 2:  Mode-based bypass  →  bypassPermissions / auto / acceptEdits / dontAsk
Layer 3:  YOLO classifier →  AI reads the transcript, decides if safe
</code></pre><p>The most interesting layer is the <strong>YOLO classifier</strong> — a separate small model that reads the full conversation transcript and classifies each tool call as safe or dangerous. It&rsquo;s a two-stage system: a fast classifier for obvious cases, and a deeper thinking classifier for edge cases.</p>
<p>But the layer that matters most for our use case isn&rsquo;t the AI classifier. It&rsquo;s how Claude Code <strong>structurally prevents certain tools from being called in the wrong context</strong> — through tool allowlists, denylists, and sub-agent specialization.</p>
<hr>
<h2 id="the-agent-pattern-not-multi-agent-but-specialized-workers">The Agent Pattern: Not Multi-Agent, but Specialized Workers</h2>
<p>Claude Code doesn&rsquo;t use multi-agent &ldquo;collaboration&rdquo; in the negotiation sense. It uses a <strong>single coordinator that spawns specialized workers</strong>:</p>
<pre tabindex="0"><code>Main Agent (Tool Calling, all tools)
  │
  ├─ Simple: &#34;read file X&#34; → Read tool
  │
  └─ Complex: &#34;audit this branch&#34; → Agent(&#34;Explore&#34;)
                                       │
                                       ├─ Tools: [Read, Grep, Glob]  ← whitelist
                                       ├─ Disallowed: [Edit, Write]   ← denylist
                                       ├─ System prompt: &#34;You are a file search specialist&#34;
                                       └─ Returns findings → Main agent acts on them
</code></pre><p>Each sub-agent type is defined by three things:</p>
<ol>
<li><strong>Tool permissions</strong> (allowlist + denylist) — what it can touch</li>
<li><strong>System prompt</strong> — specialized instructions for its role</li>
<li><strong>Model</strong> — Explore agents use Haiku ($) for speed; Plan agents use Sonnet for reasoning</li>
</ol>
<p>The key insight: <strong>the main agent doesn&rsquo;t get more complex</strong>. It stays simple but has ONE tool (<code>Agent</code>) that lets it offload complex work. The sub-agent is just another Tool Calling loop with restricted tools and a different prompt.</p>
<p>This architecture is elegant because it composes: each piece is simple, but the combination handles complexity that would overwhelm a single prompt.</p>
<hr>
<h2 id="how-we-applied-this-to-terra_llm_bridge">How We Applied This to terra_llm_bridge</h2>
<p>Our Terraria bridge has a simpler job than Claude Code — 46 tools instead of hundreds, and the &ldquo;security&rdquo; problem is &ldquo;don&rsquo;t let the AI toggle hardmode when the player asked about weather&rdquo; rather than &ldquo;don&rsquo;t let the AI rm -rf /&rdquo;. But the patterns transfer directly.</p>
<h3 id="the-problem">The Problem</h3>
<p>Before: our LLM saw all 46 tools at once. When a player asked &ldquo;give me the strongest armor set,&rdquo; the LLM would fire <code>wiki_search</code> AND <code>give_item</code> in parallel — researching while also pre-committing to Solar Flare Armor before reading the wiki results. Sometimes it guessed right. Sometimes it gave a summoner player melee gear.</p>
<h3 id="our-solution-two-phase-tool-access">Our Solution: Two-Phase Tool Access</h3>
<p>We didn&rsquo;t add sub-agents — that would be overkill for 46 tools. Instead, we applied the <strong>tool restriction pattern</strong> at the graph level:</p>
<pre tabindex="0"><code>route → llm(research)  ⇄  tool      →  escalate  →  llm(action)  ⇄  authorize  ⇄  tool  →  output
         17 read tools                            46 full tools     keyword gate
         wiki, lookup, status                     give, kick, spawn
</code></pre><p>The graph has two phases:</p>
<p><strong>Research phase</strong> — the LLM gets only 17 read-only tools (wiki_search, item_lookup, player_list, world_info, etc.). It <em>cannot</em> call give_item, kick, spawn, or any destructive tool. It researches first.</p>
<p><strong>Escalate</strong> — when the LLM produces text (no more tool calls needed), the graph automatically flips to action mode and injects a hint: &ldquo;You now have access to ALL tools.&rdquo;</p>
<p><strong>Action phase</strong> — the LLM gets the full 46-tool set and can act on what it found.</p>
<p>This is structurally enforced. Not a prompt suggestion. The LLM physically cannot call <code>give_item</code> during research because the tool isn&rsquo;t bound.</p>
<h3 id="the-permission-gate">The Permission Gate</h3>
<p>Before the two-phase split, we also added <code>authorize_node</code> — a hard gate between the LLM and ToolNode that checks whether the player&rsquo;s recent chat messages contain keywords for the tool&rsquo;s domain:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>GATED_TOOLS <span style="color:#ff79c6">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;terra_world_hardmode&#34;</span>: {<span style="color:#f1fa8c">&#34;hardmode&#34;</span>, <span style="color:#f1fa8c">&#34;hard mode&#34;</span>, <span style="color:#f1fa8c">&#34;肉山&#34;</span>, <span style="color:#f1fa8c">&#34;困难模式&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;terra_player_kick&#34;</span>:    {<span style="color:#f1fa8c">&#34;kick&#34;</span>, <span style="color:#f1fa8c">&#34;踢出&#34;</span>, <span style="color:#f1fa8c">&#34;踢了&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;terra_server_stop&#34;</span>:    {<span style="color:#f1fa8c">&#34;stop server&#34;</span>, <span style="color:#f1fa8c">&#34;关服&#34;</span>, <span style="color:#f1fa8c">&#34;停服&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4"># ... 8 more</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>If the player says &ldquo;set weather to rain&rdquo; and the LLM tries to call <code>world_hardmode</code>, authorize_node checks: do any of the hardmode keywords appear in the player&rsquo;s recent messages? No? <strong>Blocked.</strong> The tool call is replaced with a BLOCKED message before ToolNode ever sees it.</p>
<p>This is a coarse filter — it checks what the player <em>mentioned</em>, not what they <em>requested</em>. &ldquo;上次打肉山的时候&rdquo; (last time when I fought Wall of Flesh) would pass the keyword check even though the player didn&rsquo;t ask for hardmode. But coarse is fine here: the goal is blocking catastrophic mismatches (weather → hardmode), not perfect intent understanding.</p>
<hr>
<h2 id="what-we-chose-not-to-build">What We Chose NOT to Build</h2>
<h3 id="no-yolo-classifier">No YOLO Classifier</h3>
<p>Claude Code&rsquo;s AI classifier reads the full transcript and classifies tool calls as safe/dangerous. We didn&rsquo;t build this because:</p>
<ul>
<li>It adds latency — an extra LLM call before every gated tool execution</li>
<li>Terraria chat is low-stakes — a false positive (giving the wrong armor) is fixable</li>
<li>Keyword matching catches the catastrophic cases</li>
</ul>
<h3 id="no-sub-agent-spawning">No Sub-Agent Spawning</h3>
<p>Claude Code spawns sub-agent processes for complex tasks. We didn&rsquo;t need this because:</p>
<ul>
<li>Terraria tool surface is small (46 tools)</li>
<li>Multi-turn tool calling handles the complexity we actually face</li>
<li>Spawning sub-processes for a game chat bot is over-engineering</li>
</ul>
<h3 id="no-react-pattern">No ReAct Pattern</h3>
<p>The classic Thought → Action → Observation loop would add token overhead without changing our core capability. DeepSeek&rsquo;s thinking tokens already handle the reasoning, and the two-phase tool access enforces &ldquo;research before action&rdquo; more reliably than prompt-based ReAct would.</p>
<hr>
<h2 id="the-architecture-in-one-diagram">The Architecture in One Diagram</h2>
<pre tabindex="0"><code>┌──────────────────────────────────────────────────────────┐
│  Terraria Server (TShock + C# plugin, 24 game hooks)      │
│  Player types &#34;@ai give me the best armor&#34;                │
└──────────────────────┬───────────────────────────────────┘
                       │ JSON webhook
┌──────────────────────▼───────────────────────────────────┐
│  Python aiohttp listener (:9876)                          │
└──────────────────────┬───────────────────────────────────┘
                       │
┌──────────────────────▼───────────────────────────────────┐
│  LangGraph StateGraph                                     │
│                                                           │
│  route  →  llm(research)  ⇄  tool    17 read tools      │
│               │                                           │
│          escalate  →  llm(action)  ⇄  authorize  ⇄  tool │
│                          46 full tools    keyword gate    │
│               │                                           │
│             output  →  broadcast to game chat             │
│                                                           │
│  Memory: AsyncSqliteSaver per player (thread_id)          │
└──────────────────────────────────────────────────────────┘
                       │
         ┌─────────────┴──────────────┐
         ▼                            ▼
   TShock REST API              Terraria Wiki API
   (give / kick / spawn)        (terraria.wiki.gg)
</code></pre><hr>
<h2 id="source-diving-lessons">Source Diving Lessons</h2>
<p>Reading Claude Code&rsquo;s source taught us three things that apply to any agent project:</p>
<p><strong>1. Security is layered, not binary.</strong> A single <code>confirm</code> parameter is a soft suggestion to the LLM. Real security needs structural enforcement — the LLM shouldn&rsquo;t be able to call a tool it isn&rsquo;t authorized to use, same way a web server shouldn&rsquo;t let you access endpoints without authentication, no matter how nicely you ask.</p>
<p><strong>2. Tool restrictions are the cheapest and most reliable form of safety.</strong> Claude Code&rsquo;s Explore agent is &ldquo;read-only&rdquo; not because of a prompt — because Edit and Write aren&rsquo;t in its tool list. Our research phase isn&rsquo;t &ldquo;research-first&rdquo; because of a prompt — because give_item literally isn&rsquo;t bound. You can&rsquo;t prompt-inject your way past a tool that doesn&rsquo;t exist.</p>
<p><strong>3. Specialization beats complexity.</strong> Claude Code&rsquo;s sub-agents aren&rsquo;t smarter than the main agent — they&rsquo;re more constrained. Fewer tools + focused prompt = more reliable behavior. Our two-phase system does the same: constrain first, expand only when ready.</p>
<hr>
<h2 id="the-project">The Project</h2>
<p><code>terra_llm_bridge</code> is an open-source project connecting Terraria game servers to LLMs. It features:</p>
<ul>
<li><strong>24 game hooks</strong> — custom C# TShock plugin captures chat, boss kills, deaths, logins, and 20 more events</li>
<li><strong>46 admin tools</strong> — give items, manage players, control weather, spawn NPCs, manage regions and permissions</li>
<li><strong>Two-phase agent</strong> — research (17 tools) → action (46 tools)</li>
<li><strong>Hard permission gate</strong> — keyword-based authorize_node blocks unauthorized tool calls</li>
<li><strong>MCP server</strong> — same 46 tools exposed to Claude Code for server administration</li>
<li><strong>Persistent memory</strong> — per-player conversation history via LangGraph&rsquo;s AsyncSqliteSaver</li>
</ul>
<p>The project is currently in <strong>active testing</strong> and not yet published on GitHub. We&rsquo;re running it on a private Terraria server, iterating on the agent architecture before open-sourcing. If you&rsquo;re interested in the code or want early access, reach out.</p>
<hr>
<p><em>Built with: Python 3.14, LangGraph 1.x, DeepSeek (Anthropic-compatible API), C# .NET 9, TShock v6.1.0, aiohttp, httpx.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
