sciClaw Engineering

sciClaw Development Log

Paired scientist AI for research workspaces
Addon System: sciClaw Can Host Real Scientific Tools Now
23Core Commits
3Repos
180+New Tests
0New Deps

Why We Built This

Three pressures hit sciClaw at the same time. First, scientists asked for browser desktops so collaborators outside the lab network could open the files the agent is discussing, not just chat about them. Second, labs wanted Jupyter notebooks wired into the same workspace. Third, every new capability was starting to land as a conditional in main.go, which was going to bloat the binary for laptop users who just wanted Discord and local skills.

The answer is a plugin system. Optional capabilities ship as separate installable addons that core sciClaw has zero knowledge of at build time. Laptop users who do not install the webtop addon pay nothing. Server operators who do, get a real Ubuntu desktop per scientist plus Jupyter Lab, both sharing the same workspace the agent already watches.

The design lives in docs/issues/addon-system-rfc.md. It was written before any code, reviewed, and then implemented across roughly two dozen commits.

What An Addon Actually Is

An addon is a separate git repository with three things in it:

  • An addon.json manifest declaring name, version, required runtime (docker or podman), sidecar binary path, and which hooks it wants to subscribe to.
  • A sidecar binary that speaks HTTP over a Unix socket, answering a small protocol: /health, /version, /hook/routing_changed, /shutdown, plus whatever control endpoints the addon needs.
  • Optional install and uninstall shell scripts for one-time setup like pulling Docker images.

The sidecar is spawned by the sciClaw gateway, talks to core over ~/.picoclaw/addons/<name>/sock, and has no direct access to any of sciClaw's internal Go types. The contract is HTTP, so addons can be written in any language a future maintainer prefers.

The Pipeline From sciclaw addon install To A Running Desktop

Trace through what happens when an operator runs sciclaw addon install file:///path/to/webtop --version v0.1.0:

CLI process Gateway process ---------- --------------- git clone the addon repo parse addon.json + validate resolve --version to a commit SHA compute SHA256 of manifest + bin run install.sh (pulls Docker images) write ~/.picoclaw/addons/registry.json touch ~/.picoclaw/addons/reload ─────► watchRoutingReload sees mtime bump exit Reconciler loads registry finds webtop state=enabled NewSidecar(addonDir, spec) exec.Command(bin/sciclaw-addon-webtop) poll /health until 200 Register in live SidecarRegistry log {"event":"start","addon":"webtop"} User clicks "Desktops" in the web UI browser GET /addons/webtop/ui/ ─────► sciClaw web proxies over the socket sidecar serves React bundle user clicks "Add user: alice" POST /addons/webtop/control/users ────► sidecar validates mount paths docker run --mount ... linuxserver/webtop registers alice in webtop.json alice clicks the returned URL (redirect to /addons/webtop/alice/)────► selkies WebRTC desktop loads

Every step above is real. It was smoke-tested end to end on this machine with the actual linuxserver/webtop:ubuntu-xfce image. Total time from install to running container was about 30 seconds on a cold cache.

What Shipped

ComponentRepoLinesWhat It Does
Core addon system sciClaw main ~3000 Go + 3100 tests Manifest parsing, registry, integrity hashing, git ref resolution, lifecycle state machine, sidecar HTTP client, hook dispatcher, reconciler, CLI commands, web proxy.
webtop addon sciclaw-addon-webtop ~2800 Go + 1200 React Per-user Ubuntu XFCE desktops via linuxserver/webtop, shared workspace mounts derived from routing rules, React admin panel.
jupyter addon sciclaw-addon-jupyter ~2800 Go Per-user Jupyter Lab with token auth, shared workspace mounts, token rotation, one-shot URL retrieval.

The two reference addons were built in sequence on purpose. The first one (webtop) forced every design question in the core contract. The second one (jupyter) proved the contract generalized by stressing different patterns: token auth instead of forward-proxy auth, own-UI iframing, and different hook semantics.

How It Extends The sciClaw Base

The base sciClaw model is one scientist talking to one agent in a chat channel. Multiple scientists in the same channel share the agent's context but each keeps their own /theme profile. Routing rules scope channels to workspaces so the agent only touches files the channel is allowed to see.

The addon system layers on top without moving any of that. When a routing rule changes, core emits a routing_changed hook with the new rules. The webtop addon catches that hook, recomputes the list of mounts each scientist should see, and restarts their container with the new bind mounts. The agent and the desktop now see the same files. A scientist who edits a .docx in Thunar sees the change reflected in the next agent turn, because both are writing to the same bytes on disk.

