feat(cli,backend): auto-detect nix/docker when backend is omitted#8
Merged
Conversation
ikeikeikeike
added a commit
that referenced
this pull request
Jun 17, 2026
CI on main has been red since v0.2.1 because the Λ-5a Docker backend
landed three classes of lint debt that local `make format-changed`
never caught (errcheck excludes ${io.Closer} family but not the moby
SDK's *client.Client.Close, and unused helpers slipped past
`go vet`):
- defer cli.Close() / defer rc.Close() in 4 plugins: wrap each in a
`_ = ...Close()` thunk so errcheck stops complaining without losing
the cleanup semantics
- removeIfExists in 4 plugin docker.go files: dead code since the
Λ-5d hardening replaced it with the upOrReuse / start-fail-cleanup
pair, but the function definitions stayed behind
- (*Provider).dockerCleanup in 4 plugin docker.go files: dead code
because each plugin's public Cleanup() goes through the nix code
path's os.RemoveAll directly; the docker variant was never wired
`os` import auto-pruned by goimports in mysql / postgres / redis
docker.go after dockerCleanup removal (elasticsearch keeps os for
its MkdirAll + chown helper).
Local verification:
golangci-lint run ./... 0 issues
go test ./... all packages pass
Unblocks PR #8 (Λ-5b auto-detect) and PR #9 (Λ-5c dockerutil pkg)
both of which inherited these warnings from main.
… empty
bough has shipped a hybrid backend selector (nix vs docker) since
v0.2-alpha, but the YAML required an explicit `backend:` per engine —
operators upgrading from v0.1.x kept hitting the nix path even on
docker-only hosts because the host would inject an empty string into
Extras and the plugin would silently fall through to its nix code path
and explode at `nix run` time with an unhelpful error.
This change wires a new internal/backend package whose `Detect()` runs
once per `bough create` invocation:
1. `nix` on PATH AND `nix-command` + `flakes` enabled in active nix
config (via `nix config show experimental-features`) → "nix"
2. `docker info` exits 0 → "docker"
3. neither → error naming the YAML knob the operator can use to
bypass detection
Detection is cached across the dbCfg loop so a 4-database YAML still
costs at most one probe. Explicit `backend: nix` / `backend: docker`
in YAML short-circuits detection entirely — operator intent always
wins. The contract with the plugin layer (`Extras["backend"] ==
"docker"`) is unchanged, so the four DB plugins need no companion edit.
Roadmap row for v0.2.0 reworded to reflect that auto-detect is now the
default when `backend:` is omitted, and the inline YAML comment in the
quick-start example matches.
Tests cover the contract (returns nix|docker or ErrNoBackend), the
tie-break (nix wins when both are reachable), and the LookPath
fast-path. Host-dependent tests skip cleanly on CI hosts that lack one
or both backends.
ikeikeikeike
force-pushed
the
feat/L5b-auto-detect-backend
branch
from
June 17, 2026 01:22
93c168b to
2ad4966
Compare
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
… caps gating + mock hard-delete Review #23 #8 surfaced a contract divergence: the mem0 plugin advertised Capabilities.SoftDelete=true while real mem0 cloud's DELETE /api/v1/memories/<id>/ hard-deletes the row. The in-tree conformance suite shipped CI-green because the mock_mem0 server faked soft-delete in the wire layer (metadata.bough_state="forgotten" gated by mockStore.search), masking the production divergence. Make the mem0 plugin honest, then make the conformance suite caps- aware so a backend that says SoftDelete=false still passes the suite without faking it. plugins/memory/mem0/mem0.go Capabilities.SoftDelete: true → false, with an updated docstring explaining the hard-delete semantics and pointing at the conformance gate. plugins/memory/mem0/CONTRACT.md `soft_delete` row: true → false, with the reason ("mem0 cloud hard-deletes on DELETE…; the conformance suite gates its Export- after-Forget assertion on this flag"). conformance/memory/lifecycle.go - Store#2 WasUpsert assertion gated on caps.DedupeKey (mem0 honestly advertises DedupeKey=false: no native dedupe primitive). - Reorder so Export → Import round-trip runs BEFORE Forget, so every backend exercises the round-trip regardless of soft-delete semantics. - Forget runs on every backend; the post-Forget Query asserts the row stops being queryable (common contract). - The post-Forget Export-still-returns-the-row assertion is gated on caps.SoftDelete so honest hard-deleting backends pass. conformance/memory/mock_mem0/main.go - softDelete → hardDelete: delete(s.rows, id) directly, mirroring real mem0 cloud's DELETE semantic. - search/list bough_state filter removed: nothing soft-deletes through this path any more. - Header comment expanded to record the divergence #8 surfaced and why the mock now matches production. Verified locally: go test ./conformance/memory/... → TestSelf + TestMem0_Conformance pass go test -tags=conformance \ BOUGH_CONFORMANCE_MEMORY_PLUGIN_BIN=dist/bough-plugin-memory-sqlite \ ./plugins/memory/sqlite/... → pass (caps.SoftDelete=true path)
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
… caps gating + mock hard-delete Review #23 #8 surfaced a contract divergence: the mem0 plugin advertised Capabilities.SoftDelete=true while real mem0 cloud's DELETE /api/v1/memories/<id>/ hard-deletes the row. The in-tree conformance suite shipped CI-green because the mock_mem0 server faked soft-delete in the wire layer (metadata.bough_state="forgotten" gated by mockStore.search), masking the production divergence. Make the mem0 plugin honest, then make the conformance suite caps- aware so a backend that says SoftDelete=false still passes the suite without faking it. plugins/memory/mem0/mem0.go Capabilities.SoftDelete: true → false, with an updated docstring explaining the hard-delete semantics and pointing at the conformance gate. plugins/memory/mem0/CONTRACT.md `soft_delete` row: true → false, with the reason ("mem0 cloud hard-deletes on DELETE…; the conformance suite gates its Export- after-Forget assertion on this flag"). conformance/memory/lifecycle.go - Store#2 WasUpsert assertion gated on caps.DedupeKey (mem0 honestly advertises DedupeKey=false: no native dedupe primitive). - Reorder so Export → Import round-trip runs BEFORE Forget, so every backend exercises the round-trip regardless of soft-delete semantics. - Forget runs on every backend; the post-Forget Query asserts the row stops being queryable (common contract). - The post-Forget Export-still-returns-the-row assertion is gated on caps.SoftDelete so honest hard-deleting backends pass. conformance/memory/mock_mem0/main.go - softDelete → hardDelete: delete(s.rows, id) directly, mirroring real mem0 cloud's DELETE semantic. - search/list bough_state filter removed: nothing soft-deletes through this path any more. - Header comment expanded to record the divergence #8 surfaced and why the mock now matches production. Verified locally: go test ./conformance/memory/... → TestSelf + TestMem0_Conformance pass go test -tags=conformance \ BOUGH_CONFORMANCE_MEMORY_PLUGIN_BIN=dist/bough-plugin-memory-sqlite \ ./plugins/memory/sqlite/... → pass (caps.SoftDelete=true path)
ikeikeikeike
added a commit
that referenced
this pull request
Jul 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
internal/backend.Detect()— probesnix(withnix-command+flakesenabled) first, thendocker info, else returnsErrNoBackendwith a message naming the YAML knob to bypass.bough create: runs at most once per invocation, only when at least onedatabases[].backendis empty. Result is injected viaextras["backend"], preserving the plugin contract unchanged.backend: nix/backend: dockerstill short-circuits detection — operator intent wins.Test plan
nix develop .#ci -c go build ./...nix develop .#ci -c go test ./...(incl. 4 new tests ininternal/backend)nix develop .#ci -c golangci-lint run ./internal/backend/... ./internal/cli/...(0 issues in touched scope; pre-existing lint debt inplugins/db/*/docker.gobelongs to Λ-5c)backend:from a real.worktree-isolation.yaml, runbough create, observe[bough] backend: auto-detected nix(or docker) on stderr and the plugin Up taking the right code path