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.
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. SeePLAN-cube-os-and-cubed.mdandREADME.mdfor 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.--jsongives parseable output. The newmindex-*commands (mindex-build / mindex-out / mindex-follow) expose thecube-indexadjacency index:mindex-outlists a record's out-edges (with payload text, O(deg)),mindex-followwalks edgeindexstraight to the target payload. These read the rawFileBackedStoreviaHeaderStore::inner()and build anAdjacencyIndexin-memory — additive, no change tom*. - cube-index (Rust): the association-index crate.
AdjacencyIndex(reserved space0xAA55AA55..AA55AA58) materializes the EDG out/in adjacency from the store into O(deg)outgoing/incoming/followlookups (vs the legacy O(scan)HeaderStore::outgoing).PortalCatalogue(reserved space0x5072...) 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, viaHeaderStore),link+follow(association plane, walkingAdjacencyIndex),header, andadjacency. This is the deliberate successor to shelling out tocubecli— 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 throughcube_bridge.py->cubecli;cube-apiis the native target for a future in-process daemon (or forcube_bridge.pyto link against instead of spawning a subprocess).followalways uses theAdjacencyIndexfast-path;listkeys 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 aCubeApiover 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 ofcube_bridge.pyshelling out tocubecliper op (subprocess + string parsing), the agent opens ONE socket tocubedand speaks JSON. Request handling is serialized through one mutex so concurrent connections never race the store or the cachedAdjacencyIndex.cubeddefaults:--store /home/luulu/.cubelinux-agent/cube.store,--socket $XDG_RUNTIME_DIR/cubelinux/cubed.sock(the$XDG_RUNTIME_DIRdefault means aluuluuser service can create the socket without root). The daemon'slist/read/follow/adjacencyresponses all emit the full 64-hex space form for consistency. The crate also shipscubecli_socket— a thin reference client (CLI + smoke test) that is the verified native replacement for thecubeclishell-out, plus acubed.servicesystemd --user unit and acrates/cube-daemon/README.mddeployment 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_listdelegate 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 againstcubed(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 unitcube-agent@.service(Type=oneshot,Environment=XDG_RUNTIME_DIR=/run/user/1000,After= cubed.service llama-server.service,TimeoutStartSec=600). Templates are invoked on demand, notenabled. Launch:T=$(systemd-escape --template=cube-agent@.service "<task>"); systemctl --user start "$T"; read the answer viajournalctl --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 vialoginctl 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 (magicCUBE+ 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 callm*over a socket). It inherits the same mapping for free. - Guard policy changes -> edit guard.py; cube_bridge consults it via the
existing
can/taginterface. 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).