ADR-0010: Introduce acq Pluggable-Backend Wrapper and Deprecate qsbx
July 22, 2026 · View on GitHub
Context and Problem Statement
qsbx is the current wrapper around the sbx CLI. It provides sandbox
create/attach, kit application, USAi key validation, and advisories. It works
well but has two structural limitations:
- It is
sbx-only. The shape of the CLI is baked intoqsbx— there is no seam where a different isolation runtime (e.g. microsandbox, Podman) can be substituted without rewriting the wrapper. - Its name encodes the backend.
qsbxmakes the implementation detail visible in every documented command and in users' muscle memory.
A second backend (msb — microsandbox) is planned for 1.2.0. Before it ships,
the architecture needs a formal adapter seam so adding msb.sh is purely
additive and does not require editing the core dispatch.
Decision Drivers
- Additive, non-breaking. 1.1.0 adds
acqwithout changing or removingqsbx.qsbxremains fully functional until 2.0.0. - Bash + adapter directory. Matches the existing
qsbxtech stack; proven code moves verbatim (no rewrite). A Go/Python port is a possible future (noted in the design doc), not now. - XDG config. The config file belongs at
${XDG_CONFIG_HOME:-$HOME/.config}/acq/config.yaml— consistent with OpenCode (~/.config/opencode/) and other tooling in this ecosystem. The design doc drafts~/.acq/but this ADR corrects it. - Minimal footprint. No new runtime dependencies. Config parsed with
awk/grep(one key). Kit constants are copied fromqsbxfor 1.1.x and diverge only when 1.2.x introduces neutral kits.
Considered Options
- Add
acq(sbx driver) alongsideqsbx, deprecateqsbx. Chosen. - Refactor
qsbxin place to support backends via an env var. Rejected: changesqsbxbehavior, entangles the deprecation story, harder to test. - Skip the wrapper entirely and document raw sbx. Rejected: loses the kit application, USAi key validation, and advisory logic that users depend on.
Decision Outcome
Chosen: Option 1.
- New file
acq(bash,chmod +x). Sourcesacq.backends/common.sh(backend-agnostic logic, resolution, shared utilities) and dispatches throughacq_backend_*functions defined by the active backend adapter (acq.backends/sbx.shfor 1.1.x). acqis the recommended entry point from 1.1.0 onward.qsbxgains a one-line stderr deprecation notice (silenceable viaQSBX_SILENCE_DEPRECATION=1) but is otherwise unchanged.qsbxwill be removed in 2.0.0.
Adapter contract (backend acq.backends/*.sh files)
Each backend file is sourced and must define the following functions plus four capability flag variables:
| Function | Purpose |
|---|---|
acq_backend_prepare | Check CLI installed + version floor; fail closed |
acq_backend_provision NAME AGENT PATHS... | Create sandbox, apply kits |
acq_backend_run NAME -- CMD... | Run command inside; return exit status |
acq_backend_attach NAME [-- AGENT_ARGS...] | Interactive attach (TTY) |
acq_backend_exists NAME | 0 if sandbox exists, else 1 |
acq_backend_stop NAME | Stop without removing |
acq_backend_terminate NAME | Permanently remove |
acq_backend_cp SRC DST | Copy in/out (NAME:path syntax) |
acq_backend_ports NAME [args...] | List/publish ports |
acq_backend_list | List sandboxes |
acq_backend_apply_kit NAME KITREF | Apply a kit mid-life |
acq_backend_secret_set SERVICE [args...] | Thin secret wrapper |
acq_backend_version | Echo backend version string |
acq_backend_doctor | Echo one matrix row for acq doctor output |
Contract addition (ADR-0012):
acq_backend_rotate_key— rotate the USAi API key using the backend's native secret mechanism and validate the new key. Added so key rotation stays backend-neutral (the core dispatch no longer shells out tosbxdirectly).
Capability flags (ACQ_BACKEND_SUPPORTS_PORT_FORWARD, etc.) allow common.sh
to gate features and future backends to declare gaps.
Backend resolution order
--backend <name>flagACQ_BACKENDenv varbackend:key in${XDG_CONFIG_HOME:-$HOME/.config}/acq/config.yaml- Auto-detect: first installed backend (
sbx version)
XDG config path correction
The design document (docs/explorations/acq-design.md) used ~/.acq/… as
the config path. This ADR corrects it to
${XDG_CONFIG_HOME:-$HOME/.config}/acq/config.yaml (XDG Base Directory
standard). Future state data lives at
${XDG_DATA_HOME:-$HOME/.local/share}/acq/. The design doc's own
secrets.age fallback already assumed $XDG_DATA_HOME, so this is a
correction, not a change.
Consequences
- Better: users have a stable, backend-agnostic CLI. Adding
msbin 1.2.0 is purely additive (one newacq.backends/msb.sh). - Tradeoff (temporary): two entry points exist during 1.1.x–1.x lifecycle. Mitigated by the deprecation notice and docs update.
- Compliance: no change to attack surface, no new external services, no data classification change (CM-2/CM-3/CM-6). The adapter seam is a structural improvement (SA-8/SA-15/SA-17).
- Testing: offline unit harness
scripts/test-acqcovers backend resolution, dispatch routing, secret command shapes, kit list completeness, and the qsbx deprecation notice.
Validation
bash -n acq acq.backends/*.sh scripts/test-acqclean../scripts/test-acqpasses (42 of 42 checks).npm run lint(markdownlint, shellcheck, gitleaks, YAML/JSON) clean.- Deferred, requires a sandbox-capable host on sbx 0.35.0+:
acq run opencode <path>end-to-end cannot run inside an sbx sandbox (no nested sandboxes). Document in the PR — mirror how ADR-0009 defers./scripts/verify-migrate-live.
Links
- Design:
docs/explorations/acq-design.md(long-form vision) - Contract: this ADR, §"Adapter contract" (authoritative)
- Deprecation timeline:
qsbxfrozen 1.1.0, removed 2.0.0 - Related: ADR-0009 (sbx version floor)