Skip to content

feat(ruflo): govern ruflo memory on the ruvector-postgres sidecar (ADR-2123) - #19

Merged
jjohare merged 6 commits into
mainfrom
ruflo-memory-governed
Oct 4, 2026
Merged

jjohare merged 6 commits into
mainfrom
ruflo-memory-governed

Conversation

@jjohare

@jjohare jjohare commented Oct 3, 2026

Copy link
Copy Markdown

Why

ruflo 3.51.1's memory subsystem is local-only (sql.js under .swarm/, an AgentDB mirror, the native engine's ./ruvector.db in the CWD, a MiniLM ONNX embedder). Its --backend flag is a label; no value reaches Postgres and no setting selects an embedding endpoint. Its MCP server given the sidecar's full RUVECTOR_PG_*/PG* env still searched an empty local store (live stdio test, 2026-10-03). Every ruflo invocation, --help included, autostarted a daemon into the CWD. The ruflo-console memory pane reaches memory only by shelling out to ruflo memory stats|list --format json, so with the stock binary it shows an empty local store. One memory init run left three local stores in a repository, one not gitignored.

What

The image's ruflo / claude-flow bins are governed wrappers (rufloGovernedPkg, ADR-2123):

  • ruflo memory … → mcp/servers/ruflo-memory-cli.cjs, reusing lib/memory-tools.js with the MCP server's pool, Xinference embedder (bge-small-en-v1.5, 384-d, GPU) and agentbox:<ns>:<key> entry ids. Serves store/retrieve/search/list/delete/stats from the sidecar in ruflo 3.51.1's --format json shapes, so the console's parsers are unchanged and its memory pane shows the governed corpus. init, configure, cleanup, compress, export, import, purge, distill, backup, classify, select-operator, migrate are refused (exit 2, no file). Fails closed without pg or an embedding (ADR-2014).
  • Everything else runs the real CLI with overridable defaults, also exported at boot: RUFLO_DAEMON_AUTOSTART=0, CLAUDE_FLOW_DISABLE_BRIDGE=1, CLAUDE_FLOW_MEMORY_PATH=~/.cache/ruflo/memory.
  • claude-flow-mcp (ADR-2082 proxy child) passes through unchanged. Gate unchanged: gate-off image byte-identical (ADR-2020).

Evidence

  • Live against the sidecar (213,332 rows, 464 namespaces, ruvector-postgres 0.3.0): stats and list in the ruflo shapes; HNSW search via hnsw-xinference (0.795 / 0.776); store → search (0.769) → retrieve → delete round trip in cli-probe; refusals exit 2, tree clean.
  • Simulated wrapper against the real binary in an empty directory: no daemon, no files; swarm status works; memory stats routed.
  • tests/contract/ruflo-memory-cli.contract.spec.js: 23 stub-backed cases. tests/config/ruflo-memory-governed.test.sh: static wrapper/gate/env/refusal checks plus a live check inside the image.
  • Nix is not evaluable in the authoring container; flake-check here is the first real evaluation. The baked wrapper is verified at the host rebuild.

ADR ledger

invariants is red on main for its last three runs: 19 records went stale on flake.nix / agentbox.toml from the sidechain, poker and console commits after the pins' last re-verification (09e6271e9). A follow-up commit on this branch re-verifies them and sets ADR-2123's verified_commit.

🤖 Generated by Claude Code

Claude Code and others added 6 commits October 3, 2026 19:48
…ADR-2123)

ruflo 3.51.1's memory subsystem is local-only: sql.js under .swarm/, an
AgentDB mirror, the native engine's ./ruvector.db in the CWD and a MiniLM
ONNX embedder. Its --backend flag is a label written into a metadata table;
no value reaches Postgres and no setting selects an embedding endpoint. Its
MCP server given the sidecar's full RUVECTOR_PG_*/PG* env still searched an
empty local store (tested 2026-10-03). Every `ruflo` call, --help included,
autostarted a daemon into the CWD. The ruflo-console memory pane reaches
memory only via `ruflo memory stats|list --format json`, so with the stock
binary it shows an empty local store, never the governed corpus.

The image's `ruflo`/`claude-flow` bins are now governed wrappers
(rufloGovernedPkg):
- `ruflo memory …` execs mcp/servers/ruflo-memory-cli.cjs, which reuses
  lib/memory-tools.js with the MCP server's pool, Xinference embedder and
  `agentbox:<ns>:<key>` entry ids, serves store/retrieve/search/list/delete/
  stats from the sidecar, and emits ruflo 3.51.1's --format json shapes so
  the console's parsers are unchanged. init/configure/cleanup/compress/
  export/import/purge/distill/backup/classify/select-operator/migrate are
  refused (exit 2, no file). Fails closed without pg or an embedding.
- every other subcommand runs the real CLI with overridable defaults, also
  exported at boot: RUFLO_DAEMON_AUTOSTART=0, CLAUDE_FLOW_DISABLE_BRIDGE=1,
  CLAUDE_FLOW_MEMORY_PATH=~/.cache/ruflo/memory.
- claude-flow-mcp (the ADR-2082 proxy child) passes through unchanged; the
  gate is unchanged, so a gate-off image is byte-identical (ADR-2020).

Verified live against the sidecar (213,332 rows, 464 namespaces): stats and
list in the ruflo shapes, HNSW search via hnsw-xinference (0.795/0.776), a
store→search→retrieve→delete round trip in `cli-probe`, refusals exit 2 with
a clean tree. The simulated wrapper run against the real binary in an empty
directory: no daemon, no files; `swarm status` works; `memory stats` routed.
tests/contract/ruflo-memory-cli.contract.spec.js (23 stub-backed cases) and
tests/config/ruflo-memory-governed.test.sh pass.

Co-Authored-By: jjohare <github@thedreamlab.uk>
…(ADR-2123)

RUFLO_DAEMON_AUTOSTART, CLAUDE_FLOW_DISABLE_BRIDGE and CLAUDE_FLOW_MEMORY_PATH
are switches and a path, exported at boot and defaulted by the ruflo wrapper;
env-secret-inventory requires every name the boot path reads to be classed.

Co-Authored-By: jjohare <github@thedreamlab.uk>
1fc26c7); ADR-2123 verified

Co-Authored-By: jjohare <github@thedreamlab.uk>
… as a root-side literal (RC-X1-01)

The runtime-env export defaulted the path to a literal /home/devuser, which
the runtime contract forbids for any PATH-named assignment executed by
root. Escaped, it resolves to the sourcing user's $HOME/.cache/ruflo/memory.

Co-Authored-By: jjohare <github@thedreamlab.uk>
…e-affirm ADR-2014

Grounded in ruvnet-kb: ruvector-core is one library in two products (embedded
engine, Postgres extension) sharing algorithms, not storage. ruflo's memory is
local-first by design (claude-flow ADR-009/017/057/342); Postgres was deferred
and later scoped as a bridge (ruflo ADR-027, Proposed). The extension is current
(v2.0.6, ruvector ADR-044 v0.3 in progress); this image runs 2.0.5 / ext 0.3.0.
Rule added: same vendor and dimension do not mean same wire; prove it live.

Co-Authored-By: jjohare <github@thedreamlab.uk>
@jjohare
jjohare merged commit 25b4c7f into main Oct 4, 2026
10 of 13 checks passed
jjohare added a commit that referenced this pull request Oct 5, 2026
Co-Authored-By: jjohare <github@thedreamlab.uk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant