Imported from xbin-dev/xbin (
AGENTS.md). Install upstream withnpx skills add xbin-dev/xbin. Copyright stays with the author.
AGENTS.md — developing xbin itself
This file is for agents and humans hacking on the xbin platform in this
repo (daemon, CLI, SDK, core elements, builtin tiles, docs). For building
tiles inside a workspace, the reference is workspace-template/AGENTS.md —
the file every workspace ships to its own agents. Design docs live in
ARCHITECTURE.md and plans/ (with plans/DECISIONS.md recording every
decision + rationale — check it before re-litigating a choice).
Layout
| Path | What |
|---|---|
cmd/xbind, cmd/bx |
daemon, CLI |
internal/{server,registry,watch,term,runner,proxy,broker,sandbox,auth,vault,resenc,events,deps,jsonc,util,…} |
the platform |
sdk/ |
Go SDK — own module, zero dependencies, forever (components inherit it) |
web/ |
core elements (bx-frame, bx-terminal, bx-grants, bx-bindings, bx-multiselect, xbin-client, theme.css), served at /vendor/ |
workspace-template/ |
the scaffold xbind init stamps out — incl. the shell, admin/manager tiles, welcome tile, and the workspace AGENTS.md |
builtin-tiles/, builtin-templates/ |
optional importable tiles / blueprints |
docs/ |
builder docs, embedded into xbind, served at /docs/ |
examples/ |
doubles as integration fixtures and docs — keep them working |
Dev flow
make dev # xbind from source against ./devws — isolated, auth ON
# (login admin/admin), dev vault key, web/ + docs/ served
# FROM DISK: frontend and doc edits are live, Go changes
# need a restart
make dev-noauth # frictionless: every request is admin (plaintext vault)
make test # unit tests — fast, no network
make integration # end-to-end; compiles real Go backends (network on first
# run for module downloads). Includes the container-store
# fs suites (test/containerfs/): those need bin/gocryptfs
# (`make gocryptfs`) + unprivileged userns, and skip with
# instructions when missing
make check # the definition of done: fmt-check vet js-check shellcheck
# pins-offline test — CI runs exactly this, then integration
make hooks # once per clone: fmt-check + js-check run pre-commit
make release TAG=vX.Y.Z # the whole release (docs/maintenance.md → Releasing)
./hack/vendor.sh # refresh pinned frontend deps (lit, xterm, marked) + hack/vendor.sha256
devws/is throwaway state; never ship anything that lives there.make devserves the scaffold (shell, admin tile, …) fromworkspace-template/via--dev-overlay, so edit the source and reload — never copy files intodevws/(docs/maintenance.md → "the dev overlay").- Build output is ignored (
bin/,dist/,/bx,*/backend/backend);git statusafter a build must be clean. The five embedded trees (web/,docs/,workspace-template/,builtin-tiles/,builtin-templates/) ship inside every xbind and are guarded byassets_test.go: no binaries, nothing over 512 KB outsideweb/vendor/, no nested repos, and noplans/pointers — inside those trees cite the served docs and decision IDs (docs/maintenance.md→ "Embedded assets"). - Frontend has no unit-test runner:
make js-checkparses every shipped.jsand inline<script type="module">block (part ofmake check); behaviour is verified by clicking through inmake devor the harness below. Say so honestly in the commit if you couldn't drive the UI. To look without a browser session — and to run the browser regression passes —hack/ui-harness/run.shseeds a throwaway workspace (orgs, network sets, users, org tiles in every binding state) and runs the Playwright passes inshots.js: screenshots +<select>dumps, plus asserting passes (windows, reload focus, net pickers, permission sets, open-links, context copy) that fail the run.--shots <pass>runs one. Passes drivetestApi()on bx-shell / bx-frame / bx-admin, never private members (make js-checkenforces it); rules indocs/maintenance.md→ "UI harness". It found real bugs on its first run — use it for UI work. - Builtin tile backends aren't part of the workspace build:
make tile-checkvets and tests each one (and the agent template's) against its owngo.mod.tilein a scratch copy, the way a workspace builds it (CI runs it;hack/tile-check.sh devboxfor one). Never copygo.mod.tiletogo.modin place. - Verify before pushing:
make check(every guard, under a minute) — andmake integrationwhen you touched the runner/sandbox/broker path. The guards and what each protects:docs/maintenance.md.
Hard rules
- No JS build step, ever. Plain ES modules + import maps; vendored
single-file deps in
web/vendor/. TS syntax is banned inweb/. - One sanctioned HTML transform (
internal/server/static.go). No other rewriting. sdk/stays zero-dependency.- Grants/identity keep
plans/auth.mdsemantics: xbind strips inboundX-XBin-*, default-deny for element principals, owner is admin. - Nothing runs with xbind's privileges on data a sandbox can write
(D78). Tile directories and homes are written from inside sandboxes —
terminals, coding agents — and tools read content as configuration: a
repo's
.git/confignames commands (core.fsmonitor, filters,diff.external),go buildruns git for VCS stamping,go.modsteers downloads. So a host-sidegit logon a tile is code execution as xbind for whoever last wrote that tile. Every tool on workspace data runs throughinternal/confine—confine.Git/GitRead/Run: a throwaway sandbox with only the paths it needs, no capabilities, no network unless asked. A directexec.Commandin daemon code needs// exec-ok: <why>on the call — legitimate only when isolation is off (no sandbox exists; tiles already run as xbind) or the input is xbind's alone (data/,.xbin/, host inventory);TestNoDirectExec(internal/confine) fails otherwise. With isolation on, a sandbox that will not start is an error: never fall back to running on the host (a terminal once did — a non-admin got a host shell). - Never hardcode an install path in tiles OR persist one into workspace state — paths change under rename/clone.
- Never break an existing workspace.
docs/compat.mdis the contract and every change to shipped files, routes, manifests,bx, the SDK or boot-time migrations is checked against it: API additive-only, shipped URLs frozen, scaffold layouts additive (entry files keep their names, new siblings imported relatively, no bare import-map specifiers), theme fallbacks kept, CLI a superset, migrations idempotent with a fixture test. A silent path that must become an error warns for one release first — except a security hole: that closes now, with a changelog entry.
Docs discipline — docs are the contract
docs/ is what bx, the SDKs, workspace agents, and every tile builder
program against; drift there becomes other people's 404s. Update docs in
the same commit as the change, per this table:
| You changed | Update |
|---|---|
HTTP/WS surface (any /api/xbin/*, /ws/*) |
docs/protocol.md and internal/server/openapi.go — make test fails on drift (internal/apicheck; rules in docs/maintenance.md) |
| builder-visible behavior | the relevant docs/*.md, workspace-template/AGENTS.md, and the welcome tile notes (workspace-template/apps/welcome/notes.js) if it teaches that area |
| a design decision | plans/<area>.md + an entry in plans/DECISIONS.md |
| a guard, budget or check | docs/maintenance.md (what it protects, how to satisfy it) |
| the upgrade contract | docs/compat.md — and a changelog line when a rule gains a warned transition |
bx |
docs/bx.md + the usage strings in cmd/bx |
| anything builder-visible | a docs/changelog.md entry (see below) |
| a breaking change | also docs/changes/YYYY-MM-DD-<slug>.md, linked from the changelog |
When you change a contract, grep for every place it is stated — docs,
examples, error messages, comments — and fix them all. Cautionary tale: the
instance-path contract was correct in plans/ and in code, but one
misleading example literal ("/api/path/prefix") repeated across
protocol.md, elements.md, the workspace AGENTS.md and the endpoint's
own 400 message taught two independent teams the wrong contract
(docs/changes/2026-07-06-instance-paths.md). The API's error strings are
documentation too.
The changelog (docs/changelog.md)
Every builder-visible change lands a changelog entry in the same commit:
new/changed endpoints or env vars, manifest fields, SDK/xbin.* API, core
element behavior, builtin tile capabilities, wire/on-disk formats. Internal
refactors don't.
- Newest first, grouped by date under
## YYYY-MM-DD, one- area: what changedline each — written for a tile builder, not for us. - A change is BREAKING when existing tiles/providers must change code or
config, or a wire/persisted format changes incompatibly. Mark the entry
**BREAKING**, and add a migration note atdocs/changes/YYYY-MM-DD-<slug>.mdwith exactly these sections: What changed · Who's affected · How to migrate · Why. Link it from the entry. - These files are embedded and served at
/docs/changelog.mdand/docs/changes/…— workspace agents are told (in the workspaceAGENTS.md) to check the changelog after an xbind upgrade, so this is the channel by which every workspace learns what moved. Keep it true.
Commit style
Small commits, imperative subject with an area prefix (interfaces:,
shell:, term:, vault:, install:, docs: — see git log). The body
explains why and names the failure mode fixed, not just the diff. Report
verification honestly (what was tested, what wasn't).