checkpoint: matrix-agent-validated (2026-04-25)

Architectural snapshot of the lakehouse codebase at the point where the
full matrix-driven agent loop with Mem0 versioning + deletion was
validated end-to-end.

WHAT THIS REPO IS
A clean single-commit snapshot of the lakehouse code. Heavy test data
(.parquet datasets, vector indexes) excluded — see REPLICATION.md for
regen path. Full lakehouse history at git.agentview.dev/profit/lakehouse.

WHAT WAS PROVEN
- Vector retrieval across multi-corpora matrix (chicago_permits + entity
  briefs + sec_tickers + distilled procedural + llm_team runs)
- Observer hand-review (cloud + heuristic fallback) gating each candidate
- Local-model agent loop (qwen3.5:latest) with tool use + scratchpad
- Playbook seal on success → next-iter retrieval surfaces it as preamble
- Mem0 versioning + deletion in pathway_memory:
    * UPSERT: ADD on new workflow, UPDATE bumps replay_count on identical
    * REVISE: chains versions, parent.superseded_at + superseded_by stamped
    * RETIRE: marks specific trace retired with reason, excluded from retrieval
    * HISTORY: walks chain root→tip, cycle-safe

KEY DIRECTORIES
- crates/vectord/src/pathway_memory.rs — Mem0 ops live here
- crates/vectord/src/playbook_memory.rs — original Mem0 reference
- tests/agent_test/ — local-model agent harness + PRD + session archives
- scripts/dump_raw_corpus.sh — MinIO bucket dump (raw test corpus)
- scripts/vectorize_raw_corpus.ts — corpus → vector indexes
- scripts/analyze_chicago_contracts.ts — real inference pipeline
- scripts/seal_agent_playbook.ts — Mem0 upsert from agent traces