The user identity is unified through a new /api/core/users endpoint. Adding a user to webtop or jupyter no longer accepts freeform text. It reads from the profile store and routing allowed_senders, presents a dropdown of known scientists, and scopes all subsequent state to their existing sender ID. The same alice in Discord is the same alice in webtop and jupyter.

Security Went Through Two Full Reviews

The first review after the core system landed found three critical items. Proxy scope was wrong, so any HTTP request to the sciClaw web port could hit the sidecar's /shutdown endpoint. Container mount sources were unvalidated, so a well-crafted POST could mount / into a container with --privileged. Docker image names were unvalidated, so an image field of --privileged would get parsed as a flag by docker run.

All three got fixed before any reference addon was built. Then webtop and jupyter inherited the hardened baseline from day one: mount allowlists tied to WEBTOP_ALLOWED_MOUNT_ROOTS, the --mount type=bind KV syntax that cannot be tricked by colons or commas in user input, a -- terminator before every image name to block docker flag injection, Unix sockets explicitly chmod'd to 0600 after startup, and label-based uninstall filters so tearing down the addon does not delete unrelated containers on the host.

The second review surfaced six more items. A CLI routing change did not reach subscribed addons because the CLI process had no live dispatcher. A cross-process web proxy call could not reach a sidecar spawned by a different process. The routing reload watcher only started if routing was enabled at boot, so enabling routing at runtime silently dropped addon hooks. All closed. Upgrade failures now roll the working tree back when the registry write fails so the on-disk binary and the recorded commit stay in sync. Rollback now stops the old sidecar before the git checkout and restarts from the new binary, the same dance Upgrade already did.

Still open: signing verification is stubbed. The sidecar code for GPG tag checks exists but is never called from install or upgrade. The SBOM now reports verification_status: not_implemented instead of the old misleading field pair, so auditors see uncertainty explicitly. Real signing is a separate design pass that needs a trust model decision first.

The Operator Experience

From a clean sciClaw install to a running browser desktop for alice is four commands:

sciclaw addon install https://github.com/sciclaw/sciclaw-addon-webtop --version v0.1.0 sciclaw addon enable webtop sciclaw gateway (or: sciclaw service gateway start) # open http://127.0.0.1:4142 in a browser, click Desktops, click Add user, pick alice

The gateway reconciler picks up the enabled state and spawns the sidecar within one tick, about two seconds. The React UI polls /api/core/users and populates the dropdown from the same identity list the routing system and /theme profiles already use. Everything stays consistent with the rest of sciClaw.

Uninstalling is two commands. sciclaw addon disable webtop stops the sidecar gracefully over its HTTP shutdown path. sciclaw addon uninstall webtop removes the directory, runs the addon's own uninstall hook (which label-filters and removes only containers this addon spawned), and clears the registry entry.

What Is Next

  • Signing: wire the existing Verifier into the install flow. Pick a GPG integration path, document the trust model, add --trust-key and --allow-untrusted CLI flags.
  • Jupyter React UI: the jupyter addon's admin tab is still a placeholder. Wave B will ship a token rotation panel and a one-shot open URL flow, reusing the identity dropdown webtop already has.
  • Third addon: the RFC predicted the third addon would be when shared infrastructure (Caddy, cloudflared) should get promoted to a platform addon. Candidates include a persistent R Studio Server, an LSF submitter, or a shared pandoc pipeline.
  • Multi-user concurrency limits: nothing currently caps how many webtop containers a host can spawn. On a lab server this should be a per-operator quota.

Notable Commits

3dc573c8 L5 rollback sidecar restart + H1 honest SBOM signing status
55f57397 H2 per-name install flock + H5 upgrade rollback on Store.Set fail
29b8ec06 /api/core/users endpoint for addon identity dropdown
5abe88de H-E hook ctx wiring + L-2 reconciler log tag
ec21f2ba Security sweep round 2: proxy scope, payload leak, socket lockdown
76fae9cb Proxy addon UIs across sciclaw web/gateway process boundary
4257a2ef Always start reload watcher, even when routing disabled
83ee8a28 Wire CLI routing changes to gateway addon hook dispatch
1f791210 Wave 4a: live sidecar registry + lifecycle wiring
880c7000 Wave 4d: gateway reconciliation control loop
3d37841a Wave 4b: web backend endpoints for addon UI
4e2dfae4 Wave 4c: addon system UI integration (sidebar tabs)
b165f97f Wave 3a: CLI subcommand group
d2008e22 Wave 2a runtime: lifecycle, sidecar, hooks
0ac4755e Wave 1: data plane (manifest, registry, integrity, resolver)
e117d3d8 RFC: modular addon system with webtop + jupyter references