root 511083ae40 docs: SPEC §3.9 (chatd) + §3.10 (local-review-harness sibling)
- SPEC §1 component table: add chatd row marked DONE; replaces
  Rust gateway's v1::ollama_cloud / openrouter / opencode adapters
  + the aibridge crate.
- SPEC §3.9 — chatd shipped: 5-provider routing (ollama, ollama_cloud,
  openrouter, opencode, kimi) by model-name prefix or :cloud suffix.
  Captures the Anthropic 4.7 temperature-deprecation quirk + the
  local-Ollama think=false default that the playbook_lift judge
  needed. Mentions scrum_review.sh as the reusable cross-lineage
  vehicle eating chatd's own /v1/chat.
- SPEC §3.10 — local-review-harness sibling tool: separate repo at
  git.agentview.dev/profit/local-review-harness, MVP shipped today.
  Documents the cross-pollination plan for when both substrates
  stabilize (chatd as the harness's LLM backend; harness findings
  as Lakehouse pathway-memory drift signal; .memory/known-risks
  as a matrix corpus). Explicit "don't re-port" so future Claudes
  don't try to absorb the harness into Lakehouse.
- STATE_OF_PLAY.md: SIBLING TOOLS section with 1-line summary
  + pointer to SPEC §3.10.

No code changes. just verify still PASS — touched only docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:01:23 -05:00

34 KiB
Raw Permalink Blame History

SPEC: Lakehouse-Go Component Port Plan

Status: DRAFT — companion to PRD.md. Component-by-component port plan with library choices, effort estimates, and acceptance gates. Created: 2026-04-28 Owner: J

This spec answers: for each piece of the Rust Lakehouse, what Go library carries it, what the effort looks like, and what gate proves the port is real.

Effort scale (one engineer-week = ~40h focused work):

  • S — 13 days
  • M — 1 engineer-week
  • L — 23 engineer-weeks
  • XL — 1+ months
  • HARD — open research, see PRD §Hard problems

§1. Component port table — Rust crates