Replication: see REPLICATION.md for Debian 13 clean install + cloud-only
adaptation (no local Ollama).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
profit
2026-04-25 19:43:27 -05:00
co-authored by Claude Opus 4.7
commit ac01fffd9a
325 changed files with 82720 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
FROM python:3.13-slim
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir .
COPY sidecar/ sidecar/
ENV OLLAMA_URL=http://host.docker.internal:11434
ENV EMBED_MODEL=nomic-embed-text
ENV GEN_MODEL=qwen2.5
ENV RERANK_MODEL=qwen2.5
EXPOSE 3200
CMD ["uvicorn", "sidecar.main:app", "--host", "0.0.0.0", "--port", "3200"]
+14
View File
@@ -0,0 +1,14 @@
[project]
name = "lakehouse-sidecar"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.115",
"uvicorn>=0.34",
"httpx>=0.28",
"pydantic>=2.0",
]
[tool.uvicorn]
host = "0.0.0.0"
port = 3200
View File
+137
View File
@@ -0,0 +1,137 @@
"""Admin endpoints: model lifecycle + VRAM introspection.
Phase 17 / Phase C — the VRAM-aware profile swap story. Ollama loads
models lazily and unloads after a TTL (default 5min). For predictable
swaps between model profiles, we need explicit control:
- unload: send keep_alive=0 to force immediate unload
- preload: send keep_alive=5m with empty prompt to proactively load
- ps: query Ollama's /api/ps to see what's currently loaded
All three are thin wrappers over Ollama's own API. No state held here.
"""
import asyncio
import shutil
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from .ollama import client
router = APIRouter()
class ModelRequest(BaseModel):
model: str
@router.post("/unload")
async def unload(req: ModelRequest):
"""Force Ollama to unload a model immediately (keep_alive=0 trick)."""
async with client() as c:
resp = await c.post(
"/api/generate",
json={"model": req.model, "prompt": "", "keep_alive": 0, "stream": False},
)
# Ollama returns 200 even on unload; anything else is abnormal.
if resp.status_code not in (200,):
raise HTTPException(502, f"Ollama unload error: {resp.text}")
return {"unloaded": req.model}
@router.post("/preload")
async def preload(req: ModelRequest):
"""Force Ollama to load a model into VRAM and keep it there."""
async with client() as c:
resp = await c.post(
"/api/generate",
json={
"model": req.model,
# "" can confuse some models; a single token is safer and still ~instant.
"prompt": " ",
"keep_alive": "5m",
"stream": False,
"options": {"num_predict": 1},
},
)
if resp.status_code != 200:
raise HTTPException(502, f"Ollama preload error: {resp.text}")
data = resp.json()
return {
"preloaded": req.model,
"load_duration_ns": data.get("load_duration"),
"total_duration_ns": data.get("total_duration"),
}
@router.get("/ps")
async def list_loaded():
"""What models does Ollama currently have in VRAM?"""
async with client() as c:
resp = await c.get("/api/ps")
if resp.status_code != 200:
raise HTTPException(502, f"Ollama ps error: {resp.text}")
data = resp.json()
# Flatten Ollama's fields into something small + useful.
models = [
{
"name": m.get("name"),
"size_bytes": m.get("size"),
"size_vram_bytes": m.get("size_vram"),
"expires_at": m.get("expires_at"),
}
for m in data.get("models", [])
]
return {"models": models}
@router.get("/vram")
async def vram_summary():
"""Combined: nvidia-smi VRAM + Ollama loaded models.
Shells out to nvidia-smi; if it's not on PATH, returns just the
Ollama view. Intentionally async-via-to_thread so the blocking
subprocess doesn't stall the event loop.
"""
gpu = None
if shutil.which("nvidia-smi"):
gpu = await asyncio.to_thread(_nvidia_smi_snapshot)
async with client() as c:
resp = await c.get("/api/ps")
loaded = resp.json().get("models", []) if resp.status_code == 200 else []
return {
"gpu": gpu,
"ollama_loaded": [
{
"name": m.get("name"),
"size_vram_mib": (m.get("size_vram", 0) or 0) // (1024 * 1024),
"expires_at": m.get("expires_at"),
}
for m in loaded
],
}
def _nvidia_smi_snapshot():
"""One-shot nvidia-smi poll. Returns None on failure."""
import subprocess
try:
out = subprocess.check_output(
[
"nvidia-smi",
"--query-gpu=memory.used,memory.total,utilization.gpu,name",
"--format=csv,noheader,nounits",
],
timeout=2,
).decode().strip()
used_mib, total_mib, util_pct, name = [s.strip() for s in out.split(",")]
return {
"name": name,
"used_mib": int(used_mib),
"total_mib": int(total_mib),
"utilization_pct": int(util_pct),
}
except Exception:
return None
+44
View File
@@ -0,0 +1,44 @@
import os
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from .ollama import client
router = APIRouter()
EMBED_MODEL = os.environ.get("EMBED_MODEL", "nomic-embed-text")
class EmbedRequest(BaseModel):
texts: list[str]
model: str | None = None
class EmbedResponse(BaseModel):
embeddings: list[list[float]]
model: str
dimensions: int
@router.post("", response_model=EmbedResponse)
async def embed(req: EmbedRequest):
model = req.model or EMBED_MODEL
embeddings = []
async with client() as c:
for text in req.texts:
resp = await c.post("/api/embed", json={"model": model, "input": text})
if resp.status_code != 200:
raise HTTPException(502, f"Ollama error: {resp.text}")
data = resp.json()
embeddings.extend(data.get("embeddings", []))
if not embeddings:
raise HTTPException(502, "No embeddings returned")
return EmbedResponse(
embeddings=embeddings,
model=model,
dimensions=len(embeddings[0]),
)
+61
View File
@@ -0,0 +1,61 @@
import os
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from .ollama import client
router = APIRouter()
GEN_MODEL = os.environ.get("GEN_MODEL", "qwen2.5")
class GenerateRequest(BaseModel):
prompt: str
model: str | None = None
system: str | None = None
temperature: float = 0.7
max_tokens: int = 2048
# think=false disables hidden reasoning blocks on thinking models
# (qwen3, qwen3.5, gpt-oss). Required for hot-path JSON emitters
# that need the whole token budget for the visible response.
think: bool | None = None
class GenerateResponse(BaseModel):
text: str
model: str
tokens_evaluated: int | None = None
tokens_generated: int | None = None
@router.post("", response_model=GenerateResponse)
async def generate(req: GenerateRequest):
model = req.model or GEN_MODEL
payload = {
"model": model,
"prompt": req.prompt,
"stream": False,
"options": {
"temperature": req.temperature,
"num_predict": req.max_tokens,
},
}
if req.system:
payload["system"] = req.system
if req.think is not None:
payload["think"] = req.think
async with client() as c:
resp = await c.post("/api/generate", json=payload)
if resp.status_code != 200:
raise HTTPException(502, f"Ollama error: {resp.text}")
data = resp.json()
return GenerateResponse(
text=data.get("response", ""),
model=model,
tokens_evaluated=data.get("prompt_eval_count"),
tokens_generated=data.get("eval_count"),
)
+25
View File
@@ -0,0 +1,25 @@
import os
from fastapi import FastAPI
from .admin import router as admin_router
from .embed import router as embed_router
from .generate import router as generate_router
from .rerank import router as rerank_router
app = FastAPI(title="Lakehouse AI Sidecar")
app.include_router(embed_router, prefix="/embed", tags=["embed"])
app.include_router(generate_router, prefix="/generate", tags=["generate"])
app.include_router(rerank_router, prefix="/rerank", tags=["rerank"])
app.include_router(admin_router, prefix="/admin", tags=["admin"])
@app.get("/health")
async def health():
return {
"status": "ok",
"ollama_url": os.environ.get("OLLAMA_URL", "http://localhost:11434"),
"embed_model": os.environ.get("EMBED_MODEL", "nomic-embed-text"),
"gen_model": os.environ.get("GEN_MODEL", "qwen2.5"),
}
+12
View File
@@ -0,0 +1,12 @@
"""Shared Ollama HTTP client."""
import os
import httpx
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
TIMEOUT = float(os.environ.get("OLLAMA_TIMEOUT", "120"))
def client() -> httpx.AsyncClient:
return httpx.AsyncClient(base_url=OLLAMA_URL, timeout=TIMEOUT)
+70
View File
@@ -0,0 +1,70 @@
import os
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from .ollama import client
router = APIRouter()
RERANK_MODEL = os.environ.get("RERANK_MODEL", "qwen2.5")
class RerankRequest(BaseModel):
query: str
documents: list[str]
model: str | None = None
top_k: int | None = None
class ScoredDocument(BaseModel):
index: int
text: str
score: float
class RerankResponse(BaseModel):
results: list[ScoredDocument]
model: str
@router.post("", response_model=RerankResponse)
async def rerank(req: RerankRequest):
"""Cross-encoder reranking via Ollama generate.
Scores each document against the query by asking the model to rate relevance 0-10,
then sorts by score descending.
"""
model = req.model or RERANK_MODEL
scored = []
async with client() as c:
for i, doc in enumerate(req.documents):
prompt = (
f"Rate the relevance of the following document to the query on a scale of 0 to 10. "
f"Respond with ONLY a number.\n\n"
f"Query: {req.query}\n\n"
f"Document: {doc}\n\n"
f"Score:"
)
resp = await c.post(
"/api/generate",
json={"model": model, "prompt": prompt, "stream": False, "options": {"temperature": 0.0, "num_predict": 8}},
)
if resp.status_code != 200:
raise HTTPException(502, f"Ollama error: {resp.text}")
text = resp.json().get("response", "").strip()
try:
score = float(text.split()[0])
score = max(0.0, min(10.0, score))
except (ValueError, IndexError):
score = 0.0
scored.append(ScoredDocument(index=i, text=doc, score=score))
scored.sort(key=lambda x: x.score, reverse=True)
if req.top_k:
scored = scored[: req.top_k]
return RerankResponse(results=scored, model=model)