clawdie-ai/html/docs-clawdie-si/guides/nanoclaw-upstream.html

580 lines
20 KiB
HTML
Raw Normal View History

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NanoClaw Upstream — Clawdie Docs</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=DM+Mono:wght@300;400&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/css/shared.css" />
</head>
<body>
<div class="hex-bg"></div>
<div class="sidebar-overlay" id="overlay"></div>
<header class="top-bar">
<button class="mobile-menu-btn" id="menuBtn" aria-label="Toggle menu">
&#9776;
</button>
<a href="/" class="brand"><span>&#9651;</span> Clawdie Docs</a>
<div class="nav-links">
<a href="https://clawdie.si">Home</a>
<a href="https://codeberg.org/Clawdie" target="_blank" rel="noopener"
>Source</a
>
</div>
</header>
<div class="docs-layout">
<nav class="sidebar" id="sidebar">
<div class="sidebar-section">
<span class="section-label">Getting Started</span>
<ul>
<li><a href="/">Introduction</a></li>
<li><a href="/docs/install.html">Installation</a></li>
<li><a href="/docs/iso.html">ISO Install</a></li>
<li><a href="/docs/split-brain.html">Layered Memory</a></li>
</ul>
</div>
<div class="sidebar-section">
<span class="section-label">Architecture</span>
<ul>
<li><a href="/docs/">How It Works</a></li>
<li><a href="/docs/#jails-not-docker">Jails, Not Docker</a></li>
<li><a href="/docs/#wayland-first-display">Wayland Display</a></li>
<li>
<a href="/docs/#prompt-injection-and-web-browsing"
>Prompt Injection</a
>
</li>
<li>
<a href="/guides/nanoclaw-upstream.html" class="active"
>NanoClaw Upstream</a
>
</li>
<li>
<a
href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/docs/public/operate/monitoring.md"
target="_blank"
rel="noopener"
>Monitoring</a
>
</li>
<li>
<a
href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/docs/public/operate/security.md"
target="_blank"
rel="noopener"
>Security</a
>
</li>
</ul>
</div>
<div class="sidebar-section">
<span class="section-label">Setup Guides</span>
<ul>
<li><a href="/guides/nginx-ssl.html">Nginx + SSL</a></li>
<li><a href="/guides/tailscale-vpn.html">Tailscale VPN</a></li>
</ul>
</div>
<div class="sidebar-section">
<span class="section-label">Integrations</span>
<ul>
<li><a href="/guides/stripe-agents.html">Stripe Agents</a></li>
<li><a href="/guides/protonmail.html">ProtonMail</a></li>
</ul>
</div>
<div class="sidebar-section">
<span class="section-label">Project</span>
<ul>
<li><a href="/changelog.html">Changelog</a></li>
<li><a href="/license.html">License</a></li>
</ul>
</div>
</nav>
<main class="content">
<div class="breadcrumb">
<a href="/">Home</a><span class="sep">/</span>
<a href="/docs/">Docs</a><span class="sep">/</span>
NanoClaw Upstream
</div>
<div class="page-header">
<h1>NanoClaw <span>Upstream</span></h1>
<p class="subtitle">
Fetch-only tracking &mdash; operator decides what to apply, nothing
auto-merges
</p>
</div>
<p>
<a
href="https://github.com/qwibitai/nanoclaw"
target="_blank"
rel="noopener"
>NanoClaw</a
>
is an upstream project by Gavriel in the broader OpenClaw line
inspired by
<a href="https://github.com/steipete" target="_blank" rel="noopener"
>Peter Steinberger's</a
>
<a
href="https://github.com/openclaw/openclaw"
target="_blank"
rel="noopener"
>OpenClaw</a
>. Clawdie is the FreeBSD-first fork in that lineage. The upstream
toggle lets you see what new commits are available in NanoClaw and
decide what to apply to your installation.
</p>
<div class="info-box">
<span class="info-label">Read-only by design</span>
<p>
This feature only fetches. It never merges, rebases, or modifies
your working tree. You are always in control. The agent can report
what's available; a human applies the changes.
</p>
</div>
<div class="info-box">
<span class="info-label">Why this matters in Clawdie</span>
<p>
NanoClaw gives us the Linux-origin upstream line. Clawdie gives
operators a FreeBSD-native deployment path with jails, PF, ZFS, and
lower-friction onboarding through preloaded skills memory. Upstream
tracking is the maintainer side of that story; polished FreeBSD
bootstrap is the operator side.
</p>
</div>
<div class="info-box">
<span class="info-label">Built on giants' shoulders</span>
<p>
OpenClaw set the broader direction. NanoClaw carried that line into
a lean upstream personal assistant. Clawdie takes the same line into
FreeBSD and the wider
<a
href="https://osa.smilepowered.org"
target="_blank"
rel="noopener"
>OSA</a
>
stack.
</p>
</div>
<div class="divider"></div>
<section>
<h2>Setup</h2>
<h3>Enable upstream tracking</h3>
<p>Run once during or after initial setup:</p>
<pre><code>npx tsx setup/index.ts --step upstream --enable</code></pre>
<p>This does three things atomically:</p>
<ol>
<li>
Adds a <code>nanoclaw</code> git remote pointing to
<code>github.com/qwibitai/nanoclaw.git</code>
</li>
<li>
Runs <code>git fetch nanoclaw --no-tags</code> (read-only, no tag
pollution)
</li>
<li>
Writes <code>NANOCLAW_UPSTREAM_ENABLED=true</code> to your
<code>.env</code>
</li>
</ol>
<h3>Check status</h3>
<pre><code>npx tsx setup/index.ts --step upstream --status</code></pre>
<h3>Disable</h3>
<pre><code>npx tsx setup/index.ts --step upstream --disable</code></pre>
<p>
Sets <code>NANOCLAW_UPSTREAM_ENABLED=false</code> in
<code>.env</code>. The remote stays configured &mdash; re-enable any
time without re-fetching history.
</p>
<div class="info-box warning">
<span class="info-label">Prerequisites</span>
<p>
Clawdie must be initialised as a git repository (happens during
install). Internet access required for the initial fetch.
Subsequent fetches are incremental &mdash; only new commits are
transferred.
</p>
</div>
<h3>Not the same as skills-memory bootstrap</h3>
<p>
Upstream tracking and preloaded skills memory solve different
problems. The upstream toggle helps maintainers follow NanoClaw
evolution. The skills-memory bootstrap helps operators get through
install with fewer setup-time LLM calls by importing precomputed
vectors into a separate skills database.
</p>
</section>
<div class="divider"></div>
<section>
<h2>Agent tool: check_upstream_updates</h2>
<p>
Once upstream is enabled, the agent has a
<code>check_upstream_updates</code>
MCP tool available in every session. Ask from chat:
</p>
<div class="info-box">
<span class="info-label">Example conversation</span>
<p><em>"What's new in NanoClaw upstream?"</em></p>
<p>
&rarr; Agent calls <code>check_upstream_updates</code>, reads
commits in <code>nanoclaw/main</code> not yet in
<code>HEAD</code>, and returns a readable list with a cherry-pick
hint.
</p>
</div>
<p>The tool returns one of three results:</p>
<table>
<thead>
<tr>
<th>Situation</th>
<th>Response</th>
</tr>
</thead>
<tbody>
<tr>
<td>Remote not configured</td>
<td>
Instruction to run <code>--step upstream --enable</code>
</td>
</tr>
<tr>
<td>Up to date</td>
<td>
"Up to date with NanoClaw upstream" (+ local-ahead count if
any)
</td>
</tr>
<tr>
<td>Commits available</td>
<td>Commit list with hashes, messages, cherry-pick hint</td>
</tr>
</tbody>
</table>
<p>
The tool runs against the host project directory
(<code>/workspace/project</code>) from inside the agent jail &mdash;
it does not need network access and never modifies any files.
</p>
</section>
<div class="divider"></div>
<section>
<h2>Automatic fetch (optional cron)</h2>
<p>
The <code>scripts/fetch-upstream.ts</code> script is designed for
cron. It fetches the remote, prints a divergence summary, and exits.
Nothing is modified on the working tree.
</p>
<p>Typical weekly cron (run as the agent user):</p>
<pre><code># crontab -e (as clawdie user)
0 3 * * 1 cd /home/clawdie/clawdie-ai && npx tsx scripts/fetch-upstream.ts >> logs/upstream.log 2>&amp;1</code></pre>
<p>Sample output:</p>
<pre><code>[13.mar.2026, 03:00:01] Fetching nanoclaw/main...
! 3 upstream commit(s) available:
a4f2c11 feat: add add-image-vision skill
9e3b881 fix: channel registry disconnect edge case
1d0a9c4 chore: bump @modelcontextprotocol/sdk to 1.9.0
(7 local commit(s) ahead of upstream)</code></pre>
<p>
Set <code>NANOCLAW_UPSTREAM_ENABLED=false</code> in
<code>.env</code>
to skip the fetch silently &mdash; useful if you need to temporarily
disable without removing the cron entry.
</p>
</section>
<div class="divider"></div>
<section>
<h2>Applying upstream changes</h2>
<p>
Upstream commits are never applied automatically. The standard
workflow after reviewing what's available:
</p>
<pre><code># Inspect a commit before touching your tree
git show a4f2c11
# Apply one commit
git cherry-pick a4f2c11
# Apply a range of commits
git cherry-pick 9e3b881^..a4f2c11
# Just read for ideas &mdash; the FreeBSD port often diverges intentionally
git diff HEAD...nanoclaw/main -- src/channels/registry.ts</code></pre>
<div class="info-box warning">
<span class="info-label">FreeBSD divergence is intentional</span>
<p>
NanoClaw targets Linux/Docker. Clawdie targets FreeBSD/Bastille
jails. Not every upstream commit applies cleanly &mdash; and some
shouldn't. Read before you cherry-pick. The agent can explain what
a commit does.
</p>
</div>
</section>
<div class="divider"></div>
<section>
<h2>Option A vs Option B</h2>
<p>
Two architectures for upstream tracking. Option A is implemented and
running. Option B is documented in Phase 7 of the refactor plan
&mdash; deferred until Option A proves insufficient.
</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>
Option A &mdash; current <span class="tag live">live</span>
</th>
<th>
Option B &mdash; Gitea jail
<span class="tag defer">phase 7</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Upstream remote</td>
<td><code>codeberg.org</code> (public)</td>
<td>Self-hosted Gitea jail</td>
</tr>
<tr>
<td>Infrastructure</td>
<td>None &mdash; just a git remote</td>
<td>
Gitea jail + <code>zroot/git/nanoclaw</code> ZFS dataset
</td>
</tr>
<tr>
<td>Auto-fetch</td>
<td>Optional cron script</td>
<td>Gitea mirror (hourly, webhook-triggered)</td>
</tr>
<tr>
<td>Agent visibility</td>
<td><code>check_upstream_updates</code> tool</td>
<td>
Same + <code>gitea_list_repos</code>,
<code>gitea_create_branch</code>
</td>
</tr>
<tr>
<td>Applying changes</td>
<td>Manual <code>git cherry-pick</code></td>
<td>
Agent proposes branch &rarr; operator reviews PR in Gitea UI
</td>
</tr>
<tr>
<td>Private fork support</td>
<td>Not applicable</td>
<td>Full: private repos, per-agent datasets</td>
</tr>
<tr>
<td>Internet required</td>
<td>Yes (fetch)</td>
<td>Only for initial mirror; air-gapped after</td>
</tr>
<tr>
<td>When to upgrade</td>
<td colspan="2">
When you need GitOps, private forks, or air-gapped deployment
</td>
</tr>
</tbody>
</table>
<p>
Switching from Option A to Option B: update
<code>NANOCLAW_REMOTE_URL</code> in
<code>setup/upstream.ts</code> to point to your Gitea instance,
re-run <code>--step upstream --enable</code>. The
<code>NANOCLAW_UPSTREAM_ENABLED</code>
flag and all agent tools remain the same.
</p>
</section>
<div class="divider"></div>
<section>
<h2>How it works</h2>
<p>
The <code>upstream</code> setup step lives in
<code>setup/upstream.ts</code> and registers in the standard setup
step registry in <code>setup/index.ts</code>. It uses the same
<code>emitStatus</code> / <code>logger</code> pattern as every other
setup step.
</p>
<p>
The <code>check_upstream_updates</code> MCP tool is registered
directly in
<code>jail/agent-runner/src/ipc-mcp-stdio.ts</code> &mdash; the same
server used for task scheduling and messaging. It calls
<code>git log HEAD..nanoclaw/main --oneline --no-merges</code>
against <code>/workspace/project</code> (the host repo mounted
read-only into the jail) and returns the result as text.
</p>
<p>
The cron script at <code>scripts/fetch-upstream.ts</code> runs on
the host (not inside a jail) and is intentionally minimal &mdash; it
has no dependencies beyond Node.js and git.
</p>
<table>
<thead>
<tr>
<th>File</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>setup/upstream.ts</code></td>
<td>Setup step: enable/disable/fetch/status</td>
</tr>
<tr>
<td><code>setup/index.ts</code></td>
<td>Registers <code>upstream</code> in STEPS registry</td>
</tr>
<tr>
<td><code>scripts/fetch-upstream.ts</code></td>
<td>Cron-safe host-side fetch script</td>
</tr>
<tr>
<td><code>jail/agent-runner/src/ipc-mcp-stdio.ts</code></td>
<td>Registers <code>check_upstream_updates</code> MCP tool</td>
</tr>
<tr>
<td><code>.env</code></td>
<td><code>NANOCLAW_UPSTREAM_ENABLED</code> toggle flag</td>
</tr>
</tbody>
</table>
</section>
<footer>
<div class="footer-left">
<a href="https://clawdie.si">Clawdie AI</a> &middot;
<a
href="https://osa.smilepowered.org"
target="_blank"
rel="noopener"
>OSA — Mission Statement</a
>
&middot; NanoClaw Upstream<br />
<a
href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/html/docs-clawdie-si/guides/nanoclaw-upstream.html"
target="_blank"
rel="noopener"
>Page source</a
>
·
<a
href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/docs/internal/nanoclaw-architecture-final.md"
target="_blank"
rel="noopener"
>Architecture</a
><br />
Last updated: 13.mar.2026
</div>
<div class="footer-hex">&#9651;</div>
</footer>
</main>
<aside class="toc">
<p class="toc-title">On this page</p>
<nav id="toc-list"></nav>
</aside>
</div>
<script>
const toc = document.getElementById('toc-list');
if (toc) {
document.querySelectorAll('.content h2, .content h3').forEach((h) => {
if (!h.id)
h.id = h.textContent
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-');
const a = document.createElement('a');
a.href = '#' + h.id;
a.className = 'toc-link' + (h.tagName === 'H3' ? ' toc-sub' : '');
a.textContent = h.textContent;
toc.appendChild(a);
});
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((e, i) => {
if (e.isIntersecting)
setTimeout(() => e.target.classList.add('visible'), i * 80);
});
},
{ threshold: 0.08 },
);
document.querySelectorAll('section').forEach((s) => observer.observe(s));
const menuBtn = document.getElementById('menuBtn');
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
if (menuBtn && sidebar) {
menuBtn.addEventListener('click', () => {
sidebar.classList.toggle('open');
overlay?.classList.toggle('open');
});
overlay?.addEventListener('click', () => {
sidebar.classList.remove('open');
overlay.classList.remove('open');
});
}
</script>
</body>
</html>