Files
cubelinux/INTEGRATION-cube-agent.md
T
luulu ad648b4ea6 finish: README, the kernel decision, and the spec reconciled
Part one of the two-part plan, closing out the userspace substrate before the
kernel work:

- PLAN-kernel-cubelinux.md: the decision (the kernel owns the store; coordinates
  are resolved in the kernel; the block driver is persistence plumbing; POSIX is
  an edge view) plus the two-part plan and the one design fork left for the
  write-authority phase.
- README.md: what CUBE-OS is, the crate map, quickstart, the release gates, and
  the "claims not yet true" list (kernel not coordinate-native yet; installed
  kernel is CUBED's).
- Superseded banners on the pre-split docs (PLAN-os-layer.md,
  INTEGRATION-cube-agent.md, PLAN-debugging.md, cube_harness.py) and the fixed
  stale rows (DESIGN-coordinate-layer.md, a cube-header comment).
- cube_ctl.py: the `spawn` subcommand and CubeSpawner, which shelled the deleted
  cube-spawn crate, now refuse with a clear message instead of pretending.

Release checklist green: 157 tests, 0 clippy warnings, image verifies (snapshot
8,399,506 B; live 16,537,516 B), cube-core cross-builds for thumbv7em-none-eabihf,
durability harness PASS, and the name-tier path end to end. Tagged cubelinux-0.1.0.
2026-09-18 19:25:30 -04:00

8.7 KiB

CUBE <-> Local LLM Agent Integration Contract

Superseded. This document predates the two-product split. The path/POSIX/name tier it describes left CUBE OS and belongs to CUBED (/home/CUBEdb); CUBE OS is the coordinate substrate only. See PLAN-cube-os-and-cubed.md and README.md for what is true now.

Status: 2026-08-09 (rev 2026-08-10: +cube-index crate, mindex-* CLI, AdjacencyIndex fast-path; +cube-api crate, native one-type binding surface; +cube-daemon crate (cubed), Unix-socket service over cube-api — the agent's native endgame). Owner: CUBELinux build. Enforced by code, not trust.

1. The rule (read this first)

CUBE (the Rust crates: cube-core / cube-store / cube-header / cube-index / cube-cli / cube-api / cube-daemon) is canonical. The local LLM agent (cagent.py, run as luulu, talking to llama.cpp at 127.0.0.1:8080) adapts to CUBE. We do NOT bend the crates to match the agent's old file-store. This was the deliberate decision: the design doc's model (memory = coordinate-addressed, recall via EDG graph walk) lives in the crates; the agent consumes it.

2. The stable boundary (this is what makes future churn cheap)

There is exactly ONE adapter between agent and store:

cagent.py  --calls-->  cube_bridge.py  --shells out to-->  cubecli  --uses-->  FileBackedStore / Morton / HeaderStore

Responsibilities, hard-bounded:

  • cube-core (Rust): owns ALL coordinate math. SpaceId::from_name, Point::from_name, Coord::named(ns, name). Single source of truth. The namespace+name -> coordinate mapping exists ONLY here, so Python and Rust can never disagree.
  • cube-cli (Rust): the m* logical commands (mput/mget/mdel/mlist/mlink/ mout/mfollow) are the machine API. --json gives parseable output. The new mindex-* commands (mindex-build / mindex-out / mindex-follow) expose the cube-index adjacency index: mindex-out lists a record's out-edges (with payload text, O(deg)), mindex-follow walks edge index straight to the target payload. These read the raw FileBackedStore via HeaderStore::inner() and build an AdjacencyIndex in-memory — additive, no change to m*.
  • cube-index (Rust): the association-index crate. AdjacencyIndex (reserved space 0xAA55AA55..AA55AA58) materializes the EDG out/in adjacency from the store into O(deg) outgoing/incoming/follow lookups (vs the legacy O(scan) HeaderStore::outgoing). PortalCatalogue (reserved space 0x5072...) maps portal short-codes to target coordinates for cross-space teleport. Both are pure readers over the store — no write path lives here.
  • cube-api (Rust): the surface a program actually calls. A single owned type CubeApi<T: Store, C: Curve> that composes the lower crates into one import: read/write/list/delete (data plane, via HeaderStore), link + follow (association plane, walking AdjacencyIndex), header, and adjacency. This is the deliberate successor to shelling out to cubecli — same canonical semantics (coordinate-addressed, recall via EDG graph walk), but a Rust type with no subprocess and no string parsing. The local-LLM agent currently binds through cube_bridge.py -> cubecli; cube-api is the native target for a future in-process daemon (or for cube_bridge.py to link against instead of spawning a subprocess). follow always uses the AdjacencyIndex fast-path; list keys are coordinate display strings (<space_hex>:x,y,z) because names are non-reversible FNV fingerprints.
  • cube-daemon (Rust, binary cubed): the long-running service that serves a CubeApi over a Unix socket using a newline-delimited JSON protocol (one JSON object per line; binary values base64; coords as <64-hex-space>:x,y,z). This is the agent's native endgame: instead of cube_bridge.py shelling out to cubecli per op (subprocess + string parsing), the agent opens ONE socket to cubed and speaks JSON. Request handling is serialized through one mutex so concurrent connections never race the store or the cached AdjacencyIndex. cubed defaults: --store /home/luulu/.cubelinux-agent/cube.store, --socket $XDG_RUNTIME_DIR/cubelinux/cubed.sock (the $XDG_RUNTIME_DIR default means a luulu user service can create the socket without root). The daemon's list/read/follow/adjacency responses all emit the full 64-hex space form for consistency. The crate also ships cubecli_socket — a thin reference client (CLI + smoke test) that is the verified native replacement for the cubecli shell-out, plus a cubed.service systemd --user unit and a crates/cube-daemon/README.md deployment guide.
  • cube_bridge.py (Python, thin): the ONLY agent-side touchpoint. Speaks cube_write/read/list/link/follow (namespace, name). Never computes a coordinate. Honors WALL 2 (guard.py can/tag on a shadow metatag) and falls back to the legacy file store on read during the transition.
  • cagent.py (Python): the model-facing tools cube_write/cube_read/ cube_list delegate to cube_bridge. Their signature/semantics to the model are frozen; the model never sees a coordinate.
  • cube-agent (Rust, binary cube-agent): the in-process local agent that runs a task end-to-end against cubed (via cube-api) + llama-server (127.0.0.1:8080). cube-agent run <task> executes ONE task and exits. It is launched as a systemd --user template unit cube-agent@.service (Type=oneshot, Environment=XDG_RUNTIME_DIR=/run/user/1000, After= cubed.service llama-server.service, TimeoutStartSec=600). Templates are invoked on demand, not enabled. Launch: T=$(systemd-escape --template=cube-agent@.service "<task>"); systemctl --user start "$T"; read the answer via journalctl --user -u "$T" --no-pager. Unit file: /home/luulu/.config/systemd/user/cube-agent@.service (luulu:cubelinux, 640) — intentionally NOT in the repo (matches cubed.service / cube-stats.service). Survives reboot via loginctl enable-linger luulu.

3. Coordinate mapping (reference)

namespace "memory"  -> SpaceId = SpaceId::from_name("memory")   (FNV-1a, 256-bit)
name     "foo"      -> Point   = Point::from_name("foo")        (FNV-1a -> 3 lanes x/y/z)
record coord        = Coord { space, point }  ==  "a913:<x>,<y>,<z>"

To change the mapping, edit cube-core only. Nothing else needs to move.

4. Associations (the design doc's EDG path)

cube_link(ns, name, target_ns, target_name) creates an EDG edge between the two coordinates; cube_follow(ns, name, index) walks edge index to the target payload. Recall is therefore a graph walk, not a directory listing — exactly as the design intended. Curve choice is independent of edge semantics.

Index acceleration (new): cube_follow now prefers the AdjacencyIndex fast path (mindex-follow, O(deg) walk + one point lookup) and falls back to the legacy HeaderStore::outgoing scan (mfollow) only if the index returns nothing. The model-facing cube_follow(ns, name, index) signature and semantics are unchanged — the bridge is the only thing that knows which path ran. The same O(deg) walk is what bench_edge_walk_store (cube-bench workload 4b) measures: ~650k edges/ms on a real FileBackedStore at 500 nodes / 8 edges / 20k hops.

5. Forward-change policy (the "constant adjustment" guardrail)

If a future change touches EITHER side, it stays contained:

  • Store format / curve / header layout changes -> edit the crates. Rebuild cubecli. cube_bridge and cagent are untouched (they only speak namespace+ name). Existing store file is versioned (magic CUBE + version byte) so a reopen migrates/refuses cleanly.
  • Agent tool API changes -> edit cagent's cube_* signatures only; keep delegating to cube_bridge. Do not let the agent compute coordinates.
  • New consumer (robotics client, etc.) -> give it Coord::named (or call m* over a socket). It inherits the same mapping for free.
  • Guard policy changes -> edit guard.py; cube_bridge consults it via the existing can/tag interface. Do not inline policy in cagent.

Never "fix" a mismatch by moving coordinate math into Python. If you are tempted, the right move is to add the primitive to cube-core and re-expose it via m*.

6. Verification

  • Rust: cargo test --workspace + cargo clippy --workspace --all-targets -- -D warnings (green).
  • Index: cargo run -p cube-cli -- mindex-out memory src --json (lists out-edges with payloads); mindex-follow memory src 0 (walks straight to target payload).
  • Bridge: python3 cube_bridge.py selftest (as luulu).
  • Agent path: cube_write -> cube_read -> cube_link -> cube_follow -> cube_list, plus legacy file fallback, all confirmed as luulu.

Store location: /home/luulu/.cubelinux-agent/cube.store (luulu:cubelinux, rw).