<?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>Go on Mini Fish</title>
    <link>https://blog.minifish.org/tags/go/</link>
    <description>Recent content in Go on Mini Fish</description>
    <image>
      <title>Mini Fish</title>
      <url>https://blog.minifish.org/android-chrome-512x512.png</url>
      <link>https://blog.minifish.org/android-chrome-512x512.png</link>
    </image>
    <generator>Hugo -- 0.165.0</generator>
    <language>en-US</language>
    <copyright>Mini Fish 2014-present. Licensed under CC-BY-NC</copyright>
    <lastBuildDate>Tue, 29 Sep 2026 18:00:00 +0800</lastBuildDate>
    <atom:link href="https://blog.minifish.org/tags/go/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Porting Pi to Go: Building Portsmith and Pith</title>
      <link>https://blog.minifish.org/posts/porting-pi-to-go-with-portsmith/</link>
      <pubDate>Tue, 29 Sep 2026 18:00:00 +0800</pubDate>
      <guid>https://blog.minifish.org/posts/porting-pi-to-go-with-portsmith/</guid>
      <description>Why I built a migration workbench, what failed during the first Pi-to-Go port, what the tests and billing dashboard actually show, and the plan to use Pith for future updates.</description>
      <content:encoded><![CDATA[<p>I wanted an agent runtime I could embed in a product, customize for a customer, and deliver without asking that customer to install Node.js or download npm dependencies. I also wanted to understand the runtime I was shipping.</p>
<p>That led to two projects:</p>
<ul>
<li><strong><a href="https://github.com/minifish-org/portsmith">Portsmith</a></strong>: a TypeScript-to-Go migration workbench built around the Pi Coding Agent.</li>
<li><strong><a href="https://github.com/minifish-org/pith">Pith</a></strong>: the resulting Go port of selected Pi AI, agent-core, and built-in tool capabilities.</li>
</ul>
<p>The first migration plan is complete: <strong>26 accepted steps across three modules</strong>. Pith builds without CGO and produces native command-line programs. That is a useful milestone, but it is not proof of complete Pi compatibility or a production-ready agent.</p>
<p>This is the record of what we built, how we checked it, and where the experiment goes next.</p>
<h2 id="why-port-instead-of-starting-over">Why port instead of starting over?</h2>
<p>The attraction of Pi was its separation of model access, agent execution, and tools. I did not want to reinvent every streaming edge case or every interaction in an agent loop before I could build a useful product.</p>
<p>Go appealed to me because of its build workflow and distribution model. A compiled Go program can be a convenient artifact for a server or a user&rsquo;s computer. That does not eliminate operating-system differences, and a shell tool still needs a shell. It does reduce the runtime setup I need to explain to someone else.</p>
<p>A port also does not have to reproduce every implementation detail. A mature Go library may be a better answer than translating a TypeScript utility line by line. The important questions are which behaviors must remain equivalent, which dependencies are acceptable, and how the differences will be tested.</p>
<p>We pinned Pi to <strong>v0.87.1</strong>, commit <a href="https://github.com/earendil-works/pi/tree/f07218c4d4bbc12bef056a7058c3dd49dfe41abe"><code>f07218c4d4bbc12bef056a7058c3dd49dfe41abe</code></a>. The scope was deliberately narrower than the entire repository: AI, the selected stable agent core, and built-in tools. TUI, Web UI, desktop packaging, Computer Use, MCP, and experimental/pico3 were not part of this migration.</p>
<h2 id="a-plan-before-a-loop">A plan before a loop</h2>
<p>My initial picture was simple: give an agent some TypeScript and ask for Go. The dependency graph made that insufficient almost immediately.</p>
<p>We needed an inventory of source files and exported symbols, a target Go package graph, decisions about third-party libraries, explicit behavior contracts, and tests that were not just written by the same model to approve its own answer.</p>
<p>Portsmith therefore separates planning from execution. A human or an external coding assistant prepares the plan. Portsmith executes reviewed steps, runs verification, records checkpoints, and integrates completed modules.</p>
<p>The migration had <strong>three delivery modules, 24 batches, and 26 internal steps</strong>:</p>
<table>
	<thead>
			<tr>
					<th>Module</th>
					<th style="text-align: right">Accepted steps</th>
					<th>Focus</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>AI</td>
					<td style="text-align: right">13</td>
					<td>Types, streaming utilities, authentication, models, and provider adapters</td>
			</tr>
			<tr>
					<td>Core</td>
					<td style="text-align: right">7</td>
					<td>Contracts, execution loop, resources, sessions, compaction, runtime, and harness</td>
			</tr>
			<tr>
					<td>Tools</td>
					<td style="text-align: right">6</td>
					<td>Execution environment, file and shell tools, and SDK/CLI delivery</td>
			</tr>
	</tbody>
</table>
<p>This gave me a small number of meaningful delivery milestones while keeping each generation task manageable. I did not have to type a separate command for every source file or judge.</p>
<h2 id="pi-needed-its-coding-agent-tools">Pi needed its coding-agent tools</h2>
<p>One early mistake was giving the migration model a restricted set of custom file operations. When the generated Go failed to compile, the model did not have the same practical workflow as a coding agent working in a repository.</p>
<p>We changed Portsmith to embed the full <strong>Pi Coding Agent</strong>. It retained its native file, edit, search, and Bash tools, persistent conversations, context management, and configured skills/extensions. Portsmith added a <code>verify_candidate</code> tool so the model could receive compiler and test diagnostics, repair the candidate, and try again in the same conversation.</p>
<p>The division of responsibility became much clearer:</p>
<ul>
<li>Pi explores, implements, runs commands, and repairs code.</li>
<li>Portsmith checks the frozen materials, runs independent acceptance, records progress, and decides whether a module can be integrated.</li>
</ul>
<p>A model saying “done” does not create an accepted module. Neither do self-tests alone.</p>
<p>The execution environment is still the local user&rsquo;s environment. Setting a candidate working directory is <strong>not sandboxing</strong>. That matters if this approach is applied to untrusted repositories or given access to sensitive credentials.</p>
<h2 id="the-failures-that-improved-the-tool">The failures that improved the tool</h2>
<p>The useful lessons came from interruptions rather than from the final success message.</p>
<p><strong>Compiler diagnostics must reach the model.</strong> Go&rsquo;s structured output can include <code>build-output</code> events. Filtering only ordinary test-output events hid the exact compiler error that the model needed. We fixed the diagnostic parser instead of asking the user to patch generated Go by hand.</p>
<p><strong>An arbitrary repair budget can turn automation into babysitting.</strong> The initial outer loop stopped after a few failed attempts. We changed the default to continued repair, while keeping explicit budgets available. The same applied to fixed file-count, file-size, and verification-time limits that had been chosen for a small experiment rather than a full migration.</p>
<p><strong>Bookkeeping can fail after the code has passed.</strong> The AI module&rsquo;s cumulative receipt grew to about 1.16 MB. Portsmith then rejected its own report under a 512 KiB source-file limit. The code had already passed; integration bookkeeping was the failure. The pending transaction and file hashes let us resume without regenerating the AI module.</p>
<p><strong>A live process is not necessarily making progress.</strong> One overnight stretch was dominated by laptop sleep and request timeouts. Elapsed wall time was a poor proxy for model or compiler speed. This is one reason I am not presenting the run as a clean performance benchmark.</p>
<p>Removing fixed limits did not make failures disappear. Authentication problems, exhausted provider retries, changed inputs, and Git conflicts can still require intervention. Tests can also hang. “Keep going by default” is a policy choice, not a guarantee that a run will finish unattended.</p>
<h2 id="what-the-completion-message-proves">What the completion message proves</h2>
<p>The final run reported <code>status: complete</code> and recorded these module commits:</p>
<table>
	<thead>
			<tr>
					<th>Module</th>
					<th>Commit</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>AI</td>
					<td><code>a5aeab8214f672d5d83e35fec78bf4d0daf67473</code></td>
			</tr>
			<tr>
					<td>Core</td>
					<td><code>adbc36268f0c0cad3f5aad7c591b5f0a18f79ac0</code></td>
			</tr>
			<tr>
					<td>Tools</td>
					<td><code>0d4d1479739fcbe849fec21fc02cf8ac31aee237</code></td>
			</tr>
	</tbody>
</table>
<p>The receipts record successful compilation, vet, candidate tests, independent behavior tests, and race checks. They deliberately retain <strong><code>fullParityProven: false</code></strong>.</p>
<p>For release preparation, I also ran a separate check on the completed tree:</p>
<ul>
<li><strong>963 passing Go test events</strong>, with no failures or skips, using <code>CGO_ENABLED=0</code> on macOS arm64.</li>
<li>All packages compiled without CGO for <strong>macOS arm64, Linux amd64, and Windows amd64</strong>.</li>
<li>The native <code>pith --help</code> command ran locally.</li>
</ul>
<p>Cross-compilation is not a runtime test on those other operating systems. This release check did not make live calls to every provider. A fixed set of judges cannot prove behavior that it does not exercise.</p>
<p>The <a href="migration-evidence.json">machine-readable evidence summary</a> includes module receipts, hashes, verification outcomes, and the release checks. Pith keeps the original receipts under <code>migration/results/</code>, along with contracts, judges, and source mappings. Raw agent conversations stay local rather than becoming public artifacts.</p>
<p>Portsmith itself changed during the run, including changes that had not yet been committed at execution time. The earlier Portsmith commit alone is therefore not a reproducible identifier for the whole experiment. Preserving and reviewing those changes is part of preparing the repositories for release.</p>
<h2 id="what-the-deepseek-dashboard-showed">What the DeepSeek dashboard showed</h2>
<p>For <strong>September 28–29, 2026</strong>, in <strong>GMT+7</strong>, the DeepSeek dashboard showed:</p>
<table>
	<thead>
			<tr>
					<th>Metric</th>
					<th style="text-align: right">Dashboard value</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Cost</td>
					<td style="text-align: right"><strong>¥30.59 CNY</strong></td>
			</tr>
			<tr>
					<td>API requests</td>
					<td style="text-align: right"><strong>2,543</strong></td>
			</tr>
			<tr>
					<td>Tokens</td>
					<td style="text-align: right"><strong>345,687,190</strong></td>
			</tr>
			<tr>
					<td>Model shown</td>
					<td style="text-align: right"><code>deepseek-flash</code></td>
			</tr>
			<tr>
					<td>API-key filter</td>
					<td style="text-align: right">All</td>
			</tr>
	</tbody>
</table>
<p><img alt="DeepSeek usage for September 28–29: ¥30.59 CNY, 2,543 requests, and 345,687,190 tokens, with the All API Key filter visible." loading="lazy" src="/posts/porting-pi-to-go-with-portsmith/deepseek-usage-2026-09-28-29.png"></p>
<p>This is <strong>account-wide usage for the selected dates</strong>, not a separately metered migration invoice. It includes whatever requests were billed to the account in that window, potentially including experiments and retries. It does not include the cost of the external planning assistant or local compute. The dashboard also notes that usage can lag by up to five minutes.</p>
<p>The token total is not the number of unique source-code tokens or generated Go tokens. It is the dashboard&rsquo;s aggregate usage across requests. The screenshot does not expose a cache-hit/input/output breakdown, so I am not using it to infer one.</p>
<p>The image is a cropped browser screenshot: account balance, profile, and the unrelated lifetime-cost figure are excluded. The date filter, API-key scope, totals, and model label remain visible.</p>
<h2 id="building-the-result">Building the result</h2>
<p>The completed checkout can build its command-line programs with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>CGO_ENABLED<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> go build -mod<span style="color:#f92672">=</span>readonly -trimpath -o ./bin/ ./cmd/...
</span></span><span style="display:flex;"><span>./bin/pith --help
</span></span></code></pre></div><p>Normal runtime builds should remain possible without CGO. The race detector is a separate testing concern and enables CGO for instrumentation. I want dependency selection to respect the no-CGO runtime goal, rather than discovering a mandatory C dependency just before shipping.</p>
<p>The current <code>pith</code> CLI is non-interactive and uses an OpenAI-compatible Chat Completions path. Having multiple provider packages in the repository does not mean every provider is exposed through that command. Real model calls, tool behavior in actual working directories, and session recovery still deserve hands-on acceptance testing.</p>
<p>Both repositories retain their existing AGPL licenses. Pi-derived material retains upstream MIT attribution. This is an independent project, not an official Pi distribution.</p>
<h2 id="next-let-pith-help-maintain-pith">Next: let Pith help maintain Pith</h2>
<p>The next experiment is to use <strong>Pith itself as the execution backend for future Pi-to-Pith migrations</strong>.</p>
<p>The loop I want is periodic rather than a one-off rewrite:</p>
<ol>
<li>Compare a new pinned Pi revision with the previous source inventory.</li>
<li>Identify affected symbols, packages, tests, and dependencies.</li>
<li>Review an incremental migration plan and update the independent contracts.</li>
<li>Use Pith to perform the port and repair candidates against those tests.</li>
<li>Preserve new receipts and submit the result for review.</li>
</ol>
<p>That backend and scheduled loop are <strong>planned, not implemented</strong>. The current Portsmith executor is Pi, and an upstream change can require a design decision rather than a translation.</p>
<p>What I have now is a concrete starting point: a Go runtime, a migration workbench, a completed first plan, and enough evidence to inspect the result. The next question is whether this method can keep the port useful as upstream continues to change.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
