# Knowledge Graphs: Model the Relationships

> Build log · Month 7 of 12

the shared idea under CalDesk's "healthier swaps" and Aether's DeFi graph. Ontology design, entity resolution, GraphRAG, and why the questions worth asking are relational.

Day 176–200 · 25 posts · Building in public as @caelum0x4

**Day 176** — Knowledge-graph month. Two apps, one idea. CalDesk models food ("healthier swap for this?"). Aether models DeFi tokens ("is this the real USDC?"). Both are graphs because the valuable questions are about *relationships* — edges, not columns. Same spine, two domains.

**Day 177** — Ontology first, always. Before a single node, I define entity and relation types. CalDesk: Food, Nutrient, Category; relations "is-source-of," "is-swap-for," "belongs-to." Get the schema wrong and every query fights you forever. The ontology is the constitution.

**Day 178** — CalDesk's killer feature is "healthier swaps": log white rice, get brown rice or quinoa — same role in a meal, better Nutri-Score. That's a graph traversal: find foods sharing the "meal role" edge, rank by nutrition delta. A flat food table can't answer this.

**Day 179** — Nutri-Score as a ranking function: it compresses a food's nutrition into a comparable grade (A–E). The swap engine finds foods in the same category and surfaces the better grades. The graph finds *candidates*; Nutri-Score *ranks* them. Structure plus scoring.

**Day 180** — Entity resolution is the unglamorous heart of any graph. "Greek yogurt," "yogurt, greek," "yoghurt (greek)" are one node, not three. Get dedup wrong and your graph fragments into synonyms that don't connect. I spent more time merging than on any query.

**Day 181** — Thread: how I build a knowledge graph that's useful, not a demo.
  - Ontology first: define entity + relation types before ingesting anything.
  - Extraction: pull entities + relations from your source into that schema.
  - Resolution: merge duplicates ruthlessly. "Greek yogurt" and "yogurt, greek" are ONE node.
  - Serving: GraphRAG — retrieve a relevant subgraph, feed it to the LLM as grounded context.
  - Payoff: relational questions a table can't answer — "healthier swap," "same deployer as a scam."

**Day 182** — GraphRAG is why CalDesk's AI doesn't hallucinate your nutrition. Instead of asking the LLM "is quinoa healthier than rice" from memory, I retrieve the relevant food subgraph and feed it as context. The LLM reasons over *facts I gave it*, not vibes it half-remembers.

**Day 183** — The rule that keeps CalDesk honest: the LLM never invents a number. Macros, Nutri-Score, swap candidates all come from the graph. The AI's job is phrasing and reasoning over retrieved facts, never being the source of them. Hallucinated calories are worse than no app.

**Day 184** — Aether's DeFi graph, same machinery: nodes are tokens/pools/protocols, edges are "paired-with," "deployed-by," "wrapped-version-of." "Is this token legit?" becomes "does its contract match the canonical node, and who deployed it?" Fraud detection is a graph query.

**Day 185** — Deployer-reputation caught a scam token this month. The edge "deployed-by → address that also deployed 3 known scams" is a one-hop traversal. No price data, no ML model, just graph structure. Sometimes the fanciest-sounding feature is a well-designed JOIN.

**Day 186** — Thread: ontology mistakes I made building the graphs, and the fixes.
  - Modeled "Nutrient" as a property of Food. Couldn't ask "foods high in iron." → made it a node.
  - Left Nutri-Score as a bare number. → gave it provenance so I can trace and fix it.
  - Let swap edges cross categories (apple for rice). → constrained swaps to same role.
  - Treated every extracted edge as certain. → added confidence scores; suppress the low ones.
  - Rule: if you'll ever query FROM a thing, it's a node, not a field.

**Day 187** — Graph storage decision: I don't run a heavyweight graph DB for these. The graphs are big-ish but bounded, so I model nodes/edges in the existing store with good indexes and traverse in code. A dedicated graph DB is a dependency I didn't need yet. Right-size the tool.

**Day 188** — The "swap" edge needed a quality gate: a healthy swap must preserve the food's *role*. Suggesting an apple for rice is nutritionally "better" and culinarily insane. So swap edges are constrained to same-category. Unconstrained graphs suggest nonsense confidently.

**Day 189** — Freshness problem: the DeFi graph decays fast — new tokens, pools, scams daily. A stale fraud graph is a dangerous one. I built incremental ingestion that updates edges continuously rather than rebuilding nightly. A knowledge graph is a living thing or a liability.

**Day 190** — Opinion: "add AI" is usually the wrong instinct. "Add structure, then point AI at it" is almost always right. The graph makes the AI trustworthy. Without grounding, an LLM in a nutrition app is a confident liar with a nice UI. Structure turns a chatbot into a product.

**Day 191** — Provenance matters: every fact should know where it came from. When CalDesk says a food's Nutri-Score, I can trace it to the source. When something's wrong (and it will be), provenance is how you fix the *cause*, not the symptom. Facts need footnotes.

**Day 192** — Edge weights turned "healthier swap" from a list into a ranking. Not all swaps are equal — some a big nutrition win, some marginal, some the user won't accept. Weighting by nutrition delta *and* similarity gives an ordered list. Unweighted graphs give a pile.

**Day 193** — Hard UX lesson: users don't want to see the graph. They want the *answer* it produces. I built a beautiful node-link visualization and nobody cared; they wanted "eat this instead." The graph is infrastructure, not interface. Hide the machinery, ship the outcome.

**Day 194** — Cross-app reuse win: CalDesk's graph engine and Aether's share the same core traversal + resolution code, specialized by ontology. Two wildly different domains, one graph spine. The studio thesis in miniature — build the general engine once, specialize cheaply.

**Day 195** — Confidence scores on extracted relations save you from your own graph. Not every edge is certain. Low-confidence swaps get suppressed or labeled. Treating every edge as gospel is how you confidently recommend nonsense. The graph should know what it doesn't know.

**Day 196** — Month 7 lesson: knowledge graphs aren't academic. They're how you answer *relational* questions — swaps, lookalikes, "same deployer as" — a flat schema can't touch, and how you ground an LLM so it stops lying. Structure is a feature users feel without seeing.

**Day 197** — Honest status: CalDesk and Aether are the smartest apps in the fleet and still early on users. Great engineering keeps not being the bottleneck. 197 days in, the graph, the wallet, the real-time system — none of it matters until someone installs it.

**Day 198** — Next month I finally stop dodging money. RevenueCat, AdMob, paywall design, free-trial psychology, and — if it happens — the single most important tweet of the year: the first paying customer. Time to find out if any of this can pay for itself.

**Day 199** — Before I leave graphs: the same GraphRAG pattern keeps GhostOS (the assistant) grounded — retrieve context, reason over it, never freewheel. The knowledge-graph spine quietly touches half the fleet. Build the general capability, harvest it everywhere.

**Day 200** — Day 200. Two hundred days in public, and the through-line is loud: I can build almost anything, and I've sold almost nothing. Not failure, a diagnosis. The disease is distribution. Next month I start treating it directly, beginning with the money.

← [Month 6: Huddle: Real-Time Is Hard](https://ereboslab.com/blog/month-6-huddle-real-time-is-hard/) · [Month 8: Making It Pay](https://ereboslab.com/blog/month-8-making-it-pay/) →

[← All build-log months](https://ereboslab.com/blog/)