Crate Rust deps that mattered Go target Library Effort Risk
gateway axum, tokio, tonic, tower cmd/gateway chi + stdlib net/http + google.golang.org/grpc L low — Go's strongest domain
catalogd parquet-rs, arrow, sqlite cmd/catalogd apache/arrow-go/v18, mattn/go-sqlite3 L low
storaged object_store, aws-sdk cmd/storaged aws-sdk-go-v2, minio-go for MinIO-specific paths M low
queryd datafusion, arrow cmd/queryd duckdb/duckdb-go/v2 (cgo, official) HARD high — see §3
ingestd csv, json, lopdf, postgres cmd/ingestd stdlib encoding/csv, encoding/json, pdfcpu/pdfcpu, jackc/pgx/v5 L low
vectord hora, arrow, hnsw cmd/vectord coder/hnsw, apache/arrow-go/v18 L medium — re-validate HNSW recall
matrix indexer (emergent in Rust — mode.rs + build_*_corpus.ts + observer /relevance) scripts/build_*_corpus.ts, crates/gateway/src/v1/mode.rs, mcp-server/observer.ts internal/matrix/ + gateway routes (/v1/matrix/*) stdlib + vectord client L medium — see §3.4. Corpus-as-shard composer; relevance filter; strong-model downgrade gate; multi-corpus retrieve+merge. The learning-loop layer that lifts vectord from "static index" to "meta-index that learns from playbooks."
vectord-lance lance DROPPED n/a n/a n/a — Parquet+HNSW only
journald parquet, arrow cmd/journald apache/arrow-go/v18 M low
aibridge reqwest library net/http + connection pool · anthropics/anthropic-sdk-go available for direct Claude calls (currently routed via opencode) S low
chatd (Phase 4 — multi-provider LLM dispatcher) crates/gateway/src/v1/{ollama_cloud,openrouter,opencode}.rs cmd/chatd + internal/chat/ stdlib net/http only DONE medium — see §3.9. 5-provider routing (ollama / ollama_cloud / openrouter / opencode / kimi) by model-name prefix or :cloud suffix. Replaces the Rust gateway's v1:: adapters.
validator parquet, custom library apache/arrow-go/v18 parquet reader M low — port the 24 unit tests as gates
truth tomli, custom DSL library pelletier/go-toml/v2 M low
proto tonic-build proto/ + protoc-gen-go buf + protoc-gen-go-grpc S low
shared serde, anyhow library stdlib encoding/json, errors S low
ui dioxus, wasm REPLACED html/template + HTMX L medium — see §3
lance-bench criterion n/a — dropped with Lance n/a n/a n/a

Total Rust crate port effort: ~1218 engineer-weeks (34 months for one engineer; 68 weeks for two).


§2. Component port table — TypeScript surfaces

TS surface Current location Go target Library Effort Risk
mcp-server/index.ts Bun, :3700 cmd/mcp modelcontextprotocol/go-sdk (official Go SDK, v1.5.0, Google-collab) L medium — MCP semantics
mcp-server/observer.ts Bun, :3800 cmd/observer stdlib net/http, slog M low
mcp-server/tracing.ts Bun, Langfuse client library go.opentelemetry.io/otel + Langfuse Go client (or hand-roll) M low — Langfuse Go OSS support varies
auditor/*.ts TS, runs as systemd cmd/auditor stdlib + gitea API client L medium — auditor cross-lineage logic is intricate
tests/real-world/scrum_master_pipeline.ts TS, ad-hoc cmd/scrum stdlib L medium — chunking + embed + ladder logic
tests/real-world/scrum_applier.ts TS, ad-hoc cmd/scrum-apply stdlib + git CLI shell-out M medium
bot/propose.ts TS cmd/bot stdlib S low
Search demo HTML/JS static static (no port) n/a n/a n/a — copied as-is

Total TS port effort: ~610 engineer-weeks.


§3. Hard problem details

§3.1 — Query engine (DuckDB via cgo)

Library: github.com/duckdb/duckdb-go/v2 — official Go bindings via cgo. (Replaces the legacy marcboeker/go-duckdb, which was deprecated when the DuckDB team and Marc Boeker jointly relocated maintenance to the DuckDB org at v2.5.0. Migration is a one-line gofmt -r rewrite of import paths.) Current version v2.10502.0 (April 2026), DuckDB v1.5.2 compat. Statically links default extensions: ICU, JSON, Parquet, Autocomplete.

API shape (replaces the DataFusion SessionContext pattern):

db, _ := sql.Open("duckdb", "")
defer db.Close()
db.Exec("CREATE VIEW workers AS SELECT * FROM read_parquet('s3://bucket/workers/*.parquet')")
rows, _ := db.Query("SELECT role, count(*) FROM workers WHERE state='IL' GROUP BY role")

Acceptance gates:

  • G3.1.A — SELECT * FROM read_parquet('workers_500k.parquet') LIMIT 1 returns a row with the expected schema. Establishes Parquet read works.
  • G3.1.B — Hybrid SQL+vector query (the POST /vectors/hybrid surface) returns same workers as the Rust path on the same input, ranked the same way modulo embedding precision.
  • G3.1.C — Hot-cache merge-on-read: register a base table + a delta Parquet, query, observe both rows merged with the delta winning on conflict.

Fallback if cgo is rejected: run DuckDB as an external process (duckdb -json -c '...' shelled or HTTP via a thin Go wrapper). Adds operational surface; preserves SQL model.

§3.2 — HNSW index

Library: coder/hnsw — pure-Go HNSW, in-process. Supports add / delete / search / persist.

Open question: does coder/hnsw match the recall@10 we measured on the Rust hora path? Need a calibration test:

  • Rebuild lakehouse_arch_v1 (the 1086-chunk arch corpus) in Go.
  • Compare recall@10 on a fixed query set to the Rust baseline.
  • Acceptance: ≤2% drop or we switch library / parameters.

Persistence format: TBD — coder/hnsw has its own snapshot format; ADR equivalent of ADR-008 (Parquet for embeddings + sidecar HNSW file) needs revisiting in Go to confirm the sidecar format we ship.

Acceptance gates:

  • G3.2.A — Build HNSW from a Parquet of 100K vectors in <60s
  • G3.2.B — Search 100K vectors at k=10 in <50ms p50
  • G3.2.C — Recall@10 within 2% of Rust baseline on lakehouse_arch_v1

§3.4 — Matrix indexer (corpus-as-shard composer)

What it is. The matrix indexer is the layer above vectord that turns a fleet of single-corpus HNSW indexes into a learning meta-index. In the Rust system this is emergent — split between corpus builders (scripts/build_*_corpus.ts), the mode runner (crates/gateway/src/v1/mode.rs), the observer relevance endpoint (mcp-server/observer.ts), and the strong-model downgrade gate (mode.rs::execute). In Go we name it explicitly so future sessions don't reduce it to "vectord."

Why corpus-as-shard, not shard-by-id. Sharding a single index by hash(id) is a pure throughput hack with a recall tax. Sharding by corpus is the existing retrieval shape — lakehouse_arch_v1, lakehouse_symbols_v1, scrum_findings_v1, lakehouse_answers_v1, kb_team_runs_v1, successful_playbooks_live, etc. — each with distinct topology and a distinct retrieval intent. Concurrent Adds parallelize naturally because they go to different corpora; the matrix layer's job is to retrieve+merge across them, filter for relevance, and downgrade composition when strong models prove the matrix is anti-additive.

Components to port (in dependency order):

  1. Corpus builders — Go equivalents of scripts/build_*_corpus.ts. For each named corpus, a builder that reads source, splits into chunks per the corpus's schema, embeds via /v1/embed, and adds to a vectord index of the same name. Effort: M for the first builder, S for each subsequent.

  2. Multi-corpus retrieve+merge (internal/matrix/retrieve.go) — given a query and a list of corpus names, search each at top_k=K, merge by score, return top N globally. Match Rust's pattern: top_k=6 per corpus, top 8 globally before relevance filter.

  3. Relevance filter (internal/matrix/relevance.go) — port the threshold-based filter from mcp-server/observer.ts:/relevance. Drops adjacency-pollution chunks that share a corpus with the hit but aren't actually about the query. LH_RELEVANCE_FILTER / LH_RELEVANCE_THRESHOLD env knobs preserved.

  4. Strong-model downgrade gate (internal/matrix/downgrade.go) — port is_weak_model + the codereview_lakehouse → codereview_isolation flip from mode.rs::execute. Pass5 proved composed corpora lose 5/5 vs isolation on grok-4.1-fast (p=0.031); the gate is load-bearing for paid-model retrieval quality.

  5. Learning-loop integration — write outcomes back to a playbook-memory corpus (probably lakehouse_answers_v1 analogue). This is what makes the matrix INDEX a learning system rather than static retrieval. Per feedback_meta_index_vision.md: this is the north star, not the data structure.

Gateway routes: /v1/matrix/search (multi-corpus retrieve+merge), /v1/matrix/corpora (list + metadata), /v1/matrix/relevance (filter endpoint, used by both internal callers and external tooling).

Acceptance gates:

  • G3.4.A — /v1/matrix/search against ≥3 corpora returns merged top-N with corpus attribution per result.
  • G3.4.B — Relevance filter drops at least the threshold-margin chunks on a known adjacency-pollution test case.
  • G3.4.C — Strong-model downgrade gate flips composed→isolation when the model is non-weak; bypassed when caller sets force_mode.
  • G3.4.D — Concurrent Adds across N=4 corpora parallelize (no shared write-lock); Add throughput scales near-linearly with corpus count.

Persistence: each corpus's vectord index persists via the existing G1P LHV1 format. The matrix layer is stateless above that — corpus list lives in catalog, retrieval params in config.

Why this is its own §3.x: in Rust the matrix indexer was emergent and got reduced to "we have vectord" in earlier port-planning. The SPEC names it explicitly so the port preserves the multi-corpus retrieval shape AND the learning loop, not just the HNSW substrate.

§3.5 — Drift quantification (loop 5 of the PRD)

What it is. PRD names "drift" as the 5th loop: quantify when historical decisions stop matching current reality. Distinct from the rating+distillation loop because drift is MEASUREMENT, not LEARNING. The learning loop says "this match worked, remember it"; the drift loop says "this 4-month-old playbook entry — does it still match what the substrate would surface today?"

What's shipped (commit be65f85):

  • SCORER drift: re-runs current distillation.ScoreRecord over historical (EvidenceRecord, persisted_category) pairs and reports mismatches + a sorted shift matrix
  • internal/drift/drift.go — pure-function ComputeScorerDrift
  • 6 unit tests covering no-drift, shift detection, multi-shift sorted-by-count, includeEntries flag, empty input, scorer-version stamping

Future drift shapes (not shipped):

  • PLAYBOOK drift: re-run playbook queries through current matrix-search; recorded answer not in top-K = drift
  • EMBEDDING drift: KS-test on vector distribution at T1 vs T2
  • AUDIT BASELINE drift: matches Rust audit_baselines.jsonl longitudinal signal

Acceptance gates:

  • G3.5.A — A scorer-version bump triggers a non-zero Drifted count on a corpus of historical ScoredRuns where the new logic produces different categories than the persisted ones.
  • G3.5.B — ScorerDriftReport.ShiftMatrix is deterministic-ordered (count desc, ties broken alphabetically) so JSON output is stable across runs.

§3.6 — Staffing-side structured filter

What it is. Reality tests on the candidates + workers corpora (commits 0d1553c, a97881d) surfaced that pure semantic retrieval can't gate by location/status/availability — the matrix indexer returns Production Workers for a Forklift+OSHA-30 query because nomic-embed-text's geometry doesn't separate the role labels well. Structured filtering is the addressable piece: pre-filter the candidate set on metadata fields BEFORE semantic ranking.

What's shipped (commit b199093):

  • SearchRequest.MetadataFiltermap[string]any of metadata field → expected value (single value or list-of-values for OR semantics within a key, AND across keys)
  • Post-retrieval filter applied before top-K truncation in internal/matrix/retrieve.go
  • SearchResponse.MetadataFilterDropped for telemetry on filter aggressiveness
  • 7 unit tests covering nil filter, missing metadata, exact match, AND across keys, OR within list, bool match, malformed JSON

Deferred:

  • Pre-retrieval SQL gate via queryd (the actual hybrid). The post-retrieval filter is an MVP that helps when the candidate set is mostly relevant; for aggressive filters that drop most results, a SQL pre-filter into matrix retrieval would surface the right candidates with less wasted embedding work.
  • Filter language richer than equality (e.g. range, prefix, regex).

Acceptance gates:

  • G3.6.A — MetadataFilter: {"state": "IL"} against a mixed-state corpus drops every non-IL result; MetadataFilterDropped reports the count.
  • G3.6.B — List filter {"state": ["IL", "WI"]} keeps both states, drops the rest (OR within key).
  • G3.6.C — Multi-key filter is AND: a result missing any key is dropped, no exception.

§3.7 — Operational rating wiring

What it is. PRD loop 4 (rating + distillation) needs real inflows to be a learning system rather than a substrate. The playbook-record endpoint (06e7152) takes one (query, answer, score) per call; productizing it into actual signal sources is what makes the system get smarter with use.

What's shipped (commit 6392772):

  • POST /v1/matrix/playbooks/bulk — bulk-record N successes; per-entry success/failure response so callers can see which of a 4,701-row historical placement import succeeded vs which failed validation.
  • Single-record path from 06e7152 unchanged.

Deferred:

  • UI shim for click-tracking (no Go demo UI yet — the Bun demo at devop.live/lakehouse/ is still serving the public surface). When the Go UI lands or a feedback API is added to the Bun UI, every coordinator click → bulk-batched POST → playbook entry.
  • Negative feedback (this match didn't work). Currently only positive scores are recorded; a rejection signal would help the learning loop avoid pushing bad matches.
  • Time-decay on playbook scores so stale recommendations attenuate.

Acceptance gates:

  • G3.7.A — Bulk POST of N entries returns {recorded, failed, results[]} with per-entry IDs/errors, no single-entry failure aborting the batch.
  • G3.7.B — Each recorded entry surfaces in /v1/matrix/search with use_playbook=true after a re-query.

§3.8 — Observer-KB workflow runner (Archon-style multi-pass)

What it is. The architectural pattern documented in the Rust observer-kb branch (10 commits ahead of main, never merged) and proven by /home/profit/external/Archon's workflow engine. Multiple mode passes processing data, with each pass an objective measurement that contributes to the KB:

Raw data
   ↓ Mode: EXTRACT       structured facts/entities/relationships
   ↓ Mode: VALIDATOR     fact-check, confidence 1-10
   ↓ Mode: HALLUCINATION verify each claim, flag likely fabrications
   ↓ Mode: CONSENSUS     multiple passes until extraction converges
   ↓ Mode: REDTEAM       attack what survived, patch what fails
   ↓ Mode: PIPELINE      clean → Q&A structure → topic group → rank
   ↓ RENDER              curated doc anchored on questions

This is the orchestrator missing from §3.4 components 1-5: each SPEC §3.4 piece (relevance, downgrade, scorer, drift) is a "mode"; what's missing is the workflow engine that chains them.

Why it matters. Per the PRD's product vision: the observer should make actionable decisions based on watching what's successful. The workflow runner is how observers compose modes into multi-pass pipelines that score outcomes rigorously enough to feed the KB and inform the playbook substrate.

Reference materials on the system:

  • /home/profit/lakehouse/.archon/workflows/lakehouse-architect-review.yaml (committed 69919d9 in main) — proves Archon-via-Lakehouse works with a 3-node shape → weakness → improvement workflow
  • /home/profit/external/Archon — the upstream workflow engine (cloned 2026-04-26); packages/providers/src/community/pi/provider.ts has the local Lakehouse-routing mod committed locally as 3f2afc8 (not pushed to upstream coleam00/Archon)
  • Rust observer-kb branch (10 commits, +4338/-55506 LoC) — apps/observer-kb/docs/PRD.md documents the multi-pass architecture; scripts/{deep_analysis,extract_knowledge,process_knowledge}.py are the Python prototypes that proved it on real ChatGPT/Claude PDF data (496 topics, 300 decisions, 100 insights extracted)

Components to port (in dependency order):

  1. Workflow definition (internal/workflow/types.go) — YAML schema matching Archon's shape: name, description, provider, model, list of nodes each with id, prompt, allowed_tools, effort, idle_timeout, depends_on. The depends_on edges form a DAG; the runner resolves topologically.

  2. Node executor (internal/workflow/runner.go) — given a workflow and a starting context, walks the DAG, executes each node by dispatching to the configured backend (matrix.Search, distillation.ScoreRecord, drift.ComputeScorerDrift, or a generic prompt-against-LLM via gateway /v1/chat), captures per-node output, makes it available as $<node_id>.output in subsequent nodes.

  3. Provenance recording — every node execution lands an ObservedOp (via the observerd substrate from bc9ab93) with source: "workflow", the workflow name + node ID, input/output summaries, and timing. The ring buffer + JSONL log become the substrate for the rating+distillation loop's KB feed.

  4. Mode catalog (internal/workflow/modes.go) — registry of the modes the runner can dispatch to. Each mode is a Go function matching a uniform func(ctx, input map[string]any) (map[string]any, error) signature so workflows can compose them. Initial modes from §3.4: matrix.search, matrix.relevance, matrix.downgrade, playbook.record, playbook.lookup, distillation.score, drift.scorer. Plus llm.chat for free-form mode prompts.

  5. HTTP surfacePOST /v1/observer/workflow/run accepts a workflow YAML body + a starting context; returns the per-node results + the chain of ObservedOps generated. GET /v1/observer/workflow/list lists workflows in a known directory for operator discoverability.

Why integrate into observerd, not a new service. The observer is the system resource that watches and records. Workflows ARE observation patterns — multi-step processes whose every step is recorded. Putting the runner inside observerd keeps the "measurement → KB feed" wiring tight; a separate service would re-implement the recording layer.

Acceptance gates:

  • G3.8.A — Load a workflow YAML matching the Archon lakehouse-architect-review.yaml shape; runner executes the 3-node DAG topologically.
  • G3.8.B — Each node execution lands an ObservedOp with source: "workflow" and the node's input/output. Stats endpoint shows the workflow ops.
  • G3.8.C — A node referencing $<prior_node>.output in its prompt resolves correctly; missing reference is a clear error not a silent empty string.
  • G3.8.D — Mode catalog dispatches matrix.search invocation to the matrixd backend without going through HTTP (in-process function call when matrixd is co-resident).

Status: PORT TARGET, not yet started. SPEC commits the design; implementation is its own wave (estimated L effort given the DAG runner + mode dispatch + provenance recording).

§3.9 — chatd (multi-provider LLM dispatcher) — SHIPPED 2026-04-30

Status: done at commit 05273ac (Phase 4 wave) + scrum-hardened at 0efc736. Composite port: 1,624 LoC, 19+ tests, 6/6 chatd_smoke.

What: cmd/chatd on :3220 routes POST /chat to a provider selected by model-name prefix or :cloud suffix:

ollama/<m>            → local Ollama at :11434 (no auth)
ollama_cloud/<m>      → ollama.com /api/generate (Bearer)
<m>:cloud             → ollama_cloud (suffix variant)
openrouter/<v>/<m>    → openrouter.ai (OpenAI-compat, Bearer)
opencode/<m>          → opencode.ai/zen/v1 (OpenAI-compat, Bearer)
kimi/<m>              → api.kimi.com/coding/v1 (OpenAI-compat, Bearer)
bare names            → ollama (default)

Provider key resolution: env var first (OPENROUTER_API_KEY, OPENCODE_API_KEY, KIMI_API_KEY, OLLAMA_CLOUD_KEY); then /etc/lakehouse/<provider>.env fallback (mode 0600). Empty key → provider stays unregistered (404 at first call instead of 503).

Companion to the model tier registry in lakehouse.toml [models] (local_fast / local_judge / cloud_judge / frontier_review / etc.), which maps tier names to model IDs. Callers reference cfg.Models.LocalJudge instead of literal strings. Bumping a tier is a 1-line config edit.

Quirks captured by today's scrum:

  • Request.Temperature is *float64 (pointer) — Anthropic 4.7 (via OpenCode) rejects the field entirely with "temperature is deprecated." Pointer lets us omit when caller didn't set explicitly.
  • Local Ollama defaults think=false — qwen3.5:latest is reasoning- capable but the inner-loop hot path wants direct answers, not reasoning traces consuming the token budget.

Replaces the Rust adapters:

  • crates/gateway/src/v1/ollama_cloud.rsinternal/chat/ollama_cloud.go
  • crates/gateway/src/v1/openrouter.rsinternal/chat/openai_compat.go (shared with opencode + kimi)
  • crates/gateway/src/v1/opencode.rs → same shared helper
  • crates/aibridge/*internal/chat/ (cleaner abstraction)

Reusable downstream: scripts/scrum_review.sh runs a 3-lineage cross-review (Opus + Kimi + Qwen3-coder) by POST-ing each diff to chatd's /v1/chat. Same vehicle the harness's own scrum-hardening used to find its own bugs.

§3.10 — local-review-harness (sibling tool, separate repo)

Where: git.agentview.dev/profit/local-review-harness (also SMB-mounted at /home/profit/share/local-review-harness-full-md/).

What: a local-first code review harness — walks a target repo, runs evidence-bearing static checks (12 analyzers covering hardcoded paths, raw SQL, wildcard CORS, secrets, exec/spawn, large files, TODO/FIXME, missing tests, .env files committed, exposed mutation endpoints, hardcoded private IPs), produces Scrum-style markdown reports + JSON receipts. No cloud deps. Single static Go binary.

Status: Phase A (skeleton) + Phase B (MVP — static-only path) shipped at first commit f3ee472. 5 acceptance gates green plus self-review (the harness reviews its own repo).

Phases CE pending: local-Ollama LLM review, validation cross- check, append-only .memory/, diff/rules subcommands.

Why a sibling tool, not a Lakehouse module: PROMPT.md "Strategic Goal" — the harness eventually plugs into OpenClaw / MCP tools / Lakehouse memory / playbook sealing / observer review loop. But first it has to be reliable, inspectable, and evidence-driven on its own. Lakehouse-Go integration is post-Phase-E (probably E+1: write the harness's findings into Lakehouse's playbook substrate via /v1/matrix/playbooks/record).

Cross-pollination opportunities (post-MVP):

  • Replace internal/llm/ollama.go in the harness with a thin client pointed at chatd's /v1/chat — frontier judges become a config toggle.
  • Feed harness findings into Lakehouse-Go's pathway memory as a drift signal (which static checks fired this run vs last).
  • Use the harness's .memory/known-risks.json as a corpus the matrix indexer can retrieve from when the same risk pattern appears.

§3.3 — UI (HTMX)

Approach: server-rendered Go templates using html/template, HTMX for partial-page swaps, Alpine.js for client-side interactivity where needed. Single binary serves API + UI.

Acceptance gates:

  • G3.3.A — Ask tab: type natural-language question, get answer from RAG endpoint, render in-page without full reload
  • G3.3.B — Explore tab: paginated dataset list with hot-swap badge rendering
  • G3.3.C — SQL tab: textarea → submit → tabular result rendered in-page
  • G3.3.D — System tab: live tail of /storage/errors and /hnsw/trials via HTMX polling

Fallback if HTMX feels limiting: split repo golangLAKEHOUSE-ui with Vite + React, served as static files by Go gateway. Costs an extra repo + build chain.

§3.4 — Pathway memory port

Constraint: the Rust pathway_memory and TS implementations were byte-matching by ADR-021. The byte contract was verified by running both implementations on the same input tokens and asserting matching bucket indices.

Go port plan:

  • Port the 32-bucket SHA256-keyed token hash exactly. Verify on a golden input that Go produces the same bucket vector as Rust.
  • Port the JSON state file format verbatim — the existing 88 traces in data/_pathway_memory/state.json reload as-is into the Go implementation.
  • Port the matrix-correctness layer (ADR-021's SemanticFlag, BugFingerprint, TypeHint) — these are pure value types, trivially portable.

Acceptance gates:

  • G3.4.A — Load existing state.json, run replay on the same 11 prior successful pathways, all 11 succeed (matching the Rust 11/11 baseline).
  • G3.4.B — Bucket vector for a fixed test input byte-matches the Rust output.

§4. Phase plan

Phase G0 — Skeleton (Week 13)

Scope: smallest end-to-end ingest + query path working in Go.

Component Deliverable
cmd/gateway HTTP on :3100, /health, /v1/chat proxy stub
cmd/catalogd In-memory registry + Parquet manifest persistence
cmd/storaged Single-bucket S3 / local FS, no error journal yet
cmd/ingestd CSV → Parquet, schema inference, register-on-ingest
cmd/queryd DuckDB-backed POST /sql endpoint

Acceptance: upload a CSV via POST /ingest, query it via POST /sql with a SELECT, get rows back. Single-bucket. No vector, no profile, no UI.

Phase G1 — Vector + RAG (Week 46)

Component Deliverable
cmd/vectord Embed-on-ingest (calls Python sidecar), HNSW build, POST /search
cmd/gateway Add POST /rag (embed → search → retrieve → generate via aibridge)
cmd/aibridge HTTP client to existing Python sidecar

Acceptance: ingest 15K resumes (the original Phase 7 fixture), ask "find me a forklift operator with OSHA-10 in IL", get ranked results with LLM-generated explanation grounded in the retrieved chunks.

Phase G2 — Federation + profiles (Week 78)

Component Deliverable
cmd/storaged Multi-bucket registry, rescue bucket, error journal at primary://_errors/
Profile system Per-reader profile bound to bucket + vector index
Hot-swap Atomic pointer swap for index generations

Acceptance: two profiles bound to two buckets, queries scoped correctly, hot-swap a vector index without query interruption, rollback works.

Phase G3 — Pathway memory + distillation (Week 911)

Component Deliverable
cmd/vectord Pathway memory module ported, 88 traces reloaded
Distillation pipeline SFT export, contamination firewall, scorer
Audit baselines audit_baselines.jsonl longitudinal signal port

Acceptance: replay 11 prior successful pathways, all 11 succeed. Re-run distillation acceptance on the frozen fixture set, 22/22 pass.

Phase G4 — TS surfaces → Go (Week 1214)

Component Deliverable
cmd/mcp MCP server (replaces Bun) — /v1/chat, intelligence endpoints
cmd/observer Autonomous iteration loop, op recording
cmd/auditor PR audit pipeline (kimi/haiku/opus rotation)
cmd/scrum Scrum master pipeline (replaces TS)

Acceptance: open a test PR, auditor cycles within 90s, emits verdict to data/_auditor/kimi_verdicts/, behavior matches Rust+TS era within tolerance.

Phase G5 — UI + demo parity (Week 1516)

Component Deliverable
cmd/gateway Serves HTMX templates + static demo HTML
Demo at devop.live/lakehouse/ Parity with current Bun demo
Staffer console at /console Parity

Acceptance: devop.live/lakehouse/ cuts over from Bun to Go gateway. Section ① / ② / ③ all render. Compact contract cards still expand with Project Index. Fill-probability bars still paint.


§5. Repo layout

golangLAKEHOUSE/
├── docs/
│   ├── PRD.md                    ← this PRD
│   ├── SPEC.md                   ← this spec
│   ├── DECISIONS.md              ← Go-era ADRs (start fresh, reference Rust ADRs by number)
│   └── ADR-XXX-*.md              ← per-ADR detail
├── cmd/
│   ├── gateway/                  ← main HTTP/gRPC ingress
│   ├── catalogd/
│   ├── storaged/
│   ├── queryd/
│   ├── ingestd/
│   ├── vectord/
│   ├── journald/
│   ├── mcp/
│   ├── observer/
│   ├── auditor/
│   └── scrum/
├── internal/                     ← shared packages, not exported
│   ├── aibridge/
│   ├── validator/
│   ├── truth/
│   ├── shared/
│   ├── proto/                    ← generated protobuf
│   └── pathway/
├── pkg/                          ← public Go packages (none initially)
├── web/                          ← UI (HTMX templates + static)
│   ├── templates/
│   └── static/
├── scripts/                      ← cold-start, smoke, distill scripts
├── tests/                        ← golden files, integration tests
├── go.mod
├── go.sum
└── README.md

Single Go module. All commands and internal packages live under golangLAKEHOUSE/. No nested modules unless a package needs an independent release cadence (none expected).

Build: go build ./cmd/... produces all binaries.


§6. Migration data plan

What ports verbatim

  • Parquet datasets at data/datasets/*.parquet — read by Go directly.
  • Catalog manifests — Parquet, ports as data not code.
  • Pathway memory state — JSON, ports if §3.4 byte-matching gate passes.

What rebuilds

  • HNSW indexes — rebuild from Parquet embeddings on first Go startup.
  • Auditor verdicts on PRs — old PRs won't be re-audited; lineage starts fresh on the new repo's PRs.

What's archived

  • The Rust crates/ tree — preserved in the original repo at the cutover commit, tagged pre-go-rewrite-2026-04-28 for reference.
  • TS surfaces (mcp-server/, auditor/, etc.) — preserved in the original repo at the same tag.
  • Distillation v1.0.0 substrate (tag distillation-v1.0.0, e7636f2) — kept as the historical reference; Go re-implementation ports the LOGIC but not the bit-identical-reproducibility property unless an ADR re-establishes it.

What's discarded

  • crates/vectord-lance/ (Lance backend, see PRD §Hard problems §2)
  • crates/lance-bench/ (criterion benchmarks specific to Lance)

§7. Acceptance: when is the rewrite done?

The Go Lakehouse reaches feature parity when:

  1. All 12 Rust PRD invariants hold (object-storage source of truth, catalog metadata authority, idempotent ingest, hot-swap atomicity, profiles, etc.).
  2. The 16 distillation acceptance gates pass (re-run ./scripts/distill audit-full against the Go pipeline).
  3. The 22/22 acceptance fixtures from tests/fixtures/distillation/ acceptance/ pass under the Go implementation.
  4. The 145 unit tests of distillation v1.0.0 are ported and pass.
  5. devop.live/lakehouse/ demo cuts over to Go gateway with no visible UI regressions.
  6. Auditor emits Kimi/Haiku/Opus verdicts on a test PR, matching the cross-lineage rotation behavior.
  7. The 88 pathway traces replay with 11/11 prior successes reproduced.

At that point the Rust repo enters maintenance-only mode (security fixes), and the Go repo becomes the live system.


§8. Ratified — Phase G0 unblocked (2026-04-28, J)

# Decision Spec impact
1 DuckDB via cgo (marcboeker/go-duckdb) §3.1 option A — proceed
2 HTMX + html/template + Alpine.js §3.3 option A — proceed
3 git.agentview.dev/profit/golangLAKEHOUSE repo location locked
4 Distillation rebuilt in Go (no bit-identical port) §6 — port logic, not fixtures
5 Pathway memory starts empty; old traces noted §3.4 G3.4.A is now "build initial state from scratch in Phase G3"; G3.4.B (byte-match) preserved as the porting correctness gate when the algorithm is reimplemented
6 Auditor longitudinal signal restarts new audit_baselines.jsonl lineage starts on first Go-era PR

See docs/DECISIONS.md ADR-001 for full rationale and docs/RUST_PATHWAY_MEMORY_NOTE.md for where the legacy 88 traces live.

Phase G0 is now unblocked. Next step: bootstrap the Go module skeleton + push to Gitea, then begin §4 Phase G0 implementation.