The click that just happened
shapes the very next request.
A user taps "like." The click row and the updated preference vector commit in one atomic
write, the next retrieval already ranks with them, and /watch pushes
the change to every subscriber the instant it lands.
> POST /v1/tenants/:t/batch
> { "ops": [
> { "sql": "INSERT INTO clicks …" },
> { "vector": { "schema": "user_pref",
> "put": [{ "id": "u_84", "embedding": […] }] } }
> ] }
committed atomically
row clicks/… ✓
vector user_pref/u_84 ✓
/watch data: {"op":"put","schema":"user_pref", …}
next search ranks with the vector that just committed Two systems means a window.
Write the click to your OLTP database, push the embedding update to your vector DB, query the vector DB on the next request — and during the window between the two writes, retrieval is blind to the click. The user just told you what they want, and the system serves the page as if they hadn't.
For batch jobs that doesn't matter. For personalisation it's the whole product.
In between: a window where retrieval doesn't see the click.
Row and vector land together. There is no window, by construction.
Update and serve in the same write.
The profile, its embedding and its events are shapes on one substrate — not three databases held together by a pipeline. A single write moves all of them forward at once.
One batch write carries the click row and the updated preference vector. Both commit together — either every surface sees the new state, or none does. No sync job runs afterwards, because there is nothing left to sync.
Read-your-writes: the write is durable and visible immediately. The next personalised kNN query — on any connection — ranks with the vector that just committed. No 'eventually consistent' explainer in your runbook.
Every write puts an event on the SSE stream by construction — including the new vector. Your ranking service, your session cache, your UI: all of them hear about the click the moment it commits.
> POST /v1/tenants/:t/vector/search
> {
> "schema": "items",
> "k": 12,
> "dense": { "query": "@user_pref:u_84",
> "metric": "cosine" }
> }
ranked with u_84's new preference vector
no refresh, no reindex, no wait > POST /v1/tenants/:t/ask
> { "q": "show me things like the one I just liked" }
works — the substrate just saw the like
/ask routes the retrieval; the answer
always sees the latest state Natural-language routing is /ask — the question compiles to a plan and runs through the same security boundary as any other query.
Every service hears the click the moment it commits.
/watch is one HTTP GET: an SSE stream of every write that matches your prefix, the moment it lands. Every write puts an event on the stream by construction — there is no separate CDC pipeline, no Debezium, no Kafka, no replication lag between "the click committed" and "the ranking service knows."
Subscribe to user_pref/ and see every profile
change; filtering is server-side, so your client never sees other shapes. Client disconnect
releases the subscription slot the same instant.
> curl -N \
> -H 'Authorization: Bearer …' \
> 'https://<tenant>.db.originchain.ai/v1/tenants/:t/watch?prefix=user_pref/'
data: {"op":"put","schema":"user_pref","row":{…}}
data: {"op":"put","schema":"user_pref","row":{…}}
data: {"op":"delete","schema":"user_pref","key":"u_12"}
…every matching write, as it lands The guarantee personalisation actually needs.
Rows, vector embeddings and events land on the same write-ahead log in a single append. A query in another connection cannot see a state where the click row exists but the vector index hasn't caught up — it is structurally impossible to observe a half-written profile. That is the property the "why did it recommend that?" ticket class depends on, and it comes from the substrate's design, not from a reconciler that usually keeps up.
A multi-store stack cannot make this promise without a saga, a reconciler, and a worker that detects skew — and the cross-system drift is exactly what produces the recommendations that lag a session behind the user. The full mechanics are on the architecture page.
The write is durable and visible immediately. No eventual-consistency window before your next read sees it.
Preference vector + click row + audit hit commit together. Either all land, or none.
The next personalised retrieval sees the new state — filters and vector search run against the same committed snapshot.